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 bbcollections, NotABranch 
 20  from buildbot.changes import filter, changes 
 21  from buildbot.schedulers import base, dependent 
22 23 -class BaseBasicScheduler(base.BaseScheduler):
24 """ 25 @param onlyImportant: If True, only important changes will be added to the 26 buildset. 27 @type onlyImportant: boolean 28 29 """ 30 31 compare_attrs = (base.BaseScheduler.compare_attrs + 32 ('treeStableTimer', 'change_filter', 'fileIsImportant', 33 'onlyImportant') ) 34 35 _reactor = reactor # for tests 36
37 - class NotSet: pass
38 - def __init__(self, name, shouldntBeSet=NotSet, treeStableTimer=None, 39 builderNames=None, branch=NotABranch, branches=NotABranch, 40 fileIsImportant=None, properties={}, categories=None, 41 change_filter=None, onlyImportant=False):
42 if shouldntBeSet is not self.NotSet: 43 config.error( 44 "pass arguments to schedulers using keyword arguments") 45 if fileIsImportant and not callable(fileIsImportant): 46 config.error( 47 "fileIsImportant must be a callable") 48 49 # initialize parent classes 50 base.BaseScheduler.__init__(self, name, builderNames, properties) 51 52 self.treeStableTimer = treeStableTimer 53 self.fileIsImportant = fileIsImportant 54 self.onlyImportant = onlyImportant 55 self.change_filter = self.getChangeFilter(branch=branch, 56 branches=branches, change_filter=change_filter, 57 categories=categories) 58 59 # the IDelayedCall used to wake up when this scheduler's 60 # treeStableTimer expires. 61 self._stable_timers = bbcollections.defaultdict(lambda : None) 62 self._stable_timers_lock = defer.DeferredLock()
63
64 - def getChangeFilter(self, branch, branches, change_filter, categories):
65 raise NotImplementedError
66
67 - def startService(self, _returnDeferred=False):
68 base.BaseScheduler.startService(self) 69 70 d = self.startConsumingChanges(fileIsImportant=self.fileIsImportant, 71 change_filter=self.change_filter, 72 onlyImportant=self.onlyImportant) 73 74 # if treeStableTimer is False, then we don't care about classified 75 # changes, so get rid of any hanging around from previous 76 # configurations 77 if not self.treeStableTimer: 78 d.addCallback(lambda _ : 79 self.master.db.schedulers.flushChangeClassifications( 80 self.objectid)) 81 82 # otherwise, if there are classified changes out there, start their 83 # treeStableTimers again 84 else: 85 d.addCallback(lambda _ : 86 self.scanExistingClassifiedChanges()) 87 88 # handle Deferred errors, since startService does not return a Deferred 89 d.addErrback(log.err, "while starting SingleBranchScheduler '%s'" 90 % self.name) 91 92 if _returnDeferred: 93 return d # only used in tests
94
95 - def stopService(self):
96 # the base stopService will unsubscribe from new changes 97 d = base.BaseScheduler.stopService(self) 98 d.addCallback(lambda _ : 99 self._stable_timers_lock.acquire()) 100 def cancel_timers(_): 101 for timer in self._stable_timers.values(): 102 if timer: 103 timer.cancel() 104 self._stable_timers = {} 105 self._stable_timers_lock.release()
106 d.addCallback(cancel_timers) 107 return d
108 109 @util.deferredLocked('_stable_timers_lock')
110 - def gotChange(self, change, important):
111 if not self.treeStableTimer: 112 # if there's no treeStableTimer, we can completely ignore 113 # unimportant changes 114 if not important: 115 return defer.succeed(None) 116 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.deferredGenerator
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 wfd = defer.waitForDeferred( 151 self.master.db.schedulers.getChangeClassifications( 152 self.objectid)) 153 yield wfd 154 classifications = wfd.getResult() 155 156 # call gotChange for each change, after first fetching it from the db 157 for changeid, important in classifications.iteritems(): 158 wfd = defer.waitForDeferred( 159 self.master.db.changes.getChange(changeid)) 160 yield wfd 161 chdict = wfd.getResult() 162 163 if not chdict: 164 continue 165 166 wfd = defer.waitForDeferred( 167 changes.Change.fromChdict(self.master, chdict)) 168 yield wfd 169 change = wfd.getResult() 170 171 wfd = defer.waitForDeferred( 172 self.gotChange(change, important)) 173 yield wfd 174 wfd.getResult()
175
176 - def getTimerNameForChange(self, change):
177 raise NotImplementedError # see subclasses
178
179 - def getChangeClassificationsForTimer(self, objectid, timer_name):
180 """similar to db.schedulers.getChangeClassifications, but given timer 181 name""" 182 raise NotImplementedError # see subclasses
183 184 @util.deferredLocked('_stable_timers_lock') 185 @defer.deferredGenerator
186 - def stableTimerFired(self, timer_name):
187 # if the service has already been stoppd then just bail out 188 if not self._stable_timers[timer_name]: 189 return 190 191 # delete this now-fired timer 192 del self._stable_timers[timer_name] 193 194 wfd = defer.waitForDeferred( 195 self.getChangeClassificationsForTimer(self.objectid, 196 timer_name)) 197 yield wfd 198 classifications = wfd.getResult() 199 200 # just in case: databases do weird things sometimes! 201 if not classifications: # pragma: no cover 202 return 203 204 changeids = sorted(classifications.keys()) 205 wfd = defer.waitForDeferred( 206 self.addBuildsetForChanges(reason='scheduler', 207 changeids=changeids)) 208 yield wfd 209 wfd.getResult() 210 211 max_changeid = changeids[-1] # (changeids are sorted) 212 wfd = defer.waitForDeferred( 213 self.master.db.schedulers.flushChangeClassifications( 214 self.objectid, less_than=max_changeid+1)) 215 yield wfd 216 wfd.getResult()
217
218 -class SingleBranchScheduler(BaseBasicScheduler):
219 - def getChangeFilter(self, branch, branches, change_filter, categories):
220 if branch is NotABranch and not change_filter: 221 config.error( 222 "the 'branch' argument to SingleBranchScheduler is " + 223 "mandatory unless change_filter is provided") 224 elif branches is not NotABranch: 225 config.error( 226 "the 'branches' argument is not allowed for " + 227 "SingleBranchScheduler") 228 229 230 return filter.ChangeFilter.fromSchedulerConstructorArgs( 231 change_filter=change_filter, branch=branch, 232 categories=categories)
233
234 - def getTimerNameForChange(self, change):
235 return "only" # this class only uses one timer
236
237 - def getChangeClassificationsForTimer(self, objectid, timer_name):
238 return self.master.db.schedulers.getChangeClassifications( 239 self.objectid)
240
241 242 -class Scheduler(SingleBranchScheduler):
243 "alias for SingleBranchScheduler"
244 - def __init__(self, *args, **kwargs):
245 log.msg("WARNING: the name 'Scheduler' is deprecated; use " + 246 "buildbot.schedulers.basic.SingleBranchScheduler instead " + 247 "(note that this may require you to change your import " + 248 "statement)") 249 SingleBranchScheduler.__init__(self, *args, **kwargs)
250
251 252 -class AnyBranchScheduler(BaseBasicScheduler):
253 - def getChangeFilter(self, branch, branches, change_filter, categories):
254 assert branch is NotABranch 255 return filter.ChangeFilter.fromSchedulerConstructorArgs( 256 change_filter=change_filter, branch=branches, 257 categories=categories)
258
259 - def getTimerNameForChange(self, change):
260 return change.branch
261
262 - def getChangeClassificationsForTimer(self, objectid, timer_name):
263 branch = timer_name # set in getTimerNameForChange 264 return self.master.db.schedulers.getChangeClassifications( 265 self.objectid, branch=branch)
266 267 # now at buildbot.schedulers.dependent, but keep the old name alive 268 Dependent = dependent.Dependent 269