普通文本  |  284行  |  10.35 KB

# Copyright (c) 2014 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 time

from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros.network import iw_runner
from autotest_lib.client.common_lib.cros.network import ping_runner
from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
from autotest_lib.server import hosts
from autotest_lib.server.cros.network import wifi_client
from autotest_lib.server.cros.network import netperf_runner

WORK_CLIENT_CONNECTION_RETRIES = 3
WAIT_FOR_CONNECTION = 10

class ConnectionWorker(object):
    """ ConnectionWorker is a thin layer of interfaces for worker classes """

    @property
    def name(self):
        """@return a string: representing name of the worker class"""
        raise NotImplementedError('Missing subclass implementation')


    @classmethod
    def create_from_parent(cls, parent_obj, **init_args):
        """Creates a derived ConnectionWorker object from the provided parent
        object.

        @param cls: derived class object which we're trying to create.
        @param obj: existing parent class object.
        @param init_args: Args to be passed to the derived class constructor.

        @returns Instance of cls with the required fields copied from parent.
        """
        obj = cls(**init_args)
        obj.work_client = parent_obj.work_client
        obj.host = parent_obj.host
        return obj


    def prepare_work_client(self, work_client_machine):
        """Prepare the SSHHost object into WiFiClient object

        @param work_client_machine: a SSHHost object to be wrapped

        """
        work_client_host = hosts.create_host(work_client_machine.hostname)
        # All packet captures in chaos lab have dual NICs. Let us use phy1 to
        # be a radio dedicated for work client
        iw = iw_runner.IwRunner(remote_host=work_client_host)
        phys = iw.list_phys()
        devs = iw.list_interfaces(desired_if_type='managed')
        if len(devs) > 0:
            logging.debug('Removing interfaces in work host machine %s', devs)
            for i in range(len(devs)):
                iw.remove_interface(devs[i].if_name)
        if len(phys) > 1:
            logging.debug('Adding interfaces in work host machine')
            iw.add_interface('phy1', 'work0', 'managed')
            logging.debug('Interfaces in work client %s', iw.list_interfaces())
        elif len(phys) == 1:
            raise error.TestError('Not enough phys available to create a'
                                  'work client interface %s.' %
                                   work_client_host.hostname)
        self.work_client = wifi_client.WiFiClient(
                work_client_host, './debug', False)
        # Make the host object easily accessible
        self.host = self.work_client.host


    def connect_work_client(self, assoc_params):
        """
        Connect client to the AP.

        Tries to connect the work client to AP in WORK_CLIENT_CONNECTION_RETRIES
        tries. If we fail to connect in all tries then we would return False
        otherwise returns True on successful connection to the AP.

        @param assoc_params: an AssociationParameters object.
        @return a boolean: True if work client is successfully connected to AP
                or False on failing to connect to the AP

        """
        if not self.work_client.shill.init_test_network_state():
            logging.error('Failed to set up isolated test context profile for '
                          'work client.')
            return False

        success = False
        for i in range(WORK_CLIENT_CONNECTION_RETRIES):
            logging.info('Connecting work client to AP')
            assoc_result = xmlrpc_datatypes.deserialize(
                           self.work_client.shill.connect_wifi(assoc_params))
            success = assoc_result.success
            if not success:
                logging.error('Connection attempt of work client failed, try %d'
                              ' reason: %s', (i+1), assoc_result.failure_reason)
            else:
                logging.info('Work client connected to the AP')
                self.ssid = assoc_params.ssid
                break
        return success


    def cleanup(self):
        """Teardown work_client"""
        self.work_client.shill.disconnect(self.ssid)
        self.work_client.shill.clean_profiles()


    def run(self, client):
        """Executes the connection worker

        @param client: WiFiClient object representing the DUT

        """
        raise NotImplementedError('Missing subclass implementation')


class ConnectionDuration(ConnectionWorker):
    """This test is to check the liveliness of the connection to the AP. """

    def __init__(self, duration_sec=30):
        """
        Holds WiFi connection open with periodic pings

        @param duration_sec: amount of time to hold connection in seconds

        """

        self.duration_sec = duration_sec


    @property
    def name(self):
        """@return a string: representing name of this class"""
        return 'duration'


    def run(self, client):
        """Periodically pings work client to check liveliness of the connection

        @param client: WiFiClient object representing the DUT

        """
        ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
        logging.info('Pinging work client ip: %s', self.work_client.wifi_ip)
        start_time = time.time()
        while time.time() - start_time < self.duration_sec:
            time.sleep(10)
            ping_result = client.ping(ping_config)
            logging.info('Connection liveness %r', ping_result)


