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