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