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