| # 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) |