Home | History | Annotate | Download | only in network
      1 # Copyright (c) 2014 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 
      5 import logging
      6 import time
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib.cros.network import iw_runner
     10 from autotest_lib.client.common_lib.cros.network import ping_runner
     11 from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
     12 from autotest_lib.server import hosts
     13 from autotest_lib.server.cros.network import wifi_client
     14 from autotest_lib.server.cros.network import netperf_runner
     15 
     16 WORK_CLIENT_CONNECTION_RETRIES = 3
     17 WAIT_FOR_CONNECTION = 10
     18 
     19 class ConnectionWorker(object):
     20     """ ConnectionWorker is a thin layer of interfaces for worker classes """
     21 
     22     @property
     23     def name(self):
     24         """@return a string: representing name of the worker class"""
     25         raise NotImplementedError('Missing subclass implementation')
     26 
     27 
     28     @classmethod
     29     def create_from_parent(cls, parent_obj, **init_args):
     30         """Creates a derived ConnectionWorker object from the provided parent
     31         object.
     32 
     33         @param cls: derived class object which we're trying to create.
     34         @param obj: existing parent class object.
     35         @param init_args: Args to be passed to the derived class constructor.
     36 
     37         @returns Instance of cls with the required fields copied from parent.
     38         """
     39         obj = cls(**init_args)
     40         obj.work_client = parent_obj.work_client
     41         obj.host = parent_obj.host
     42         return obj
     43 
     44 
     45     def prepare_work_client(self, work_client_machine):
     46         """Prepare the SSHHost object into WiFiClient object
     47 
     48         @param work_client_machine: a SSHHost object to be wrapped
     49 
     50         """
     51         work_client_host = hosts.create_host(work_client_machine.hostname)
     52         # All packet captures in chaos lab have dual NICs. Let us use phy1 to
     53         # be a radio dedicated for work client
     54         iw = iw_runner.IwRunner(remote_host=work_client_host)
     55         phys = iw.list_phys()
     56         devs = iw.list_interfaces(desired_if_type='managed')
     57         if len(devs) > 0:
     58             logging.debug('Removing interfaces in work host machine %s', devs)
     59             for i in range(len(devs)):
     60                 iw.remove_interface(devs[i].if_name)
     61         if len(phys) > 1:
     62             logging.debug('Adding interfaces in work host machine')
     63             iw.add_interface('phy1', 'work0', 'managed')
     64             logging.debug('Interfaces in work client %s', iw.list_interfaces())
     65         elif len(phys) == 1:
     66             raise error.TestError('Not enough phys available to create a'
     67                                   'work client interface %s.' %
     68                                    work_client_host.hostname)
     69         self.work_client = wifi_client.WiFiClient(
     70                 work_client_host, './debug', False)
     71         # Make the host object easily accessible
     72         self.host = self.work_client.host
     73 
     74 
     75     def connect_work_client(self, assoc_params):
     76         """
     77         Connect client to the AP.
     78 
     79         Tries to connect the work client to AP in WORK_CLIENT_CONNECTION_RETRIES
     80         tries. If we fail to connect in all tries then we would return False
     81         otherwise returns True on successful connection to the AP.
     82 
     83         @param assoc_params: an AssociationParameters object.
     84         @return a boolean: True if work client is successfully connected to AP
     85                 or False on failing to connect to the AP
     86 
     87         """
     88         if not self.work_client.shill.init_test_network_state():
     89             logging.error('Failed to set up isolated test context profile for '
     90                           'work client.')
     91             return False
     92 
     93         success = False
     94         for i in range(WORK_CLIENT_CONNECTION_RETRIES):
     95             logging.info('Connecting work client to AP')
     96             assoc_result = xmlrpc_datatypes.deserialize(
     97                            self.work_client.shill.connect_wifi(assoc_params))
     98             success = assoc_result.success
     99             if not success:
    100                 logging.error('Connection attempt of work client failed, try %d'
    101                               ' reason: %s', (i+1), assoc_result.failure_reason)
    102             else:
    103                 logging.info('Work client connected to the AP')
    104                 self.ssid = assoc_params.ssid
    105                 break
    106         return success
    107 
    108 
    109     def cleanup(self):
    110         """Teardown work_client"""
    111         self.work_client.shill.disconnect(self.ssid)
    112         self.work_client.shill.clean_profiles()
    113 
    114 
    115     def run(self, client):
    116         """Executes the connection worker
    117 
    118         @param client: WiFiClient object representing the DUT
    119 
    120         """
    121         raise NotImplementedError('Missing subclass implementation')
    122 
    123 
    124 class ConnectionDuration(ConnectionWorker):
    125     """This test is to check the liveliness of the connection to the AP. """
    126 
    127     def __init__(self, duration_sec=30):
    128         """
    129         Holds WiFi connection open with periodic pings
    130 
    131         @param duration_sec: amount of time to hold connection in seconds
    132 
    133         """
    134 
    135         self.duration_sec = duration_sec
    136 
    137 
    138     @property
    139     def name(self):
    140         """@return a string: representing name of this class"""
    141         return 'duration'
    142 
    143 
    144     def run(self, client):
    145         """Periodically pings work client to check liveliness of the connection
    146 
    147         @param client: WiFiClient object representing the DUT
    148 
    149         """
    150         ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
    151         logging.info('Pinging work client ip: %s', self.work_client.wifi_ip)
    152         start_time = time.time()
    153         while time.time() - start_time < self.duration_sec:
    154             time.sleep(10)
    155             ping_result = client.ping(ping_config)
    156             logging.info('Connection liveness %r', ping_result)
    157 
    158 
    159 class ConnectionSuspend(ConnectionWorker):
    160     """
    161     This test is to check the liveliness of the connection to the AP with
    162     suspend resume cycle involved.
    163 
    164     """
    165 
    166     def __init__(self, suspend_sec=30):
    167         """
    168         Construct a ConnectionSuspend.
    169 
    170         @param suspend_sec: amount of time to suspend in seconds
    171 
    172         """
    173 
    174         self._suspend_sec = suspend_sec
    175 
    176 
    177     @property
    178     def name(self):
    179         """@return a string: representing name of this class"""
    180         return 'suspend'
    181 
    182 
    183     def run(self, client):
    184         """
    185         Check the liveliness of the connection to the AP by pinging the work
    186         client before and after a suspend resume.
    187 
    188         @param client: WiFiClient object representing the DUT
    189 
    190         """
    191         ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
    192         # pinging work client to ensure we have a connection
    193         logging.info('work client ip: %s', self.work_client.wifi_ip)
    194         ping_result = client.ping(ping_config)
    195         logging.info('before suspend:%r', ping_result)
    196         client.do_suspend(self._suspend_sec)
    197         # When going to suspend, DUTs using ath9k devices do not disassociate
    198         # from the AP. On resume, DUTs would re-use the association from prior
    199         # to suspend. However, this leads to some confused state for some APs
    200         # (see crbug.com/346417) where the AP responds to actions frames like
    201         # NullFunc but not to any data frames like DHCP/ARP packets from the
    202         # DUT.  Let us sleep for:
    203         #       + 2 seconds for linkmonitor to detect failure if any
    204         #       + 10 seconds for ReconnectTimer timeout
    205         #       + 5 seconds to reconnect to the AP
    206         #       + 3 seconds let us not have a very strict timeline.
    207         # 20 seconds before we start to query shill about the connection state.
    208         # TODO (krisr): add board detection code in wifi_client and adjust the
    209         # sleep time here based on the wireless chipset
    210         time.sleep(20)
    211 
    212         # Wait for WAIT_FOR_CONNECTION time before trying to ping.
    213         success, state, elapsed_time = client.wait_for_service_states(
    214                 self.ssid, ('ready', 'portal', 'online'), WAIT_FOR_CONNECTION)
    215         if not success:
    216             raise error.TestFail('DUT failed to connect to AP (%s state) after'
    217                                  'resume in %d seconds' %
    218                                  (state, WAIT_FOR_CONNECTION))
    219         else:
    220             logging.info('DUT entered %s state after %s seconds',
    221                          state, elapsed_time)
    222             # ping work client to ensure we have connection after resume.
    223             ping_result = client.ping(ping_config)
    224             logging.info('after resume:%r', ping_result)
    225 
    226 
    227 class ConnectionNetperf(ConnectionWorker):
    228     """
    229     This ConnectionWorker is used to run a sustained data transfer between the
    230     DUT and the work_client through an AP.
    231 
    232     """
    233 
    234     # Minimum expected throughput for netperf streaming tests
    235     NETPERF_MIN_THROUGHPUT = 2.0 # Mbps
    236 
    237     def __init__(self, netperf_config):
    238         """
    239         Construct a ConnectionNetperf object.
    240 
    241         @param netperf_config: NetperfConfig object to define transfer test.
    242 
    243         """
    244         self._config = netperf_config
    245 
    246 
    247     @property
    248     def name(self):
    249         """@return a string: representing name of this class"""
    250         return 'netperf_%s' % self._config.human_readable_tag
    251 
    252 
    253     def run(self, client):
    254         """
    255         Create a NetperfRunner, run netperf between DUT and work_client.
    256 
    257         @param client: WiFiClient object representing the DUT
    258 
    259         """
    260         with netperf_runner.NetperfRunner(
    261                 client, self.work_client, self._config) as netperf:
    262             ping_config = ping_runner.PingConfig(
    263                     self.work_client.wifi_ip, count=10)
    264             # pinging work client to ensure we have a connection
    265             logging.info('work client ip: %s', self.work_client.wifi_ip)
    266             ping_result = client.ping(ping_config)
    267 
    268             result = netperf.run(self._config)
    269             logging.info('Netperf Result: %s', result)
    270 
    271         if result is None:
    272             raise error.TestError('Failed to create NetperfResult')
    273 
    274         if result.duration_seconds < self._config.test_time:
    275             raise error.TestFail(
    276                     'Netperf duration too short: %0.2f < %0.2f' %
    277                     (result.duration_seconds, self._config.test_time))
    278 
    279         # TODO: Convert this limit to a perf metric crbug.com/348780
    280         if result.throughput <self.NETPERF_MIN_THROUGHPUT:
    281             raise error.TestFail(
    282                     'Netperf throughput too low: %0.2f < %0.2f' %
    283                     (result.throughput, self.NETPERF_MIN_THROUGHPUT))
    284