1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import time
17 from buildbot import util
18 from buildbot.schedulers import base
19 from twisted.internet import defer, reactor
20 from twisted.python import log
21 from buildbot.changes import filter
22
23 -class Timed(base.BaseScheduler):
24 """
25 Parent class for timed schedulers. This takes care of the (surprisingly
26 subtle) mechanics of ensuring that each timed actuation runs to completion
27 before the service stops.
28 """
29
30 compare_attrs = base.BaseScheduler.compare_attrs
31
32 - def __init__(self, name, builderNames, properties={}):
46
48 base.BaseScheduler.startService(self)
49
50
51 self.actuateOk = True
52
53
54 d = self.getState('last_build', None)
55 def set_last(lastActuated):
56 self.lastActuated = lastActuated
57 d.addCallback(set_last)
58
59
60 d.addCallback(lambda _ : self.scheduleNextBuild())
61
62
63 d.addCallback(lambda _ : self.startTimedSchedulerService())
64
65
66 d.addErrback(log.err, "while initializing %s '%s'" %
67 (self.__class__.__name__, self.name))
68
70 """Hook for subclasses to participate in the L{startService} process;
71 can return a Deferred"""
72
74
75
76
77 d = self.actuationLock.acquire()
78 def stop_actuating(_):
79 self.actuateOk = False
80 self.actuateAt = None
81 if self.actuateAtTimer:
82 self.actuateAtTimer.cancel()
83 self.actuateAtTimer = None
84 d.addCallback(stop_actuating)
85 d.addCallback(lambda _ : self.actuationLock.release())
86
87
88 d.addCallback(lambda _ : base.BaseScheduler.stopService(self))
89 return d
90
91
92
94
95
96 return [ self.actuateAt ]
97
98
99
101 """The time has come to start a new build. Returns a Deferred.
102 Override in subclasses."""
103 raise NotImplementedError
104
106 """
107 Called by to calculate the next time to actuate a BuildSet. Override
108 in subclasses. To trigger a fresh call to this method, use
109 L{rescheduleNextBuild}.
110
111 @param lastActuation: the time of the last actuation, or None for never
112
113 @returns: a Deferred firing with the next time a build should occur (in
114 the future), or None for never.
115 """
116 raise NotImplementedError
117
119 """
120 Schedule the next build, re-invoking L{getNextBuildTime}. This can be
121 called at any time, and it will avoid contention with builds being
122 started concurrently.
123
124 @returns: Deferred
125 """
126 d = self.actuationLock.acquire()
127 d.addCallback(lambda _ : self._scheduleNextBuild_locked())
128
129 def release(x):
130 self.actuationLock.release()
131 return x
132 d.addBoth(release)
133 return d
134
135
136
138 "Similar to util.now, but patchable by tests"
139 return util.now(self._reactor)
140
142
143 if self.actuateAtTimer:
144 self.actuateAtTimer.cancel()
145 self.actuateAtTimer = None
146
147
148 d = self.getNextBuildTime(self.lastActuated)
149
150
151 def set_timer(actuateAt):
152 now = self.now()
153 self.actuateAt = max(actuateAt, now)
154 if actuateAt is not None:
155 untilNext = self.actuateAt - now
156 if untilNext == 0:
157 log.msg(("%s: missed scheduled build time, so building "
158 "immediately") % self.name)
159 self.actuateAtTimer = self._reactor.callLater(untilNext,
160 self._actuate)
161 d.addCallback(set_timer)
162
163 return d
164
166
167 self.actuateAtTimer = None
168 self.lastActuated = self.actuateAt
169
170 d = self.actuationLock.acquire()
171
172 @defer.deferredGenerator
173 def set_state_and_start(_):
174
175 if not self.actuateOk:
176 return
177
178
179 self.actuateAt = None
180 wfd = defer.waitForDeferred(self.setState('last_build',
181 self.lastActuated))
182 yield wfd
183 wfd.getResult()
184
185
186 wfd = defer.waitForDeferred(self.startBuild())
187 yield wfd
188 wfd.getResult()
189
190
191 wfd = defer.waitForDeferred(self._scheduleNextBuild_locked())
192 yield wfd
193 wfd.getResult()
194 d.addCallback(set_state_and_start)
195
196 def unlock(x):
197 self.actuationLock.release()
198 return x
199 d.addBoth(unlock)
200
201
202
203 d.addErrback(log.err, 'while actuating')
204
207 compare_attrs = Timed.compare_attrs + ('periodicBuildTimer', 'branch',)
208
209 - def __init__(self, name, builderNames, periodicBuildTimer,
210 branch=None, properties={}, onlyImportant=False):
211 Timed.__init__(self, name=name, builderNames=builderNames,
212 properties=properties)
213 assert periodicBuildTimer > 0, "periodicBuildTimer must be positive"
214 self.periodicBuildTimer = periodicBuildTimer
215 self.branch = branch
216 self.reason = "The Periodic scheduler named '%s' triggered this build" % self.name
217
219 if lastActuated is None:
220 return defer.succeed(self.now())
221 else:
222 return defer.succeed(lastActuated + self.periodicBuildTimer)
223
226
228 compare_attrs = (Timed.compare_attrs
229 + ('minute', 'hour', 'dayOfMonth', 'month',
230 'dayOfWeek', 'onlyIfChanged', 'fileIsImportant',
231 'change_filter', 'onlyImportant',))
232
234 - def __init__(self, name, builderNames, minute=0, hour='*',
235 dayOfMonth='*', month='*', dayOfWeek='*',
236 branch=NoBranch, fileIsImportant=None, onlyIfChanged=False,
237 properties={}, change_filter=None, onlyImportant=False):
238 Timed.__init__(self, name=name, builderNames=builderNames, properties=properties)
239
240
241 self.onlyImportant = onlyImportant
242
243 if fileIsImportant:
244 assert callable(fileIsImportant), \
245 "fileIsImportant must be a callable"
246 assert branch is not Nightly.NoBranch, \
247 "Nightly parameter 'branch' is required"
248
249 self.minute = minute
250 self.hour = hour
251 self.dayOfMonth = dayOfMonth
252 self.month = month
253 self.dayOfWeek = dayOfWeek
254 self.branch = branch
255 self.onlyIfChanged = onlyIfChanged
256 self.fileIsImportant = fileIsImportant
257 self.change_filter = filter.ChangeFilter.fromSchedulerConstructorArgs(
258 change_filter=change_filter)
259 self.reason = "The Nightly scheduler named '%s' triggered this build" % self.name
260
268
278
280 def addTime(timetuple, secs):
281 return time.localtime(time.mktime(timetuple)+secs)
282
283 def check(ourvalue, value):
284 if ourvalue == '*': return True
285 if isinstance(ourvalue, int): return value == ourvalue
286 return (value in ourvalue)
287
288 dateTime = time.localtime(lastActuated or self.now())
289
290
291 dateTime = addTime(dateTime, 60-dateTime[5])
292
293
294
295
296 yearLimit = dateTime[0]+2
297 def isRunTime(timetuple):
298
299 if not check(self.minute, timetuple[4]):
300 return False
301
302 if not check(self.hour, timetuple[3]):
303 return False
304
305 if not check(self.month, timetuple[1]):
306 return False
307
308 if self.dayOfMonth != '*' and self.dayOfWeek != '*':
309
310
311
312 if not (check(self.dayOfMonth, timetuple[2]) or
313 check(self.dayOfWeek, timetuple[6])):
314 return False
315 else:
316 if not check(self.dayOfMonth, timetuple[2]):
317 return False
318
319 if not check(self.dayOfWeek, timetuple[6]):
320 return False
321
322 return True
323
324 while not isRunTime(dateTime):
325 dateTime = addTime(dateTime, 60)
326 assert dateTime[0] < yearLimit, 'Something is wrong with this code'
327 return defer.succeed(time.mktime(dateTime))
328
329 @defer.deferredGenerator
331 scheds = self.master.db.schedulers
332
333
334 if self.onlyIfChanged:
335 wfd = defer.waitForDeferred(scheds.getChangeClassifications(self.schedulerid))
336 yield wfd
337 classifications = wfd.getResult()
338
339
340 for imp in classifications.itervalues():
341 if imp:
342 break
343 else:
344 log.msg(("Nightly Scheduler <%s>: skipping build " +
345 "- No important changes on configured branch") % self.name)
346 return
347
348 changeids = sorted(classifications.keys())
349 wfd = defer.waitForDeferred(
350 self.addBuildsetForChanges(reason=self.reason, changeids=changeids))
351 yield wfd
352 wfd.getResult()
353
354 max_changeid = changeids[-1]
355 wfd = defer.waitForDeferred(
356 scheds.flushChangeClassifications(self.schedulerid,
357 less_than=max_changeid+1))
358 yield wfd
359 wfd.getResult()
360 else:
361
362 wfd = defer.waitForDeferred(
363 self.addBuildsetForLatest(reason=self.reason, branch=self.branch))
364 yield wfd
365 wfd.getResult()
366