• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14r"""List entry point.
15
16List will handle all the logic related to list a local/remote instance
17of an Android Virtual Device.
18"""
19
20from __future__ import print_function
21import getpass
22import logging
23import os
24
25from acloud import errors
26from acloud.internal import constants
27from acloud.internal.lib import auth
28from acloud.internal.lib import gcompute_client
29from acloud.internal.lib import utils
30from acloud.list import instance
31from acloud.public import config
32
33
34logger = logging.getLogger(__name__)
35
36_COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
37
38
39def _ProcessInstances(instance_list):
40    """Get more details of remote instances.
41
42    Args:
43        instance_list: List of dicts which contain info about the remote instances,
44                       they're the response from the GCP GCE api.
45
46    Returns:
47        instance_detail_list: List of instance.Instance() with detail info.
48    """
49    return [instance.RemoteInstance(gce_instance) for gce_instance in instance_list]
50
51
52def _SortInstancesForDisplay(instances):
53    """Sort the instances by connected first and then by age.
54
55    Args:
56        instances: List of instance.Instance()
57
58    Returns:
59        List of instance.Instance() after sorted.
60    """
61    instances.sort(key=lambda ins: ins.createtime, reverse=True)
62    instances.sort(key=lambda ins: ins.AdbConnected(), reverse=True)
63    return instances
64
65
66def PrintInstancesDetails(instance_list, verbose=False):
67    """Display instances information.
68
69    Example of non-verbose case:
70    [1]device serial: 127.0.0.1:55685 (ins-1ff036dc-5128057-cf-x86-phone-userdebug)
71    [2]device serial: 127.0.0.1:60979 (ins-80952669-5128057-cf-x86-phone-userdebug)
72    [3]device serial: 127.0.0.1:6520 (local-instance)
73
74    Example of verbose case:
75    [1] name: ins-244710f0-5091715-aosp-cf-x86-phone-userdebug
76        IP: None
77        create time: 2018-10-25T06:32:08.182-07:00
78        status: TERMINATED
79        avd type: cuttlefish
80        display: 1080x1920 (240)
81
82    [2] name: ins-82979192-5091715-aosp-cf-x86-phone-userdebug
83        IP: 35.232.77.15
84        adb serial: 127.0.0.1:33537
85        create time: 2018-10-25T06:34:22.716-07:00
86        status: RUNNING
87        avd type: cuttlefish
88        display: 1080x1920 (240)
89
90    Args:
91        verbose: Boolean, True to print all details and only full name if False.
92        instance_list: List of instances.
93    """
94    if not instance_list:
95        print("No remote or local instances found")
96
97    for num, instance_info in enumerate(instance_list, 1):
98        idx_str = "[%d]" % num
99        utils.PrintColorString(idx_str, end="")
100        if verbose:
101            print(instance_info.Summary())
102            # add space between instances in verbose mode.
103            print("")
104        else:
105            print(instance_info)
106
107
108def GetRemoteInstances(cfg):
109    """Look for remote instances.
110
111    We're going to query the GCP project for all instances that created by user.
112
113    Args:
114        cfg: AcloudConfig object.
115
116    Returns:
117        instance_list: List of remote instances.
118    """
119    credentials = auth.CreateCredentials(cfg)
120    compute_client = gcompute_client.ComputeClient(cfg, credentials)
121    filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser())
122    all_instances = compute_client.ListInstances(instance_filter=filter_item)
123
124    logger.debug("Instance list from: (filter: %s\n%s):",
125                 filter_item, all_instances)
126
127    return _SortInstancesForDisplay(_ProcessInstances(all_instances))
128
129
130def _GetLocalCuttlefishInstances(id_cfg_pairs):
131    """Look for local cuttelfish instances.
132
133    Gather local instances information from cuttlefish runtime config.
134
135    Args:
136        id_cfg_pairs: List of tuples. Each tuple consists of an instance id and
137                      a config path.
138
139    Returns:
140        instance_list: List of local instances.
141    """
142    local_instance_list = []
143    for ins_id, cfg_path in id_cfg_pairs:
144        ins_lock = instance.GetLocalInstanceLock(ins_id)
145        if not ins_lock.Lock():
146            logger.warning("Cuttlefish Instance %d is locked by another "
147                           "process.", ins_id)
148            continue
149        try:
150            if not os.path.isfile(cfg_path):
151                continue
152            ins = instance.LocalInstance(cfg_path)
153            if ins.CvdStatus():
154                local_instance_list.append(ins)
155            else:
156                logger.info("Cvd runtime config is found at %s but instance "
157                            "%d is not active.", cfg_path, ins_id)
158        finally:
159            ins_lock.Unlock()
160    return local_instance_list
161
162
163def GetActiveCVD(local_instance_id):
164    """Check if the local AVD with specific instance id is running
165
166    This function does not lock the instance.
167
168    Args:
169        local_instance_id: Integer of instance id.
170
171    Return:
172        LocalInstance object.
173    """
174    cfg_path = instance.GetLocalInstanceConfig(local_instance_id)
175    if cfg_path:
176        ins = instance.LocalInstance(cfg_path)
177        if ins.CvdStatus():
178            return ins
179    cfg_path = instance.GetDefaultCuttlefishConfig()
180    if local_instance_id == 1 and cfg_path:
181        ins = instance.LocalInstance(cfg_path)
182        if ins.CvdStatus():
183            return ins
184    return None
185
186
187def GetLocalInstances():
188    """Look for local cuttleifsh and goldfish instances.
189
190    Returns:
191        List of local instances.
192    """
193    # Running instances on local is not supported on all OS.
194    if not utils.IsSupportedPlatform():
195        return []
196
197    id_cfg_pairs = instance.GetAllLocalInstanceConfigs()
198    return (_GetLocalCuttlefishInstances(id_cfg_pairs) +
199            instance.LocalGoldfishInstance.GetExistingInstances())
200
201
202def GetInstances(cfg):
203    """Look for remote/local instances.
204
205    Args:
206        cfg: AcloudConfig object.
207
208    Returns:
209        instance_list: List of instances.
210    """
211    return GetRemoteInstances(cfg) + GetLocalInstances()
212
213
214def ChooseInstancesFromList(instances):
215    """Let user choose instances from a list.
216
217    Args:
218        instances: List of Instance objects.
219
220    Returns:
221         List of Instance objects.
222    """
223    if len(instances) > 1:
224        print("Multiple instances detected, choose any one to proceed:")
225        return utils.GetAnswerFromList(instances, enable_choose_all=True)
226    return instances
227
228
229def ChooseInstances(cfg, select_all_instances=False):
230    """Get instances.
231
232    Retrieve all remote/local instances and if there is more than 1 instance
233    found, ask user which instance they'd like.
234
235    Args:
236        cfg: AcloudConfig object.
237        select_all_instances: True if select all instances by default and no
238                              need to ask user to choose.
239
240    Returns:
241        List of Instance() object.
242    """
243    instances = GetInstances(cfg)
244    if not select_all_instances:
245        return ChooseInstancesFromList(instances)
246    return instances
247
248
249def ChooseOneRemoteInstance(cfg):
250    """Get one remote cuttlefish instance.
251
252    Retrieve all remote cuttlefish instances and if there is more than 1 instance
253    found, ask user which instance they'd like.
254
255    Args:
256        cfg: AcloudConfig object.
257
258    Raises:
259        errors.NoInstancesFound: No cuttlefish remote instance found.
260
261    Returns:
262        list.Instance() object.
263    """
264    instances_list = GetCFRemoteInstances(cfg)
265    if not instances_list:
266        raise errors.NoInstancesFound(
267            "Can't find any cuttlefish remote instances, please try "
268            "'$acloud create' to create instances")
269    if len(instances_list) > 1:
270        print("Multiple instances detected, choose any one to proceed:")
271        instances = utils.GetAnswerFromList(instances_list,
272                                            enable_choose_all=False)
273        return instances[0]
274
275    return instances_list[0]
276
277
278def _FilterInstancesByNames(instances, names):
279    """Find instances by names.
280
281    Args:
282        instances: Collection of Instance objects.
283        names: Collection of strings, the names of the instances to search for.
284
285    Returns:
286        List of Instance objects.
287
288    Raises:
289        errors.NoInstancesFound if any instance is not found.
290    """
291    instance_map = {inst.name: inst for inst in instances}
292    found_instances = []
293    missing_instance_names = []
294    for name in names:
295        if name in instance_map:
296            found_instances.append(instance_map[name])
297        else:
298            missing_instance_names.append(name)
299
300    if missing_instance_names:
301        raise errors.NoInstancesFound("Did not find the following instances: %s" %
302                                      " ".join(missing_instance_names))
303    return found_instances
304
305
306def GetLocalInstanceLockByName(name):
307    """Get the lock of a local cuttelfish or goldfish instance.
308
309    Args:
310        name: The instance name.
311
312    Returns:
313        LocalInstanceLock object. None if the name is invalid.
314    """
315    cf_id = instance.GetLocalInstanceIdByName(name)
316    if cf_id is not None:
317        return instance.GetLocalInstanceLock(cf_id)
318
319    gf_id = instance.LocalGoldfishInstance.GetIdByName(name)
320    if gf_id is not None:
321        return instance.LocalGoldfishInstance.GetLockById(gf_id)
322
323    return None
324
325
326def GetLocalInstancesByNames(names):
327    """Get local cuttlefish and goldfish instances by names.
328
329    This method does not raise an error if it cannot find all instances.
330
331    Args:
332        names: Collection of instance names.
333
334    Returns:
335        List consisting of LocalInstance and LocalGoldfishInstance objects.
336    """
337    id_cfg_pairs = []
338    for name in names:
339        ins_id = instance.GetLocalInstanceIdByName(name)
340        if ins_id is None:
341            continue
342        cfg_path = instance.GetLocalInstanceConfig(ins_id)
343        if cfg_path:
344            id_cfg_pairs.append((ins_id, cfg_path))
345        if ins_id == 1:
346            cfg_path = instance.GetDefaultCuttlefishConfig()
347            if cfg_path:
348                id_cfg_pairs.append((ins_id, cfg_path))
349
350    gf_instances = [ins for ins in
351                    instance.LocalGoldfishInstance.GetExistingInstances()
352                    if ins.name in names]
353
354    return _GetLocalCuttlefishInstances(id_cfg_pairs) + gf_instances
355
356
357def GetInstancesFromInstanceNames(cfg, instance_names):
358    """Get instances from instance names.
359
360    Turn a list of instance names into a list of Instance().
361
362    Args:
363        cfg: AcloudConfig object.
364        instance_names: list of instance name.
365
366    Returns:
367        List of Instance() objects.
368
369    Raises:
370        errors.NoInstancesFound: No instances found.
371    """
372    return _FilterInstancesByNames(
373        GetLocalInstancesByNames(instance_names) + GetRemoteInstances(cfg),
374        instance_names)
375
376
377def FilterInstancesByAdbPort(instances, adb_port):
378    """Find an instance by adb port.
379
380    Args:
381        instances: Collection of Instance objects.
382        adb_port: int, adb port of the instance to search for.
383
384    Returns:
385        List of Instance() objects.
386
387    Raises:
388        errors.NoInstancesFound: No instances found.
389    """
390    all_instance_info = []
391    for instance_object in instances:
392        if instance_object.adb_port == adb_port:
393            return [instance_object]
394        all_instance_info.append(instance_object.fullname)
395
396    # Show devices information to user when user provides wrong adb port.
397    if all_instance_info:
398        hint_message = ("No instance with adb port %d, available instances:\n%s"
399                        % (adb_port, "\n".join(all_instance_info)))
400    else:
401        hint_message = "No instances to delete."
402    raise errors.NoInstancesFound(hint_message)
403
404
405def GetCFRemoteInstances(cfg):
406    """Look for cuttlefish remote instances.
407
408    Args:
409        cfg: AcloudConfig object.
410
411    Returns:
412        instance_list: List of instance names.
413    """
414    instances = GetRemoteInstances(cfg)
415    return [ins for ins in instances if ins.avd_type == constants.TYPE_CF]
416
417
418def Run(args):
419    """Run list.
420
421    Args:
422        args: Namespace object from argparse.parse_args.
423    """
424    instances = GetLocalInstances()
425    cfg = config.GetAcloudConfig(args)
426    if not args.local_only and cfg.SupportRemoteInstance():
427        instances.extend(GetRemoteInstances(cfg))
428
429    PrintInstancesDetails(instances, args.verbose)
430