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