You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
13 KiB
430 lines
13 KiB
# 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)
|