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