# Copyright (c) 2013 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 logging
import os
import random
import time

from autotest_lib.client.common_lib import error
from autotest_lib.server import test
from autotest_lib.server.cros import queue_barrier


# P2P_PATH is the path where the p2p server expects the sharing files.
P2P_PATH = '/var/cache/p2p'

# Prefix all the test files with P2P_TEST_PREFIX.
P2P_TEST_PREFIX = 'p2p-test'

# File size of the shared file in KB.
P2P_FILE_SIZE_KB = 80 * 1000

# After a peer finishes the download we need it to keep serving the file for
# other peers. This peer will then wait up to P2P_SERVING_TIMEOUT_SECS seconds
# for the test to conclude.
P2P_SERVING_TIMEOUT_SECS = 600

# The file is initialy shared by the master in two parts. The first part is
# available at the beginning of the test, while the second part of the file
# becomes ready in the master after P2P_SHARING_GAP_SECS seconds.
P2P_SHARING_GAP_SECS = 90

# The master and clients have to initialize the p2p service and, in the case
# of the master, generate the first part of the file on disk.
P2P_INITIALIZE_TIMEOUT_SECS = 90

class p2p_EndToEndTest(test.test):
    """Test to check that p2p works."""
    version = 1


    def run_once(self, dut, file_id, is_master, peers, barrier):
        self._dut = dut

        file_id = '%s-%s' % (P2P_TEST_PREFIX, file_id)
        file_temp_name = os.path.join(P2P_PATH, file_id + '.tmp')
        file_shared_name = os.path.join(P2P_PATH, file_id + '.p2p')

        # Ensure that p2p is running.
        dut.run('start p2p || true')
        dut.run('status p2p | grep running')

        # Prepare the file - this includes specifying its final size.
        dut.run('touch %s' % file_temp_name)
        dut.run('setfattr -n user.cros-p2p-filesize -v %d %s'
                % (P2P_FILE_SIZE_KB * 1000, file_temp_name))
        dut.run('mv %s %s' % (file_temp_name, file_shared_name))

        if is_master:
            # The master generates a file and shares a part of it but announces
            # the total size via the "user.cros-p2p-filesize" attribute.
            # To ensure that the clients are retrieving this first shared part
            # and hopefully blocking until the rest of the file is available,
            # a sleep is included in the master side.

            logging.info('Master process running.')

            first_part_size_kb = P2P_FILE_SIZE_KB / 3
            dut.run('dd if=/dev/urandom of=%s bs=1000 count=%d'
                    % (file_shared_name, first_part_size_kb))

            # This small sleep is to ensure that the new file size is updated
            # by avahi daemon.
            time.sleep(5)

            # At this point, the master is sharing a non-empty file, signal all
            # the clients that they can start the test. The clients should not
            # take more and a few seconds to launch.
            barrier.master_barrier(timeout=P2P_INITIALIZE_TIMEOUT_SECS)

            # Wait some time to allow clients download a partial file.
            time.sleep(P2P_SHARING_GAP_SECS)
            dut.run('dd if=/dev/urandom of=%s bs=1000 count=%d'
                    ' conv=notrunc oflag=append'
                    % (file_shared_name, P2P_FILE_SIZE_KB - first_part_size_kb))
        else:
            # On the client side, first wait until the master is sharing
            # a non-empty file, otherwise p2p-client will ignore the file.
            # The master should not take more than a few seconds to generate
            # the file.
            barrier.slave_barrier(timeout=P2P_INITIALIZE_TIMEOUT_SECS)

            # Wait a random time in order to not launch all the downloads
            # at the same time, otherwise all devices would be seeing
            # num-connections < $THRESHOLD .
            r = random.Random()
            secs_to_sleep = r.randint(1, 10)
            logging.debug('Sleeping %d seconds', secs_to_sleep)
            time.sleep(secs_to_sleep)

            # Attempt the file download and start sharing it while
            # downloading it.
            ret = dut.run('p2p-client --get-url=%s' % file_id)
            url = ret.stdout.strip()

            if not url:
                raise error.TestFail('p2p-client returned an empty URL.')
            else:
                logging.info('Using URL %s', url)
                dut.run('curl %s -o %s' % (url, file_shared_name))

        # Calculate the SHA1 (160 bits -> 40 characters when
        # hexencoded) of the file and report this back so the
        # server-side test can check they're all the same.
        ret = dut.run('sha1sum %s' % file_shared_name)
        sha1 = ret.stdout.strip()[0:40]
        logging.info('SHA1 is %s', sha1)

        # Wait for all the clients to finish and check the received SHA1.
        if is_master:
            try:
                client_sha1s = barrier.master_barrier(
                        timeout=P2P_SERVING_TIMEOUT_SECS)
            except queue_barrier.QueueBarrierTimeout:
                raise error.TestFail("Test failed to complete in %d seconds."
                                     % P2P_SERVING_TIMEOUT_SECS)

            for client_sha1 in client_sha1s:
                if client_sha1 != sha1:
                    # Wrong SHA1 received.
                    raise error.TestFail("Received SHA1 (%s) doesn't match "
                            "master's SHA1 (%s)." % (client_sha1, sha1))
        else:
            try:
                barrier.slave_barrier(sha1, timeout=P2P_SERVING_TIMEOUT_SECS)
            except queue_barrier.QueueBarrierTimeout:
                raise error.TestFail("Test failed to complete in %d seconds."
                                     % P2P_SERVING_TIMEOUT_SECS)


    def cleanup(self):
        # Clean the test environment and stop sharing this file.
        self._dut.run('rm -f %s/%s-*.p2p' % (P2P_PATH, P2P_TEST_PREFIX))