merged from default production-0.8 FIREFOX_24_0b3_BUILD1 FIREFOX_24_0b3_RELEASE
authorChris AtLee <catlee@mozilla.com>
Mon, 06 May 2013 10:01:04 -0400
branchproduction-0.8
changeset 769 0ba2f2a46a9326dc2821d619ed61da44ac2df228
parent 749 bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb (current diff)
parent 768 e76ca0833c983f32f4ca027617c046e37655a0c5 (diff)
child 840 801d11f42e07836088ada75cef023788a866e983
push id537
push usercatlee@mozilla.com
push dateMon, 06 May 2013 14:02:35 +0000
bugs685569
merged from default changeset: 782:6c8460a00c9a parent: 780:797a4d4b2c01 user: tbirdbld date: Thu Mar 28 11:05:17 2013 -0700 summary: Added THUNDERBIRD_17_0_5_RELEASE THUNDERBIRD_17_0_5_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 783:c31e1f68f47a user: tbirdbld date: Thu Mar 28 11:06:21 2013 -0700 summary: Added THUNDERBIRD_17_0_5esr_RELEASE THUNDERBIRD_17_0_5esr_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 784:ccf6abe22049 user: ffxbld date: Thu Mar 28 11:35:31 2013 -0700 summary: Added FIREFOX_17_0_5esr_RELEASE FIREFOX_17_0_5esr_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 785:4d3d4bf0e07d user: ffxbld date: Mon Apr 01 22:16:36 2013 -0400 summary: Added FENNEC_21_0b1_RELEASE FENNEC_21_0b1_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 786:1f1e51de9bfb user: ffxbld date: Mon Apr 01 22:27:55 2013 -0400 summary: Added FIREFOX_21_0b1_RELEASE FIREFOX_21_0b1_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 787:7a29a24c1e82 user: ffxbld date: Mon Apr 08 19:52:50 2013 -0400 summary: Added FIREFOX_21_0b2_RELEASE FIREFOX_21_0b2_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 788:093385d5a489 user: ffxbld date: Tue Apr 09 22:28:45 2013 -0400 summary: Added FENNEC_21_0b2_RELEASE FENNEC_21_0b2_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 789:4b668da86c47 user: tbirdbld date: Tue Apr 09 22:39:41 2013 -0400 summary: Added THUNDERBIRD_21_0b1_RELEASE THUNDERBIRD_21_0b1_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 790:facaf621d334 user: ffxbld date: Tue Apr 09 22:49:30 2013 -0400 summary: Added FIREFOX_20_0_1_RELEASE FIREFOX_20_0_1_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 791:6058a2698842 user: ffxbld date: Wed Apr 10 18:45:07 2013 -0400 summary: Added FENNEC_20_0_1_RELEASE FENNEC_20_0_1_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 792:15e66b834c67 user: ffxbld date: Fri Apr 12 22:08:11 2013 -0400 summary: Added FENNEC_21_0b2_RELEASE FENNEC_21_0b2_BUILD2 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 793:bec4144e365b user: ffxbld date: Tue Apr 16 22:57:56 2013 -0400 summary: Added FENNEC_21_0b3_RELEASE FENNEC_21_0b3_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 794:b904a84d89d3 user: ffxbld date: Tue Apr 16 23:05:15 2013 -0400 summary: Added FIREFOX_21_0b3_RELEASE FIREFOX_21_0b3_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 795:ec3fe7351d30 user: ffxbld date: Wed Apr 24 00:08:46 2013 -0400 summary: Added FENNEC_21_0b4_RELEASE FENNEC_21_0b4_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 796:969928815427 user: ffxbld date: Wed Apr 24 00:25:36 2013 -0400 summary: Added FIREFOX_21_0b4_RELEASE FIREFOX_21_0b4_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 797:c83b857a8da4 user: ffxbld date: Thu Apr 25 19:28:47 2013 -0400 summary: Added FIREFOX_21_0b5_RELEASE FIREFOX_21_0b5_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 798:19350127597d user: ffxbld date: Tue Apr 30 23:25:55 2013 -0400 summary: Added FENNEC_21_0b6_RELEASE FENNEC_21_0b6_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 799:256485fac7df user: ffxbld date: Tue Apr 30 23:42:27 2013 -0400 summary: Added FIREFOX_21_0b6_RELEASE FIREFOX_21_0b6_BUILD1 tag(s) for changeset production-0.8. DONTBUILD CLOSED TREE a=release changeset: 800:e76ca0833c98 tag: tip user: Chris AtLee <catlee@mozilla.com> date: Fri May 03 15:55:03 2013 -0400 summary: Bug 685569: backport upstream buildbot revision e94483f9 to our repo r=dustin
master/buildbot/buildslave.py
--- a/.hgtags
+++ b/.hgtags
@@ -987,8 +987,45 @@ 41fc8a9db7a0b0233c00d887b83cc237f8904c7e
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b7_RELEASE
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0b7_BUILD1
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b7_RELEASE
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0b7_BUILD1
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0_RELEASE
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FENNEC_20_0_BUILD1
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0_RELEASE
 41fc8a9db7a0b0233c00d887b83cc237f8904c7e FIREFOX_20_0_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_17_0_5_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_17_0_5_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_17_0_5esr_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_17_0_5esr_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_17_0_5esr_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_17_0_5esr_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b1_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b1_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b1_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b1_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b2_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b2_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b2_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b2_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_21_0b1_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb THUNDERBIRD_21_0b1_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_20_0_1_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_20_0_1_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_20_0_1_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_20_0_1_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b2_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b2_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b2_BUILD2
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b3_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b3_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b3_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b3_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b4_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b4_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b4_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b4_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b5_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b5_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b6_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FENNEC_21_0b6_BUILD1
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b6_RELEASE
+bccbfc2a314f928cf80d86b0c1de2a58ffdc48bb FIREFOX_21_0b6_BUILD1
--- a/master/buildbot/buildslave.py
+++ b/master/buildbot/buildslave.py
@@ -373,16 +373,20 @@ class AbstractBuildSlave(pb.Avatar, serv
         our_builders = self.botmaster.getBuildersForSlave(self.slavename)
         blist = [(b.name, b.slavebuilddir) for b in our_builders]
         d = self.slave.callRemote("setBuilderList", blist)
         return d
 
     def perspective_keepalive(self):
         pass
 
+    def perspective_shutdown(self):
+        log.msg("slave %s wants to shut down" % self.slavename)
+        self.slave_status.setGraceful(True)
+
     def addSlaveBuilder(self, sb):
         self.slavebuilders[sb.builder_name] = sb
 
     def removeSlaveBuilder(self, sb):
         try:
             del self.slavebuilders[sb.builder_name]
         except KeyError:
             pass
--- a/master/docs/installation.texinfo
+++ b/master/docs/installation.texinfo
@@ -561,20 +561,34 @@ system when spawning new processes.
 
 The default value is what python's sys.getfilesystemencoding() returns, which
 on Windows is 'mbcs', on Mac OSX is 'utf-8', and on Unix depends on your locale
 settings.
 
 If you need a different encoding, this can be changed in your build slave's
 buildbot.tac file by adding a unicode_encoding argument to BuildSlave:
 
+@item allow_shutdown
+allow_shutdown can be passed to the BuildSlave constructor in buildbot.tac.
+
+Setting allow_shutdown to 'file' will cause the buildslave to watch
+'shutdown.stamp' in basedir for updates to its mtime.  The file does not need
+to exist prior to starting the slave.
+
+Setting allow_shutdown to 'signal' will set up a SIGHUP handler to start a
+graceful shutdown.
+
+The default value is None, in which case this feature will be disabled.
+
+Both master and slave must be at least version 0.8.3 for this feature to work.
+
 @example
 s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir,
                keepalive, usepty, umask=umask, maxdelay=maxdelay,
-               unicode_encoding='utf-8')
+               unicode_encoding='utf-8', allow_shutdown='signal')
 @end example
 
 @end table
 
 @node Upgrading an Existing Buildslave
 @section Upgrading an Existing Buildslave
 
 If you have just installed a new version of Buildbot-slave, you may need to
