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