| # 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) |