--- a/slave/buildslave/bot.py
+++ b/slave/buildslave/bot.py
@@ -1,15 +1,16 @@
 import os.path
 import socket
 import sys
+import signal
 
 from twisted.spread import pb
 from twisted.python import log
-from twisted.internet import reactor, defer, error
+from twisted.internet import reactor, defer, error, task
 from twisted.application import service, internet
 from twisted.cred import credentials
 
 import buildslave
 from buildslave.util import now
 from buildslave.pbutil import ReconnectingPBClientFactory
 from buildslave.commands import registry, base
 
@@ -425,26 +426,35 @@ class BotFactory(ReconnectingPBClientFac
     def stopFactory(self):
         ReconnectingPBClientFactory.stopFactory(self)
         self.stopTimers()
 
 
 class BuildSlave(service.MultiService):
     def __init__(self, buildmaster_host, port, name, passwd, basedir,
                  keepalive, usePTY, keepaliveTimeout=30, umask=None,
-                 maxdelay=300, unicode_encoding=None):
+                 maxdelay=300, unicode_encoding=None, allow_shutdown=None):
         log.msg("Creating BuildSlave -- version: %s" % buildslave.version)
         self.recordHostname()
         service.MultiService.__init__(self)
         bot = Bot(basedir, usePTY, unicode_encoding=unicode_encoding)
         bot.setServiceParent(self)
         self.bot = bot
         if keepalive == 0:
             keepalive = None
         self.umask = umask
