# 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!'