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