1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from twisted.python import failure, log
17 from twisted.application import service
18 from twisted.internet import defer
19 from buildbot import util
20 from buildbot.process.properties import Properties
21 from buildbot.util import ComparableMixin
22 from buildbot.changes import changes
25 "Check that an object is a scheduler; used for configuration checks."
26 return isinstance(sch, BaseScheduler)
27
29 """
30 Base class for all schedulers; this provides the equipment to manage
31 reconfigurations and to handle basic scheduler state. It also provides
32 utility methods to begin various sorts of builds.
33
34 Subclasses should add any configuration-derived attributes to
35 C{base.Scheduler.compare_attrs}.
36 """
37
38 compare_attrs = ('name', 'builderNames', 'properties')
39
40 - def __init__(self, name, builderNames, properties):
41 """
42 Initialize a Scheduler.
43
44 @param name: name of this scheduler (used as a key for state)
45 @type name: unicode
46
47 @param builderNames: list of builders this scheduler may start
48 @type builderNames: list of unicode
49
50 @param properties: properties to add to builds triggered by this
51 scheduler
52 @type properties: dictionary
53
54 @param consumeChanges: true if this scheduler wishes to be informed
55 about the addition of new changes. Defaults to False. This should
56 be passed explicitly from subclasses to indicate their interest in
57 consuming changes.
58 @type consumeChanges: boolean
59 """
60 service.MultiService.__init__(self)
61 self.name = name
62 "name of this scheduler; used to identify replacements on reconfig"
63
64 errmsg = ("The builderNames argument to a scheduler must be a list "
65 "of Builder names.")
66 assert isinstance(builderNames, (list, tuple)), errmsg
67 for b in builderNames:
68 assert isinstance(b, str), errmsg
69 self.builderNames = builderNames
70 "list of builder names to start in each buildset"
71
72 self.properties = Properties()
73 "properties that are contributed to each buildset"
74 self.properties.update(properties, "Scheduler")
75 self.properties.setProperty("scheduler", name, "Scheduler")
76
77 self.schedulerid = None
78 """ID of this scheduler; set just before the scheduler starts, and set
79 to None after stopService is complete."""
80
81 self.master = None
82 """BuildMaster instance; set just before the scheduler starts, and set
83 to None after stopService is complete."""
84
85
86 self._change_subscription = None
87 self._state_lock = defer.DeferredLock()
88 self._change_consumption_lock = defer.DeferredLock()
89
90
91
93
94 self.schedulerid = schedulerid
95 self.master = master
96
99
101 d = defer.maybeDeferred(self._stopConsumingChanges)
102 d.addCallback(lambda _ : service.MultiService.stopService(self))
103 return d
104
106
107 self.schedulerid = None
108 self.master = None
109
110
111
114 """
115 For use by subclasses; get a named state value from the scheduler's
116 state, defaulting to DEFAULT; raises C{KeyError} if default is not
117 given and no value exists. Scheduler must be started. Returns the
118 value via a deferred.
119 """
120 d = self.master.db.schedulers.getState(self.schedulerid)
121 def get_value(state_dict):
122 if key in state_dict:
123 return state_dict[key]
124 if default is BaseScheduler.Thunk:
125 raise KeyError("state key '%s' not found" % (key,))
126 return default
127 d.addCallback(get_value)
128 return d
129
130 @util.deferredLocked('_state_lock')
132 """
133 For use by subclasses; set a named state value in the scheduler's
134 persistent state. Note that value must be json-able. Returns a
135 Deferred.
136
137 Note that this method is safe if called simultaneously in the same
138 process, although it is not safe between processes.
139 """
140 d = self.master.db.schedulers.getState(self.schedulerid)
141 def set_value_and_store(state_dict):
142 state_dict[key] = value
143 return self.master.db.schedulers.setState(self.schedulerid, state_dict)
144 d.addCallback(set_value_and_store)
145
146
147
148
149
151 "Returns the list of builder names"
152 return self.builderNames
153
155 "Returns a list of the next times that builds are scheduled, if known."
156 return []
157
158
159
162 """
163 Subclasses should call this method from startService to register to
164 receive changes. The BaseScheduler class will take care of filtering
165 the changes (using change_filter) and (if fileIsImportant is not None)
166 classifying them. See L{gotChange}. Returns a Deferred.
167
168 @param fileIsImportant: a callable provided by the user to distinguish
169 important and unimportant changes
170 @type fileIsImportant: callable
171
172 @param change_filter: a filter to determine which changes are even
173 considered by this scheduler, or C{None} to consider all changes
174 @type change_filter: L{buildbot.changes.filter.ChangeFilter} instance
175
176 @param onlyImportant: If True, only important changes, as specified by
177 fileIsImportant, will be added to the buildset.
178 @type onlyImportant: boolean
179
180 """
181 assert fileIsImportant is None or callable(fileIsImportant)
182
183
184 assert not self._change_subscription
185 def changeCallback(change):
186
187 if not self._change_subscription:
188 return
189
190 if change_filter and not change_filter.filter_change(change):
191 return
192 if fileIsImportant:
193 try:
194 important = fileIsImportant(change)
195 if not important and onlyImportant:
196 return
197 except:
198 log.err(failure.Failure(),
199 'in fileIsImportant check for %s' % change)
200 return
201 else:
202 important = True
203
204
205
206 d = self._change_consumption_lock.acquire()
207 d.addCallback(lambda _ : self.gotChange(change, important))
208 def release(x):
209 self._change_consumption_lock.release()
210 d.addBoth(release)
211 d.addErrback(log.err, 'while processing change')
212 self._change_subscription = self.master.subscribeToChanges(changeCallback)
213
214 return defer.succeed(None)
215
217
218
219
220
221 d = self._change_consumption_lock.acquire()
222 def stop(x):
223 if self._change_subscription:
224 self._change_subscription.unsubscribe()
225 self._change_subscription = None
226 self._change_consumption_lock.release()
227 d.addBoth(stop)
228 return d
229
231 """
232 Called when a change is received; returns a Deferred. If the
233 C{fileIsImportant} parameter to C{startConsumingChanges} was C{None},
234 then all changes are considered important.
235
236 @param change: the new change object
237 @type change: L{buildbot.changes.changes.Change} instance
238 @param important: true if this is an important change, according to
239 C{fileIsImportant}.
240 @type important: boolean
241 @returns: Deferred
242 """
243 raise NotImplementedError
244
245
246
247 - def addBuildsetForLatest(self, reason='', external_idstring=None,
248 branch=None, repository='', project='',
249 builderNames=None, properties=None):
250 """
251 Add a buildset for the 'latest' source in the given branch,
252 repository, and project. This will create a relative sourcestamp for
253 the buildset.
254
255 This method will add any properties provided to the scheduler
256 constructor to the buildset, and will call the master's addBuildset
257 method with the appropriate parameters.
258
259 @param reason: reason for this buildset
260 @type reason: unicode string
261 @param external_idstring: external identifier for this buildset, or None
262 @param branch: branch to build (note that None often has a special meaning)
263 @param repository: repository name for sourcestamp
264 @param project: project name for sourcestamp
265 @param builderNames: builders to name in the buildset (defaults to
266 C{self.builderNames})
267 @param properties: a properties object containing initial properties for
268 the buildset
269 @type properties: L{buildbot.process.properties.Properties}
270 @returns: (buildset ID, buildrequest IDs) via Deferred
271 """
272 d = self.master.db.sourcestamps.addSourceStamp(
273 branch=branch, revision=None, repository=repository,
274 project=project)
275 d.addCallback(self.addBuildsetForSourceStamp, reason=reason,
276 external_idstring=external_idstring,
277 builderNames=builderNames,
278 properties=properties)
279 return d
280
281 - def addBuildsetForChanges(self, reason='', external_idstring=None,
282 changeids=[], builderNames=None, properties=None):
283 """
284 Add a buildset for the combination of the given changesets, creating
285 a sourcestamp based on those changes. The sourcestamp for the buildset
286 will reference all of the indicated changes.
287
288 This method will add any properties provided to the scheduler
289 constructor to the buildset, and will call the master's addBuildset
290 method with the appropriate parameters.
291
292 @param reason: reason for this buildset
293 @type reason: unicode string
294 @param external_idstring: external identifier for this buildset, or None
295 @param changeids: nonempty list of changes to include in this buildset
296 @param builderNames: builders to name in the buildset (defaults to
297 C{self.builderNames})
298 @param properties: a properties object containing initial properties for
299 the buildset
300 @type properties: L{buildbot.process.properties.Properties}
301 @returns: (buildset ID, buildrequest IDs) via Deferred
302 """
303 assert changeids is not []
304
305
306
307 d = self.master.db.changes.getChange(max(changeids))
308 def chdict2change(chdict):
309 if not chdict:
310 return None
311 return changes.Change.fromChdict(self.master, chdict)
312 d.addCallback(chdict2change)
313 def create_sourcestamp(change):
314 return self.master.db.sourcestamps.addSourceStamp(
315 branch=change.branch,
316 revision=change.revision,
317 repository=change.repository,
318 project=change.project,
319 changeids=changeids)
320 d.addCallback(create_sourcestamp)
321 d.addCallback(self.addBuildsetForSourceStamp, reason=reason,
322 external_idstring=external_idstring,
323 builderNames=builderNames,
324 properties=properties)
325 return d
326
329 """
330 Add a buildset for the given, already-existing sourcestamp.
331
332 This method will add any properties provided to the scheduler
333 constructor to the buildset, and will call the master's
334 L{BuildMaster.addBuildset} method with the appropriate parameters, and
335 return the same result.
336
337 @param reason: reason for this buildset
338 @type reason: unicode string
339 @param external_idstring: external identifier for this buildset, or None
340 @param properties: a properties object containing initial properties for
341 the buildset
342 @type properties: L{buildbot.process.properties.Properties}
343 @param builderNames: builders to name in the buildset (defaults to
344 C{self.builderNames})
345 @returns: (buildset ID, buildrequest IDs) via Deferred
346 """
347
348 if properties:
349 properties.updateFromProperties(self.properties)
350 else:
351 properties = self.properties
352
353
354 if not builderNames:
355 builderNames = self.builderNames
356
357
358
359 properties_dict = properties.asDict()
360
361
362 return self.master.addBuildset(
363 ssid=ssid, reason=reason, properties=properties_dict,
364 builderNames=builderNames, external_idstring=external_idstring)
365