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 5import logging 6import os 7import sys 8import tempfile 9 10from autotest_lib.client.bin import test 11from autotest_lib.client.common_lib import error, utils 12from autotest_lib.client.common_lib.cros import avahi_utils 13from autotest_lib.client.cros import service_stopper 14from autotest_lib.client.cros.netprotos import cros_p2p, zeroconf 15 16 17P2P_CLIENT = '/usr/sbin/p2p-client' 18 19 20class 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