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