Package buildbot :: Package schedulers :: Module timed
[frames] | no frames]

Source Code for Module buildbot.schedulers.timed

  1  # ***** BEGIN LICENSE BLOCK ***** 
  2  # Version: MPL 1.1/GPL 2.0/LGPL 2.1 
  3  # 
  4  # The contents of this file are subject to the Mozilla Public License Version 
  5  # 1.1 (the "License"); you may not use this file except in compliance with 
  6  # the License. You may obtain a copy of the License at 
  7  # http://www.mozilla.org/MPL/ 
  8  # 
  9  # Software distributed under the License is distributed on an "AS IS" basis, 
 10  # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 11  # for the specific language governing rights and limitations under the 
 12  # License. 
 13  # 
 14  # The Original Code is Mozilla-specific Buildbot steps. 
 15  # 
 16  # The Initial Developer of the Original Code is 
 17  # Mozilla Foundation. 
 18  # Portions created by the Initial Developer are Copyright (C) 2009 
 19  # the Initial Developer. All Rights Reserved. 
 20  # 
 21  # Contributor(s): 
 22  #   Brian Warner <warner@lothar.com> 
 23  # 
 24  # Alternatively, the contents of this file may be used under the terms of 
 25  # either the GNU General Public License Version 2 or later (the "GPL"), or 
 26  # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 
 27  # in which case the provisions of the GPL or the LGPL are applicable instead 
 28  # of those above. If you wish to allow use of your version of this file only 
 29  # under the terms of either the GPL or the LGPL, and not to allow others to 
 30  # use your version of this file under the terms of the MPL, indicate your 
 31  # decision by deleting the provisions above and replace them with the notice 
 32  # and other provisions required by the GPL or the LGPL. If you do not delete 
 33  # the provisions above, a recipient may use your version of this file under 
 34  # the terms of any one of the MPL, the GPL or the LGPL. 
 35  # 
 36  # ***** END LICENSE BLOCK ***** 
 37   
 38  import time 
 39  from twisted.internet import defer 
 40  from twisted.python import log 
 41  from buildbot.sourcestamp import SourceStamp 
 42  from buildbot.schedulers import base 
 43   
