• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 import logging
18 
19 from vts.proto import AndroidSystemControlMessage_pb2 as ASysCtrlMsg
20 from vts.runners.host import const
21 from vts.runners.host import errors
22 from vts.runners.host.tcp_client import vts_tcp_client
23 from vts.runners.host.tcp_server import callback_server
24 from vts.utils.python.mirror import hal_mirror
25 from vts.utils.python.mirror import lib_mirror
26 from vts.utils.python.mirror import shell_mirror
27 from 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 
35 class 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