Home | History | Annotate | Download | only in clique_lib
      1 # Copyright 2015 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 
      5 import datetime
      6 import logging
      7 import os
      8 import pprint
      9 import time
     10 import re
     11 
     12 import common
     13 from autotest_lib.client.common_lib import error
     14 from autotest_lib.client.common_lib.cros.network import ap_constants
     15 from autotest_lib.server import hosts
     16 from autotest_lib.server import site_linux_system
     17 from autotest_lib.server.cros import host_lock_manager
     18 from autotest_lib.server.cros.ap_configurators import ap_batch_locker
     19 from autotest_lib.server.cros.network import chaos_clique_utils as utils
     20 from autotest_lib.server.cros.network import connection_worker
     21 from autotest_lib.server.cros.clique_lib import clique_dut_locker
     22 from autotest_lib.server.cros.clique_lib import clique_dut_log_collector
     23 from autotest_lib.server.cros.clique_lib import clique_dut_updater
     24 
     25 
     26 class CliqueRunner(object):
     27     """Object to run a network_WiFi_CliqueXXX test."""
     28 
     29     def __init__(self, test, dut_pool_spec, ap_specs):
     30         """Initializes and runs test.
     31 
     32         @param test: a string, test name.
     33         @param dut_pool_spec: a list of pool sets. Each set contains a list of
     34                               board: <board_name> labels to chose the required
     35                               DUT's.
     36         @param ap_specs: a list of APSpec objects corresponding to the APs
     37                          needed for the test.
     38         """
     39         self._test = test
     40         self._ap_specs = ap_specs
     41         self._dut_pool_spec = dut_pool_spec
     42         self._dut_pool = []
     43         # Log server and DUT times
     44         dt = datetime.datetime.now()
     45         logging.info('Server time: %s', dt.strftime('%a %b %d %H:%M:%S %Y'))
     46 
     47     def _allocate_dut_pool(self, dut_locker):
     48         """Allocate the required DUT's from the spec for the test.
     49         The DUT objects are stored in a list of sets in |_dut_pool| attribute.
     50 
     51         @param dut_locker: DUTBatchLocker object used to allocate the DUTs
     52                            for the test pool.
     53 
     54         @return: Returns a list of DUTObjects allocated.
     55         """
     56         self._dut_pool  = dut_locker.get_dut_pool()
     57         # Flatten the list of DUT objects into a single list.
     58         dut_objects = sum(self._dut_pool, [])
     59         return dut_objects
     60 
     61     @staticmethod
     62     def _update_dut_pool(dut_objects, release_version):
     63         """Allocate the required DUT's from the spec for the test.
     64 
     65         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
     66                             test.
     67         @param release_version: A chromeOS release version.
     68 
     69         @return: True if all the DUT's successfully upgraded, False otherwise.
     70         """
     71         dut_updater = clique_dut_updater.CliqueDUTUpdater()
     72         return dut_updater.update_dut_pool(dut_objects, release_version)
     73 
     74     @staticmethod
     75     def _collect_dut_pool_logs(dut_objects, job):
     76         """Allocate the required DUT's from the spec for the test.
     77         The DUT objects are stored in a list of sets in |_dut_pool| attribute.
     78 
     79         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
     80                             test.
     81         @param job: Autotest job object to be used for log collection.
     82 
     83         @return: Returns a list of DUTObjects allocated.
     84         """
     85         log_collector = clique_dut_log_collector.CliqueDUTLogCollector()
     86         log_collector.collect_logs(dut_objects, job)
     87 
     88     @staticmethod
     89     def _are_all_duts_healthy(dut_objects, ap):
     90         """Returns if iw scan is not working on any of the DUTs.
     91 
     92         Sometimes iw scan will die, especially on the Atheros chips.
     93         This works around that bug.  See crbug.com/358716.
     94 
     95         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
     96                             test.
     97         @param ap: ap_configurator object
     98 
     99         @returns True if all the DUTs are healthy, False otherwise.
    100         """
    101         healthy = True
    102         for dut in dut_objects:
    103             if not utils.is_dut_healthy(dut.wifi_client, ap):
    104                 logging.error('DUT %s not healthy.', dut.host.hostname)
    105                 healthy = False
    106         return healthy
    107 
    108     @staticmethod
    109     def _sanitize_all_duts(dut_objects):
    110         """Clean up logs and reboot all the DUTs.
    111 
    112         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
    113                             test.
    114         """
    115         for dut in dut_objects:
    116             utils.sanitize_client(dut.host)
    117 
    118     @staticmethod
    119     def _sync_time_on_all_duts(dut_objects):
    120         """Syncs time on all the DUTs in the pool to the time on the host.
    121 
    122         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
    123                             test.
    124         """
    125         # Let's get the timestamp once on the host and then set it on all
    126         # the duts.
    127         epoch_seconds = time.time()
    128         logging.info('Syncing epoch time on DUTs to %d seconds.', epoch_seconds)
    129         for dut in dut_objects:
    130             dut.wifi_client.shill.sync_time_to(epoch_seconds)
    131 
    132     @staticmethod
    133     def _get_debug_string(dut_objects, aps):
    134         """Gets the debug info for all the DUT's and APs in the pool.
    135 
    136         This is printed in the logs at the end of each test scenario for
    137         debugging.
    138         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
    139                             test.
    140         @param aps: A list of APConfigurator for all APs allocated for
    141                     the test.
    142 
    143         @returns a string with the list of information for each DUT and AP
    144                  in the pool.
    145         """
    146         debug_string = ""
    147         for dut in dut_objects:
    148             kernel_ver = dut.host.get_kernel_ver()
    149             firmware_ver = utils.get_firmware_ver(dut.host)
    150             if not firmware_ver:
    151                 firmware_ver = "Unknown"
    152             debug_dict = {'host_name': dut.host.hostname,
    153                           'kernel_versions': kernel_ver,
    154                           'wifi_firmware_versions': firmware_ver}
    155             debug_string += pprint.pformat(debug_dict)
    156         for ap in aps:
    157             debug_string += pprint.pformat({'ap_name': ap.name})
    158         return debug_string
    159 
    160     @staticmethod
    161     def _are_all_conn_workers_healthy(workers, aps, assoc_params_list, job):
    162         """Returns if all the connection workers are working properly.
    163 
    164         From time to time the connection worker will fail to establish a
    165         connection to the APs.
    166 
    167         @param workers: a list of conn_worker objects.
    168         @param aps: a list of an ap_configurator objects.
    169         @param assoc_params_list: list of connection association parameters.
    170         @param job: the Autotest job object.
    171 
    172         @returns True if all the workers are healthy, False otherwise.
    173         """
    174         healthy = True
    175         for worker, ap, assoc_params in zip(workers, aps, assoc_params_list):
    176             if not utils.is_conn_worker_healthy(worker, ap, assoc_params, job):
    177                 logging.error('Connection worker %s not healthy.',
    178                               worker.host.hostname)
    179                 healthy = False
    180         return healthy
    181 
    182     def _cleanup(self, dut_objects, dut_locker, ap_locker, capturer,
    183                  conn_workers):
    184         """Cleans up after the test is complete.
    185 
    186         @param dut_objects: A list of DUTObjects for all DUTs allocated for the
    187                             test.
    188         @param dut_locker: DUTBatchLocker object used to allocate the DUTs
    189                            for the test pool.
    190         @param ap_locker: the AP batch locker object.
    191         @param capturer: a packet capture device.
    192         @param conn_workers: a list of conn_worker objects.
    193         """
    194         self._collect_dut_pool_logs(dut_objects)
    195         for worker in conn_workers:
    196             if worker: worker.cleanup()
    197         capturer.close()
    198         ap_locker.unlock_aps()
    199         dut_locker.unlock_and_close_duts()
    200 
    201     def run(self, job, tries=10, capturer_hostname=None,
    202             conn_worker_hostnames=[], release_version="",
    203             disabled_sysinfo=False):
    204         """Executes Clique test.
    205 
    206         @param job: an Autotest job object.
    207         @param tries: an integer, number of iterations to run per AP.
    208         @param capturer_hostname: a string or None, hostname or IP of capturer.
    209         @param conn_worker_hostnames: a list of string, hostname of
    210                                       connection workers.
    211         @param release_version: the DUT cros image version to use for testing.
    212         @param disabled_sysinfo: a bool, disable collection of logs from DUT.
    213         """
    214         lock_manager = host_lock_manager.HostLockManager()
    215         with host_lock_manager.HostsLockedBy(lock_manager):
    216             dut_locker = clique_dut_locker.CliqueDUTBatchLocker(
    217                     lock_manager, self._dut_pool_spec)
    218             dut_objects = self._allocate_dut_pool(dut_locker)
    219             if not dut_objects:
    220                 raise error.TestError('No DUTs allocated for test.')
    221             update_status = self._update_dut_pool(dut_objects, release_version)
    222             if not update_status:
    223                 raise error.TestError('DUT pool update failed. Bailing!')
    224 
    225             capture_host = utils.allocate_packet_capturer(
    226                     lock_manager, hostname=capturer_hostname)
    227             capturer = site_linux_system.LinuxSystem(
    228                     capture_host, {}, 'packet_capturer')
    229 
    230             conn_workers = []
    231             for hostname in conn_worker_hostnames:
    232                 conn_worker_host = utils.allocate_packet_capturer(
    233                         lock_manager, hostname=hostname)
    234                 # Let's create generic connection workers and make them connect
    235                 # to the corresponding AP. The DUT role will recast each of
    236                 # these connection workers based on the role we want them to
    237                 # perform.
    238                 conn_worker = connection_worker.ConnectionWorker()
    239                 conn_worker.prepare_work_client(conn_worker_host)
    240                 conn_workers.append(conn_worker)
    241 
    242             aps = []
    243             for ap_spec in self._ap_specs:
    244                 ap_locker = ap_batch_locker.ApBatchLocker(
    245                         lock_manager, ap_spec,
    246                         ap_test_type=ap_constants.AP_TEST_TYPE_CLIQUE)
    247                 ap = ap_locker.get_ap_batch(batch_size=1)
    248                 if not ap:
    249                     raise error.TestError('AP matching spec not found.')
    250                 aps.append(ap)
    251 
    252             # Reset all the DUTs before the test starts and configure all the
    253             # APs.
    254             self._sanitize_all_duts(dut_objects)
    255             utils.configure_aps(aps, self._ap_specs)
    256 
    257             # This is a list of association parameters for the test for all the
    258             # APs in the test.
    259             assoc_params_list = []
    260             # Check if all our APs, DUTs and connection workers are in good
    261             # state before we proceed.
    262             for ap, ap_spec in zip(aps, self._ap_specs):
    263                 if ap.ssid == None:
    264                     self._cleanup(dut_objects, dut_locker, ap_locker,
    265                                   capturer, conn_workers)
    266                     raise error.TestError('SSID not set for the AP: %s.' %
    267                                           ap.configurator.host_name)
    268                 networks = utils.return_available_networks(
    269                         ap, ap_spec, capturer, job)
    270                 if ((networks is None) or (networks == list())):
    271                     self._cleanup(dut_objects, dut_locker, ap_locker,
    272                                   capturer, conn_workers)
    273                     raise error.TestError('Scanning error on the AP %s.' %
    274                                           ap.configurator.host_name)
    275 
    276                 assoc_params = ap.get_association_parameters()
    277                 assoc_params_list.append(assoc_params)
    278 
    279             if not self._are_all_duts_healthy(dut_objects, ap):
    280                 self._cleanup(dut_objects, dut_locker, ap_locker,
    281                               capturer, conn_workers)
    282                 raise error.TestError('Not all DUTs healthy.')
    283 
    284             if not self._are_all_conn_workers_healthy(
    285                     conn_workers, aps, assoc_params_list, job):
    286                 self._cleanup(dut_objects, dut_locker, ap_locker,
    287                               capturer, conn_workers)
    288                 raise error.TestError('Not all connection workers healthy.')
    289 
    290             debug_string = self._get_debug_string(dut_objects, aps)
    291             self._sync_time_on_all_duts(dut_objects)
    292 
    293             result = job.run_test(
    294                     self._test,
    295                     capturer=capturer,
    296                     capturer_frequency=networks[0].frequency,
    297                     capturer_ht_type=networks[0].ht,
    298                     dut_pool=self._dut_pool,
    299                     assoc_params_list=assoc_params_list,
    300                     tries=tries,
    301                     debug_info=debug_string,
    302                     conn_workers=conn_workers,
    303                     # Copy all logs from the system
    304                     disabled_sysinfo=disabled_sysinfo)
    305 
    306             # Reclaim all the APs, DUTs and capturers used in the test and
    307             # collect the required logs.
    308             self._cleanup(dut_objects, dut_locker, ap_locker,
    309                           capturer, conn_workers)
    310