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