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, utils
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