Home | History | Annotate | Download | only in tendo
      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 
      5 import collections
      6 import dbus
      7 import dbus.mainloop.glib
      8 import logging
      9 import time
     10 
     11 from autotest_lib.client.common_lib import error
     12 from autotest_lib.client.common_lib import utils
     13 from autotest_lib.client.cros import dbus_util
     14 
     15 Service = collections.namedtuple('Service',
     16                                  ['service_id', 'service_info', 'service_ips'])
     17 Peer = collections.namedtuple('Peer', ['uuid', 'last_seen', 'services'])
     18 
     19 # DBus constants for use with peerd.
     20 SERVICE_NAME = 'org.chromium.peerd'
     21 DBUS_INTERFACE_MANAGER = 'org.chromium.peerd.Manager'
     22 DBUS_INTERFACE_PEER = 'org.chromium.peerd.Peer'
     23 DBUS_INTERFACE_SERVICE = 'org.chromium.peerd.Service'
     24 DBUS_INTERFACE_OBJECT_MANAGER = 'org.freedesktop.DBus.ObjectManager'
     25 DBUS_PATH_MANAGER = '/org/chromium/peerd/Manager'
     26 DBUS_PATH_OBJECT_MANAGER = '/org/chromium/peerd'
     27 DBUS_PATH_SELF = '/org/chromium/peerd/Self'
     28 PEER_PATH_PREFIX = '/org/chromium/peerd/peers/'
     29 PEER_PROPERTY_ID = 'UUID'
     30 PEER_PROPERTY_LAST_SEEN = 'LastSeen'
     31 SERVICE_PROPERTY_ID = 'ServiceId'
     32 SERVICE_PROPERTY_INFO = 'ServiceInfo'
     33 SERVICE_PROPERTY_IPS = 'IpInfos'
     34 SERVICE_PROPERTY_PEER_ID = 'PeerId'
     35 
     36 # Possible technologies for use with PeerdDBusHelper.start_monitoring().
     37 TECHNOLOGY_ALL = 'all'
     38 TECHNOLOGY_MDNS = 'mDNS'
     39 
     40 # We can give some options to ExposeService.
     41 EXPOSE_SERVICE_SECTION_MDNS = 'mdns'
     42 EXPOSE_SERVICE_MDNS_PORT = 'port'
     43 
     44 def 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 
     65 class 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