blob: 6df05d06defebae663ed4930b902001e703f28f7 [file] [log] [blame]
# Copyright (c) 2013 The Chromium 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 logging
from time import sleep
from autotest_lib.server.cros.chaos_ap_configurators import ap_cartridge
from autotest_lib.server.cros.chaos_ap_configurators import \
ap_configurator_factory
from autotest_lib.server.cros.dynamic_suite import host_lock_manager
# Max number of retry attempts to lock an ap.
MAX_RETRIES = 3
class ApLocker(object):
"""Object to keep track of AP lock state.
@attribute configurator: an APConfigurator object.
@attribute to_be_locked: a boolean, True iff ap has not been locked.
@attribute retries: an integer, max number of retry attempts to lock ap.
"""
def __init__(self, configurator, retries):
"""Initialize.
@param configurator: an APConfigurator object.
@param retries: an integer, max number of retry attempts to lock ap.
"""
self.configurator = configurator
self.to_be_locked = True
self.retries = retries
def __repr__(self):
"""@return class name, ap host name, lock status and retries."""
return 'class: %s, host name: %s, to_be_locked = %s, retries = %d' % (
self.__class__.__name__,
self.configurator.host_name,
self.to_be_locked,
self.retries)
def construct_ap_lockers(ap_spec, retries):
"""Convert APConfigurator objects to ApLocker objects for locking.
@param ap_spec: a dict of strings, AP attributes.
@param retries: an integer, max number of retry attempts to lock ap.
@return a list of ApLocker objects.
"""
ap_lockers_list = []
factory = ap_configurator_factory.APConfiguratorFactory()
for ap in factory.get_ap_configurators(ap_spec):
ap_lockers_list.append(ApLocker(ap, retries))
logging.debug('Found %d APs', len(ap_lockers_list))
return ap_lockers_list
class ApBatchLocker(object):
"""Object to lock/unlock an APConfigurator.
@attribute SECONDS_TO_SLEEP: an integer, number of seconds to sleep between
retries.
@attribute ap_spec: a dict of strings, AP attributes.
@attribute retries: an integer, max number of retry attempts to lock ap.
Defaults to MAX_RETRIES.
@attribute aps_to_lock: a list of ApLocker objects.
@attribute manager: a HostLockManager object, used to lock/unlock APs.
"""
SECONDS_TO_SLEEP = 30
def __init__(self, ap_spec, retries=MAX_RETRIES):
"""Initialize.
@param ap_spec: a dict of strings, AP attributes.
@param retries: an integer, max number of retry attempts to lock ap.
Defaults to MAX_RETRIES.
"""
self.aps_to_lock = construct_ap_lockers(ap_spec, retries)
self.manager = host_lock_manager.HostLockManager()
def has_more_aps(self):
"""@return True iff there is at least one AP to be locked."""
return len(self.aps_to_lock) > 0
def lock_ap_in_afe(self, ap_locker):
"""Locks an AP host in AFE.
@param ap_locker: an ApLocker object, AP to be locked.
@return a boolean, True iff ap_locker is locked.
"""
try:
self.manager.add([ap_locker.configurator.host_name])
self.manager.lock()
logging.info('locked %s and removed it from list',
ap_locker.configurator.host_name)
self.aps_to_lock.remove(ap_locker)
ap_locker.to_be_locked = False
return True
# Catching a wide exception b/c frontend.AFE.run() throws Exception
except Exception as e:
ap_locker.retries -= 1
logging.info('%d retries left for %s',
ap_locker.retries,
ap_locker.configurator.host_name)
if ap_locker.retries == 0:
logging.info('No more retries left. Remove %s from list',
ap_locker.configurator.host_name)
self.aps_to_lock.remove(ap_locker)
# FIXME(tgao): check error msg and remove unlockable aps sooner?
# e.g., an AP not registered w/ AFE.
return False
# TODO(tgao): have the batch locker running in its own thread just adding
# to the batch list and then the test can pop them off as it uses them.
def get_ap_batch(self, batch_size=ap_cartridge.THREAD_MAX):
"""Allocates a batch of locked APs.
@param batch_size: an integer, max. number of aps to lock in one batch.
Defaults to THREAD_MAX in ap_cartridge.py
@return a list of APConfigurator objects, locked on AFE.
"""
ap_batch = []
# We need this while loop to continuously loop over the for loop.
# To exit the while loop, we either:
# - locked batch_size number of aps and return them
# - exhausted all retries on all aps in aps_to_lock
while len(self.aps_to_lock) > 0:
for ap_locker in self.aps_to_lock:
logging.debug('checking %s', ap_locker.configurator.host_name)
if ap_locker.to_be_locked:
if self.lock_ap_in_afe(ap_locker):
ap_batch.append(ap_locker.configurator)
if len(ap_batch) == batch_size:
return ap_batch
# Unable to lock ap, sleep before moving on to next ap.
else:
logging.info('Sleep %d sec before retry',
self.SECONDS_TO_SLEEP)
sleep(self.SECONDS_TO_SLEEP)
if ap_batch:
logging.info('partial batch with %d ap', len(ap_batch))
return ap_batch
class ApBatchLockerManager(object):
"""Context manager to make sure that 'ApBatchLocker' shuts down properly.
@attribute ap_spec: a dict of strings, AP attributes.
@attributes _batch_locker: a ApBatchLocker object.
@attributes _hosts_locked_by: a HostLockedBy object.
"""
def __init__(self, ap_spec):
"""Initialize.
@param ap_spec: a dict of strings, attributes of desired APs.
See docstring of get_ap_configurators() in
ap_configurator_factory.py.
@param capturer: a PacketCaptureManager object, packet tracer.
"""
self.ap_spec = ap_spec
self._batch_locker = None
self._hosts_locked_by = None
# TODO(milleral): This code needs to be cleaned up once the host locking
# code is made more usable (see crosbug.com/36072).
def __enter__(self):
self._batch_locker = ApBatchLocker(self.ap_spec)
self._hosts_locked_by = host_lock_manager.HostsLockedBy(
self._batch_locker.manager)
self._hosts_locked_by.__enter__()
return self._batch_locker
def __exit__(self, exit_type, exit_value, exit_traceback):
if self._hosts_locked_by:
self._hosts_locked_by.__exit__(exit_type, exit_value,
exit_traceback)