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 PrintInstancesDetails(instance_list, verbose=False): 53 """Display instances information. 54 55 Example of non-verbose case: 56 [1]device serial: 127.0.0.1:55685 (ins-1ff036dc-5128057-cf-x86-phone-userdebug) 57 [2]device serial: 127.0.0.1:60979 (ins-80952669-5128057-cf-x86-phone-userdebug) 58 [3]device serial: 127.0.0.1:6520 (local-instance) 59 60 Example of verbose case: 61 [1] name: ins-244710f0-5091715-aosp-cf-x86-phone-userdebug 62 IP: None 63 create time: 2018-10-25T06:32:08.182-07:00 64 status: TERMINATED 65 avd type: cuttlefish 66 display: 1080x1920 (240) 67 68 [2] name: ins-82979192-5091715-aosp-cf-x86-phone-userdebug 69 IP: 35.232.77.15 70 adb serial: 127.0.0.1:33537 71 create time: 2018-10-25T06:34:22.716-07:00 72 status: RUNNING 73 avd type: cuttlefish 74 display: 1080x1920 (240) 75 76 Args: 77 verbose: Boolean, True to print all details and only full name if False. 78 instance_list: List of instances. 79 """ 80 if not instance_list: 81 print("No remote or local instances found") 82 83 for num, instance_info in enumerate(instance_list, 1): 84 idx_str = "[%d]" % num 85 utils.PrintColorString(idx_str, end="") 86 if verbose: 87 print(instance_info.Summary()) 88 # add space between instances in verbose mode. 89 print("") 90 else: 91 print(instance_info) 92 93 94def GetRemoteInstances(cfg): 95 """Look for remote instances. 96 97 We're going to query the GCP project for all instances that created by user. 98 99 Args: 100 cfg: AcloudConfig object. 101 102 Returns: 103 instance_list: List of remote instances. 104 """ 105 credentials = auth.CreateCredentials(cfg) 106 compute_client = gcompute_client.ComputeClient(cfg, credentials) 107 filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser()) 108 all_instances = compute_client.ListInstances(instance_filter=filter_item) 109 110 logger.debug("Instance list from: (filter: %s\n%s):", 111 filter_item, all_instances) 112 113 return _ProcessInstances(all_instances) 114 115 116def _GetLocalCuttlefishInstances(): 117 """Look for local cuttelfish instances. 118 119 Gather local instances information from cuttlefish runtime config. 120 121 Returns: 122 instance_list: List of local instances. 123 """ 124 local_instance_list = [] 125 for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs(): 126 ins = instance.LocalInstance(cf_runtime_config_path) 127 if ins.CvdStatus(): 128 local_instance_list.append(ins) 129 else: 130 logger.info("cvd runtime config found but instance is not active:%s" 131 , cf_runtime_config_path) 132 return local_instance_list 133 134 135def GetActiveCVD(local_instance_id): 136 """Check if the local AVD with specific instance id is running 137 138 Args: 139 local_instance_id: Integer of instance id. 140 141 Return: 142 LocalInstance object. 143 """ 144 cfg_path = instance.GetLocalInstanceConfig(local_instance_id) 145 if cfg_path: 146 ins = instance.LocalInstance(cfg_path) 147 if ins.CvdStatus(): 148 return ins 149 cfg_path = instance.GetDefaultCuttlefishConfig() 150 if local_instance_id == 1 and os.path.isfile(cfg_path): 151 ins = instance.LocalInstance(cfg_path) 152 if ins.CvdStatus(): 153 return ins 154 return None 155 156 157def GetLocalInstances(): 158 """Look for local cuttleifsh and goldfish instances. 159 160 Returns: 161 List of local instances. 162 """ 163 # Running instances on local is not supported on all OS. 164 if not utils.IsSupportedPlatform(): 165 return [] 166 167 return (_GetLocalCuttlefishInstances() + 168 instance.LocalGoldfishInstance.GetExistingInstances()) 169 170 171def GetInstances(cfg): 172 """Look for remote/local instances. 173 174 Args: 175 cfg: AcloudConfig object. 176 177 Returns: 178 instance_list: List of instances. 179 """ 180 return GetRemoteInstances(cfg) + GetLocalInstances() 181 182 183def ChooseInstancesFromList(instances): 184 """Let user choose instances from a list. 185 186 Args: 187 instances: List of Instance objects. 188 189 Returns: 190 List of Instance objects. 191 """ 192 if len(instances) > 1: 193 print("Multiple instances detected, choose any one to proceed:") 194 return utils.GetAnswerFromList(instances, enable_choose_all=True) 195 return instances 196 197 198def ChooseInstances(cfg, select_all_instances=False): 199 """Get instances. 200 201 Retrieve all remote/local instances and if there is more than 1 instance 202 found, ask user which instance they'd like. 203 204 Args: 205 cfg: AcloudConfig object. 206 select_all_instances: True if select all instances by default and no 207 need to ask user to choose. 208 209 Returns: 210 List of Instance() object. 211 """ 212 instances = GetInstances(cfg) 213 if not select_all_instances: 214 return ChooseInstancesFromList(instances) 215 return instances 216 217 218def ChooseOneRemoteInstance(cfg): 219 """Get one remote cuttlefish instance. 220 221 Retrieve all remote cuttlefish instances and if there is more than 1 instance 222 found, ask user which instance they'd like. 223 224 Args: 225 cfg: AcloudConfig object. 226 227 Raises: 228 errors.NoInstancesFound: No cuttlefish remote instance found. 229 230 Returns: 231 list.Instance() object. 232 """ 233 instances_list = GetCFRemoteInstances(cfg) 234 if not instances_list: 235 raise errors.NoInstancesFound( 236 "Can't find any cuttlefish remote instances, please try " 237 "'$acloud create' to create instances") 238 if len(instances_list) > 1: 239 print("Multiple instances detected, choose any one to proceed:") 240 instances = utils.GetAnswerFromList(instances_list, 241 enable_choose_all=False) 242 return instances[0] 243 244 return instances_list[0] 245 246 247def FilterInstancesByNames(instances, names): 248 """Find instances by names. 249 250 Args: 251 instances: Collection of Instance objects. 252 names: Collection of strings, the names of the instances to search for. 253 254 Returns: 255 List of Instance objects. 256 257 Raises: 258 errors.NoInstancesFound if any instance is not found. 259 """ 260 instance_map = {inst.name: inst for inst in instances} 261 found_instances = [] 262 missing_instance_names = [] 263 for name in names: 264 if name in instance_map: 265 found_instances.append(instance_map[name]) 266 else: 267 missing_instance_names.append(name) 268 269 if missing_instance_names: 270 raise errors.NoInstancesFound("Did not find the following instances: %s" % 271 " ".join(missing_instance_names)) 272 return found_instances 273 274 275def GetInstancesFromInstanceNames(cfg, instance_names): 276 """Get instances from instance names. 277 278 Turn a list of instance names into a list of Instance(). 279 280 Args: 281 cfg: AcloudConfig object. 282 instance_names: list of instance name. 283 284 Returns: 285 List of Instance() objects. 286 287 Raises: 288 errors.NoInstancesFound: No instances found. 289 """ 290 return FilterInstancesByNames(GetInstances(cfg), instance_names) 291 292 293def FilterInstancesByAdbPort(instances, adb_port): 294 """Find an instance by adb port. 295 296 Args: 297 instances: Collection of Instance objects. 298 adb_port: int, adb port of the instance to search for. 299 300 Returns: 301 List of Instance() objects. 302 303 Raises: 304 errors.NoInstancesFound: No instances found. 305 """ 306 all_instance_info = [] 307 for instance_object in instances: 308 if instance_object.adb_port == adb_port: 309 return [instance_object] 310 all_instance_info.append(instance_object.fullname) 311 312 # Show devices information to user when user provides wrong adb port. 313 if all_instance_info: 314 hint_message = ("No instance with adb port %d, available instances:\n%s" 315 % (adb_port, "\n".join(all_instance_info))) 316 else: 317 hint_message = "No instances to delete." 318 raise errors.NoInstancesFound(hint_message) 319 320 321def GetCFRemoteInstances(cfg): 322 """Look for cuttlefish remote instances. 323 324 Args: 325 cfg: AcloudConfig object. 326 327 Returns: 328 instance_list: List of instance names. 329 """ 330 instances = GetRemoteInstances(cfg) 331 return [ins for ins in instances if ins.avd_type == constants.TYPE_CF] 332 333 334def Run(args): 335 """Run list. 336 337 Args: 338 args: Namespace object from argparse.parse_args. 339 """ 340 instances = GetLocalInstances() 341 cfg = config.GetAcloudConfig(args) 342 if not args.local_only and cfg.SupportRemoteInstance(): 343 instances.extend(GetRemoteInstances(cfg)) 344 345 PrintInstancesDetails(instances, args.verbose) 346