• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import collections
6import dbus
7import dbus.mainloop.glib
8import gobject
9import time
10
11from autotest_lib.client.cros import dbus_util
12
13
14class ShillProxyError(Exception):
15    """Exceptions raised by ShillProxy and its children."""
16    pass
17
18
19class ShillProxyTimeoutError(ShillProxyError):
20    """Timeout exception raised by ShillProxy and its children."""
21    def __init__(self, desc):
22        super(ShillProxyTimeoutError, self).__init__(
23                'Timed out waiting for condition %s.' % desc)
24
25
26class ShillProxy(object):
27    """A wrapper around a DBus proxy for shill."""
28
29    # Core DBus error names
30    DBUS_ERROR_UNKNOWN_OBJECT = 'org.freedesktop.DBus.Error.UnknownObject'
31    # Shill error names
32    ERROR_ALREADY_CONNECTED = 'org.chromium.flimflam.Error.AlreadyConnected'
33    ERROR_FAILURE = 'org.chromium.flimflam.Error.Failure'
34    ERROR_INCORRECT_PIN = 'org.chromium.flimflam.Error.IncorrectPin'
35    ERROR_IN_PROGRESS = 'org.chromium.flimflam.Error.InProgress'
36    ERROR_NOT_CONNECTED = 'org.chromium.flimflam.Error.NotConnected'
37    ERROR_NOT_SUPPORTED = 'org.chromium.flimflam.Error.NotSupported'
38    ERROR_PIN_BLOCKED = 'org.chromium.flimflam.Error.PinBlocked'
39
40
41    DBUS_INTERFACE = 'org.chromium.flimflam'
42    DBUS_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
43    DBUS_TYPE_DEVICE = 'org.chromium.flimflam.Device'
44    DBUS_TYPE_IPCONFIG = 'org.chromium.flimflam.IPConfig'
45    DBUS_TYPE_MANAGER = 'org.chromium.flimflam.Manager'
46    DBUS_TYPE_PROFILE = 'org.chromium.flimflam.Profile'
47    DBUS_TYPE_SERVICE = 'org.chromium.flimflam.Service'
48
49    ENTRY_FIELD_NAME = 'Name'
50    ENTRY_FIELD_TYPE = 'Type'
51
52    MANAGER_PROPERTY_ACTIVE_PROFILE = 'ActiveProfile'
53    MANAGER_PROPERTY_DEVICES = 'Devices'
54    MANAGER_PROPERTY_NO_AUTOCONNECT_TECHNOLOGIES = 'NoAutoConnectTechnologies'
55    MANAGER_PROPERTY_ENABLED_TECHNOLOGIES = 'EnabledTechnologies'
56    MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES = 'ProhibitedTechnologies'
57    MANAGER_PROPERTY_UNINITIALIZED_TECHNOLOGIES = 'UninitializedTechnologies'
58    MANAGER_PROPERTY_PROFILES = 'Profiles'
59    MANAGER_PROPERTY_SERVICES = 'Services'
60    MANAGER_PROPERTY_DEFAULT_SERVICE = 'DefaultService'
61    MANAGER_PROPERTY_ALL_SERVICES = 'ServiceCompleteList'
62    MANAGER_PROPERTY_DHCPPROPERTY_HOSTNAME = 'DHCPProperty.Hostname'
63    MANAGER_PROPERTY_DHCPPROPERTY_VENDORCLASS = 'DHCPProperty.VendorClass'
64    MANAGER_PROPERTY_WIFI_GLOBAL_FT_ENABLED = 'WiFi.GlobalFTEnabled'
65
66    MANAGER_OPTIONAL_PROPERTY_MAP = {
67        MANAGER_PROPERTY_DHCPPROPERTY_HOSTNAME: dbus.String,
68        MANAGER_PROPERTY_DHCPPROPERTY_VENDORCLASS: dbus.String,
69        MANAGER_PROPERTY_WIFI_GLOBAL_FT_ENABLED: dbus.Boolean
70    }
71
72    PROFILE_PROPERTY_ENTRIES = 'Entries'
73    PROFILE_PROPERTY_NAME = 'Name'
74
75    OBJECT_TYPE_PROPERTY_MAP = {
76        'Device': ( DBUS_TYPE_DEVICE, MANAGER_PROPERTY_DEVICES ),
77        'Profile': ( DBUS_TYPE_PROFILE, MANAGER_PROPERTY_PROFILES ),
78        'Service': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_SERVICES ),
79        'AnyService': ( DBUS_TYPE_SERVICE, MANAGER_PROPERTY_ALL_SERVICES )
80    }
81
82    DEVICE_ENUMERATION_TIMEOUT = 30
83    DEVICE_ENABLE_DISABLE_TIMEOUT = 60
84    SERVICE_DISCONNECT_TIMEOUT = 5
85
86    SERVICE_PROPERTY_AUTOCONNECT = 'AutoConnect'
87    SERVICE_PROPERTY_DEVICE = 'Device'
88    SERVICE_PROPERTY_GUID = 'GUID'
89    SERVICE_PROPERTY_HEX_SSID = 'WiFi.HexSSID'
90    SERVICE_PROPERTY_HIDDEN = 'WiFi.HiddenSSID'
91    SERVICE_PROPERTY_MODE = 'Mode'
92    SERVICE_PROPERTY_NAME = 'Name'
93    SERVICE_PROPERTY_PASSPHRASE = 'Passphrase'
94    SERVICE_PROPERTY_PROFILE = 'Profile'
95    SERVICE_PROPERTY_SAVE_CREDENTIALS = 'SaveCredentials'
96    SERVICE_PROPERTY_FT_ENABLED = 'WiFi.FTEnabled'
97    # Unless you really care whether a network is WPA (TSN) vs. WPA-2
98    # (RSN), you should use SERVICE_PROPERTY_SECURITY_CLASS.
99    SERVICE_PROPERTY_SECURITY_RAW = 'Security'
100    SERVICE_PROPERTY_SECURITY_CLASS = 'SecurityClass'
101    SERVICE_PROPERTY_SSID = 'SSID'
102    SERVICE_PROPERTY_STRENGTH = 'Strength'
103    SERVICE_PROPERTY_STATE = 'State'
104    SERVICE_PROPERTY_STATIC_IP_CONFIG = 'StaticIPConfig'
105    SERVICE_PROPERTY_TYPE = 'Type'
106
107    # EAP related properties.
108    SERVICE_PROPERTY_EAP_EAP = 'EAP.EAP'
109    SERVICE_PROPERTY_EAP_INNER_EAP = 'EAP.InnerEAP'
110    SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity'
111    SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password'
112    SERVICE_PROPERTY_EAP_CA_CERT_PEM = 'EAP.CACertPEM'
113    SERVICE_PROPERTY_CLIENT_CERT_ID = 'EAP.CertID'
114    SERVICE_PROPERTY_EAP_KEY_MGMT = 'EAP.KeyMgmt'
115    SERVICE_PROPERTY_EAP_PIN = 'EAP.PIN'
116    SERVICE_PROPERTY_PRIVATE_KEY_ID = 'EAP.KeyID'
117    SERVICE_PROPERTY_USE_SYSTEM_CAS = 'EAP.UseSystemCAs'
118
119    # OpenVPN related properties.
120    SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM = 'OpenVPN.CACertPEM'
121    SERVICE_PROPERTY_OPENVPN_PASSWORD = 'OpenVPN.Password'
122    SERVICE_PROPERTY_OPENVPN_PKCS11_ID = 'OpenVPN.Pkcs11.ID'
123    SERVICE_PROPERTY_OPENVPN_PKCS11_PIN = 'OpenVPN.Pkcs11.PIN'
124    SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST = 'Provider.Host'
125    SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE = 'Provider.Type'
126    SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU = 'OpenVPN.RemoteCertEKU'
127    SERVICE_PROPERTY_OPENVPN_USER = 'OpenVPN.User'
128    SERVICE_PROPERTY_OPENVPN_VERB = 'OpenVPN.Verb'
129    SERVICE_PROPERTY_OPENVPN_VERIFY_HASH = 'OpenVPN.VerifyHash'
130    SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME = 'OpenVPN.VerifyX509Name'
131    SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE = 'OpenVPN.VerifyX509Type'
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 (dbus-type, additional kwargs).
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_CONFIG: (dbus.Dictionary,
164                                            {'signature' : 'sv'}),
165
166        SERVICE_PROPERTY_EAP_EAP: (dbus.String, {}),
167        SERVICE_PROPERTY_EAP_INNER_EAP: (dbus.String, {}),
168        SERVICE_PROPERTY_EAP_IDENTITY: (dbus.String, {}),
169        SERVICE_PROPERTY_EAP_PASSWORD: (dbus.String, {}),
170        SERVICE_PROPERTY_EAP_CA_CERT_PEM: (dbus.Array, {}),
171        SERVICE_PROPERTY_CLIENT_CERT_ID: (dbus.String, {}),
172        SERVICE_PROPERTY_EAP_KEY_MGMT: (dbus.String, {}),
173        SERVICE_PROPERTY_EAP_PIN: (dbus.String, {}),
174        SERVICE_PROPERTY_PRIVATE_KEY_ID: (dbus.String, {}),
175        SERVICE_PROPERTY_USE_SYSTEM_CAS: (dbus.Boolean, {}),
176
177        SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM: (dbus.Array, {}),
178        SERVICE_PROPERTY_OPENVPN_PASSWORD: (dbus.String, {}),
179        SERVICE_PROPERTY_OPENVPN_PKCS11_ID: (dbus.String, {}),
180        SERVICE_PROPERTY_OPENVPN_PKCS11_PIN: (dbus.String, {}),
181        SERVICE_PROPERTY_OPENVPN_PROVIDER_HOST: (dbus.String, {}),
182        SERVICE_PROPERTY_OPENVPN_PROVIDER_TYPE: (dbus.String, {}),
183        SERVICE_PROPERTY_OPENVPN_REMOTE_CERT_EKU: (dbus.String, {}),
184        SERVICE_PROPERTY_OPENVPN_USER: (dbus.String, {}),
185        SERVICE_PROPERTY_OPENVPN_VERB: (dbus.String, {}),
186        SERVICE_PROPERTY_OPENVPN_VERIFY_HASH: (dbus.String, {}),
187        SERVICE_PROPERTY_OPENVPN_VERIFY_X509_NAME: (dbus.String, {}),
188        SERVICE_PROPERTY_OPENVPN_VERIFY_X509_TYPE: (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', 'no-connectivity', 'redirect-found',
202                                'portal-suspected', 'online', 'ready']
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
224    VALUE_POWERED_ON = True
225    VALUE_POWERED_OFF = False
226
227    POLLING_INTERVAL_SECONDS = 0.2
228
229    # Default log level used in connectivity tests.
230    LOG_LEVEL_FOR_TEST = -4
231
232    # Default log scopes used in connectivity tests.
233    LOG_SCOPES_FOR_TEST_COMMON = [
234        'connection',
235        'dbus',
236        'device',
237        'link',
238        'manager',
239        'portal',
240        'service'
241    ]
242
243    # Default log scopes used in connectivity tests for specific technologies.
244    LOG_SCOPES_FOR_TEST = {
245        TECHNOLOGY_CELLULAR: LOG_SCOPES_FOR_TEST_COMMON + ['cellular'],
246        TECHNOLOGY_ETHERNET: LOG_SCOPES_FOR_TEST_COMMON + ['ethernet'],
247        TECHNOLOGY_VPN: LOG_SCOPES_FOR_TEST_COMMON + ['vpn'],
248        TECHNOLOGY_WIFI: LOG_SCOPES_FOR_TEST_COMMON + ['wifi'],
249    }
250
251    UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
252
253
254    @staticmethod
255    def str2dbus(dbus_class, value):
256        """Typecast string property values to dbus types.
257
258        This mostly makes it easy to special case Boolean constructors
259        to interpret strings like 'false' and '0' as False.
260
261        @param dbus_class: DBus class object.
262        @param value: value to pass to constructor.
263
264        """
265        if isinstance(dbus_class, dbus.Boolean):
266            return dbus_class(value.lower() in ('true','1'))
267        else:
268            return dbus_class(value)
269
270
271    @staticmethod
272    def service_properties_to_dbus_types(in_dict):
273        """Convert service properties to dbus types.
274
275        @param in_dict: Dictionary containing service properties.
276        @return DBus variant dictionary containing service properties.
277
278        """
279        dbus_dict = {}
280        for key, value in in_dict.iteritems():
281                if key not in ShillProxy.SERVICE_PROPERTY_MAP:
282                        raise ShillProxyError('Unsupported property %s' % (key))
283                (dbus_type, kwargs) = ShillProxy.SERVICE_PROPERTY_MAP[key]
284                dbus_dict[key] = dbus_type(value, variant_level=1, **kwargs)
285        return dbus_dict
286
287
288    @classmethod
289    def dbus2primitive(cls, value):
290        """Typecast values from dbus types to python types.
291
292        @param value: dbus object to convert to a primitive.
293
294        """
295        return dbus_util.dbus2primitive(value)
296
297
298    @staticmethod
299    def get_dbus_property(interface, property_key):
300        """get property on a dbus Interface
301
302        @param interface dbus Interface to receive new setting
303        @param property_key string name of property on interface
304        @return python typed object representing property value or None
305
306        """
307        properties = interface.GetProperties(utf8_strings=True)
308        if property_key in properties:
309            return ShillProxy.dbus2primitive(properties[property_key])
310        else:
311            return None
312
313
314    @staticmethod
315    def set_dbus_property(interface, property_key, value):
316        """set property on a dbus Interface
317
318        @param interface dbus Interface to receive new setting
319        @param property_key string name of property on interface
320        @param value string value to set for property on interface from string
321
322        """
323        properties = interface.GetProperties(utf8_strings=True)
324        if property_key not in properties:
325            raise ShillProxyError('No property %s found in %s' %
326                    (property_key, interface.object_path))
327        else:
328            dbus_class = properties[property_key].__class__
329            interface.SetProperty(property_key,
330                    ShillProxy.str2dbus(dbus_class, value))
331
332
333    @staticmethod
334    def set_optional_dbus_property(interface, property_key, value):
335        """set an optional property on a dbus Interface.
336
337        This method can be used for properties that are optionally listed
338        in the profile.  It skips the initial check of the property
339        being in the interface.GetProperties list.
340
341        @param interface dbus Interface to receive new setting
342        @param property_key string name of property on interface
343        @param value string value to set for property on interface from string
344
345        """
346        if property_key not in ShillProxy.MANAGER_OPTIONAL_PROPERTY_MAP:
347                raise ShillProxyError('Unsupported property %s' %
348                                      (property_key))
349        else:
350            dbus_class = ShillProxy.MANAGER_OPTIONAL_PROPERTY_MAP[property_key]
351            interface.SetProperty(property_key,
352                                  ShillProxy.str2dbus(dbus_class, value))
353
354
355    @classmethod
356    def get_proxy(cls, bus=None, timeout_seconds=10):
357        """Create a Proxy, retrying if necessary.
358
359        This method creates a proxy object of the required subclass of
360        ShillProxy. A call to SomeSubclassOfShillProxy.get_proxy() will return
361        an object of type SomeSubclassOfShillProxy.
362
363        Connects to shill over D-Bus. If shill is not yet running,
364        retry until it is, or until |timeout_seconds| expires.
365
366        After connecting to shill, this method will verify that shill
367        is answering RPCs. No timeout is applied to the test RPC, so
368        this method _may_ block indefinitely.
369
370        @param bus D-Bus bus to use, or specify None and this object will
371            create a mainloop and bus.
372        @param timeout_seconds float number of seconds to try connecting
373            A value <= 0 will cause the method to return immediately,
374            without trying to connect.
375        @return a ShillProxy instance if we connected, or None otherwise
376
377        """
378        end_time = time.time() + timeout_seconds
379        connection = None
380        while connection is None and time.time() < end_time:
381            try:
382                # We create instance of class on which this classmethod was
383                # called. This way, calling SubclassOfShillProxy.get_proxy()
384                # will get a proxy of the right type.
385                connection = cls(bus=bus)
386            except dbus.exceptions.DBusException as e:
387                if e.get_dbus_name() != ShillProxy.DBUS_SERVICE_UNKNOWN:
388                    raise ShillProxyError('Error connecting to shill')
389                else:
390                    # Wait a moment before retrying
391                    time.sleep(ShillProxy.POLLING_INTERVAL_SECONDS)
392
393        if connection is None:
394            return None
395
396        # Although shill is connected to D-Bus at this point, it may
397        # not have completed initialization just yet. Call into shill,
398        # and wait for the response, to make sure that it is truly up
399        # and running. (Shill will not service D-Bus requests until
400        # initialization is complete.)
401        connection.get_profiles()
402        return connection
403
404
405    def __init__(self, bus=None):
406        if bus is None:
407            dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
408            bus = dbus.SystemBus()
409        self._bus = bus
410        self._manager = self.get_dbus_object(self.DBUS_TYPE_MANAGER, '/')
411
412
413    def configure_service_by_guid(self, guid, properties={}):
414        """Configure a service identified by its GUID.
415
416        @param guid string unique identifier of service.
417        @param properties dictionary of service property:value pairs.
418
419        """
420        config = properties.copy()
421        config[self.SERVICE_PROPERTY_GUID] = guid
422        self.configure_service(config)
423
424
425    def configure_service(self, config):
426        """Configure a service with given properties.
427
428        @param config dictionary of service property:value pairs.
429        @return DBus object interface representing configured Service.
430
431        """
432        # Convert configuration values to dbus variant typed values.
433        dbus_config = ShillProxy.service_properties_to_dbus_types(config)
434        path = self.manager.ConfigureService(dbus_config)
435        return self.get_dbus_object(self.DBUS_TYPE_SERVICE, path)
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                                     introspect=False),
576                type_str)
577
578
579    def get_devices(self):
580        """Return the list of devices as dbus Interface objects"""
581        properties = self.manager.GetProperties(utf8_strings=True)
582        return [self.get_dbus_object(self.DBUS_TYPE_DEVICE, path)
583                for path in properties[self.MANAGER_PROPERTY_DEVICES]]
584
585
586    def get_profiles(self):
587        """Return the list of profiles as dbus Interface objects"""
588        properties = self.manager.GetProperties(utf8_strings=True)
589        return [self.get_dbus_object(self.DBUS_TYPE_PROFILE, path)
590                for path in properties[self.MANAGER_PROPERTY_PROFILES]]
591
592
593    def get_service(self, params):
594        """
595        Get the shill service that matches |params|.
596
597        @param params dict of strings understood by shill to describe
598            a service.
599        @return DBus object interface representing a service.
600
601        """
602        dbus_params = self.service_properties_to_dbus_types(params)
603        path = self.manager.GetService(dbus_params)
604        return self.get_dbus_object(self.DBUS_TYPE_SERVICE, path)
605
606
607    def get_service_for_device(self, device):
608        """Attempt to find a service that manages |device|.
609
610        @param device a dbus object interface representing a device.
611        @return Dbus object interface representing a service if found. None
612                otherwise.
613
614        """
615        properties = self.manager.GetProperties(utf8_strings=True)
616        all_services = properties.get(self.MANAGER_PROPERTY_ALL_SERVICES,
617                                      None)
618        if not all_services:
619            return None
620
621        for service_path in all_services:
622            service = self.get_dbus_object(self.DBUS_TYPE_SERVICE,
623                                           service_path)
624            properties = service.GetProperties(utf8_strings=True)
625            device_path = properties.get(self.SERVICE_PROPERTY_DEVICE, None)
626            if device_path == device.object_path:
627                return service
628
629        return None
630
631
632    def find_object(self, object_type, properties):
633        """Find a shill object with the specified type and properties.
634
635        Return the first shill object of |object_type| whose properties match
636        all that of |properties|.
637
638        @param object_type string representing the type of object to be
639            returned. Valid values are those object types defined in
640            |OBJECT_TYPE_PROPERTY_MAP|.
641        @param properties dict of strings understood by shill to describe
642            a service.
643        @return DBus object interface representing the object found or None
644            if no matching object is found.
645
646        """
647        if object_type not in self.OBJECT_TYPE_PROPERTY_MAP:
648            return None
649
650        dbus_type, manager_property = self.OBJECT_TYPE_PROPERTY_MAP[object_type]
651        manager_properties = self.manager.GetProperties(utf8_strings=True)
652        for path in manager_properties[manager_property]:
653            try:
654                test_object = self.get_dbus_object(dbus_type, path)
655                object_properties = test_object.GetProperties(utf8_strings=True)
656                for name, value in properties.iteritems():
657                    if (name not in object_properties or
658                        self.dbus2primitive(object_properties[name]) != value):
659                        break
660                else:
661                    return test_object
662
663            except dbus.exceptions.DBusException, e:
664                # This could happen if for instance, you're enumerating services
665                # and test_object was removed in shill between the call to get
666                # the manager properties and the call to get the service
667                # properties.  This causes failed method invocations.
668                continue
669        return None
670
671
672    def find_matching_service(self, properties):
673        """Find a service object that matches the given properties.
674
675        This re-implements the manager DBus method FindMatchingService.
676        The advantage of doing this here is that FindMatchingServices does
677        not exist on older images, which will cause tests to fail.
678
679        @param properties dict of strings understood by shill to describe
680            a service.
681
682        """
683        return self.find_object('Service', properties)
684
685
686    def connect_service_synchronous(self, service, timeout_seconds):
687        """Connect a service and wait for its state to become connected.
688
689        @param service DBus service object to connect.
690        @param timeout_seconds number of seconds to wait for service to go
691            enter a connected state.
692        @return True if the service connected successfully.
693
694        """
695        try:
696            service.Connect()
697        except dbus.exceptions.DBusException as e:
698            if e.get_dbus_name() != self.ERROR_ALREADY_CONNECTED:
699                raise e
700        success, _, _ = self.wait_for_property_in(
701                service, self.SERVICE_PROPERTY_STATE,
702                self.SERVICE_CONNECTED_STATES,
703                timeout_seconds=timeout_seconds)
704        return success
705
706
707    def disconnect_service_synchronous(self, service, timeout_seconds):
708        """Disconnect a service and wait for its state to go idle.
709
710        @param service DBus service object to disconnect.
711        @param timeout_seconds number of seconds to wait for service to go idle.
712        @return True if the service disconnected successfully.
713
714        """
715        try:
716            service.Disconnect()
717        except dbus.exceptions.DBusException as e:
718            if e.get_dbus_name() not in [self.ERROR_IN_PROGRESS,
719                                         self.ERROR_NOT_CONNECTED]:
720                raise e
721        success, _, _ = self.wait_for_property_in(
722                service, self.SERVICE_PROPERTY_STATE, ['idle'],
723                timeout_seconds=timeout_seconds)
724        return success
725
726
727    def get_default_interface_name(self):
728        """Retrieve the name of the default interface.
729
730        Default interface is determined via the Manager's default service.
731
732        @return Device name string, or None.
733        """
734        service_path = self.get_dbus_property(self.manager,
735                self.MANAGER_PROPERTY_DEFAULT_SERVICE)
736        if not service_path:
737            return None
738        service = self.get_dbus_object(self.DBUS_TYPE_SERVICE, service_path)
739        device_path = self.get_dbus_property(service,
740                self.SERVICE_PROPERTY_DEVICE)
741        if not device_path:
742            return None
743        device = self.get_dbus_object(self.DBUS_TYPE_DEVICE, device_path)
744        return self.get_dbus_property(device, self.DEVICE_PROPERTY_INTERFACE)
745