[CalendarServer-changes] [13925] CalendarServer/trunk/calendarserver/tap

source_changes at macosforge.org source_changes at macosforge.org
Fri Aug 29 11:44:34 PDT 2014


Revision: 13925
          http://trac.calendarserver.org//changeset/13925
Author:   sagen at apple.com
Date:     2014-08-29 11:44:34 -0700 (Fri, 29 Aug 2014)
Log Message:
-----------
If pre-flight checks don't pass, shut down more gracefully

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tap/test/test_util.py
    CalendarServer/trunk/calendarserver/tap/util.py

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2014-08-29 18:44:34 UTC (rev 13925)
@@ -127,7 +127,7 @@
     checkDirectories, getRootResource,
     oracleConnectorFromConfig, pgConnectorFromConfig,
     pgServiceFromConfig, getDBPool, MemoryLimitService,
-    storeFromConfig, getSSLPassphrase, PreFlightChecksStep
+    storeFromConfig, getSSLPassphrase, preFlightChecks
 )
 try:
     from calendarserver.version import version
@@ -1406,8 +1406,7 @@
 
         uid, gid = getSystemIDs(config.UserName, config.GroupName)
         return self.storageService(
-            toolServiceCreator, None, uid=uid, gid=gid, directory=None,
-            preFlightChecks=False
+            toolServiceCreator, None, uid=uid, gid=gid, directory=None
         )
 
 
@@ -1445,7 +1444,7 @@
 
         uid, gid = getSystemIDs(config.UserName, config.GroupName)
         svc = self.storageService(
-            agentServiceCreator, None, uid=uid, gid=gid, preFlightChecks=False
+            agentServiceCreator, None, uid=uid, gid=gid
         )
         agentLoggingService = ErrorLoggingMultiService(
             config.ErrorLogEnabled,
@@ -1458,8 +1457,7 @@
 
 
     def storageService(
-        self, createMainService, logObserver, uid=None, gid=None, directory=None,
-        preFlightChecks=True
+        self, createMainService, logObserver, uid=None, gid=None, directory=None
     ):
         """
         If necessary, create a service to be started used for storage; for
@@ -1586,11 +1584,6 @@
                     )
                 )
 
-                if preFlightChecks:
-                    pps.addStep(
-                        PreFlightChecksStep(config)
-                    )
-
                 pps.addStep(
                     UpgradeReleaseLockStep(store)
                 )
@@ -1650,6 +1643,7 @@
         Create a master service to coordinate a multi-process configuration,
         spawning subprocesses that use L{makeService_Slave} to perform work.
         """
+
         s = ErrorLoggingMultiService(
             config.ErrorLogEnabled,
             config.ErrorLogFile,
@@ -1657,6 +1651,18 @@
             config.ErrorLogMaxRotatedFiles
         )
 
+        # Perform early pre-flight checks.  If this returns True, continue on.
+        # Otherwise one of two things will happen:
+        #   A) preFlightChecks( ) will have registered an "after startup" system
+        #      event trigger to request a shutdown via the
+        #      ServiceDisablingProgram, and we want to return our
+        #      ErrorLoggingMultiService so that logging is set up enough to
+        #      emit our reason for shutting down into the error.log.
+        #   B) preFlightChecks( ) will sys.exit(1) if there is not a
+        #      ServiceDisablingProgram configured.
+        if not preFlightChecks(config):
+            return s
+
         # Add a service to re-exec the master when it receives SIGHUP
         ReExecService(config.PIDFile).setServiceParent(s)
 

Modified: CalendarServer/trunk/calendarserver/tap/test/test_util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_util.py	2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/test/test_util.py	2014-08-29 18:44:34 UTC (rev 13925)
@@ -14,16 +14,15 @@
 # limitations under the License.
 ##
 
-import OpenSSL
 from calendarserver.tap.util import (
-    MemoryLimitService, Stepper, PreFlightChecksStep
+    MemoryLimitService, Stepper, verifyTLSCertificate
 )
 from twistedcaldav.util import computeProcessCount
 from twistedcaldav.test.util import TestCase
 from twisted.internet.task import Clock
 from twisted.internet.defer import succeed, inlineCallbacks
 from twisted.python.filepath import FilePath
-from twistedcaldav.config import ConfigDict, ConfigurationError
+from twistedcaldav.config import ConfigDict
 
 
 class ProcessCountTestCase(TestCase):
@@ -236,54 +235,41 @@
             ['one success', 'two failure', 'three success', 'four failure'])
 
 
