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