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 9from autotest_lib.client.common_lib.cros import dev_server 10from 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 25def 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 33def 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 41def 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 53class 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