add Linux_i686

This commit is contained in:
j 2014-05-17 18:11:40 +00:00 committed by Ubuntu
commit 95cd9b11f2
1644 changed files with 564260 additions and 0 deletions

View file

@ -0,0 +1,18 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Twisted runner: run and monitor processes
Maintainer: Andrew Bennetts
classic inetd(8) support:
Future Plans: The basic design should be final. There are some bugs that need
fixing regarding UDP and Sun-RPC support. Perhaps some day xinetd
compatibility will be added.
procmon:monitor and restart processes
"""
from twisted.runner._version import version
__version__ = version.short()

View file

@ -0,0 +1,11 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
# This is an auto-generated file. Do not edit it.
"""
Provides Twisted version information.
"""
from twisted.python import versions
version = versions.Version('twisted.runner', 14, 0, 0)

View file

@ -0,0 +1,70 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
"""
Twisted inetd.
Maintainer: Andrew Bennetts
Future Plans: Bugfixes. Specifically for UDP and Sun-RPC, which don't work
correctly yet.
"""
import os
from twisted.internet import process, reactor, fdesc
from twisted.internet.protocol import Protocol, ServerFactory
from twisted.protocols import wire
# A dict of known 'internal' services (i.e. those that don't involve spawning
# another process.
internalProtocols = {
'echo': wire.Echo,
'chargen': wire.Chargen,
'discard': wire.Discard,
'daytime': wire.Daytime,
'time': wire.Time,
}
class InetdProtocol(Protocol):
"""Forks a child process on connectionMade, passing the socket as fd 0."""
def connectionMade(self):
sockFD = self.transport.fileno()
childFDs = {0: sockFD, 1: sockFD}
if self.factory.stderrFile:
childFDs[2] = self.factory.stderrFile.fileno()
# processes run by inetd expect blocking sockets
# FIXME: maybe this should be done in process.py? are other uses of
# Process possibly affected by this?
fdesc.setBlocking(sockFD)
if childFDs.has_key(2):
fdesc.setBlocking(childFDs[2])
service = self.factory.service
uid = service.user
gid = service.group
# don't tell Process to change our UID/GID if it's what we
# already are
if uid == os.getuid():
uid = None
if gid == os.getgid():
gid = None
process.Process(None, service.program, service.programArgs, os.environ,
None, None, uid, gid, childFDs)
reactor.removeReader(self.transport)
reactor.removeWriter(self.transport)
class InetdFactory(ServerFactory):
protocol = InetdProtocol
stderrFile = None
def __init__(self, service):
self.service = service

View file

@ -0,0 +1,194 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
"""
Parser for inetd.conf files
Maintainer: Andrew Bennetts
Future Plans: xinetd configuration file support?
"""
# Various exceptions
class InvalidConfError(Exception):
"""Invalid configuration file"""
class InvalidInetdConfError(InvalidConfError):
"""Invalid inetd.conf file"""
class InvalidServicesConfError(InvalidConfError):
"""Invalid services file"""
class InvalidRPCServicesConfError(InvalidConfError):
"""Invalid rpc services file"""
class UnknownService(Exception):
"""Unknown service name"""
class SimpleConfFile:
"""Simple configuration file parser superclass.
Filters out comments and empty lines (which includes lines that only
contain comments).
To use this class, override parseLine or parseFields.
"""
commentChar = '#'
defaultFilename = None
def parseFile(self, file=None):
"""Parse a configuration file
If file is None and self.defaultFilename is set, it will open
defaultFilename and use it.
"""
if file is None and self.defaultFilename:
file = open(self.defaultFilename,'r')
for line in file.readlines():
# Strip out comments
comment = line.find(self.commentChar)
if comment != -1:
line = line[:comment]
# Strip whitespace
line = line.strip()
# Skip empty lines (and lines which only contain comments)
if not line:
continue
self.parseLine(line)
def parseLine(self, line):
"""Override this.
By default, this will split the line on whitespace and call
self.parseFields (catching any errors).
"""
try:
self.parseFields(*line.split())
except ValueError:
raise InvalidInetdConfError, 'Invalid line: ' + repr(line)
def parseFields(self, *fields):
"""Override this."""
class InetdService:
"""A simple description of an inetd service."""
name = None
port = None
socketType = None
protocol = None
wait = None
user = None
group = None
program = None
programArgs = None
def __init__(self, name, port, socketType, protocol, wait, user, group,
program, programArgs):
self.name = name
self.port = port
self.socketType = socketType
self.protocol = protocol
self.wait = wait
self.user = user
self.group = group
self.program = program
self.programArgs = programArgs
class InetdConf(SimpleConfFile):
"""Configuration parser for a traditional UNIX inetd(8)"""
defaultFilename = '/etc/inetd.conf'
def __init__(self, knownServices=None):
self.services = []
if knownServices is None:
knownServices = ServicesConf()
knownServices.parseFile()
self.knownServices = knownServices
def parseFields(self, serviceName, socketType, protocol, wait, user,
program, *programArgs):
"""Parse an inetd.conf file.
Implemented from the description in the Debian inetd.conf man page.
"""
# Extract user (and optional group)
user, group = (user.split('.') + [None])[:2]
# Find the port for a service
port = self.knownServices.services.get((serviceName, protocol), None)
if not port and not protocol.startswith('rpc/'):
# FIXME: Should this be discarded/ignored, rather than throwing
# an exception?
try:
port = int(serviceName)
serviceName = 'unknown'
except:
raise UnknownService, "Unknown service: %s (%s)" \
% (serviceName, protocol)
self.services.append(InetdService(serviceName, port, socketType,
protocol, wait, user, group, program,
programArgs))
class ServicesConf(SimpleConfFile):
"""/etc/services parser
@ivar services: dict mapping service names to (port, protocol) tuples.
"""
defaultFilename = '/etc/services'
def __init__(self):
self.services = {}
def parseFields(self, name, portAndProtocol, *aliases):
try:
port, protocol = portAndProtocol.split('/')
port = long(port)
except:
raise InvalidServicesConfError, 'Invalid port/protocol:' + \
repr(portAndProtocol)
self.services[(name, protocol)] = port
for alias in aliases:
self.services[(alias, protocol)] = port
class RPCServicesConf(SimpleConfFile):
"""/etc/rpc parser
@ivar self.services: dict mapping rpc service names to rpc ports.
"""
defaultFilename = '/etc/rpc'
def __init__(self):
self.services = {}
def parseFields(self, name, port, *aliases):
try:
port = long(port)
except:
raise InvalidRPCServicesConfError, 'Invalid port:' + repr(port)
self.services[name] = port
for alias in aliases:
self.services[alias] = port

View file

@ -0,0 +1,163 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
"""
Twisted inetd TAP support
Maintainer: Andrew Bennetts
Future Plans: more configurability.
"""
import os, pwd, grp, socket
from twisted.runner import inetd, inetdconf
from twisted.python import log, usage
from twisted.internet.protocol import ServerFactory
from twisted.application import internet, service as appservice
try:
import portmap
rpcOk = 1
except ImportError:
rpcOk = 0
# Protocol map
protocolDict = {'tcp': socket.IPPROTO_TCP, 'udp': socket.IPPROTO_UDP}
class Options(usage.Options):
optParameters = [
['rpc', 'r', '/etc/rpc', 'RPC procedure table file'],
['file', 'f', '/etc/inetd.conf', 'Service configuration file']
]
optFlags = [['nointernal', 'i', "Don't run internal services"]]
compData = usage.Completions(
optActions={"file": usage.CompleteFiles('*.conf')}
)
class RPCServer(internet.TCPServer):
def __init__(self, rpcVersions, rpcConf, proto, service):
internet.TCPServer.__init__(0, ServerFactory())
self.rpcConf = rpcConf
self.proto = proto
self.service = service
def startService(self):
internet.TCPServer.startService(self)
import portmap
portNo = self._port.getHost()[2]
service = self.service
for version in rpcVersions:
portmap.set(self.rpcConf.services[name], version, self.proto,
portNo)
inetd.forkPassingFD(service.program, service.programArgs,
os.environ, service.user, service.group, p)
def makeService(config):
s = appservice.MultiService()
conf = inetdconf.InetdConf()
conf.parseFile(open(config['file']))
rpcConf = inetdconf.RPCServicesConf()
try:
rpcConf.parseFile(open(config['rpc']))
except:
# We'll survive even if we can't read /etc/rpc
log.deferr()
for service in conf.services:
rpc = service.protocol.startswith('rpc/')
protocol = service.protocol
if rpc and not rpcOk:
log.msg('Skipping rpc service due to lack of rpc support')
continue
if rpc:
# RPC has extra options, so extract that
protocol = protocol[4:] # trim 'rpc/'
if not protocolDict.has_key(protocol):
log.msg('Bad protocol: ' + protocol)
continue
try:
name, rpcVersions = service.name.split('/')
except ValueError:
log.msg('Bad RPC service/version: ' + service.name)
continue
if not rpcConf.services.has_key(name):
log.msg('Unknown RPC service: ' + repr(service.name))
continue
try:
if '-' in rpcVersions:
start, end = map(int, rpcVersions.split('-'))
rpcVersions = range(start, end+1)
else:
rpcVersions = [int(rpcVersions)]
except ValueError:
log.msg('Bad RPC versions: ' + str(rpcVersions))
continue
if (protocol, service.socketType) not in [('tcp', 'stream'),
('udp', 'dgram')]:
log.msg('Skipping unsupported type/protocol: %s/%s'
% (service.socketType, service.protocol))
continue
# Convert the username into a uid (if necessary)
try:
service.user = int(service.user)
except ValueError:
try:
service.user = pwd.getpwnam(service.user)[2]
except KeyError:
log.msg('Unknown user: ' + service.user)
continue
# Convert the group name into a gid (if necessary)
if service.group is None:
# If no group was specified, use the user's primary group
service.group = pwd.getpwuid(service.user)[3]
else:
try:
service.group = int(service.group)
except ValueError:
try:
service.group = grp.getgrnam(service.group)[2]
except KeyError:
log.msg('Unknown group: ' + service.group)
continue
if service.program == 'internal':
if config['nointernal']:
continue
# Internal services can use a standard ServerFactory
if not inetd.internalProtocols.has_key(service.name):
log.msg('Unknown internal service: ' + service.name)
continue
factory = ServerFactory()
factory.protocol = inetd.internalProtocols[service.name]
elif rpc:
i = RPCServer(rpcVersions, rpcConf, proto, service)
i.setServiceParent(s)
continue
else:
# Non-internal non-rpc services use InetdFactory
factory = inetd.InetdFactory(service)
if protocol == 'tcp':
internet.TCPServer(service.port, factory).setServiceParent(s)
elif protocol == 'udp':
raise RuntimeError("not supporting UDP")
return s

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2001-2004 Twisted Matrix Laboratories.
* See LICENSE for details.
*
*/
/* portmap.c: A simple Python wrapper for pmap_set(3) and pmap_unset(3) */
#include <Python.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
static PyObject * portmap_set(PyObject *self, PyObject *args)
{
unsigned long program, version;
int protocol;
unsigned short port;
if (!PyArg_ParseTuple(args, "llih:set",
&program, &version, &protocol, &port))
return NULL;
pmap_unset(program, version);
pmap_set(program, version, protocol, port);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject * portmap_unset(PyObject *self, PyObject *args)
{
unsigned long program, version;
if (!PyArg_ParseTuple(args, "ll:unset",
&program, &version))
return NULL;
pmap_unset(program, version);
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef PortmapMethods[] = {
{"set", portmap_set, METH_VARARGS,
"Set an entry in the portmapper."},
{"unset", portmap_unset, METH_VARARGS,
"Unset an entry in the portmapper."},
{NULL, NULL, 0, NULL}
};
void initportmap(void)
{
(void) Py_InitModule("portmap", PortmapMethods);
}

View file

@ -0,0 +1,308 @@
# -*- test-case-name: twisted.runner.test.test_procmon -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Support for starting, monitoring, and restarting child process.
"""
from twisted.python import log
from twisted.internet import error, protocol, reactor as _reactor
from twisted.application import service
from twisted.protocols import basic
class DummyTransport:
disconnecting = 0
transport = DummyTransport()
class LineLogger(basic.LineReceiver):
tag = None
delimiter = '\n'
def lineReceived(self, line):
log.msg('[%s] %s' % (self.tag, line))
class LoggingProtocol(protocol.ProcessProtocol):
service = None
name = None
empty = 1
def connectionMade(self):
self.output = LineLogger()
self.output.tag = self.name
self.output.makeConnection(transport)
def outReceived(self, data):
self.output.dataReceived(data)
self.empty = data[-1] == '\n'
errReceived = outReceived
def processEnded(self, reason):
if not self.empty:
self.output.dataReceived('\n')
self.service.connectionLost(self.name)
class ProcessMonitor(service.Service):
"""
ProcessMonitor runs processes, monitors their progress, and restarts
them when they die.
The ProcessMonitor will not attempt to restart a process that appears to
die instantly -- with each "instant" death (less than 1 second, by
default), it will delay approximately twice as long before restarting
it. A successful run will reset the counter.
The primary interface is L{addProcess} and L{removeProcess}. When the
service is running (that is, when the application it is attached to is
running), adding a process automatically starts it.
Each process has a name. This name string must uniquely identify the
process. In particular, attempting to add two processes with the same
name will result in a C{KeyError}.
@type threshold: C{float}
@ivar threshold: How long a process has to live before the death is
considered instant, in seconds. The default value is 1 second.
@type killTime: C{float}
@ivar killTime: How long a process being killed has to get its affairs
in order before it gets killed with an unmaskable signal. The
default value is 5 seconds.
@type minRestartDelay: C{float}
@ivar minRestartDelay: The minimum time (in seconds) to wait before
attempting to restart a process. Default 1s.
@type maxRestartDelay: C{float}
@ivar maxRestartDelay: The maximum time (in seconds) to wait before
attempting to restart a process. Default 3600s (1h).
@type _reactor: L{IReactorProcess} provider
@ivar _reactor: A provider of L{IReactorProcess} and L{IReactorTime}
which will be used to spawn processes and register delayed calls.
"""
threshold = 1
killTime = 5
minRestartDelay = 1
maxRestartDelay = 3600
def __init__(self, reactor=_reactor):
self._reactor = reactor
self.processes = {}
self.protocols = {}
self.delay = {}
self.timeStarted = {}
self.murder = {}
self.restart = {}
def __getstate__(self):
dct = service.Service.__getstate__(self)
del dct['_reactor']
dct['protocols'] = {}
dct['delay'] = {}
dct['timeStarted'] = {}
dct['murder'] = {}
dct['restart'] = {}
return dct
def addProcess(self, name, args, uid=None, gid=None, env={}):
"""
Add a new monitored process and start it immediately if the
L{ProcessMonitor} service is running.
Note that args are passed to the system call, not to the shell. If
running the shell is desired, the common idiom is to use
C{ProcessMonitor.addProcess("name", ['/bin/sh', '-c', shell_script])}
@param name: A name for this process. This value must be
unique across all processes added to this monitor.
@type name: C{str}
@param args: The argv sequence for the process to launch.
@param uid: The user ID to use to run the process. If C{None},
the current UID is used.
@type uid: C{int}
@param gid: The group ID to use to run the process. If C{None},
the current GID is used.
@type uid: C{int}
@param env: The environment to give to the launched process. See
L{IReactorProcess.spawnProcess}'s C{env} parameter.
@type env: C{dict}
@raises: C{KeyError} if a process with the given name already
exists
"""
if name in self.processes:
raise KeyError("remove %s first" % (name,))
self.processes[name] = args, uid, gid, env
self.delay[name] = self.minRestartDelay
if self.running:
self.startProcess(name)
def removeProcess(self, name):
"""
Stop the named process and remove it from the list of monitored
processes.
@type name: C{str}
@param name: A string that uniquely identifies the process.
"""
self.stopProcess(name)
del self.processes[name]
def startService(self):
"""
Start all monitored processes.
"""
service.Service.startService(self)
for name in self.processes:
self.startProcess(name)
def stopService(self):
"""
Stop all monitored processes and cancel all scheduled process restarts.
"""
service.Service.stopService(self)
# Cancel any outstanding restarts
for name, delayedCall in self.restart.items():
if delayedCall.active():
delayedCall.cancel()
for name in self.processes:
self.stopProcess(name)
def connectionLost(self, name):
"""
Called when a monitored processes exits. If
L{ProcessMonitor.running} is C{True} (ie the service is started), the
process will be restarted.
If the process had been running for more than
L{ProcessMonitor.threshold} seconds it will be restarted immediately.
If the process had been running for less than
L{ProcessMonitor.threshold} seconds, the restart will be delayed and
each time the process dies before the configured threshold, the restart
delay will be doubled - up to a maximum delay of maxRestartDelay sec.
@type name: C{str}
@param name: A string that uniquely identifies the process
which exited.
"""
# Cancel the scheduled _forceStopProcess function if the process
# dies naturally
if name in self.murder:
if self.murder[name].active():
self.murder[name].cancel()
del self.murder[name]
del self.protocols[name]
if self._reactor.seconds() - self.timeStarted[name] < self.threshold:
# The process died too fast - backoff
nextDelay = self.delay[name]
self.delay[name] = min(self.delay[name] * 2, self.maxRestartDelay)
else:
# Process had been running for a significant amount of time
# restart immediately
nextDelay = 0
self.delay[name] = self.minRestartDelay
# Schedule a process restart if the service is running
if self.running and name in self.processes:
self.restart[name] = self._reactor.callLater(nextDelay,
self.startProcess,
name)
def startProcess(self, name):
"""
@param name: The name of the process to be started
"""
# If a protocol instance already exists, it means the process is
# already running
if name in self.protocols:
return
args, uid, gid, env = self.processes[name]
proto = LoggingProtocol()
proto.service = self
proto.name = name
self.protocols[name] = proto
self.timeStarted[name] = self._reactor.seconds()
self._reactor.spawnProcess(proto, args[0], args, uid=uid,
gid=gid, env=env)
def _forceStopProcess(self, proc):
"""
@param proc: An L{IProcessTransport} provider
"""
try:
proc.signalProcess('KILL')
except error.ProcessExitedAlready:
pass
def stopProcess(self, name):
"""
@param name: The name of the process to be stopped
"""
if name not in self.processes:
raise KeyError('Unrecognized process name: %s' % (name,))
proto = self.protocols.get(name, None)
if proto is not None:
proc = proto.transport
try:
proc.signalProcess('TERM')
except error.ProcessExitedAlready:
pass
else:
self.murder[name] = self._reactor.callLater(
self.killTime,
self._forceStopProcess, proc)
def restartAll(self):
"""
Restart all processes. This is useful for third party management
services to allow a user to restart servers because of an outside change
in circumstances -- for example, a new version of a library is
installed.
"""
for name in self.processes:
self.stopProcess(name)
def __repr__(self):
l = []
for name, proc in self.processes.items():
uidgid = ''
if proc[1] is not None:
uidgid = str(proc[1])
if proc[2] is not None:
uidgid += ':'+str(proc[2])
if uidgid:
uidgid = '(' + uidgid + ')'
l.append('%r%s: %r' % (name, uidgid, proc[0]))
return ('<' + self.__class__.__name__ + ' '
+ ' '.join(l)
+ '>')

