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