-class PreFlightChecksStepTestCase(TestCase):
+class PreFlightChecksTestCase(TestCase):
     """
     Verify that missing, empty, or bogus TLS Certificates are detected
     """
 
-    @inlineCallbacks
     def test_missingCertificate(self):
-        step = PreFlightChecksStep(
+        success, reason = verifyTLSCertificate(
             ConfigDict(
                 {
                     "SSLCertificate": "missing",
                 }
             )
         )
-        try:
-            yield step.stepWithResult(None)
-        except ConfigurationError as e:
-            self.assertTrue("Missing" in str(e))
-        else:
-            self.fail("Did not raise ConfigurationError")
+        self.assertFalse(success)
 
 
-    @inlineCallbacks
     def test_emptyCertificate(self):
         certFilePath = FilePath(self.mktemp())
         certFilePath.setContent("")
-        step = PreFlightChecksStep(
+        success, reason = verifyTLSCertificate(
             ConfigDict(
                 {
                     "SSLCertificate": certFilePath.path,
                 }
             )
         )
-        try:
-            yield step.stepWithResult(None)
-        except ConfigurationError as e:
-            self.assertTrue("Empty" in str(e))
-        else:
-            self.fail("Did not raise ConfigurationError")
+        self.assertFalse(success)
 
 
-    @inlineCallbacks
     def test_bogusCertificate(self):
         certFilePath = FilePath(self.mktemp())
         certFilePath.setContent("bogus")
         keyFilePath = FilePath(self.mktemp())
         keyFilePath.setContent("bogus")
-        step = PreFlightChecksStep(
+        success, reason = verifyTLSCertificate(
             ConfigDict(
                 {
                     "SSLCertificate": certFilePath.path,
@@ -294,9 +280,4 @@
                 }
             )
         )
-        try:
-            yield step.stepWithResult(None)
-        except OpenSSL.SSL.Error:
-            pass
-        else:
-            self.fail("Did not raise OpenSSL.SSL.Error")
+        self.assertFalse(success)

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2014-08-29 18:44:34 UTC (rev 13925)
@@ -20,12 +20,12 @@
 """
 
 __all__ = [
+    "FakeRequest",
+    "getDBPool",
     "getRootResource",
-    "getDBPool",
-    "FakeRequest",
+    "getSSLPassphrase",
     "MemoryLimitService",
-    "PreFlightChecksStep",
-    "getSSLPassphrase",
+    "preFlightChecks",
 ]
 
 import errno
@@ -34,6 +34,7 @@
 import psutil
 from socket import fromfd, AF_UNIX, SOCK_STREAM, socketpair
 from subprocess import Popen, PIPE
+import sys
 
 
 from twext.internet.ssl import ChainingOpenSSLContextFactory
@@ -1114,63 +1115,108 @@
         return self.deferred
 
 
-class PreFlightChecksStep(object):
+def requestShutdown(programPath, reason):
     """
-    A place to make any other checks before finishing up the
-    PreProcessingService.
-    """
+    Log the shutdown reason and call the shutdown-requesting program.
 
-    def __init__(self, config):
-        self.config = config
+    In the case the service is spawned by launchd (or equivalent), if our
+    service decides it needs to shut itself down, because of a misconfiguration,
+    for example, we can't just exit.  We may need to go through the system
+    machinery to unload our job, manage reverse proxies, update admin UI, etc.
+    Therefore you can configure the ServiceDisablingProgram plist key to point
+    to a program to run which will stop our service.
 
+    @param programPath: the full path to a program to call (with no args)
+    @type programPath: C{str}
+    @param reason: a shutdown reason to log
+    @type reason: C{str}
+    """
+    log.error("Shutting down Calendar and Contacts server")
+    log.error(reason)
+    Popen(
+        args=[config.ServiceDisablingProgram],
+        stdout=PIPE,
+        stderr=PIPE,
+    ).communicate()
 
-    def stepWithResult(self, result):
-        self.verifyTLSCertificate()
-        return succeed(None)
 
+def preFlightChecks(config):
+    """
+    Perform checks prior to spawning any processes.  Returns True if the checks
+    are ok, False if they don't and we have a ServiceDisablingProgram configured.
+    Otherwise exits.
+    """
 
-    def verifyTLSCertificate(self):
-        """
-        If a TLS certificate is configured, make sure it exists, is non empty,
-        and that it's valid.
-        """
+    success, reason = verifyTLSCertificate(config)
 
-        if self.config.SSLCertificate:
-            if not os.path.exists(self.config.SSLCertificate):
-                log.error(
-                    "The configured TLS certificate ({cert}) is missing",
-                    cert=self.config.SSLCertificate
+    if not success:
+        if config.ServiceDisablingProgram:
+            # If pre-flight checks fail, we don't want launchd to
+            # repeatedly launch us, we want our job to get unloaded.
+            # If the config.ServiceDisablingProgram is assigned and exists
+            # we schedule it to run after startService finishes.
+            # Its job is to carry out the platform-specific tasks of disabling
+            # the service.
+            if os.path.exists(config.ServiceDisablingProgram):
+                addSystemEventTrigger(
+                    "after", "startup",
+                    requestShutdown, config.ServiceDisablingProgram, reason
                 )
-                raise ConfigurationError("Missing certificate file")
+            return False
+
         else:
-            return
+            sys.exit(1)
 
-        length = os.stat(self.config.SSLCertificate).st_size
-        if length == 0:
-                log.error(
-                    "The configured TLS certificate ({cert}) is empty",
-                    cert=self.config.SSLCertificate
+    return True
+
+
+def verifyTLSCertificate(config):
+    """
+    If a TLS certificate is configured, make sure it exists, is non empty,
+    and that it's valid.
+    """
+
+    if config.SSLCertificate:
+        if not os.path.exists(config.SSLCertificate):
+            message = (
+                "The configured TLS certificate ({cert}) is missing".format(
+                    cert=config.SSLCertificate
                 )
-                raise ConfigurationError("Empty certificate file")
+            )
+            return False, message
+    else:
+        return True, "TLS disabled"
 
-        try:
-            ChainingOpenSSLContextFactory(
-                self.config.SSLPrivateKey,
-                self.config.SSLCertificate,
-                certificateChainFile=self.config.SSLAuthorityChain,
-                passwdCallback=getSSLPassphrase,
-                sslmethod=getattr(OpenSSL.SSL, self.config.SSLMethod),
-                ciphers=self.config.SSLCiphers.strip()
+    length = os.stat(config.SSLCertificate).st_size
+    if length == 0:
+            message = (
+                "The configured TLS certificate ({cert}) is empty".format(
+                    cert=config.SSLCertificate
+                )
             )
-        except Exception as e:
-            log.error(
-                "The configured TLS certificate ({cert}) cannot be used: {reason}",
-                cert=self.config.SSLCertificate,
+            return False, message
+
+    try:
+        ChainingOpenSSLContextFactory(
+            config.SSLPrivateKey,
+            config.SSLCertificate,
+            certificateChainFile=config.SSLAuthorityChain,
+            passwdCallback=getSSLPassphrase,
+            sslmethod=getattr(OpenSSL.SSL, config.SSLMethod),
+            ciphers=config.SSLCiphers.strip()
+        )
+    except Exception as e:
+        message = (
+            "The configured TLS certificate ({cert}) cannot be used: {reason}".format(
+                cert=config.SSLCertificate,
                 reason=str(e)
             )
-            raise
+        )
+        return False, message
 
+    return True, "TLS enabled"
 
+
 def getSSLPassphrase(*ignored):
 
     if not config.SSLPrivateKey:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140829/c0f38732/attachment-0001.html>


More information about the calendarserver-changes mailing list