[CalendarServer-changes] [5390] CalendarServer/branches/users/glyph/sendfdport
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 24 08:44:13 PDT 2010
Revision: 5390
http://trac.macosforge.org/projects/calendarserver/changeset/5390
Author: glyph at apple.com
Date: 2010-03-24 08:44:13 -0700 (Wed, 24 Mar 2010)
Log Message:
-----------
refactor existing file-descriptor inheritance and add some test coverage (plus stub for new mode)
Modified Paths:
--------------
CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/caldav.py
CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/test/test_caldav.py
CalendarServer/branches/users/glyph/sendfdport/twistedcaldav/stdconfig.py
Modified: CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/caldav.py 2010-03-24 15:40:14 UTC (rev 5389)
+++ CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/caldav.py 2010-03-24 15:44:13 UTC (rev 5390)
@@ -40,7 +40,7 @@
from twisted.python.usage import Options, UsageError
from twisted.python.reflect import namedClass
from twisted.plugin import IPlugin
-from twisted.internet.reactor import callLater, spawnProcess, addSystemEventTrigger
+from twisted.internet.reactor import callLater, addSystemEventTrigger
from twisted.internet.process import ProcessExitedAlready
from twisted.internet.protocol import Protocol, Factory
from twisted.application.internet import TCPServer, UNIXServer
@@ -516,6 +516,20 @@
return service
+ def createContextFactory(self):
+ """
+ Create an SSL context factory for use with any SSL socket talking to
+ this server.
+ """
+ return ChainingOpenSSLContextFactory(
+ config.SSLPrivateKey,
+ config.SSLCertificate,
+ certificateChainFile=config.SSLAuthorityChain,
+ passwdCallback=getSSLPassphrase,
+ sslmethod=getattr(OpenSSL.SSL, config.SSLMethod),
+ )
+
+
def makeService_Slave(self, options):
#
# Change default log level to "info" as its useful to have
@@ -585,19 +599,14 @@
config.addPostUpdateHooks((updateFactory,))
- if config.InheritFDs or config.InheritSSLFDs:
+ if config.InheritSSLFDs or config.InheritFDs:
+ # Inherit sockets to call accept() on them individually.
for fd in config.InheritSSLFDs:
fd = int(fd)
try:
- contextFactory = ChainingOpenSSLContextFactory(
- config.SSLPrivateKey,
- config.SSLCertificate,
- certificateChainFile=config.SSLAuthorityChain,
- passwdCallback=getSSLPassphrase,
- sslmethod=getattr(OpenSSL.SSL, config.SSLMethod),
- )
+ contextFactory = self.createContextFactory()
except SSLError, e:
log.error("Unable to set up SSL context factory: %s" % (e,))
else:
@@ -623,7 +632,11 @@
inherit=True
).setServiceParent(service)
+ elif config.MetaFD:
+ fd = int(config.MetaFD)
+ # XXX sendmsg()-FD case
+
else: # Not inheriting, therefore we open our own:
if not config.BindAddresses:
@@ -651,13 +664,7 @@
% (bindAddress, port))
try:
- contextFactory = ChainingOpenSSLContextFactory(
- config.SSLPrivateKey,
- config.SSLCertificate,
- certificateChainFile=config.SSLAuthorityChain,
- passwdCallback=getSSLPassphrase,
- sslmethod=getattr(OpenSSL.SSL, config.SSLMethod),
- )
+ contextFactory = self.createContextFactory()
except SSLError, e:
self.log_error("Unable to set up SSL context factory: %s"
% (e,))
@@ -957,10 +964,23 @@
class TwistdSlaveProcess(object):
+ """
+ A L{TwistdSlaveProcess} is information about how to start a slave process
+ running a C{twistd} plugin, to be used by
+ L{DelayedStartupProcessMonitor.addProcessObject}.
+
+ @ivar inheritFDs: File descriptors to be inherited for calling accept() on
+ in the subprocess.
+ @type inheritFDs: C{list} of C{int}
+
+ @ivar inheritSSLFDs: File descriptors to be inherited for calling accept()
+ on in the subprocess, and speaking TLS on the resulting sockets.
+ @type inheritSSLFDs: C{list} of C{int}
+ """
prefix = "caldav"
def __init__(self, twistd, tapname, configFile, id, interfaces,
- inheritFDs=None, inheritSSLFDs=None):
+ inheritFDs=None, inheritSSLFDs=None, metaFD=None):
self.twistd = twistd
@@ -969,16 +989,42 @@
self.configFile = configFile
self.id = id
+ def emptyIfNone(x):
+ if x is None:
+ return []
+ else:
+ return x
+ self.inheritFDs = emptyIfNone(inheritFDs)
+ self.inheritSSLFDs = emptyIfNone(inheritSSLFDs)
+ self.metaFD = metaFD
- self.inheritFDs = inheritFDs
- self.inheritSSLFDs = inheritSSLFDs
-
self.interfaces = interfaces
def getName(self):
return '%s-%s' % (self.prefix, self.id)
+
+ def getFileDescriptors(self):
+ """
+ @return: a mapping of file descriptor numbers for the new (child)
+ process to file descriptor numbers in the current (master) process.
+ """
+ fds = {}
+ maybeMetaFD = []
+ if self.metaFD:
+ maybeMetaFD.append(self.metaFD)
+ for fd in self.inheritSSLFDs + self.inheritFDs + maybeMetaFD:
+ fds[fd] = fd
+ return fds
+
+
def getCommandLine(self):
+ """
+ @return: a list of command-line arguments, including the executable to
+ be used to start this subprocess.
+
+ @rtype: C{list} of C{str}
+ """
args = [sys.executable, self.twistd]
if config.UserName:
@@ -1020,6 +1066,11 @@
"-o", "InheritSSLFDs=%s" % (",".join(map(str, self.inheritSSLFDs)),)
])
+ if self.metaFD:
+ args.extend([
+ "-o", "MetaFD=%s" % (self.metaFD,)
+ ])
+
return args
@@ -1034,17 +1085,48 @@
config.ControlPort = self._port.getHost().port
class DelayedStartupProcessMonitor(procmon.ProcessMonitor):
+ """
+ A L{DelayedStartupProcessMonitor} is a L{procmon.ProcessMonitor} that
+ defers building its command lines until the service is actually ready to
+ start. It also specializes process-starting to allow for process objects
+ to
+ @ivar processObjects: a list of L{TwistdSlaveProcess} to add using
+ C{self.addProcess} when this service starts up.
+
+ @ivar _extraFDs: a mapping from process names to extra file-descriptor
+ maps. (By default, all processes will have the standard stdio mapping,
+ so all file descriptors here should be >2.) This is updated during
+ L{DelayedStartupProcessMonitor.startService}, by inspecting
+ L{TwistdSlaveProcess.getFileDescriptors}.
+
+ @ivar reactor: an L{IReactorProcess} for spawning processes, defaulting to
+ the global reactor.
+ """
+
def __init__(self, *args, **kwargs):
procmon.ProcessMonitor.__init__(self, *args, **kwargs)
# processObjects stores TwistdSlaveProcesses which need to have their
# command-lines determined just in time
self.processObjects = []
+ self._extraFDs = {}
+ from twisted.internet import reactor
+ self.reactor = reactor
+
def addProcessObject(self, process, env):
+ """
+ Add a process object to be run when this service is started.
+
+ @param env: a dictionary of environment variables.
+
+ @param process: a L{TwistdSlaveProcesses} object to be started upon
+ service startup.
+ """
self.processObjects.append((process, env))
+
def startService(self):
Service.startService(self)
@@ -1057,19 +1139,21 @@
processObject.getCommandLine(),
env=env
)
+ self._extraFDs[
+ processObject.getName()] = processObject.getFileDescriptors()
self.active = 1
delay = 0
if config.MultiProcess.StaggeredStartup.Enabled:
- delay_interval = config.MultiProcess.StaggeredStartup.Interval
+ interval = config.MultiProcess.StaggeredStartup.Interval
else:
- delay_interval = 0
+ interval = 0
for name in self.processes.keys():
if name.startswith("caldav"):
when = delay
- delay += delay_interval
+ delay += interval
else:
when = 0
callLater(when, self.startProcess, name)
@@ -1122,16 +1206,12 @@
childFDs = { 0 : "w", 1 : "r", 2 : "r" }
- # Examine args for -o InheritFDs= and -o InheritSSLFDs=
- # Add any file descriptors listed in those args to the childFDs
- # dictionary so those don't get closed across the spawn.
- for i in xrange(len(args)-1):
- if args[i] == "-o" and args[i+1].startswith("Inherit"):
- for fd in map(int, args[i+1].split("=")[1].split(",")):
- childFDs[fd] = fd
+ childFDs.update(self._extraFDs.get(name, {}))
- spawnProcess(p, args[0], args, uid=uid, gid=gid, env=env,
- childFDs=childFDs)
+ self.reactor.spawnProcess(
+ p, args[0], args, uid=uid, gid=gid, env=env,
+ childFDs=childFDs
+ )
class DelayedStartupLineLogger(object):
Modified: CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/test/test_caldav.py 2010-03-24 15:40:14 UTC (rev 5389)
+++ CalendarServer/branches/users/glyph/sendfdport/calendarserver/tap/test/test_caldav.py 2010-03-24 15:44:13 UTC (rev 5390)
@@ -21,6 +21,8 @@
from os.path import dirname, abspath
+from zope.interface import implements
+
from twisted.trial.unittest import TestCase as BaseTestCase
from twisted.python.threadable import isInIOThread
@@ -30,7 +32,8 @@
from twisted.python import log
from twisted.internet.protocol import ServerFactory
-from twisted.internet.defer import Deferred
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.interfaces import IProcessTransport, IReactorProcess
from twisted.application.service import IService
from twisted.application import internet
@@ -50,16 +53,76 @@
from twistedcaldav.directory.directory import UnknownRecordTypeError
from twistedcaldav.test.util import TestCase
-from calendarserver.tap.caldav import (CalDAVOptions, CalDAVServiceMaker,
- CalDAVService, GroupOwnedUNIXServer,
- DelayedStartupProcessMonitor,
- DelayedStartupLineLogger)
+from calendarserver.tap.caldav import (
+ CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
+ DelayedStartupProcessMonitor, DelayedStartupLineLogger, TwistdSlaveProcess
+)
# Points to top of source tree.
sourceRoot = dirname(dirname(dirname(dirname(abspath(__file__)))))
+class NotAProcessTransport(object):
+ """
+ Simple L{IProcessTransport} stub.
+ """
+ implements(IProcessTransport)
+
+ def __init__(self, processProtocol, executable, args, env, path,
+ uid, gid, usePTY, childFDs):
+ """
+ Hold on to all the attributes passed to spawnProcess.
+ """
+ self.processProtocol = processProtocol
+ self.executable = executable
+ self.args = args
+ self.env = env
+ self.path = path
+ self.uid = uid
+ self.gid = gid
+ self.usePTY = usePTY
+ self.childFDs = childFDs
+
+
+class InMemoryProcessSpawner(object):
+ """
+ Stub out L{IReactorProcess.spawnProcess} so that we can examine the
+ interaction of L{DelayedStartupProcessMonitor} and the reactor.
+ """
+ implements(IReactorProcess)
+
+ def __init__(self):
+ """
+ Create some storage to hold on to all the fake processes spawned.
+ """
+ self.processTransports = []
+ self.waiting = []
+
+ def waitForOneProcess(self):
+ """
+ Return a L{Deferred} which will fire when spawnProcess has been
+ invoked, with the L{IProcessTransport}.
+ """
+ d = Deferred()
+ self.waiting.append(d)
+ return d
+
+ def spawnProcess(self, processProtocol, executable, args=(), env={},
+ path=None, uid=None, gid=None, usePTY=0,
+ childFDs=None):
+
+ transport = NotAProcessTransport(
+ processProtocol, executable, args, env, path, uid, gid, usePTY,
+ childFDs
+ )
+ self.processTransports.append(transport)
+ if self.waiting:
+ self.waiting.pop(0).callback(transport)
+ return transport
+
+
+
class TestCalDAVOptions (CalDAVOptions):
"""
A fake implementation of CalDAVOptions that provides
@@ -798,6 +861,8 @@
class DummyProcessObject(object):
"""
Simple stub for the Process Object API that will run a test script.
+
+ This is a stand in for L{TwistdSlaveProcess}.
"""
def __init__(self, scriptname, *args):
@@ -809,9 +874,17 @@
"""
Get the command line to invoke this script.
"""
- return [sys.executable, FilePath(__file__).sibling(self.scriptname).path] + self.args
+ return [sys.executable,
+ FilePath(__file__).sibling(self.scriptname).path] + self.args
+ def getFileDescriptors(self):
+ """
+ Return a dummy, empty mapping of file descriptors.
+ """
+ return {}
+
+
def getName(self):
"""
Get a dummy name.
@@ -873,3 +946,68 @@
return d
+ @inlineCallbacks
+ def test_acceptDescriptorInheritance(self):
+ """
+ If a L{TwistdSlaveProcess} specifies some file descriptors to be
+ inherited, they should be inherited by the subprocess.
+ """
+ dspm = DelayedStartupProcessMonitor()
+ dspm.reactor = InMemoryProcessSpawner()
+
+ # Most arguments here will be ignored, so these are bogus values.
+ slave = TwistdSlaveProcess(
+ twistd = "bleh",
+ tapname = "caldav",
+ configFile = "/does/not/exist",
+ id = 10,
+ interfaces = '127.0.0.1',
+ inheritFDs = [3, 7],
+ inheritSSLFDs = [19, 25],
+ )
+
+ dspm.addProcessObject(slave, {})
+ dspm.startService()
+ self.addCleanup(dspm.consistency.cancel)
+ # We can easily stub out spawnProcess, because caldav calls it, but a
+ # bunch of callLater calls are buried in procmon itself, so we need to
+ # use the real clock.
+ oneProcessTransport = yield dspm.reactor.waitForOneProcess()
+ self.assertEquals(oneProcessTransport.childFDs,
+ {0: 'w', 1: 'r', 2: 'r',
+ 3: 3, 7: 7,
+ 19: 19, 25: 25})
+ @inlineCallbacks
+ def test_metaDescriptorInheritance(self):
+ """
+ If a L{TwistdSlaveProcess} specifies a meta-file-descriptor to be
+ inherited, it should be inherited by the subprocess, and a
+ configuration argument should be passed that indicates to the
+ subprocess.
+ """
+ dspm = DelayedStartupProcessMonitor()
+ dspm.reactor = InMemoryProcessSpawner()
+
+ # Most arguments here will be ignored, so these are bogus values.
+ slave = TwistdSlaveProcess(
+ twistd = "bleh",
+ tapname = "caldav",
+ configFile = "/does/not/exist",
+ id = 10,
+ interfaces = '127.0.0.1',
+ metaFD = 4
+ )
+
+ dspm.addProcessObject(slave, {})
+ dspm.startService()
+ self.addCleanup(dspm.consistency.cancel)
+ oneProcessTransport = yield dspm.reactor.waitForOneProcess()
+ self.assertEquals(oneProcessTransport.childFDs,
+ {0: 'w', 1: 'r', 2: 'r',
+ 4: 4})
+ self.assertIn("MetaFD=4", oneProcessTransport.args)
+ self.assertEquals(
+ oneProcessTransport.args[oneProcessTransport.args.index("MetaFD=4")-1],
+ '-o',
+ "MetaFD argument was not passed as an option"
+ )
Modified: CalendarServer/branches/users/glyph/sendfdport/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/glyph/sendfdport/twistedcaldav/stdconfig.py 2010-03-24 15:40:14 UTC (rev 5389)
+++ CalendarServer/branches/users/glyph/sendfdport/twistedcaldav/stdconfig.py 2010-03-24 15:44:13 UTC (rev 5390)
@@ -141,6 +141,7 @@
"BindSSLPorts" : [], # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
"InheritFDs" : [], # File descriptors to inherit for HTTP requests (empty = don't inherit)
"InheritSSLFDs": [], # File descriptors to inherit for HTTPS requests (empty = don't inherit)
+ "MetaFD": 0, # Inherited file descriptor to call recvmsg() on to recive sockets (none = don't inherit)
#
# Types of service provided
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100324/43183f3e/attachment-0001.html>
More information about the calendarserver-changes
mailing list