588 lines
25 KiB
Python
588 lines
25 KiB
Python
#
|
|
# 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
|