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(id_cfg_pairs): 131 """Look for local cuttelfish instances. 132 133 Gather local instances information from cuttlefish runtime config. 134 135 Args: 136 id_cfg_pairs: List of tuples. Each tuple consists of an instance id and 137 a config path. 138 139 Returns: 140 instance_list: List of local instances. 141 """ 142 local_instance_list = [] 143 for ins_id, cfg_path in id_cfg_pairs: 144 ins_lock = instance.GetLocalInstanceLock(ins_id) 145 if not ins_lock.Lock(): 146 logger.warning("Cuttlefish Instance %d is locked by another " 147 "process.", ins_id) 148 continue 149 try: 150 if not os.path.isfile(cfg_path): 151 continue 152 ins = instance.LocalInstance(cfg_path) 153 if ins.CvdStatus(): 154 local_instance_list.append(ins) 155 else: 156 logger.info("Cvd runtime config is found at %s but instance " 157 "%d is not active.", cfg_path, ins_id) 158 finally: 159 ins_lock.Unlock() 160 return local_instance_list 161 162 163def GetActiveCVD(local_instance_id): 164 """Check if the local AVD with specific instance id is running 165 166 This function does not lock the instance. 167 168 Args: 169 local_instance_id: Integer of instance id. 170 171 Return: 172 LocalInstance object. 173 """ 174 cfg_path = instance.GetLocalInstanceConfig(local_instance_id) 175 if cfg_path: 176 ins = instance.LocalInstance(cfg_path) 177 if ins.CvdStatus(): 178 return ins 179 cfg_path = instance.GetDefaultCuttlefishConfig() 180 if local_instance_id == 1 and cfg_path: 181 ins = instance.LocalInstance(cfg_path) 182 if ins.CvdStatus(): 183 return ins 184 return None 185 186 187def GetLocalInstances(): 188 """Look for local cuttleifsh and goldfish instances. 189 190 Returns: 191 List of local instances. 192 """ 193 # Running instances on local is not supported on all OS. 194 if not utils.IsSupportedPlatform(): 195 return [] 196 197 id_cfg_pairs = instance.GetAllLocalInstanceConfigs() 198 return (_GetLocalCuttlefishInstances(id_cfg_pairs) + 199 instance.LocalGoldfishInstance.GetExistingInstances()) 200 201 202def GetInstances(cfg): 203 """Look for remote/local instances. 204 205 Args: 206 cfg: AcloudConfig object. 207 208 Returns: 209 instance_list: List of instances. 210 """ 211 return GetRemoteInstances(cfg) + GetLocalInstances() 212 213 214def ChooseInstancesFromList(instances): 215 """Let user choose instances from a list. 216 217 Args: 218 instances: List of Instance objects. 219 220 Returns: 221 List of Instance objects. 222 """ 223 if len(instances) > 1: 224 print("Multiple instances detected, choose any one to proceed:") 225 return utils.GetAnswerFromList(instances, enable_choose_all=True) 226 return instances 227 228 229def ChooseInstances(cfg, select_all_instances=False): 230 """Get instances. 231 232 Retrieve all remote/local instances and if there is more than 1 instance 233 found, ask user which instance they'd like. 234 235 Args: 236 cfg: AcloudConfig object. 237 select_all_instances: True if select all instances by default and no 238 need to ask user to choose. 239 240 Returns: 241 List of Instance() object. 242 """ 243 instances = GetInstances(cfg) 244 if not select_all_instances: 245 return ChooseInstancesFromList(instances) 246 return instances 247 248 249def ChooseOneRemoteInstance(cfg): 250 """Get one remote cuttlefish instance. 251 252 Retrieve all remote cuttlefish instances and if there is more than 1 instance 253 found, ask user which instance they'd like. 254 255 Args: 256 cfg: AcloudConfig object. 257 258 Raises: 259 errors.NoInstancesFound: No cuttlefish remote instance found. 260 261 Returns: 262 list.Instance() object. 263 """ 264 instances_list = GetCFRemoteInstances(cfg) 265 if not instances_list: 266 raise errors.NoInstancesFound( 267 "Can't find any cuttlefish remote instances, please try " 268 "'$acloud create' to create instances") 269 if len(instances_list) > 1: 270 print("Multiple instances detected, choose any one to proceed:") 271 instances = utils.GetAnswerFromList(instances_list, 272 enable_choose_all=False) 273 return instances[0] 274 275 return instances_list[0] 276 277 278def _FilterInstancesByNames(instances, names): 279 """Find instances by names. 280 281 Args: 282 instances: Collection of Instance objects. 283 names: Collection of strings, the names of the instances to search for. 284 285 Returns: 286 List of Instance objects. 287 288 Raises: 289 errors.NoInstancesFound if any instance is not found. 290 """ 291 instance_map = {inst.name: inst for inst in instances} 292 found_instances = [] 293 missing_instance_names = [] 294 for name in names: 295 if name in instance_map: 296 found_instances.append(instance_map[name]) 297 else: 298 missing_instance_names.append(name) 299 300 if missing_instance_names: 301 raise errors.NoInstancesFound("Did not find the following instances: %s" % 302 " ".join(missing_instance_names)) 303 return found_instances 304 305 306def GetLocalInstanceLockByName(name): 307 """Get the lock of a local cuttelfish or goldfish instance. 308 309 Args: 310 name: The instance name. 311 312 Returns: 313 LocalInstanceLock object. None if the name is invalid. 314 """ 315 cf_id = instance.GetLocalInstanceIdByName(name) 316 if cf_id is not None: 317 return instance.GetLocalInstanceLock(cf_id) 318 319 gf_id = instance.LocalGoldfishInstance.GetIdByName(name) 320 if gf_id is not None: 321 return instance.LocalGoldfishInstance.GetLockById(gf_id) 322 323 return None 324 325 326def GetLocalInstancesByNames(names): 327 """Get local cuttlefish and goldfish instances by names. 328 329 This method does not raise an error if it cannot find all instances. 330 331 Args: 332 names: Collection of instance names. 333 334 Returns: 335 List consisting of LocalInstance and LocalGoldfishInstance objects. 336 """ 337 id_cfg_pairs = [] 338 for name in names: 339 ins_id = instance.GetLocalInstanceIdByName(name) 340 if ins_id is None: 341 continue 342 cfg_path = instance.GetLocalInstanceConfig(ins_id) 343 if cfg_path: 344 id_cfg_pairs.append((ins_id, cfg_path)) 345 if ins_id == 1: 346 cfg_path = instance.GetDefaultCuttlefishConfig() 347 if cfg_path: 348 id_cfg_pairs.append((ins_id, cfg_path)) 349 350 gf_instances = [ins for ins in 351 instance.LocalGoldfishInstance.GetExistingInstances() 352 if ins.name in names] 353 354 return _GetLocalCuttlefishInstances(id_cfg_pairs) + gf_instances 355 356 357def GetInstancesFromInstanceNames(cfg, instance_names): 358 """Get instances from instance names. 359 360 Turn a list of instance names into a list of Instance(). 361 362 Args: 363 cfg: AcloudConfig object. 364 instance_names: list of instance name. 365 366 Returns: 367 List of Instance() objects. 368 369 Raises: 370 errors.NoInstancesFound: No instances found. 371 """ 372 return _FilterInstancesByNames( 373 GetLocalInstancesByNames(instance_names) + GetRemoteInstances(cfg), 374 instance_names) 375 376 377def FilterInstancesByAdbPort(instances, adb_port): 378 """Find an instance by adb port. 379 380 Args: 381 instances: Collection of Instance objects. 382 adb_port: int, adb port of the instance to search for. 383 384 Returns: 385 List of Instance() objects. 386 387 Raises: 388 errors.NoInstancesFound: No instances found. 389 """ 390 all_instance_info = [] 391 for instance_object in instances: 392 if instance_object.adb_port == adb_port: 393 return [instance_object] 394 all_instance_info.append(instance_object.fullname) 395 396 # Show devices information to user when user provides wrong adb port. 397 if all_instance_info: 398 hint_message = ("No instance with adb port %d, available instances:\n%s" 399 % (adb_port, "\n".join(all_instance_info))) 400 else: 401 hint_message = "No instances to delete." 402 raise errors.NoInstancesFound(hint_message) 403 404 405def GetCFRemoteInstances(cfg): 406 """Look for cuttlefish remote instances. 407 408 Args: 409 cfg: AcloudConfig object. 410 411 Returns: 412 instance_list: List of instance names. 413 """ 414 instances = GetRemoteInstances(cfg) 415 return [ins for ins in instances if ins.avd_type == constants.TYPE_CF] 416 417 418def Run(args): 419 """Run list. 420 421 Args: 422 args: Namespace object from argparse.parse_args. 423 """ 424 instances = GetLocalInstances() 425 cfg = config.GetAcloudConfig(args) 426 if not args.local_only and cfg.SupportRemoteInstance(): 427 instances.extend(GetRemoteInstances(cfg)) 428 429 PrintInstancesDetails(instances, args.verbose) 430