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