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     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