# Copyright 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import collections import dbus import dbus.mainloop.glib import logging import time from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib import utils from autotest_lib.client.cros import dbus_util Service = collections.namedtuple('Service', ['service_id', 'service_info', 'service_ips']) Peer = collections.namedtuple('Peer', ['uuid', 'last_seen', 'services']) # DBus constants for use with peerd. SERVICE_NAME = 'org.chromium.peerd' DBUS_INTERFACE_MANAGER = 'org.chromium.peerd.Manager' DBUS_INTERFACE_PEER = 'org.chromium.peerd.Peer' DBUS_INTERFACE_SERVICE = 'org.chromium.peerd.Service' DBUS_INTERFACE_OBJECT_MANAGER = 'org.freedesktop.DBus.ObjectManager' DBUS_PATH_MANAGER = '/org/chromium/peerd/Manager' DBUS_PATH_OBJECT_MANAGER = '/org/chromium/peerd' DBUS_PATH_SELF = '/org/chromium/peerd/Self' PEER_PATH_PREFIX = '/org/chromium/peerd/peers/' PEER_PROPERTY_ID = 'UUID' PEER_PROPERTY_LAST_SEEN = 'LastSeen' SERVICE_PROPERTY_ID = 'ServiceId' SERVICE_PROPERTY_INFO = 'ServiceInfo' SERVICE_PROPERTY_IPS = 'IpInfos' SERVICE_PROPERTY_PEER_ID = 'PeerId' # Possible technologies for use with PeerdDBusHelper.start_monitoring(). TECHNOLOGY_ALL = 'all' TECHNOLOGY_MDNS = 'mDNS' # We can give some options to ExposeService. EXPOSE_SERVICE_SECTION_MDNS = 'mdns' EXPOSE_SERVICE_MDNS_PORT = 'port' def make_helper(peerd_config, bus=None, timeout_seconds=10): """Wait for peerd to come up, then return a PeerdDBusHelper for it. @param peerd_config: a PeerdConfig object. @param bus: DBus bus to use, or specify None to create one internally. @param timeout_seconds: number of seconds to wait for peerd to come up. @return PeerdDBusHelper instance if peerd comes up, None otherwise. """ start_time = time.time() peerd_config.restart_with_config(timeout_seconds=timeout_seconds) if bus is None: dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() while time.time() - start_time < timeout_seconds: if not bus.name_has_owner(SERVICE_NAME): time.sleep(0.2) return PeerdDBusHelper(bus) raise error.TestFail('peerd did not start in a timely manner.') class PeerdDBusHelper(object): """Container for convenience methods related to peerd.""" def __init__(self, bus): """Construct a PeerdDBusHelper. @param bus: DBus bus to use, or specify None and this object will create a mainloop and bus. """ self._bus = bus self._manager = dbus.Interface( self._bus.get_object(SERVICE_NAME, DBUS_PATH_MANAGER), DBUS_INTERFACE_MANAGER) def _get_peers(self): object_manager = dbus.Interface( self._bus.get_object(SERVICE_NAME, DBUS_PATH_OBJECT_MANAGER), DBUS_INTERFACE_OBJECT_MANAGER) # |dbus_objects| is a map<object path, # map<interface name, # map<property name, value>>> dbus_objects = object_manager.GetManagedObjects() objects = dbus_util.dbus2primitive(dbus_objects) peer_objects = [(path, interfaces) for path, interfaces in objects.iteritems() if (path.startswith(PEER_PATH_PREFIX) and DBUS_INTERFACE_PEER in interfaces)] peers = [] for peer_path, interfaces in peer_objects: service_property_sets = [ interfaces[DBUS_INTERFACE_SERVICE] for path, interfaces in objects.iteritems() if (path.startswith(peer_path + '/services/') and DBUS_INTERFACE_SERVICE in interfaces)] services = [] for service_properties in service_property_sets: logging.debug('Found service with properties: %r', service_properties) ip_addrs = [('.'.join(map(str, ip)), port) for ip, port in service_properties[SERVICE_PROPERTY_IPS]] services.append(Service( service_id=service_properties[SERVICE_PROPERTY_ID], service_info=service_properties[SERVICE_PROPERTY_INFO], service_ips=ip_addrs)) peer_properties = interfaces[DBUS_INTERFACE_PEER] peer = Peer(uuid=peer_properties[PEER_PROPERTY_ID], last_seen=peer_properties[PEER_PROPERTY_LAST_SEEN], services=services) peers.append(peer) return peers def close(self): """Clean up peerd state related to this helper.""" utils.run('stop peerd') utils.run('start peerd') def start_monitoring(self, technologies): """Monitor the specified technologies. Note that peerd will watch bus connections and stop monitoring a technology if this bus connection goes away.A @param technologies: iterable container of TECHNOLOGY_* defined above. @return string monitoring_token for use with stop_monitoring(). """ return self._manager.StartMonitoring(technologies, dbus.Dictionary(signature='sv')) def has_peer(self, uuid): """ Return a Peer instance if peerd has found a matching peer. Optional parameters are also matched if not None. @param uuid: string unique identifier of peer. @return Peer tuple if a matching peer exists, None otherwise. """ peers = self._get_peers() logging.debug('Found peers: %r.', peers) for peer in peers: if peer.uuid != uuid: continue return peer logging.debug('No peer had a matching ID.') return None def expose_service(self, service_id, service_info, mdns_options=None): """Expose a service via peerd. Note that peerd should watch DBus connections and remove this service if our bus connection ever goes down. @param service_id: string id of service. See peerd documentation for limitations on this string. @param service_info: dict of string, string entries. See peerd documentation for relevant restrictions. @param mdns_options: dict of string, <variant type>. @return string service token for use with remove_service(). """ options = dbus.Dictionary(signature='sv') if mdns_options is not None: options[EXPOSE_SERVICE_SECTION_MDNS] = dbus.Dictionary( signature='sv') # We're going to do a little work here to make calling us easier. for k,v in mdns_options.iteritems(): if k == EXPOSE_SERVICE_MDNS_PORT: v = dbus.UInt16(v) options[EXPOSE_SERVICE_SECTION_MDNS][k] = v self._manager.ExposeService(service_id, service_info, options) def remove_service(self, service_id): """Remove a service previously added via expose_service(). @param service_id: string service ID of service to remove. """ self._manager.RemoveExposedService(service_id)