1# Copyright 2014 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 5import collections 6import logging 7 8from autotest_lib.client.bin import test 9from autotest_lib.client.common_lib import error 10from autotest_lib.client.common_lib.cros.tendo import peerd_config 11from autotest_lib.client.cros import chrooted_avahi 12from autotest_lib.client.cros.netprotos import interface_host 13from autotest_lib.client.cros.netprotos import zeroconf 14from autotest_lib.client.cros.tendo import peerd_dbus_helper 15 16 17class peerd_DiscoverServices(test.test): 18 """Test that peerd can correctly discover services over mDNS.""" 19 version = 1 20 21 FakeService = collections.namedtuple('FakeService', 22 'service_id service_info port') 23 FAKE_HOST_HOSTNAME = 'test-host' 24 TEST_TIMEOUT_SECONDS = 30 25 PEER_ID = '123e4567-e89b-12d3-a456-426655440000' 26 PEER_SERBUS_VERSION = '1.12' 27 PEER_SERVICES = [FakeService('test-service-0', 28 {'some_data': 'a value', 29 'other_data': 'another value', 30 }, 31 8080), 32 FakeService('test-service-1', 33 {'again': 'so much data', 34 }, 35 8081), 36 ] 37 SERBUS_SERVICE_NAME = '_serbus' 38 SERBUS_PROTOCOL = '_tcp' 39 SERBUS_PORT = 0 40 SERBUS_TXT_DICT = {'ver': PEER_SERBUS_VERSION, 41 'id': PEER_ID, 42 'services': '.'.join([service.service_id 43 for service in PEER_SERVICES]) 44 } 45 UNIQUE_PREFIX = 'a_unique_mdns_prefix' 46 47 48 49 def initialize(self): 50 # Make sure these are initiallized to None in case we throw 51 # during self.initialize(). 52 self._chrooted_avahi = None 53 self._peerd = None 54 self._host = None 55 self._zc_listener = None 56 self._chrooted_avahi = chrooted_avahi.ChrootedAvahi() 57 self._chrooted_avahi.start() 58 # Start up a fresh copy of peerd with really verbose logging. 59 self._peerd = peerd_dbus_helper.make_helper( 60 peerd_config.PeerdConfig(verbosity_level=3)) 61 # Listen on our half of the interface pair for mDNS advertisements. 62 self._host = interface_host.InterfaceHost( 63 self._chrooted_avahi.unchrooted_interface_name) 64 self._zc_listener = zeroconf.ZeroconfDaemon(self._host, 65 self.FAKE_HOST_HOSTNAME) 66 # The queries for hostname/dns_domain are IPCs and therefore relatively 67 # expensive. Do them just once. 68 hostname = self._chrooted_avahi.hostname 69 dns_domain = self._chrooted_avahi.dns_domain 70 if not hostname or not dns_domain: 71 raise error.TestFail('Failed to get hostname/domain from avahi.') 72 self._dns_domain = dns_domain 73 self._hostname = '%s.%s' % (hostname, dns_domain) 74 75 76 def cleanup(self): 77 for obj in (self._chrooted_avahi, 78 self._host, 79 self._peerd): 80 if obj is not None: 81 obj.close() 82 83 84 def _has_expected_peer(self): 85 peer = self._peerd.has_peer(self.PEER_ID) 86 if peer is None: 87 logging.debug('No peer found.') 88 return False 89 logging.debug('Found peer=%s', peer) 90 if len(peer.services) != len(self.PEER_SERVICES): 91 logging.debug('Found %d services, but expected %d.', 92 len(peer.services), len(self.PEER_SERVICES)) 93 return False 94 for service_id, info, port in self.PEER_SERVICES: 95 service = None 96 for s in peer.services: 97 if s.service_id == service_id: 98 service = s 99 break 100 else: 101 logging.debug('No service %s found.', service_id) 102 return False 103 if service.service_info != info: 104 logging.debug('Invalid info found for service %s, ' 105 'expected %r but got %r.', service_id, 106 info, service.service_info) 107 return False 108 if len(service.service_ips) != 1: 109 logging.debug('Missing service IP for service %s.', 110 service_id) 111 return False 112 # We're publishing records from a "peer" outside the chroot. 113 expected_addr = (self._chrooted_avahi.MONITOR_IF_IP.addr, port) 114 if service.service_ips[0] != expected_addr: 115 logging.debug('Expected service IP for service %s=%r ' 116 'but got %r.', 117 service_id, expected_addr, service.service_ips[0]) 118 return False 119 return True 120 121 122 def run_once(self): 123 # Expose serbus mDNS records through our fake peer. 124 self._zc_listener.register_service( 125 self.UNIQUE_PREFIX, 126 self.SERBUS_SERVICE_NAME, 127 self.SERBUS_PROTOCOL, 128 self.SERBUS_PORT, 129 ['='.join(pair) for pair in self.SERBUS_TXT_DICT.iteritems()]) 130 for service_id, info, port in self.PEER_SERVICES: 131 self._zc_listener.register_service( 132 self.UNIQUE_PREFIX, 133 '_' + service_id, 134 self.SERBUS_PROTOCOL, 135 port, 136 ['='.join(pair) for pair in info.iteritems()]) 137 138 # Look for mDNS records through peerd 139 self._peerd.start_monitoring([peerd_dbus_helper.TECHNOLOGY_MDNS]) 140 # Wait for advertisements of that service to appear from avahi. 141 logging.info('Waiting for peerd to discover our services.') 142 success, duration = self._host.run_until(self._has_expected_peer, 143 self.TEST_TIMEOUT_SECONDS) 144 logging.debug('Took %f seconds to find our peer.', duration) 145 if not success: 146 raise error.TestFail('Peerd failed to publish suitable DBus ' 147 'proxies in time.') 148