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