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