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 5import logging 6 7import common 8from 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 21def SectionName(keyword): 22 """Generate a section name for a *Event config stanza.""" 23 return keyword + _SECTION_SUFFIX 24 25 26def HonoredSection(section): 27 """Returns True if section is something _ParseConfig() might consume.""" 28 return section.endswith(_SECTION_SUFFIX) 29 30 31def 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 43class 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