44 -class TimedBuildMixin:
45
46 - def start_HEAD_build(self, t):
47 # start a build (of the tip of self.branch) 48 db = self.parent.db 49 ss = SourceStamp(branch=self.branch) 50 ssid = db.get_sourcestampid(ss, t) 51 self.create_buildset(ssid, self.reason, t)
52
53 - def start_requested_build(self, t, relevant_changes):
54 # start a build with the requested list of changes on self.branch 55 db = self.parent.db 56 ss = SourceStamp(branch=self.branch, changes=relevant_changes) 57 ssid = db.get_sourcestampid(ss, t) 58 self.create_buildset(ssid, self.reason, t)
59
60 - def update_last_build(self, t, when):
61 # and record when we did it 62 state = self.get_state(t) 63 state["last_build"] = when 64 self.set_state(t, state)
65
66 -class Periodic(base.BaseScheduler, TimedBuildMixin):
67 """Instead of watching for Changes, this Scheduler can just start a build 68 at fixed intervals. The C{periodicBuildTimer} parameter sets the number 69 of seconds to wait between such periodic builds. The first build will be 70 run immediately.""" 71 72 # TODO: consider having this watch another (changed-based) scheduler and 73 # merely enforce a minimum time between builds. 74 compare_attrs = ('name', 'builderNames', 'periodicBuildTimer', 'branch', 75 'properties') 76
77 - def __init__(self, name, builderNames, periodicBuildTimer, 78 branch=None, properties={}):
79 base.BaseScheduler.__init__(self, name, builderNames, properties) 80 self.periodicBuildTimer = periodicBuildTimer 81 self.branch = branch 82 self.reason = ("The Periodic scheduler named '%s' triggered this build" 83 % name)
84
85 - def get_initial_state(self, max_changeid):
86 return {"last_build": None}
87
88 - def getPendingBuildTimes(self):
89 db = self.parent.db 90 s = db.runInteractionNow(self.get_state) 91 last_build = s["last_build"] 92 now = time.time() 93 if last_build is None: 94 return [now] 95 return [last_build + self.periodicBuildTimer]
96
97 - def run(self):
98 db = self.parent.db 99 d = db.runInteraction(self._run) 100 return d
101
102 - def _run(self, t):
103 now = time.time() 104 s = self.get_state(t) 105 last_build = s["last_build"] 106 if last_build is None: 107 self.start_HEAD_build(t) 108 self.update_last_build(t, now) 109 last_build = now 110 when = last_build + self.periodicBuildTimer 111 if when < now: 112 self.start_HEAD_build(t) 113 self.update_last_build(t, now) 114 last_build = now 115 when = now + self.periodicBuildTimer 116 return when + 1.0
117 118
119 -class Nightly(base.BaseScheduler, base.ClassifierMixin, TimedBuildMixin):
120 """Imitate 'cron' scheduling. This can be used to schedule a nightly 121 build, or one which runs are certain times of the day, week, or month. 122 123 Pass some subset of minute, hour, dayOfMonth, month, and dayOfWeek; each 124 may be a single number or a list of valid values. The builds will be 125 triggered whenever the current time matches these values. Wildcards are 126 represented by a '*' string. All fields default to a wildcard except 127 'minute', so with no fields this defaults to a build every hour, on the 128 hour. 129 130 For example, the following master.cfg clause will cause a build to be 131 started every night at 3:00am:: 132 133 s = Nightly(name='nightly', builderNames=['builder1', 'builder2'], 134 hour=3, minute=0) 135 c['schedules'].append(s) 136 137 This scheduler will perform a build each monday morning at 6:23am and 138 again at 8:23am:: 139 140 s = Nightly(name='BeforeWork', builderNames=['builder1'], 141 dayOfWeek=0, hour=[6,8], minute=23) 142 143 The following runs a build every two hours:: 144 145 s = Nightly(name='every2hours', builderNames=['builder1'], 146 hour=range(0, 24, 2)) 147 148 And this one will run only on December 24th:: 149 150 s = Nightly(name='SleighPreflightCheck', 151 builderNames=['flying_circuits', 'radar'], 152 month=12, dayOfMonth=24, hour=12, minute=0) 153 154 For dayOfWeek and dayOfMonth, builds are triggered if the date matches 155 either of them. All time values are compared against the tuple returned 156 by time.localtime(), so month and dayOfMonth numbers start at 1, not 157 zero. dayOfWeek=0 is Monday, dayOfWeek=6 is Sunday. 158 159 When onlyIfChanged is True, the build is triggered only if changes have 160 arrived on the given branch since the last build was performed. As a 161 further restriction, if fileIsImportant= is provided (a one-argument 162 callable which takes a Change object and returns a bool), then the build 163 will be triggered only if at least one of those changes qualifies as 164 'important'. The following example will run a build at 3am, but only when 165 a source code file (.c/.h) has been changed: 166 167 def isSourceFile(change): 168 for fn in change.files: 169 if fn.endswith('.c') or fn.endswith('.h'): 170 return True 171 return False 172 s = Nightly(name='nightly-when-changed', builderNames=['builder1'], 173 hour=3, minute=0, 174 onlyIfChanged=True, fileIsImportant=isSourceFile) 175 176 onlyIfChanged defaults to False, which means a build will be performed 177 even if nothing has changed. 178 """ 179 180 compare_attrs = ('name', 'builderNames', 181 'minute', 'hour', 'dayOfMonth', 'month', 182 'dayOfWeek', 'branch', 'onlyIfChanged', 183 'fileIsImportant', 'properties') 184
185 - def __init__(self, name, builderNames, minute=0, hour='*', 186 dayOfMonth='*', month='*', dayOfWeek='*', 187 branch=None, fileIsImportant=None, onlyIfChanged=False, 188 properties={}):
189 # Setting minute=0 really makes this an 'Hourly' scheduler. This 190 # seemed like a better default than minute='*', which would result in 191 # a build every 60 seconds. 192 base.BaseScheduler.__init__(self, name, builderNames, properties) 193 self.minute = minute 194 self.hour = hour 195 self.dayOfMonth = dayOfMonth 196 self.month = month 197 self.dayOfWeek = dayOfWeek 198 self.branch = branch 199 self.onlyIfChanged = onlyIfChanged 200 self.delayedRun = None 201 self.nextRunTime = None 202 self.reason = ("The Nightly scheduler named '%s' triggered this build" 203 % name) 204 self.fileIsImportant = None 205 if fileIsImportant: 206 assert callable(fileIsImportant) 207 self.fileIsImportant = fileIsImportant 208 self._start_time = time.time() 209 210 # this scheduler does not support filtering, but ClassifierMixin needs a 211 # filter anyway 212 self.make_filter()
213
214 - def get_initial_state(self, max_changeid):
215 return { 216 "last_build": None, 217 "last_processed": max_changeid, 218 }
219
220 - def getPendingBuildTimes(self):
221 now = time.time() 222 next = self._calculateNextRunTimeFrom(now) 223 # note: this ignores onlyIfChanged 224 return [next]
225
226 - def run(self):
227 d = defer.succeed(None) 228 db = self.parent.db 229 # always call classify_changes, so that we can keep last_processed 230 # up to date, in case we are configured with onlyIfChanged. 231 d.addCallback(lambda ign: db.runInteraction(self.classify_changes)) 232 d.addCallback(lambda ign: db.runInteraction(self._check_timer)) 233 return d
234
235 - def _check_timer(self, t):
236 now = time.time() 237 s = self.get_state(t) 238 last_build = s["last_build"] 239 if last_build is None: 240 next = self._calculateNextRunTimeFrom(self._start_time) 241 else: 242 next = self._calculateNextRunTimeFrom(last_build) 243 244 # not ready to fire yet 245 if next >= now: 246 return next + 1.0 247 248 self._maybe_start_build(t) 249 self.update_last_build(t, now) 250 251 # reschedule for the next timer 252 return self._check_timer(t)
253
254 - def _maybe_start_build(self, t):
255 if self.onlyIfChanged: 256 db = self.parent.db 257 res = db.scheduler_get_classified_changes(self.schedulerid, t) 258 (important, unimportant) = res 259 if not important: 260 log.msg("Nightly Scheduler <%s>: " 261 "skipping build - No important change" % self.name) 262 return 263 relevant_changes = [c for c in (important + unimportant) if 264 c.branch == self.branch] 265 if not relevant_changes: 266 log.msg("Nightly Scheduler <%s>: " 267 "skipping build - No relevant change on branch" % 268 self.name) 269 return 270 self.start_requested_build(t, relevant_changes) 271 # retire the changes 272 changeids = [c.number for c in relevant_changes] 273 db.scheduler_retire_changes(self.schedulerid, changeids, t) 274 else: 275 # start it unconditionally 276 self.start_HEAD_build(t)
277
278 - def _addTime(self, timetuple, secs):
279 return time.localtime(time.mktime(timetuple)+secs)
280
281 - def _isRunTime(self, timetuple):
282 def check(ourvalue, value): 283 if ourvalue == '*': return True 284 if isinstance(ourvalue, int): return value == ourvalue 285 return (value in ourvalue)
286 287 if not check(self.minute, timetuple[4]): 288 #print 'bad minute', timetuple[4], self.minute 289 return False 290 291 if not check(self.hour, timetuple[3]): 292 #print 'bad hour', timetuple[3], self.hour 293 return False 294 295 if not check(self.month, timetuple[1]): 296 #print 'bad month', timetuple[1], self.month 297 return False 298 299 if self.dayOfMonth != '*' and self.dayOfWeek != '*': 300 # They specified both day(s) of month AND day(s) of week. 301 # This means that we only have to match one of the two. If 302 # neither one matches, this time is not the right time. 303 if not (check(self.dayOfMonth, timetuple[2]) or 304 check(self.dayOfWeek, timetuple[6])): 305 #print 'bad day' 306 return False 307 else: 308 if not check(self.dayOfMonth, timetuple[2]): 309 #print 'bad day of month' 310 return False 311 312 if not check(self.dayOfWeek, timetuple[6]): 313 #print 'bad day of week' 314 return False 315 316 return True
317
318 - def _calculateNextRunTimeFrom(self, now):
319 dateTime = time.localtime(now) 320 321 # Remove seconds by advancing to at least the next minute 322 dateTime = self._addTime(dateTime, 60-dateTime[5]) 323 324 # Now we just keep adding minutes until we find something that matches 325 326 # It not an efficient algorithm, but it'll *work* for now 327 yearLimit = dateTime[0]+2 328 while not self._isRunTime(dateTime): 329 dateTime = self._addTime(dateTime, 60) 330 #print 'Trying', time.asctime(dateTime) 331 assert dateTime[0] < yearLimit, 'Something is wrong with this code' 332 return time.mktime(dateTime)
333