• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import datetime
6import logging
7import os
8import pprint
9import time
10import re
11
12import common
13from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib.cros.network import ap_constants
15from autotest_lib.server import hosts
16from autotest_lib.server import site_linux_system
17from autotest_lib.server.cros import host_lock_manager
18from autotest_lib.server.cros.ap_configurators import ap_batch_locker
19from autotest_lib.server.cros.network import chaos_clique_utils as utils
20from autotest_lib.server.cros.network import connection_worker
21from autotest_lib.server.cros.clique_lib import clique_dut_locker
22from autotest_lib.server.cros.clique_lib import clique_dut_log_collector
23from autotest_lib.server.cros.clique_lib import clique_dut_updater
24
25
26class 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