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