• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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 logging
9import time
10
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib import utils
13from autotest_lib.client.cros import dbus_util
14
15Service = collections.namedtuple('Service',
16                                 ['service_id', 'service_info', 'service_ips'])
17Peer = collections.namedtuple('Peer', ['uuid', 'last_seen', 'services'])
18
19# DBus constants for use with peerd.
20SERVICE_NAME = 'org.chromium.peerd'
21DBUS_INTERFACE_MANAGER = 'org.chromium.peerd.Manager'
22DBUS_INTERFACE_PEER = 'org.chromium.peerd.Peer'
23DBUS_INTERFACE_SERVICE = 'org.chromium.peerd.Service'
24DBUS_INTERFACE_OBJECT_MANAGER = 'org.freedesktop.DBus.ObjectManager'
25DBUS_PATH_MANAGER = '/org/chromium/peerd/Manager'
26DBUS_PATH_OBJECT_MANAGER = '/org/chromium/peerd'
27DBUS_PATH_SELF = '/org/chromium/peerd/Self'
28PEER_PATH_PREFIX = '/org/chromium/peerd/peers/'
29PEER_PROPERTY_ID = 'UUID'
30PEER_PROPERTY_LAST_SEEN = 'LastSeen'
31SERVICE_PROPERTY_ID = 'ServiceId'
32SERVICE_PROPERTY_INFO = 'ServiceInfo'
33SERVICE_PROPERTY_IPS = 'IpInfos'
34SERVICE_PROPERTY_PEER_ID = 'PeerId'
35
36# Possible technologies for use with PeerdDBusHelper.start_monitoring().
37TECHNOLOGY_ALL = 'all'
38TECHNOLOGY_MDNS = 'mDNS'
39
40# We can give some options to ExposeService.
41EXPOSE_SERVICE_SECTION_MDNS = 'mdns'
42EXPOSE_SERVICE_MDNS_PORT = 'port'
43
44def make_helper(peerd_config, bus=None, timeout_seconds=10):
45    """Wait for peerd to come up, then return a PeerdDBusHelper for it.
46
47    @param peerd_config: a PeerdConfig object.
48    @param bus: DBus bus to use, or specify None to create one internally.
49    @param timeout_seconds: number of seconds to wait for peerd to come up.
50    @return PeerdDBusHelper instance if peerd comes up, None otherwise.
51
52    """
53    start_time = time.time()
54    peerd_config.restart_with_config(timeout_seconds=timeout_seconds)
55    if bus is None:
56        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
57        bus = dbus.SystemBus()
58    while time.time() - start_time < timeout_seconds:
59        if not bus.name_has_owner(SERVICE_NAME):
60            time.sleep(0.2)
61        return PeerdDBusHelper(bus)
62    raise error.TestFail('peerd did not start in a timely manner.')
63
64
65class PeerdDBusHelper(object):
66    """Container for convenience methods related to peerd."""
67
68    def __init__(self, bus):
69        """Construct a PeerdDBusHelper.
70
71        @param bus: DBus bus to use, or specify None and this object will
72                    create a mainloop and bus.
73
74        """
75        self._bus = bus
76        self._manager = dbus.Interface(
77                self._bus.get_object(SERVICE_NAME, DBUS_PATH_MANAGER),
78                DBUS_INTERFACE_MANAGER)
79
80
81    def _get_peers(self):
82        object_manager = dbus.Interface(
83                self._bus.get_object(SERVICE_NAME, DBUS_PATH_OBJECT_MANAGER),
84                DBUS_INTERFACE_OBJECT_MANAGER)
85        # |dbus_objects| is a map<object path,
86        #                         map<interface name,
87        #                             map<property name, value>>>
88        dbus_objects = object_manager.GetManagedObjects()
89        objects = dbus_util.dbus2primitive(dbus_objects)
90        peer_objects = [(path, interfaces)
91                        for path, interfaces in objects.iteritems()
92                        if (path.startswith(PEER_PATH_PREFIX) and
93                            DBUS_INTERFACE_PEER in interfaces)]
94        peers = []
95        for peer_path, interfaces in peer_objects:
96            service_property_sets = [
97                    interfaces[DBUS_INTERFACE_SERVICE]
98                    for path, interfaces in objects.iteritems()
99                    if (path.startswith(peer_path + '/services/') and
100                        DBUS_INTERFACE_SERVICE in interfaces)]
101            services = []
102            for service_properties in service_property_sets:
103                logging.debug('Found service with properties: %r',
104                              service_properties)
105                ip_addrs = [('.'.join(map(str, ip)), port) for ip, port
106                            in service_properties[SERVICE_PROPERTY_IPS]]
107                services.append(Service(
108                        service_id=service_properties[SERVICE_PROPERTY_ID],
109                        service_info=service_properties[SERVICE_PROPERTY_INFO],
110                        service_ips=ip_addrs))
111            peer_properties = interfaces[DBUS_INTERFACE_PEER]
112            peer = Peer(uuid=peer_properties[PEER_PROPERTY_ID],
113                        last_seen=peer_properties[PEER_PROPERTY_LAST_SEEN],
114                        services=services)
115            peers.append(peer)
116        return peers
117
118
119    def close(self):
120        """Clean up peerd state related to this helper."""
121        utils.run('stop peerd')
122        utils.run('start peerd')
123
124
125    def start_monitoring(self, technologies):
126        """Monitor the specified technologies.
127
128        Note that peerd will watch bus connections and stop monitoring a
129        technology if this bus connection goes away.A
130
131        @param technologies: iterable container of TECHNOLOGY_* defined above.
132        @return string monitoring_token for use with stop_monitoring().
133
134        """
135        return self._manager.StartMonitoring(technologies,
136                                             dbus.Dictionary(signature='sv'))
137
138
139    def has_peer(self, uuid):
140        """
141        Return a Peer instance if peerd has found a matching peer.
142
143        Optional parameters are also matched if not None.
144
145        @param uuid: string unique identifier of peer.
146        @return Peer tuple if a matching peer exists, None otherwise.
147
148        """
149        peers = self._get_peers()
150        logging.debug('Found peers: %r.', peers)
151        for peer in peers:
152            if peer.uuid != uuid:
153                continue
154            return peer
155        logging.debug('No peer had a matching ID.')
156        return None
157
158
159    def expose_service(self, service_id, service_info, mdns_options=None):
160        """Expose a service via peerd.
161
162        Note that peerd should watch DBus connections and remove this service
163        if our bus connection ever goes down.
164
165        @param service_id: string id of service.  See peerd documentation
166                           for limitations on this string.
167        @param service_info: dict of string, string entries.  See peerd
168                             documentation for relevant restrictions.
169        @param mdns_options: dict of string, <variant type>.
170        @return string service token for use with remove_service().
171
172        """
173        options = dbus.Dictionary(signature='sv')
174        if mdns_options is not None:
175            options[EXPOSE_SERVICE_SECTION_MDNS] = dbus.Dictionary(
176                    signature='sv')
177            # We're going to do a little work here to make calling us easier.
178            for k,v in mdns_options.iteritems():
179                if k == EXPOSE_SERVICE_MDNS_PORT:
180                    v = dbus.UInt16(v)
181                options[EXPOSE_SERVICE_SECTION_MDNS][k] = v
182        self._manager.ExposeService(service_id, service_info, options)
183
184
185    def remove_service(self, service_id):
186        """Remove a service previously added via expose_service().
187
188        @param service_id: string service ID of service to remove.
189
190        """
191        self._manager.RemoveExposedService(service_id)
192