# 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, client.CONNECTED_STATES, 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