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
317 sl = self.slaves[slavename]
318 if not sl:
319 return None
320
321
322 sl.recordConnectTime()
323
324 if sl.isConnected():
325
326
327
328
329
330
331
332 log.msg("duplicate slave %s; rejecting new slave and pinging old" % sl.slavename)
333
334
335
336
337 old_tport = sl.slave.broker.transport
338 new_tport = mind.broker.transport
339 log.msg("old slave was connected from", old_tport.getPeer())
340 log.msg("new slave is from", new_tport.getPeer())
341
342
343
344 d = sl.slave.callRemote("print", "master got a duplicate connection; keeping this one")
345
346
347
348
349 def kill():
350 log.msg("killing new slave on", new_tport.getPeer())
351 new_tport.loseConnection()
352 reactor.callLater(5, kill)
353 class DummyAvatar(pb.Avatar):
354 def attached(self, *args):
355 pass
356 def detached(self, *args):
357 pass
358 return DummyAvatar()
359
360 return sl
361
366
368 for b in self.builders.values():
369 b.builder_status.addPointEvent(["master", "shutdown"])
370 b.builder_status.saveYourself()
371 return service.MultiService.stopService(self)
372
374 """Convert a Lock identifier into an actual Lock instance.
375 @param lockid: a locks.MasterLock or locks.SlaveLock instance
376 @return: a locks.RealMasterLock or locks.RealSlaveLock instance
377 """
378 assert isinstance(lockid, (locks.MasterLock, locks.SlaveLock))
379 if not lockid in self.locks:
380 self.locks[lockid] = lockid.lockClass(lockid)
381
382
383
384
385 return self.locks[lockid]
386
387
388
389
390
396
405
410
411 - def perspective_fakeChange(self, file, revision=None, who="fakeUser",
412 branch=None, repository="",
413 project=""):
419
421 builder = self.botmaster.builders.get(buildername)
422 if not builder: return
423 if state == "offline":
424 builder.statusbag.currentlyOffline()
425 if state == "idle":
426 builder.statusbag.currentlyIdle()
427 if state == "waiting":
428 builder.statusbag.currentlyWaiting(now()+10)
429 if state == "building":
430 builder.statusbag.currentlyBuilding(None)
435 print "saying something on IRC"
436 from buildbot.status import words
437 for s in self.master:
438 if isinstance(s, words.IRC):
439 bot = s.f
440 for channel in bot.channels:
441 print " channel", channel
442 bot.p.msg(channel, "Ow, quit it")
443
446
448 implements(portal.IRealm)
449
452
454 self.names[name] = afactory
457
459 assert interface == pb.IPerspective
460 afactory = self.names.get(avatarID)
461 if afactory:
462 p = afactory.getPerspective()
463 elif avatarID == "change":
464 raise ValueError("no PBChangeSource installed")
465 elif avatarID == "debug":
466 p = DebugPerspective()
467 p.master = self.master
468 p.botmaster = self.botmaster
469 elif avatarID == "statusClient":
470 p = self.statusClientService.getPerspective()
471 else:
472
473
474 p = self.botmaster.getPerspective(mind, avatarID)
475
476 if not p:
477 raise ValueError("no perspective for '%s'" % avatarID)
478
479 d = defer.maybeDeferred(p.attached, mind)
480 def _avatarAttached(_, mind):
481 return (pb.IPerspective, p, lambda: p.detached(mind))
482 d.addCallback(_avatarAttached, mind)
483 return d
484
485
486
487
489
491 '''holds log rotation parameters (for WebStatus)'''
493 self.rotateLength = 1 * 1000 * 1000
494 self.maxRotatedFiles = 10
495
497 debug = 0
498 manhole = None
499 debugPassword = None
500 projectName = "(unspecified)"
501 projectURL = None
502 buildbotURL = None
503 change_svc = None
504 properties = Properties()
505
506 - def __init__(self, basedir, configFileName="master.cfg", db_spec=None):
507 service.MultiService.__init__(self)
508 self.setName("buildmaster")
509 self.basedir = basedir
510 self.configFileName = configFileName
511
512
513
514
515 dispatcher = Dispatcher()
516 dispatcher.master = self
517 self.dispatcher = dispatcher
518 self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
519
520 p = portal.Portal(dispatcher)
521 p.registerChecker(self.checker)
522 self.slaveFactory = pb.PBServerFactory(p)
523 self.slaveFactory.unsafeTracebacks = True
524
525 self.slavePortnum = None
526 self.slavePort = None
527
528 self.change_svc = ChangeManager()
529 self.change_svc.setServiceParent(self)
530 self.dispatcher.changemaster = self.change_svc
531
532 try:
533 hostname = os.uname()[1]
534 except AttributeError:
535 hostname = "?"
536 self.master_name = "%s:%s" % (hostname, os.path.abspath(self.basedir))
537 self.master_incarnation = "pid%d-boot%d" % (os.getpid(), time.time())
538
539 self.botmaster = BotMaster()
540 self.botmaster.setName("botmaster")
541 self.botmaster.setMasterName(self.master_name, self.master_incarnation)
542 self.botmaster.setServiceParent(self)
543 self.dispatcher.botmaster = self.botmaster
544
545 self.status = Status(self.botmaster, self.basedir)
546 self.statusTargets = []
547
548 self.db = None
549 self.db_url = None
550 self.db_poll_interval = _Unset
551 if db_spec:
552 self.loadDatabase(db_spec)
553
554 self.readConfig = False
555
556
557 self.log_rotation = LogRotation()
558
560 service.MultiService.startService(self)
561 if not self.readConfig:
562
563
564
565
566
567 self.loadTheConfigFile()
568 if hasattr(signal, "SIGHUP"):
569 signal.signal(signal.SIGHUP, self._handleSIGHUP)
570 for b in self.botmaster.builders.values():
571 b.builder_status.addPointEvent(["master", "started"])
572 b.builder_status.saveYourself()
573
576
578 """
579 @rtype: L{buildbot.status.builder.Status}
580 """
581 return self.status
582
584 if not configFile:
585 configFile = os.path.join(self.basedir, self.configFileName)
586
587 log.msg("Creating BuildMaster -- buildbot.version: %s" % buildbot.version)
588 log.msg("loading configuration from %s" % configFile)
589 configFile = os.path.expanduser(configFile)
590
591 try:
592 f = open(configFile, "r")
593 except IOError, e:
594 log.msg("unable to open config file '%s'" % configFile)
595 log.msg("leaving old configuration in place")
596 log.err(e)
597 return
598
599 try:
600 d = self.loadConfig(f)
601 except:
602 log.msg("error during loadConfig")
603 log.err()
604 log.msg("The new config file is unusable, so I'll ignore it.")
605 log.msg("I will keep using the previous config file instead.")
606 return
607 f.close()
608 return d
609
610 - def loadConfig(self, f, check_synchronously_only=False):
611 """Internal function to load a specific configuration file. Any
612 errors in the file will be signalled by raising an exception.
613
614 If check_synchronously_only=True, I will return (with None)
615 synchronously, after checking the config file for sanity, or raise an
616 exception. I may also emit some DeprecationWarnings.
617
618 If check_synchronously_only=False, I will return a Deferred that
619 fires (with None) when the configuration changes have been completed.
620 This may involve a round-trip to each buildslave that was involved."""
621
622 localDict = {'basedir': os.path.expanduser(self.basedir)}
623 try:
624 exec f in localDict
625 except:
626 log.msg("error while parsing config file")
627 raise
628
629 try:
630 config = localDict['BuildmasterConfig']
631 except KeyError:
632 log.err("missing config dictionary")
633 log.err("config file must define BuildmasterConfig")
634 raise
635
636 known_keys = ("slaves", "change_source",
637 "schedulers", "builders", "mergeRequests",
638 "slavePortnum", "debugPassword", "logCompressionLimit",
639 "manhole", "status", "projectName", "projectURL",
640 "buildbotURL", "properties", "prioritizeBuilders",
641 "eventHorizon", "buildCacheSize", "changeCacheSize",
642 "logHorizon", "buildHorizon", "changeHorizon",
643 "logMaxSize", "logMaxTailSize", "logCompressionMethod",
644 "db_url", "multiMaster", "db_poll_interval",
645 )
646 for k in config.keys():
647 if k not in known_keys:
648 log.msg("unknown key '%s' defined in config dictionary" % k)
649
650 try:
651
652 schedulers = config['schedulers']
653 builders = config['builders']
654 slavePortnum = config['slavePortnum']
655
656
657
658
659 db_url = config.get("db_url", "sqlite:///state.sqlite")
660 db_poll_interval = config.get("db_poll_interval", None)
661 debugPassword = config.get('debugPassword')
662 manhole = config.get('manhole')
663 status = config.get('status', [])
664 projectName = config.get('projectName')
665 projectURL = config.get('projectURL')
666 buildbotURL = config.get('buildbotURL')
667 properties = config.get('properties', {})
668 buildCacheSize = config.get('buildCacheSize', None)
669 changeCacheSize = config.get('changeCacheSize', None)
670 eventHorizon = config.get('eventHorizon', 50)
671 logHorizon = config.get('logHorizon', None)
672 buildHorizon = config.get('buildHorizon', None)
673 logCompressionLimit = config.get('logCompressionLimit', 4*1024)
674 if logCompressionLimit is not None and not \
675 isinstance(logCompressionLimit, int):
676 raise ValueError("logCompressionLimit needs to be bool or int")
677 logCompressionMethod = config.get('logCompressionMethod', "bz2")
678 if logCompressionMethod not in ('bz2', 'gz'):
679 raise ValueError("logCompressionMethod needs to be 'bz2', or 'gz'")
680 logMaxSize = config.get('logMaxSize')
681 if logMaxSize is not None and not \
682 isinstance(logMaxSize, int):
683 raise ValueError("logMaxSize needs to be None or int")
684 logMaxTailSize = config.get('logMaxTailSize')
685 if logMaxTailSize is not None and not \
686 isinstance(logMaxTailSize, int):
687 raise ValueError("logMaxTailSize needs to be None or int")
688 mergeRequests = config.get('mergeRequests')
689 if mergeRequests is not None and not callable(mergeRequests):
690 raise ValueError("mergeRequests must be a callable")
691 prioritizeBuilders = config.get('prioritizeBuilders')
692 if prioritizeBuilders is not None and not callable(prioritizeBuilders):
693 raise ValueError("prioritizeBuilders must be callable")
694 changeHorizon = config.get("changeHorizon")
695 if changeHorizon is not None and not isinstance(changeHorizon, int):
696 raise ValueError("changeHorizon needs to be an int")
697
698 multiMaster = config.get("multiMaster", False)
699
700 except KeyError:
701 log.msg("config dictionary is missing a required parameter")
702 log.msg("leaving old configuration in place")
703 raise
704
705 if "sources" in config:
706 m = ("c['sources'] is deprecated as of 0.7.6 and is no longer "
707 "accepted in >= 0.8.0 . Please use c['change_source'] instead.")
708 raise KeyError(m)
709
710 if "bots" in config:
711 m = ("c['bots'] is deprecated as of 0.7.6 and is no longer "
712 "accepted in >= 0.8.0 . Please use c['slaves'] instead.")
713 raise KeyError(m)
714
715 slaves = config.get('slaves', [])
716 if "slaves" not in config:
717 log.msg("config dictionary must have a 'slaves' key")
718 log.msg("leaving old configuration in place")
719 raise KeyError("must have a 'slaves' key")
720
721 if changeHorizon is not None:
722 self.change_svc.changeHorizon = changeHorizon
723
724 change_source = config.get('change_source', [])
725 if isinstance(change_source, (list, tuple)):
726 change_sources = change_source
727 else:
728 change_sources = [change_source]
729
730
731 for s in slaves:
732 assert interfaces.IBuildSlave.providedBy(s)
733 if s.slavename in ("debug", "change", "status"):
734 raise KeyError(
735 "reserved name '%s' used for a bot" % s.slavename)
736 if config.has_key('interlocks'):
737 raise KeyError("c['interlocks'] is no longer accepted")
738 assert self.db_url is None or db_url == self.db_url, \
739 "Cannot change db_url after master has started"
740 assert db_poll_interval is None or isinstance(db_poll_interval, int), \
741 "db_poll_interval must be an integer: seconds between polls"
742 assert self.db_poll_interval is _Unset or db_poll_interval == self.db_poll_interval, \
743 "Cannot change db_poll_interval after master has started"
744
745 assert isinstance(change_sources, (list, tuple))
746 for s in change_sources:
747 assert interfaces.IChangeSource(s, None)
748
749
750 errmsg = "c['schedulers'] must be a list of Scheduler instances"
751 assert isinstance(schedulers, (list, tuple)), errmsg
752 for s in schedulers:
753 assert interfaces.IScheduler(s, None), errmsg
754 assert isinstance(status, (list, tuple))
755 for s in status:
756 assert interfaces.IStatusReceiver(s, None)
757
758 slavenames = [s.slavename for s in slaves]
759 buildernames = []
760 dirnames = []
761
762
763 builders_dicts = []
764 for b in builders:
765 if isinstance(b, BuilderConfig):
766 builders_dicts.append(b.getConfigDict())
767 elif type(b) is dict:
768 builders_dicts.append(b)
769 else:
770 raise ValueError("builder %s is not a BuilderConfig object (or a dict)" % b)
771 builders = builders_dicts
772
773 for b in builders:
774 if b.has_key('slavename') and b['slavename'] not in slavenames:
775 raise ValueError("builder %s uses undefined slave %s" \
776 % (b['name'], b['slavename']))
777 for n in b.get('slavenames', []):
778 if n not in slavenames:
779 raise ValueError("builder %s uses undefined slave %s" \
780 % (b['name'], n))
781 if b['name'] in buildernames:
782 raise ValueError("duplicate builder name %s"
783 % b['name'])
784 buildernames.append(b['name'])
785
786
787 if b['name'].startswith("_"):
788 errmsg = ("builder names must not start with an "
789 "underscore: " + b['name'])
790 log.err(errmsg)
791 raise ValueError(errmsg)
792
793
794
795 b.setdefault('builddir', safeTranslate(b['name']))
796 b.setdefault('slavebuilddir', b['builddir'])
797 b.setdefault('buildHorizon', buildHorizon)
798 b.setdefault('logHorizon', logHorizon)
799 b.setdefault('eventHorizon', eventHorizon)
800 if b['builddir'] in dirnames:
801 raise ValueError("builder %s reuses builddir %s"
802 % (b['name'], b['builddir']))
803 dirnames.append(b['builddir'])
804
805 unscheduled_buildernames = buildernames[:]
806 schedulernames = []
807 for s in schedulers:
808 for b in s.listBuilderNames():
809
810 if not multiMaster:
811 assert b in buildernames, \
812 "%s uses unknown builder %s" % (s, b)
813 if b in unscheduled_buildernames:
814 unscheduled_buildernames.remove(b)
815
816 if s.name in schedulernames:
817 msg = ("Schedulers must have unique names, but "
818 "'%s' was a duplicate" % (s.name,))
819 raise ValueError(msg)
820 schedulernames.append(s.name)
821
822
823 if not multiMaster and unscheduled_buildernames:
824 log.msg("Warning: some Builders have no Schedulers to drive them:"
825 " %s" % (unscheduled_buildernames,))
826
827
828
829 lock_dict = {}
830 for b in builders:
831 for l in b.get('locks', []):
832 if isinstance(l, locks.LockAccess):
833 l = l.lockid
834 if lock_dict.has_key(l.name):
835 if lock_dict[l.name] is not l:
836 raise ValueError("Two different locks (%s and %s) "
837 "share the name %s"
838 % (l, lock_dict[l.name], l.name))
839 else:
840 lock_dict[l.name] = l
841
842
843
844 for s in b['factory'].steps:
845 for l in s[1].get('locks', []):
846 if isinstance(l, locks.LockAccess):
847 l = l.lockid
848 if lock_dict.has_key(l.name):
849 if lock_dict[l.name] is not l:
850 raise ValueError("Two different locks (%s and %s)"
851 " share the name %s"
852 % (l, lock_dict[l.name], l.name))
853 else:
854 lock_dict[l.name] = l
855
856 if not isinstance(properties, dict):
857 raise ValueError("c['properties'] must be a dictionary")
858
859
860 if type(slavePortnum) is int:
861 slavePortnum = "tcp:%d" % slavePortnum
862
863 if check_synchronously_only:
864 return
865
866
867
868
869
870 d = defer.succeed(None)
871
872 self.projectName = projectName
873 self.projectURL = projectURL
874 self.buildbotURL = buildbotURL
875
876 self.properties = Properties()
877 self.properties.update(properties, self.configFileName)
878
879 self.status.logCompressionLimit = logCompressionLimit
880 self.status.logCompressionMethod = logCompressionMethod
881 self.status.logMaxSize = logMaxSize
882 self.status.logMaxTailSize = logMaxTailSize
883
884
885
886 for builder in self.botmaster.builders.values():
887 builder.builder_status.setLogCompressionLimit(logCompressionLimit)
888 builder.builder_status.setLogCompressionMethod(logCompressionMethod)
889 builder.builder_status.setLogMaxSize(logMaxSize)
890 builder.builder_status.setLogMaxTailSize(logMaxTailSize)
891
892 if mergeRequests is not None:
893 self.botmaster.mergeRequests = mergeRequests
894 if prioritizeBuilders is not None:
895 self.botmaster.prioritizeBuilders = prioritizeBuilders
896
897 self.buildCacheSize = buildCacheSize
898 self.changeCacheSize = changeCacheSize
899 self.eventHorizon = eventHorizon
900 self.logHorizon = logHorizon
901 self.buildHorizon = buildHorizon
902
903
904 d.addCallback(lambda res:
905 self.loadConfig_Database(db_url, db_poll_interval))
906
907
908
909
910 d.addCallback(lambda res: self.loadConfig_Slaves(slaves))
911
912
913 if debugPassword:
914 self.checker.addUser("debug", debugPassword)
915 self.debugPassword = debugPassword
916
917
918 if manhole != self.manhole:
919
920 if self.manhole:
921
922 d.addCallback(lambda res: self.manhole.disownServiceParent())
923 def _remove(res):
924 self.manhole = None
925 return res
926 d.addCallback(_remove)
927 if manhole:
928 def _add(res):
929 self.manhole = manhole
930 manhole.setServiceParent(self)
931 d.addCallback(_add)
932
933
934
935 d.addCallback(lambda res: self.loadConfig_Builders(builders))
936
937 d.addCallback(lambda res: self.loadConfig_status(status))
938
939
940 d.addCallback(lambda res:
941 self.scheduler_manager.updateSchedulers(schedulers))
942
943 d.addCallback(lambda res: self.loadConfig_Sources(change_sources))
944
945
946 if self.slavePortnum != slavePortnum:
947 if self.slavePort:
948 def closeSlavePort(res):
949 d1 = self.slavePort.disownServiceParent()
950 self.slavePort = None
951 return d1
952 d.addCallback(closeSlavePort)
953 if slavePortnum is not None:
954 def openSlavePort(res):
955 self.slavePort = strports.service(slavePortnum,
956 self.slaveFactory)
957 self.slavePort.setServiceParent(self)
958 d.addCallback(openSlavePort)
959 log.msg("BuildMaster listening on port %s" % slavePortnum)
960 self.slavePortnum = slavePortnum
961
962 log.msg("configuration update started")
963 def _done(res):
964 self.readConfig = True
965 log.msg("configuration update complete")
966 d.addCallback(_done)
967 d.addCallback(lambda res: self.botmaster.triggerNewBuildCheck())
968 d.addErrback(log.err)
969 return d
970
972 if self.db:
973 return
974
975
976 sm = DBSchemaManager(db_spec, self.basedir)
977 if not sm.is_current():
978 raise exceptions.DatabaseNotReadyError, textwrap.dedent("""
979 The Buildmaster database needs to be upgraded before this version of buildbot
980 can run. Use the following command-line
981 buildbot upgrade-master path/to/master
982 to upgrade the database, and try starting the buildmaster again. You may want
983 to make a backup of your buildmaster before doing so. If you are using MySQL,
984 you must specify the connector string on the upgrade-master command line:
985 buildbot upgrade-master --db=<db-connector-string> path/to/master
986 """)
987
988 self.db = connector.DBConnector(db_spec)
989 if self.changeCacheSize:
990 self.db.setChangeCacheSize(self.changeCacheSize)
991 self.db.start()
992
993 self.botmaster.db = self.db
994 self.status.setDB(self.db)
995
996 self.db.subscribe_to("add-buildrequest",
997 self.botmaster.trigger_add_buildrequest)
998
999 sm = SchedulerManager(self, self.db, self.change_svc)
1000 self.db.subscribe_to("add-change", sm.trigger_add_change)
1001 self.db.subscribe_to("modify-buildset", sm.trigger_modify_buildset)
1002
1003 self.scheduler_manager = sm
1004 sm.setServiceParent(self)
1005
1006
1007
1008
1009
1010 if db_poll_interval:
1011
1012 t1 = TimerService(db_poll_interval, sm.trigger)
1013 t1.setServiceParent(self)
1014 t2 = TimerService(db_poll_interval, self.botmaster.loop.trigger)
1015 t2.setServiceParent(self)
1016
1017
1018
1019
1021 self.db_url = db_url
1022 self.db_poll_interval = db_poll_interval
1023 db_spec = DBSpec.from_url(db_url, self.basedir)
1024 self.loadDatabase(db_spec, db_poll_interval)
1025
1027
1028 self.checker.users = {}
1029 for s in new_slaves:
1030 self.checker.addUser(s.slavename, s.password)
1031 self.checker.addUser("change", "changepw")
1032
1033 return self.botmaster.loadConfig_Slaves(new_slaves)
1034
1036 if not sources:
1037 log.msg("warning: no ChangeSources specified in c['change_source']")
1038
1039 deleted_sources = [s for s in self.change_svc if s not in sources]
1040 added_sources = [s for s in sources if s not in self.change_svc]
1041 log.msg("adding %d new changesources, removing %d" %
1042 (len(added_sources), len(deleted_sources)))
1043 dl = [self.change_svc.removeSource(s) for s in deleted_sources]
1044 def addNewOnes(res):
1045 [self.change_svc.addSource(s) for s in added_sources]
1046 d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=0)
1047 d.addCallback(addNewOnes)
1048 return d
1049
1051 return list(self.scheduler_manager)
1052
1054 somethingChanged = False
1055 newList = {}
1056 newBuilderNames = []
1057 allBuilders = self.botmaster.builders.copy()
1058 for data in newBuilderData:
1059 name = data['name']
1060 newList[name] = data
1061 newBuilderNames.append(name)
1062
1063
1064 for oldname in self.botmaster.getBuildernames():
1065 if oldname not in newList:
1066 log.msg("removing old builder %s" % oldname)
1067 del allBuilders[oldname]
1068 somethingChanged = True
1069
1070 self.status.builderRemoved(oldname)
1071
1072
1073 for name, data in newList.items():
1074 old = self.botmaster.builders.get(name)
1075 basedir = data['builddir']
1076
1077 if not old:
1078
1079 category = data.get('category', None)
1080 log.msg("adding new builder %s for category %s" %
1081 (name, category))
1082 statusbag = self.status.builderAdded(name, basedir, category)
1083 builder = Builder(data, statusbag)
1084 allBuilders[name] = builder
1085 somethingChanged = True
1086 elif old.compareToSetup(data):
1087
1088
1089 diffs = old.compareToSetup(data)
1090 log.msg("updating builder %s: %s" % (name, "\n".join(diffs)))
1091
1092 statusbag = old.builder_status
1093 statusbag.saveYourself()
1094
1095
1096 new_builder = Builder(data, statusbag)
1097 new_builder.consumeTheSoulOfYourPredecessor(old)
1098
1099
1100
1101
1102 statusbag.addPointEvent(["config", "updated"])
1103
1104 allBuilders[name] = new_builder
1105 somethingChanged = True
1106 else:
1107
1108 log.msg("builder %s is unchanged" % name)
1109 pass
1110
1111
1112
1113 for builder in allBuilders.values():
1114 builder.builder_status.reconfigFromBuildmaster(self)
1115
1116
1117 if somethingChanged:
1118 sortedAllBuilders = [allBuilders[name] for name in newBuilderNames]
1119 d = self.botmaster.setBuilders(sortedAllBuilders)
1120 return d
1121 return None
1122
1124 dl = []
1125
1126
1127 for s in self.statusTargets[:]:
1128 if not s in status:
1129 log.msg("removing IStatusReceiver", s)
1130 d = defer.maybeDeferred(s.disownServiceParent)
1131 dl.append(d)
1132 self.statusTargets.remove(s)
1133
1134 def addNewOnes(res):
1135 for s in status:
1136 if not s in self.statusTargets:
1137 log.msg("adding IStatusReceiver", s)
1138 s.setServiceParent(self)
1139 self.statusTargets.append(s)
1140 d = defer.DeferredList(dl, fireOnOneErrback=1)
1141 d.addCallback(addNewOnes)
1142 return d
1143
1144
1148
1151
1152 - def submitBuildSet(self, builderNames, ss, reason, props=None, now=False):
1165
1170
1171
1187
1188 components.registerAdapter(Control, BuildMaster, interfaces.IControl)
1189