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 dbus 8import logging 9import six 10 11from contextlib import contextmanager 12 13from autotest_lib.client.common_lib import utils 14from autotest_lib.client.cros.networking import shill_proxy 15from autotest_lib.client.cros.power import sys_power 16 17class ContextError(Exception): 18 """An error raised by a context managers dealing with shill objects.""" 19 pass 20 21 22class AllowedTechnologiesContext(object): 23 """A context manager for allowing only specified technologies in shill. 24 25 Usage: 26 # Suppose both 'wifi' and 'cellular' technology are originally enabled. 27 allowed = [shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR] 28 with AllowedTechnologiesContext(allowed): 29 # Within this context, only the 'cellular' technology is allowed to 30 # be enabled. The 'wifi' technology is temporarily prohibited and 31 # disabled until after the context ends. 32 33 """ 34 35 def __init__(self, allowed): 36 self._allowed = set(allowed) 37 38 39 def __enter__(self): 40 shill = shill_proxy.ShillProxy.get_proxy() 41 42 # The EnabledTechologies property is an array of strings of technology 43 # identifiers. 44 enabled = shill.get_dbus_property( 45 shill.manager, 46 shill_proxy.ShillProxy.MANAGER_PROPERTY_ENABLED_TECHNOLOGIES) 47 self._originally_enabled = set(enabled) 48 49 # The ProhibitedTechnologies property is a comma-separated string of 50 # technology identifiers. 51 prohibited_csv = shill.get_dbus_property( 52 shill.manager, 53 shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES) 54 prohibited = prohibited_csv.split(',') if prohibited_csv else [] 55 self._originally_prohibited = set(prohibited) 56 57 prohibited = ((self._originally_prohibited | self._originally_enabled) 58 - self._allowed) 59 prohibited_csv = ','.join(prohibited) 60 61 logging.debug('Allowed technologies = [%s]', ','.join(self._allowed)) 62 logging.debug('Originally enabled technologies = [%s]', 63 ','.join(self._originally_enabled)) 64 logging.debug('Originally prohibited technologies = [%s]', 65 ','.join(self._originally_prohibited)) 66 logging.debug('To be prohibited technologies = [%s]', 67 ','.join(prohibited)) 68 69 # Setting the ProhibitedTechnologies property will disable those 70 # prohibited technologies. 71 shill.set_dbus_property( 72 shill.manager, 73 shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES, 74 prohibited_csv) 75 76 return self 77 78 79 def __exit__(self, exc_type, exc_value, traceback): 80 shill = shill_proxy.ShillProxy.get_proxy() 81 82 prohibited_csv = ','.join(self._originally_prohibited) 83 shill.set_dbus_property( 84 shill.manager, 85 shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES, 86 prohibited_csv) 87 88 # Re-enable originally enabled technologies as they may have been 89 # disabled. 90 enabled = shill.get_dbus_property( 91 shill.manager, 92 shill_proxy.ShillProxy.MANAGER_PROPERTY_ENABLED_TECHNOLOGIES) 93 to_be_reenabled = self._originally_enabled - set(enabled) 94 for technology in to_be_reenabled: 95 shill.manager.EnableTechnology(technology) 96 97 return False 98 99 100class ServiceAutoConnectContext(object): 101 """A context manager for overriding a service's 'AutoConnect' property. 102 103 As the service object of the same service may change during the lifetime 104 of the context, this context manager does not take a service object at 105 construction. Instead, it takes a |get_service| function at construction, 106 which it invokes to obtain a service object when entering and exiting the 107 context. It is assumed that |get_service| always returns a service object 108 that refers to the same service. 109 110 Usage: 111 def get_service(): 112 # Some function that returns a service object. 113 114 with ServiceAutoConnectContext(get_service, False): 115 # Within this context, the 'AutoConnect' property of the service 116 # returned by |get_service| is temporarily set to False if it's 117 # initially set to True. The property is restored to its initial 118 # value after the context ends. 119 120 """ 121 def __init__(self, get_service, autoconnect): 122 self._get_service = get_service 123 self._autoconnect = autoconnect 124 self._initial_autoconnect = None 125 126 127 def __enter__(self): 128 service = self._get_service() 129 if service is None: 130 raise ContextError('Could not obtain a service object.') 131 132 # Always set the AutoConnect property even if the requested value 133 # is the same so that shill will retain the AutoConnect property, else 134 # shill may override it. 135 service_properties = service.GetProperties() 136 self._initial_autoconnect = shill_proxy.ShillProxy.dbus2primitive( 137 service_properties[ 138 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT]) 139 logging.info('ServiceAutoConnectContext: change autoconnect to %s', 140 self._autoconnect) 141 service.SetProperty( 142 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT, 143 self._autoconnect) 144 145 # Make sure the cellular service gets persisted by taking it out of 146 # the ephemeral profile. 147 if not service_properties[ 148 shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE]: 149 shill = shill_proxy.ShillProxy.get_proxy() 150 if six.PY2: 151 manager_properties = shill.manager.GetProperties( 152 utf8_strings=True) 153 else: 154 manager_properties = shill.manager.GetProperties() 155 active_profile = manager_properties[ 156 shill_proxy.ShillProxy.MANAGER_PROPERTY_ACTIVE_PROFILE] 157 logging.info('ServiceAutoConnectContext: change cellular service ' 158 'profile to %s', active_profile) 159 service.SetProperty( 160 shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE, 161 active_profile) 162 163 return self 164 165 166 def __exit__(self, exc_type, exc_value, traceback): 167 if self._initial_autoconnect != self._autoconnect: 168 service = self._get_service() 169 if service is None: 170 raise ContextError('Could not obtain a service object.') 171 172 logging.info('ServiceAutoConnectContext: restore autoconnect to %s', 173 self._initial_autoconnect) 174 service.SetProperty( 175 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT, 176 self._initial_autoconnect) 177 return False 178 179 180 @property 181 def autoconnect(self): 182 """AutoConnect property value within this context.""" 183 return self._autoconnect 184 185 186 @property 187 def initial_autoconnect(self): 188 """Initial AutoConnect property value when entering this context.""" 189 return self._initial_autoconnect 190 191 192@contextmanager 193def stopped_shill(): 194 """A context for executing code which requires shill to be stopped. 195 196 This context stops shill on entry to the context, and starts shill 197 before exit from the context. This context further guarantees that 198 shill will be not restarted by recover_duts and that recover_duts 199 will not otherwise try to run its network connectivity checks, while 200 this context is active. 201 202 Note that the no-restart guarantee applies only if the user of 203 this context completes with a 'reasonable' amount of time. In 204 particular: if networking is unavailable for 15 minutes or more, 205 recover_duts will reboot the DUT. 206 207 """ 208 sys_power.pause_check_network_hook() 209 utils.stop_service('shill') 210 yield 211 utils.start_service('shill') 212 sys_power.resume_check_network_hook() 213 214 215class StaticIPContext(object): 216 """StaticIPConfig context manager class. 217 218 Set a StaticIPConfig to the given service. 219 220 """ 221 def __init__(self, service, config): 222 self._service = service 223 self._config = config 224 225 226 def __enter__(self): 227 """Configure the StaticIP parameters for the Service and apply those 228 parameters to the interface by forcing a re-connect.""" 229 self._service.SetProperty( 230 shill_proxy.ShillProxy.SERVICE_PROPERTY_STATIC_IP_CONFIG, 231 dbus.Dictionary(self._config, signature='sv')) 232 self._service.Disconnect() 233 self._service.Connect() 234 235 236 def __exit__(self, exception, value, traceback): 237 """Clear configuration of StaticIP parameters for the Service and force 238 a re-connect.""" 239 self._service.ClearProperty( 240 shill_proxy.ShillProxy.SERVICE_PROPERTY_STATIC_IP_CONFIG) 241 self._service.Disconnect() 242 self._service.Connect() 243