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