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