普通文本  |  212行  |  7.11 KB

# 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))