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( 78 self._device, 79 'Class %s is not a subclass of BaseService!' % service_class, 80 ) 81 if alias in self._service_objects: 82 raise Error( 83 self._device, 84 'A service is already registered with alias "%s".' % alias, 85 ) 86 service_obj = service_class(self._device, configs) 87 service_obj.alias = alias 88 if start_service: 89 service_obj.start() 90 self._service_objects[alias] = service_obj 91 92 def unregister(self, alias): 93 """Unregisters a service instance. 94 95 Stops a service and removes it from the manager. 96 97 Args: 98 alias: string, the alias of the service instance to unregister. 99 """ 100 if alias not in self._service_objects: 101 raise Error( 102 self._device, 'No service is registered with alias "%s".' % alias 103 ) 104 service_obj = self._service_objects.pop(alias) 105 if service_obj.is_alive: 106 with expects.expect_no_raises( 107 'Failed to stop service instance "%s".' % alias 108 ): 109 service_obj.stop() 110 111 def for_each(self, func): 112 """Executes a function with all registered services. 113 114 Args: 115 func: function, the function to execute. This function should take 116 a service object as args. 117 """ 118 aliases = list(self._service_objects.keys()) 119 for alias in aliases: 120 with expects.expect_no_raises( 121 'Failed to execute "%s" for service "%s".' % (func.__name__, alias) 122 ): 123 func(self._service_objects[alias]) 124 125 def list_live_services(self): 126 """Lists the aliases of all the services that are alive. 127 128 Order of this list is determined by the order the services are 129 registered in. 130 131 Returns: 132 list of strings, the aliases of the services that are running. 133 """ 134 aliases = [] 135 self.for_each( 136 lambda service: aliases.append(service.alias) 137 if service.is_alive 138 else None 139 ) 140 return aliases 141 142 def create_output_excerpts_all(self, test_info): 143 """Creates output excerpts from all services. 144 145 This calls `create_output_excerpts` on all registered services. 146 147 Args: 148 test_info: RuntimeTestInfo, the test info associated with the scope 149 of the excerpts. 150 151 Returns: 152 Dict, keys are the names of the services, values are the paths to 153 the excerpt files created by the corresponding services. 154 """ 155 excerpt_paths = {} 156 157 def create_output_excerpts_for_one(service): 158 if not service.is_alive: 159 return 160 paths = service.create_output_excerpts(test_info) 161 excerpt_paths[service.alias] = paths 162 163 self.for_each(create_output_excerpts_for_one) 164 return excerpt_paths 165 166 def unregister_all(self): 167 """Safely unregisters all active instances. 168 169 Errors occurred here will be recorded but not raised. 170 """ 171 aliases = list(self._service_objects.keys()) 172 for alias in aliases: 173 self.unregister(alias) 174 175 def start_all(self): 176 """Starts all inactive service instances. 177 178 Services will be started in the order they were registered. 179 """ 180 for alias, service in self._service_objects.items(): 181 if not service.is_alive: 182 with expects.expect_no_raises('Failed to start service "%s".' % alias): 183 service.start() 184 185 def start_services(self, service_alises): 186 """Starts the specified services. 187 188 Services will be started in the order specified by the input list. 189 No-op for services that are already running. 190 191 Args: 192 service_alises: list of strings, the aliases of services to start. 193 """ 194 for name in service_alises: 195 if name not in self._service_objects: 196 raise Error( 197 self._device, 198 'No service is registered under the name "%s", cannot start.' 199 % name, 200 ) 201 service = self._service_objects[name] 202 if not service.is_alive: 203 service.start() 204 205 def stop_all(self): 206 """Stops all active service instances. 207 208 Services will be stopped in the reverse order they were registered. 209 """ 210 # OrdereDict#items does not return a sequence in Python 3.4, so we have 211 # to do a list conversion here. 212 for alias, service in reversed(list(self._service_objects.items())): 213 if service.is_alive: 214 with expects.expect_no_raises('Failed to stop service "%s".' % alias): 215 service.stop() 216 217 def pause_all(self): 218 """Pauses all service instances. 219 220 Services will be paused in the reverse order they were registered. 221 """ 222 # OrdereDict#items does not return a sequence in Python 3.4, so we have 223 # to do a list conversion here. 224 for alias, service in reversed(list(self._service_objects.items())): 225 with expects.expect_no_raises('Failed to pause service "%s".' % alias): 226 service.pause() 227 228 def resume_all(self): 229 """Resumes all service instances. 230 231 Services will be resumed in the order they were registered. 232 """ 233 for alias, service in self._service_objects.items(): 234 with expects.expect_no_raises('Failed to resume service "%s".' % alias): 235 service.resume() 236 237 def resume_services(self, service_alises): 238 """Resumes the specified services. 239 240 Services will be resumed in the order specified by the input list. 241 242 Args: 243 service_alises: list of strings, the names of services to start. 244 """ 245 for name in service_alises: 246 if name not in self._service_objects: 247 raise Error( 248 self._device, 249 'No service is registered under the name "%s", cannot resume.' 250 % name, 251 ) 252 service = self._service_objects[name] 253 service.resume() 254 255 def __getattr__(self, name): 256 """Syntactic sugar to enable direct access of service objects by alias. 257 258 Args: 259 name: string, the alias a service object was registered under. 260 """ 261 if self.has_service_by_name(name): 262 return self._service_objects[name] 263 return self.__getattribute__(name) 264