blob: 1edafd4208782af69dc07ad3db2c0e2abda5f55a [file] [log] [blame]
# Copyright (c) 2012 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 logging
import common
from autotest_lib.client.common_lib import error
from autotest_lib.server import site_utils
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers, reporting
class DedupingSchedulerException(Exception):
"""Base class for exceptions from this module."""
pass
class ScheduleException(DedupingSchedulerException):
"""Raised when an error is returned from the AFE during scheduling."""
pass
class DedupException(DedupingSchedulerException):
"""Raised when an error occurs while checking for duplicate jobs."""
pass
class DedupingScheduler(object):
"""A class that will schedule suites to run on a given board, build.
Includes logic to check whether or not a given (suite, board, build)
has already been run. If so, it will skip scheduling that suite.
@var _afe: a frontend.AFE instance used to talk to autotest.
"""
def __init__(self, afe=None, file_bug=False):
"""Constructor
@param afe: an instance of AFE as defined in server/frontend.py.
Defaults to a frontend_wrappers.RetryingAFE instance.
"""
self._afe = afe or frontend_wrappers.RetryingAFE(timeout_min=30,
delay_sec=10,
debug=False)
self._file_bug = file_bug
def _ShouldScheduleSuite(self, suite, board, build):
"""Return True if |suite| has not yet been run for |build| on |board|.
True if |suite| has not been run for |build| on |board|, and
the lab is open for this particular request. False otherwise.
@param suite: the name of the suite to run, e.g. 'bvt'
@param board: the board to run the suite on, e.g. x86-alex
@param build: the build to install e.g.
x86-alex-release/R18-1655.0.0-a1-b1584.
@return False if the suite was already scheduled, True if not
@raise DedupException if the AFE raises while searching for jobs.
"""
try:
site_utils.check_lab_status(build)
except site_utils.TestLabException as ex:
logging.debug('Skipping suite %s, board %s, build %s: %s',
suite, board, build, str(ex))
return False
try:
return not self._afe.get_jobs(name__startswith=build,
name__endswith='control.'+suite)
except Exception as e:
raise DedupException(e)
# TODO(fdeng): Add status='Available' back into the bug submittal;
# right now it will be marked as Unconfirmed.
def _ReportBug(self, title, description):
"""File a bug using bug reporter.
@param title: A string, representing the bug title.
@param description: A string, representing the bug description.
@return: The id of the issue that was created,
or None if bug is not filed.
"""
if not self._file_bug:
return None
lab_sheriff = site_utils.get_sheriffs(lab_only=True)
logging.info('Filing a bug: %s', title)
return reporting.submit_generic_bug_report(
title=title,
summary=description,
cc=lab_sheriff,
labels=['Hardware-lab'])
def _Schedule(self, suite, board, build, pool, num, priority, timeout):
"""Schedule |suite|, if it hasn't already been run.
@param suite: the name of the suite to run, e.g. 'bvt'
@param board: the board to run the suite on, e.g. x86-alex
@param build: the build to install e.g.
x86-alex-release/R18-1655.0.0-a1-b1584.
@param pool: the pool of machines to use for scheduling purposes.
Default: None
@param num: the number of devices across which to shard the test suite.
Type: integer or None
Default: None (uses sharding factor in global_config.ini).
@param priority: One of the values from
client.common_lib.priorities.Priority.
@param timeout: The max lifetime of the suite in hours.
@return True if the suite got scheduled
@raise ScheduleException if an error occurs while scheduling.
"""
try:
logging.info('Scheduling %s on %s against %s (pool: %s)',
suite, build, board, pool)
if self._afe.run(
'create_suite_job', suite_name=suite, board=board,
build=build, check_hosts=False, num=num, pool=pool,
priority=priority, timeout=timeout,
wait_for_results=False) is not None:
return True
else:
raise ScheduleException(
"Can't schedule %s for %s." % (suite, build))
except (error.ControlFileNotFound, error.ControlFileEmpty,
error.ControlFileMalformed, error.NoControlFileList) as e:
title = ('Exception "%s" occurs when scheduling %s on '
'%s against %s (pool: %s)' %
(e.__class__.__name__, suite, build, board, pool))
if self._ReportBug(title, str(e)) is None:
# Raise the exception if not filing or failed to file a bug.
raise ScheduleException(e)
else:
return False
except Exception as e:
raise ScheduleException(e)
def ScheduleSuite(self, suite, board, build, pool, num, priority, timeout,
force=False):
"""Schedule |suite|, if it hasn't already been run.
If |suite| has not already been run against |build| on |board|,
schedule it and return True. If it has, return False.
@param suite: the name of the suite to run, e.g. 'bvt'
@param board: the board to run the suite on, e.g. x86-alex
@param build: the build to install e.g.
x86-alex-release/R18-1655.0.0-a1-b1584.
@param pool: the pool of machines to use for scheduling purposes.
@param num: the number of devices across which to shard the test suite.
Type: integer or None
@param priority: One of the values from
client.common_lib.priorities.Priority.
@param timeout: The max lifetime of the suite in hours.
@param force: Always schedule the suite.
@return True if the suite got scheduled, False if not
@raise DedupException if we can't check for dups.
@raise ScheduleException if the suite cannot be scheduled.
"""
if force or self._ShouldScheduleSuite(suite, board, build):
return self._Schedule(suite, board, build, pool, num, priority,
timeout)
return False
def GetHosts(self, *args, **kwargs):
"""Forward a request to get hosts onto the AFE instance's get_hosts."""
return self._afe.get_hosts(*args, **kwargs)