+
+        if allow_shutdown == 'signal':
+            if not hasattr(signal, 'SIGHUP'):
+                raise ValueError("Can't install signal handler")
+        elif allow_shutdown == 'file':
+            self.shutdown_file = os.path.join(basedir, 'shutdown.stamp')
+            self.shutdown_mtime = 0
+
+        self.allow_shutdown = allow_shutdown
         bf = self.bf = BotFactory(buildmaster_host, port, keepalive, keepaliveTimeout, maxdelay)
         bf.startLogin(credentials.UsernamePassword(name, passwd), client=bot)
         self.connection = c = internet.TCPClient(buildmaster_host, port, bf)
         c.setServiceParent(self)
 
     def recordHostname(self):
         "Record my hostname in twistd.hostname, for user convenience"
         log.msg("recording hostname in twistd.hostname")
@@ -453,12 +463,56 @@ class BuildSlave(service.MultiService):
         except:
             log.msg("failed - ignoring")
 
     def startService(self):
         if self.umask is not None:
             os.umask(self.umask)
         service.MultiService.startService(self)
 
+        if self.allow_shutdown == 'signal':
+            log.msg("Setting up SIGHUP handler to initiate shutdown")
+            signal.signal(signal.SIGHUP, self._handleSIGHUP)
+        elif self.allow_shutdown == 'file':
+            log.msg("Watching %s's mtime to initiate shutdown" % self.shutdown_file)
+            if os.path.exists(self.shutdown_file):
+                self.shutdown_mtime = os.path.getmtime(self.shutdown_file)
+            l = task.LoopingCall(self._checkShutdownFile)
+            l.start(interval=10)
+
     def stopService(self):
         self.bf.continueTrying = 0
         self.bf.stopTrying()
         service.MultiService.stopService(self)
+
+    def _handleSIGHUP(self, *args):
+        log.msg("Initiating shutdown because we got SIGHUP")
+        return self.gracefulShutdown()
+
+    def _checkShutdownFile(self):
+        if os.path.exists(self.shutdown_file) and \
+                os.path.getmtime(self.shutdown_file) > self.shutdown_mtime:
+            log.msg("Initiating shutdown because %s was touched" % self.shutdown_file)
+            self.gracefulShutdown()
+
+            # In case the shutdown fails, update our mtime so we don't keep
+            # trying to shutdown over and over again.
+            # We do want to be able to try again later if the master is
+            # restarted, so we'll keep monitoring the mtime.
+            self.shutdown_mtime = os.path.getmtime(self.shutdown_file)
+
+    def gracefulShutdown(self):
+        """Start shutting down"""
+        if not self.bf.perspective:
+            log.msg("No active connection, shutting down NOW")
+            reactor.stop()
+
+        log.msg("Telling the master we want to shutdown after any running builds are finished")
+        d = self.bf.perspective.callRemote("shutdown")
+        def _shutdownfailed(err):
+            if err.check(AttributeError):
+                log.msg("Master does not support slave initiated shutdown.  Upgrade master to 0.8.3 or later to use this feature.")
+            else:
+                log.msg('callRemote("shutdown") failed')
+                log.err(err)
+
+        d.addErrback(_shutdownfailed)
+        return d
--- a/slave/buildslave/test/unit/test_bot_BuildSlave.py
+++ b/slave/buildslave/test/unit/test_bot_BuildSlave.py
@@ -1,19 +1,22 @@
 import os
 import shutil
