277 lines
10 KiB
Python
277 lines
10 KiB
Python
# 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 collections
|
|
import logging
|
|
import random
|
|
|
|
from time import sleep
|
|
|
|
import common
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.server import hosts
|
|
from autotest_lib.server import frontend
|
|
from autotest_lib.server import site_utils
|
|
from autotest_lib.server.cros.dynamic_suite import constants
|
|
from autotest_lib.server.cros.network import wifi_client
|
|
|
|
# Max number of retry attempts to lock a DUT.
|
|
MAX_RETRIES = 3
|
|
|
|
# Tuple containing the DUT objects
|
|
DUTObject = collections.namedtuple('DUTObject', ['host', 'wifi_client'])
|
|
|
|
class DUTSpec():
|
|
"""Object to specify the DUT spec.
|
|
|
|
@attribute board_name: String representing the board name corresponding to
|
|
the board.
|
|
@attribute host_name: String representing the host name corresponding to
|
|
the machine.
|
|
"""
|
|
|
|
def __init__(self, board_name=None, host_name=None):
|
|
"""Initialize.
|
|
|
|
@param board_name: String representing the board name corresponding to
|
|
the board.
|
|
@param host_name: String representing the host name corresponding to
|
|
the machine.
|
|
"""
|
|
self.board_name = board_name
|
|
self.host_name = host_name
|
|
|
|
def __repr__(self):
|
|
"""@return class name, dut host name, lock status and retries."""
|
|
return 'class: %s, Board name: %s, Num DUTs = %d' % (
|
|
self.__class__.__name__,
|
|
self.board_name,
|
|
self.host_name)
|
|
|
|
|
|
class DUTSetSpec(list):
|
|
"""Object to specify the DUT set spec. It's a list of DUTSpec objects."""
|
|
|
|
def __init__(self):
|
|
"""Initialize."""
|
|
super(DUTSetSpec, self)
|
|
|
|
|
|
class DUTPoolSpec(list):
|
|
"""Object to specify the DUT pool spec.It's a list of DUTSetSpec objects."""
|
|
|
|
def __init__(self):
|
|
"""Initialize."""
|
|
super(DUTPoolSpec, self)
|
|
|
|
|
|
class DUTLocker(object):
|
|
"""Object to keep track of DUT lock state.
|
|
|
|
@attribute dut_spec: an DUTSpec object corresponding to the DUT we need.
|
|
@attribute retries: an integer, max number of retry attempts to lock DUT.
|
|
@attribute to_be_locked: a boolean, True iff DUT has not been locked.
|
|
"""
|
|
|
|
|
|
def __init__(self, dut_spec, retries):
|
|
"""Initialize.
|
|
|
|
@param dut_spec: a DUTSpec object corresponding to the spec of the DUT
|
|
to be locked.
|
|
@param retries: an integer, max number of retry attempts to lock DUT.
|
|
"""
|
|
self.dut_spec = dut_spec
|
|
self.retries = retries
|
|
self.to_be_locked = True
|
|
|
|
def __repr__(self):
|
|
"""@return class name, dut host name, lock status and retries."""
|
|
return 'class: %s, host name: %s, to_be_locked = %s, retries = %d' % (
|
|
self.__class__.__name__,
|
|
self.dut.host.hostname,
|
|
self.to_be_locked,
|
|
self.retries)
|
|
|
|
|
|
class CliqueDUTBatchLocker(object):
|
|
"""Object to lock/unlock an DUT.
|
|
|
|
@attribute SECONDS_TO_SLEEP: an integer, number of seconds to sleep between
|
|
retries.
|
|
@attribute duts_to_lock: a list of DUTLocker objects.
|
|
@attribute locked_duts: a list of DUTObject's corresponding to DUT's which
|
|
have already been allocated.
|
|
@attribute manager: a HostLockManager object, used to lock/unlock DUTs.
|
|
"""
|
|
|
|
MIN_SECONDS_TO_SLEEP = 30
|
|
MAX_SECONDS_TO_SLEEP = 120
|
|
|
|
def __init__(self, lock_manager, dut_pool_spec, retries=MAX_RETRIES):
|
|
"""Initialize.
|
|
|
|
@param lock_manager: a HostLockManager object, used to lock/unlock DUTs.
|
|
@param dut_pool_spec: A DUTPoolSpec object corresponding to the DUT's in
|
|
the pool.
|
|
@param retries: Number of times to retry the locking of DUT's.
|
|
|
|
"""
|
|
self.lock_manager = lock_manager
|
|
self.duts_to_lock = self._construct_dut_lockers(dut_pool_spec, retries)
|
|
self.locked_duts = []
|
|
|
|
@staticmethod
|
|
def _construct_dut_lockers(dut_pool_spec, retries):
|
|
"""Convert DUTObject objects to DUTLocker objects for locking.
|
|
|
|
@param dut_pool_spec: A DUTPoolSpec object corresponding to the DUT's in
|
|
the pool.
|
|
@param retries: an integer, max number of retry attempts to lock DUT.
|
|
|
|
@return a list of DUTLocker objects.
|
|
"""
|
|
dut_lockers_list = []
|
|
for dut_set_spec in dut_pool_spec:
|
|
dut_set_lockers_list = []
|
|
for dut_spec in dut_set_spec:
|
|
dut_locker = DUTLocker(dut_spec, retries)
|
|
dut_set_lockers_list.append(dut_locker)
|
|
dut_lockers_list.append(dut_set_lockers_list)
|
|
return dut_lockers_list
|
|
|
|
def _allocate_dut(self, host_name=None, board_name=None):
|
|
"""Allocates a machine to the DUT pool for running the test.
|
|
|
|
Locks the allocated machine if the machine was discovered via AFE
|
|
to prevent tests stomping on each other.
|
|
|
|
@param host_name: Host name for the DUT.
|
|
@param board_name: Board name Label to use for finding the DUT.
|
|
|
|
@return: hostname of the device locked in AFE.
|
|
"""
|
|
hostname = None
|
|
if host_name:
|
|
if self.lock_manager.lock([host_name]):
|
|
logging.info('Locked device %s.', host_name)
|
|
hostname = host_name
|
|
else:
|
|
logging.error('Unable to lock device %s.', host_name)
|
|
else:
|
|
afe = frontend.AFE(debug=True,
|
|
server=site_utils.get_global_afe_hostname())
|
|
labels = []
|
|
labels.append(constants.BOARD_PREFIX + board_name)
|
|
labels.append('clique_dut')
|
|
try:
|
|
hostname = site_utils.lock_host_with_labels(
|
|
afe, self.lock_manager, labels=labels) + '.cros'
|
|
except error.NoEligibleHostException as e:
|
|
raise error.TestError("Unable to find a suitable device.")
|
|
except error.TestError as e:
|
|
logging.error(e)
|
|
return hostname
|
|
|
|
@staticmethod
|
|
def _create_dut_object(host_name):
|
|
"""Create the DUTObject tuple for the DUT.
|
|
|
|
@param host_name: Host name for the DUT.
|
|
|
|
@return: Tuple of Host and Wifi client objects representing DUTObject
|
|
for invoking RPC calls.
|
|
"""
|
|
dut_host = hosts.create_host(host_name)
|
|
dut_wifi_client = wifi_client.WiFiClient(dut_host, './debug', False)
|
|
return DUTObject(dut_host, dut_wifi_client)
|
|
|
|
def _lock_dut_in_afe(self, dut_locker):
|
|
"""Locks an DUT host in AFE.
|
|
|
|
@param dut_locker: an DUTLocker object, DUT to be locked.
|
|
@return a hostname iff dut_locker is locked, else returns None.
|
|
"""
|
|
logging.debug('Trying to find a device with spec (%s, %s)',
|
|
dut_locker.dut_spec.host_name,
|
|
dut_locker.dut_spec.board_name)
|
|
host_name = self._allocate_dut(
|
|
dut_locker.dut_spec.host_name, dut_locker.dut_spec.board_name)
|
|
if host_name:
|
|
logging.info('Locked %s', host_name)
|
|
dut_locker.to_be_locked = False
|
|
else:
|
|
dut_locker.retries -= 1
|
|
logging.info('%d retries left for (%s, %s)',
|
|
dut_locker.retries,
|
|
dut_locker.dut_spec.host_name,
|
|
dut_locker.dut_spec.board_name)
|
|
if dut_locker.retries == 0:
|
|
raise error.TestError("No more retries left to lock a "
|
|
"suitable device.")
|
|
return host_name
|
|
|
|
def get_dut_pool(self):
|
|
"""Allocates a batch of locked DUTs for the test.
|
|
|
|
@return a list of DUTObject, locked on AFE.
|
|
"""
|
|
# We need this while loop to continuously loop over the for loop.
|
|
# To exit the while loop, we either:
|
|
# - locked batch_size number of duts and return them
|
|
# - exhausted all retries on a dut in duts_to_lock
|
|
|
|
# It is important to preserve the order of DUT sets, but the order of
|
|
# DUT's within the set is not important as all the DUT's within a set
|
|
# have to perform the same role.
|
|
dut_pool = []
|
|
for dut_set in self.duts_to_lock:
|
|
dut_pool.append([])
|
|
|
|
num_duts_to_lock = sum(map(len, self.duts_to_lock))
|
|
while num_duts_to_lock:
|
|
set_num = 0
|
|
for dut_locker_set in self.duts_to_lock:
|
|
for dut_locker in dut_locker_set:
|
|
if dut_locker.to_be_locked:
|
|
host_name = self._lock_dut_in_afe(dut_locker)
|
|
if host_name:
|
|
dut_object = self._create_dut_object(host_name)
|
|
self.locked_duts.append(dut_object)
|
|
dut_pool[set_num].append(dut_object)
|
|
num_duts_to_lock -= 1
|
|
set_num += 1
|
|
|
|
logging.info('Remaining DUTs to lock: %d', num_duts_to_lock)
|
|
|
|
if num_duts_to_lock:
|
|
seconds_to_sleep = random.randint(self.MIN_SECONDS_TO_SLEEP,
|
|
self.MAX_SECONDS_TO_SLEEP)
|
|
logging.debug('Sleep %d sec before retry', seconds_to_sleep)
|
|
sleep(seconds_to_sleep)
|
|
return dut_pool
|
|
|
|
def _unlock_one_dut(self, dut):
|
|
"""Unlock one DUT after we're done.
|
|
|
|
@param dut: a DUTObject corresponding to the DUT.
|
|
"""
|
|
host_name = dut.host.host_name
|
|
if self.manager.unlock(hosts=[host_name]):
|
|
self._locked_duts.remove(dut)
|
|
else:
|
|
logging.error('Tried to unlock a host we have not locked (%s)?',
|
|
host_name)
|
|
|
|
def unlock_duts(self):
|
|
"""Unlock DUTs after we're done."""
|
|
for dut in self.locked_duts:
|
|
self._unlock_one_dut(dut)
|
|
|
|
def unlock_and_close_duts(self):
|
|
"""Unlock DUTs after we're done and close the associated WifiClient."""
|
|
for dut in self.locked_duts:
|
|
dut.wifi_client.close()
|
|
self._unlock_one_dut(dut)
|