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 random
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server import test
12from autotest_lib.server.cros import queue_barrier
13
14
15# P2P_PATH is the path where the p2p server expects the sharing files.
16P2P_PATH = '/var/cache/p2p'
17
18# Prefix all the test files with P2P_TEST_PREFIX.
19P2P_TEST_PREFIX = 'p2p-test'
20
21# File size of the shared file in KB.
22P2P_FILE_SIZE_KB = 80 * 1000
23
24# After a peer finishes the download we need it to keep serving the file for
25# other peers. This peer will then wait up to P2P_SERVING_TIMEOUT_SECS seconds
26# for the test to conclude.
27P2P_SERVING_TIMEOUT_SECS = 600
28
29# The file is initialy shared by the master in two parts. The first part is
30# available at the beginning of the test, while the second part of the file
31# becomes ready in the master after P2P_SHARING_GAP_SECS seconds.
32P2P_SHARING_GAP_SECS = 90
33
34# The master and clients have to initialize the p2p service and, in the case
35# of the master, generate the first part of the file on disk.
36P2P_INITIALIZE_TIMEOUT_SECS = 90
37
38class p2p_EndToEndTest(test.test):
39    """Test to check that p2p works."""
40    version = 1
41
42
43    def run_once(self, dut, file_id, is_master, peers, barrier):
44        self._dut = dut
45
46        file_id = '%s-%s' % (P2P_TEST_PREFIX, file_id)
47        file_temp_name = os.path.join(P2P_PATH, file_id + '.tmp')
48        file_shared_name = os.path.join(P2P_PATH, file_id + '.p2p')
49
50        # Ensure that p2p is running.
51        dut.run('start p2p || true')
52        dut.run('status p2p | grep running')
53
54        # Prepare the file - this includes specifying its final size.
55        dut.run('touch %s' % file_temp_name)
56        dut.run('setfattr -n user.cros-p2p-filesize -v %d %s'
57                % (P2P_FILE_SIZE_KB * 1000, file_temp_name))
58        dut.run('mv %s %s' % (file_temp_name, file_shared_name))
59
60        if is_master:
61            # The master generates a file and shares a part of it but announces
62            # the total size via the "user.cros-p2p-filesize" attribute.
63            # To ensure that the clients are retrieving this first shared part
64            # and hopefully blocking until the rest of the file is available,
65            # a sleep is included in the master side.
66
67            logging.info('Master process running.')
68
69            first_part_size_kb = P2P_FILE_SIZE_KB / 3
70            dut.run('dd if=/dev/urandom of=%s bs=1000 count=%d'
71                    % (file_shared_name, first_part_size_kb))
72
73            # This small sleep is to ensure that the new file size is updated
74            # by avahi daemon.
75            time.sleep(5)
76
77            # At this point, the master is sharing a non-empty file, signal all
78            # the clients that they can start the test. The clients should not
79            # take more and a few seconds to launch.
80            barrier.master_barrier(timeout=P2P_INITIALIZE_TIMEOUT_SECS)
81
82            # Wait some time to allow clients download a partial file.
83            time.sleep(P2P_SHARING_GAP_SECS)
84            dut.run('dd if=/dev/urandom of=%s bs=1000 count=%d'
85                    ' conv=notrunc oflag=append'
86                    % (file_shared_name, P2P_FILE_SIZE_KB - first_part_size_kb))
87        else:
88            # On the client side, first wait until the master is sharing
89            # a non-empty file, otherwise p2p-client will ignore the file.
90            # The master should not take more than a few seconds to generate
91            # the file.
92            barrier.slave_barrier(timeout=P2P_INITIALIZE_TIMEOUT_SECS)
93
94            # Wait a random time in order to not launch all the downloads
95            # at the same time, otherwise all devices would be seeing
96            # num-connections < $THRESHOLD .
97            r = random.Random()
98            secs_to_sleep = r.randint(1, 10)
99            logging.debug('Sleeping %d seconds', secs_to_sleep)
100            time.sleep(secs_to_sleep)
101
102            # Attempt the file download and start sharing it while
103            # downloading it.
104            ret = dut.run('p2p-client --get-url=%s' % file_id)
105            url = ret.stdout.strip()
106
107            if not url:
108                raise error.TestFail('p2p-client returned an empty URL.')
109            else:
110                logging.info('Using URL %s', url)
111                dut.run('curl %s -o %s' % (url, file_shared_name))
112
113        # Calculate the SHA1 (160 bits -> 40 characters when
114        # hexencoded) of the file and report this back so the
115        # server-side test can check they're all the same.
116        ret = dut.run('sha1sum %s' % file_shared_name)
117        sha1 = ret.stdout.strip()[0:40]
118        logging.info('SHA1 is %s', sha1)
119
120        # Wait for all the clients to finish and check the received SHA1.
121        if is_master:
122            try:
123                client_sha1s = barrier.master_barrier(
124                        timeout=P2P_SERVING_TIMEOUT_SECS)
125            except queue_barrier.QueueBarrierTimeout:
126                raise error.TestFail("Test failed to complete in %d seconds."
127                                     % P2P_SERVING_TIMEOUT_SECS)
128
129            for client_sha1 in client_sha1s:
130                if client_sha1 != sha1:
131                    # Wrong SHA1 received.
132                    raise error.TestFail("Received SHA1 (%s) doesn't match "
133                            "master's SHA1 (%s)." % (client_sha1, sha1))
134        else:
135            try:
136                barrier.slave_barrier(sha1, timeout=P2P_SERVING_TIMEOUT_SECS)
137            except queue_barrier.QueueBarrierTimeout:
138                raise error.TestFail("Test failed to complete in %d seconds."
139                                     % P2P_SERVING_TIMEOUT_SECS)
140
141
142    def cleanup(self):
143        # Clean the test environment and stop sharing this file.
144        self._dut.run('rm -f %s/%s-*.p2p' % (P2P_PATH, P2P_TEST_PREFIX))
145