# Copyright 2015 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.bus import dbus.service import logging import uuid from autotest_lib.client.cros import dbus_util from autotest_lib.client.cros.tendo import peerd_dbus_helper from autotest_lib.client.cros.tendo.n_faced_peerd import dbus_property_exposer from autotest_lib.client.cros.tendo.n_faced_peerd import peer from autotest_lib.client.cros.tendo.n_faced_peerd import service # A tuple of a bus name that sent us an ExposeService message, and an # object responsible for watching for the death of that bus name's # DBus connection. SenderWatch = collections.namedtuple('SenderWatch', ['sender', 'watch']) IGNORED_MONITORING_TOKEN_VALUE = 'This is a monitoring token.' class InvalidMonitoringTokenException(Exception): """Self explanatory.""" class Manager(dbus_property_exposer.DBusPropertyExposer): """Represents an instance of org.chromium.peerd.Manager.""" def __init__(self, bus, ip_address, on_service_modified, unique_name, object_manager): """Construct an instance of Manager. @param bus: dbus.Bus object to export this object on. @param ip_address: string IP address (e.g. '127.0.01'). @param on_service_modified: callback that takes a Manager instance and a service ID. We'll call this whenever we Expose/Remove a service via the DBus API. @param unique_name: string DBus well known name to claim on DBus. @param object_manager: an instance of ObjectManager. """ super(Manager, self).__init__(bus, peerd_dbus_helper.DBUS_PATH_MANAGER, peerd_dbus_helper.DBUS_INTERFACE_MANAGER) self._bus = bus self._object_manager = object_manager self._peer_counter = 0 self._peers = dict() self._ip_address = ip_address self._on_service_modified = on_service_modified # A map from service_ids to dbus.bus.NameOwnerWatch objects. self._watches = dict() self.self_peer = peer.Peer(self._bus, peerd_dbus_helper.DBUS_PATH_SELF, uuid.uuid4(), self._object_manager, is_self=True) # TODO(wiley) Expose monitored technologies property self._object_manager.claim_interface( peerd_dbus_helper.DBUS_PATH_MANAGER, peerd_dbus_helper.DBUS_INTERFACE_MANAGER, self.property_getter) if (self._bus.request_name(unique_name) != dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER): raise Exception('Failed to claim name %s' % unique_name) def _on_name_owner_changed(self, service_id, owner): """Callback that removes a service when the owner disconnects from DBus. @param service_id: string service_id of service to remove. @param owner: dbus.String identifier of service owner. """ owner = dbus_util.dbus2primitive(owner) logging.debug('Name owner for service=%s changed to "%s".', service_id, owner) if not owner: self.RemoveExposedService(service_id) def close(self): """Release resources held by this object and child objects.""" # TODO(wiley) call close on self and remote peers. raise NotImplementedError('Manager.close() does not work properly') def add_remote_peer(self, remote_peer): """Add a remote peer to this object. For any given face of NFacedPeerd, the other N - 1 faces are treated as "remote peers" that we instantly discover changes on. @param remote_peer: Peer object. Should be the |self_peer| of another instance of Manager. """ logging.info('Adding remote peer %s', remote_peer.uuid) self._peer_counter += 1 peer_path = '%s%d' % (peerd_dbus_helper.PEER_PATH_PREFIX, self._peer_counter) self._peers[remote_peer.uuid] = peer.Peer( self._bus, peer_path, remote_peer.uuid, self._object_manager) def on_remote_service_modified(self, remote_peer, service_id): """Cause this face to update its view of a remote peer. @param remote_peer: Peer object. Should be the |self_peer| of another instance of Manager. @param service_id: string service ID of remote service that has changed. Note that this service may have been removed. """ local_peer = self._peers[remote_peer.uuid] remote_service = remote_peer.services.get(service_id) if remote_service is not None: logging.info('Exposing remote service: %s', service_id) local_peer.update_service(remote_service.service_id, remote_service.service_info, remote_service.ip_info) else: logging.info('Removing remote service: %s', service_id) local_peer.remove_service(service_id) @dbus.service.method( dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, in_signature='sa{ss}a{sv}', out_signature='', sender_keyword='sender') def ExposeService(self, service_id, service_info, options, sender=None): """Implementation of org.chromium.peerd.Manager.ExposeService(). @param service_id: see DBus API documentation. @param service_info: see DBus API documentation. @param options: see DBus API documentation. @param sender: string DBus connection ID of caller. Must match |sender_keyword| value in decorator. """ if (service_id in self._watches and sender != self._watches[service_id].sender): logging.error('Asked to advertise a duplicate service by %s. ' 'Expected %s.', sender, self._watches[service_id].sender) raise Exception('Will not advertise duplicate services.') logging.info('Exposing service %s', service_id) port = options.get('mdns', dict()).get('port', 0) self.self_peer.update_service( service_id, service_info, service.IpInfo(addr=self._ip_address, port=port)) if service_id not in self._watches: cb = lambda owner: self._on_name_owner_changed(service_id, owner) watch = dbus.bus.NameOwnerWatch(self._bus, sender, cb) self._watches[service_id] = SenderWatch(sender, watch) self._on_service_modified(self, service_id) @dbus.service.method( dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, in_signature='s', out_signature='') def RemoveExposedService(self, service_id): """Implementation of org.chromium.peerd.Manager.RemoveExposedService(). @param service_id: see DBus API documentation. """ logging.info('Removing service %s', service_id) self._watches.pop(service_id, None) self.self_peer.remove_service(service_id) self._on_service_modified(self, service_id) @dbus.service.method( dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, in_signature='asa{sv}', out_signature='s') def StartMonitoring(self, technologies, options): """Fake implementation of org.chromium.peerd.Manager.StartMonitoring(). We do not monitor anything in NFacedPeerd. @param technologies: See DBus API documentation. @param options: See DBus API documentation. """ return IGNORED_MONITORING_TOKEN_VALUE @dbus.service.method( dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, in_signature='s', out_signature='') def StopMonitoring(self, monitoring_token): """Fake implementation of org.chromium.peerd.Manager.StopMonitoring(). We do not monitor anything in NFacedPeerd @param monitoring_token: See DBus API documentation. """ if monitoring_token != IGNORED_MONITORING_TOKEN_VALUE: raise InvalidMonitoringTokenException() @dbus.service.method( dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, in_signature='', out_signature='s') def Ping(self): """Implementation of org.chromium.peerd.Manager.Ping().""" return 'Hello world!'