1# Copyright 2015 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 os 7import time 8import re 9 10import common 11from autotest_lib.client.common_lib.cros.network import ap_constants 12from autotest_lib.client.common_lib.cros.network import iw_runner 13from autotest_lib.server import hosts 14from autotest_lib.server import frontend 15from autotest_lib.server import site_utils 16from autotest_lib.server.cros.ap_configurators import ap_configurator 17from autotest_lib.server.cros.ap_configurators import ap_cartridge 18from autotest_lib.server.cros.ap_configurators import ap_spec as ap_spec_module 19 20 21def allocate_packet_capturer(lock_manager, hostname): 22 """Allocates a machine to capture packets. 23 24 Locks the allocated machine if the machine was discovered via AFE 25 to prevent tests stomping on each other. 26 27 @param lock_manager HostLockManager object. 28 @param hostname string optional hostname of a packet capture machine. 29 30 @return: An SSHHost object representing a locked packet_capture machine. 31 """ 32 if hostname is not None: 33 return hosts.SSHHost(hostname) 34 35 afe = frontend.AFE(debug=True, 36 server=site_utils.get_global_afe_hostname()) 37 return hosts.SSHHost(site_utils.lock_host_with_labels( 38 afe, lock_manager, labels=['packet_capture']) + '.cros') 39 40 41def allocate_webdriver_instance(lock_manager): 42 """Allocates a machine to capture webdriver instance. 43 44 Locks the allocated machine if the machine was discovered via AFE 45 to prevent tests stomping on each other. 46 47 @param lock_manager HostLockManager object. 48 49 @return string hostname of locked webdriver instance 50 """ 51 afe = frontend.AFE(debug=True, 52 server=site_utils.get_global_afe_hostname()) 53 webdriver_hostname = site_utils.lock_host_with_labels(afe, lock_manager, 54 labels=['webdriver']) 55 if webdriver_hostname is not None: 56 return webdriver_hostname 57 logging.error("Unable to allocate VM instance") 58 return None 59 60 61def power_on_VM(master, instance): 62 """Power on VM 63 64 @param master: chaosvmmaster SSHHost 65 @param instance: locked webdriver instance 66 67 """ 68 logging.debug('Powering on %s VM', instance) 69 power_on_cmd = 'VBoxManage startvm %s' % instance 70 master.run(power_on_cmd) 71 72 73def power_off_VM(master, instance): 74 """Power off VM 75 76 @param master: chaosvmmaster SSHHost 77 @param instance: locked webdriver instance 78 79 """ 80 logging.debug('Powering off %s VM', instance) 81 power_off_cmd = 'VBoxManage controlvm %s poweroff' % instance 82 master.run(power_off_cmd) 83 84 85def power_down_aps(aps, broken_pdus=[]): 86 """Powers down a list of aps. 87 88 @param aps: a list of APConfigurator objects. 89 @param broken_pdus: a list of broken PDUs identified. 90 """ 91 cartridge = ap_cartridge.APCartridge() 92 for ap in aps: 93 ap.power_down_router() 94 cartridge.push_configurator(ap) 95 cartridge.run_configurators(broken_pdus) 96 97 98def configure_aps(aps, ap_spec, broken_pdus=[]): 99 """Configures a given list of APs. 100 101 @param aps: a list of APConfigurator objects. 102 @param ap_spec: APSpec object corresponding to the AP configuration. 103 @param broken_pdus: a list of broken PDUs identified. 104 """ 105 cartridge = ap_cartridge.APCartridge() 106 for ap in aps: 107 ap.set_using_ap_spec(ap_spec) 108 cartridge.push_configurator(ap) 109 cartridge.run_configurators(broken_pdus) 110 111 112def is_dut_healthy(client, ap): 113 """Returns if iw scan is working properly. 114 115 Sometimes iw scan will die, especially on the Atheros chips. 116 This works around that bug. See crbug.com/358716. 117 118 @param client: a wifi_client for the DUT 119 @param ap: ap_configurator object 120 121 @returns True if the DUT is healthy (iw scan works); False otherwise. 122 """ 123 # The SSID doesn't matter, all that needs to be verified is that iw 124 # works. 125 networks = client.iw_runner.wait_for_scan_result( 126 client.wifi_if, ssids=[ap.ssid]) 127 if networks == None: 128 return False 129 return True 130 131 132def is_conn_worker_healthy(conn_worker, ap, assoc_params, job): 133 """Returns if the connection worker is working properly. 134 135 From time to time the connection worker will fail to establish a 136 connection to the APs. 137 138 @param conn_worker: conn_worker object 139 @param ap: an ap_configurator object 140 @param assoc_params: the connection association parameters 141 @param job: the Autotest job object 142 143 @returns True if the worker is healthy; False otherwise 144 """ 145 if conn_worker is None: 146 return True 147 conn_status = conn_worker.connect_work_client(assoc_params) 148 if not conn_status: 149 job.run_test('network_WiFi_ChaosConfigFailure', ap=ap, 150 error_string=ap_constants.WORK_CLI_CONNECT_FAIL, 151 tag=ap.ssid) 152 # Obtain the logs from the worker 153 log_dir_name = str('worker_client_logs_%s' % ap.ssid) 154 log_dir = os.path.join(job.resultdir, log_dir_name) 155 conn_worker.host.collect_logs( 156 '/var/log', log_dir, ignore_errors=True) 157 return False 158 return True 159 160 161def release_ap(ap, batch_locker, broken_pdus=[]): 162 """Powers down and unlocks the given AP. 163 164 @param ap: the APConfigurator under test. 165 @param batch_locker: the batch locker object. 166 @param broken_pdus: a list of broken PDUs identified. 167 """ 168 ap.power_down_router() 169 try: 170 ap.apply_settings() 171 except ap_configurator.PduNotResponding as e: 172 if ap.pdu not in broken_pdus: 173 broken_pdus.append(ap.pdu) 174 batch_locker.unlock_one_ap(ap.host_name) 175 176 177def filter_quarantined_and_config_failed_aps(aps, batch_locker, job, 178 broken_pdus=[]): 179 """Filter out all PDU quarantined and config failed APs. 180 181 @param aps: the list of ap_configurator objects to filter 182 @param batch_locker: the batch_locker object 183 @param job: an Autotest job object 184 @param broken_pdus: a list of broken PDUs identified. 185 186 @returns a list of ap_configuration objects. 187 """ 188 aps_to_remove = list() 189 for ap in aps: 190 failed_ap = False 191 if ap.pdu in broken_pdus: 192 ap.configuration_success = ap_constants.PDU_FAIL 193 if (ap.configuration_success == ap_constants.PDU_FAIL): 194 failed_ap = True 195 error_string = ap_constants.AP_PDU_DOWN 196 tag = ap.host_name + '_PDU' 197 elif (ap.configuration_success == ap_constants.CONFIG_FAIL): 198 failed_ap = True 199 error_string = ap_constants.AP_CONFIG_FAIL 200 tag = ap.host_name 201 if failed_ap: 202 tag += '_' + str(int(round(time.time()))) 203 job.run_test('network_WiFi_ChaosConfigFailure', 204 ap=ap, 205 error_string=error_string, 206 tag=tag) 207 aps_to_remove.append(ap) 208 if error_string == ap_constants.AP_CONFIG_FAIL: 209 release_ap(ap, batch_locker, broken_pdus) 210 else: 211 # Cannot use _release_ap, since power_down will fail 212 batch_locker.unlock_one_ap(ap.host_name) 213 return list(set(aps) - set(aps_to_remove)) 214 215 216def get_security_from_scan(ap, networks, job): 217 """Returns a list of securities determined from the scan result. 218 219 @param ap: the APConfigurator being testing against. 220 @param networks: List of matching networks returned from scan. 221 @param job: an Autotest job object 222 223 @returns a list of possible securities for the given network. 224 """ 225 securities = list() 226 # Sanitize MIXED security setting for both Static and Dynamic 227 # configurators before doing the comparison. 228 security = networks[0].security 229 if (security == iw_runner.SECURITY_MIXED and 230 ap.configurator_type == ap_spec_module.CONFIGURATOR_STATIC): 231 securities = [iw_runner.SECURITY_WPA, iw_runner.SECURITY_WPA2] 232 # We have only seen WPA2 be backwards compatible, and we want 233 # to verify the configurator did the right thing. So we 234 # promote this to WPA2 only. 235 elif (security == iw_runner.SECURITY_MIXED and 236 ap.configurator_type == ap_spec_module.CONFIGURATOR_DYNAMIC): 237 securities = [iw_runner.SECURITY_WPA2] 238 else: 239 securities = [security] 240 return securities 241 242 243def scan_for_networks(ssid, capturer, ap_spec): 244 """Returns a list of matching networks after running iw scan. 245 246 @param ssid: the SSID string to look for in scan. 247 @param capturer: a packet capture device. 248 @param ap_spec: APSpec object corresponding to the AP configuration. 249 250 @returns a list of the matching networks; if no networks are found at 251 all, returns None. 252 """ 253 # Setup a managed interface to perform scanning on the 254 # packet capture device. 255 freq = ap_spec_module.FREQUENCY_TABLE[ap_spec.channel] 256 wifi_if = capturer.get_wlanif(freq, 'managed') 257 capturer.host.run('%s link set %s up' % (capturer.cmd_ip, wifi_if)) 258 # We have some APs that need a while to come on-line 259 networks = capturer.iw_runner.wait_for_scan_result( 260 wifi_if, ssids=[ssid], timeout_seconds=300) 261 capturer.remove_interface(wifi_if) 262 return networks 263 264 265def return_available_networks(ap, capturer, job, ap_spec): 266 """Returns a list of networks configured as described by an APSpec. 267 268 @param ap: the APConfigurator being testing against. 269 @param capturer: a packet capture device 270 @param job: an Autotest job object. 271 @param ap_spec: APSpec object corresponding to the AP configuration. 272 273 @returns a list of networks returned from _scan_for_networks(). 274 """ 275 for i in range(2): 276 networks = scan_for_networks(ap.ssid, capturer, ap_spec) 277 if networks is None: 278 return None 279 if len(networks) == 0: 280 # The SSID wasn't even found, abort 281 logging.error('The ssid %s was not found in the scan', ap.ssid) 282 job.run_test('network_WiFi_ChaosConfigFailure', ap=ap, 283 error_string=ap_constants.AP_SSID_NOTFOUND, 284 tag=ap.ssid) 285 return list() 286 security = get_security_from_scan(ap, networks, job) 287 if ap_spec.security in security: 288 return networks 289 if i == 0: 290 # The SSID exists but the security is wrong, give the AP time 291 # to possible update it. 292 time.sleep(60) 293 if ap_spec.security not in security: 294 logging.error('%s was the expected security but got %s: %s', 295 ap_spec.security, 296 str(security).strip('[]'), 297 networks) 298 job.run_test('network_WiFi_ChaosConfigFailure', 299 ap=ap, 300 error_string=ap_constants.AP_SECURITY_MISMATCH, 301 tag=ap.ssid) 302 networks = list() 303 return networks 304 305 306def sanitize_client(host): 307 """Clean up logs and reboot the DUT. 308 309 @param host: the cros host object to use for RPC calls. 310 """ 311 host.run('rm -rf /var/log') 312 host.reboot() 313 314 315def get_firmware_ver(host): 316 """Get firmware version of DUT from /var/log/messages. 317 318 WiFi firmware version is matched against list of known firmware versions 319 from ToT. 320 321 @param host: the cros host object to use for RPC calls. 322 323 @returns the WiFi firmware version as a string, None if the version 324 cannot be found. 325 """ 326 # TODO(rpius): Need to find someway to get this info for Android/Brillo. 327 if host.get_os_type() != 'cros': 328 return None 329 330 # Firmware versions manually aggregated by installing ToT on each device 331 known_firmware_ver = ['Atheros', 'mwifiex', 'loaded firmware version', 332 'brcmf_c_preinit_dcmds'] 333 # Find and return firmware version in logs 334 for firmware_ver in known_firmware_ver: 335 result_str = host.run( 336 'awk "/%s/ {print}" /var/log/messages' % firmware_ver).stdout 337 if not result_str: 338 continue 339 else: 340 if 'Atheros' in result_str: 341 pattern = '%s \w+ Rev:\d' % firmware_ver 342 elif 'mwifiex' in result_str: 343 pattern = '%s [\d.]+ \([\w.]+\)' % firmware_ver 344 elif 'loaded firmware version' in result_str: 345 pattern = '(\d+\.\d+\.\d+)' 346 elif 'Firmware version' in result_str: 347 pattern = '\d+\.\d+\.\d+ \([\w.]+\)' 348 else: 349 logging.info('%s does not match known firmware versions.', 350 result_str) 351 return None 352 result = re.search(pattern, result_str) 353 if result: 354 return result.group(0) 355 return None 356