Home | History | Annotate | Download | only in suite_scheduler
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 
      7 import common
      8 from autotest_lib.client.common_lib import priorities
      9 
     10 
     11 """Module containing base class and methods for working with scheduler events.
     12 
     13 @var _SECTION_SUFFIX: suffix of config file sections that apply to derived
     14                       classes of TimedEvent.
     15 """
     16 
     17 
     18 _SECTION_SUFFIX = '_params'
     19 
     20 
     21 def SectionName(keyword):
     22     """Generate a section name for a *Event config stanza."""
     23     return keyword + _SECTION_SUFFIX
     24 
     25 
     26 def HonoredSection(section):
     27     """Returns True if section is something _ParseConfig() might consume."""
     28     return section.endswith(_SECTION_SUFFIX)
     29 
     30 
     31 def BuildName(board, type, milestone, manifest):
     32     """Format a build name, given board, type, milestone, and manifest number.
     33 
     34     @param board: board the manifest is for, e.g. x86-alex.
     35     @param type: one of 'release', 'factory', or 'firmware'
     36     @param milestone: (numeric) milestone the manifest was associated with.
     37     @param manifest: manifest number, e.g. '2015.0.0'
     38     @return a build name, e.g. 'x86-alex-release/R20-2015.0.0'
     39     """
     40     return '%s-%s/R%s-%s' % (board, type, milestone, manifest)
     41 
     42 
     43 class BaseEvent(object):
     44     """Represents a supported scheduler event.
     45 
     46     @var PRIORITY: The priority of suites kicked off by this event.
     47     @var TIMEOUT: The max lifetime of suites kicked off by this event.
     48 
     49     @var _keyword: the keyword/name of this event, e.g. new_build, nightly.
     50     @var _mv: ManifestVersions instance used to query for new builds, etc.
     51     @var _always_handle: whether to make ShouldHandle always return True.
     52     @var _tasks: set of Task instances that run on this event.
     53                  Use a set so that instances that encode logically equivalent
     54                  Tasks get de-duped before we even try to schedule them.
     55     """
     56 
     57 
     58     PRIORITY = priorities.Priority.DEFAULT
     59     TIMEOUT = 24  # Hours
     60 
     61 
     62     @classmethod
     63     def CreateFromConfig(cls, config, manifest_versions):
     64         """Instantiate a cls object, options from |config|."""
     65         return cls(manifest_versions, **cls._ParseConfig(config))
     66 
     67 
     68     @classmethod
     69     def _ParseConfig(cls, config):
     70         """Parse config and return a dict of parameters for this event.
     71 
     72         Uses cls.KEYWORD to determine which section to look at, and parses
     73         the following options:
     74           always_handle: If True, ShouldHandle() must always return True.
     75 
     76         @param config: a ForgivingConfigParser instance.
     77         """
     78         section = SectionName(cls.KEYWORD)
     79         return {'always_handle': config.getboolean(section, 'always_handle')}
     80 
     81 
     82     def __init__(self, keyword, manifest_versions, always_handle):
     83         """Constructor.
     84 
     85         @param keyword: the keyword/name of this event, e.g. nightly.
     86         @param manifest_versions: ManifestVersions instance to use for querying.
     87         @param always_handle: If True, make ShouldHandle() always return True.
     88         """
     89         self._keyword = keyword
     90         self._mv = manifest_versions
     91         self._tasks = set()
     92         self._always_handle = always_handle
     93 
     94 
     95     @property
     96     def keyword(self):
     97         """Getter for private |self._keyword| property."""
     98         return self._keyword
     99 
    100 
    101     @property
    102     def tasks(self):
    103         return self._tasks
    104 
    105 
    106     @tasks.setter
    107     def tasks(self, iterable_of_tasks):
    108         """Set the tasks property with an iterable.
    109 
    110         @param iterable_of_tasks: list of Task instances that can fire on this.
    111         """
    112         self._tasks = set(iterable_of_tasks)
    113 
    114 
    115     def Merge(self, to_merge):
    116         """Merge this event with to_merge, changing all mutable properties.
    117 
    118         keyword remains unchanged; the following take on values from to_merge:
    119           _tasks
    120           _mv
    121           _always_handle
    122 
    123         @param to_merge: A BaseEvent instance to merge into this instance.
    124         """
    125         self.tasks = to_merge.tasks
    126         self._mv = to_merge._mv
    127         self._always_handle = to_merge._always_handle
    128 
    129 
    130     def Prepare(self):
    131         """Perform any one-time setup that must occur before [Should]Handle().
    132 
    133         Must be implemented by subclasses.
    134         """
    135         raise NotImplementedError()
    136 
    137 
    138     def GetBranchBuildsForBoard(self, board):
    139         """Get per-branch, per-board builds since last run of this event.
    140 
    141         @param board: the board whose builds we want.
    142         @return {branch: [build-name]}
    143 
    144         Must be implemented by subclasses.
    145         """
    146         raise NotImplementedError()
    147 
    148 
    149     def ShouldHandle(self):
    150         """Returns True if this BaseEvent should be Handle()'d, False if not.
    151 
    152         Must be extended by subclasses.
    153         """
    154         return self._always_handle
    155 
    156 
    157     def UpdateCriteria(self):
    158         """Updates internal state used to decide if this event ShouldHandle()
    159 
    160         Must be implemented by subclasses.
    161         """
    162         raise NotImplementedError()
    163 
    164 
    165     def FilterTasks(self):
    166         """Filter the tasks to only return tasks should run now.
    167 
    168         One use case is that Nightly task can run at each hour. The override of
    169         this function in Nightly class will only return the tasks set to run in
    170         current hour.
    171 
    172         @return: A list of tasks can run now.
    173         """
    174         return list(self.tasks)
    175 
    176 
    177     def Handle(self, scheduler, branch_builds, board, force=False):
    178         """Runs all tasks in self._tasks that if scheduled, can be
    179         successfully run.
    180 
    181         @param scheduler: an instance of DedupingScheduler, as defined in
    182                           deduping_scheduler.py
    183         @param branch_builds: a dict mapping branch name to the build to
    184                               install for that branch, e.g.
    185                               {'R18': ['x86-alex-release/R18-1655.0.0'],
    186                                'R19': ['x86-alex-release/R19-2077.0.0']
    187                                'factory': ['x86-alex-factory/R19-2077.0.5']}
    188         @param board: the board against which to Run() all of self._tasks.
    189         @param force: Tell every Task to always Run().
    190         """
    191         logging.info('Handling %s for %s', self.keyword, board)
    192         # we need to iterate over an immutable copy of self._tasks
    193         for task in self.FilterTasks():
    194             if task.AvailableHosts(scheduler, board):
    195                 if not task.Run(scheduler, branch_builds, board, force,
    196                                 self._mv):
    197                     self._tasks.remove(task)
    198             elif task.ShouldHaveAvailableHosts():
    199                 logging.warning('Skipping %s on %s, due to lack of hosts.',
    200                                 task, board)
    201