Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1493)

Side by Side Diff: third_party/buildbot_7_12/buildbot/master.py

Issue 12207158: Bye bye buildbot 0.7.12. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: buildbot.test.test_run -*-
2
3 import os
4 signal = None
5 try:
6 import signal
7 except ImportError:
8 pass
9 from cPickle import load
10 import warnings
11
12 from zope.interface import implements
13 from twisted.python import log, components
14 from twisted.python.failure import Failure
15 from twisted.internet import defer, reactor
16 from twisted.spread import pb
17 from twisted.cred import portal, checkers
18 from twisted.application import service, strports
19 from twisted.persisted import styles
20
21 import buildbot
22 # sibling imports
23 from buildbot.util import now, safeTranslate
24 from buildbot.pbutil import NewCredPerspective
25 from buildbot.process.builder import Builder, IDLE
26 from buildbot.process.base import BuildRequest
27 from buildbot.status.builder import Status
28 from buildbot.changes.changes import Change, ChangeMaster, TestChangeMaster
29 from buildbot.sourcestamp import SourceStamp
30 from buildbot.buildslave import BuildSlave
31 from buildbot import interfaces, locks
32 from buildbot.process.properties import Properties
33 from buildbot.config import BuilderConfig
34
35 ########################################
36
37 class BotMaster(service.MultiService):
38
39 """This is the master-side service which manages remote buildbot slaves.
40 It provides them with BuildSlaves, and distributes file change
41 notification messages to them.
42 """
43
44 debug = 0
45
46 def __init__(self):
47 service.MultiService.__init__(self)
48 self.builders = {}
49 self.builderNames = []
50 # builders maps Builder names to instances of bb.p.builder.Builder,
51 # which is the master-side object that defines and controls a build.
52 # They are added by calling botmaster.addBuilder() from the startup
53 # code.
54
55 # self.slaves contains a ready BuildSlave instance for each
56 # potential buildslave, i.e. all the ones listed in the config file.
57 # If the slave is connected, self.slaves[slavename].slave will
58 # contain a RemoteReference to their Bot instance. If it is not
59 # connected, that attribute will hold None.
60 self.slaves = {} # maps slavename to BuildSlave
61 self.statusClientService = None
62 self.watchers = {}
63
64 # self.locks holds the real Lock instances
65 self.locks = {}
66
67 # self.mergeRequests is the callable override for merging build
68 # requests
69 self.mergeRequests = None
70
71 # self.prioritizeBuilders is the callable override for builder order
72 # traversal
73 self.prioritizeBuilders = None
74
75 # these four are convenience functions for testing
76
77 def waitUntilBuilderAttached(self, name):
78 b = self.builders[name]
79 #if b.slaves:
80 # return defer.succeed(None)
81 d = defer.Deferred()
82 b.watchers['attach'].append(d)
83 return d
84
85 def waitUntilBuilderDetached(self, name):
86 b = self.builders.get(name)
87 if not b or not b.slaves:
88 return defer.succeed(None)
89 d = defer.Deferred()
90 b.watchers['detach'].append(d)
91 return d
92
93 def waitUntilBuilderFullyDetached(self, name):
94 b = self.builders.get(name)
95 # TODO: this looks too deeply inside the Builder object
96 if not b or not b.slaves:
97 return defer.succeed(None)
98 d = defer.Deferred()
99 b.watchers['detach_all'].append(d)
100 return d
101
102 def waitUntilBuilderIdle(self, name):
103 b = self.builders[name]
104 # TODO: this looks way too deeply inside the Builder object
105 for sb in b.slaves:
106 if sb.state != IDLE:
107 d = defer.Deferred()
108 b.watchers['idle'].append(d)
109 return d
110 return defer.succeed(None)
111
112 def loadConfig_Slaves(self, new_slaves):
113 old_slaves = [c for c in list(self)
114 if interfaces.IBuildSlave.providedBy(c)]
115
116 # identify added/removed slaves. For each slave we construct a tuple
117 # of (name, password, class), and we consider the slave to be already
118 # present if the tuples match. (we include the class to make sure
119 # that BuildSlave(name,pw) is different than
120 # SubclassOfBuildSlave(name,pw) ). If the password or class has
121 # changed, we will remove the old version of the slave and replace it
122 # with a new one. If anything else has changed, we just update the
123 # old BuildSlave instance in place. If the name has changed, of
124 # course, it looks exactly the same as deleting one slave and adding
125 # an unrelated one.
126 old_t = {}
127 for s in old_slaves:
128 old_t[(s.slavename, s.password, s.__class__)] = s
129 new_t = {}
130 for s in new_slaves:
131 new_t[(s.slavename, s.password, s.__class__)] = s
132 removed = [old_t[t]
133 for t in old_t
134 if t not in new_t]
135 added = [new_t[t]
136 for t in new_t
137 if t not in old_t]
138 remaining_t = [t
139 for t in new_t
140 if t in old_t]
141 # removeSlave will hang up on the old bot
142 dl = []
143 for s in removed:
144 dl.append(self.removeSlave(s))
145 d = defer.DeferredList(dl, fireOnOneErrback=True)
146 def _add(res):
147 for s in added:
148 self.addSlave(s)
149 for t in remaining_t:
150 old_t[t].update(new_t[t])
151 d.addCallback(_add)
152 return d
153
154 def addSlave(self, s):
155 s.setServiceParent(self)
156 s.setBotmaster(self)
157 self.slaves[s.slavename] = s
158
159 def removeSlave(self, s):
160 # TODO: technically, disownServiceParent could return a Deferred
161 s.disownServiceParent()
162 d = self.slaves[s.slavename].disconnect()
163 del self.slaves[s.slavename]
164 return d
165
166 def slaveLost(self, bot):
167 for name, b in self.builders.items():
168 if bot.slavename in b.slavenames:
169 b.detached(bot)
170
171 def getBuildersForSlave(self, slavename):
172 return [b
173 for b in self.builders.values()
174 if slavename in b.slavenames]
175
176 def getBuildernames(self):
177 return self.builderNames
178
179 def getBuilders(self):
180 allBuilders = [self.builders[name] for name in self.builderNames]
181 return allBuilders
182
183 def setBuilders(self, builders):
184 self.builders = {}
185 self.builderNames = []
186 for b in builders:
187 for slavename in b.slavenames:
188 # this is actually validated earlier
189 assert slavename in self.slaves
190 self.builders[b.name] = b
191 self.builderNames.append(b.name)
192 b.setBotmaster(self)
193 d = self._updateAllSlaves()
194 return d
195
196 def _updateAllSlaves(self):
197 """Notify all buildslaves about changes in their Builders."""
198 dl = [s.updateSlave() for s in self.slaves.values()]
199 return defer.DeferredList(dl)
200
201 def maybeStartAllBuilds(self):
202 builders = self.builders.values()
203 if self.prioritizeBuilders is not None:
204 try:
205 builders = self.prioritizeBuilders(self.parent, builders)
206 except:
207 log.msg("Exception prioritizing builders")
208 log.err(Failure())
209 return
210 else:
211 def _sortfunc(b1, b2):
212 t1 = b1.getOldestRequestTime()
213 t2 = b2.getOldestRequestTime()
214 # If t1 or t2 is None, then there are no build requests,
215 # so sort it at the end
216 if t1 is None:
217 return 1
218 if t2 is None:
219 return -1
220 return cmp(t1, t2)
221 builders.sort(_sortfunc)
222 try:
223 for b in builders:
224 b.maybeStartBuild()
225 except:
226 log.msg("Exception starting builds")
227 log.err(Failure())
228
229 def shouldMergeRequests(self, builder, req1, req2):
230 """Determine whether two BuildRequests should be merged for
231 the given builder.
232
233 """
234 if self.mergeRequests is not None:
235 return self.mergeRequests(builder, req1, req2)
236 return req1.canBeMergedWith(req2)
237
238 def getPerspective(self, slavename):
239 return self.slaves[slavename]
240
241 def shutdownSlaves(self):
242 # TODO: make this into a bot method rather than a builder method
243 for b in self.slaves.values():
244 b.shutdownSlave()
245
246 def stopService(self):
247 for b in self.builders.values():
248 b.builder_status.addPointEvent(["master", "shutdown"])
249 b.builder_status.saveYourself()
250 return service.Service.stopService(self)
251
252 def getLockByID(self, lockid):
253 """Convert a Lock identifier into an actual Lock instance.
254 @param lockid: a locks.MasterLock or locks.SlaveLock instance
255 @return: a locks.RealMasterLock or locks.RealSlaveLock instance
256 """
257 assert isinstance(lockid, (locks.MasterLock, locks.SlaveLock))
258 if not lockid in self.locks:
259 self.locks[lockid] = lockid.lockClass(lockid)
260 # if the master.cfg file has changed maxCount= on the lock, the next
261 # time a build is started, they'll get a new RealLock instance. Note
262 # that this requires that MasterLock and SlaveLock (marker) instances
263 # be hashable and that they should compare properly.
264 return self.locks[lockid]
265
266 ########################################
267
268
269
270 class DebugPerspective(NewCredPerspective):
271 def attached(self, mind):
272 return self
273 def detached(self, mind):
274 pass
275
276 def perspective_requestBuild(self, buildername, reason, branch, revision, pr operties={}):
277 c = interfaces.IControl(self.master)
278 bc = c.getBuilder(buildername)
279 ss = SourceStamp(branch, revision)
280 bpr = Properties()
281 bpr.update(properties, "remote requestBuild")
282 br = BuildRequest(reason, ss, builderName=buildername, properties=bpr)
283 bc.requestBuild(br)
284
285 def perspective_pingBuilder(self, buildername):
286 c = interfaces.IControl(self.master)
287 bc = c.getBuilder(buildername)
288 bc.ping()
289
290 def perspective_fakeChange(self, file, revision=None, who="fakeUser",
291 branch=None):
292 change = Change(who, [file], "some fake comments\n",
293 branch=branch, revision=revision)
294 c = interfaces.IControl(self.master)
295 c.addChange(change)
296
297 def perspective_setCurrentState(self, buildername, state):
298 builder = self.botmaster.builders.get(buildername)
299 if not builder: return
300 if state == "offline":
301 builder.statusbag.currentlyOffline()
302 if state == "idle":
303 builder.statusbag.currentlyIdle()
304 if state == "waiting":
305 builder.statusbag.currentlyWaiting(now()+10)
306 if state == "building":
307 builder.statusbag.currentlyBuilding(None)
308 def perspective_reload(self):
309 print "doing reload of the config file"
310 self.master.loadTheConfigFile()
311 def perspective_pokeIRC(self):
312 print "saying something on IRC"
313 from buildbot.status import words
314 for s in self.master:
315 if isinstance(s, words.IRC):
316 bot = s.f
317 for channel in bot.channels:
318 print " channel", channel
319 bot.p.msg(channel, "Ow, quit it")
320
321 def perspective_print(self, msg):
322 print "debug", msg
323
324 class Dispatcher:
325 implements(portal.IRealm)
326
327 def __init__(self):
328 self.names = {}
329
330 def register(self, name, afactory):
331 self.names[name] = afactory
332 def unregister(self, name):
333 del self.names[name]
334
335 def requestAvatar(self, avatarID, mind, interface):
336 assert interface == pb.IPerspective
337 afactory = self.names.get(avatarID)
338 if afactory:
339 p = afactory.getPerspective()
340 elif avatarID == "debug":
341 p = DebugPerspective()
342 p.master = self.master
343 p.botmaster = self.botmaster
344 elif avatarID == "statusClient":
345 p = self.statusClientService.getPerspective()
346 else:
347 # it must be one of the buildslaves: no other names will make it
348 # past the checker
349 p = self.botmaster.getPerspective(avatarID)
350
351 if not p:
352 raise ValueError("no perspective for '%s'" % avatarID)
353
354 d = defer.maybeDeferred(p.attached, mind)
355 d.addCallback(self._avatarAttached, mind)
356 return d
357
358 def _avatarAttached(self, p, mind):
359 return (pb.IPerspective, p, lambda p=p,mind=mind: p.detached(mind))
360
361 ########################################
362
363 # service hierarchy:
364 # BuildMaster
365 # BotMaster
366 # ChangeMaster
367 # all IChangeSource objects
368 # StatusClientService
369 # TCPClient(self.ircFactory)
370 # TCPServer(self.slaveFactory) -> dispatcher.requestAvatar
371 # TCPServer(self.site)
372 # UNIXServer(ResourcePublisher(self.site))
373
374
375 class BuildMaster(service.MultiService):
376 debug = 0
377 manhole = None
378 debugPassword = None
379 projectName = "(unspecified)"
380 projectURL = None
381 buildbotURL = None
382 change_svc = None
383 properties = Properties()
384
385 def __init__(self, basedir, configFileName="master.cfg"):
386 service.MultiService.__init__(self)
387 self.setName("buildmaster")
388 self.basedir = basedir
389 self.configFileName = configFileName
390
391 # the dispatcher is the realm in which all inbound connections are
392 # looked up: slave builders, change notifications, status clients, and
393 # the debug port
394 dispatcher = Dispatcher()
395 dispatcher.master = self
396 self.dispatcher = dispatcher
397 self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
398 # the checker starts with no user/passwd pairs: they are added later
399 p = portal.Portal(dispatcher)
400 p.registerChecker(self.checker)
401 self.slaveFactory = pb.PBServerFactory(p)
402 self.slaveFactory.unsafeTracebacks = True # let them see exceptions
403
404 self.slavePortnum = None
405 self.slavePort = None
406
407 self.botmaster = BotMaster()
408 self.botmaster.setName("botmaster")
409 self.botmaster.setServiceParent(self)
410 dispatcher.botmaster = self.botmaster
411
412 self.status = Status(self.botmaster, self.basedir)
413
414 self.statusTargets = []
415
416 # this ChangeMaster is a dummy, only used by tests. In the real
417 # buildmaster, where the BuildMaster instance is activated
418 # (startService is called) by twistd, this attribute is overwritten.
419 self.useChanges(TestChangeMaster())
420
421 self.readConfig = False
422
423 def startService(self):
424 service.MultiService.startService(self)
425 self.loadChanges() # must be done before loading the config file
426 if not self.readConfig:
427 # TODO: consider catching exceptions during this call to
428 # loadTheConfigFile and bailing (reactor.stop) if it fails,
429 # since without a config file we can't do anything except reload
430 # the config file, and it would be nice for the user to discover
431 # this quickly.
432 self.loadTheConfigFile()
433 if signal and hasattr(signal, "SIGHUP"):
434 signal.signal(signal.SIGHUP, self._handleSIGHUP)
435 for b in self.botmaster.builders.values():
436 b.builder_status.addPointEvent(["master", "started"])
437 b.builder_status.saveYourself()
438
439 def useChanges(self, changes):
440 if self.change_svc:
441 # TODO: can return a Deferred
442 self.change_svc.disownServiceParent()
443 self.change_svc = changes
444 self.change_svc.basedir = self.basedir
445 self.change_svc.setName("changemaster")
446 self.dispatcher.changemaster = self.change_svc
447 self.change_svc.setServiceParent(self)
448
449 def loadChanges(self):
450 filename = os.path.join(self.basedir, "changes.pck")
451 try:
452 changes = load(open(filename, "rb"))
453 styles.doUpgrade()
454 except IOError:
455 log.msg("changes.pck missing, using new one")
456 changes = ChangeMaster()
457 except EOFError:
458 log.msg("corrupted changes.pck, using new one")
459 changes = ChangeMaster()
460 self.useChanges(changes)
461
462 def _handleSIGHUP(self, *args):
463 reactor.callLater(0, self.loadTheConfigFile)
464
465 def getStatus(self):
466 """
467 @rtype: L{buildbot.status.builder.Status}
468 """
469 return self.status
470
471 def loadTheConfigFile(self, configFile=None):
472 if not configFile:
473 configFile = os.path.join(self.basedir, self.configFileName)
474
475 log.msg("Creating BuildMaster -- buildbot.version: %s" % buildbot.versio n)
476 log.msg("loading configuration from %s" % configFile)
477 configFile = os.path.expanduser(configFile)
478
479 try:
480 f = open(configFile, "r")
481 except IOError, e:
482 log.msg("unable to open config file '%s'" % configFile)
483 log.msg("leaving old configuration in place")
484 log.err(e)
485 return
486
487 try:
488 self.loadConfig(f)
489 except:
490 log.msg("error during loadConfig")
491 log.err()
492 log.msg("The new config file is unusable, so I'll ignore it.")
493 log.msg("I will keep using the previous config file instead.")
494 f.close()
495
496 def loadConfig(self, f):
497 """Internal function to load a specific configuration file. Any
498 errors in the file will be signalled by raising an exception.
499
500 @return: a Deferred that will fire (with None) when the configuration
501 changes have been completed. This may involve a round-trip to each
502 buildslave that was involved."""
503
504 localDict = {'basedir': os.path.expanduser(self.basedir)}
505 try:
506 exec f in localDict
507 except:
508 log.msg("error while parsing config file")
509 raise
510
511 try:
512 config = localDict['BuildmasterConfig']
513 except KeyError:
514 log.err("missing config dictionary")
515 log.err("config file must define BuildmasterConfig")
516 raise
517
518 known_keys = ("bots", "slaves",
519 "sources", "change_source",
520 "schedulers", "builders", "mergeRequests",
521 "slavePortnum", "debugPassword", "logCompressionLimit",
522 "manhole", "status", "projectName", "projectURL",
523 "buildbotURL", "properties", "prioritizeBuilders",
524 "eventHorizon", "buildCacheSize", "logHorizon", "buildHori zon",
525 "changeHorizon", "logMaxSize", "logMaxTailSize",
526 "logCompressionMethod",
527 )
528 for k in config.keys():
529 if k not in known_keys:
530 log.msg("unknown key '%s' defined in config dictionary" % k)
531
532 try:
533 # required
534 schedulers = config['schedulers']
535 builders = config['builders']
536 slavePortnum = config['slavePortnum']
537 #slaves = config['slaves']
538 #change_source = config['change_source']
539
540 # optional
541 debugPassword = config.get('debugPassword')
542 manhole = config.get('manhole')
543 status = config.get('status', [])
544 projectName = config.get('projectName')
545 projectURL = config.get('projectURL')
546 buildbotURL = config.get('buildbotURL')
547 properties = config.get('properties', {})
548 buildCacheSize = config.get('buildCacheSize', None)
549 eventHorizon = config.get('eventHorizon', None)
550 logHorizon = config.get('logHorizon', None)
551 buildHorizon = config.get('buildHorizon', None)
552 logCompressionLimit = config.get('logCompressionLimit', 4*1024)
553 if logCompressionLimit is not None and not \
554 isinstance(logCompressionLimit, int):
555 raise ValueError("logCompressionLimit needs to be bool or int")
556 logCompressionMethod = config.get('logCompressionMethod', "bz2")
557 if logCompressionMethod not in ('bz2', 'gz'):
558 raise ValueError("logCompressionMethod needs to be 'bz2', or 'gz '")
559 logMaxSize = config.get('logMaxSize')
560 if logMaxSize is not None and not \
561 isinstance(logMaxSize, int):
562 raise ValueError("logMaxSize needs to be None or int")
563 logMaxTailSize = config.get('logMaxTailSize')
564 if logMaxTailSize is not None and not \
565 isinstance(logMaxTailSize, int):
566 raise ValueError("logMaxTailSize needs to be None or int")
567 mergeRequests = config.get('mergeRequests')
568 if mergeRequests is not None and not callable(mergeRequests):
569 raise ValueError("mergeRequests must be a callable")
570 prioritizeBuilders = config.get('prioritizeBuilders')
571 if prioritizeBuilders is not None and not callable(prioritizeBuilder s):
572 raise ValueError("prioritizeBuilders must be callable")
573 changeHorizon = config.get("changeHorizon")
574 if changeHorizon is not None and not isinstance(changeHorizon, int):
575 raise ValueError("changeHorizon needs to be an int")
576
577 except KeyError, e:
578 log.msg("config dictionary is missing a required parameter")
579 log.msg("leaving old configuration in place")
580 raise
581
582 #if "bots" in config:
583 # raise KeyError("c['bots'] is no longer accepted")
584
585 slaves = config.get('slaves', [])
586 if "bots" in config:
587 m = ("c['bots'] is deprecated as of 0.7.6 and will be "
588 "removed by 0.8.0 . Please use c['slaves'] instead.")
589 log.msg(m)
590 warnings.warn(m, DeprecationWarning)
591 for name, passwd in config['bots']:
592 slaves.append(BuildSlave(name, passwd))
593
594 if "bots" not in config and "slaves" not in config:
595 log.msg("config dictionary must have either 'bots' or 'slaves'")
596 log.msg("leaving old configuration in place")
597 raise KeyError("must have either 'bots' or 'slaves'")
598
599 #if "sources" in config:
600 # raise KeyError("c['sources'] is no longer accepted")
601
602 if changeHorizon is not None:
603 self.change_svc.changeHorizon = changeHorizon
604
605 change_source = config.get('change_source', [])
606 if isinstance(change_source, (list, tuple)):
607 change_sources = change_source
608 else:
609 change_sources = [change_source]
610 if "sources" in config:
611 m = ("c['sources'] is deprecated as of 0.7.6 and will be "
612 "removed by 0.8.0 . Please use c['change_source'] instead.")
613 log.msg(m)
614 warnings.warn(m, DeprecationWarning)
615 for s in config['sources']:
616 change_sources.append(s)
617
618 # do some validation first
619 for s in slaves:
620 assert interfaces.IBuildSlave.providedBy(s)
621 if s.slavename in ("debug", "change", "status"):
622 raise KeyError(
623 "reserved name '%s' used for a bot" % s.slavename)
624 if config.has_key('interlocks'):
625 raise KeyError("c['interlocks'] is no longer accepted")
626
627 assert isinstance(change_sources, (list, tuple))
628 for s in change_sources:
629 assert interfaces.IChangeSource(s, None)
630 # this assertion catches c['schedulers'] = Scheduler(), since
631 # Schedulers are service.MultiServices and thus iterable.
632 errmsg = "c['schedulers'] must be a list of Scheduler instances"
633 assert isinstance(schedulers, (list, tuple)), errmsg
634 for s in schedulers:
635 assert interfaces.IScheduler(s, None), errmsg
636 assert isinstance(status, (list, tuple))
637 for s in status:
638 assert interfaces.IStatusReceiver(s, None)
639
640 slavenames = [s.slavename for s in slaves]
641 buildernames = []
642 dirnames = []
643
644 # convert builders from objects to config dictionaries
645 builders_dicts = []
646 for b in builders:
647 if isinstance(b, BuilderConfig):
648 builders_dicts.append(b.getConfigDict())
649 elif type(b) is dict:
650 builders_dicts.append(b)
651 else:
652 raise ValueError("builder %s is not a BuilderConfig object (or a dict)" % b)
653 builders = builders_dicts
654
655 for b in builders:
656 if b.has_key('slavename') and b['slavename'] not in slavenames:
657 raise ValueError("builder %s uses undefined slave %s" \
658 % (b['name'], b['slavename']))
659 for n in b.get('slavenames', []):
660 if n not in slavenames:
661 raise ValueError("builder %s uses undefined slave %s" \
662 % (b['name'], n))
663 if b['name'] in buildernames:
664 raise ValueError("duplicate builder name %s"
665 % b['name'])
666 buildernames.append(b['name'])
667
668 # sanity check name (BuilderConfig does this too)
669 if b['name'].startswith("_"):
670 errmsg = ("builder names must not start with an "
671 "underscore: " + b['name'])
672 log.err(errmsg)
673 raise ValueError(errmsg)
674
675 # Fix the dictionnary with default values, in case this wasn't
676 # specified with a BuilderConfig object (which sets the same default s)
677 b.setdefault('builddir', safeTranslate(b['name']))
678 b.setdefault('slavebuilddir', b['builddir'])
679
680 if b['builddir'] in dirnames:
681 raise ValueError("builder %s reuses builddir %s"
682 % (b['name'], b['builddir']))
683 dirnames.append(b['builddir'])
684
685 unscheduled_buildernames = buildernames[:]
686 schedulernames = []
687 for s in schedulers:
688 for b in s.listBuilderNames():
689 assert b in buildernames, \
690 "%s uses unknown builder %s" % (s, b)
691 if b in unscheduled_buildernames:
692 unscheduled_buildernames.remove(b)
693
694 if s.name in schedulernames:
695 # TODO: schedulers share a namespace with other Service
696 # children of the BuildMaster node, like status plugins, the
697 # Manhole, the ChangeMaster, and the BotMaster (although most
698 # of these don't have names)
699 msg = ("Schedulers must have unique names, but "
700 "'%s' was a duplicate" % (s.name,))
701 raise ValueError(msg)
702 schedulernames.append(s.name)
703
704 if unscheduled_buildernames:
705 log.msg("Warning: some Builders have no Schedulers to drive them:"
706 " %s" % (unscheduled_buildernames,))
707
708 # assert that all locks used by the Builds and their Steps are
709 # uniquely named.
710 lock_dict = {}
711 for b in builders:
712 for l in b.get('locks', []):
713 if isinstance(l, locks.LockAccess): # User specified access to t he lock
714 l = l.lockid
715 if lock_dict.has_key(l.name):
716 if lock_dict[l.name] is not l:
717 raise ValueError("Two different locks (%s and %s) "
718 "share the name %s"
719 % (l, lock_dict[l.name], l.name))
720 else:
721 lock_dict[l.name] = l
722 # TODO: this will break with any BuildFactory that doesn't use a
723 # .steps list, but I think the verification step is more
724 # important.
725 for s in b['factory'].steps:
726 for l in s[1].get('locks', []):
727 if isinstance(l, locks.LockAccess): # User specified access to the lock
728 l = l.lockid
729 if lock_dict.has_key(l.name):
730 if lock_dict[l.name] is not l:
731 raise ValueError("Two different locks (%s and %s)"
732 " share the name %s"
733 % (l, lock_dict[l.name], l.name))
734 else:
735 lock_dict[l.name] = l
736
737 if not isinstance(properties, dict):
738 raise ValueError("c['properties'] must be a dictionary")
739
740 # slavePortnum supposed to be a strports specification
741 if type(slavePortnum) is int:
742 slavePortnum = "tcp:%d" % slavePortnum
743
744 # now we're committed to implementing the new configuration, so do
745 # it atomically
746 # TODO: actually, this is spread across a couple of Deferreds, so it
747 # really isn't atomic.
748
749 d = defer.succeed(None)
750
751 self.projectName = projectName
752 self.projectURL = projectURL
753 self.buildbotURL = buildbotURL
754
755 self.properties = Properties()
756 self.properties.update(properties, self.configFileName)
757
758 self.status.logCompressionLimit = logCompressionLimit
759 self.status.logCompressionMethod = logCompressionMethod
760 self.status.logMaxSize = logMaxSize
761 self.status.logMaxTailSize = logMaxTailSize
762 # Update any of our existing builders with the current log parameters.
763 # This is required so that the new value is picked up after a
764 # reconfig.
765 for builder in self.botmaster.builders.values():
766 builder.builder_status.setLogCompressionLimit(logCompressionLimit)
767 builder.builder_status.setLogCompressionMethod(logCompressionMethod)
768 builder.builder_status.setLogMaxSize(logMaxSize)
769 builder.builder_status.setLogMaxTailSize(logMaxTailSize)
770
771 if mergeRequests is not None:
772 self.botmaster.mergeRequests = mergeRequests
773 if prioritizeBuilders is not None:
774 self.botmaster.prioritizeBuilders = prioritizeBuilders
775
776 self.buildCacheSize = buildCacheSize
777 self.eventHorizon = eventHorizon
778 self.logHorizon = logHorizon
779 self.buildHorizon = buildHorizon
780
781 # self.slaves: Disconnect any that were attached and removed from the
782 # list. Update self.checker with the new list of passwords, including
783 # debug/change/status.
784 d.addCallback(lambda res: self.loadConfig_Slaves(slaves))
785
786 # self.debugPassword
787 if debugPassword:
788 self.checker.addUser("debug", debugPassword)
789 self.debugPassword = debugPassword
790
791 # self.manhole
792 if manhole != self.manhole:
793 # changing
794 if self.manhole:
795 # disownServiceParent may return a Deferred
796 d.addCallback(lambda res: self.manhole.disownServiceParent())
797 def _remove(res):
798 self.manhole = None
799 return res
800 d.addCallback(_remove)
801 if manhole:
802 def _add(res):
803 self.manhole = manhole
804 manhole.setServiceParent(self)
805 d.addCallback(_add)
806
807 # add/remove self.botmaster.builders to match builders. The
808 # botmaster will handle startup/shutdown issues.
809 d.addCallback(lambda res: self.loadConfig_Builders(builders))
810
811 d.addCallback(lambda res: self.loadConfig_status(status))
812
813 # Schedulers are added after Builders in case they start right away
814 d.addCallback(lambda res: self.loadConfig_Schedulers(schedulers))
815 # and Sources go after Schedulers for the same reason
816 d.addCallback(lambda res: self.loadConfig_Sources(change_sources))
817
818 # self.slavePort
819 if self.slavePortnum != slavePortnum:
820 if self.slavePort:
821 def closeSlavePort(res):
822 d1 = self.slavePort.disownServiceParent()
823 self.slavePort = None
824 return d1
825 d.addCallback(closeSlavePort)
826 if slavePortnum is not None:
827 def openSlavePort(res):
828 self.slavePort = strports.service(slavePortnum,
829 self.slaveFactory)
830 self.slavePort.setServiceParent(self)
831 d.addCallback(openSlavePort)
832 log.msg("BuildMaster listening on port %s" % slavePortnum)
833 self.slavePortnum = slavePortnum
834
835 log.msg("configuration update started")
836 def _done(res):
837 self.readConfig = True
838 log.msg("configuration update complete")
839 d.addCallback(_done)
840 d.addCallback(lambda res: self.botmaster.maybeStartAllBuilds())
841 return d
842
843 def loadConfig_Slaves(self, new_slaves):
844 # set up the Checker with the names and passwords of all valid bots
845 self.checker.users = {} # violates abstraction, oh well
846 for s in new_slaves:
847 self.checker.addUser(s.slavename, s.password)
848 self.checker.addUser("change", "changepw")
849 # let the BotMaster take care of the rest
850 return self.botmaster.loadConfig_Slaves(new_slaves)
851
852 def loadConfig_Sources(self, sources):
853 if not sources:
854 log.msg("warning: no ChangeSources specified in c['change_source']")
855 # shut down any that were removed, start any that were added
856 deleted_sources = [s for s in self.change_svc if s not in sources]
857 added_sources = [s for s in sources if s not in self.change_svc]
858 log.msg("adding %d new changesources, removing %d" %
859 (len(added_sources), len(deleted_sources)))
860 dl = [self.change_svc.removeSource(s) for s in deleted_sources]
861 def addNewOnes(res):
862 [self.change_svc.addSource(s) for s in added_sources]
863 d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=0)
864 d.addCallback(addNewOnes)
865 return d
866
867 def allSchedulers(self):
868 return [child for child in self
869 if interfaces.IScheduler.providedBy(child)]
870
871
872 def loadConfig_Schedulers(self, newschedulers):
873 oldschedulers = self.allSchedulers()
874 removed = [s for s in oldschedulers if s not in newschedulers]
875 added = [s for s in newschedulers if s not in oldschedulers]
876 dl = [defer.maybeDeferred(s.disownServiceParent) for s in removed]
877 def addNewOnes(res):
878 log.msg("adding %d new schedulers, removed %d" %
879 (len(added), len(dl)))
880 for s in added:
881 s.setServiceParent(self)
882 d = defer.DeferredList(dl, fireOnOneErrback=1)
883 d.addCallback(addNewOnes)
884 if removed or added:
885 # notify Downstream schedulers to potentially pick up
886 # new schedulers now that we have removed and added some
887 def updateDownstreams(res):
888 log.msg("notifying downstream schedulers of changes")
889 for s in newschedulers:
890 if interfaces.IDownstreamScheduler.providedBy(s):
891 s.checkUpstreamScheduler()
892 d.addCallback(updateDownstreams)
893 return d
894
895 def loadConfig_Builders(self, newBuilderData):
896 somethingChanged = False
897 newList = {}
898 newBuilderNames = []
899 allBuilders = self.botmaster.builders.copy()
900 for data in newBuilderData:
901 name = data['name']
902 newList[name] = data
903 newBuilderNames.append(name)
904
905 # identify all that were removed
906 for oldname in self.botmaster.getBuildernames():
907 if oldname not in newList:
908 log.msg("removing old builder %s" % oldname)
909 del allBuilders[oldname]
910 somethingChanged = True
911 # announce the change
912 self.status.builderRemoved(oldname)
913
914 # everything in newList is either unchanged, changed, or new
915 for name, data in newList.items():
916 old = self.botmaster.builders.get(name)
917 basedir = data['builddir']
918 #name, slave, builddir, factory = data
919 if not old: # new
920 # category added after 0.6.2
921 category = data.get('category', None)
922 log.msg("adding new builder %s for category %s" %
923 (name, category))
924 statusbag = self.status.builderAdded(name, basedir, category)
925 builder = Builder(data, statusbag)
926 allBuilders[name] = builder
927 somethingChanged = True
928 elif old.compareToSetup(data):
929 # changed: try to minimize the disruption and only modify the
930 # pieces that really changed
931 diffs = old.compareToSetup(data)
932 log.msg("updating builder %s: %s" % (name, "\n".join(diffs)))
933
934 statusbag = old.builder_status
935 statusbag.saveYourself() # seems like a good idea
936 # TODO: if the basedir was changed, we probably need to make
937 # a new statusbag
938 new_builder = Builder(data, statusbag)
939 new_builder.consumeTheSoulOfYourPredecessor(old)
940 # that migrates any retained slavebuilders too
941
942 # point out that the builder was updated. On the Waterfall,
943 # this will appear just after any currently-running builds.
944 statusbag.addPointEvent(["config", "updated"])
945
946 allBuilders[name] = new_builder
947 somethingChanged = True
948 else:
949 # unchanged: leave it alone
950 log.msg("builder %s is unchanged" % name)
951 pass
952
953 # regardless of whether anything changed, get each builder status
954 # to update its config
955 for builder in allBuilders.values():
956 builder.builder_status.reconfigFromBuildmaster(self)
957
958 # and then tell the botmaster if anything's changed
959 if somethingChanged:
960 sortedAllBuilders = [allBuilders[name] for name in newBuilderNames]
961 d = self.botmaster.setBuilders(sortedAllBuilders)
962 return d
963 return None
964
965 def loadConfig_status(self, status):
966 dl = []
967
968 # remove old ones
969 for s in self.statusTargets[:]:
970 if not s in status:
971 log.msg("removing IStatusReceiver", s)
972 d = defer.maybeDeferred(s.disownServiceParent)
973 dl.append(d)
974 self.statusTargets.remove(s)
975 # after those are finished going away, add new ones
976 def addNewOnes(res):
977 for s in status:
978 if not s in self.statusTargets:
979 log.msg("adding IStatusReceiver", s)
980 s.setServiceParent(self)
981 self.statusTargets.append(s)
982 d = defer.DeferredList(dl, fireOnOneErrback=1)
983 d.addCallback(addNewOnes)
984 return d
985
986
987 def addChange(self, change):
988 for s in self.allSchedulers():
989 s.addChange(change)
990 self.status.changeAdded(change)
991
992 def submitBuildSet(self, bs):
993 # determine the set of Builders to use
994 builders = []
995 for name in bs.builderNames:
996 b = self.botmaster.builders.get(name)
997 if b:
998 if b not in builders:
999 builders.append(b)
1000 continue
1001 # TODO: add aliases like 'all'
1002 raise KeyError("no such builder named '%s'" % name)
1003
1004 # now tell the BuildSet to create BuildRequests for all those
1005 # Builders and submit them
1006 bs.start(builders)
1007 self.status.buildsetSubmitted(bs.status)
1008
1009
1010 class Control:
1011 implements(interfaces.IControl)
1012
1013 def __init__(self, master):
1014 self.master = master
1015
1016 def addChange(self, change):
1017 self.master.change_svc.addChange(change)
1018
1019 def submitBuildSet(self, bs):
1020 self.master.submitBuildSet(bs)
1021
1022 def getBuilder(self, name):
1023 b = self.master.botmaster.builders[name]
1024 return interfaces.IBuilderControl(b)
1025
1026 components.registerAdapter(Control, BuildMaster, interfaces.IControl)
1027
1028 # so anybody who can get a handle on the BuildMaster can cause a build with:
1029 # IControl(master).getBuilder("full-2.3").requestBuild(buildrequest)
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/manhole.py ('k') | third_party/buildbot_7_12/buildbot/pbutil.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698