# Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import datetime import logging import pprint import time import common from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib.cros.network import ap_constants from autotest_lib.server import site_linux_system from autotest_lib.server.cros import host_lock_manager from autotest_lib.server.cros.ap_configurators import ap_batch_locker from autotest_lib.server.cros.network import chaos_clique_utils as utils from autotest_lib.server.cros.network import connection_worker from autotest_lib.server.cros.clique_lib import clique_dut_locker from autotest_lib.server.cros.clique_lib import clique_dut_log_collector from autotest_lib.server.cros.clique_lib import clique_dut_updater class CliqueRunner(object): """Object to run a network_WiFi_CliqueXXX test.""" def __init__(self, test, dut_pool_spec, ap_specs): """Initializes and runs test. @param test: a string, test name. @param dut_pool_spec: a list of pool sets. Each set contains a list of board: labels to chose the required DUT's. @param ap_specs: a list of APSpec objects corresponding to the APs needed for the test. """ self._test = test self._ap_specs = ap_specs self._dut_pool_spec = dut_pool_spec self._dut_pool = [] # Log server and DUT times dt = datetime.datetime.now() logging.info('Server time: %s', dt.strftime('%a %b %d %H:%M:%S %Y')) def _allocate_dut_pool(self, dut_locker): """Allocate the required DUT's from the spec for the test. The DUT objects are stored in a list of sets in |_dut_pool| attribute. @param dut_locker: DUTBatchLocker object used to allocate the DUTs for the test pool. @return: Returns a list of DUTObjects allocated. """ self._dut_pool = dut_locker.get_dut_pool() # Flatten the list of DUT objects into a single list. dut_objects = sum(self._dut_pool, []) return dut_objects @staticmethod def _update_dut_pool(dut_objects, release_version): """Allocate the required DUT's from the spec for the test. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. @param release_version: A chromeOS release version. @return: True if all the DUT's successfully upgraded, False otherwise. """ dut_updater = clique_dut_updater.CliqueDUTUpdater() return dut_updater.update_dut_pool(dut_objects, release_version) @staticmethod def _collect_dut_pool_logs(dut_objects, job): """Allocate the required DUT's from the spec for the test. The DUT objects are stored in a list of sets in |_dut_pool| attribute. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. @param job: Autotest job object to be used for log collection. @return: Returns a list of DUTObjects allocated. """ log_collector = clique_dut_log_collector.CliqueDUTLogCollector() log_collector.collect_logs(dut_objects, job) @staticmethod def _are_all_duts_healthy(dut_objects, ap): """Returns if iw scan is not working on any of the DUTs. Sometimes iw scan will die, especially on the Atheros chips. This works around that bug. See crbug.com/358716. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. @param ap: ap_configurator object @returns True if all the DUTs are healthy, False otherwise. """ healthy = True for dut in dut_objects: if not utils.is_dut_healthy(dut.wifi_client, ap): logging.error('DUT %s not healthy.', dut.host.hostname) healthy = False return healthy @staticmethod def _sanitize_all_duts(dut_objects): """Clean up logs and reboot all the DUTs. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. """ for dut in dut_objects: utils.sanitize_client(dut.host) @staticmethod def _sync_time_on_all_duts(dut_objects): """Syncs time on all the DUTs in the pool to the time on the host. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. """ # Let's get the timestamp once on the host and then set it on all # the duts. epoch_seconds = time.time() logging.info('Syncing epoch time on DUTs to %d seconds.', epoch_seconds) for dut in dut_objects: dut.wifi_client.shill.sync_time_to(epoch_seconds) @staticmethod def _get_debug_string(dut_objects, aps): """Gets the debug info for all the DUT's and APs in the pool. This is printed in the logs at the end of each test scenario for debugging. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. @param aps: A list of APConfigurator for all APs allocated for the test. @returns a string with the list of information for each DUT and AP in the pool. """ debug_string = "" for dut in dut_objects: kernel_ver = dut.host.get_kernel_ver() firmware_ver = utils.get_firmware_ver(dut.host) if not firmware_ver: firmware_ver = "Unknown" debug_dict = {'host_name': dut.host.hostname, 'kernel_versions': kernel_ver, 'wifi_firmware_versions': firmware_ver} debug_string += pprint.pformat(debug_dict) for ap in aps: debug_string += pprint.pformat({'ap_name': ap.name}) return debug_string @staticmethod def _are_all_conn_workers_healthy(workers, aps, assoc_params_list, job): """Returns if all the connection workers are working properly. From time to time the connection worker will fail to establish a connection to the APs. @param workers: a list of conn_worker objects. @param aps: a list of an ap_configurator objects. @param assoc_params_list: list of connection association parameters. @param job: the Autotest job object. @returns True if all the workers are healthy, False otherwise. """ healthy = True for worker, ap, assoc_params in zip(workers, aps, assoc_params_list): if not utils.is_conn_worker_healthy(worker, ap, assoc_params, job): logging.error('Connection worker %s not healthy.', worker.host.hostname) healthy = False return healthy def _cleanup(self, dut_objects, dut_locker, ap_locker, capturer, conn_workers): """Cleans up after the test is complete. @param dut_objects: A list of DUTObjects for all DUTs allocated for the test. @param dut_locker: DUTBatchLocker object used to allocate the DUTs for the test pool. @param ap_locker: the AP batch locker object. @param capturer: a packet capture device. @param conn_workers: a list of conn_worker objects. """ self._collect_dut_pool_logs(dut_objects) for worker in conn_workers: if worker: worker.cleanup() capturer.close() ap_locker.unlock_aps() dut_locker.unlock_and_close_duts() def run(self, job, tries=10, capturer_hostname=None, conn_worker_hostnames=[], release_version="", disabled_sysinfo=False): """Executes Clique test. @param job: an Autotest job object. @param tries: an integer, number of iterations to run per AP. @param capturer_hostname: a string or None, hostname or IP of capturer. @param conn_worker_hostnames: a list of string, hostname of connection workers. @param release_version: the DUT cros image version to use for testing. @param disabled_sysinfo: a bool, disable collection of logs from DUT. """ lock_manager = host_lock_manager.HostLockManager() with host_lock_manager.HostsLockedBy(lock_manager): dut_locker = clique_dut_locker.CliqueDUTBatchLocker( lock_manager, self._dut_pool_spec) dut_objects = self._allocate_dut_pool(dut_locker) if not dut_objects: raise error.TestError('No DUTs allocated for test.') update_status = self._update_dut_pool(dut_objects, release_version) if not update_status: raise error.TestError('DUT pool update failed. Bailing!') capture_host = utils.allocate_packet_capturer( lock_manager, hostname=capturer_hostname) capturer = site_linux_system.LinuxSystem( capture_host, {}, 'packet_capturer') conn_workers = [] for hostname in conn_worker_hostnames: conn_worker_host = utils.allocate_packet_capturer( lock_manager, hostname=hostname) # Let's create generic connection workers and make them connect # to the corresponding AP. The DUT role will recast each of # these connection workers based on the role we want them to # perform. conn_worker = connection_worker.ConnectionWorker() conn_worker.prepare_work_client(conn_worker_host) conn_workers.append(conn_worker) aps = [] for ap_spec in self._ap_specs: ap_locker = ap_batch_locker.ApBatchLocker( lock_manager, ap_spec, ap_test_type=ap_constants.AP_TEST_TYPE_CLIQUE) ap = ap_locker.get_ap_batch(batch_size=1) if not ap: raise error.TestError('AP matching spec not found.') aps.append(ap) # Reset all the DUTs before the test starts and configure all the # APs. self._sanitize_all_duts(dut_objects) utils.configure_aps(aps, self._ap_specs) # This is a list of association parameters for the test for all the # APs in the test. assoc_params_list = [] # Check if all our APs, DUTs and connection workers are in good # state before we proceed. for ap, ap_spec in zip(aps, self._ap_specs): if ap.ssid == None: self._cleanup(dut_objects, dut_locker, ap_locker, capturer, conn_workers) raise error.TestError('SSID not set for the AP: %s.' % ap.configurator.host_name) networks = utils.return_available_networks( ap, ap_spec, capturer, job) if ((networks is None) or (networks == list())): self._cleanup(dut_objects, dut_locker, ap_locker, capturer, conn_workers) raise error.TestError('Scanning error on the AP %s.' % ap.configurator.host_name) assoc_params = ap.get_association_parameters() assoc_params_list.append(assoc_params) if not self._are_all_duts_healthy(dut_objects, ap): self._cleanup(dut_objects, dut_locker, ap_locker, capturer, conn_workers) raise error.TestError('Not all DUTs healthy.') if not self._are_all_conn_workers_healthy( conn_workers, aps, assoc_params_list, job): self._cleanup(dut_objects, dut_locker, ap_locker, capturer, conn_workers) raise error.TestError('Not all connection workers healthy.') debug_string = self._get_debug_string(dut_objects, aps) self._sync_time_on_all_duts(dut_objects) result = job.run_test( self._test, capturer=capturer, capturer_frequency=networks[0].frequency, capturer_ht_type=networks[0].width, dut_pool=self._dut_pool, assoc_params_list=assoc_params_list, tries=tries, debug_info=debug_string, conn_workers=conn_workers, # Copy all logs from the system disabled_sysinfo=disabled_sysinfo) # Reclaim all the APs, DUTs and capturers used in the test and # collect the required logs. self._cleanup(dut_objects, dut_locker, ap_locker, capturer, conn_workers)