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

Source Code for Module buildbot.schedulers.basic

  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  from twisted.internet import defer, reactor 
 17  from twisted.python import log 
 18  from buildbot import util, config 
 19  from buildbot.util import NotABranch 
 20  from collections import defaultdict 
 21  from buildbot.changes import filter, changes 
 22  from buildbot.schedulers import base, dependent 
23 24 -class BaseBasicScheduler(base.BaseScheduler):
25 """ 26 @param onlyImportant: If True, only important changes will be added to the 27 buildset. 28 @type onlyImportant: boolean 29 30 """ 31 32 compare_attrs = ['treeStableTimer', 'change_filter', 'fileIsImportant', 33 'onlyImportant'] 34 35 _reactor = reactor # for tests 36 37 fileIsImportant = None 38
39 - class NotSet: pass
40 - def __init__(self, name, shouldntBeSet=NotSet, treeStableTimer=None, 41 builderNames=None, branch=NotABranch, branches=NotABranch, 42 fileIsImportant=None, properties={}, categories=None, 43 change_filter=None, onlyImportant=False, **kwargs):
44 if shouldntBeSet is not self.NotSet: 45 config.error( 46 "pass arguments to schedulers using keyword arguments") 47 if fileIsImportant and not callable(fileIsImportant): 48 config.error( 49 "fileIsImportant must be a callable") 50 51 # initialize parent classes 52 base.BaseScheduler.__init__(self, name, builderNames, properties, **kwargs) 53 54 self.treeStableTimer = treeStableTimer 55 if fileIsImportant is not None: 56 self.fileIsImportant = fileIsImportant 57 self.onlyImportant = onlyImportant 58 self.change_filter = self.getChangeFilter(branch=branch, 59 branches=branches, change_filter=change_filter, 60 categories=categories) 61 62 # the IDelayedCall used to wake up when this scheduler's 63 # treeStableTimer expires. 64 self._stable_timers = defaultdict(lambda : None) 65 self._stable_timers_lock = defer.DeferredLock()
66
67 - def getChangeFilter(self, branch, branches, change_filter, categories):
68 raise NotImplementedError
69
70 - def startService(self, _returnDeferred=False):
71 base.BaseScheduler.startService(self) 72 73 d = self.startConsumingChanges(fileIsImportant=self.fileIsImportant, 74 change_filter=self.change_filter, 75 onlyImportant=self.onlyImportant) 76 77 # if treeStableTimer is False, then we don't care about classified 78 # changes, so get rid of any hanging around from previous 79 # configurations 80 if not self.treeStableTimer: 81 d.addCallback(lambda _ : 82 self.master.db.schedulers.flushChangeClassifications( 83 self.objectid)) 84 85 # otherwise, if there are classified changes out there, start their 86 # treeStableTimers again 87 else: 88 d.addCallback(lambda _ : 89 self.scanExistingClassifiedChanges()) 90 91 # handle Deferred errors, since startService does not return a Deferred 92 d.addErrback(log.err, "while starting SingleBranchScheduler '%s'" 93 % self.name) 94 95 if _returnDeferred: 96 return d # only used in tests
97
98 - def stopService(self):
99 # the base stopService will unsubscribe from new changes 100 d = base.BaseScheduler.stopService(self) 101 @util.deferredLocked(self._stable_timers_lock) 102 def cancel_timers(_): 103 for timer in self._stable_timers.values(): 104 if timer: 105 timer.cancel() 106 self._stable_timers.clear()
107 d.addCallback(cancel_timers) 108 return d
109 110 @util.deferredLocked('_stable_timers_lock')
111 - def gotChange(self, change, important):
112 if not self.treeStableTimer: 113 # if there's no treeStableTimer, we can completely ignore 114 # unimportant changes 115 if not important: 116 return defer.succeed(None) 117 # otherwise, we'll build it right away 118 return self.addBuildsetForChanges(reason='scheduler', 119 changeids=[ change.number ]) 120 121 timer_name = self.getTimerNameForChange(change) 122 123 # if we have a treeStableTimer, then record the change's importance 124 # and: 125 # - for an important change, start the timer 126 # - for an unimportant change, reset the timer if it is running 127 d = self.master.db.schedulers.classifyChanges( 128 self.objectid, { change.number : important }) 129 def fix_timer(_): 130 if not important and not self._stable_timers[timer_name]: 131 return 132 if self._stable_timers[timer_name]: 133 self._stable_timers[timer_name].cancel() 134 def fire_timer(): 135 d = self.stableTimerFired(timer_name) 136 d.addErrback(log.err, "while firing stable timer")
137 self._stable_timers[timer_name] = self._reactor.callLater( 138 self.treeStableTimer, fire_timer) 139 d.addCallback(fix_timer) 140 return d 141 142 @defer.inlineCallbacks
143 - def scanExistingClassifiedChanges(self):
144 # call gotChange for each classified change. This is called at startup 145 # and is intended to re-start the treeStableTimer for any changes that 146 # had not yet been built when the scheduler was stopped. 147 148 # NOTE: this may double-call gotChange for changes that arrive just as 149 # the scheduler starts up. In practice, this doesn't hurt anything. 150 classifications = \ 151 yield self.master.db.schedulers.getChangeClassifications( 152 self.objectid) 153 154 # call gotChange for each change, after first fetching it from the db 155 for changeid, important in classifications.iteritems(): 156 chdict = yield self.master.db.changes.getChange(changeid) 157 158 if not chdict: 159 continue 160 161 change = yield changes.Change.fromChdict(self.master, chdict) 162 yield self.gotChange(change, important)
163
164 - def getTimerNameForChange(self, change):
165 raise NotImplementedError # see subclasses
166
167 - def getChangeClassificationsForTimer(self, objectid, timer_name):
168 """similar to db.schedulers.getChangeClassifications, but given timer 169 name""" 170 raise NotImplementedError # see subclasses
171 172 @util.deferredLocked('_stable_timers_lock') 173 @defer.inlineCallbacks
174 - def stableTimerFired(self, timer_name):
175 # if the service has already been stoppd then just bail out 176 if not self._stable_timers[timer_name]: 177 return 178 179 # delete this now-fired timer 180 del self._stable_timers[timer_name] 181 182 classifications = \ 183 yield self.getChangeClassificationsForTimer(self.objectid, 184 timer_name) 185 186 # just in case: databases do weird things sometimes! 187 if not classifications: # pragma: no cover 188 return 189 190 changeids = sorted(classifications.keys()) 191 yield self.addBuildsetForChanges(reason='scheduler', 192 changeids=changeids) 193 194 max_changeid = changeids[-1] # (changeids are sorted) 195 yield self.master.db.schedulers.flushChangeClassifications( 196 self.objectid, less_than=max_changeid+1)
197
198 - def getPendingBuildTimes(self):
199 # This isn't locked, since the caller expects and immediate value, 200 # and in any case, this is only an estimate. 201 return [timer.getTime() for timer in self._stable_timers.values() if timer and timer.active()]
202
203 -class SingleBranchScheduler(BaseBasicScheduler):
204 - def getChangeFilter(self, branch, branches, change_filter, categories):
205 if branch is NotABranch and not change_filter: 206 config.error( 207 "the 'branch' argument to SingleBranchScheduler is " + 208 "mandatory unless change_filter is provided") 209 elif branches is not NotABranch: 210 config.error( 211 "the 'branches' argument is not allowed for " + 212 "SingleBranchScheduler") 213 214 215 return filter.ChangeFilter.fromSchedulerConstructorArgs( 216 change_filter=change_filter, branch=branch, 217 categories=categories)
218
219 - def getTimerNameForChange(self, change):
220 return "only" # this class only uses one timer
221
222 - def getChangeClassificationsForTimer(self, objectid, timer_name):
223 return self.master.db.schedulers.getChangeClassifications( 224 self.objectid)
225
226 227 -class Scheduler(SingleBranchScheduler):
228 "alias for SingleBranchScheduler"
229 - def __init__(self, *args, **kwargs):
230 log.msg("WARNING: the name 'Scheduler' is deprecated; use " + 231 "buildbot.schedulers.basic.SingleBranchScheduler instead " + 232 "(note that this may require you to change your import " + 233 "statement)") 234 SingleBranchScheduler.__init__(self, *args, **kwargs)
235
236 237 -class AnyBranchScheduler(BaseBasicScheduler):
238 - def getChangeFilter(self, branch, branches, change_filter, categories):
239 assert branch is NotABranch 240 return filter.ChangeFilter.fromSchedulerConstructorArgs( 241 change_filter=change_filter, branch=branches, 242 categories=categories)
243
244 - def getTimerNameForChange(self, change):
245 # Py2.6+: could be a namedtuple 246 return (change.codebase, change.project, change.repository, change.branch)
247
248 - def getChangeClassificationsForTimer(self, objectid, timer_name):
249 codebase, project, repository, branch = timer_name # set in getTimerNameForChange 250 return self.master.db.schedulers.getChangeClassifications( 251 self.objectid, branch=branch, repository=repository, 252 codebase=codebase, project=project)
253 254 # now at buildbot.schedulers.dependent, but keep the old name alive 255 Dependent = dependent.Dependent 256