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