• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Module for the manager of services."""
15# TODO(xpconanfan: move the device errors to a more generic location so
16# other device controllers like iOS can share it.
17import collections
18import inspect
19
20from mobly import expects
21from mobly.controllers.android_device_lib import errors
22from mobly.controllers.android_device_lib.services import base_service
23
24
25class Error(errors.DeviceError):
26  """Root error type for this module."""
27
28
29class ServiceManager:
30  """Manager for services of AndroidDevice.
31
32  A service is a long running process that involves an Android device, like
33  adb logcat or Snippet.
34  """
35
36  def __init__(self, device):
37    self._service_objects = collections.OrderedDict()
38    self._device = device
39
40  def has_service_by_name(self, name):
41    """Checks if the manager has a service registered with a specific name.
42
43    Args:
44      name: string, the name to look for.
45
46    Returns:
47      True if a service is registered with the specified name, False
48      otherwise.
49    """
50    return name in self._service_objects
51
52  @property
53  def is_any_alive(self):
54    """True if any service is alive; False otherwise."""
55    for service in self._service_objects.values():
56      if service.is_alive:
57        return True
58    return False
59
60  def register(self, alias, service_class, configs=None, start_service=True):
61    """Registers a service.
62
63    This will create a service instance, starts the service, and adds the
64    instance to the mananger.
65
66    Args:
67      alias: string, the alias for this instance.
68      service_class: class, the service class to instantiate.
69      configs: (optional) config object to pass to the service class's
70        constructor.
71      start_service: bool, whether to start the service instance or not.
72        Default is True.
73    """
74    if not inspect.isclass(service_class):
75      raise Error(self._device, '"%s" is not a class!' % service_class)
76    if not issubclass(service_class, base_service.BaseService):
77      raise Error(self._device,
78                  'Class %s is not a subclass of BaseService!' % service_class)
79    if alias in self._service_objects:
80      raise Error(self._device,
81                  'A service is already registered with alias "%s".' % alias)
82    service_obj = service_class(self._device, configs)
83    service_obj.alias = alias
84    if start_service:
85      service_obj.start()
86    self._service_objects[alias] = service_obj
87
88  def unregister(self, alias):
89    """Unregisters a service instance.
90
91    Stops a service and removes it from the manager.
92
93    Args:
94      alias: string, the alias of the service instance to unregister.
95    """
96    if alias not in self._service_objects:
97      raise Error(self._device,
98                  'No service is registered with alias "%s".' % alias)
99    service_obj = self._service_objects.pop(alias)
100    if service_obj.is_alive:
101      with expects.expect_no_raises('Failed to stop service instance "%s".' %
102                                    alias):
103        service_obj.stop()
104
105  def for_each(self, func):
106    """Executes a function with all registered services.
107
108    Args:
109      func: function, the function to execute. This function should take
110        a service object as args.
111    """
112    aliases = list(self._service_objects.keys())
113    for alias in aliases:
114      with expects.expect_no_raises('Failed to execute "%s" for service "%s".' %
115                                    (func.__name__, alias)):
116        func(self._service_objects[alias])
117
118  def list_live_services(self):
119    """Lists the aliases of all the services that are alive.
120
121    Order of this list is determined by the order the services are
122    registered in.
123
124    Returns:
125      list of strings, the aliases of the services that are running.
126    """
127    aliases = []
128    self.for_each(lambda service: aliases.append(service.alias)
129                  if service.is_alive else None)
130    return aliases
131
132  def create_output_excerpts_all(self, test_info):
133    """Creates output excerpts from all services.
134
135    This calls `create_output_excerpts` on all registered services.
136
137    Args:
138      test_info: RuntimeTestInfo, the test info associated with the scope
139        of the excerpts.
140
141    Returns:
142      Dict, keys are the names of the services, values are the paths to
143        the excerpt files created by the corresponding services.
144    """
145    excerpt_paths = {}
146
147    def create_output_excerpts_for_one(service):
148      if not service.is_alive:
149        return
150      paths = service.create_output_excerpts(test_info)
151      excerpt_paths[service.alias] = paths
152
153    self.for_each(create_output_excerpts_for_one)
154    return excerpt_paths
155
156  def unregister_all(self):
157    """Safely unregisters all active instances.
158
159    Errors occurred here will be recorded but not raised.
160    """
161    aliases = list(self._service_objects.keys())
162    for alias in aliases:
163      self.unregister(alias)
164
165  def start_all(self):
166    """Starts all inactive service instances.
167
168    Services will be started in the order they were registered.
169    """
170    for alias, service in self._service_objects.items():
171      if not service.is_alive:
172        with expects.expect_no_raises('Failed to start service "%s".' % alias):
173          service.start()
174
175  def start_services(self, service_alises):
176    """Starts the specified services.
177
178    Services will be started in the order specified by the input list.
179    No-op for services that are already running.
180
181    Args:
182      service_alises: list of strings, the aliases of services to start.
183    """
184    for name in service_alises:
185      if name not in self._service_objects:
186        raise Error(
187            self._device,
188            'No service is registered under the name "%s", cannot start.' %
189            name)
190      service = self._service_objects[name]
191      if not service.is_alive:
192        service.start()
193
194  def stop_all(self):
195    """Stops all active service instances.
196
197    Services will be stopped in the reverse order they were registered.
198    """
199    # OrdereDict#items does not return a sequence in Python 3.4, so we have
200    # to do a list conversion here.
201    for alias, service in reversed(list(self._service_objects.items())):
202      if service.is_alive:
203        with expects.expect_no_raises('Failed to stop service "%s".' % alias):
204          service.stop()
205
206  def pause_all(self):
207    """Pauses all service instances.
208
209    Services will be paused in the reverse order they were registered.
210    """
211    # OrdereDict#items does not return a sequence in Python 3.4, so we have
212    # to do a list conversion here.
213    for alias, service in reversed(list(self._service_objects.items())):
214      with expects.expect_no_raises('Failed to pause service "%s".' % alias):
215        service.pause()
216
217  def resume_all(self):
218    """Resumes all service instances.
219
220    Services will be resumed in the order they were registered.
221    """
222    for alias, service in self._service_objects.items():
223      with expects.expect_no_raises('Failed to resume service "%s".' % alias):
224        service.resume()
225
226  def resume_services(self, service_alises):
227    """Resumes the specified services.
228
229    Services will be resumed in the order specified by the input list.
230
231    Args:
232      service_alises: list of strings, the names of services to start.
233    """
234    for name in service_alises:
235      if name not in self._service_objects:
236        raise Error(
237            self._device,
238            'No service is registered under the name "%s", cannot resume.' %
239            name)
240      service = self._service_objects[name]
241      service.resume()
242
243  def __getattr__(self, name):
244    """Syntactic sugar to enable direct access of service objects by alias.
245
246    Args:
247      name: string, the alias a service object was registered under.
248    """
249    if self.has_service_by_name(name):
250      return self._service_objects[name]
251    return self.__getattribute__(name)
252