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"""Delete entry point. 15 16Delete will handle all the logic related to deleting a local/remote instance 17of an Android Virtual Device. 18""" 19 20from __future__ import print_function 21 22import logging 23import re 24import subprocess 25 26from acloud import errors 27from acloud.internal import constants 28from acloud.internal.lib import auth 29from acloud.internal.lib import cvd_compute_client_multi_stage 30from acloud.internal.lib import utils 31from acloud.internal.lib import ssh as ssh_object 32from acloud.list import list as list_instances 33from acloud.public import config 34from acloud.public import device_driver 35from acloud.public import report 36 37 38logger = logging.getLogger(__name__) 39 40_COMMAND_GET_PROCESS_ID = ["pgrep", "run_cvd"] 41_COMMAND_GET_PROCESS_COMMAND = ["ps", "-o", "command", "-p"] 42_RE_RUN_CVD = re.compile(r"^(?P<run_cvd>.+run_cvd)") 43_LOCAL_INSTANCE_PREFIX = "local-" 44 45 46def DeleteInstances(cfg, instances_to_delete): 47 """Delete instances according to instances_to_delete. 48 49 Args: 50 cfg: AcloudConfig object. 51 instances_to_delete: List of list.Instance() object. 52 53 Returns: 54 Report object. 55 """ 56 delete_report = report.Report(command="delete") 57 remote_instance_list = [] 58 for instance in instances_to_delete: 59 if instance.islocal: 60 if instance.avd_type == constants.TYPE_GF: 61 DeleteLocalGoldfishInstance(instance, delete_report) 62 elif instance.avd_type == constants.TYPE_CF: 63 DeleteLocalCuttlefishInstance(instance, delete_report) 64 else: 65 delete_report.AddError("Deleting %s is not supported." % 66 instance.avd_type) 67 delete_report.SetStatus(report.Status.FAIL) 68 else: 69 remote_instance_list.append(instance.name) 70 # Delete ssvnc viewer 71 if instance.vnc_port: 72 utils.CleanupSSVncviewer(instance.vnc_port) 73 74 if remote_instance_list: 75 # TODO(119283708): We should move DeleteAndroidVirtualDevices into 76 # delete.py after gce is deprecated. 77 # Stop remote instances. 78 return DeleteRemoteInstances(cfg, remote_instance_list, delete_report) 79 80 return delete_report 81 82 83@utils.TimeExecute(function_description="Deleting remote instances", 84 result_evaluator=utils.ReportEvaluator, 85 display_waiting_dots=False) 86def DeleteRemoteInstances(cfg, instances_to_delete, delete_report=None): 87 """Delete remote instances. 88 89 Args: 90 cfg: AcloudConfig object. 91 instances_to_delete: List of instance names(string). 92 delete_report: Report object. 93 94 Returns: 95 Report instance if there are instances to delete, None otherwise. 96 97 Raises: 98 error.ConfigError: when config doesn't support remote instances. 99 """ 100 if not cfg.SupportRemoteInstance(): 101 raise errors.ConfigError("No gcp project info found in config! " 102 "The execution of deleting remote instances " 103 "has been aborted.") 104 utils.PrintColorString("") 105 for instance in instances_to_delete: 106 utils.PrintColorString(" - %s" % instance, utils.TextColors.WARNING) 107 utils.PrintColorString("") 108 utils.PrintColorString("status: waiting...", end="") 109 110 # TODO(119283708): We should move DeleteAndroidVirtualDevices into 111 # delete.py after gce is deprecated. 112 # Stop remote instances. 113 delete_report = device_driver.DeleteAndroidVirtualDevices( 114 cfg, instances_to_delete, delete_report) 115 116 return delete_report 117 118 119@utils.TimeExecute(function_description="Deleting local cuttlefish instances", 120 result_evaluator=utils.ReportEvaluator) 121def DeleteLocalCuttlefishInstance(instance, delete_report): 122 """Delete a local cuttlefish instance. 123 124 Delete local instance and write delete instance 125 information to report. 126 127 Args: 128 instance: instance.LocalInstance object. 129 delete_report: Report object. 130 131 Returns: 132 delete_report. 133 """ 134 ins_lock = instance.GetLock() 135 if not ins_lock.Lock(): 136 delete_report.AddError("%s is locked by another process." % 137 instance.name) 138 delete_report.SetStatus(report.Status.FAIL) 139 return delete_report 140 141 try: 142 ins_lock.SetInUse(False) 143 instance.Delete() 144 delete_report.SetStatus(report.Status.SUCCESS) 145 device_driver.AddDeletionResultToReport( 146 delete_report, [instance.name], failed=[], 147 error_msgs=[], 148 resource_name="instance") 149 except subprocess.CalledProcessError as e: 150 delete_report.AddError(str(e)) 151 delete_report.SetStatus(report.Status.FAIL) 152 finally: 153 ins_lock.Unlock() 154 155 return delete_report 156 157 158@utils.TimeExecute(function_description="Deleting local goldfish instances", 159 result_evaluator=utils.ReportEvaluator) 160def DeleteLocalGoldfishInstance(instance, delete_report): 161 """Delete a local goldfish instance. 162 163 Args: 164 instance: LocalGoldfishInstance object. 165 delete_report: Report object. 166 167 Returns: 168 delete_report. 169 """ 170 lock = instance.GetLock() 171 if not lock.Lock(): 172 delete_report.AddError("%s is locked by another process." % 173 instance.name) 174 delete_report.SetStatus(report.Status.FAIL) 175 return delete_report 176 177 try: 178 lock.SetInUse(False) 179 if instance.adb.EmuCommand("kill") == 0: 180 delete_report.SetStatus(report.Status.SUCCESS) 181 device_driver.AddDeletionResultToReport( 182 delete_report, [instance.name], failed=[], 183 error_msgs=[], 184 resource_name="instance") 185 else: 186 delete_report.AddError("Cannot kill %s." % instance.device_serial) 187 delete_report.SetStatus(report.Status.FAIL) 188 finally: 189 lock.Unlock() 190 191 return delete_report 192 193 194def ResetLocalInstanceLockByName(name, delete_report): 195 """Set the lock state of a local instance to be not in use. 196 197 Args: 198 name: The instance name. 199 delete_report: Report object. 200 """ 201 ins_lock = list_instances.GetLocalInstanceLockByName(name) 202 if not ins_lock: 203 delete_report.AddError("%s is not a valid local instance name." % name) 204 delete_report.SetStatus(report.Status.FAIL) 205 return 206 207 if not ins_lock.Lock(): 208 delete_report.AddError("%s is locked by another process." % name) 209 delete_report.SetStatus(report.Status.FAIL) 210 return 211 212 try: 213 ins_lock.SetInUse(False) 214 delete_report.SetStatus(report.Status.SUCCESS) 215 device_driver.AddDeletionResultToReport( 216 delete_report, [name], failed=[], error_msgs=[], 217 resource_name="instance") 218 finally: 219 ins_lock.Unlock() 220 221 222def CleanUpRemoteHost(cfg, remote_host, host_user, 223 host_ssh_private_key_path=None): 224 """Clean up the remote host. 225 226 Args: 227 cfg: An AcloudConfig instance. 228 remote_host : String, ip address or host name of the remote host. 229 host_user: String of user login into the instance. 230 host_ssh_private_key_path: String of host key for logging in to the 231 host. 232 233 Returns: 234 A Report instance. 235 """ 236 delete_report = report.Report(command="delete") 237 credentials = auth.CreateCredentials(cfg) 238 compute_client = cvd_compute_client_multi_stage.CvdComputeClient( 239 acloud_config=cfg, 240 oauth2_credentials=credentials) 241 ssh = ssh_object.Ssh( 242 ip=ssh_object.IP(ip=remote_host), 243 user=host_user, 244 ssh_private_key_path=( 245 host_ssh_private_key_path or cfg.ssh_private_key_path)) 246 try: 247 compute_client.InitRemoteHost(ssh, remote_host, host_user) 248 delete_report.SetStatus(report.Status.SUCCESS) 249 device_driver.AddDeletionResultToReport( 250 delete_report, [remote_host], failed=[], 251 error_msgs=[], 252 resource_name="remote host") 253 except subprocess.CalledProcessError as e: 254 delete_report.AddError(str(e)) 255 delete_report.SetStatus(report.Status.FAIL) 256 257 return delete_report 258 259 260def DeleteInstanceByNames(cfg, instances): 261 """Delete instances by the names of these instances. 262 263 Args: 264 cfg: AcloudConfig object. 265 instances: List of instance name. 266 267 Returns: 268 A Report instance. 269 """ 270 delete_report = report.Report(command="delete") 271 local_names = set(name for name in instances if 272 name.startswith(_LOCAL_INSTANCE_PREFIX)) 273 remote_names = list(set(instances) - set(local_names)) 274 if local_names: 275 active_instances = list_instances.GetLocalInstancesByNames(local_names) 276 inactive_names = local_names.difference(ins.name for ins in 277 active_instances) 278 if active_instances: 279 utils.PrintColorString("Deleting local instances") 280 delete_report = DeleteInstances(cfg, active_instances) 281 if inactive_names: 282 utils.PrintColorString("Unlocking local instances") 283 for name in inactive_names: 284 ResetLocalInstanceLockByName(name, delete_report) 285 if remote_names: 286 delete_report = DeleteRemoteInstances(cfg, remote_names, delete_report) 287 return delete_report 288 289 290def Run(args): 291 """Run delete. 292 293 After delete command executed, tool will return one Report instance. 294 If there is no instance to delete, just reutrn empty Report. 295 296 Args: 297 args: Namespace object from argparse.parse_args. 298 299 Returns: 300 A Report instance. 301 """ 302 # Prioritize delete instances by names without query all instance info from 303 # GCP project. 304 cfg = config.GetAcloudConfig(args) 305 if args.instance_names: 306 return DeleteInstanceByNames(cfg, 307 args.instance_names) 308 if args.remote_host: 309 return CleanUpRemoteHost(cfg, args.remote_host, args.host_user, 310 args.host_ssh_private_key_path) 311 312 instances = list_instances.GetLocalInstances() 313 if not args.local_only and cfg.SupportRemoteInstance(): 314 instances.extend(list_instances.GetRemoteInstances(cfg)) 315 316 if args.adb_port: 317 instances = list_instances.FilterInstancesByAdbPort(instances, 318 args.adb_port) 319 elif not args.all: 320 # Provide instances list to user and let user choose what to delete if 321 # user didn't specify instances in args. 322 instances = list_instances.ChooseInstancesFromList(instances) 323 324 if not instances: 325 utils.PrintColorString("No instances to delete") 326 return DeleteInstances(cfg, instances) 327