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