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 5import collections 6import dbus 7import dbus.mainloop.glib 8import gobject 9import time 10 11from autotest_lib.client.cros import dbus_util 12 13 14class ShillProxyError(Exception): 15 """Exceptions raised by ShillProxy and its children.""" 16 pass 17 18 19class ShillProxyTimeoutError(ShillProxyError): 20 """Timeout exception raised by ShillProxy and its children.""" 21 def __init__(self, desc): 22 super(ShillProxyTimeoutError, self).__init__( 23 'Timed out waiting for condition %s.' % desc) 24 25 26class ShillProxy(object): 27 """A wrapper around a DBus proxy for shill.""" 28 29 # Core DBus error names 30 DBUS_ERROR_UNKNOWN_OBJECT = 'org.freedesktop.DBus.Error.UnknownObject' 31 # Shill error names 32 ERROR_ALREADY_CONNECTED = 'org.chromium.flimflam.Error.AlreadyConnected' 33 ERROR_FAILURE = 'org.chromium.flimflam.Error.Failure' 34 ERROR_INCORRECT_PIN = 'org.chromium.flimflam.Error.IncorrectPin' 35 ERROR_IN_PROGRESS = 'org.chromium.flimflam.Error.InProgress' 36 ERROR_NOT_CONNECTED = 'org.chromium.flimflam.Error.NotConnected' 37 ERROR_NOT_SUPPORTED = 'org.chromium.flimflam.Error.NotSupported' 38 ERROR_PIN_BLOCKED = 'org.chromium.flimflam.Error.PinBlocked' 39 40 41 DBUS_INTERFACE = 'org.chromium.flimflam' 42 DBUS_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown' 43 DBUS_TYPE_DEVICE = 'org.chromium.flimflam.Device' 44 DBUS_TYPE_IPCONFIG = 'org.chromium.flimflam.IPConfig' 45 DBUS_TYPE_MANAGER = 'org.chromium.flimflam.Manager' 46 DBUS_TYPE_PROFILE = 'org.chromium.flimflam.Profile' 47 DBUS_TYPE_SERVICE = 'org.chromium.flimflam.Service' 48 49 ENTRY_FIELD_NAME = 'Name' 50 ENTRY_FIELD_TYPE = 'Type' 51 52 MANAGER_PROPERTY_ACTIVE_PROFILE = 'ActiveProfile' 53 MANAGER_PROPERTY_DEVICES = 'Devices' 54 MANAGER_PROPERTY_NO_AUTOCONNECT_TECHNOLOGIES = 'NoAutoConnectTechnologies' 55 MANAGER_PROPERTY_ENABLED_TECHNOLOGIES = 'EnabledTechnologies' 56 MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES = 'ProhibitedTechnologies' 57 MANAGER_PROPERTY_UNINITIALIZED_TECHNOLOGIES = 'UninitializedTechnologies' 58 MANAGER_PROPERTY_PROFILES = 'Profiles' 59 MANAGER_PROPERTY_SERVICES = 'Services' 60 MANAGER_PROPERTY_ALL_SERVICES = 'ServiceCompleteList' 61 MANAGER_PROPERTY_DHCPPROPERTY_HOSTNAME = 'DHCPProperty.Hostname' 62 MANAGER_PROPERTY_DHCPPROPERTY_VENDORCLASS = 'DHCPProperty.VendorClass' 63 MANAGER_PROPERTY_WIFI_GLOBAL_FT_ENABLED = 'WiFi.GlobalFTEnabled' 64 65 MANAGER_OPTIONAL_PROPERTY_MAP = { 66 MANAGER_PROPERTY_DHCPPROPERTY_HOSTNAME: dbus.String, 67 MANAGER_PROPERTY_DHCPPROPERTY_VENDORCLASS: dbus.String, 68 MANAGER_PROPERTY_WIFI_GLOBAL_FT_ENABLED: dbus.Boolean 69 } 70 71 PROFILE_PROPERTY_ENTRIES = 'Entries' 72 PROFILE_PROPERTY_NAME = 'Name' 73 74 OBJECT_TYPE_PROPERTY_MAP = { 75 'Device': ( DBUS_TYPE_DEVICE, MANAGER_PROPERTY_DEVICES ), 76 'Profile': ( DBUS_TYPE_PROFILE, MANAGER_PROPERTY_PROFILES ), 77 'Service': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_SERVICES ), 78 'AnyService': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_ALL_SERVICES ) 79 } 80 81 DEVICE_ENUMERATION_TIMEOUT = 30 82 DEVICE_ENABLE_DISABLE_TIMEOUT = 60 83 SERVICE_DISCONNECT_TIMEOUT = 5 84 85 SERVICE_PROPERTY_AUTOCONNECT = 'AutoConnect' 86 SERVICE_PROPERTY_DEVICE = 'Device' 87 SERVICE_PROPERTY_GUID = 'GUID' 88 SERVICE_PROPERTY_HEX_SSID = 'WiFi.HexSSID' 89 SERVICE_PROPERTY_HIDDEN = 'WiFi.HiddenSSID' 90 SERVICE_PROPERTY_MODE = 'Mode' 91 SERVICE_PROPERTY_NAME = 'Name' 92 SERVICE_PROPERTY_PASSPHRASE = 'Passphrase' 93 SERVICE_PROPERTY_PROFILE = 'Profile' 94 SERVICE_PROPERTY_SAVE_CREDENTIALS = 'SaveCredentials' 95 SERVICE_PROPERTY_FT_ENABLED = 'WiFi.FTEnabled' 96 # Unless you really care whether a network is WPA (TSN) vs. WPA-2 97 # (RSN), you should use SERVICE_PROPERTY_SECURITY_CLASS. 98 SERVICE_PROPERTY_SECURITY_RAW = 'Security' 99 SERVICE_PROPERTY_SECURITY_CLASS = 'SecurityClass' 100 SERVICE_PROPERTY_SSID = 'SSID' 101 SERVICE_PROPERTY_STRENGTH = 'Strength' 102 SERVICE_PROPERTY_STATE = 'State' 103 SERVICE_PROPERTY_STATIC_IP_NAMESERVERS = 'StaticIP.NameServers' 104 SERVICE_PROPERTY_TYPE = 'Type' 105 106 # EAP related properties. 107 SERVICE_PROPERTY_EAP_EAP = 'EAP.EAP' 108 SERVICE_PROPERTY_EAP_INNER_EAP = 'EAP.InnerEAP' 109 SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity' 110 SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password' 111 SERVICE_PROPERTY_EAP_CA_CERT_PEM = 'EAP.CACertPEM' 112 SERVICE_PROPERTY_CLIENT_CERT_ID = 'EAP.CertID' 113 SERVICE_PROPERTY_EAP_KEY_MGMT = 'EAP.KeyMgmt' 114 SERVICE_PROPERTY_EAP_PIN = 'EAP.PIN' 115 SERVICE_PROPERTY_PRIVATE_KEY_ID = 'EAP.KeyID' 116 SERVICE_PROPERTY_USE_SYSTEM_CAS = 'EAP.UseSystemCAs' 117 118 # OpenVPN related properties. 119 SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM = 'OpenVPN.CACertPEM' 120 SERVICE_PROPERTY_OPENVPN_PASSWORD = 'OpenVPN.Password' 121 SERVICE_PROPERTY_OPENVPN_PKCS11_ID = 'OpenVPN.Pkcs11.ID' 122 SERVICE_PROPERTY_OPENVPN_PKCS11_PIN = 'OpenVPN.Pkcs11.PIN' 123 SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST = 'Provider.Host' 124 SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE = 'Provider.Type' 125 SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU = 'OpenVPN.RemoteCertEKU' 126 SERVICE_PROPERTY_OPENVPN_USER = 'OpenVPN.User' 127 SERVICE_PROPERTY_OPENVPN_VERB = 'OpenVPN.Verb' 128 SERVICE_PROPERTY_OPENVPN_VERIFY_HASH = 'OpenVPN.VerifyHash' 129 SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME = 'OpenVPN.VerifyX509Name' 130 SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE = 'OpenVPN.VerifyX509Type' 131 SERVICE_PROPERTY_OPENVPN_VPN_DOMAIN = 'VPN.Domain' 132 133 # L2TP VPN related properties. 134 SERVICE_PROPERTY_L2TP_CA_CERT_PEM = 'L2TPIPsec.CACertPEM' 135 SERVICE_PROPERTY_L2TP_CLIENT_CERT_ID = 'L2TPIPsec.ClientCertID' 136 SERVICE_PROPERTY_L2TP_CLIENT_CERT_SLOT = 'L2TPIPsec.ClientCertSlot' 137 SERVICE_PROPERTY_L2TP_PASSWORD = 'L2TPIPsec.Password' 138 SERVICE_PROPERTY_L2TP_PIN = 'L2TPIPsec.PIN' 139 SERVICE_PROPERTY_L2TP_PSK = 'L2TPIPsec.PSK' 140 SERVICE_PROPERTY_L2TP_USER = 'L2TPIPsec.User' 141 SERVICE_PROPERTY_L2TP_XAUTH_PASSWORD = 'L2TPIPsec.XauthPassword' 142 SERVICE_PROPERTY_L2TP_XAUTH_USER = 'L2TPIPsec.XauthUser' 143 144 # Mapping of service property to its dbus type. 145 SERVICE_PROPERTY_MAP = { 146 SERVICE_PROPERTY_AUTOCONNECT: dbus.Boolean, 147 SERVICE_PROPERTY_DEVICE: dbus.ObjectPath, 148 SERVICE_PROPERTY_GUID: dbus.String, 149 SERVICE_PROPERTY_HEX_SSID: dbus.String, 150 SERVICE_PROPERTY_HIDDEN: dbus.Boolean, 151 SERVICE_PROPERTY_MODE: dbus.String, 152 SERVICE_PROPERTY_NAME: dbus.String, 153 SERVICE_PROPERTY_PASSPHRASE: dbus.String, 154 SERVICE_PROPERTY_PROFILE: dbus.ObjectPath, 155 SERVICE_PROPERTY_SAVE_CREDENTIALS: dbus.Boolean, 156 SERVICE_PROPERTY_SECURITY_RAW: dbus.String, 157 SERVICE_PROPERTY_SECURITY_CLASS: dbus.String, 158 SERVICE_PROPERTY_SSID: dbus.String, 159 SERVICE_PROPERTY_STRENGTH: dbus.Byte, 160 SERVICE_PROPERTY_STATE: dbus.String, 161 SERVICE_PROPERTY_TYPE: dbus.String, 162 SERVICE_PROPERTY_FT_ENABLED: dbus.Boolean, 163 SERVICE_PROPERTY_STATIC_IP_NAMESERVERS: dbus.String, 164 165 SERVICE_PROPERTY_EAP_EAP: dbus.String, 166 SERVICE_PROPERTY_EAP_INNER_EAP: dbus.String, 167 SERVICE_PROPERTY_EAP_IDENTITY: dbus.String, 168 SERVICE_PROPERTY_EAP_PASSWORD: dbus.String, 169 SERVICE_PROPERTY_EAP_CA_CERT_PEM: dbus.Array, 170 SERVICE_PROPERTY_CLIENT_CERT_ID: dbus.String, 171 SERVICE_PROPERTY_EAP_KEY_MGMT: dbus.String, 172 SERVICE_PROPERTY_EAP_PIN: dbus.String, 173 SERVICE_PROPERTY_PRIVATE_KEY_ID: dbus.String, 174 SERVICE_PROPERTY_USE_SYSTEM_CAS: dbus.Boolean, 175 176 SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM: dbus.Array, 177 SERVICE_PROPERTY_OPENVPN_PASSWORD: dbus.String, 178 SERVICE_PROPERTY_OPENVPN_PKCS11_ID: dbus.String, 179 SERVICE_PROPERTY_OPENVPN_PKCS11_PIN: dbus.String, 180 SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST: dbus.String, 181 SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE: dbus.String, 182 SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU: dbus.String, 183 SERVICE_PROPERTY_OPENVPN_USER: dbus.String, 184 SERVICE_PROPERTY_OPENVPN_VERB: dbus.String, 185 SERVICE_PROPERTY_OPENVPN_VERIFY_HASH: dbus.String, 186 SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME: dbus.String, 187 SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE: dbus.String, 188 SERVICE_PROPERTY_OPENVPN_VPN_DOMAIN: dbus.String, 189 190 SERVICE_PROPERTY_L2TP_CA_CERT_PEM: dbus.Array, 191 SERVICE_PROPERTY_L2TP_CLIENT_CERT_ID: dbus.String, 192 SERVICE_PROPERTY_L2TP_CLIENT_CERT_SLOT: dbus.String, 193 SERVICE_PROPERTY_L2TP_PASSWORD: dbus.String, 194 SERVICE_PROPERTY_L2TP_PIN: dbus.String, 195 SERVICE_PROPERTY_L2TP_PSK: dbus.String, 196 SERVICE_PROPERTY_L2TP_USER: dbus.String, 197 SERVICE_PROPERTY_L2TP_XAUTH_PASSWORD: dbus.String, 198 SERVICE_PROPERTY_L2TP_XAUTH_USER: dbus.String 199 } 200 201 SERVICE_CONNECTED_STATES = ['portal', 'online'] 202 203 SUPPORTED_WIFI_STATION_TYPES = {'managed': 'managed', 204 'ibss': 'adhoc', 205 None: 'managed'} 206 207 DEVICE_PROPERTY_ADDRESS = 'Address' 208 DEVICE_PROPERTY_EAP_AUTHENTICATION_COMPLETED = 'EapAuthenticationCompleted' 209 DEVICE_PROPERTY_EAP_AUTHENTICATOR_DETECTED = 'EapAuthenticatorDetected' 210 DEVICE_PROPERTY_IP_CONFIG = 'IpConfig' 211 DEVICE_PROPERTY_INTERFACE = 'Interface' 212 DEVICE_PROPERTY_NAME = 'Name' 213 DEVICE_PROPERTY_POWERED = 'Powered' 214 DEVICE_PROPERTY_RECEIVE_BYTE_COUNT = 'ReceiveByteCount' 215 DEVICE_PROPERTY_SCANNING = 'Scanning' 216 DEVICE_PROPERTY_TRANSMIT_BYTE_COUNT = 'TransmitByteCount' 217 DEVICE_PROPERTY_TYPE = 'Type' 218 219 TECHNOLOGY_CELLULAR = 'cellular' 220 TECHNOLOGY_ETHERNET = 'ethernet' 221 TECHNOLOGY_VPN = 'vpn' 222 TECHNOLOGY_WIFI = 'wifi' 223 TECHNOLOGY_WIMAX = 'wimax' 224 225 VALUE_POWERED_ON = True 226 VALUE_POWERED_OFF = False 227 228 POLLING_INTERVAL_SECONDS = 0.2 229 230 # Default log level used in connectivity tests. 231 LOG_LEVEL_FOR_TEST = -4 232 233 # Default log scopes used in connectivity tests. 234 LOG_SCOPES_FOR_TEST_COMMON = [ 235 'connection', 236 'dbus', 237 'device', 238 'link', 239 'manager', 240 'portal', 241 'service' 242 ] 243 244 # Default log scopes used in connectivity tests for specific technologies. 245 LOG_SCOPES_FOR_TEST = { 246 TECHNOLOGY_CELLULAR: LOG_SCOPES_FOR_TEST_COMMON + ['cellular'], 247 TECHNOLOGY_ETHERNET: LOG_SCOPES_FOR_TEST_COMMON + ['ethernet'], 248 TECHNOLOGY_VPN: LOG_SCOPES_FOR_TEST_COMMON + ['vpn'], 249 TECHNOLOGY_WIFI: LOG_SCOPES_FOR_TEST_COMMON + ['wifi'], 250 TECHNOLOGY_WIMAX: LOG_SCOPES_FOR_TEST_COMMON + ['wimax'] 251 } 252 253 UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' 254 255 256 @staticmethod 257 def str2dbus(dbus_class, value): 258 """Typecast string property values to dbus types. 259 260 This mostly makes it easy to special case Boolean constructors 261 to interpret strings like 'false' and '0' as False. 262 263 @param dbus_class: DBus class object. 264 @param value: value to pass to constructor. 265 266 """ 267 if isinstance(dbus_class, dbus.Boolean): 268 return dbus_class(value.lower() in ('true','1')) 269 else: 270 return dbus_class(value) 271 272 273 @staticmethod 274 def service_properties_to_dbus_types(in_dict): 275 """Convert service properties to dbus types. 276 277 @param in_dict: Dictionary containing service properties. 278 @return DBus variant dictionary containing service properties. 279 280 """ 281 dbus_dict = {} 282 for key, value in in_dict.iteritems(): 283 if key not in ShillProxy.SERVICE_PROPERTY_MAP: 284 raise ShillProxyError('Unsupported property %s' % (key)) 285 dbus_dict[key] = ShillProxy.SERVICE_PROPERTY_MAP[key]( 286 value, variant_level=1) 287 return dbus_dict 288 289 290 @classmethod 291 def dbus2primitive(cls, value): 292 """Typecast values from dbus types to python types. 293 294 @param value: dbus object to convert to a primitive. 295 296 """ 297 return dbus_util.dbus2primitive(value) 298 299 300 @staticmethod 301 def get_dbus_property(interface, property_key): 302 """get property on a dbus Interface 303 304 @param interface dbus Interface to receive new setting 305 @param property_key string name of property on interface 306 @return python typed object representing property value or None 307 308 """ 309 properties = interface.GetProperties(utf8_strings=True) 310 if property_key in properties: 311 return ShillProxy.dbus2primitive(properties[property_key]) 312 else: 313 return None 314 315 316 @staticmethod 317 def set_dbus_property(interface, property_key, value): 318 """set property on a dbus Interface 319 320 @param interface dbus Interface to receive new setting 321 @param property_key string name of property on interface 322 @param value string value to set for property on interface from string 323 324 """ 325 properties = interface.GetProperties(utf8_strings=True) 326 if property_key not in properties: 327 raise ShillProxyError('No property %s found in %s' % 328 (property_key, interface.object_path)) 329 else: 330 dbus_class = properties[property_key].__class__ 331 interface.SetProperty(property_key, 332 ShillProxy.str2dbus(dbus_class, value)) 333 334 335 @staticmethod 336 def set_optional_dbus_property(interface, property_key, value): 337 """set an optional property on a dbus Interface. 338 339 This method can be used for properties that are optionally listed 340 in the profile. It skips the initial check of the property 341 being in the interface.GetProperties list. 342 343 @param interface dbus Interface to receive new setting 344 @param property_key string name of property on interface 345 @param value string value to set for property on interface from string 346 347 """ 348 if property_key not in ShillProxy.MANAGER_OPTIONAL_PROPERTY_MAP: 349 raise ShillProxyError('Unsupported property %s' % 350 (property_key)) 351 else: 352 dbus_class = ShillProxy.MANAGER_OPTIONAL_PROPERTY_MAP[property_key] 353 interface.SetProperty(property_key, 354 ShillProxy.str2dbus(dbus_class, value)) 355 356 357 @classmethod 358 def get_proxy(cls, bus=None, timeout_seconds=10): 359 """Create a Proxy, retrying if necessary. 360 361 This method creates a proxy object of the required subclass of 362 ShillProxy. A call to SomeSubclassOfShillProxy.get_proxy() will return 363 an object of type SomeSubclassOfShillProxy. 364 365 Connects to shill over D-Bus. If shill is not yet running, 366 retry until it is, or until |timeout_seconds| expires. 367 368 After connecting to shill, this method will verify that shill 369 is answering RPCs. No timeout is applied to the test RPC, so 370 this method _may_ block indefinitely. 371 372 @param bus D-Bus bus to use, or specify None and this object will 373 create a mainloop and bus. 374 @param timeout_seconds float number of seconds to try connecting 375 A value <= 0 will cause the method to return immediately, 376 without trying to connect. 377 @return a ShillProxy instance if we connected, or None otherwise 378 379 """ 380 end_time = time.time() + timeout_seconds 381 connection = None 382 while connection is None and time.time() < end_time: 383 try: 384 # We create instance of class on which this classmethod was 385 # called. This way, calling SubclassOfShillProxy.get_proxy() 386 # will get a proxy of the right type. 387 connection = cls(bus=bus) 388 except dbus.exceptions.DBusException as e: 389 if e.get_dbus_name() != ShillProxy.DBUS_SERVICE_UNKNOWN: 390 raise ShillProxyError('Error connecting to shill') 391 else: 392 # Wait a moment before retrying 393 time.sleep(ShillProxy.POLLING_INTERVAL_SECONDS) 394 395 if connection is None: 396 return None 397 398 # Although shill is connected to D-Bus at this point, it may 399 # not have completed initialization just yet. Call into shill, 400 # and wait for the response, to make sure that it is truly up 401 # and running. (Shill will not service D-Bus requests until 402 # initialization is complete.) 403 connection.get_profiles() 404 return connection 405 406 407 def __init__(self, bus=None): 408 if bus is None: 409 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 410 bus = dbus.SystemBus() 411 self._bus = bus 412 self._manager = self.get_dbus_object(self.DBUS_TYPE_MANAGER, '/') 413 414 415 def configure_service_by_guid(self, guid, properties={}): 416 """Configure a service identified by its GUID. 417 418 @param guid string unique identifier of service. 419 @param properties dictionary of service property:value pairs. 420 421 """ 422 config = properties.copy() 423 config[self.SERVICE_PROPERTY_GUID] = guid 424 self.configure_service(config) 425 426 427 def configure_service(self, config): 428 """Configure a service with given properties. 429 430 @param config dictionary of service property:value pairs. 431 432 """ 433 # Convert configuration values to dbus variant typed values. 434 dbus_config = ShillProxy.service_properties_to_dbus_types(config) 435 self.manager.ConfigureService(dbus_config) 436 437 438 def configure_service_for_profile(self, path, config): 439 """Configure a service in the given profile with given properties. 440 441 @param path string path of profile for which service should be 442 configured. 443 @param config dictionary of service property:value pairs. 444 445 """ 446 # Convert configuration values to dbus variant typed values. 447 dbus_config = ShillProxy.service_properties_to_dbus_types(config) 448 self.manager.ConfigureServiceForProfile(dbus.ObjectPath(path), 449 dbus_config) 450 451 452 def set_logging(self, level, scopes): 453 """Set the logging in shill to the specified |level| and |scopes|. 454 455 @param level int log level to set to in shill. 456 @param scopes list of strings of log scopes to set to in shill. 457 458 """ 459 self.manager.SetDebugLevel(level) 460 self.manager.SetDebugTags('+'.join(scopes)) 461 462 463 def set_logging_for_test(self, technology): 464 """Set the logging in shill for a test of the specified |technology|. 465 466 Set the log level to |LOG_LEVEL_FOR_TEST| and the log scopes to the 467 ones defined in |LOG_SCOPES_FOR_TEST| for |technology|. If |technology| 468 is not found in |LOG_SCOPES_FOR_TEST|, the log scopes are set to 469 |LOG_SCOPES_FOR_TEST_COMMON|. 470 471 @param technology string representing the technology type of a test 472 that the logging in shill is to be customized for. 473 474 """ 475 scopes = self.LOG_SCOPES_FOR_TEST.get(technology, 476 self.LOG_SCOPES_FOR_TEST_COMMON) 477 self.set_logging(self.LOG_LEVEL_FOR_TEST, scopes) 478 479 480 def wait_for_property_in(self, dbus_object, property_name, 481 expected_values, timeout_seconds): 482 """Wait till a property is in a list of expected values. 483 484 Block until the property |property_name| in |dbus_object| is in 485 |expected_values|, or |timeout_seconds|. 486 487 @param dbus_object DBus proxy object as returned by 488 self.get_dbus_object. 489 @param property_name string property key in dbus_object. 490 @param expected_values iterable set of values to return successfully 491 upon seeing. 492 @param timeout_seconds float number of seconds to return if we haven't 493 seen the appropriate property value in time. 494 @return tuple(successful, final_value, duration) 495 where successful is True iff we saw one of |expected_values| for 496 |property_name|, final_value is the member of |expected_values| we 497 saw, and duration is how long we waited to see that value. 498 499 """ 500 start_time = time.time() 501 duration = lambda: time.time() - start_time 502 503 update_queue = collections.deque() 504 signal_receiver = lambda key, value: update_queue.append((key, value)) 505 receiver_ref = self._bus.add_signal_receiver( 506 signal_receiver, 507 signal_name='PropertyChanged', 508 dbus_interface=dbus_object.dbus_interface, 509 path=dbus_object.object_path) 510 try: 511 # Check to make sure we're not already in a target state. 512 try: 513 properties = self.dbus2primitive( 514 dbus_object.GetProperties(utf8_strings=True)) 515 last_value = properties.get(property_name, '(no value found)') 516 if last_value in expected_values: 517 return True, last_value, duration() 518 519 except dbus.exceptions.DBusException: 520 return False, '(object reference became invalid)', duration() 521 522 context = gobject.MainLoop().get_context() 523 while duration() < timeout_seconds: 524 # Dispatch all pending events. 525 while context.iteration(False): 526 pass 527 528 while update_queue: 529 updated_property, value = map(self.dbus2primitive, 530 update_queue.popleft()) 531 if property_name != updated_property: 532 continue 533 534 last_value = value 535 if not last_value in expected_values: 536 continue 537 538 return True, last_value, duration() 539 540 time.sleep(0.2) # Give that CPU a break. CPUs love breaks. 541 finally: 542 receiver_ref.remove() 543 544 return False, last_value, duration() 545 546 547 @property 548 def manager(self): 549 """ @return DBus proxy object representing the shill Manager. """ 550 return self._manager 551 552 553 def get_active_profile(self): 554 """Get the active profile in shill. 555 556 @return dbus object representing the active profile. 557 558 """ 559 properties = self.manager.GetProperties(utf8_strings=True) 560 return self.get_dbus_object( 561 self.DBUS_TYPE_PROFILE, 562 properties[self.MANAGER_PROPERTY_ACTIVE_PROFILE]) 563 564 565 def get_dbus_object(self, type_str, path): 566 """Return the DBus object of type |type_str| at |path| in shill. 567 568 @param type_str string (e.g. self.DBUS_TYPE_SERVICE). 569 @param path path to object in shill (e.g. '/service/12'). 570 @return DBus proxy object. 571 572 """ 573 return dbus.Interface( 574 self._bus.get_object(self.DBUS_INTERFACE, path), 575 type_str) 576 577 578 def get_devices(self): 579 """Return the list of devices as dbus Interface objects""" 580 properties = self.manager.GetProperties(utf8_strings=True) 581 return [self.get_dbus_object(self.DBUS_TYPE_DEVICE, path) 582 for path in properties[self.MANAGER_PROPERTY_DEVICES]] 583 584 585 def get_profiles(self): 586 """Return the list of profiles as dbus Interface objects""" 587 properties = self.manager.GetProperties(utf8_strings=True) 588 return [self.get_dbus_object(self.DBUS_TYPE_PROFILE, path) 589 for path in properties[self.MANAGER_PROPERTY_PROFILES]] 590 591 592 def get_service(self, params): 593 """ 594 Get the shill service that matches |params|. 595 596 @param params dict of strings understood by shill to describe 597 a service. 598 @return DBus object interface representing a service. 599 600 """ 601 dbus_params = self.service_properties_to_dbus_types(params) 602 path = self.manager.GetService(dbus_params) 603 return self.get_dbus_object(self.DBUS_TYPE_SERVICE, path) 604 605 606 def get_service_for_device(self, device): 607 """Attempt to find a service that manages |device|. 608 609 @param device a dbus object interface representing a device. 610 @return Dbus object interface representing a service if found. None 611 otherwise. 612 613 """ 614 properties = self.manager.GetProperties(utf8_strings=True) 615 all_services = properties.get(self.MANAGER_PROPERTY_ALL_SERVICES, 616 None) 617 if not all_services: 618 return None 619 620 for service_path in all_services: 621 service = self.get_dbus_object(self.DBUS_TYPE_SERVICE, 622 service_path) 623 properties = service.GetProperties(utf8_strings=True) 624 device_path = properties.get(self.SERVICE_PROPERTY_DEVICE, None) 625 if device_path == device.object_path: 626 return service 627 628 return None 629 630 631 def find_object(self, object_type, properties): 632 """Find a shill object with the specified type and properties. 633 634 Return the first shill object of |object_type| whose properties match 635 all that of |properties|. 636 637 @param object_type string representing the type of object to be 638 returned. Valid values are those object types defined in 639 |OBJECT_TYPE_PROPERTY_MAP|. 640 @param properties dict of strings understood by shill to describe 641 a service. 642 @return DBus object interface representing the object found or None 643 if no matching object is found. 644 645 """ 646 if object_type not in self.OBJECT_TYPE_PROPERTY_MAP: 647 return None 648 649 dbus_type, manager_property = self.OBJECT_TYPE_PROPERTY_MAP[object_type] 650 manager_properties = self.manager.GetProperties(utf8_strings=True) 651 for path in manager_properties[manager_property]: 652 try: 653 test_object = self.get_dbus_object(dbus_type, path) 654 object_properties = test_object.GetProperties(utf8_strings=True) 655 for name, value in properties.iteritems(): 656 if (name not in object_properties or 657 self.dbus2primitive(object_properties[name]) != value): 658 break 659 else: 660 return test_object 661 662 except dbus.exceptions.DBusException, e: 663 # This could happen if for instance, you're enumerating services 664 # and test_object was removed in shill between the call to get 665 # the manager properties and the call to get the service 666 # properties. This causes failed method invocations. 667 continue 668 return None 669 670 671 def find_matching_service(self, properties): 672 """Find a service object that matches the given properties. 673 674 This re-implements the manager DBus method FindMatchingService. 675 The advantage of doing this here is that FindMatchingServices does 676 not exist on older images, which will cause tests to fail. 677 678 @param properties dict of strings understood by shill to describe 679 a service. 680 681 """ 682 return self.find_object('Service', properties) 683 684 685 def connect_service_synchronous(self, service, timeout_seconds): 686 """Connect a service and wait for its state to become connected. 687 688 @param service DBus service object to connect. 689 @param timeout_seconds number of seconds to wait for service to go 690 enter a connected state. 691 @return True if the service connected successfully. 692 693 """ 694 try: 695 service.Connect() 696 except dbus.exceptions.DBusException as e: 697 if e.get_dbus_name() != self.ERROR_ALREADY_CONNECTED: 698 raise e 699 success, _, _ = self.wait_for_property_in( 700 service, self.SERVICE_PROPERTY_STATE, 701 self.SERVICE_CONNECTED_STATES, 702 timeout_seconds=timeout_seconds) 703 return success 704 705 706 def disconnect_service_synchronous(self, service, timeout_seconds): 707 """Disconnect a service and wait for its state to go idle. 708 709 @param service DBus service object to disconnect. 710 @param timeout_seconds number of seconds to wait for service to go idle. 711 @return True if the service disconnected successfully. 712 713 """ 714 try: 715 service.Disconnect() 716 except dbus.exceptions.DBusException as e: 717 if e.get_dbus_name() not in [self.ERROR_IN_PROGRESS, 718 self.ERROR_NOT_CONNECTED]: 719 raise e 720 success, _, _ = self.wait_for_property_in( 721 service, self.SERVICE_PROPERTY_STATE, ['idle'], 722 timeout_seconds=timeout_seconds) 723 return success 724