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