Home | History | Annotate | Download | only in p2p_ConsumeFiles
      1 # Copyright (c) 2013 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 logging
      6 import os
      7 import sys
      8 import tempfile
      9 
     10 from autotest_lib.client.bin import test, utils
     11 from autotest_lib.client.common_lib import error, utils
     12 from autotest_lib.client.common_lib.cros import avahi_utils
     13 from autotest_lib.client.cros import service_stopper
     14 from autotest_lib.client.cros.netprotos import cros_p2p, zeroconf
     15 
     16 
     17 P2P_CLIENT = '/usr/sbin/p2p-client'
     18 
     19 
     20 class p2p_ConsumeFiles(test.test):
     21     """The P2P Client class tester.
     22 
     23     Creates a fake network of peers with lansim and tests if p2p-client can
     24     discover files on that network.
     25     """
     26     version = 1
     27 
     28     def setup(self):
     29         self.job.setup_dep(['lansim'])
     30 
     31 
     32     def initialize(self):
     33         dep = 'lansim'
     34         dep_dir = os.path.join(self.autodir, 'deps', dep)
     35         logging.info('lansim is at %s', dep_dir)
     36         self.job.install_pkg(dep, 'dep', dep_dir)
     37 
     38         # Import the lansim modules installed on lansim/build/
     39         sys.path.append(os.path.join(dep_dir, 'build'))
     40 
     41         self._services = None
     42         self._tap = None
     43 
     44 
     45     def cleanup(self):
     46         avahi_utils.avahi_stop()
     47 
     48         if self._tap:
     49             self._tap.down()
     50 
     51         if self._services:
     52             self._services.restore_services()
     53 
     54 
     55     def _setup_avahi(self):
     56         """Initializes avahi daemon on a new tap interface."""
     57         from lansim import tuntap
     58         # Ensure p2p and avahi aren't running.
     59         self._services = service_stopper.ServiceStopper(['p2p', 'avahi'])
     60         self._services.stop_services()
     61 
     62         # Initialize avahi-daemon listenning only on the fake TAP interface.
     63         self._tap = tuntap.TunTap(tuntap.IFF_TAP, name='faketap')
     64 
     65         # The network 169.254/16 shouldn't clash with other real services. We
     66         # use a /24 subnet of it here.
     67         self._tap.set_addr('169.254.10.1', mask=24)
     68         self._tap.up()
     69 
     70         # Re-launch avahi-daemon on the tap interface.
     71         avahi_utils.avahi_start_on_iface(self._tap.name)
     72 
     73 
     74     def _run_p2p_client(self, args, timeout=10., ignore_status=False):
     75         """Run p2p-client with the provided arguments.
     76 
     77         @param args: list of strings, each one representing an argument.
     78         @param timeout: Timeout for p2p-client in seconds before it's killed.
     79         @return: the return value of the process and the stdout content.
     80         """
     81         fd, tempfn = tempfile.mkstemp(prefix='p2p-output')
     82         ret = utils.run(
     83                 P2P_CLIENT, args=['-v=1'] + list(args), timeout=timeout,
     84                 ignore_timeout=True, ignore_status=True,
     85                 stdout_tee=open(tempfn, 'w'), stderr_tee=sys.stdout)
     86         url = os.fdopen(fd).read()
     87         os.unlink(tempfn)
     88 
     89         if not ignore_status and ret is None:
     90             self._join_simulator()
     91             raise error.TestFail('p2p-client %s timeout.' % ' '.join(args))
     92 
     93         if not ignore_status and ret.exit_status != 0:
     94             self._join_simulator()
     95             raise error.TestFail('p2p-client %s finished with value: %d' % (
     96                                  ' '.join(args), ret.exit_status))
     97 
     98         return None if ret is None else ret.exit_status, url
     99 
    100 
    101     def _join_simulator(self):
    102         """Stops the simulator and logs any exception generated there."""
    103         self._sim.stop()
    104         self._sim.join()
    105         if self._sim.error:
    106             logging.error('SimulatorThread exception: %r', self._sim.error)
    107             logging.error(self._sim.traceback)
    108 
    109 
    110     def run_once(self):
    111         from lansim import simulator, host
    112 
    113         # Setup the environment where avahi-daemon runs during the test.
    114         self._setup_avahi()
    115 
    116         self._sim = simulator.SimulatorThread(self._tap)
    117         # Create three peers host-a, host-b and host-c sharing a set of files.
    118         # This first block creates the fake host on the simulator. For clarity
    119         # and easier debug, note that the last octect on the IPv4 address is the
    120         # ASCII for a, b and c respectively.
    121         peer_a = host.SimpleHost(self._sim, '94:EB:2C:00:00:61',
    122                                  '169.254.10.97')
    123         peer_b = host.SimpleHost(self._sim, '94:EB:2C:00:00:62',
    124                                  '169.254.10.98')
    125         peer_c = host.SimpleHost(self._sim, '94:EB:2C:00:00:63',
    126                                  '169.254.10.99')
    127 
    128         # Run a userspace implementation of avahi + p2p-server on the fake
    129         # hosts. This announces the P2P service on each fake host.
    130         zero_a = zeroconf.ZeroconfDaemon(peer_a, 'host-a')
    131         zero_b = zeroconf.ZeroconfDaemon(peer_b, 'host-b')
    132         zero_c = zeroconf.ZeroconfDaemon(peer_c, 'host-c')
    133 
    134         cros_a = cros_p2p.CrosP2PDaemon(zero_a)
    135         cros_b = cros_p2p.CrosP2PDaemon(zero_b)
    136         cros_c = cros_p2p.CrosP2PDaemon(zero_c)
    137 
    138         # Add files to each host. All the three hosts share the file "everyone"
    139         # with different size, used to test the minimum-size argument.
    140         # host-a and host-b share another file only-a and only-b respectively,
    141         # used to check that the p2p-client picks the right peer.
    142         cros_a.add_file('everyone', 1000)
    143         cros_b.add_file('everyone', 10000)
    144         cros_c.add_file('everyone', 20000)
    145 
    146         cros_a.add_file('only-a', 5000)
    147 
    148         cros_b.add_file('only-b', 8000)
    149 
    150         # Initially set the number of connections on the network to a low number
    151         # (two) that later will be increased to test if p2p-client hangs when
    152         # there are too many connections.
    153         cros_a.set_num_connections(1)
    154         cros_c.set_num_connections(1)
    155 
    156         self._sim.start()
    157 
    158         ### Request a file shared from only one peer.
    159         _ret, url = self._run_p2p_client(
    160                 args=('--get-url=only-a',), timeout=10.)
    161 
    162         if url.strip() != 'http://169.254.10.97:16725/only-a':
    163             self._join_simulator()
    164             raise error.TestFail('Received unknown url: "%s"' % url)
    165 
    166         ### Check that the num_connections is reported properly.
    167         _ret, conns = self._run_p2p_client(args=('--num-connections',),
    168                                           timeout=10.)
    169         if conns.strip() != '2':
    170             self._join_simulator()
    171             raise error.TestFail('Wrong number of connections reported: %s' %
    172                                  conns)
    173 
    174         ### Request a file shared from a peer with enough of the file.
    175         _ret, url = self._run_p2p_client(
    176                 args=('--get-url=everyone', '--minimum-size=15000'),
    177                 timeout=10.)
    178 
    179         if url.strip() != 'http://169.254.10.99:16725/everyone':
    180             self._join_simulator()
    181             raise error.TestFail('Received unknown url: "%s"' % url)
    182 
    183         ### Request too much bytes of an existing file.
    184         ret, url = self._run_p2p_client(
    185                 args=('--get-url=only-b', '--minimum-size=10000'),
    186                 timeout=10., ignore_status=True)
    187 
    188         if url:
    189             self._join_simulator()
    190             raise error.TestFail('Received url but expected none: "%s"' % url)
    191         if ret == 0:
    192             raise error.TestFail('p2p-client returned no URL, but without an '
    193                                  'error.')
    194 
    195         ### Check that p2p-client hangs while waiting for a peer when there are
    196         ### too many connections.
    197         self._sim.run_on_simulator(
    198                 lambda: cros_a.set_num_connections(99, announce=True))
    199 
    200         # For a query on the DUT to check that the new information is received.
    201         for attempt in range(5):
    202             _ret, conns = self._run_p2p_client(args=('--num-connections',),
    203                                                timeout=10.)
    204             conns = conns.strip()
    205             if conns == '100':
    206                 break
    207         if conns != '100':
    208             self._join_simulator()
    209             raise error.TestFail("p2p-client --num-connections doesn't reflect "
    210                                  "the current number of connections on the "
    211                                  "network, returned %s" % conns)
    212 
    213         ret, url = self._run_p2p_client(
    214                 args=('--get-url=only-b',), timeout=5., ignore_status=True)
    215         if not ret is None:
    216             self._join_simulator()
    217             raise error.TestFail('p2p-client finished but should have waited '
    218                                  'for num_connections to drop.')
    219 
    220         self._sim.stop()
    221         self._sim.join()
    222 
    223         if self._sim.error:
    224             raise error.TestError('SimulatorThread ended with an exception: %r'
    225                                   % self._sim.error)
    226