普通文本  |  159行  |  6.65 KB

# Copyright 2014 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 dpkt
import logging
import time

from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros.tendo import peerd_config
from autotest_lib.client.cros import chrooted_avahi
from autotest_lib.client.cros.netprotos import interface_host
from autotest_lib.client.cros.netprotos import zeroconf
from autotest_lib.client.cros.tendo import peerd_dbus_helper


class peerd_HandlesNameConflicts(test.test):
    """Test that peerd can handle mDNS name conflicts."""
    version = 1

    CACHE_REFRESH_PERIOD_SECONDS = 5
    FAKE_HOST_HOSTNAME = 'a-test-host'
    TEST_TIMEOUT_SECONDS = 30
    TEST_SERVICE_ID = 'test-service-0'
    TEST_SERVICE_INFO = {'some_data': 'a value',
                          'other_data': 'another value'}
    INITIAL_MDNS_PREFIX = 'initial_mdns_prefix'
    SERBUS_SERVICE_ID = 'serbus'
    SERBUS_SERVICE_NAME = '_serbus'
    SERBUS_PROTOCOL = '_tcp'
    SERBUS_PORT = 0


    def reset_peerd(self):
        """Start up a peerd instance.

        This instance will have really verbose logging and will attempt
        to use a known MDNS prefix to start out.

        """
        self._peerd = peerd_dbus_helper.make_helper(
                peerd_config.PeerdConfig(verbosity_level=3,
                                         mdns_prefix=self.INITIAL_MDNS_PREFIX))


    def initialize(self):
        # Make sure these are initiallized to None in case we throw
        # during self.initialize().
        self._chrooted_avahi = None
        self._peerd = None
        self._host = None
        self._zc_listener = None
        self._chrooted_avahi = chrooted_avahi.ChrootedAvahi()
        self._chrooted_avahi.start()
        self.reset_peerd()
        # Listen on our half of the interface pair for mDNS advertisements.
        self._host = interface_host.InterfaceHost(
                self._chrooted_avahi.unchrooted_interface_name)
        self._zc_listener = zeroconf.ZeroconfDaemon(self._host,
                                                    self.FAKE_HOST_HOSTNAME)
        # The queries for hostname/dns_domain are IPCs and therefor relatively
        # expensive.  Do them just once.
        hostname = self._chrooted_avahi.hostname
        dns_domain = self._chrooted_avahi.dns_domain
        if not hostname or not dns_domain:
            raise error.TestFail('Failed to get hostname/domain from avahi.')
        self._dns_domain = dns_domain
        self._hostname = '%s.%s' % (hostname, dns_domain)
        self._last_cache_refresh_seconds = 0


    def cleanup(self):
        for obj in (self._chrooted_avahi,
                    self._host,
                    self._peerd):
            if obj is not None:
                obj.close()


    def _get_PTR_prefix(self, service_id):
        ptr_name = '_%s._tcp.%s' % (service_id, self._dns_domain)
        found_records = self._zc_listener.cached_results(
                ptr_name, dpkt.dns.DNS_PTR)
        if len(found_records) == 0:
            logging.debug('Found no PTR records for %s', ptr_name)
            return None
        if len(found_records) > 1:
            logging.debug('Found multiple PTR records for %s', ptr_name)
            return None
        unique_name = found_records[0].data
        expected_suffix = '.' + ptr_name
        if not unique_name.endswith(expected_suffix):
            logging.error('PTR record for "%s" points to odd name: "%s"',
                          ptr_name, unique_name)
            return None
        ptr_prefix = unique_name[0:-len(expected_suffix)]
        logging.debug('PTR record for "%s" points to service with name "%s"',
                      ptr_name, ptr_prefix)
        return ptr_prefix


    def _found_expected_PTR_records(self, forbidden_record_prefix):
        for service_id in (self.SERBUS_SERVICE_ID, self.TEST_SERVICE_ID):
            prefix = self._get_PTR_prefix(service_id)
            if prefix is None:
                break
            if prefix == forbidden_record_prefix:
                logging.debug('Ignoring service with conflicting prefix')
                break
        else:
            return True
        delta_seconds = time.time() - self._last_cache_refresh_seconds
        if delta_seconds > self.CACHE_REFRESH_PERIOD_SECONDS:
            self._zc_listener.clear_cache()
            self._last_cache_refresh_seconds = time.time()
        return False


    def run_once(self):
        # Tell peerd about this exciting new service we have.
        self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO)
        # Wait for advertisements of that service to appear from avahi.
        # They should be prefixed with our special name, since there are no
        # conflicts.
        logging.info('Waiting to receive mDNS advertisements of '
                     'peerd services.')
        success, duration = self._host.run_until(
                lambda: self._found_expected_PTR_records(None),
                self.TEST_TIMEOUT_SECONDS)
        if not success:
            raise error.TestFail('Did not receive mDNS advertisements in time.')
        actual_prefix = self._get_PTR_prefix(self.SERBUS_SERVICE_ID)
        if actual_prefix != self.INITIAL_MDNS_PREFIX:
            raise error.TestFail('Expected initial mDNS advertisements to have '
                                 'a prefix=%s' % self.INITIAL_MDNS_PREFIX)
        logging.info('Found initial records advertised by peerd.')
        # Now register services with the same name, and restart peerd.
        # 1) The old instance of peerd should withdraw its services from Avahi
        # 2) The new instance of peerd should re-register services with Avahi
        # 3) Avahi should notice that the name.service_type.domain tuple
        #    conflicts with existing records, and signal this to peerd.
        # 4) Peerd should pick a new prefix and try again.
        self.reset_peerd()
        self._zc_listener.register_service(
                self.INITIAL_MDNS_PREFIX,
                self.SERBUS_SERVICE_NAME,
                self.SERBUS_PROTOCOL,
                self.SERBUS_PORT,
                ['invalid=record'])
        self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO)
        run_until_predicate = lambda: self._found_expected_PTR_records(
                self.INITIAL_MDNS_PREFIX)
        success, duration = self._host.run_until(run_until_predicate,
                                                 self.TEST_TIMEOUT_SECONDS)
        if not success:
            raise error.TestFail('Timed out waiting for peerd to change the '
                                 'record prefix.')