[Buildbot-commits] buildbot/buildbot/status client.py,1.23,1.24 html.py,1.68,1.69 words.py,1.40,1.41 builder.py,1.67,1.68

Brian Warner warner at users.sourceforge.net
Fri Oct 14 19:42:42 UTC 2005


Update of /cvsroot/buildbot/buildbot/buildbot/status
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32254/buildbot/status

Modified Files:
	client.py html.py words.py builder.py 
Log Message:
Revision: arch at buildbot.sf.net--2004/buildbot--dev--0--patch-326
Creator:  Brian Warner <warner at lothar.com>

implement multiple slaves per Builder, allowing concurrent Builds

	* lots: implement multiple slaves per Builder, which means multiple
	current builds per Builder. Some highlights:
	* buildbot/interfaces.py (IBuilderStatus.getState): return a tuple
	of (state,currentBuilds) instead of (state,currentBuild)
	(IBuilderStatus.getCurrentBuilds): replace getCurrentBuild()
	(IBuildStatus.getSlavename): new method, so you can tell which
	slave got used. This only gets set when the build completes.
	(IBuildRequestStatus.getBuilds): new method

	* buildbot/process/builder.py (SlaveBuilder): add a .state
	attribute to track things like ATTACHING and IDLE and BUILDING,
	instead of..
	(Builder): .. the .slaves attribute here, which has been turned
	into a simple list of available slaves. Added a separate
	attaching_slaves list to track ones that are not yet ready for
	builds.
	(Builder.fireTestEvent): put off the test-event callback for a
	reactor turn, to make tests a bit more consistent.
	(Ping): cleaned up the slaveping a bit, now it disconnects if the
	ping fails due to an exception. This needs work, I'm worried that
	a code error could lead to a constantly re-connecting slave.
	Especially since I'm trying to move to a distinct remote_ping
	method, separate from the remote_print that we currently use.
	(BuilderControl.requestBuild): return a convenience Deferred that
	provides an IBuildStatus when the build finishes.
	(BuilderControl.ping): ping all connected slaves, only return True
	if they all respond.

	* buildbot/slave/bot.py (BuildSlave.stopService): stop trying to
	reconnect when we shut down.

	* buildbot/status/builder.py: implement new methods, convert
	one-build-at-a-time methods to handle multiple builds
	* buildbot/status/*.py: do the same in all default status targets
	* buildbot/status/html.py: report the build's slavename in the
	per-Build page, report all buildslaves on the per-Builder page

	* buildbot/test/test_run.py: update/create tests
	* buildbot/test/test_slaves.py: same
	* buildbot/test/test_scheduler.py: remove stale test

	* docs/buildbot.texinfo: document the new builder-specification
	'slavenames' parameter


Index: builder.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/status/builder.py,v
retrieving revision 1.67
retrieving revision 1.68
diff -u -d -r1.67 -r1.68
--- builder.py	14 Oct 2005 19:32:55 -0000	1.67
+++ builder.py	14 Oct 2005 19:42:40 -0000	1.68
@@ -645,6 +645,8 @@
         return self.source
     def getBuilderName(self):
         return self.builderName
+    def getBuilds(self):
+        return self.builds
 
     def subscribe(self, observer):
         self.observers.append(observer)
@@ -909,6 +911,7 @@
     text = []
     color = None
     results = None
+    slavename = None
 
     # these lists/dicts are defined here so that unserialized instances have
     # (empty) values. They are set in __init__ to new objects to make sure
@@ -1018,6 +1021,9 @@
     def getResults(self):
         return self.results
 
+    def getSlavename(self):
+        return self.slavename
+
     def getTestResults(self):
         return self.testResults
 
@@ -1094,6 +1100,9 @@
         # the world about us
         self.builder.buildStarted(self)
 
+    def setSlavename(self, slavename):
+        self.slavename = slavename
+
     def setText(self, text):
         assert type(text) in (list, tuple)
         self.text = text
@@ -1280,9 +1289,7 @@
     buildHorizon = 100 # forget builds beyond this
     stepHorizon = 50 # forget steps in builds beyond this
 
-    slavename = None
     category = None
-    currentBuild = None
     currentBigState = "offline" # or idle/waiting/interlocked/building
     nextBuildNumber = 0
     basedir = None # filled in by our parent
@@ -1291,27 +1298,30 @@
         self.name = buildername
         self.category = category
 
+        self.slavenames = []
         self.events = []
         # these three hold Events, and are used to retrieve the current
         # state of the boxes.
         self.lastBuildStatus = None
         #self.currentBig = None
         #self.currentSmall = None
+        self.currentBuilds = []
         self.pendingBuilds = []
         self.nextBuild = None
         self.watchers = []
         self.buildCache = [] # TODO: age builds out of the cache
 
     # persistence
+    # TODO: why am I not using styles.Versioned for this?
 
     def __getstate__(self):
         d = self.__dict__.copy()
         d['watchers'] = []
         del d['buildCache']
-        if self.currentBuild:
-            self.currentBuild.saveYourself()
+        for b in self.currentBuilds:
+            b.saveYourself()
             # TODO: push a 'hey, build was interrupted' event
-            del d['currentBuild']
+        del d['currentBuilds']
         del d['pendingBuilds']
         del d['currentBigState']
         del d['basedir']
@@ -1321,8 +1331,12 @@
     def __setstate__(self, d):
         self.__dict__ = d
         self.buildCache = []
+        self.currentBuilds = []
         self.pendingBuilds = []
         self.watchers = []
+        if d.has_key('slavename'):
+            self.slavenames = [self.slavename]
+            del self.slavename
         # self.basedir must be filled in by our parent
         # self.status must be filled in by our parent
 
@@ -1356,8 +1370,9 @@
             self.buildCache.pop(0)
 
     def getBuildByNumber(self, number):
-        if self.currentBuild and self.currentBuild.number == number:
-            return self.currentBuild
+        for b in self.currentBuilds:
+            if b.number == number:
+                return b
         for build in self.buildCache:
             if build.number == number:
                 return build
@@ -1387,16 +1402,16 @@
         return self.name
 
     def getState(self):
-        return (self.currentBigState, self.currentBuild)
+        return (self.currentBigState, self.currentBuilds)
 
-    def getSlave(self):
-        return self.status.getSlave(self.slavename)
+    def getSlaves(self):
+        return [self.status.getSlave(name) for name in self.slavenames]
 
     def getPendingBuilds(self):
         return self.pendingBuilds
 
-    def getCurrentBuild(self):
-        return self.currentBuild
+    def getCurrentBuilds(self):
+        return self.currentBuilds
 
     def getLastFinishedBuild(self):
         b = self.getBuild(-1)
@@ -1461,8 +1476,8 @@
 
     ## Builder interface (methods called by the Builder which feeds us)
 
-    def setSlavename(self, name):
-        self.slavename = name
+    def setSlavenames(self, names):
+        self.slavenames = names
 
     def addEvent(self, text=[], color=None):
         # this adds a duration event. When it is done, the user should call
@@ -1486,7 +1501,7 @@
         return e # for consistency, but they really shouldn't touch it
 
     def setBigState(self, state):
-        needToUpdate = state != self.currentBuild
+        needToUpdate = state != self.currentBigState
         self.currentBigState = state
         if needToUpdate:
             self.publishState()
@@ -1525,8 +1540,9 @@
 
         assert s.builder is self # paranoia
         assert s.number == self.nextBuildNumber - 1
-        self.currentBuild = s
-        self.addBuildToCache(self.currentBuild)
+        assert s not in self.currentBuilds
+        self.currentBuilds.append(s)
+        self.addBuildToCache(s)
 
         # now that the BuildStatus is prepared to answer queries, we can
         # announce the new build to all our watchers
@@ -1540,9 +1556,9 @@
                     s.subscribe(receiver)
 
     def _buildFinished(self, s):
-        assert s is self.currentBuild
-        self.currentBuild.saveYourself()
-        self.currentBuild = None
+        assert s in self.currentBuilds
+        s.saveYourself()
+        self.currentBuilds.remove(s)
 
         name = self.getName()
         results = s.getResults()

Index: client.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/status/client.py,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -d -r1.23 -r1.24
--- client.py	31 Aug 2005 01:12:06 -0000	1.23
+++ client.py	14 Oct 2005 19:42:40 -0000	1.24
@@ -72,17 +72,19 @@
         return self.b.getName()
 
     def remote_getState(self):
-        state, build = self.b.getState()
-        return (state, None, makeRemote(build)) # TODO: remove leftover ETA
+        state, builds = self.b.getState()
+        return (state,
+                None, # TODO: remove leftover ETA
+                [makeRemote(b) for b in builds])
 
-    def remote_getSlave(self):
-        return IRemote(self.b.getSlave())
+    def remote_getSlaves(self):
+        return [IRemote(s) for s in self.b.getSlaves()]
 
     def remote_getLastFinishedBuild(self):
         return makeRemote(self.b.getLastFinishedBuild())
 
-    def remote_getCurrentBuild(self):
-        return makeRemote(self.b.getCurrentBuild())
+    def remote_getCurrentBuilds(self):
+        return makeRemote(self.b.getCurrentBuilds())
 
     def remote_getBuild(self, number):
         return makeRemote(self.b.getBuild(number))

Index: html.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/status/html.py,v
retrieving revision 1.68
retrieving revision 1.69
diff -u -d -r1.68 -r1.69
--- html.py	2 Sep 2005 15:40:41 -0000	1.68
+++ html.py	14 Oct 2005 19:42:40 -0000	1.69
@@ -303,7 +303,8 @@
                 % (b.getBuilder().getName(), b.getNumber(),
                    html.escape(b.getReason())))
         if b.isFinished():
-            data += "<h2>Results:</h2>"
+            data += "<h4>Buildslave: %s</h4>\n" % html.escape(b.getSlavename())
+            data += "<h2>Results:</h2>\n"
             data += " ".join(b.getText()) + "\n"
             if b.getTestResults():
                 url = request.childLink("tests")
@@ -382,22 +383,30 @@
 
     def body(self, request):
         b = self.builder
-        slave = b.getSlave()
+        slaves = b.getSlaves()
+        connected_slaves = [s for s in slaves if s.isConnected()]
+
         data = make_row("Builder:", html.escape(b.getName()))
         b1 = b.getBuild(-1)
         if b1 is not None:
             data += make_row("Current/last build:", str(b1.getNumber()))
-        if slave.isConnected():
-            data += "\nCONNECTED (slave '%s')<br />\n" % slave.getName()
-            if slave.getAdmin():
-                data += make_row("Admin:", html.escape(slave.getAdmin()))
-            if slave.getHost():
-                data += "<span class='label'>Host info:</span>\n"
-                data += html.PRE(slave.getHost())
-        else:
-            data += "\nNOT CONNECTED (slave '%s')<br />\n" % slave.getName()
+        data += "\n<br />BUILDSLAVES<br />\n"
+        data += "<ol>\n"
+        for slave in slaves:
+            data += "<li><b>%s</b>: " % html.escape(slave.getName())
+            if slave.isConnected():
+                data += "CONNECTED\n"
+                if slave.getAdmin():
+                    data += make_row("Admin:", html.escape(slave.getAdmin()))
+                if slave.getHost():
+                    data += "<span class='label'>Host info:</span>\n"
+                    data += html.PRE(slave.getHost())
+            else:
+                data += ("NOT CONNECTED\n")
+            data += "</li>\n"
+        data += "</ol>\n"
 
-        if self.control is not None and slave.isConnected():
+        if self.control is not None and connected_slaves:
             forceURL = urllib.quote(request.childLink("force"))
             data += (
                 """
@@ -414,7 +423,7 @@
                 """) % {"forceURL": forceURL}
         elif self.control is not None:
             data += """
-            <p>This slave appears to be offline, so it's not possible
+            <p>All buildslaves appear to be offline, so it's not possible
             to force this build to execute at this time.</p>
             """
 
@@ -422,7 +431,7 @@
             pingURL = urllib.quote(request.childLink("ping"))
             data += """
             <form action="%s" class='command pingbuilder'>
-            <p>To ping a builder, push the 'Ping' button</p>
+            <p>To ping the buildslave(s), push the 'Ping' button</p>
 
             <input type="submit" value="Ping Builder" />
             </form>
@@ -684,15 +693,16 @@
 
     def getBox(self, status):
         # getState() returns offline, idle, or building
-        state, build = self.original.getState()
+        state, builds = self.original.getState()
         color = "white"
         if state == "building":
             color = "yellow"
             text = ["building"]
-            if build:
-                eta = build.getETA()
-                if eta:
-                    text.extend(self.formatETA(eta))
+            if builds:
+                for b in builds:
+                    eta = b.getETA()
+                    if eta:
+                        text.extend(self.formatETA(eta))
         elif state == "offline":
             color = "red"
             text = ["offline"]
@@ -1013,7 +1023,7 @@
         for b in builders:
             text = ""
             color = "#ca88f7"
-            state, build = b.getState()
+            state, builds = b.getState()
             if state != "offline":
                 text += "%s<br />\n" % state #b.getCurrentBig().text[0]
             else:

Index: words.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/status/words.py,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -d -r1.40 -r1.41
--- words.py	1 Sep 2005 20:53:21 -0000	1.40
+++ words.py	14 Oct 2005 19:42:40 -0000	1.41
@@ -242,20 +242,21 @@
             raise UsageError("try 'watch <builder>'")
         which = args[0]
         b = self.getBuilder(which)
-        build = b.getCurrentBuild()
-        if not build:
-            self.reply(reply, "there is no build currently running")
+        builds = b.getCurrentBuilds()
+        if not builds:
+            self.reply(reply, "there are no builds currently running")
             return
-        assert not build.isFinished()
-        d = build.waitUntilFinished()
-        d.addCallback(self.buildFinished, reply)
-        r = "watching build %s #%d until it finishes" \
-            % (which, build.getNumber())
-        eta = build.getETA()
-        if eta is not None:
-            r += " [%s]" % self.convertTime(eta)
-        r += ".."
-        self.reply(reply, r)
+        for build in builds:
+            assert not build.isFinished()
+            d = build.waitUntilFinished()
+            d.addCallback(self.buildFinished, reply)
+            r = "watching build %s #%d until it finishes" \
+                % (which, build.getNumber())
+            eta = build.getETA()
+            if eta is not None:
+                r += " [%s]" % self.convertTime(eta)
+            r += ".."
+            self.reply(reply, r)
     command_WATCH.usage = "watch <which> - announce the completion of an active build"
 
     def buildFinished(self, b, reply):
@@ -331,25 +332,27 @@
 
         # find an in-progress build
         builderstatus = self.getBuilder(which)
-        buildstatus = builderstatus.getCurrentBuild()
-        if not buildstatus:
+        builds = builderstatus.getCurrentBuilds()
+        if not builds:
             self.reply(reply, "sorry, no build is currently running")
             return
-        num = buildstatus.getNumber()
+        for build in builds:
+            num = build.getNumber()
 
-        # obtain the BuildControl object
-        buildcontrol = buildercontrol.getBuild(num)
+            # obtain the BuildControl object
+            buildcontrol = buildercontrol.getBuild(num)
 
-        # make it stop
-        buildcontrol.stopBuild(r)
+            # make it stop
+            buildcontrol.stopBuild(r)
+
+            self.reply(reply, "build %d interrupted" % num)
 
-        self.reply(reply, "build %d interrupted" % num)
     command_STOP.usage = "stop build <which> <reason> - Stop a running build"
 
     def emit_status(self, reply, which):
         b = self.getBuilder(which)
         str = "%s: " % which
-        state, build = b.getState()
+        state, builds = b.getState()
         str += state
         if state == "idle":
             last = b.getLastFinishedBuild()
@@ -358,13 +361,15 @@
                 str += ", last build %s secs ago: %s" % \
                        (int(util.now() - finished), " ".join(last.getText()))
         if state == "building":
-            build = b.getCurrentBuild()
-            if build:
+            t = []
+            for build in builds:
                 step = build.getCurrentStep()
-                str += " (%s)" % " ".join(step.getText())
+                s = "(%s)" % " ".join(step.getText())
                 ETA = build.getETA()
                 if ETA is not None:
-                    str += " [ETA %s]" % self.convertTime(ETA)
+                    s += " [ETA %s]" % self.convertTime(ETA)
+                t.append(s)
+            str += ", ".join(t)
         self.reply(reply, str)
 
     def emit_last(self, reply, which):





More information about the Commits mailing list