View file

@ -0,0 +1,73 @@
# -*- test-case-name: twisted.runner.test.test_procmontap -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Support for creating a service which runs a process monitor.
"""
from twisted.python import usage
from twisted.runner.procmon import ProcessMonitor
class Options(usage.Options):
"""
Define the options accepted by the I{twistd procmon} plugin.
"""
synopsis = "[procmon options] commandline"
optParameters = [["threshold", "t", 1, "How long a process has to live "
"before the death is considered instant, in seconds.",
float],
["killtime", "k", 5, "How long a process being killed "
"has to get its affairs in order before it gets killed "
"with an unmaskable signal.",
float],
["minrestartdelay", "m", 1, "The minimum time (in "
"seconds) to wait before attempting to restart a "
"process", float],
["maxrestartdelay", "M", 3600, "The maximum time (in "
"seconds) to wait before attempting to restart a "
"process", float]]
optFlags = []
longdesc = """\
procmon runs processes, monitors their progress, and restarts them when they
die.
procmon will not attempt to restart a process that appears to die instantly;
with each "instant" death (less than 1 second, by default), it will delay
approximately twice as long before restarting it. A successful run will reset
the counter.
Eg twistd procmon sleep 10"""
def parseArgs(self, *args):
"""
Grab the command line that is going to be started and monitored
"""
self['args'] = args
def postOptions(self):
"""
Check for dependencies.
"""
if len(self["args"]) < 1:
raise usage.UsageError("Please specify a process commandline")
def makeService(config):
s = ProcessMonitor()
s.threshold = config["threshold"]
s.killTime = config["killtime"]
s.minRestartDelay = config["minrestartdelay"]
s.maxRestartDelay = config["maxrestartdelay"]
s.addProcess(" ".join(config["args"]), config["args"])
return s

View file

@ -0,0 +1,6 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test package for Twisted Runner.
"""

