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