blob: a7308e9fcd0af3fa6a7366c1842361bd8b2840bd [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 priorities
import task
"""Module containing base class and methods for working with scheduler events.
@var _SECTION_SUFFIX: suffix of config file sections that apply to derived
classes of TimedEvent.
"""
_SECTION_SUFFIX = '_params'
def SectionName(keyword):
"""Generate a section name for a *Event config stanza."""
return keyword + _SECTION_SUFFIX
def HonoredSection(section):
"""Returns True if section is something _ParseConfig() might consume."""
return section.endswith(_SECTION_SUFFIX)
def BuildName(board, type, milestone, manifest):
"""Format a build name, given board, type, milestone, and manifest number.
@param board: board the manifest is for, e.g. x86-alex.
@param type: one of 'release', 'factory', or 'firmware'
@param milestone: (numeric) milestone the manifest was associated with.
@param manifest: manifest number, e.g. '2015.0.0'
@return a build name, e.g. 'x86-alex-release/R20-2015.0.0'
"""
return '%s-%s/R%s-%s' % (board, type, milestone, manifest)
class BaseEvent(object):
"""Represents a supported scheduler event.
@var PRIORITY: The priority of suites kicked off by this event.
@var TIMEOUT: The max lifetime of suites kicked off by this event.
@var _keyword: the keyword/name of this event, e.g. new_build, nightly.
@var _mv: ManifestVersions instance used to query for new builds, etc.
@var _always_handle: whether to make ShouldHandle always return True.
@var _tasks: set of Task instances that run on this event.
Use a set so that instances that encode logically equivalent
Tasks get de-duped before we even try to schedule them.
"""
PRIORITY = priorities.Priority.DEFAULT
TIMEOUT = 24 # Hours
@classmethod
def CreateFromConfig(cls, config, manifest_versions):
"""Instantiate a cls object, options from |config|."""
return cls(manifest_versions, **cls._ParseConfig(config))
@classmethod
def _ParseConfig(cls, config):
"""Parse config and return a dict of parameters for this event.
Uses cls.KEYWORD to determine which section to look at, and parses
the following options:
always_handle: If True, ShouldHandle() must always return True.
@param config: a ForgivingConfigParser instance.
"""
section = SectionName(cls.KEYWORD)
return {'always_handle': config.getboolean(section, 'always_handle')}
def __init__(self, keyword, manifest_versions, always_handle):
"""Constructor.
@param keyword: the keyword/name of this event, e.g. nightly.
@param manifest_versions: ManifestVersions instance to use for querying.
@param always_handle: If True, make ShouldHandle() always return True.
"""
self._keyword = keyword
self._mv = manifest_versions
self._tasks = set()
self._always_handle = always_handle
@property
def keyword(self):
"""Getter for private |self._keyword| property."""
return self._keyword
@property
def tasks(self):
return self._tasks
@tasks.setter
def tasks(self, iterable_of_tasks):
"""Set the tasks property with an iterable.
@param iterable_of_tasks: list of Task instances that can fire on this.
"""
self._tasks = set(iterable_of_tasks)
def Merge(self, to_merge):
"""Merge this event with to_merge, changing all mutable properties.
keyword remains unchanged; the following take on values from to_merge:
_tasks
_mv
_always_handle
@param to_merge: A BaseEvent instance to merge into this instance.
"""
self.tasks = to_merge.tasks
self._mv = to_merge._mv
self._always_handle = to_merge._always_handle
def Prepare(self):
"""Perform any one-time setup that must occur before [Should]Handle().
Must be implemented by subclasses.
"""
raise NotImplementedError()
def GetBranchBuildsForBoard(self, board):
"""Get per-branch, per-board builds since last run of this event.
@param board: the board whose builds we want.
@return {branch: [build-name]}
Must be implemented by subclasses.
"""
raise NotImplementedError()
def ShouldHandle(self):
"""Returns True if this BaseEvent should be Handle()'d, False if not.
Must be extended by subclasses.
"""
return self._always_handle
def UpdateCriteria(self):
"""Updates internal state used to decide if this event ShouldHandle()
Must be implemented by subclasses.
"""
raise NotImplementedError()
def Handle(self, scheduler, branch_builds, board, force=False):
"""Runs all tasks in self._tasks that if scheduled, can be
successfully run.
@param scheduler: an instance of DedupingScheduler, as defined in
deduping_scheduler.py
@param branch_builds: a dict mapping branch name to the build to
install for that branch, e.g.
{'R18': ['x86-alex-release/R18-1655.0.0'],
'R19': ['x86-alex-release/R19-2077.0.0']
'factory': ['x86-alex-factory/R19-2077.0.5']}
@param board: the board against which to Run() all of self._tasks.
@param force: Tell every Task to always Run().
"""
logging.info('Handling %s for %s', self.keyword, board)
# we need to iterate over an immutable copy of self._tasks
for task in list(self.tasks):
if task.AvailableHosts(scheduler, board):
if not task.Run(scheduler, branch_builds, board, force):
self._tasks.remove(task)
elif task.ShouldHaveAvailableHosts():
logging.warning('Skipping %s on %s, due to lack of hosts.',
task, board)