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