class ConnectionSuspend(ConnectionWorker):
    """
    This test is to check the liveliness of the connection to the AP with
    suspend resume cycle involved.

    """

    def __init__(self, suspend_sec=30):
        """
        Construct a ConnectionSuspend.

        @param suspend_sec: amount of time to suspend in seconds

        """

        self._suspend_sec = suspend_sec


    @property
    def name(self):
        """@return a string: representing name of this class"""
        return 'suspend'


    def run(self, client):
        """
        Check the liveliness of the connection to the AP by pinging the work
        client before and after a suspend resume.

        @param client: WiFiClient object representing the DUT

        """
        ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
        # pinging work client to ensure we have a connection
        logging.info('work client ip: %s', self.work_client.wifi_ip)
        ping_result = client.ping(ping_config)
        logging.info('before suspend:%r', ping_result)
        client.do_suspend(self._suspend_sec)
        # When going to suspend, DUTs using ath9k devices do not disassociate
        # from the AP. On resume, DUTs would re-use the association from prior
        # to suspend. However, this leads to some confused state for some APs
        # (see crbug.com/346417) where the AP responds to actions frames like
        # NullFunc but not to any data frames like DHCP/ARP packets from the
        # DUT.  Let us sleep for:
        #       + 2 seconds for linkmonitor to detect failure if any
        #       + 10 seconds for ReconnectTimer timeout
        #       + 5 seconds to reconnect to the AP
        #       + 3 seconds let us not have a very strict timeline.
        # 20 seconds before we start to query shill about the connection state.
        # TODO (krisr): add board detection code in wifi_client and adjust the
        # sleep time here based on the wireless chipset
        time.sleep(20)

        # Wait for WAIT_FOR_CONNECTION time before trying to ping.
        success, state, elapsed_time = client.wait_for_service_states(
                self.ssid, ('ready', 'portal', 'online'), WAIT_FOR_CONNECTION)
        if not success:
            raise error.TestFail('DUT failed to connect to AP (%s state) after'
                                 'resume in %d seconds' %
                                 (state, WAIT_FOR_CONNECTION))
        else:
            logging.info('DUT entered %s state after %s seconds',
                         state, elapsed_time)
            # ping work client to ensure we have connection after resume.
            ping_result = client.ping(ping_config)
            logging.info('after resume:%r', ping_result)


class ConnectionNetperf(ConnectionWorker):
    """
    This ConnectionWorker is used to run a sustained data transfer between the
    DUT and the work_client through an AP.

    """

    # Minimum expected throughput for netperf streaming tests
    NETPERF_MIN_THROUGHPUT = 2.0 # Mbps

    def __init__(self, netperf_config):
        """
        Construct a ConnectionNetperf object.

        @param netperf_config: NetperfConfig object to define transfer test.

        """
        self._config = netperf_config


    @property
    def name(self):
        """@return a string: representing name of this class"""
        return 'netperf_%s' % self._config.human_readable_tag


    def run(self, client):
        """
        Create a NetperfRunner, run netperf between DUT and work_client.

        @param client: WiFiClient object representing the DUT

        """
        with netperf_runner.NetperfRunner(
                client, self.work_client, self._config) as netperf:
            ping_config = ping_runner.PingConfig(
                    self.work_client.wifi_ip, count=10)
            # pinging work client to ensure we have a connection
            logging.info('work client ip: %s', self.work_client.wifi_ip)
            ping_result = client.ping(ping_config)

            result = netperf.run(self._config)
            logging.info('Netperf Result: %s', result)

        if result is None:
            raise error.TestError('Failed to create NetperfResult')

        if result.duration_seconds < self._config.test_time:
            raise error.TestFail(
                    'Netperf duration too short: %0.2f < %0.2f' %
                    (result.duration_seconds, self._config.test_time))

        # TODO: Convert this limit to a perf metric crbug.com/348780
        if result.throughput <self.NETPERF_MIN_THROUGHPUT:
            raise error.TestFail(
                    'Netperf throughput too low: %0.2f < %0.2f' %
                    (result.throughput, self.NETPERF_MIN_THROUGHPUT))