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

Source Code for Module buildbot.schedulers.base

  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.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 
23 24 -def isScheduler(sch):
25 "Check that an object is a scheduler; used for configuration checks." 26 return isinstance(sch, BaseScheduler)
27
28 -class BaseScheduler(service.MultiService, ComparableMixin):
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 # internal variables 86 self._change_subscription = None 87 self._state_lock = defer.DeferredLock() 88 self._change_consumption_lock = defer.DeferredLock()
89 90 ## service handling 91
92 - def _setUpScheduler(self, schedulerid, master, manager):
93 # this is called by SchedulerManager *before* startService 94 self.schedulerid = schedulerid 95 self.master = master
96
97 - def startService(self):
98 service.MultiService.startService(self)
99
100 - def stopService(self):
101 d = defer.maybeDeferred(self._stopConsumingChanges) 102 d.addCallback(lambda _ : service.MultiService.stopService(self)) 103 return d
104
105 - def _shutDownScheduler(self):
106 # called by SchedulerManager *after* stopService is complete 107 self.schedulerid = None 108 self.master = None
109 110 ## state management 111
112 - class Thunk: pass
113 - def getState(self, key, default=Thunk):
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')
131 - def setState(self, key, value):
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 ## status queries 147 148 # TODO: these aren't compatible with distributed schedulers 149
150 - def listBuilderNames(self):
151 "Returns the list of builder names" 152 return self.builderNames
153
154 - def getPendingBuildTimes(self):
155 "Returns a list of the next times that builds are scheduled, if known." 156 return []
157 158 ## change handling 159
160 - def startConsumingChanges(self, fileIsImportant=None, change_filter=None, 161 onlyImportant=False):
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 # register for changes with master 184 assert not self._change_subscription 185 def changeCallback(change): 186 # ignore changes delivered while we're not running 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 # use change_consumption_lock to ensure the service does not stop 205 # while this change is being processed 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
216 - def _stopConsumingChanges(self):
217 # (note: called automatically in stopService) 218 219 # acquire the lock change consumption lock to ensure that any change 220 # consumption is complete before we are done stopping consumption 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
230 - def gotChange(self, change, important):
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 ## starting bulids 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 # attributes for this sourcestamp will be based on the most recent 306 # change, so fetch the change with the highest id 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
327 - def addBuildsetForSourceStamp(self, ssid, reason='', external_idstring=None, 328 properties=None, builderNames=None):
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 # combine properties 348 if properties: 349 properties.updateFromProperties(self.properties) 350 else: 351 properties = self.properties 352 353 # apply the default builderNames 354 if not builderNames: 355 builderNames = self.builderNames 356 357 # translate properties object into a dict as required by the 358 # addBuildset method 359 properties_dict = properties.asDict() 360 361 # add the buildset 362 return self.master.addBuildset( 363 ssid=ssid, reason=reason, properties=properties_dict, 364 builderNames=builderNames, external_idstring=external_idstring)
365