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 5import logging 6import time 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib.cros.network import iw_runner 10from autotest_lib.client.common_lib.cros.network import ping_runner 11from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes 12from autotest_lib.server import hosts 13from autotest_lib.server.cros.network import wifi_client 14from autotest_lib.server.cros.network import netperf_runner 15 16WORK_CLIENT_CONNECTION_RETRIES = 3 17WAIT_FOR_CONNECTION = 10 18 19class 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 124class 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 159class 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 227class 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