• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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