View file

@ -0,0 +1,477 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.runner.procmon}.
"""
from twisted.trial import unittest
from twisted.runner.procmon import LoggingProtocol, ProcessMonitor
from twisted.internet.error import (ProcessDone, ProcessTerminated,
ProcessExitedAlready)
from twisted.internet.task import Clock
from twisted.python.failure import Failure
from twisted.test.proto_helpers import MemoryReactor
class DummyProcess(object):
"""
An incomplete and fake L{IProcessTransport} implementation for testing how
L{ProcessMonitor} behaves when its monitored processes exit.
@ivar _terminationDelay: the delay in seconds after which the DummyProcess
will appear to exit when it receives a TERM signal
"""
pid = 1
proto = None
_terminationDelay = 1
def __init__(self, reactor, executable, args, environment, path,
proto, uid=None, gid=None, usePTY=0, childFDs=None):
self.proto = proto
self._reactor = reactor
self._executable = executable
self._args = args
self._environment = environment
self._path = path
self._uid = uid
self._gid = gid
self._usePTY = usePTY
self._childFDs = childFDs
def signalProcess(self, signalID):
"""
A partial implementation of signalProcess which can only handle TERM and
KILL signals.
- When a TERM signal is given, the dummy process will appear to exit
after L{DummyProcess._terminationDelay} seconds with exit code 0
- When a KILL signal is given, the dummy process will appear to exit
immediately with exit code 1.
@param signalID: The signal name or number to be issued to the process.
@type signalID: C{str}
"""
params = {
"TERM": (self._terminationDelay, 0),
"KILL": (0, 1)
}
if self.pid is None:
raise ProcessExitedAlready()
if signalID in params:
delay, status = params[signalID]
self._signalHandler = self._reactor.callLater(
delay, self.processEnded, status)
def processEnded(self, status):
"""
Deliver the process ended event to C{self.proto}.
"""
self.pid = None
statusMap = {
0: ProcessDone,
1: ProcessTerminated,
}
self.proto.processEnded(Failure(statusMap[status](status)))
class DummyProcessReactor(MemoryReactor, Clock):
"""
@ivar spawnedProcesses: a list that keeps track of the fake process
instances built by C{spawnProcess}.
@type spawnedProcesses: C{list}
"""
def __init__(self):
MemoryReactor.__init__(self)
Clock.__init__(self)
self.spawnedProcesses = []
def spawnProcess(self, processProtocol, executable, args=(), env={},
path=None, uid=None, gid=None, usePTY=0,
childFDs=None):
"""
Fake L{reactor.spawnProcess}, that logs all the process
arguments and returns a L{DummyProcess}.
"""
proc = DummyProcess(self, executable, args, env, path,
processProtocol, uid, gid, usePTY, childFDs)
processProtocol.makeConnection(proc)
self.spawnedProcesses.append(proc)
return proc
class ProcmonTests(unittest.TestCase):
"""
Tests for L{ProcessMonitor}.
"""
def setUp(self):
"""
Create an L{ProcessMonitor} wrapped around a fake reactor.
"""
self.reactor = DummyProcessReactor()
self.pm = ProcessMonitor(reactor=self.reactor)
self.pm.minRestartDelay = 2
self.pm.maxRestartDelay = 10
self.pm.threshold = 10
def test_getStateIncludesProcesses(self):
"""
The list of monitored processes must be included in the pickle state.
"""
self.pm.addProcess("foo", ["arg1", "arg2"],
uid=1, gid=2, env={})
self.assertEqual(self.pm.__getstate__()['processes'],
{'foo': (['arg1', 'arg2'], 1, 2, {})})
def test_getStateExcludesReactor(self):
"""
The private L{ProcessMonitor._reactor} instance variable should not be
included in the pickle state.
"""
self.assertNotIn('_reactor', self.pm.__getstate__())
def test_addProcess(self):
"""
L{ProcessMonitor.addProcess} only starts the named program if
L{ProcessMonitor.startService} has been called.
"""
self.pm.addProcess("foo", ["arg1", "arg2"],
uid=1, gid=2, env={})
self.assertEqual(self.pm.protocols, {})
self.assertEqual(self.pm.processes,
{"foo": (["arg1", "arg2"], 1, 2, {})})
self.pm.startService()
self.reactor.advance(0)
self.assertEqual(self.pm.protocols.keys(), ["foo"])
def test_addProcessDuplicateKeyError(self):
"""
L{ProcessMonitor.addProcess} raises a C{KeyError} if a process with the
given name already exists.
"""
self.pm.addProcess("foo", ["arg1", "arg2"],
uid=1, gid=2, env={})
self.assertRaises(KeyError, self.pm.addProcess,
"foo", ["arg1", "arg2"], uid=1, gid=2, env={})
def test_addProcessEnv(self):
"""
L{ProcessMonitor.addProcess} takes an C{env} parameter that is passed to
L{IReactorProcess.spawnProcess}.
"""
fakeEnv = {"KEY": "value"}
self.pm.startService()
self.pm.addProcess("foo", ["foo"], uid=1, gid=2, env=fakeEnv)
self.reactor.advance(0)
self.assertEqual(
self.reactor.spawnedProcesses[0]._environment, fakeEnv)
def test_removeProcess(self):
"""
L{ProcessMonitor.removeProcess} removes the process from the public
processes list.
"""
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.assertEqual(len(self.pm.processes), 1)
self.pm.removeProcess("foo")
self.assertEqual(len(self.pm.processes), 0)
def test_removeProcessUnknownKeyError(self):
"""
L{ProcessMonitor.removeProcess} raises a C{KeyError} if the given
process name isn't recognised.
"""
self.pm.startService()
self.assertRaises(KeyError, self.pm.removeProcess, "foo")
def test_startProcess(self):
"""
When a process has been started, an instance of L{LoggingProtocol} will
be added to the L{ProcessMonitor.protocols} dict and the start time of
the process will be recorded in the L{ProcessMonitor.timeStarted}
dictionary.
"""
self.pm.addProcess("foo", ["foo"])
self.pm.startProcess("foo")
self.assertIsInstance(self.pm.protocols["foo"], LoggingProtocol)
self.assertIn("foo", self.pm.timeStarted.keys())
def test_startProcessAlreadyStarted(self):
"""
L{ProcessMonitor.startProcess} silently returns if the named process is
already started.
"""
self.pm.addProcess("foo", ["foo"])
self.pm.startProcess("foo")
self.assertIdentical(None, self.pm.startProcess("foo"))
def test_startProcessUnknownKeyError(self):
"""
L{ProcessMonitor.startProcess} raises a C{KeyError} if the given
process name isn't recognised.
"""
self.assertRaises(KeyError, self.pm.startProcess, "foo")
def test_stopProcessNaturalTermination(self):
"""
L{ProcessMonitor.stopProcess} immediately sends a TERM signal to the
named process.
"""
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.assertIn("foo", self.pm.protocols)
# Configure fake process to die 1 second after receiving term signal
timeToDie = self.pm.protocols["foo"].transport._terminationDelay = 1
# Advance the reactor to just before the short lived process threshold
# and leave enough time for the process to die
self.reactor.advance(self.pm.threshold)
# Then signal the process to stop
self.pm.stopProcess("foo")
# Advance the reactor just enough to give the process time to die and
# verify that the process restarts
self.reactor.advance(timeToDie)
# We expect it to be restarted immediately
self.assertEqual(self.reactor.seconds(),
self.pm.timeStarted["foo"])
def test_stopProcessForcedKill(self):
"""
L{ProcessMonitor.stopProcess} kills a process which fails to terminate
naturally within L{ProcessMonitor.killTime} seconds.
"""
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.assertIn("foo", self.pm.protocols)
self.reactor.advance(self.pm.threshold)
proc = self.pm.protocols["foo"].transport
# Arrange for the fake process to live longer than the killTime
proc._terminationDelay = self.pm.killTime + 1
self.pm.stopProcess("foo")
# If process doesn't die before the killTime, procmon should
# terminate it
self.reactor.advance(self.pm.killTime - 1)
self.assertEqual(0.0, self.pm.timeStarted["foo"])
self.reactor.advance(1)
# We expect it to be immediately restarted
self.assertEqual(self.reactor.seconds(), self.pm.timeStarted["foo"])
def test_stopProcessUnknownKeyError(self):
"""
L{ProcessMonitor.stopProcess} raises a C{KeyError} if the given process
name isn't recognised.
"""
self.assertRaises(KeyError, self.pm.stopProcess, "foo")
def test_stopProcessAlreadyStopped(self):
"""
L{ProcessMonitor.stopProcess} silently returns if the named process
is already stopped. eg Process has crashed and a restart has been
rescheduled, but in the meantime, the service is stopped.
"""
self.pm.addProcess("foo", ["foo"])
self.assertIdentical(None, self.pm.stopProcess("foo"))
def test_connectionLostLongLivedProcess(self):
"""
L{ProcessMonitor.connectionLost} should immediately restart a process
if it has been running longer than L{ProcessMonitor.threshold} seconds.
"""
self.pm.addProcess("foo", ["foo"])
# Schedule the process to start
self.pm.startService()
# advance the reactor to start the process
self.reactor.advance(0)
self.assertIn("foo", self.pm.protocols)
# Long time passes
self.reactor.advance(self.pm.threshold)
# Process dies after threshold
self.pm.protocols["foo"].processEnded(Failure(ProcessDone(0)))
self.assertNotIn("foo", self.pm.protocols)
# Process should be restarted immediately
self.reactor.advance(0)
self.assertIn("foo", self.pm.protocols)
def test_connectionLostMurderCancel(self):
"""
L{ProcessMonitor.connectionLost} cancels a scheduled process killer and
deletes the DelayedCall from the L{ProcessMonitor.murder} list.
"""
self.pm.addProcess("foo", ["foo"])
# Schedule the process to start
self.pm.startService()
# Advance 1s to start the process then ask ProcMon to stop it
self.reactor.advance(1)
self.pm.stopProcess("foo")
# A process killer has been scheduled, delayedCall is active
self.assertIn("foo", self.pm.murder)
delayedCall = self.pm.murder["foo"]
self.assertTrue(delayedCall.active())
# Advance to the point at which the dummy process exits
self.reactor.advance(
self.pm.protocols["foo"].transport._terminationDelay)
# Now the delayedCall has been cancelled and deleted
self.assertFalse(delayedCall.active())
self.assertNotIn("foo", self.pm.murder)
def test_connectionLostProtocolDeletion(self):
"""
L{ProcessMonitor.connectionLost} removes the corresponding
ProcessProtocol instance from the L{ProcessMonitor.protocols} list.
"""
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.assertIn("foo", self.pm.protocols)
self.pm.protocols["foo"].transport.signalProcess("KILL")
self.reactor.advance(
self.pm.protocols["foo"].transport._terminationDelay)
self.assertNotIn("foo", self.pm.protocols)
def test_connectionLostMinMaxRestartDelay(self):
"""
L{ProcessMonitor.connectionLost} will wait at least minRestartDelay s
and at most maxRestartDelay s
"""
self.pm.minRestartDelay = 2
self.pm.maxRestartDelay = 3
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.assertEqual(self.pm.delay["foo"], self.pm.minRestartDelay)
self.reactor.advance(self.pm.threshold - 1)
self.pm.protocols["foo"].processEnded(Failure(ProcessDone(0)))
self.assertEqual(self.pm.delay["foo"], self.pm.maxRestartDelay)
def test_connectionLostBackoffDelayDoubles(self):
"""
L{ProcessMonitor.connectionLost} doubles the restart delay each time
the process dies too quickly.
"""
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
self.reactor.advance(self.pm.threshold - 1) #9s
self.assertIn("foo", self.pm.protocols)
self.assertEqual(self.pm.delay["foo"], self.pm.minRestartDelay)
# process dies within the threshold and should not restart immediately
self.pm.protocols["foo"].processEnded(Failure(ProcessDone(0)))
self.assertEqual(self.pm.delay["foo"], self.pm.minRestartDelay * 2)
def test_startService(self):
"""
L{ProcessMonitor.startService} starts all monitored processes.
"""
self.pm.addProcess("foo", ["foo"])
# Schedule the process to start
self.pm.startService()
# advance the reactor to start the process
self.reactor.advance(0)
self.assertTrue("foo" in self.pm.protocols)
def test_stopService(self):
"""
L{ProcessMonitor.stopService} should stop all monitored processes.
"""
self.pm.addProcess("foo", ["foo"])
self.pm.addProcess("bar", ["bar"])
# Schedule the process to start
self.pm.startService()
# advance the reactor to start the processes
self.reactor.advance(self.pm.threshold)
self.assertIn("foo", self.pm.protocols)
self.assertIn("bar", self.pm.protocols)
self.reactor.advance(1)
self.pm.stopService()
# Advance to beyond the killTime - all monitored processes
# should have exited
self.reactor.advance(self.pm.killTime + 1)
# The processes shouldn't be restarted
self.assertEqual({}, self.pm.protocols)
def test_stopServiceCancelRestarts(self):
"""
L{ProcessMonitor.stopService} should cancel any scheduled process
restarts.
"""
self.pm.addProcess("foo", ["foo"])
# Schedule the process to start
self.pm.startService()
# advance the reactor to start the processes
self.reactor.advance(self.pm.threshold)
self.assertIn("foo", self.pm.protocols)
self.reactor.advance(1)
# Kill the process early
self.pm.protocols["foo"].processEnded(Failure(ProcessDone(0)))
self.assertTrue(self.pm.restart['foo'].active())
self.pm.stopService()
# Scheduled restart should have been cancelled
self.assertFalse(self.pm.restart['foo'].active())
def test_stopServiceCleanupScheduledRestarts(self):
"""
L{ProcessMonitor.stopService} should cancel all scheduled process
restarts.
"""
self.pm.threshold = 5
self.pm.minRestartDelay = 5
# Start service and add a process (started immediately)
self.pm.startService()
self.pm.addProcess("foo", ["foo"])
# Stop the process after 1s
self.reactor.advance(1)
self.pm.stopProcess("foo")
# Wait 1s for it to exit it will be scheduled to restart 5s later
self.reactor.advance(1)
# Meanwhile stop the service
self.pm.stopService()
# Advance to beyond the process restart time
self.reactor.advance(6)
# The process shouldn't have restarted because stopService has cancelled
# all pending process restarts.
self.assertEqual(self.pm.protocols, {})

View file

@ -0,0 +1,87 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.runner.procmontap}.
"""
from twisted.python.usage import UsageError
from twisted.trial import unittest
from twisted.runner.procmon import ProcessMonitor
from twisted.runner import procmontap as tap
class ProcessMonitorTapTest(unittest.TestCase):
"""
Tests for L{twisted.runner.procmontap}'s option parsing and makeService
method.
"""
def test_commandLineRequired(self):
"""
The command line arguments must be provided.
"""
opt = tap.Options()
self.assertRaises(UsageError, opt.parseOptions, [])
def test_threshold(self):
"""
The threshold option is recognised as a parameter and coerced to
float.
"""
opt = tap.Options()
opt.parseOptions(['--threshold', '7.5', 'foo'])
self.assertEqual(opt['threshold'], 7.5)
def test_killTime(self):
"""
The killtime option is recognised as a parameter and coerced to float.
"""
opt = tap.Options()
opt.parseOptions(['--killtime', '7.5', 'foo'])
self.assertEqual(opt['killtime'], 7.5)
def test_minRestartDelay(self):
"""
The minrestartdelay option is recognised as a parameter and coerced to
float.
"""
opt = tap.Options()
opt.parseOptions(['--minrestartdelay', '7.5', 'foo'])
self.assertEqual(opt['minrestartdelay'], 7.5)
def test_maxRestartDelay(self):
"""
The maxrestartdelay option is recognised as a parameter and coerced to
float.
"""
opt = tap.Options()
opt.parseOptions(['--maxrestartdelay', '7.5', 'foo'])
self.assertEqual(opt['maxrestartdelay'], 7.5)
def test_parameterDefaults(self):
"""
The parameters all have default values
"""
opt = tap.Options()
opt.parseOptions(['foo'])
self.assertEqual(opt['threshold'], 1)
self.assertEqual(opt['killtime'], 5)
self.assertEqual(opt['minrestartdelay'], 1)
self.assertEqual(opt['maxrestartdelay'], 3600)
def test_makeService(self):
"""
The command line gets added as a process to the ProcessMontor.
"""
opt = tap.Options()
opt.parseOptions(['ping', '-c', '3', '8.8.8.8'])
s = tap.makeService(opt)
self.assertIsInstance(s, ProcessMonitor)
self.assertIn('ping -c 3 8.8.8.8', s.processes)

