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

Source Code for Module buildbot.schedulers.timed

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