1# 2# Copyright (C) 2017 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import logging 18 19from vts.proto import AndroidSystemControlMessage_pb2 as ASysCtrlMsg 20from vts.runners.host import const 21from vts.runners.host import errors 22from vts.runners.host.tcp_client import vts_tcp_client 23from vts.runners.host.tcp_server import callback_server 24from vts.utils.python.mirror import hal_mirror 25from vts.utils.python.mirror import lib_mirror 26from vts.utils.python.mirror import shell_mirror 27from vts.utils.python.mirror import resource_mirror 28 29_DEFAULT_TARGET_BASE_PATHS = ["/system/lib64/hw"] 30_DEFAULT_HWBINDER_SERVICE = "default" 31_DEFAULT_SHELL_NAME = "_default" 32_MAX_ADB_SHELL_LENGTH = 950 33 34 35class MirrorTracker(object): 36 """The class tracks all mirror objects on the host side. 37 38 Attributes: 39 _host_command_port: int, the host-side port for command-response 40 sessions. 41 _host_callback_port: int, the host-side port for callback sessions. 42 _adb: An AdbProxy object used for interacting with the device via adb. 43 _registered_mirrors: dict, key is mirror handler name, value is the 44 mirror object. 45 _callback_server: VtsTcpServer, the server that receives and handles 46 callback messages from target side. 47 shell_default_nohup: bool, whether to use nohup by default in shell commands. 48 """ 49 50 def __init__(self, 51 host_command_port, 52 host_callback_port=None, 53 start_callback_server=False, 54 adb=None): 55 self._host_command_port = host_command_port 56 self._host_callback_port = host_callback_port 57 self._adb = adb 58 self._registered_mirrors = {} 59 self._callback_server = None 60 self.shell_default_nohup = False 61 if start_callback_server: 62 self._StartCallbackServer() 63 64 def __del__(self): 65 self.CleanUp() 66 67 def CleanUp(self): 68 """Shutdown services and release resources held by the registered mirrors. 69 """ 70 for mirror in self._registered_mirrors.values(): 71 mirror.CleanUp() 72 self._registered_mirrors = {} 73 if self._callback_server: 74 self._callback_server.Stop() 75 self._callback_server = None 76 77 def RemoveMirror(self, mirror_name): 78 self._registered_mirrors[mirror_name].CleanUp() 79 self._registered_mirrors.pop(mirror_name) 80 81 def _StartCallbackServer(self): 82 """Starts the callback server. 83 84 Raises: 85 errors.ComponentLoadingError is raised if the callback server fails 86 to start. 87 """ 88 self._callback_server = callback_server.CallbackServer() 89 _, port = self._callback_server.Start(self._host_callback_port) 90 if port != self._host_callback_port: 91 raise errors.ComponentLoadingError( 92 "Failed to start a callback TcpServer at port %s" % 93 self._host_callback_port) 94 95 def Heal(self): 96 """Performs a self healing. 97 98 Includes self diagnosis that looks for any framework errors. 99 100 Returns: 101 bool, True if everything is ok; False otherwise. 102 """ 103 res = all(map(lambda shell: shell.Heal(), self._registered_mirrors.values())) 104 105 if not res: 106 logging.error('Self diagnosis found problems in mirror_tracker.') 107 108 return res 109 110 def InitFmq(self, 111 existing_queue=None, 112 new_queue_name=None, 113 data_type="uint16_t", 114 sync=True, 115 queue_size=0, 116 blocking=False, 117 reset_pointers=True, 118 client=None): 119 """Initializes a fast message queue object. 120 121 This method will initialize a fast message queue object on the target side, 122 create a mirror object for the FMQ, and register it in the tracker. 123 124 Args: 125 existing_queue: string or MirrorObject. 126 This argument identifies an existing queue mirror object. 127 If specified, it will tell the target driver to create a 128 new message queue object based on an existing message queue. 129 If it is None, that means creating a brand new message queue. 130 new_queue_name: string, name of the new queue, used as key in the tracker. 131 If not specified, this function dynamically generates a name. 132 data_type: string, type of data in the queue. 133 sync: bool, whether the queue is synchronized (only has one reader). 134 queue_size: int, size of the queue. 135 blocking: bool, whether blocking is enabled. 136 reset_pointers: bool, whether to reset read/write pointers when 137 creating a new message queue object based on an existing message queue. 138 client: VtsTcpClient, if an existing session should be used. 139 If not specified, creates a new one. 140 141 Returns: 142 ResourcFmqMirror object, 143 it allows users to directly call methods on the mirror object. 144 """ 145 # Check if queue name already exists in tracker. 146 if new_queue_name is not None and new_queue_name in self._registered_mirrors: 147 logging.error("Queue name already exists in tracker.") 148 return None 149 150 # Need to initialize a client if caller doesn't provide one. 151 if client is None: 152 client = vts_tcp_client.VtsTcpClient() 153 client.Connect( 154 command_port=self._host_command_port, 155 callback_port=self._host_callback_port) 156 157 # Create a new queue by default. 158 existing_queue_id = -1 159 # Check if caller wants to create a queue object based on 160 # an existing queue object. 161 if existing_queue is not None: 162 # Check if caller provides a string. 163 if type(existing_queue) == str: 164 if existing_queue in self._registered_mirrors: 165 data_type = self._registered_mirrors[ 166 existing_queue].dataType 167 sync = self._registered_mirrors[ 168 existing_queue].sync 169 existing_queue_id = self._registered_mirrors[ 170 existing_queue].queueId 171 else: 172 logging.error("Nonexisting queue name in mirror_tracker.") 173 return None 174 # Check if caller provides a resource mirror object. 175 elif isinstance(existing_queue, resource_mirror.ResourceFmqMirror): 176 data_type = existing_queue.dataType 177 sync = existing_queue.sync 178 existing_queue_id = existing_queue.queueId 179 else: 180 logging.error( 181 "Unsupported way of finding an existing queue object.") 182 return None 183 184 # Create a resource mirror object. 185 mirror = resource_mirror.ResourceFmqMirror(data_type, sync, client) 186 mirror._create(existing_queue_id, queue_size, blocking, reset_pointers) 187 if mirror.queueId == -1: 188 # Failed to create queue object, error logged in resource_mirror. 189 return None 190 191 # Needs to dynamically generate queue name if caller doesn't provide one 192 if new_queue_name is None: 193 new_queue_name = "queue_id_" + str(mirror._queue_id) 194 self._registered_mirrors[new_queue_name] = mirror 195 return mirror 196 197 def InitHidlMemory(self, mem_size=0, client=None, mem_name=None): 198 """Initialize a hidl_memory object. 199 200 This method will initialize a hidl_memory object on the target side, 201 create a mirror object, and register it in the tracker. 202 203 Args: 204 mem_size: int, size of the memory region. 205 client: VtsTcpClient, if an existing session should be used. 206 If not specified, creates a new one. 207 mem_name: string, name of the memory region. 208 If not specified, dynamically assign the memory region a name. 209 210 Returns: 211 ResourceHidlMemoryMirror object, 212 it allows users to directly call methods on the mirror object. 213 """ 214 # Check if mem_name already exists in tracker. 215 if mem_name is not None and mem_name in self._registered_mirrors: 216 logging.error("Memory name already exists in tracker.") 217 return None 218 219 # Need to initialize a client if caller doesn't provide one. 220 if client is None: 221 client = vts_tcp_client.VtsTcpClient() 222 client.Connect( 223 command_port=self._host_command_port, 224 callback_port=self._host_callback_port) 225 226 # Create a resource_mirror object. 227 mirror = resource_mirror.ResourceHidlMemoryMirror(client) 228 mirror._allocate(mem_size) 229 if mirror.memId == -1: 230 # Failed to create memory object, error logged in resource_mirror. 231 return None 232 233 # Need to dynamically assign a memory name 234 # if caller doesn't provide one. 235 if mem_name is None: 236 mem_name = "mem_id_" + str(mirror._mem_id) 237 self._registered_mirrors[mem_name] = mirror 238 return mirror 239 240 def InitHidlHandleForSingleFile(self, 241 filepath, 242 mode, 243 ints=[], 244 client=None, 245 handle_name=None): 246 """Initialize a hidl_handle object. 247 248 This method will initialize a hidl_handle object on the target side, 249 create a mirror object, and register it in the tracker. 250 TODO: Currently only support creating a handle for a single file. 251 In the future, need to support arbitrary file descriptor types 252 (e.g. socket, pipe), and more than one file. 253 254 Args: 255 filepath: string, path to the file. 256 mode: string, specifying the mode to open the file. 257 ints: int list, useful integers to be stored in handle object. 258 client: VtsTcpClient, if an existing session should be used. 259 If not specified, create a new one. 260 handle_name: string, name of the handle object. 261 If not specified, dynamically assign the handle object a name. 262 263 Returns: 264 ResourceHidlHandleMirror object, 265 it allows users to directly call methods on the mirror object. 266 """ 267 # Check if handle_name already exists in tracker. 268 if handle_name is not None and handle_name in self._registered_mirrors: 269 logging.error("Handle name already exists in tracker.") 270 return None 271 272 # Need to initialize a client if caller doesn't provide one. 273 if not client: 274 client = vts_tcp_client.VtsTcpClient() 275 client.Connect( 276 command_port=self._host_command_port, 277 callback_port=self._host_callback_port) 278 279 # Create a resource_mirror object. 280 mirror = resource_mirror.ResourceHidlHandleMirror(client) 281 mirror._createHandleForSingleFile(filepath, mode, ints) 282 if mirror.handleId == -1: 283 # Failed to create handle object, error logged in resource_mirror. 284 return None 285 286 # Need to dynamically assign a handle name 287 # if caller doesn't provide one. 288 if handle_name is None: 289 handle_name = "handle_id_" + str(mirror._handle_id) 290 self._registered_mirrors[handle_name] = mirror 291 return mirror 292 293 def InitHidlHal(self, 294 target_type, 295 target_version=None, 296 target_package=None, 297 target_component_name=None, 298 target_basepaths=_DEFAULT_TARGET_BASE_PATHS, 299 handler_name=None, 300 hw_binder_service_name=_DEFAULT_HWBINDER_SERVICE, 301 bits=64, 302 target_version_major=None, 303 target_version_minor=None, 304 is_test_hal=False): 305 """Initiates a handler for a particular HIDL HAL. 306 307 This will initiate a driver service for a HAL on the target side, create 308 a mirror object for a HAL, and register it in the tracker. 309 310 Args: 311 target_type: string, the target type name (e.g., light, camera). 312 target_version (deprecated, now use major and minor versions): 313 float, the target component version (e.g., 1.0). 314 target_package: string, the package name of a target HIDL HAL. 315 target_basepaths: list of strings, the paths to look for target 316 files in. Default is _DEFAULT_TARGET_BASE_PATHS. 317 handler_name: string, the name of the handler. target_type is used 318 by default. 319 hw_binder_service_name: string, the name of a HW binder service. 320 bits: integer, processor architecture indicator: 32 or 64. 321 target_version_major: 322 int, the target component major version (e.g., 1.0 -> 1). 323 target_version_minor: 324 int, the target component minor version (e.g., 1.0 -> 0). 325 If host doesn't provide major and minor versions separately, 326 parse it from the float version of target_version. 327 is_test_hal: bool, whether the HAL service is a test HAL 328 (e.g. msgq). 329 330 Raises: 331 USERError if user doesn't provide a version of the HAL service. 332 """ 333 target_version_major, target_version_minor = self.GetTargetVersion( 334 target_version, target_version_major, target_version_minor) 335 if not handler_name: 336 handler_name = target_type 337 client = vts_tcp_client.VtsTcpClient() 338 client.Connect( 339 command_port=self._host_command_port, 340 callback_port=self._host_callback_port) 341 mirror = hal_mirror.HalMirror(client, self._callback_server) 342 mirror.InitHalDriver(target_type, target_version_major, 343 target_version_minor, target_package, 344 target_component_name, hw_binder_service_name, 345 handler_name, bits, is_test_hal) 346 self._registered_mirrors[target_type] = mirror 347 348 def InitSharedLib(self, 349 target_type, 350 target_version=None, 351 target_basepaths=_DEFAULT_TARGET_BASE_PATHS, 352 target_package="", 353 target_filename=None, 354 handler_name=None, 355 bits=64, 356 target_version_major=None, 357 target_version_minor=None): 358 """Initiates a handler for a particular lib. 359 360 This will initiate a driver service for a lib on the target side, create 361 a mirror object for a lib, and register it in the tracker. 362 363 Args: 364 target_type: string, the target type name (e.g., light, camera). 365 target_version (deprecated, now use major and minor versions): 366 float, the target component version (e.g., 1.0). 367 target_basepaths: list of strings, the paths to look for target 368 files in. Default is _DEFAULT_TARGET_BASE_PATHS. 369 target_package: . separated string (e.g., a.b.c) to denote the 370 package name of target component. 371 target_filename: string, the target file name (e.g., libm.so). 372 handler_name: string, the name of the handler. target_type is used 373 by default. 374 bits: integer, processor architecture indicator: 32 or 64. 375 target_version_major: 376 int, the target component major version (e.g., 1.0 -> 1). 377 target_version_minor: 378 int, the target component minor version (e.g., 1.0 -> 0). 379 If host doesn't provide major and minor versions separately, 380 parse it from the float version of target_version. 381 382 Raises: 383 USERError if user doesn't provide a version of the HAL service. 384 """ 385 target_version_major, target_version_minor = self.GetTargetVersion( 386 target_version, target_version_major, target_version_minor) 387 if not handler_name: 388 handler_name = target_type 389 client = vts_tcp_client.VtsTcpClient() 390 client.Connect(command_port=self._host_command_port) 391 mirror = lib_mirror.LibMirror(client) 392 mirror.InitLibDriver(target_type, target_version_major, 393 target_version_minor, target_package, 394 target_filename, target_basepaths, handler_name, 395 bits) 396 self._registered_mirrors[handler_name] = mirror 397 398 def InvokeTerminal(self, instance_name, bits=32): 399 """Initiates a handler for a particular shell terminal. 400 401 This will initiate a driver service for a shell on the target side, 402 create a mirror object for the shell, and register it in the tracker. 403 404 Args: 405 instance_name: string, the shell terminal instance name. 406 bits: integer, processor architecture indicator: 32 or 64. 407 """ 408 if not instance_name: 409 raise error.ComponentLoadingError("instance_name is None") 410 if bits not in [32, 64]: 411 raise error.ComponentLoadingError( 412 "Invalid value for bits: %s" % bits) 413 414 if instance_name in self._registered_mirrors: 415 logging.warning("shell driver %s already exists", instance_name) 416 return 417 418 client = vts_tcp_client.VtsTcpClient() 419 client.Connect(command_port=self._host_command_port) 420 421 logging.debug("Init the driver service for shell, %s", instance_name) 422 launched = client.LaunchDriverService( 423 driver_type=ASysCtrlMsg.VTS_DRIVER_TYPE_SHELL, 424 service_name="shell_" + instance_name, 425 bits=bits) 426 427 if not launched: 428 raise errors.ComponentLoadingError( 429 "Failed to launch shell driver service %s" % instance_name) 430 431 mirror = shell_mirror.ShellMirror(client, self._adb) 432 self._registered_mirrors[instance_name] = mirror 433 434 def DisableShell(self): 435 """Disables all registered shell mirrors.""" 436 for mirror in self._registered_mirrors.values(): 437 if not isinstance(mirror, shell_mirror.ShellMirror): 438 logging.error("mirror object is not a shell mirror") 439 continue 440 mirror.enabled = False 441 442 def Execute(self, commands, no_except=False, nohup=None): 443 """Execute shell command(s). 444 445 This method automatically decide whether to use adb shell or vts shell 446 driver on the device based on performance benchmark results. 447 448 The difference in the decision logic will only have impact on the performance, but 449 will be transparent to the user of this method. 450 451 The current logic is: 452 1. If shell_default_nohup is disabled and command 453 list size is smaller or equal than 3, use adb shell. Otherwise, use 454 shell driver (with nohup). 455 456 2. If adb shell is used, no_except will always be true. 457 458 This is subject to further optimization. 459 460 Args: 461 commands: string or list or tuple, commands to execute on device. 462 no_except: bool, whether to throw exceptions. If set to True, 463 when exception happens, return code will be -1 and 464 str(err) will be in stderr. Result will maintain the 465 same length as with input commands. 466 nohup: bool or None, True for using nohup for shell commands; False for 467 not using nohup; None for using default setting. 468 469 Returns: 470 dictionary of list, command results that contains stdout, 471 stderr, and exit_code. 472 """ 473 if not isinstance(commands, (list, tuple)): 474 commands = [commands] 475 476 if nohup is None: 477 nohup = self.shell_default_nohup 478 479 # TODO(yuexima): further optimize the threshold and nohup adb command 480 non_nohup_adb_threshold = 3 481 if (not nohup and len(commands) <= non_nohup_adb_threshold 482 and not filter(lambda cmd: len(cmd) > _MAX_ADB_SHELL_LENGTH, commands)): 483 return self._ExecuteShellCmdViaAdbShell(commands) 484 else: 485 return self._ExecuteShellCmdViaVtsDriver(commands, no_except) 486 487 def _ExecuteShellCmdViaVtsDriver(self, commands, no_except): 488 """Execute shell command(s) using default shell terminal. 489 490 Args: 491 commands: string or list or tuple, commands to execute on device 492 no_except: bool, whether to throw exceptions. If set to True, 493 when exception happens, return code will be -1 and 494 str(err) will be in stderr. Result will maintain the 495 same length as with input command. 496 497 Returns: 498 dictionary of list, command results that contains stdout, 499 stderr, and exit_code. 500 """ 501 if _DEFAULT_SHELL_NAME not in self._registered_mirrors: 502 self.InvokeTerminal(_DEFAULT_SHELL_NAME) 503 504 return getattr(self, _DEFAULT_SHELL_NAME).Execute(commands, no_except) 505 506 def _ExecuteShellCmdViaAdbShell(self, commands): 507 """Execute shell command(s) using adb shell command. 508 509 Args: 510 commands: string or list or tuple, command to execute on device 511 512 Returns: 513 dictionary of list, command results that contains stdout, 514 stderr, and exit_code. 515 """ 516 all = {const.STDOUT: [], 517 const.STDERR: [], 518 const.EXIT_CODE: []} 519 520 for cmd in commands: 521 res = self._adb.shell(cmd, no_except=True) 522 all[const.STDOUT].append(res[const.STDOUT]) 523 all[const.STDERR].append(res[const.STDERR]) 524 all[const.EXIT_CODE].append(res[const.EXIT_CODE]) 525 526 return all 527 528 def SetConnTimeout(self, timeout): 529 """Set remove shell connection timeout for default shell terminal. 530 531 Args: 532 timeout: int, TCP connection timeout in seconds. 533 """ 534 if _DEFAULT_SHELL_NAME not in self._registered_mirrors: 535 self.InvokeTerminal(_DEFAULT_SHELL_NAME) 536 getattr(self, _DEFAULT_SHELL_NAME).SetConnTimeout(timeout) 537 538 def GetTargetVersion(self, target_version, target_version_major, 539 target_version_minor): 540 """Get the actual target version provided by the host. 541 542 If the host provides major and minor versions separately, directly return them. 543 Otherwise, manually parse it from the float version. 544 If all of them are None, raise a user error. 545 546 Args: 547 target_version: float, the target component HAL version (e.g. 1.0). 548 target_version_major: 549 int, the target component HAL major version (e.g. 1.0 -> 1). 550 target_version_minor: 551 int, the target component HAL minor version (e.g. 1.0 -> 0). 552 553 Returns: 554 two integers, actual major and minor HAL versions. 555 556 Raises: user error, if no version is provided. 557 """ 558 # Check if host provides major and minor versions separately 559 if (target_version_minor != None and target_version_minor != None): 560 return target_version_major, target_version_minor 561 562 # If not, manually parse it from float version 563 if (target_version != None): 564 target_version_str = str(target_version) 565 [target_version_major, 566 target_version_minor] = target_version_str.split(".") 567 return int(target_version_major), int(target_version_minor) 568 569 raise errors.USERError("User has to provide a target version.") 570 571 def GetTcpClient(self, mirror_name): 572 """Gets the TCP client used in this tracker. 573 Useful for reusing session to access shared data. 574 575 Args: 576 mirror_name: used to identify mirror object. 577 """ 578 if mirror_name in self._registered_mirrors: 579 return self._registered_mirrors[mirror_name]._client 580 return None 581 582 def __getattr__(self, name): 583 if name in self._registered_mirrors: 584 return self._registered_mirrors[name] 585 else: 586 logging.error("No mirror found with name: %s", name) 587 return None 588