[Buildbot-devel] [PATCH 1/4] use BuildSlave objects in configuration

Dustin J. Mitchell dustin at zmanda.com
Fri Jun 22 18:03:28 UTC 2007


2007-06-22  Dustin J. Mitchell <dustin at zmanda.com>
    This patch changes the bot configuration to use objects instead of
    tuples.  This will allow us to move some bot-specific functionality
    into BuildSlave, and also allow users to subclass BuildSlave for their
    own purposes.
    * buildbot/buildslave.py: add new BuildSlave class, suitable for use
      in master.cfg
    * buildbot/master.py: use the BotPerspective subclass, BuildSlave, 
      as a configuration source; support consumption of successors' souls
      as a reconfiguration technique
    * docs/buildbot.texinfo buildbot/scripts/sample.cfg: documentation
Index: wip/buildbot/buildslave.py
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ wip/buildbot/buildslave.py	2007-06-22 12:33:51.731773807 -0500
@@ -0,0 +1,29 @@
+import string
+
+from twisted.python import log
+
+from buildbot.master import BotPerspective
+
+class BuildSlave(BotPerspective):
+    """I represent a build slave -- a connection from a remote machine capable of
+    running builds.  I am instantiated by the configuration file, and can be
+    subclassed to add extra functionality."""
+
+    def __init__(self, name, password):
+        BotPerspective.__init__(self, name)
+        self.password = password
+
+    def consumeTheSoulOfYourSuccessor(self, new):
+        """
+        Given a new BuildSlave, configure this one identically.  Because
+        BuildSlave objects are remotely referenced, we can't replace them
+        without disconnecting the slave, yet there's no reason to do that.
+        """
+        BotPerspective.consumeTheSoulOfYourSuccessor(new)
+        self.password = new.password
+
+    def __repr__(self):
+        builders = self.botmaster.getBuildersForSlave(self.slavename)
+        return "<BuildSlave '%s', current builders: %s>" % \
+               (self.slavename,
+                string.join(map(lambda b: b.name, builders), ','))
Index: wip/buildbot/master.py
===================================================================
--- wip.orig/buildbot/master.py	2007-06-22 12:30:25.602283243 -0500
+++ wip/buildbot/master.py	2007-06-22 12:39:25.493196716 -0500
@@ -42,13 +42,21 @@
     reference to this instance. The BotMaster object is stashed as the
     .service attribute."""
 
-    def __init__(self, name, botmaster):
+    def __init__(self, name):
         self.slavename = name
-        self.botmaster = botmaster
+        self.botmaster = None # as-yet unowned
         self.slave_status = SlaveStatus(name)
         self.slave = None # a RemoteReference to the Bot, when connected
         self.slave_commands = None
 
+    def consumeTheSoulOfYourSuccessor(self, new):
+        # the reconfiguration logic should guarantee this:
+        assert self.slavename == new.slavename
+
+    def setBotmaster(self, botmaster):
+        assert not self.botmaster, "BotPerspective already has a botmaster"
+        self.botmaster = botmaster
+
     def updateSlave(self):
         """Called to add or remove builders after the slave has connected.
 
@@ -301,9 +309,9 @@
         return defer.succeed(None)
 
 
-    def addSlave(self, slavename):
-        slave = BotPerspective(slavename, self)
-        self.slaves[slavename] = slave
+    def addSlave(self, slave):
+        slave.setBotmaster(self)
+        self.slaves[slave.slavename] = slave
 
     def removeSlave(self, slavename):
         d = self.slaves[slavename].disconnect()
@@ -530,7 +538,7 @@
 
         self.statusTargets = []
 
-        self.bots = []
+        self.bots = {}
         # this ChangeMaster is a dummy, only used by tests. In the real
         # buildmaster, where the BuildMaster instance is activated
         # (startService is called) by twistd, this attribute is overwritten.
@@ -678,9 +686,14 @@
             log.msg("leaving old configuration in place")
             raise
 
+        # keep compatibility with old 'bots' format
+        if type(bots[0]) == type(()):
+            from buildbot.buildslave import BuildSlave # work around circular import
+            bots = [ BuildSlave(name, pw) for name, pw in bots ]
+
         # do some validation first
-        for name, passwd in bots:
-            if name in ("debug", "change", "status"):
+        for bot in bots:
+            if bot.slavename in ("debug", "change", "status"):
                 raise KeyError, "reserved name '%s' used for a bot" % name
         if config.has_key('interlocks'):
             raise KeyError("c['interlocks'] is no longer accepted")
