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