1
2
3 import os
4 import signal
5 import time
6 import warnings
7 import textwrap
8
9 from zope.interface import implements
10 from twisted.python import log, components
11 from twisted.python.failure import Failure
12 from twisted.internet import defer, reactor
13 from twisted.spread import pb
14 from twisted.cred import portal, checkers
15 from twisted.application import service, strports
16 from twisted.application.internet import TimerService
17
18 import buildbot
19
20 from buildbot.util import now, safeTranslate
21 from buildbot.pbutil import NewCredPerspective
22 from buildbot.process.builder import Builder, IDLE
23 from buildbot.status.builder import Status, BuildSetStatus
24 from buildbot.changes.changes import Change
25 from buildbot.changes.manager import ChangeManager
26 from buildbot.buildslave import BuildSlave
27 from buildbot import interfaces, locks
28 from buildbot.process.properties import Properties
29 from buildbot.config import BuilderConfig
30 from buildbot.process.builder import BuilderControl
31 from buildbot.db.dbspec import DBSpec
32 from buildbot.db import connector, schema, exceptions
33 from buildbot.schedulers.manager import SchedulerManager
34 from buildbot.util.loop import DelegateLoop
35
36
37
39
40 """This is the master-side service which manages remote buildbot slaves.
41 It provides them with BuildSlaves, and distributes file change
42 notification messages to them.
43 """
44
45 debug = 0
46
48 service.MultiService.__init__(self)
49 self.builders = {}
50 self.builderNames = []
51
52
53
54
55
56
57
58
59
60
61 self.slaves = {}
62 self.statusClientService = None
63 self.watchers = {}
64
65
66 self.locks = {}
67
68
69
70 self.mergeRequests = None
71
72
73
74 self.prioritizeBuilders = None
75
76 self.loop = DelegateLoop(self._get_processors)
77 self.loop.setServiceParent(self)
78
80 self.master_name = name
81 self.master_incarnation = incarnation
82
93
95 return sorted(builders, self._sortfunc)
96
98 builders = self.builders.values()
99 sorter = self.prioritizeBuilders or self._sort_builders
100 try:
101 builders = sorter(self.parent, builders)
102 except:
103 log.msg("Exception prioritizing builders")
104 log.err(Failure())
105
106 return [b.run for b in builders]
107
114
115
116
118 b = self.builders[name]
119
120
121 d = defer.Deferred()
122 b.watchers['attach'].append(d)
123 return d
124
126 b = self.builders.get(name)
127 if not b or not b.slaves:
128 return defer.succeed(None)
129 d = defer.Deferred()
130 b.watchers['detach'].append(d)
131 return d
132
134 b = self.builders.get(name)
135
136 if not b or not b.slaves:
137 return defer.succeed(None)
138 d = defer.Deferred()
139 b.watchers['detach_all'].append(d)
140 return d
141
143 b = self.builders[name]
144
145 for sb in b.slaves:
146 if sb.state != IDLE:
147 d = defer.Deferred()
148 b.watchers['idle'].append(d)
149 return d
150 return defer.succeed(None)
151
153 old_slaves = [c for c in list(self)
154 if interfaces.IBuildSlave.providedBy(c)]
155
156
157
158
159
160
161
162
163
164
165
166 old_t = {}
167 for s in old_slaves:
168 old_t[(s.slavename, s.password, s.__class__)] = s
169 new_t = {}
170 for s in new_slaves:
171 new_t[(s.slavename, s.password, s.__class__)] = s
172 removed = [old_t[t]
173 for t in old_t
174 if t not in new_t]
175 added = [new_t[t]
176 for t in new_t
177 if t not in old_t]
178 remaining_t = [t
179 for t in new_t
180 if t in old_t]
181
182 dl = []
183 for s in removed:
184 dl.append(self.removeSlave(s))
185 d = defer.DeferredList(dl, fireOnOneErrback=True)
186 def _add(res):
187 for s in added:
188 self.addSlave(s)
189 for t in remaining_t:
190 old_t[t].update(new_t[t])
191 d.addCallback(_add)
192 return d
193
198
205
210
212 return [b
213 for b in self.builders.values()
214 if slavename in b.slavenames]
215
217 return self.builderNames
218
220 allBuilders = [self.builders[name] for name in self.builderNames]
221 return allBuilders
222
224
225
226
227 self.builders = {}
228 self.builderNames = []
229 d = defer.DeferredList([b.disownServiceParent() for b in list(self)
230 if isinstance(b, Builder)],
231 fireOnOneErrback=True)
232 def _add(ign):
233 log.msg("setBuilders._add: %s %s" % (list(self), builders))
234 for b in builders:
235 for slavename in b.slavenames:
236
237 assert slavename in self.slaves
238 self.builders[b.name] = b
239 self.builderNames.append(b.name)
240 b.setBotmaster(self)
241 b.setServiceParent(self)
242 d.addCallback(_add)
243 d.addCallback(lambda ign: self._updateAllSlaves())
244 return d
245
247 """Notify all buildslaves about changes in their Builders."""
248 dl = []
249 for s in self.slaves.values():
250 d = s.updateSlave()
251 d.addErrback(log.err)
252 dl.append(d)
253 return defer.DeferredList(dl)
254
256 """Determine whether two BuildRequests should be merged for
257 the given builder.
258
259 """
260 if self.mergeRequests is not None:
261 return self.mergeRequests(builder, req1, req2)
262 return req1.canBeMergedWith(req2)
263
266
271
273 for b in self.builders.values():
274 b.builder_status.addPointEvent(["master", "shutdown"])
275 b.builder_status.saveYourself()
276 return service.MultiService.stopService(self)
277
279 """Convert a Lock identifier into an actual Lock instance.
280 @param lockid: a locks.MasterLock or locks.SlaveLock instance
281 @return: a locks.RealMasterLock or locks.RealSlaveLock instance
282 """
283 assert isinstance(lockid, (locks.MasterLock, locks.SlaveLock))
284 if not lockid in self.locks:
285 self.locks[lockid] = lockid.lockClass(lockid)
286
287
288
289
290 return self.locks[lockid]
291
292
293
294
295
301
306
307 - def perspective_fakeChange(self, file, revision=None, who="fakeUser",
308 branch=None, repository="",
309 project=""):
315
317 builder = self.botmaster.builders.get(buildername)
318 if not builder: return
319 if state == "offline":
320 builder.statusbag.currentlyOffline()
321 if state == "idle":
322 builder.statusbag.currentlyIdle()
323 if state == "waiting":
324 builder.statusbag.currentlyWaiting(now()+10)
325 if state == "building":
326 builder.statusbag.currentlyBuilding(None)
331 print "saying something on IRC"
332 from buildbot.status import words
333 for s in self.master:
334 if isinstance(s, words.IRC):
335 bot = s.f
336 for channel in bot.channels:
337 print " channel", channel
338 bot.p.msg(channel, "Ow, quit it")
339
342
344 implements(portal.IRealm)
345
348
350 self.names[name] = afactory
353
355 assert interface == pb.IPerspective
356 afactory = self.names.get(avatarID)
357 if afactory:
358 p = afactory.getPerspective()
359 elif avatarID == "change":
360 raise ValueError("no PBChangeSource installed")
361 elif avatarID == "debug":
362 p = DebugPerspective()
363 p.master = self.master
364 p.botmaster = self.botmaster
365 elif avatarID == "statusClient":
366 p = self.statusClientService.getPerspective()
367 else:
368
369
370 p = self.botmaster.getPerspective(avatarID)
371
372 if not p:
373 raise ValueError("no perspective for '%s'" % avatarID)
374
375 d = defer.maybeDeferred(p.attached, mind)
376 d.addCallback(self._avatarAttached, mind)
377 return d
378
380 return (pb.IPerspective, p, lambda p=p,mind=mind: p.detached(mind))
381
382
383
385
387 '''holds log rotation parameters (for WebStatus)'''
389 self.rotateLength = 1 * 1000 * 1000
390 self.maxRotatedFiles = 10
391
393 debug = 0
394 manhole = None
395 debugPassword = None
396 projectName = "(unspecified)"
397 projectURL = None
398 buildbotURL = None
399 change_svc = None
400 properties = Properties()
401
402 - def __init__(self, basedir, configFileName="master.cfg", db_spec=None):
403 service.MultiService.__init__(self)
404 self.setName("buildmaster")
405 self.basedir = basedir
406 self.configFileName = configFileName
407
408
409
410
411 dispatcher = Dispatcher()
412 dispatcher.master = self
413 self.dispatcher = dispatcher
414 self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
415
416 p = portal.Portal(dispatcher)
417 p.registerChecker(self.checker)
418 self.slaveFactory = pb.PBServerFactory(p)
419 self.slaveFactory.unsafeTracebacks = True
420
421 self.slavePortnum = None
422 self.slavePort = None
423
424 self.change_svc = ChangeManager()
425 self.change_svc.setServiceParent(self)
426 self.dispatcher.changemaster = self.change_svc
427
428 try:
429 hostname = os.uname()[1]
430 except AttributeError:
431 hostname = "?"
432 self.master_name = "%s:%s" % (hostname, os.path.abspath(self.basedir))
433 self.master_incarnation = "pid%d-boot%d" % (os.getpid(), time.time())
434
435 self.botmaster = BotMaster()
436 self.botmaster.setName("botmaster")
437 self.botmaster.setMasterName(self.master_name, self.master_incarnation)
438 self.botmaster.setServiceParent(self)
439 self.dispatcher.botmaster = self.botmaster
440
441 self.status = Status(self.botmaster, self.basedir)
442 self.statusTargets = []
443
444 self.db = None
445 self.db_url = None
446 self.db_poll_interval = _Unset
447 if db_spec:
448 self.loadDatabase(db_spec)
449
450 self.readConfig = False
451
452
453 self.log_rotation = LogRotation()
454
456 service.MultiService.startService(self)
457 if not self.readConfig:
458
459
460
461
462
463 self.loadTheConfigFile()
464 if hasattr(signal, "SIGHUP"):
465 signal.signal(signal.SIGHUP, self._handleSIGHUP)
466 for b in self.botmaster.builders.values():
467 b.builder_status.addPointEvent(["master", "started"])
468 b.builder_status.saveYourself()
469
472
474 """
475 @rtype: L{buildbot.status.builder.Status}
476 """
477 return self.status
478
480 if not configFile:
481 configFile = os.path.join(self.basedir, self.configFileName)
482
483 log.msg("Creating BuildMaster -- buildbot.version: %s" % buildbot.version)
484 log.msg("loading configuration from %s" % configFile)
485 configFile = os.path.expanduser(configFile)
486
487 try:
488 f = open(configFile, "r")
489 except IOError, e:
490 log.msg("unable to open config file '%s'" % configFile)
491 log.msg("leaving old configuration in place")
492 log.err(e)
493 return
494
495 try:
496 d = self.loadConfig(f)
497 except:
498 log.msg("error during loadConfig")
499 log.err()
500 log.msg("The new config file is unusable, so I'll ignore it.")
501 log.msg("I will keep using the previous config file instead.")
502 return
503 f.close()
504 return d
505
506 - def loadConfig(self, f, check_synchronously_only=False):
507 """Internal function to load a specific configuration file. Any
508 errors in the file will be signalled by raising an exception.
509
510 If check_synchronously_only=True, I will return (with None)
511 synchronously, after checking the config file for sanity, or raise an
512 exception. I may also emit some DeprecationWarnings.
513
514 If check_synchronously_only=False, I will return a Deferred that
515 fires (with None) when the configuration changes have been completed.
516 This may involve a round-trip to each buildslave that was involved."""
517
518 localDict = {'basedir': os.path.expanduser(self.basedir)}
519 try:
520 exec f in localDict
521 except:
522 log.msg("error while parsing config file")
523 raise
524
525 try:
526 config = localDict['BuildmasterConfig']
527 except KeyError:
528 log.err("missing config dictionary")
529 log.err("config file must define BuildmasterConfig")
530 raise
531
532 known_keys = ("slaves", "change_source",
533 "schedulers", "builders", "mergeRequests",
534 "slavePortnum", "debugPassword", "logCompressionLimit",
535 "manhole", "status", "projectName", "projectURL",
536 "buildbotURL", "properties", "prioritizeBuilders",
537 "eventHorizon", "buildCacheSize", "logHorizon", "buildHorizon",
538 "changeHorizon", "logMaxSize", "logMaxTailSize",
539 "logCompressionMethod", "db_url", "multiMaster",
540 "db_poll_interval",
541 )
542 for k in config.keys():
543 if k not in known_keys:
544 log.msg("unknown key '%s' defined in config dictionary" % k)
545
546 try:
547
548 schedulers = config['schedulers']
549 builders = config['builders']
550 slavePortnum = config['slavePortnum']
551
552
553
554
555 db_url = config.get("db_url", "sqlite:///state.sqlite")
556 db_poll_interval = config.get("db_poll_interval", None)
557 debugPassword = config.get('debugPassword')
558 manhole = config.get('manhole')
559 status = config.get('status', [])
560 projectName = config.get('projectName')
561 projectURL = config.get('projectURL')
562 buildbotURL = config.get('buildbotURL')
563 properties = config.get('properties', {})
564 buildCacheSize = config.get('buildCacheSize', None)
565 eventHorizon = config.get('eventHorizon', 50)
566 logHorizon = config.get('logHorizon', None)
567 buildHorizon = config.get('buildHorizon', None)
568 logCompressionLimit = config.get('logCompressionLimit', 4*1024)
569 if logCompressionLimit is not None and not \
570 isinstance(logCompressionLimit, int):
571 raise ValueError("logCompressionLimit needs to be bool or int")
572 logCompressionMethod = config.get('logCompressionMethod', "bz2")
573 if logCompressionMethod not in ('bz2', 'gz'):
574 raise ValueError("logCompressionMethod needs to be 'bz2', or 'gz'")
575 logMaxSize = config.get('logMaxSize')
576 if logMaxSize is not None and not \
577 isinstance(logMaxSize, int):
578 raise ValueError("logMaxSize needs to be None or int")
579 logMaxTailSize = config.get('logMaxTailSize')
580 if logMaxTailSize is not None and not \
581 isinstance(logMaxTailSize, int):
582 raise ValueError("logMaxTailSize needs to be None or int")
583 mergeRequests = config.get('mergeRequests')
584 if mergeRequests is not None and not callable(mergeRequests):
585 raise ValueError("mergeRequests must be a callable")
586 prioritizeBuilders = config.get('prioritizeBuilders')
587 if prioritizeBuilders is not None and not callable(prioritizeBuilders):
588 raise ValueError("prioritizeBuilders must be callable")
589 changeHorizon = config.get("changeHorizon")
590 if changeHorizon is not None and not isinstance(changeHorizon, int):
591 raise ValueError("changeHorizon needs to be an int")
592
593 multiMaster = config.get("multiMaster", False)
594
595 except KeyError:
596 log.msg("config dictionary is missing a required parameter")
597 log.msg("leaving old configuration in place")
598 raise
599
600 if "sources" in config:
601 m = ("c['sources'] is deprecated as of 0.7.6 and is no longer "
602 "accepted in >= 0.8.0 . Please use c['change_source'] instead.")
603 raise KeyError(m)
604
605 if "bots" in config:
606 m = ("c['bots'] is deprecated as of 0.7.6 and is no longer "
607 "accepted in >= 0.8.0 . Please use c['slaves'] instead.")
608 raise KeyError(m)
609
610 slaves = config.get('slaves', [])
611 if "slaves" not in config:
612 log.msg("config dictionary must have a 'slaves' key")
613 log.msg("leaving old configuration in place")
614 raise KeyError("must have a 'slaves' key")
615
616 if changeHorizon is not None:
617 self.change_svc.changeHorizon = changeHorizon
618
619 change_source = config.get('change_source', [])
620 if isinstance(change_source, (list, tuple)):
621 change_sources = change_source
622 else:
623 change_sources = [change_source]
624
625
626 for s in slaves:
627 assert interfaces.IBuildSlave.providedBy(s)
628 if s.slavename in ("debug", "change", "status"):
629 raise KeyError(
630 "reserved name '%s' used for a bot" % s.slavename)
631 if config.has_key('interlocks'):
632 raise KeyError("c['interlocks'] is no longer accepted")
633 assert self.db_url is None or db_url == self.db_url, \
634 "Cannot change db_url after master has started"
635 assert db_poll_interval is None or isinstance(db_poll_interval, int), \
636 "db_poll_interval must be an integer: seconds between polls"
637 assert self.db_poll_interval is _Unset or db_poll_interval == self.db_poll_interval, \
638 "Cannot change db_poll_interval after master has started"
639
640 assert isinstance(change_sources, (list, tuple))
641 for s in change_sources:
642 assert interfaces.IChangeSource(s, None)
643
644
645 errmsg = "c['schedulers'] must be a list of Scheduler instances"
646 assert isinstance(schedulers, (list, tuple)), errmsg
647 for s in schedulers:
648 assert interfaces.IScheduler(s, None), errmsg
649 assert isinstance(status, (list, tuple))
650 for s in status:
651 assert interfaces.IStatusReceiver(s, None)
652
653 slavenames = [s.slavename for s in slaves]
654 buildernames = []
655 dirnames = []
656
657
658 builders_dicts = []
659 for b in builders:
660 if isinstance(b, BuilderConfig):
661 builders_dicts.append(b.getConfigDict())
662 elif type(b) is dict:
663 builders_dicts.append(b)
664 else:
665 raise ValueError("builder %s is not a BuilderConfig object (or a dict)" % b)
666 builders = builders_dicts
667
668 for b in builders:
669 if b.has_key('slavename') and b['slavename'] not in slavenames:
670 raise ValueError("builder %s uses undefined slave %s" \
671 % (b['name'], b['slavename']))
672 for n in b.get('slavenames', []):
673 if n not in slavenames:
674 raise ValueError("builder %s uses undefined slave %s" \
675 % (b['name'], n))
676 if b['name'] in buildernames:
677 raise ValueError("duplicate builder name %s"
678 % b['name'])
679 buildernames.append(b['name'])
680
681
682 if b['name'].startswith("_"):
683 errmsg = ("builder names must not start with an "
684 "underscore: " + b['name'])
685 log.err(errmsg)
686 raise ValueError(errmsg)
687
688
689
690 b.setdefault('builddir', safeTranslate(b['name']))
691 b.setdefault('slavebuilddir', b['builddir'])
692 b.setdefault('buildHorizon', buildHorizon)
693 b.setdefault('logHorizon', logHorizon)
694 b.setdefault('eventHorizon', eventHorizon)
695 if b['builddir'] in dirnames:
696 raise ValueError("builder %s reuses builddir %s"
697 % (b['name'], b['builddir']))
698 dirnames.append(b['builddir'])
699
700 unscheduled_buildernames = buildernames[:]
701 schedulernames = []
702 for s in schedulers:
703 for b in s.listBuilderNames():
704
705 if not multiMaster:
706 assert b in buildernames, \
707 "%s uses unknown builder %s" % (s, b)
708 if b in unscheduled_buildernames:
709 unscheduled_buildernames.remove(b)
710
711 if s.name in schedulernames:
712 msg = ("Schedulers must have unique names, but "
713 "'%s' was a duplicate" % (s.name,))
714 raise ValueError(msg)
715 schedulernames.append(s.name)
716
717
718 if not multiMaster and unscheduled_buildernames:
719 log.msg("Warning: some Builders have no Schedulers to drive them:"
720 " %s" % (unscheduled_buildernames,))
721
722
723
724 lock_dict = {}
725 for b in builders:
726 for l in b.get('locks', []):
727 if isinstance(l, locks.LockAccess):
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
738
739 for s in b['factory'].steps:
740 for l in s[1].get('locks', []):
741 if isinstance(l, locks.LockAccess):
742 l = l.lockid
743 if lock_dict.has_key(l.name):
744 if lock_dict[l.name] is not l:
745 raise ValueError("Two different locks (%s and %s)"
746 " share the name %s"
747 % (l, lock_dict[l.name], l.name))
748 else:
749 lock_dict[l.name] = l
750
751 if not isinstance(properties, dict):
752 raise ValueError("c['properties'] must be a dictionary")
753
754
755 if type(slavePortnum) is int:
756 slavePortnum = "tcp:%d" % slavePortnum
757
758 if check_synchronously_only:
759 return
760
761
762
763
764
765 d = defer.succeed(None)
766
767 self.projectName = projectName
768 self.projectURL = projectURL
769 self.buildbotURL = buildbotURL
770
771 self.properties = Properties()
772 self.properties.update(properties, self.configFileName)
773
774 self.status.logCompressionLimit = logCompressionLimit
775 self.status.logCompressionMethod = logCompressionMethod
776 self.status.logMaxSize = logMaxSize
777 self.status.logMaxTailSize = logMaxTailSize
778
779
780
781 for builder in self.botmaster.builders.values():
782 builder.builder_status.setLogCompressionLimit(logCompressionLimit)
783 builder.builder_status.setLogCompressionMethod(logCompressionMethod)
784 builder.builder_status.setLogMaxSize(logMaxSize)
785 builder.builder_status.setLogMaxTailSize(logMaxTailSize)
786
787 if mergeRequests is not None:
788 self.botmaster.mergeRequests = mergeRequests
789 if prioritizeBuilders is not None:
790 self.botmaster.prioritizeBuilders = prioritizeBuilders
791
792 self.buildCacheSize = buildCacheSize
793 self.eventHorizon = eventHorizon
794 self.logHorizon = logHorizon
795 self.buildHorizon = buildHorizon
796
797
798 d.addCallback(lambda res:
799 self.loadConfig_Database(db_url, db_poll_interval))
800
801
802
803
804 d.addCallback(lambda res: self.loadConfig_Slaves(slaves))
805
806
807 if debugPassword:
808 self.checker.addUser("debug", debugPassword)
809 self.debugPassword = debugPassword
810
811
812 if manhole != self.manhole:
813
814 if self.manhole:
815
816 d.addCallback(lambda res: self.manhole.disownServiceParent())
817 def _remove(res):
818 self.manhole = None
819 return res
820 d.addCallback(_remove)
821 if manhole:
822 def _add(res):
823 self.manhole = manhole
824 manhole.setServiceParent(self)
825 d.addCallback(_add)
826
827
828
829 d.addCallback(lambda res: self.loadConfig_Builders(builders))
830
831 d.addCallback(lambda res: self.loadConfig_status(status))
832
833
834 d.addCallback(lambda res:
835 self.scheduler_manager.updateSchedulers(schedulers))
836
837 d.addCallback(lambda res: self.loadConfig_Sources(change_sources))
838
839
840 if self.slavePortnum != slavePortnum:
841 if self.slavePort:
842 def closeSlavePort(res):
843 d1 = self.slavePort.disownServiceParent()
844 self.slavePort = None
845 return d1
846 d.addCallback(closeSlavePort)
847 if slavePortnum is not None:
848 def openSlavePort(res):
849 self.slavePort = strports.service(slavePortnum,
850 self.slaveFactory)
851 self.slavePort.setServiceParent(self)
852 d.addCallback(openSlavePort)
853 log.msg("BuildMaster listening on port %s" % slavePortnum)
854 self.slavePortnum = slavePortnum
855
856 log.msg("configuration update started")
857 def _done(res):
858 self.readConfig = True
859 log.msg("configuration update complete")
860 d.addCallback(_done)
861 d.addCallback(lambda res: self.botmaster.triggerNewBuildCheck())
862 d.addErrback(log.err)
863 return d
864
866 if self.db:
867 return
868
869
870 sm = schema.DBSchemaManager(db_spec, self.basedir)
871 if not sm.is_current():
872 raise exceptions.DatabaseNotReadyError, textwrap.dedent("""
873 The Buildmaster database needs to be upgraded before this version of buildbot
874 can run. Use the following command-line
875 buildbot upgrade-master path/to/master
876 to upgrade the database, and try starting the buildmaster again. You may want
877 to make a backup of your buildmaster before doing so.""")
878
879 self.db = connector.DBConnector(db_spec)
880 self.db.start()
881
882 self.botmaster.db = self.db
883 self.status.setDB(self.db)
884
885 self.db.subscribe_to("add-buildrequest",
886 self.botmaster.trigger_add_buildrequest)
887
888 sm = SchedulerManager(self, self.db, self.change_svc)
889 self.db.subscribe_to("add-change", sm.trigger_add_change)
890 self.db.subscribe_to("modify-buildset", sm.trigger_modify_buildset)
891
892 self.scheduler_manager = sm
893 sm.setServiceParent(self)
894
895
896
897
898
899 if db_poll_interval:
900
901 t1 = TimerService(db_poll_interval, sm.trigger)
902 t1.setServiceParent(self)
903 t2 = TimerService(db_poll_interval, self.botmaster.loop.trigger)
904 t2.setServiceParent(self)
905
906
907
908
910 self.db_url = db_url
911 self.db_poll_interval = db_poll_interval
912 db_spec = DBSpec.from_url(db_url, self.basedir)
913 self.loadDatabase(db_spec, db_poll_interval)
914
916
917 self.checker.users = {}
918 for s in new_slaves:
919 self.checker.addUser(s.slavename, s.password)
920 self.checker.addUser("change", "changepw")
921
922 return self.botmaster.loadConfig_Slaves(new_slaves)
923
925 if not sources:
926 log.msg("warning: no ChangeSources specified in c['change_source']")
927
928 deleted_sources = [s for s in self.change_svc if s not in sources]
929 added_sources = [s for s in sources if s not in self.change_svc]
930 log.msg("adding %d new changesources, removing %d" %
931 (len(added_sources), len(deleted_sources)))
932 dl = [self.change_svc.removeSource(s) for s in deleted_sources]
933 def addNewOnes(res):
934 [self.change_svc.addSource(s) for s in added_sources]
935 d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=0)
936 d.addCallback(addNewOnes)
937 return d
938
940 return list(self.scheduler_manager)
941
943 somethingChanged = False
944 newList = {}
945 newBuilderNames = []
946 allBuilders = self.botmaster.builders.copy()
947 for data in newBuilderData:
948 name = data['name']
949 newList[name] = data
950 newBuilderNames.append(name)
951
952
953 for oldname in self.botmaster.getBuildernames():
954 if oldname not in newList:
955 log.msg("removing old builder %s" % oldname)
956 del allBuilders[oldname]
957 somethingChanged = True
958
959 self.status.builderRemoved(oldname)
960
961
962 for name, data in newList.items():
963 old = self.botmaster.builders.get(name)
964 basedir = data['builddir']
965
966 if not old:
967
968 category = data.get('category', None)
969 log.msg("adding new builder %s for category %s" %
970 (name, category))
971 statusbag = self.status.builderAdded(name, basedir, category)
972 builder = Builder(data, statusbag)
973 allBuilders[name] = builder
974 somethingChanged = True
975 elif old.compareToSetup(data):
976
977
978 diffs = old.compareToSetup(data)
979 log.msg("updating builder %s: %s" % (name, "\n".join(diffs)))
980
981 statusbag = old.builder_status
982 statusbag.saveYourself()
983
984
985 new_builder = Builder(data, statusbag)
986 new_builder.consumeTheSoulOfYourPredecessor(old)
987
988
989
990
991 statusbag.addPointEvent(["config", "updated"])
992
993 allBuilders[name] = new_builder
994 somethingChanged = True
995 else:
996
997 log.msg("builder %s is unchanged" % name)
998 pass
999
1000
1001
1002 for builder in allBuilders.values():
1003 builder.builder_status.reconfigFromBuildmaster(self)
1004
1005
1006 if somethingChanged:
1007 sortedAllBuilders = [allBuilders[name] for name in newBuilderNames]
1008 d = self.botmaster.setBuilders(sortedAllBuilders)
1009 return d
1010 return None
1011
1013 dl = []
1014
1015
1016 for s in self.statusTargets[:]:
1017 if not s in status:
1018 log.msg("removing IStatusReceiver", s)
1019 d = defer.maybeDeferred(s.disownServiceParent)
1020 dl.append(d)
1021 self.statusTargets.remove(s)
1022
1023 def addNewOnes(res):
1024 for s in status:
1025 if not s in self.statusTargets:
1026 log.msg("adding IStatusReceiver", s)
1027 s.setServiceParent(self)
1028 self.statusTargets.append(s)
1029 d = defer.DeferredList(dl, fireOnOneErrback=1)
1030 d.addCallback(addNewOnes)
1031 return d
1032
1033
1037
1040
1041 - def submitBuildSet(self, builderNames, ss, reason, props=None, now=False):
1054
1059
1060
1076
1077 components.registerAdapter(Control, BuildMaster, interfaces.IControl)
1078