普通文本  |  588行  |  24.74 KB

#
# Copyright (C) 2017 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.
#

import logging

from vts.proto import AndroidSystemControlMessage_pb2 as ASysCtrlMsg
from vts.runners.host import const
from vts.runners.host import errors
from vts.runners.host.tcp_client import vts_tcp_client
from vts.runners.host.tcp_server import callback_server
from vts.utils.python.mirror import hal_mirror
from vts.utils.python.mirror import lib_mirror
from vts.utils.python.mirror import shell_mirror
from vts.utils.python.mirror import resource_mirror

_DEFAULT_TARGET_BASE_PATHS = ["/system/lib64/hw"]
_DEFAULT_HWBINDER_SERVICE = "default"
_DEFAULT_SHELL_NAME = "_default"
_MAX_ADB_SHELL_LENGTH = 950


class MirrorTracker(object):
    """The class tracks all mirror objects on the host side.

    Attributes:
        _host_command_port: int, the host-side port for command-response
                            sessions.
        _host_callback_port: int, the host-side port for callback sessions.
        _adb: An AdbProxy object used for interacting with the device via adb.
        _registered_mirrors: dict, key is mirror handler name, value is the
                             mirror object.
        _callback_server: VtsTcpServer, the server that receives and handles
                          callback messages from target side.
        shell_default_nohup: bool, whether to use nohup by default in shell commands.
    """

    def __init__(self,
                 host_command_port,
                 host_callback_port=None,
                 start_callback_server=False,
                 adb=None):
        self._host_command_port = host_command_port
        self._host_callback_port = host_callback_port
        self._adb = adb
        self._registered_mirrors = {}
        self._callback_server = None
        self.shell_default_nohup = False
        if start_callback_server:
            self._StartCallbackServer()

    def __del__(self):
        self.CleanUp()

    def CleanUp(self):
        """Shutdown services and release resources held by the registered mirrors.
        """
        for mirror in self._registered_mirrors.values():
            mirror.CleanUp()
        self._registered_mirrors = {}
        if self._callback_server:
            self._callback_server.Stop()
            self._callback_server = None

    def RemoveMirror(self, mirror_name):
        self._registered_mirrors[mirror_name].CleanUp()
        self._registered_mirrors.pop(mirror_name)

    def _StartCallbackServer(self):
        """Starts the callback server.

        Raises:
            errors.ComponentLoadingError is raised if the callback server fails
            to start.
        """
        self._callback_server = callback_server.CallbackServer()
        _, port = self._callback_server.Start(self._host_callback_port)
        if port != self._host_callback_port:
            raise errors.ComponentLoadingError(
                "Failed to start a callback TcpServer at port %s" %
                self._host_callback_port)

    def Heal(self):
        """Performs a self healing.

        Includes self diagnosis that looks for any framework errors.

        Returns:
            bool, True if everything is ok; False otherwise.
        """
        res = all(map(lambda shell: shell.Heal(), self._registered_mirrors.values()))

        if not res:
            logging.error('Self diagnosis found problems in mirror_tracker.')

        return res

    def InitFmq(self,
                existing_queue=None,
                new_queue_name=None,
                data_type="uint16_t",
                sync=True,
                queue_size=0,
                blocking=False,
                reset_pointers=True,
                client=None):
        """Initializes a fast message queue object.

        This method will initialize a fast message queue object on the target side,
        create a mirror object for the FMQ, and register it in the tracker.

        Args:
            existing_queue: string or MirrorObject.
                This argument identifies an existing queue mirror object.
                If specified, it will tell the target driver to create a
                new message queue object based on an existing message queue.
                If it is None, that means creating a brand new message queue.
            new_queue_name: string, name of the new queue, used as key in the tracker.
                If not specified, this function dynamically generates a name.
            data_type: string, type of data in the queue.
            sync: bool, whether the queue is synchronized (only has one reader).
            queue_size: int, size of the queue.
            blocking: bool, whether blocking is enabled.
            reset_pointers: bool, whether to reset read/write pointers when
                creating a new message queue object based on an existing message queue.
            client: VtsTcpClient, if an existing session should be used.
                If not specified, creates a new one.

        Returns:
            ResourcFmqMirror object,
            it allows users to directly call methods on the mirror object.
        """
        # Check if queue name already exists in tracker.
        if new_queue_name is not None and new_queue_name in self._registered_mirrors:
            logging.error("Queue name already exists in tracker.")
            return None

        # Need to initialize a client if caller doesn't provide one.
        if client is None:
            client = vts_tcp_client.VtsTcpClient()
            client.Connect(
                command_port=self._host_command_port,
                callback_port=self._host_callback_port)

        # Create a new queue by default.
        existing_queue_id = -1
        # Check if caller wants to create a queue object based on
        # an existing queue object.
        if existing_queue is not None:
            # Check if caller provides a string.
            if type(existing_queue) == str:
                if existing_queue in self._registered_mirrors:
                    data_type = self._registered_mirrors[
                        existing_queue].dataType
                    sync = self._registered_mirrors[
                        existing_queue].sync
                    existing_queue_id = self._registered_mirrors[
                        existing_queue].queueId
                else:
                    logging.error("Nonexisting queue name in mirror_tracker.")
                    return None
            # Check if caller provides a resource mirror object.
            elif isinstance(existing_queue, resource_mirror.ResourceFmqMirror):
                data_type = existing_queue.dataType
                sync = existing_queue.sync
                existing_queue_id = existing_queue.queueId
            else:
                logging.error(
                    "Unsupported way of finding an existing queue object.")
                return None

        # Create a resource mirror object.
        mirror = resource_mirror.ResourceFmqMirror(data_type, sync, client)
        mirror._create(existing_queue_id, queue_size, blocking, reset_pointers)
        if mirror.queueId == -1:
            # Failed to create queue object, error logged in resource_mirror.
            return None

        # Needs to dynamically generate queue name if caller doesn't provide one
        if new_queue_name is None:
            new_queue_name = "queue_id_" + str(mirror._queue_id)
        self._registered_mirrors[new_queue_name] = mirror
        return mirror

    def InitHidlMemory(self, mem_size=0, client=None, mem_name=None):
        """Initialize a hidl_memory object.

        This method will initialize a hidl_memory object on the target side,
        create a mirror object, and register it in the tracker.

        Args:
            mem_size: int, size of the memory region.
            client: VtsTcpClient, if an existing session should be used.
                If not specified, creates a new one.
            mem_name: string, name of the memory region.
                If not specified, dynamically assign the memory region a name.

        Returns:
            ResourceHidlMemoryMirror object,
            it allows users to directly call methods on the mirror object.
        """
        # Check if mem_name already exists in tracker.
        if mem_name is not None and mem_name in self._registered_mirrors:
            logging.error("Memory name already exists in tracker.")
            return None

        # Need to initialize a client if caller doesn't provide one.
        if client is None:
            client = vts_tcp_client.VtsTcpClient()
            client.Connect(
                command_port=self._host_command_port,
                callback_port=self._host_callback_port)

        # Create a resource_mirror object.
        mirror = resource_mirror.ResourceHidlMemoryMirror(client)
        mirror._allocate(mem_size)
        if mirror.memId == -1:
            # Failed to create memory object, error logged in resource_mirror.
            return None

        # Need to dynamically assign a memory name
        # if caller doesn't provide one.
        if mem_name is None:
            mem_name = "mem_id_" + str(mirror._mem_id)
        self._registered_mirrors[mem_name] = mirror
        return mirror

    def InitHidlHandleForSingleFile(self,
                                    filepath,
                                    mode,
                                    ints=[],
                                    client=None,
                                    handle_name=None):
        """Initialize a hidl_handle object.

        This method will initialize a hidl_handle object on the target side,
        create a mirror object, and register it in the tracker.
        TODO: Currently only support creating a handle for a single file.
        In the future, need to support arbitrary file descriptor types
        (e.g. socket, pipe), and more than one file.

        Args:
            filepath: string, path to the file.
            mode: string, specifying the mode to open the file.
            ints: int list, useful integers to be stored in handle object.
            client: VtsTcpClient, if an existing session should be used.
                If not specified, create a new one.
            handle_name: string, name of the handle object.
                If not specified, dynamically assign the handle object a name.

        Returns:
            ResourceHidlHandleMirror object,
            it allows users to directly call methods on the mirror object.
        """
        # Check if handle_name already exists in tracker.
        if handle_name is not None and handle_name in self._registered_mirrors:
            logging.error("Handle name already exists in tracker.")
            return None

        # Need to initialize a client if caller doesn't provide one.
        if not client:
            client = vts_tcp_client.VtsTcpClient()
            client.Connect(
                command_port=self._host_command_port,
                callback_port=self._host_callback_port)

        # Create a resource_mirror object.
        mirror = resource_mirror.ResourceHidlHandleMirror(client)
        mirror._createHandleForSingleFile(filepath, mode, ints)
        if mirror.handleId == -1:
            # Failed to create handle object, error logged in resource_mirror.
            return None

        # Need to dynamically assign a handle name
        # if caller doesn't provide one.
        if handle_name is None:
            handle_name = "handle_id_" + str(mirror._handle_id)
        self._registered_mirrors[handle_name] = mirror
        return mirror

    def InitHidlHal(self,
                    target_type,
                    target_version=None,
                    target_package=None,
                    target_component_name=None,
                    target_basepaths=_DEFAULT_TARGET_BASE_PATHS,
                    handler_name=None,
                    hw_binder_service_name=_DEFAULT_HWBINDER_SERVICE,
                    bits=64,
                    target_version_major=None,
                    target_version_minor=None,
                    is_test_hal=False):
        """Initiates a handler for a particular HIDL HAL.

        This will initiate a driver service for a HAL on the target side, create
        a mirror object for a HAL, and register it in the tracker.

        Args:
            target_type: string, the target type name (e.g., light, camera).
            target_version (deprecated, now use major and minor versions):
              float, the target component version (e.g., 1.0).
            target_package: string, the package name of a target HIDL HAL.
            target_basepaths: list of strings, the paths to look for target
                              files in. Default is _DEFAULT_TARGET_BASE_PATHS.
            handler_name: string, the name of the handler. target_type is used
                          by default.
            hw_binder_service_name: string, the name of a HW binder service.
            bits: integer, processor architecture indicator: 32 or 64.
            target_version_major:
              int, the target component major version (e.g., 1.0 -> 1).
            target_version_minor:
              int, the target component minor version (e.g., 1.0 -> 0).
              If host doesn't provide major and minor versions separately,
              parse it from the float version of target_version.
            is_test_hal: bool, whether the HAL service is a test HAL
                         (e.g. msgq).

        Raises:
            USERError if user doesn't provide a version of the HAL service.
        """
        target_version_major, target_version_minor = self.GetTargetVersion(
            target_version, target_version_major, target_version_minor)
        if not handler_name:
            handler_name = target_type
        client = vts_tcp_client.VtsTcpClient()
        client.Connect(
            command_port=self._host_command_port,
            callback_port=self._host_callback_port)
        mirror = hal_mirror.HalMirror(client, self._callback_server)
        mirror.InitHalDriver(target_type, target_version_major,
                             target_version_minor, target_package,
                             target_component_name, hw_binder_service_name,
                             handler_name, bits, is_test_hal)
        self._registered_mirrors[target_type] = mirror

    def InitSharedLib(self,
                      target_type,
                      target_version=None,
                      target_basepaths=_DEFAULT_TARGET_BASE_PATHS,
                      target_package="",
                      target_filename=None,
                      handler_name=None,
                      bits=64,
                      target_version_major=None,
                      target_version_minor=None):
        """Initiates a handler for a particular lib.

        This will initiate a driver service for a lib on the target side, create
        a mirror object for a lib, and register it in the tracker.

        Args:
            target_type: string, the target type name (e.g., light, camera).
            target_version (deprecated, now use major and minor versions):
              float, the target component version (e.g., 1.0).
            target_basepaths: list of strings, the paths to look for target
                             files in. Default is _DEFAULT_TARGET_BASE_PATHS.
            target_package: . separated string (e.g., a.b.c) to denote the
                            package name of target component.
            target_filename: string, the target file name (e.g., libm.so).
            handler_name: string, the name of the handler. target_type is used
                          by default.
            bits: integer, processor architecture indicator: 32 or 64.
            target_version_major:
              int, the target component major version (e.g., 1.0 -> 1).
            target_version_minor:
              int, the target component minor version (e.g., 1.0 -> 0).
            If host doesn't provide major and minor versions separately,
            parse it from the float version of target_version.

        Raises:
            USERError if user doesn't provide a version of the HAL service.
        """
        target_version_major, target_version_minor = self.GetTargetVersion(
            target_version, target_version_major, target_version_minor)
        if not handler_name:
            handler_name = target_type
        client = vts_tcp_client.VtsTcpClient()
        client.Connect(command_port=self._host_command_port)
        mirror = lib_mirror.LibMirror(client)
        mirror.InitLibDriver(target_type, target_version_major,
                             target_version_minor, target_package,
                             target_filename, target_basepaths, handler_name,
                             bits)
        self._registered_mirrors[handler_name] = mirror

    def InvokeTerminal(self, instance_name, bits=32):
        """Initiates a handler for a particular shell terminal.

        This will initiate a driver service for a shell on the target side,
        create a mirror object for the shell, and register it in the tracker.

        Args:
            instance_name: string, the shell terminal instance name.
            bits: integer, processor architecture indicator: 32 or 64.
        """
        if not instance_name:
            raise error.ComponentLoadingError("instance_name is None")
        if bits not in [32, 64]:
            raise error.ComponentLoadingError(
                "Invalid value for bits: %s" % bits)

        if instance_name in self._registered_mirrors:
            logging.warning("shell driver %s already exists", instance_name)
            return

        client = vts_tcp_client.VtsTcpClient()
        client.Connect(command_port=self._host_command_port)

        logging.debug("Init the driver service for shell, %s", instance_name)
        launched = client.LaunchDriverService(
            driver_type=ASysCtrlMsg.VTS_DRIVER_TYPE_SHELL,
            service_name="shell_" + instance_name,
            bits=bits)

        if not launched:
            raise errors.ComponentLoadingError(
                "Failed to launch shell driver service %s" % instance_name)

        mirror = shell_mirror.ShellMirror(client, self._adb)
        self._registered_mirrors[instance_name] = mirror

    def DisableShell(self):
        """Disables all registered shell mirrors."""
        for mirror in self._registered_mirrors.values():
            if not isinstance(mirror, shell_mirror.ShellMirror):
                logging.error("mirror object is not a shell mirror")
                continue
            mirror.enabled = False

    def Execute(self, commands, no_except=False, nohup=None):
        """Execute shell command(s).

        This method automatically decide whether to use adb shell or vts shell
        driver on the device based on performance benchmark results.

        The difference in the decision logic will only have impact on the performance, but
        will be transparent to the user of this method.

        The current logic is:
            1. If shell_default_nohup is disabled and command
               list size is smaller or equal than 3, use adb shell. Otherwise, use
              shell driver (with nohup).

            2. If adb shell is used, no_except will always be true.

            This is subject to further optimization.

        Args:
            commands: string or list or tuple, commands to execute on device.
            no_except: bool, whether to throw exceptions. If set to True,
                       when exception happens, return code will be -1 and
                       str(err) will be in stderr. Result will maintain the
                       same length as with input commands.
            nohup: bool or None, True for using nohup for shell commands; False for
                   not using nohup; None for using default setting.

        Returns:
            dictionary of list, command results that contains stdout,
            stderr, and exit_code.
        """
        if not isinstance(commands, (list, tuple)):
            commands = [commands]

        if nohup is None:
            nohup = self.shell_default_nohup

        # TODO(yuexima): further optimize the threshold and nohup adb command
        non_nohup_adb_threshold = 3
        if (not nohup and len(commands) <= non_nohup_adb_threshold
            and not filter(lambda cmd: len(cmd) > _MAX_ADB_SHELL_LENGTH, commands)):
            return self._ExecuteShellCmdViaAdbShell(commands)
        else:
            return self._ExecuteShellCmdViaVtsDriver(commands, no_except)

    def _ExecuteShellCmdViaVtsDriver(self, commands, no_except):
        """Execute shell command(s) using default shell terminal.

        Args:
            commands: string or list or tuple, commands to execute on device
            no_except: bool, whether to throw exceptions. If set to True,
                       when exception happens, return code will be -1 and
                       str(err) will be in stderr. Result will maintain the
                       same length as with input command.

        Returns:
            dictionary of list, command results that contains stdout,
            stderr, and exit_code.
        """
        if _DEFAULT_SHELL_NAME not in self._registered_mirrors:
            self.InvokeTerminal(_DEFAULT_SHELL_NAME)

        return getattr(self, _DEFAULT_SHELL_NAME).Execute(commands, no_except)

    def _ExecuteShellCmdViaAdbShell(self, commands):
        """Execute shell command(s) using adb shell command.

        Args:
            commands: string or list or tuple, command to execute on device

        Returns:
            dictionary of list, command results that contains stdout,
            stderr, and exit_code.
        """
        all = {const.STDOUT: [],
               const.STDERR: [],
               const.EXIT_CODE: []}

        for cmd in commands:
            res = self._adb.shell(cmd, no_except=True)
            all[const.STDOUT].append(res[const.STDOUT])
            all[const.STDERR].append(res[const.STDERR])
            all[const.EXIT_CODE].append(res[const.EXIT_CODE])

        return all

    def SetConnTimeout(self, timeout):
        """Set remove shell connection timeout for default shell terminal.

        Args:
            timeout: int, TCP connection timeout in seconds.
        """
        if _DEFAULT_SHELL_NAME not in self._registered_mirrors:
            self.InvokeTerminal(_DEFAULT_SHELL_NAME)
        getattr(self, _DEFAULT_SHELL_NAME).SetConnTimeout(timeout)

    def GetTargetVersion(self, target_version, target_version_major,
                         target_version_minor):
        """Get the actual target version provided by the host.

        If the host provides major and minor versions separately, directly return them.
        Otherwise, manually parse it from the float version.
        If all of them are None, raise a user error.

        Args:
            target_version: float, the target component HAL version (e.g. 1.0).
            target_version_major:
                int, the target component HAL major version (e.g. 1.0 -> 1).
            target_version_minor:
                int, the target component HAL minor version (e.g. 1.0 -> 0).

        Returns:
            two integers, actual major and minor HAL versions.

        Raises: user error, if no version is provided.
        """
        # Check if host provides major and minor versions separately
        if (target_version_minor != None and target_version_minor != None):
            return target_version_major, target_version_minor

        # If not, manually parse it from float version
        if (target_version != None):
            target_version_str = str(target_version)
            [target_version_major,
             target_version_minor] = target_version_str.split(".")
            return int(target_version_major), int(target_version_minor)

        raise errors.USERError("User has to provide a target version.")

    def GetTcpClient(self, mirror_name):
        """Gets the TCP client used in this tracker.
        Useful for reusing session to access shared data.

        Args:
            mirror_name: used to identify mirror object.
        """
        if mirror_name in self._registered_mirrors:
            return self._registered_mirrors[mirror_name]._client
        return None

    def __getattr__(self, name):
        if name in self._registered_mirrors:
            return self._registered_mirrors[name]
        else:
            logging.error("No mirror found with name: %s", name)
            return None