| # Copyright 2018 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| r"""List entry point. |
| |
| List will handle all the logic related to list a local/remote instance |
| of an Android Virtual Device. |
| """ |
| |
| from __future__ import print_function |
| import getpass |
| import logging |
| import os |
| |
| from acloud import errors |
| from acloud.internal import constants |
| from acloud.internal.lib import auth |
| from acloud.internal.lib import gcompute_client |
| from acloud.internal.lib import utils |
| from acloud.list import instance |
| from acloud.public import config |
| |
| |
| logger = logging.getLogger(__name__) |
| |
| _COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"] |
| |
| |
| def _ProcessInstances(instance_list): |
| """Get more details of remote instances. |
| |
| Args: |
| instance_list: List of dicts which contain info about the remote instances, |
| they're the response from the GCP GCE api. |
| |
| Returns: |
| instance_detail_list: List of instance.Instance() with detail info. |
| """ |
| return [instance.RemoteInstance(gce_instance) for gce_instance in instance_list] |
| |
| |
| def _SortInstancesForDisplay(instances): |
| """Sort the instances by connected first and then by age. |
| |
| Args: |
| instances: List of instance.Instance() |
| |
| Returns: |
| List of instance.Instance() after sorted. |
| """ |
| instances.sort(key=lambda ins: ins.createtime, reverse=True) |
| instances.sort(key=lambda ins: ins.AdbConnected(), reverse=True) |
| return instances |
| |
| |
| def PrintInstancesDetails(instance_list, verbose=False): |
| """Display instances information. |
| |
| Example of non-verbose case: |
| [1]device serial: 127.0.0.1:55685 (ins-1ff036dc-5128057-cf-x86-phone-userdebug) |
| [2]device serial: 127.0.0.1:60979 (ins-80952669-5128057-cf-x86-phone-userdebug) |
| [3]device serial: 127.0.0.1:6520 (local-instance) |
| |
| Example of verbose case: |
| [1] name: ins-244710f0-5091715-aosp-cf-x86-phone-userdebug |
| IP: None |
| create time: 2018-10-25T06:32:08.182-07:00 |
| status: TERMINATED |
| avd type: cuttlefish |
| display: 1080x1920 (240) |
| |
| [2] name: ins-82979192-5091715-aosp-cf-x86-phone-userdebug |
| IP: 35.232.77.15 |
| adb serial: 127.0.0.1:33537 |
| create time: 2018-10-25T06:34:22.716-07:00 |
| status: RUNNING |
| avd type: cuttlefish |
| display: 1080x1920 (240) |
| |
| Args: |
| verbose: Boolean, True to print all details and only full name if False. |
| instance_list: List of instances. |
| """ |
| if not instance_list: |
| print("No remote or local instances found") |
| |
| for num, instance_info in enumerate(instance_list, 1): |
| idx_str = "[%d]" % num |
| utils.PrintColorString(idx_str, end="") |
| if verbose: |
| print(instance_info.Summary()) |
| # add space between instances in verbose mode. |
| print("") |
| else: |
| print(instance_info) |
| |
| |
| def GetRemoteInstances(cfg): |
| """Look for remote instances. |
| |
| We're going to query the GCP project for all instances that created by user. |
| |
| Args: |
| cfg: AcloudConfig object. |
| |
| Returns: |
| instance_list: List of remote instances. |
| """ |
| credentials = auth.CreateCredentials(cfg) |
| compute_client = gcompute_client.ComputeClient(cfg, credentials) |
| filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser()) |
| all_instances = compute_client.ListInstances(instance_filter=filter_item) |
| |
| logger.debug("Instance list from: (filter: %s\n%s):", |
| filter_item, all_instances) |
| |
| return _SortInstancesForDisplay(_ProcessInstances(all_instances)) |
| |
| |
| def _GetLocalCuttlefishInstances(id_cfg_pairs): |
| """Look for local cuttelfish instances. |
| |
| Gather local instances information from cuttlefish runtime config. |
| |
| Args: |
| id_cfg_pairs: List of tuples. Each tuple consists of an instance id and |
| a config path. |
| |
| Returns: |
| instance_list: List of local instances. |
| """ |
| local_instance_list = [] |
| for ins_id, cfg_path in id_cfg_pairs: |
| ins_lock = instance.GetLocalInstanceLock(ins_id) |
| if not ins_lock.Lock(): |
| logger.warning("Cuttlefish Instance %d is locked by another " |
| "process.", ins_id) |
| continue |
| try: |
| if not os.path.isfile(cfg_path): |
| continue |
| ins = instance.LocalInstance(cfg_path) |
| if ins.CvdStatus(): |
| local_instance_list.append(ins) |
| else: |
| logger.info("Cvd runtime config is found at %s but instance " |
| "%d is not active.", cfg_path, ins_id) |
| finally: |
| ins_lock.Unlock() |
| return local_instance_list |
| |
| |
| def GetActiveCVD(local_instance_id): |
| """Check if the local AVD with specific instance id is running |
| |
| This function does not lock the instance. |
| |
| Args: |
| local_instance_id: Integer of instance id. |
| |
| Return: |
| LocalInstance object. |
| """ |
| cfg_path = instance.GetLocalInstanceConfig(local_instance_id) |
| if cfg_path: |
| ins = instance.LocalInstance(cfg_path) |
| if ins.CvdStatus(): |
| return ins |
| cfg_path = instance.GetDefaultCuttlefishConfig() |
| if local_instance_id == 1 and cfg_path: |
| ins = instance.LocalInstance(cfg_path) |
| if ins.CvdStatus(): |
| return ins |
| return None |
| |
| |
| def GetLocalInstances(): |
| """Look for local cuttleifsh and goldfish instances. |
| |
| Returns: |
| List of local instances. |
| """ |
| # Running instances on local is not supported on all OS. |
| if not utils.IsSupportedPlatform(): |
| return [] |
| |
| id_cfg_pairs = instance.GetAllLocalInstanceConfigs() |
| return (_GetLocalCuttlefishInstances(id_cfg_pairs) + |
| instance.LocalGoldfishInstance.GetExistingInstances()) |
| |
| |
| def GetInstances(cfg): |
| """Look for remote/local instances. |
| |
| Args: |
| cfg: AcloudConfig object. |
| |
| Returns: |
| instance_list: List of instances. |
| """ |
| return GetRemoteInstances(cfg) + GetLocalInstances() |
| |
| |
| def ChooseInstancesFromList(instances): |
| """Let user choose instances from a list. |
| |
| Args: |
| instances: List of Instance objects. |
| |
| Returns: |
| List of Instance objects. |
| """ |
| if len(instances) > 1: |
| print("Multiple instances detected, choose any one to proceed:") |
| return utils.GetAnswerFromList(instances, enable_choose_all=True) |
| return instances |
| |
| |
| def ChooseInstances(cfg, select_all_instances=False): |
| """Get instances. |
| |
| Retrieve all remote/local instances and if there is more than 1 instance |
| found, ask user which instance they'd like. |
| |
| Args: |
| cfg: AcloudConfig object. |
| select_all_instances: True if select all instances by default and no |
| need to ask user to choose. |
| |
| Returns: |
| List of Instance() object. |
| """ |
| instances = GetInstances(cfg) |
| if not select_all_instances: |
| return ChooseInstancesFromList(instances) |
| return instances |
| |
| |
| def ChooseOneRemoteInstance(cfg): |
| """Get one remote cuttlefish instance. |
| |
| Retrieve all remote cuttlefish instances and if there is more than 1 instance |
| found, ask user which instance they'd like. |
| |
| Args: |
| cfg: AcloudConfig object. |
| |
| Raises: |
| errors.NoInstancesFound: No cuttlefish remote instance found. |
| |
| Returns: |
| list.Instance() object. |
| """ |
| instances_list = GetCFRemoteInstances(cfg) |
| if not instances_list: |
| raise errors.NoInstancesFound( |
| "Can't find any cuttlefish remote instances, please try " |
| "'$acloud create' to create instances") |
| if len(instances_list) > 1: |
| print("Multiple instances detected, choose any one to proceed:") |
| instances = utils.GetAnswerFromList(instances_list, |
| enable_choose_all=False) |
| return instances[0] |
| |
| return instances_list[0] |
| |
| |
| def _FilterInstancesByNames(instances, names): |
| """Find instances by names. |
| |
| Args: |
| instances: Collection of Instance objects. |
| names: Collection of strings, the names of the instances to search for. |
| |
| Returns: |
| List of Instance objects. |
| |
| Raises: |
| errors.NoInstancesFound if any instance is not found. |
| """ |
| instance_map = {inst.name: inst for inst in instances} |
| found_instances = [] |
| missing_instance_names = [] |
| for name in names: |
| if name in instance_map: |
| found_instances.append(instance_map[name]) |
| else: |
| missing_instance_names.append(name) |
| |
| if missing_instance_names: |
| raise errors.NoInstancesFound("Did not find the following instances: %s" % |
| " ".join(missing_instance_names)) |
| return found_instances |
| |
| |
| def GetLocalInstanceLockByName(name): |
| """Get the lock of a local cuttelfish or goldfish instance. |
| |
| Args: |
| name: The instance name. |
| |
| Returns: |
| LocalInstanceLock object. None if the name is invalid. |
| """ |
| cf_id = instance.GetLocalInstanceIdByName(name) |
| if cf_id is not None: |
| return instance.GetLocalInstanceLock(cf_id) |
| |
| gf_id = instance.LocalGoldfishInstance.GetIdByName(name) |
| if gf_id is not None: |
| return instance.LocalGoldfishInstance.GetLockById(gf_id) |
| |
| return None |
| |
| |
| def GetLocalInstancesByNames(names): |
| """Get local cuttlefish and goldfish instances by names. |
| |
| This method does not raise an error if it cannot find all instances. |
| |
| Args: |
| names: Collection of instance names. |
| |
| Returns: |
| List consisting of LocalInstance and LocalGoldfishInstance objects. |
| """ |
| id_cfg_pairs = [] |
| for name in names: |
| ins_id = instance.GetLocalInstanceIdByName(name) |
| if ins_id is None: |
| continue |
| cfg_path = instance.GetLocalInstanceConfig(ins_id) |
| if cfg_path: |
| id_cfg_pairs.append((ins_id, cfg_path)) |
| if ins_id == 1: |
| cfg_path = instance.GetDefaultCuttlefishConfig() |
| if cfg_path: |
| id_cfg_pairs.append((ins_id, cfg_path)) |
| |
| gf_instances = [ins for ins in |
| instance.LocalGoldfishInstance.GetExistingInstances() |
| if ins.name in names] |
| |
| return _GetLocalCuttlefishInstances(id_cfg_pairs) + gf_instances |
| |
| |
| def GetInstancesFromInstanceNames(cfg, instance_names): |
| """Get instances from instance names. |
| |
| Turn a list of instance names into a list of Instance(). |
| |
| Args: |
| cfg: AcloudConfig object. |
| instance_names: list of instance name. |
| |
| Returns: |
| List of Instance() objects. |
| |
| Raises: |
| errors.NoInstancesFound: No instances found. |
| """ |
| return _FilterInstancesByNames( |
| GetLocalInstancesByNames(instance_names) + GetRemoteInstances(cfg), |
| instance_names) |
| |
| |
| def FilterInstancesByAdbPort(instances, adb_port): |
| """Find an instance by adb port. |
| |
| Args: |
| instances: Collection of Instance objects. |
| adb_port: int, adb port of the instance to search for. |
| |
| Returns: |
| List of Instance() objects. |
| |
| Raises: |
| errors.NoInstancesFound: No instances found. |
| """ |
| all_instance_info = [] |
| for instance_object in instances: |
| if instance_object.adb_port == adb_port: |
| return [instance_object] |
| all_instance_info.append(instance_object.fullname) |
| |
| # Show devices information to user when user provides wrong adb port. |
| if all_instance_info: |
| hint_message = ("No instance with adb port %d, available instances:\n%s" |
| % (adb_port, "\n".join(all_instance_info))) |
| else: |
| hint_message = "No instances to delete." |
| raise errors.NoInstancesFound(hint_message) |
| |
| |
| def GetCFRemoteInstances(cfg): |
| """Look for cuttlefish remote instances. |
| |
| Args: |
| cfg: AcloudConfig object. |
| |
| Returns: |
| instance_list: List of instance names. |
| """ |
| instances = GetRemoteInstances(cfg) |
| return [ins for ins in instances if ins.avd_type == constants.TYPE_CF] |
| |
| |
| def Run(args): |
| """Run list. |
| |
| Args: |
| args: Namespace object from argparse.parse_args. |
| """ |
| instances = GetLocalInstances() |
| cfg = config.GetAcloudConfig(args) |
| if not args.local_only and cfg.SupportRemoteInstance(): |
| instances.extend(GetRemoteInstances(cfg)) |
| |
| PrintInstancesDetails(instances, args.verbose) |