1 # Copyright 2015 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 5 import collections 6 import dbus 7 import dbus.bus 8 import dbus.service 9 import logging 10 import uuid 11 12 13 from autotest_lib.client.cros import dbus_util 14 from autotest_lib.client.cros.tendo import peerd_dbus_helper 15 from autotest_lib.client.cros.tendo.n_faced_peerd import dbus_property_exposer 16 from autotest_lib.client.cros.tendo.n_faced_peerd import peer 17 from autotest_lib.client.cros.tendo.n_faced_peerd import service 18 19 # A tuple of a bus name that sent us an ExposeService message, and an 20 # object responsible for watching for the death of that bus name's 21 # DBus connection. 22 SenderWatch = collections.namedtuple('SenderWatch', ['sender', 'watch']) 23 24 25 IGNORED_MONITORING_TOKEN_VALUE = 'This is a monitoring token.' 26 class InvalidMonitoringTokenException(Exception): 27 """Self explanatory.""" 28 29 30 class Manager(dbus_property_exposer.DBusPropertyExposer): 31 """Represents an instance of org.chromium.peerd.Manager.""" 32 33 def __init__(self, bus, ip_address, on_service_modified, unique_name, 34 object_manager): 35 """Construct an instance of Manager. 36 37 @param bus: dbus.Bus object to export this object on. 38 @param ip_address: string IP address (e.g. '127.0.01'). 39 @param on_service_modified: callback that takes a Manager instance and 40 a service ID. We'll call this whenever we Expose/Remove a 41 service via the DBus API. 42 @param unique_name: string DBus well known name to claim on DBus. 43 @param object_manager: an instance of ObjectManager. 44 45 """ 46 super(Manager, self).__init__(bus, 47 peerd_dbus_helper.DBUS_PATH_MANAGER, 48 peerd_dbus_helper.DBUS_INTERFACE_MANAGER) 49 self._bus = bus 50 self._object_manager = object_manager 51 self._peer_counter = 0 52 self._peers = dict() 53 self._ip_address = ip_address 54 self._on_service_modified = on_service_modified 55 # A map from service_ids to dbus.bus.NameOwnerWatch objects. 56 self._watches = dict() 57 self.self_peer = peer.Peer(self._bus, 58 peerd_dbus_helper.DBUS_PATH_SELF, 59 uuid.uuid4(), 60 self._object_manager, 61 is_self=True) 62 # TODO(wiley) Expose monitored technologies property 63 self._object_manager.claim_interface( 64 peerd_dbus_helper.DBUS_PATH_MANAGER, 65 peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 66 self.property_getter) 67 if (self._bus.request_name(unique_name) != 68 dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER): 69 raise Exception('Failed to claim name %s' % unique_name) 70 71 72 def _on_name_owner_changed(self, service_id, owner): 73 """Callback that removes a service when the owner disconnects from DBus. 74 75 @param service_id: string service_id of service to remove. 76 @param owner: dbus.String identifier of service owner. 77 78 """ 79 owner = dbus_util.dbus2primitive(owner) 80 logging.debug('Name owner for service=%s changed to "%s".', 81 service_id, owner) 82 if not owner: 83 self.RemoveExposedService(service_id) 84 85 86 def close(self): 87 """Release resources held by this object and child objects.""" 88 # TODO(wiley) call close on self and remote peers. 89 raise NotImplementedError('Manager.close() does not work properly') 90 91 92 def add_remote_peer(self, remote_peer): 93 """Add a remote peer to this object. 94 95 For any given face of NFacedPeerd, the other N - 1 faces are treated 96 as "remote peers" that we instantly discover changes on. 97 98 @param remote_peer: Peer object. Should be the |self_peer| of another 99 instance of Manager. 100 101 """ 102 logging.info('Adding remote peer %s', remote_peer.uuid) 103 self._peer_counter += 1 104 peer_path = '%s%d' % (peerd_dbus_helper.PEER_PATH_PREFIX, 105 self._peer_counter) 106 self._peers[remote_peer.uuid] = peer.Peer( 107 self._bus, peer_path, remote_peer.uuid, self._object_manager) 108 109 110 def on_remote_service_modified(self, remote_peer, service_id): 111 """Cause this face to update its view of a remote peer. 112 113 @param remote_peer: Peer object. Should be the |self_peer| of another 114 instance of Manager. 115 @param service_id: string service ID of remote service that has changed. 116 Note that this service may have been removed. 117 118 """ 119 local_peer = self._peers[remote_peer.uuid] 120 remote_service = remote_peer.services.get(service_id) 121 if remote_service is not None: 122 logging.info('Exposing remote service: %s', service_id) 123 local_peer.update_service(remote_service.service_id, 124 remote_service.service_info, 125 remote_service.ip_info) 126 else: 127 logging.info('Removing remote service: %s', service_id) 128 local_peer.remove_service(service_id) 129 130 131 @dbus.service.method( 132 dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 133 in_signature='sa{ss}a{sv}', out_signature='', 134 sender_keyword='sender') 135 def ExposeService(self, service_id, service_info, options, sender=None): 136 """Implementation of org.chromium.peerd.Manager.ExposeService(). 137 138 @param service_id: see DBus API documentation. 139 @param service_info: see DBus API documentation. 140 @param options: see DBus API documentation. 141 @param sender: string DBus connection ID of caller. Must match 142 |sender_keyword| value in decorator. 143 144 """ 145 if (service_id in self._watches and 146 sender != self._watches[service_id].sender): 147 logging.error('Asked to advertise a duplicate service by %s. ' 148 'Expected %s.', 149 sender, self._watches[service_id].sender) 150 raise Exception('Will not advertise duplicate services.') 151 logging.info('Exposing service %s', service_id) 152 port = options.get('mdns', dict()).get('port', 0) 153 self.self_peer.update_service( 154 service_id, service_info, 155 service.IpInfo(addr=self._ip_address, port=port)) 156 if service_id not in self._watches: 157 cb = lambda owner: self._on_name_owner_changed(service_id, owner) 158 watch = dbus.bus.NameOwnerWatch(self._bus, sender, cb) 159 self._watches[service_id] = SenderWatch(sender, watch) 160 self._on_service_modified(self, service_id) 161 162 163 @dbus.service.method( 164 dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 165 in_signature='s', out_signature='') 166 def RemoveExposedService(self, service_id): 167 """Implementation of org.chromium.peerd.Manager.RemoveExposedService(). 168 169 @param service_id: see DBus API documentation. 170 171 """ 172 logging.info('Removing service %s', service_id) 173 self._watches.pop(service_id, None) 174 self.self_peer.remove_service(service_id) 175 self._on_service_modified(self, service_id) 176 177 178 @dbus.service.method( 179 dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 180 in_signature='asa{sv}', out_signature='s') 181 def StartMonitoring(self, technologies, options): 182 """Fake implementation of org.chromium.peerd.Manager.StartMonitoring(). 183 184 We do not monitor anything in NFacedPeerd. 185 186 @param technologies: See DBus API documentation. 187 @param options: See DBus API documentation. 188 189 """ 190 return IGNORED_MONITORING_TOKEN_VALUE 191 192 193 @dbus.service.method( 194 dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 195 in_signature='s', out_signature='') 196 def StopMonitoring(self, monitoring_token): 197 """Fake implementation of org.chromium.peerd.Manager.StopMonitoring(). 198 199 We do not monitor anything in NFacedPeerd 200 201 @param monitoring_token: See DBus API documentation. 202 203 """ 204 if monitoring_token != IGNORED_MONITORING_TOKEN_VALUE: 205 raise InvalidMonitoringTokenException() 206 207 208 @dbus.service.method( 209 dbus_interface=peerd_dbus_helper.DBUS_INTERFACE_MANAGER, 210 in_signature='', out_signature='s') 211 def Ping(self): 212 """Implementation of org.chromium.peerd.Manager.Ping().""" 213 return 'Hello world!' 214