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