@@ -698,7 +711,7 @@
         for s in status:
             assert interfaces.IStatusReceiver(s, None)
 
-        slavenames = [name for name,pw in bots]
+        slavenames = [ bot.slavename for bot in bots ]
         buildernames = []
         dirnames = []
         for b in builders:
@@ -848,19 +861,32 @@
 
     def loadConfig_Slaves(self, bots):
         # set up the Checker with the names and passwords of all valid bots
+
+        # update bot authentication
         self.checker.users = {} # violates abstraction, oh well
-        for user, passwd in bots:
-            self.checker.addUser(user, passwd)
+        for bot in bots:
+            self.checker.addUser(bot.slavename, bot.password)
         self.checker.addUser("change", "changepw")
 
         # identify new/old bots
-        old = self.bots; oldnames = [name for name,pw in old]
-        new = bots; newnames = [name for name,pw in new]
+        old = self.bots; oldnames = [ bot.slavename for bot in old ]
+        new = bots; newnames = [ bot.slavename for bot in new ]
+
         # removeSlave will hang up on the old bot
-        dl = [self.botmaster.removeSlave(name)
-              for name in oldnames if name not in newnames]
-        [self.botmaster.addSlave(name)
-         for name in newnames if name not in oldnames]
+        dl = [self.botmaster.removeSlave(bot)
+              for bot in old if bot.slavename not in newnames]
+
+        # add all of the new bots
+        [self.botmaster.addSlave(bot)
+         for bot in new if bot.slavename not in oldnames]
+
+        # and reconfigure any existing bots, and slot the *old*
+        # object into 'bots'.
+        for oldbot in old:
+            for newbot in new:
+                if oldbot.slavename == newbot.slavename:
+                    oldbot.consumeTheSoulOfYourSuccessor(newbot)
+                    bots[bots.index(newbot)] = oldbot
 
         # all done
         self.bots = bots
Index: wip/buildbot/scripts/sample.cfg
===================================================================
--- wip.orig/buildbot/scripts/sample.cfg	2007-06-22 12:30:25.602283243 -0500
+++ wip/buildbot/scripts/sample.cfg	2007-06-22 12:30:27.762215128 -0500
@@ -17,9 +17,14 @@
 ####### BUILDSLAVES
 
 # the 'bots' list defines the set of allowable buildslaves. Each element is a
-# tuple of bot-name and bot-password. These correspond to values given to the
-# buildslave's mktap invocation.
-c['bots'] = [("bot1name", "bot1passwd")]
+# BuildSlave object containing the bot-name and bot-password. These correspond to
+# values given to the buildslave's mktap invocation.  A subclass can be used here
+# to allow further bot-specific configuration.
+from buildbot.buildslave import BuildSlave
+c['bots'] = []
+c['bots'].append(
+    BuildSlave("bot1name", "bot1passwd")
+)
 
 
 # 'slavePortnum' defines the TCP port to listen on. This must match the value
Index: wip/docs/buildbot.texinfo
===================================================================
--- wip.orig/docs/buildbot.texinfo	2007-06-22 12:30:25.602283243 -0500
+++ wip/docs/buildbot.texinfo	2007-06-22 12:30:27.762215128 -0500
@@ -2214,14 +2214,16 @@
 @bcindex c['bots']
 
 The @code{c['bots']} key is a list of known buildslaves. Each
-buildslave is defined by a tuple of (slavename, slavepassword). These
+buildslave is defined by a @code{BuildSlave} instance.  The
+ at code{BuildSlave} constructor requires a botname and password.  These
 are the same two values that need to be provided to the buildslave
 administrator when they create the buildslave.
 
 @example
-c['bots'] = [('bot-solaris', 'solarispasswd'),
-             ('bot-bsd', 'bsdpasswd'),
-            ]
+from buildbot.buildslave import BuildSlave
+c['bots'] = []
+c['bots'].append(BuildSlave("bot-solaris", "solarispasswd"))
+c['bots'].append(BuildSlave("bot-bsd", "bsdpasswd"))
 @end example
 
 The slavenames must be unique, of course. The password exists to

-- 
        Dustin J. Mitchell
        Storage Software Engineer, Zmanda, Inc.
        http://www.zmanda.com/




More information about the devel mailing list