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 from autotest_lib.client.common_lib.cros import dev_server
     10 from autotest_lib.server import utils
     11 
     12 
     13 """Module containing base class and methods for working with scheduler events.
     14 
     15 @var _SECTION_SUFFIX: suffix of config file sections that apply to derived
     16                       classes of TimedEvent.
     17 """
     18 
     19 
     20 _SECTION_SUFFIX = '_params'
     21 
     22 # Pattern of latest Launch Control build for a branch and a target.
     23 _LATEST_LAUNCH_CONTROL_BUILD_FMT = '%s/%s/LATEST'
     24 
     25 def SectionName(keyword):
     26     """Generate a section name for a *Event config stanza.
     27 
     28     @param keyword: Name of the event, e.g., nightly, weekly etc.
     29     """
     30     return keyword + _SECTION_SUFFIX
     31 
     32 
     33 def HonoredSection(section):
     34     """Returns True if section is something _ParseConfig() might consume.
     35 
     36     @param section: Name of the config section.
     37     """
     38     return section.endswith(_SECTION_SUFFIX)
     39 
     40 
     41 def BuildName(board, type, milestone, manifest):
     42     """Format a build name, given board, type, milestone, and manifest number.
     43 
     44     @param board: board the manifest is for, e.g. x86-alex.
     45     @param type: one of 'release', 'factory', or 'firmware'
     46     @param milestone: (numeric) milestone the manifest was associated with.
     47     @param manifest: manifest number, e.g. '2015.0.0'
     48     @return a build name, e.g. 'x86-alex-release/R20-2015.0.0'
     49     """
     50     return '%s-%s/R%s-%s' % (board, type, milestone, manifest)
     51 
     52 
     53 class BaseEvent(object):
     54     """Represents a supported scheduler event.
     55 
     56     @var PRIORITY: The priority of suites kicked off by this event.
     57     @var TIMEOUT: The max lifetime of suites kicked off by this event.
     58 
     59     @var _keyword: the keyword/name of this event, e.g. new_build, nightly.
     60     @var _mv: ManifestVersions instance used to query for new builds, etc.
     61     @var _always_handle: whether to make ShouldHandle always return True.
     62     @var _tasks: set of Task instances that run on this event.
     63                  Use a set so that instances that encode logically equivalent
     64                  Tasks get de-duped before we even try to schedule them.
     65     """
     66 
     67 
     68     PRIORITY = priorities.Priority.DEFAULT
     69     TIMEOUT = 24  # Hours
     70 
     71 
     72     @classmethod
     73     def CreateFromConfig(cls, config, manifest_versions):
     74         """Instantiate a cls object, options from |config|.
     75 
     76         @param config: A ForgivingConfigParser instance.
     77         @param manifest_versions: ManifestVersions instance used to query for
     78                 new builds, etc.
     79         """
     80         return cls(manifest_versions, **cls._ParseConfig(config))
     81 
     82 
     83     @classmethod
     84     def _ParseConfig(cls, config):
     85         """Parse config and return a dict of parameters for this event.
     86 
     87         Uses cls.KEYWORD to determine which section to look at, and parses
     88         the following options:
     89           always_handle: If True, ShouldHandle() must always return True.
     90 
     91         @param config: a ForgivingConfigParser instance.
     92         """
     93         section = SectionName(cls.KEYWORD)
     94         return {'always_handle': config.getboolean(section, 'always_handle')}
     95 
     96 
     97     def __init__(self, keyword, manifest_versions, always_handle):
     98         """Constructor.
     99 
    100         @param keyword: the keyword/name of this event, e.g. nightly.
    101         @param manifest_versions: ManifestVersions instance to use for querying.
    102         @param always_handle: If True, make ShouldHandle() always return True.
    103         """
    104         self._keyword = keyword
    105         self._mv = manifest_versions
    106         self._tasks = set()
    107         self._always_handle = always_handle
    108 
    109 
    110     @property
    111     def keyword(self):
    112         """Getter for private |self._keyword| property."""
    113         return self._keyword
    114 
    115 
    116     @property
    117     def tasks(self):
    118         """Getter for private |self._tasks| property."""
    119         return self._tasks
    120 
    121 
    122     @property
    123     def launch_control_branches_targets(self):
    124         """Get a dict of branch:targets for Launch Control from all tasks.
    125 
    126         branch: Name of a Launch Control branch.
    127         targets: A list of targets for the given branch.
    128         """
    129         branches = {}
    130         for task in self._tasks:
    131             for branch in task.launch_control_branches:
    132                 branches.setdefault(branch, []).extend(
    133                         task.launch_control_targets)
    134         return branches
    135 
    136 
    137     @tasks.setter
    138     def tasks(self, iterable_of_tasks):
    139         """Set the tasks property with an iterable.
    140 
    141         @param iterable_of_tasks: list of Task instances that can fire on this.
    142         """
    143         self._tasks = set(iterable_of_tasks)
    144 
    145 
    146     def Merge(self, to_merge):
    147         """Merge this event with to_merge, changing all mutable properties.
    148 
    149         keyword remains unchanged; the following take on values from to_merge:
    150           _tasks
    151           _mv
    152           _always_handle
    153 
    154         @param to_merge: A BaseEvent instance to merge into this instance.
    155         """
    156         self.tasks = to_merge.tasks
    157         self._mv = to_merge._mv
    158         self._always_handle = to_merge._always_handle
    159 
    160 
    161     def Prepare(self):
    162         """Perform any one-time setup that must occur before [Should]Handle().
    163 
    164         Must be implemented by subclasses.
    165         """
    166         raise NotImplementedError()
    167 
    168 
    169     def GetBranchBuildsForBoard(self, board):
    170         """Get per-branch, per-board builds since last run of this event.
    171 
    172         @param board: the board whose builds we want.
    173         @return {branch: [build-name]}
    174 
    175         Must be implemented by subclasses.
    176         """
    177         raise NotImplementedError()
    178 
    179 
    180     def GetLaunchControlBuildsForBoard(self, board):
    181         """Get per-branch, per-board builds since last run of this event.
    182 
    183         @param board: the board whose builds we want.
    184 
    185         @return: A list of Launch Control builds for the given board, e.g.,
    186                 ['git_mnc_release/shamu-eng/123',
    187                  'git_mnc_release/shamu-eng/124'].
    188 
    189         Must be implemented by subclasses.
    190         """
    191         raise NotImplementedError()
    192 
    193 
    194     def ShouldHandle(self):
    195         """Returns True if this BaseEvent should be Handle()'d, False if not.
    196 
    197         Must be extended by subclasses.
    198         """
    199         return self._always_handle
    200 
    201 
    202     def UpdateCriteria(self):
    203         """Updates internal state used to decide if this event ShouldHandle()
    204 
    205         Must be implemented by subclasses.
    206         """
    207         raise NotImplementedError()
    208 
    209 
    210     def FilterTasks(self):
    211         """Filter the tasks to only return tasks should run now.
    212 
    213         One use case is that Nightly task can run at each hour. The override of
    214         this function in Nightly class will only return the tasks set to run in
    215         current hour.
    216 
    217         @return: A list of tasks can run now.
    218         """
    219         return list(self.tasks)
    220 
    221 
    222     def Handle(self, scheduler, branch_builds, board, force=False,
    223                launch_control_builds=None):
    224         """Runs all tasks in self._tasks that if scheduled, can be
    225         successfully run.
    226 
    227         @param scheduler: an instance of DedupingScheduler, as defined in
    228                           deduping_scheduler.py
    229         @param branch_builds: a dict mapping branch name to the build to
    230                               install for that branch, e.g.
    231                               {'R18': ['x86-alex-release/R18-1655.0.0'],
    232                                'R19': ['x86-alex-release/R19-2077.0.0']
    233                                'factory': ['x86-alex-factory/R19-2077.0.5']}
    234         @param board: the board against which to Run() all of self._tasks.
    235         @param force: Tell every Task to always Run().
    236         @param launch_control_builds: A list of Launch Control builds.
    237         """
    238         logging.info('Handling %s for %s', self.keyword, board)
    239         # we need to iterate over an immutable copy of self._tasks
    240         tasks = list(self.tasks) if force else self.FilterTasks()
    241         for task in tasks:
    242             if task.AvailableHosts(scheduler, board):
    243                 if not task.Run(scheduler, branch_builds, board, force,
    244                                 self._mv, launch_control_builds):
    245                     self._tasks.remove(task)
    246             elif task.ShouldHaveAvailableHosts():
    247                 logging.warning('Skipping %s on %s, due to lack of hosts.',
    248                                 task, board)
    249 
    250 
    251     def _LatestLaunchControlBuilds(self, board):
    252         """Get latest per-branch, per-board builds.
    253 
    254         @param board: the board whose builds we want, e.g., shamu.
    255 
    256         @return: A list of Launch Control builds for the given board, e.g.,
    257                 ['git_mnc_release/shamu-eng/123',
    258                  'git_mnc_release/shamu-eng/124'].
    259         """
    260         # Translate board name to the actual board name in build target.
    261         board = utils.ANDROID_BOARD_TO_TARGET_MAP.get(board, board)
    262         # Pick a random devserver based on tick, this is to help load balancing
    263         # across all devservers.
    264         devserver = dev_server.AndroidBuildServer.random()
    265         builds = []
    266         for branch, targets in self.launch_control_branches_targets.items():
    267             # targets is a list of Launch Control targets, e.g., shamu-eng.
    268             # The first part should match the board name.
    269             match_targets = [
    270                     t for t in targets
    271                     if board == utils.parse_launch_control_target(t)[0]]
    272             for target in match_targets:
    273                 latest_build = (_LATEST_LAUNCH_CONTROL_BUILD_FMT %
    274                                 (branch, target))
    275                 try:
    276                     builds.append(devserver.translate(latest_build))
    277                 except Exception as e:
    278                     logging.warning('Error happens in translating %s on %s: %s',
    279                                     latest_build, devserver.hostname, str(e))
    280 
    281         return builds
    282