| # Copyright 2018 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| r"""Delete entry point. |
| |
| Delete will handle all the logic related to deleting a local/remote instance |
| of an Android Virtual Device. |
| """ |
| |
| from __future__ import print_function |
| from distutils.spawn import find_executable |
| import logging |
| import os |
| import re |
| import subprocess |
| |
| from acloud import errors |
| from acloud.internal import constants |
| from acloud.internal.lib import utils |
| from acloud.list import list as list_instances |
| from acloud.list import instance as instance_class |
| from acloud.public import config |
| from acloud.public import device_driver |
| from acloud.public import report |
| |
| logger = logging.getLogger(__name__) |
| |
| _COMMAND_GET_PROCESS_ID = ["pgrep", "launch_cvd"] |
| _COMMAND_GET_PROCESS_COMMAND = ["ps", "-o", "command", "-p"] |
| _RE_LAUNCH_CVD = re.compile(r"^(?P<launch_cvd>.+launch_cvd)(.*--daemon ).+") |
| _SSVNC_VIEWER_PATTERN = "vnc://127.0.0.1:%(vnc_port)d" |
| |
| |
| def _GetStopCvd(): |
| """Get stop_cvd path. |
| |
| "stop_cvd" and "launch_cvd" are in the same folder(host package folder). |
| Try to get directory of "launch_cvd" by "ps -o command -p <pid>." command. |
| For example: "/tmp/bin/launch_cvd --daemon --cpus 2 ..." |
| |
| Raises: |
| errors.NoExecuteCmd: Can't find stop_cvd. |
| |
| Returns: |
| String of stop_cvd file path. |
| """ |
| process_id = subprocess.check_output(_COMMAND_GET_PROCESS_ID) |
| process_info = subprocess.check_output( |
| _COMMAND_GET_PROCESS_COMMAND + process_id.splitlines()) |
| for process in process_info.splitlines(): |
| match = _RE_LAUNCH_CVD.match(process) |
| if match: |
| launch_cvd_path = match.group("launch_cvd") |
| stop_cvd_cmd = os.path.join(os.path.dirname(launch_cvd_path), |
| constants.CMD_STOP_CVD) |
| if os.path.exists(stop_cvd_cmd): |
| logger.debug("stop_cvd command: %s", stop_cvd_cmd) |
| return stop_cvd_cmd |
| |
| default_stop_cvd = find_executable(constants.CMD_STOP_CVD) |
| if default_stop_cvd: |
| return default_stop_cvd |
| |
| raise errors.NoExecuteCmd("Cannot find stop_cvd binary.") |
| |
| |
| def CleanupSSVncviewer(vnc_port): |
| """Cleanup the old disconnected ssvnc viewer. |
| |
| Args: |
| vnc_port: Integer, port number of vnc. |
| """ |
| ssvnc_viewer_pattern = _SSVNC_VIEWER_PATTERN % {"vnc_port":vnc_port} |
| utils.CleanupProcess(ssvnc_viewer_pattern) |
| |
| |
| def DeleteInstances(cfg, instances_to_delete): |
| """Delete instances according to instances_to_delete. |
| |
| Args: |
| cfg: AcloudConfig object. |
| instances_to_delete: List of list.Instance() object. |
| |
| Returns: |
| Report instance if there are instances to delete, None otherwise. |
| """ |
| if not instances_to_delete: |
| print("No instances to delete") |
| return None |
| |
| delete_report = None |
| remote_instance_list = [] |
| for instance in instances_to_delete: |
| if instance.islocal: |
| delete_report = DeleteLocalInstance() |
| else: |
| remote_instance_list.append(instance.name) |
| # Delete ssvnc viewer |
| if instance.forwarding_vnc_port: |
| CleanupSSVncviewer(instance.forwarding_vnc_port) |
| |
| if remote_instance_list: |
| # TODO(119283708): We should move DeleteAndroidVirtualDevices into |
| # delete.py after gce is deprecated. |
| # Stop remote instances. |
| return DeleteRemoteInstances(cfg, remote_instance_list, delete_report) |
| |
| return delete_report |
| |
| |
| @utils.TimeExecute(function_description="Deleting remote instances", |
| result_evaluator=utils.ReportEvaluator, |
| display_waiting_dots=False) |
| def DeleteRemoteInstances(cfg, instances_to_delete, delete_report=None): |
| """Delete remote instances. |
| |
| Args: |
| cfg: AcloudConfig object. |
| instances_to_delete: List of instance names(string). |
| delete_report: Report object. |
| |
| Returns: |
| Report instance if there are instances to delete, None otherwise. |
| """ |
| utils.PrintColorString("") |
| for instance in instances_to_delete: |
| utils.PrintColorString(" - %s" % instance, utils.TextColors.WARNING) |
| utils.PrintColorString("") |
| utils.PrintColorString("status: waiting...", end="") |
| |
| # TODO(119283708): We should move DeleteAndroidVirtualDevices into |
| # delete.py after gce is deprecated. |
| # Stop remote instances. |
| delete_report = device_driver.DeleteAndroidVirtualDevices( |
| cfg, instances_to_delete, delete_report) |
| |
| return delete_report |
| |
| |
| @utils.TimeExecute(function_description="Deleting local instances", |
| result_evaluator=utils.ReportEvaluator) |
| def DeleteLocalInstance(): |
| """Delete local instance. |
| |
| Delete local instance with stop_cvd command and write delete instance |
| information to report. |
| |
| Returns: |
| A Report instance. |
| """ |
| delete_report = report.Report(command="delete") |
| try: |
| with open(os.devnull, "w") as dev_null: |
| subprocess.check_call( |
| utils.AddUserGroupsToCmd(_GetStopCvd(), |
| constants.LIST_CF_USER_GROUPS), |
| stderr=dev_null, stdout=dev_null, shell=True) |
| delete_report.SetStatus(report.Status.SUCCESS) |
| device_driver.AddDeletionResultToReport( |
| delete_report, [constants.LOCAL_INS_NAME], failed=[], |
| error_msgs=[], |
| resource_name="instance") |
| except subprocess.CalledProcessError as e: |
| delete_report.AddError(str(e)) |
| delete_report.SetStatus(report.Status.FAIL) |
| # Only CF supports local instances so assume it's a CF VNC port. |
| CleanupSSVncviewer(constants.CF_VNC_PORT) |
| return delete_report |
| |
| |
| def Run(args): |
| """Run delete. |
| |
| After delete command executed, tool will return one Report instance. |
| If there is no instance to delete, just reutrn empty Report. |
| |
| Args: |
| args: Namespace object from argparse.parse_args. |
| |
| Returns: |
| A Report instance. |
| """ |
| cfg = config.GetAcloudConfig(args) |
| remote_instances_to_delete = args.instance_names |
| |
| if remote_instances_to_delete: |
| return DeleteRemoteInstances(cfg, remote_instances_to_delete) |
| |
| if args.local_instance: |
| if instance_class.LocalInstance(): |
| return DeleteLocalInstance() |
| print("There is no local instance AVD to delete.") |
| return report.Report(command="delete") |
| |
| if args.adb_port: |
| return DeleteInstances( |
| cfg, list_instances.GetInstanceFromAdbPort(cfg, args.adb_port)) |
| |
| # Provide instances list to user and let user choose what to delete if user |
| # didn't specific instance name in args. |
| return DeleteInstances(cfg, list_instances.ChooseInstances(cfg, args.all)) |