View file

@ -0,0 +1,145 @@
Ticket numbers in this file can be looked up by visiting
http://twistedmatrix.com/trac/ticket/<number>
Twisted Runner 14.0.0 (2014-05-08)
==================================
No significant changes have been made for this release.
Other
-----
- #6992
Twisted Runner 13.2.0 (2013-10-29)
==================================
No significant changes have been made for this release.
Twisted Runner 13.1.0 (2013-06-23)
==================================
No significant changes have been made for this release.
Twisted Runner 13.0.0 (2013-03-19)
==================================
No significant changes have been made for this release.
Other
-----
- #5740
Twisted Runner 12.3.0 (2012-12-20)
==================================
No significant changes have been made for this release.
Twisted Runner 12.2.0 (2012-08-26)
==================================
No significant changes have been made for this release.
Twisted Runner 12.1.0 (2012-06-02)
==================================
Deprecations and Removals
-------------------------
- ProcessMonitor.active, consistencyDelay, and consistency in
twisted.runner.procmon were deprecated since 10.1 have been
removed. (#5517)
Twisted Runner 12.0.0 (2012-02-10)
==================================
No significant changes have been made for this release.
Twisted Runner 11.1.0 (2011-11-15)
==================================
No significant changes have been made for this release.
Twisted Runner 11.0.0 (2011-04-01)
==================================
No significant changes have been made for this release.
Twisted Runner 10.2.0 (2010-11-29)
==================================
No significant changes have been made for this release.
Twisted Runner 10.1.0 (2010-06-27)
==================================
Features
--------
- twistd now has a procmon subcommand plugin - a convenient way to
monitor and automatically restart another process. (#4356)
Deprecations and Removals
-------------------------
- twisted.runner.procmon.ProcessMonitor's active, consistency, and
consistencyDelay attributes are now deprecated. (#1763)
Other
-----
- #3775
Twisted Runner 10.0.0 (2010-03-01)
==================================
Other
-----
- #3961
Twisted Runner 9.0.0 (2009-11-24)
=================================
Features
--------
- procmon.ProcessMonitor.addProcess now accepts an 'env' parameter which allows
users to specify the environment in which a process will be run (#3691)
Other
-----
- #3540
Runner 8.2.0 (2008-12-16)
=========================
No interesting changes since Twisted 8.0.
8.0.0 (2008-03-17)
==================
Misc
----
- Remove all "API Stability" markers (#2847)
0.2.0 (2006-05-24)
==================
Fixes
-----
- Fix a bug that broke inetdtap.RPCServer.
- Misc: #1142
0.1.0
=====
- Pass *blocking* sockets to subprocesses run by inetd

View file

@ -0,0 +1,3 @@
Twisted Runner 14.0.0
Twisted Runner depends on Twisted.