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