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():
131    """Look for local cuttelfish instances.
132
133    Gather local instances information from cuttlefish runtime config.
134
135    Returns:
136        instance_list: List of local instances.
137    """
138    local_instance_list = []
139    for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs():
140        ins = instance.LocalInstance(cf_runtime_config_path)
141        if ins.CvdStatus():
142            local_instance_list.append(ins)
143        else:
144            logger.info("cvd runtime config found but instance is not active:%s"
145                        , cf_runtime_config_path)
146    return local_instance_list
147
148
149def GetActiveCVD(local_instance_id):
150    """Check if the local AVD with specific instance id is running
151
152    Args:
153        local_instance_id: Integer of instance id.
154
155    Return:
156        LocalInstance object.
157    """
158    cfg_path = instance.GetLocalInstanceConfig(local_instance_id)
159    if cfg_path:
160        ins = instance.LocalInstance(cfg_path)
161        if ins.CvdStatus():
162            return ins
163    cfg_path = instance.GetDefaultCuttlefishConfig()
164    if local_instance_id == 1 and os.path.isfile(cfg_path):
165        ins = instance.LocalInstance(cfg_path)
166        if ins.CvdStatus():
167            return ins
168    return None
169
170
171def GetLocalInstances():
172    """Look for local cuttleifsh and goldfish instances.
173
174    Returns:
175        List of local instances.
176    """
177    # Running instances on local is not supported on all OS.
178    if not utils.IsSupportedPlatform():
179        return []
180
181    return (_GetLocalCuttlefishInstances() +
182            instance.LocalGoldfishInstance.GetExistingInstances())
183
184
185def GetInstances(cfg):
186    """Look for remote/local instances.
187
188    Args:
189        cfg: AcloudConfig object.
190
191    Returns:
192        instance_list: List of instances.
193    """
194    return GetRemoteInstances(cfg) + GetLocalInstances()
195
196
197def ChooseInstancesFromList(instances):
198    """Let user choose instances from a list.
199
200    Args:
201        instances: List of Instance objects.
202
203    Returns:
204         List of Instance objects.
205    """
206    if len(instances) > 1:
207        print("Multiple instances detected, choose any one to proceed:")
208        return utils.GetAnswerFromList(instances, enable_choose_all=True)
209    return instances
210
211
212def ChooseInstances(cfg, select_all_instances=False):
213    """Get instances.
214
215    Retrieve all remote/local instances and if there is more than 1 instance
216    found, ask user which instance they'd like.
217
218    Args:
219        cfg: AcloudConfig object.
220        select_all_instances: True if select all instances by default and no
221                              need to ask user to choose.
222
223    Returns:
224        List of Instance() object.
225    """
226    instances = GetInstances(cfg)
227    if not select_all_instances:
228        return ChooseInstancesFromList(instances)
229    return instances
230
231
232def ChooseOneRemoteInstance(cfg):
233    """Get one remote cuttlefish instance.
234
235    Retrieve all remote cuttlefish instances and if there is more than 1 instance
236    found, ask user which instance they'd like.
237
238    Args:
239        cfg: AcloudConfig object.
240
241    Raises:
242        errors.NoInstancesFound: No cuttlefish remote instance found.
243
244    Returns:
245        list.Instance() object.
246    """
247    instances_list = GetCFRemoteInstances(cfg)
248    if not instances_list:
249        raise errors.NoInstancesFound(
250            "Can't find any cuttlefish remote instances, please try "
251            "'$acloud create' to create instances")
252    if len(instances_list) > 1:
253        print("Multiple instances detected, choose any one to proceed:")
254        instances = utils.GetAnswerFromList(instances_list,
255                                            enable_choose_all=False)
256        return instances[0]
257
258    return instances_list[0]
259
260
261def FilterInstancesByNames(instances, names):
262    """Find instances by names.
263
264    Args:
265        instances: Collection of Instance objects.
266        names: Collection of strings, the names of the instances to search for.
267
268    Returns:
269        List of Instance objects.
270
271    Raises:
272        errors.NoInstancesFound if any instance is not found.
273    """
274    instance_map = {inst.name: inst for inst in instances}
275    found_instances = []
276    missing_instance_names = []
277    for name in names:
278        if name in instance_map:
279            found_instances.append(instance_map[name])
280        else:
281            missing_instance_names.append(name)
282
283    if missing_instance_names:
284        raise errors.NoInstancesFound("Did not find the following instances: %s" %
285                                      " ".join(missing_instance_names))
286    return found_instances
287
288
289def GetInstancesFromInstanceNames(cfg, instance_names):
290    """Get instances from instance names.
291
292    Turn a list of instance names into a list of Instance().
293
294    Args:
295        cfg: AcloudConfig object.
296        instance_names: list of instance name.
297
298    Returns:
299        List of Instance() objects.
300
301    Raises:
302        errors.NoInstancesFound: No instances found.
303    """
304    return FilterInstancesByNames(GetInstances(cfg), instance_names)
305
306
307def FilterInstancesByAdbPort(instances, adb_port):
308    """Find an instance by adb port.
309
310    Args:
311        instances: Collection of Instance objects.
312        adb_port: int, adb port of the instance to search for.
313
314    Returns:
315        List of Instance() objects.
316
317    Raises:
318        errors.NoInstancesFound: No instances found.
319    """
320    all_instance_info = []
321    for instance_object in instances:
322        if instance_object.adb_port == adb_port:
323            return [instance_object]
324        all_instance_info.append(instance_object.fullname)
325
326    # Show devices information to user when user provides wrong adb port.
327    if all_instance_info:
328        hint_message = ("No instance with adb port %d, available instances:\n%s"
329                        % (adb_port, "\n".join(all_instance_info)))
330    else:
331        hint_message = "No instances to delete."
332    raise errors.NoInstancesFound(hint_message)
333
334
335def GetCFRemoteInstances(cfg):
336    """Look for cuttlefish remote instances.
337
338    Args:
339        cfg: AcloudConfig object.
340
341    Returns:
342        instance_list: List of instance names.
343    """
344    instances = GetRemoteInstances(cfg)
345    return [ins for ins in instances if ins.avd_type == constants.TYPE_CF]
346
347
348def Run(args):
349    """Run list.
350
351    Args:
352        args: Namespace object from argparse.parse_args.
353    """
354    instances = GetLocalInstances()
355    cfg = config.GetAcloudConfig(args)
356    if not args.local_only and cfg.SupportRemoteInstance():
357        instances.extend(GetRemoteInstances(cfg))
358
359    PrintInstancesDetails(instances, args.verbose)
360