+import signal
 
 from twisted.trial import unittest
 from twisted.spread import pb
 from twisted.internet import reactor, defer
 from twisted.cred import checkers, portal
 from zope.interface import implements
 
 from buildslave import bot
 
+from mock import Mock
+
 # I don't see any simple way to test the PB equipment without actually setting
 # up a TCP connection.  This just tests that the PB code will connect and can
 # execute a basic ping.  The rest is done without TCP (or PB) in other test modules.
 
 class MasterPerspective(pb.Avatar):
     def __init__(self, on_keepalive=None):
         self.on_keepalive = on_keepalive
 
@@ -109,8 +112,95 @@ class TestBuildSlave(unittest.TestCase):
         port = self.start_master(persp, on_attachment=call_print)
         self.buildslave = bot.BuildSlave("127.0.0.1", port,
                 "testy", "westy", self.basedir,
                 keepalive=0, usePTY=False, umask=022)
         self.buildslave.startService()
 
         # and wait for the result of the print
         return d
+
+    def test_buildslave_graceful_shutdown(self):
+        """Test that running the build slave's gracefulShutdown method results
+        in a call to the master's shutdown method"""
+        d = defer.Deferred()
+
+        fakepersp = Mock()
+        called = []
+        def fakeCallRemote(*args):
+            called.append(args)
+            d1 = defer.succeed(None)
+            return d1
+        fakepersp.callRemote = fakeCallRemote
+
+        # set up to call shutdown when we are attached, and chain the results onto
+        # the deferred for the whole test
+        def call_shutdown(mind):
+            self.buildslave.bf.perspective = fakepersp
+            shutdown_d = self.buildslave.gracefulShutdown()
+            shutdown_d.addCallbacks(d.callback, d.errback)
+
+        persp = MasterPerspective()
+        port = self.start_master(persp, on_attachment=call_shutdown)
+
+        self.buildslave = bot.BuildSlave("127.0.0.1", port,
+                "testy", "westy", self.basedir,
+                keepalive=0, usePTY=False, umask=022)
+
+        self.buildslave.startService()
+
+        def check(ign):
+            self.assertEquals(called, [('shutdown',)])
+        d.addCallback(check)
+
+        return d
+
+    def test_buildslave_shutdown(self):
+        """Test watching an existing shutdown_file results in gracefulShutdown
+        being called."""
+
+        buildslave = bot.BuildSlave("127.0.0.1", 1234,
+                "testy", "westy", self.basedir,
+                keepalive=0, usePTY=False, umask=022,
+                allow_shutdown='file')
+
+        def patch(obj, attr, val):
+            old = getattr(obj, attr)
+            def cleanup():
+                setattr(obj, attr, old)
+            self.addCleanup(cleanup)
+            setattr(obj, attr, val)
+
+        # Mock out gracefulShutdown
+        buildslave.gracefulShutdown = Mock()
+
+        # Mock out os.path methods
+        exists = Mock()
+        mtime = Mock()
+
+        patch(os.path, 'exists', exists)
+        patch(os.path, 'getmtime', mtime)
+
+        # Pretend that the shutdown file doesn't exist
+        mtime.return_value = 0
+        exists.return_value = False
+
+        buildslave._checkShutdownFile()
+
+        # We shouldn't have called gracefulShutdown
+        self.assertEquals(buildslave.gracefulShutdown.call_count, 0)
+
+        # Pretend that the file exists now, with an mtime of 2
+        exists.return_value = True
+        mtime.return_value = 2
+        buildslave._checkShutdownFile()
+
+        # Now we should have changed gracefulShutdown
+        self.assertEquals(buildslave.gracefulShutdown.call_count, 1)
+
+        # Bump the mtime again, and make sure we call shutdown again
+        mtime.return_value = 3
+        buildslave._checkShutdownFile()
+        self.assertEquals(buildslave.gracefulShutdown.call_count, 2)
+
+        # Try again, we shouldn't call shutdown another time
+        buildslave._checkShutdownFile()
+        self.assertEquals(buildslave.gracefulShutdown.call_count, 2)