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