1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""A collection of context managers for working with shill objects.""" 6 7import errno 8import logging 9import os 10 11from contextlib import contextmanager 12 13from autotest_lib.client.common_lib import error 14from autotest_lib.client.common_lib import utils 15from autotest_lib.client.cros.networking import shill_proxy 16 17SHILL_START_LOCK_PATH = '/var/lock/shill-start.lock' 18 19class ContextError(Exception): 20 """An error raised by a context managers dealing with shill objects.""" 21 pass 22 23 24class ServiceAutoConnectContext(object): 25 """A context manager for overriding a service's 'AutoConnect' property. 26 27 As the service object of the same service may change during the lifetime 28 of the context, this context manager does not take a service object at 29 construction. Instead, it takes a |get_service| function at construction, 30 which it invokes to obtain a service object when entering and exiting the 31 context. It is assumed that |get_service| always returns a service object 32 that refers to the same service. 33 34 Usage: 35 def get_service(): 36 # Some function that returns a service object. 37 38 with ServiceAutoConnectContext(get_service, False): 39 # Within this context, the 'AutoConnect' property of the service 40 # returned by |get_service| is temporarily set to False if it's 41 # initially set to True. The property is restored to its initial 42 # value after the context ends. 43 44 """ 45 def __init__(self, get_service, autoconnect): 46 self._get_service = get_service 47 self._autoconnect = autoconnect 48 self._initial_autoconnect = None 49 50 51 def __enter__(self): 52 service = self._get_service() 53 if service is None: 54 raise ContextError('Could not obtain a service object.') 55 56 # Always set the AutoConnect property even if the requested value 57 # is the same so that shill will retain the AutoConnect property, else 58 # shill may override it. 59 service_properties = service.GetProperties() 60 self._initial_autoconnect = shill_proxy.ShillProxy.dbus2primitive( 61 service_properties[ 62 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT]) 63 logging.info('ServiceAutoConnectContext: change autoconnect to %s', 64 self._autoconnect) 65 service.SetProperty( 66 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT, 67 self._autoconnect) 68 69 # Make sure the cellular service gets persisted by taking it out of 70 # the ephemeral profile. 71 if not service_properties[ 72 shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE]: 73 shill = shill_proxy.ShillProxy.get_proxy() 74 manager_properties = shill.manager.GetProperties(utf8_strings=True) 75 active_profile = manager_properties[ 76 shill_proxy.ShillProxy.MANAGER_PROPERTY_ACTIVE_PROFILE] 77 logging.info('ServiceAutoConnectContext: change cellular service ' 78 'profile to %s', active_profile) 79 service.SetProperty( 80 shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE, 81 active_profile) 82 83 return self 84 85 86 def __exit__(self, exc_type, exc_value, traceback): 87 if self._initial_autoconnect != self._autoconnect: 88 service = self._get_service() 89 if service is None: 90 raise ContextError('Could not obtain a service object.') 91 92 logging.info('ServiceAutoConnectContext: restore autoconnect to %s', 93 self._initial_autoconnect) 94 service.SetProperty( 95 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT, 96 self._initial_autoconnect) 97 return False 98 99 100 @property 101 def autoconnect(self): 102 """AutoConnect property value within this context.""" 103 return self._autoconnect 104 105 106 @property 107 def initial_autoconnect(self): 108 """Initial AutoConnect property value when entering this context.""" 109 return self._initial_autoconnect 110 111 112@contextmanager 113def stopped_shill(): 114 """A context for executing code which requires shill to be stopped. 115 116 This context stops shill on entry to the context, and starts shill 117 before exit from the context. This context further guarantees that 118 shill will be not restarted by recover_duts, while this context is 119 active. 120 121 Note that the no-restart guarantee applies only if the user of 122 this context completes with a 'reasonable' amount of time. In 123 particular: if networking is unavailable for 15 minutes or more, 124 recover_duts will reboot the DUT. 125 126 """ 127 def get_lock_holder(lock_path): 128 lock_holder = os.readlink(lock_path) 129 try: 130 os.stat(lock_holder) 131 return lock_holder # stat() success -> valid link -> locker alive 132 except OSError as e: 133 if e.errno == errno.ENOENT: # dangling link -> locker is gone 134 return None 135 else: 136 raise 137 138 our_proc_dir = '/proc/%d/' % os.getpid() 139 try: 140 os.symlink(our_proc_dir, SHILL_START_LOCK_PATH) 141 except OSError as e: 142 if e.errno != errno.EEXIST: 143 raise 144 lock_holder = get_lock_holder(SHILL_START_LOCK_PATH) 145 if lock_holder is not None: 146 raise error.TestError('Shill start lock held by %s' % lock_holder) 147 os.remove(SHILL_START_LOCK_PATH) 148 os.symlink(our_proc_dir, SHILL_START_LOCK_PATH) 149 150 utils.stop_service('shill') 151 yield 152 utils.start_service('shill') 153 os.remove(SHILL_START_LOCK_PATH) 154