|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- # -*- test-case-name: twisted.application.test.test_service -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- Service architecture for Twisted.
-
- Services are arranged in a hierarchy. At the leafs of the hierarchy,
- the services which actually interact with the outside world are started.
- Services can be named or anonymous -- usually, they will be named if
- there is need to access them through the hierarchy (from a parent or
- a sibling).
-
- Maintainer: Moshe Zadka
- """
-
- from __future__ import absolute_import, division
-
- from zope.interface import implementer, Interface, Attribute
-
- from twisted.persisted import sob
- from twisted.python.reflect import namedAny
- from twisted.python import components
- from twisted.python._oldstyle import _oldStyle
- from twisted.internet import defer
- from twisted.plugin import IPlugin
-
-
- class IServiceMaker(Interface):
- """
- An object which can be used to construct services in a flexible
- way.
-
- This interface should most often be implemented along with
- L{twisted.plugin.IPlugin}, and will most often be used by the
- 'twistd' command.
- """
- tapname = Attribute(
- "A short string naming this Twisted plugin, for example 'web' or "
- "'pencil'. This name will be used as the subcommand of 'twistd'.")
-
- description = Attribute(
- "A brief summary of the features provided by this "
- "Twisted application plugin.")
-
- options = Attribute(
- "A C{twisted.python.usage.Options} subclass defining the "
- "configuration options for this application.")
-
-
- def makeService(options):
- """
- Create and return an object providing
- L{twisted.application.service.IService}.
-
- @param options: A mapping (typically a C{dict} or
- L{twisted.python.usage.Options} instance) of configuration
- options to desired configuration values.
- """
-
-
-
- @implementer(IPlugin, IServiceMaker)
- class ServiceMaker(object):
- """
- Utility class to simplify the definition of L{IServiceMaker} plugins.
- """
- def __init__(self, name, module, description, tapname):
- self.name = name
- self.module = module
- self.description = description
- self.tapname = tapname
-
-
- def options():
- def get(self):
- return namedAny(self.module).Options
- return get,
- options = property(*options())
-
-
- def makeService():
- def get(self):
- return namedAny(self.module).makeService
- return get,
- makeService = property(*makeService())
-
-
-
- class IService(Interface):
- """
- A service.
-
- Run start-up and shut-down code at the appropriate times.
- """
-
- name = Attribute(
- "A C{str} which is the name of the service or C{None}.")
-
- running = Attribute(
- "A C{boolean} which indicates whether the service is running.")
-
- parent = Attribute(
- "An C{IServiceCollection} which is the parent or C{None}.")
-
- def setName(name):
- """
- Set the name of the service.
-
- @type name: C{str}
- @raise RuntimeError: Raised if the service already has a parent.
- """
-
- def setServiceParent(parent):
- """
- Set the parent of the service. This method is responsible for setting
- the C{parent} attribute on this service (the child service).
-
- @type parent: L{IServiceCollection}
- @raise RuntimeError: Raised if the service already has a parent
- or if the service has a name and the parent already has a child
- by that name.
- """
-
- def disownServiceParent():
- """
- Use this API to remove an L{IService} from an L{IServiceCollection}.
-
- This method is used symmetrically with L{setServiceParent} in that it
- sets the C{parent} attribute on the child.
-
- @rtype: L{Deferred<defer.Deferred>}
- @return: a L{Deferred<defer.Deferred>} which is triggered when the
- service has finished shutting down. If shutting down is immediate,
- a value can be returned (usually, L{None}).
- """
-
- def startService():
- """
- Start the service.
- """
-
- def stopService():
- """
- Stop the service.
-
- @rtype: L{Deferred<defer.Deferred>}
- @return: a L{Deferred<defer.Deferred>} which is triggered when the
- service has finished shutting down. If shutting down is immediate,
- a value can be returned (usually, L{None}).
- """
-
- def privilegedStartService():
- """
- Do preparation work for starting the service.
-
- Here things which should be done before changing directory,
- root or shedding privileges are done.
- """
-
-
-
- @implementer(IService)
- class Service(object):
- """
- Base class for services.
-
- Most services should inherit from this class. It handles the
- book-keeping responsibilities of starting and stopping, as well
- as not serializing this book-keeping information.
- """
-
- running = 0
- name = None
- parent = None
-
- def __getstate__(self):
- dict = self.__dict__.copy()
- if "running" in dict:
- del dict['running']
- return dict
-
- def setName(self, name):
- if self.parent is not None:
- raise RuntimeError("cannot change name when parent exists")
- self.name = name
-
- def setServiceParent(self, parent):
- if self.parent is not None:
- self.disownServiceParent()
- parent = IServiceCollection(parent, parent)
- self.parent = parent
- self.parent.addService(self)
-
- def disownServiceParent(self):
- d = self.parent.removeService(self)
- self.parent = None
- return d
-
- def privilegedStartService(self):
- pass
-
- def startService(self):
- self.running = 1
-
- def stopService(self):
- self.running = 0
-
-
-
- class IServiceCollection(Interface):
- """
- Collection of services.
-
- Contain several services, and manage their start-up/shut-down.
- Services can be accessed by name if they have a name, and it
- is always possible to iterate over them.
- """
-
- def getServiceNamed(name):
- """
- Get the child service with a given name.
-
- @type name: C{str}
- @rtype: L{IService}
- @raise KeyError: Raised if the service has no child with the
- given name.
- """
-
- def __iter__():
- """
- Get an iterator over all child services.
- """
-
- def addService(service):
- """
- Add a child service.
-
- Only implementations of L{IService.setServiceParent} should use this
- method.
-
- @type service: L{IService}
- @raise RuntimeError: Raised if the service has a child with
- the given name.
- """
-
- def removeService(service):
- """
- Remove a child service.
-
- Only implementations of L{IService.disownServiceParent} should
- use this method.
-
- @type service: L{IService}
- @raise ValueError: Raised if the given service is not a child.
- @rtype: L{Deferred<defer.Deferred>}
- @return: a L{Deferred<defer.Deferred>} which is triggered when the
- service has finished shutting down. If shutting down is immediate,
- a value can be returned (usually, L{None}).
- """
-
-
-
- @implementer(IServiceCollection)
- class MultiService(Service):
- """
- Straightforward Service Container.
-
- Hold a collection of services, and manage them in a simplistic
- way. No service will wait for another, but this object itself
- will not finish shutting down until all of its child services
- will finish.
- """
-
- def __init__(self):
- self.services = []
- self.namedServices = {}
- self.parent = None
-
- def privilegedStartService(self):
- Service.privilegedStartService(self)
- for service in self:
- service.privilegedStartService()
-
- def startService(self):
- Service.startService(self)
- for service in self:
- service.startService()
-
- def stopService(self):
- Service.stopService(self)
- l = []
- services = list(self)
- services.reverse()
- for service in services:
- l.append(defer.maybeDeferred(service.stopService))
- return defer.DeferredList(l)
-
- def getServiceNamed(self, name):
- return self.namedServices[name]
-
- def __iter__(self):
- return iter(self.services)
-
- def addService(self, service):
- if service.name is not None:
- if service.name in self.namedServices:
- raise RuntimeError("cannot have two services with same name"
- " '%s'" % service.name)
- self.namedServices[service.name] = service
- self.services.append(service)
- if self.running:
- # It may be too late for that, but we will do our best
- service.privilegedStartService()
- service.startService()
-
- def removeService(self, service):
- if service.name:
- del self.namedServices[service.name]
- self.services.remove(service)
- if self.running:
- # Returning this so as not to lose information from the
- # MultiService.stopService deferred.
- return service.stopService()
- else:
- return None
-
-
-
- class IProcess(Interface):
- """
- Process running parameters.
-
- Represents parameters for how processes should be run.
- """
- processName = Attribute(
- """
- A C{str} giving the name the process should have in ps (or L{None}
- to leave the name alone).
- """)
-
- uid = Attribute(
- """
- An C{int} giving the user id as which the process should run (or
- L{None} to leave the UID alone).
- """)
-
- gid = Attribute(
- """
- An C{int} giving the group id as which the process should run (or
- L{None} to leave the GID alone).
- """)
-
-
-
- @implementer(IProcess)
- @_oldStyle
- class Process:
- """
- Process running parameters.
-
- Sets up uid/gid in the constructor, and has a default
- of L{None} as C{processName}.
- """
- processName = None
-
- def __init__(self, uid=None, gid=None):
- """
- Set uid and gid.
-
- @param uid: The user ID as whom to execute the process. If
- this is L{None}, no attempt will be made to change the UID.
-
- @param gid: The group ID as whom to execute the process. If
- this is L{None}, no attempt will be made to change the GID.
- """
- self.uid = uid
- self.gid = gid
-
-
-
- def Application(name, uid=None, gid=None):
- """
- Return a compound class.
-
- Return an object supporting the L{IService}, L{IServiceCollection},
- L{IProcess} and L{sob.IPersistable} interfaces, with the given
- parameters. Always access the return value by explicit casting to
- one of the interfaces.
- """
- ret = components.Componentized()
- availableComponents = [MultiService(), Process(uid, gid),
- sob.Persistent(ret, name)]
-
- for comp in availableComponents:
- ret.addComponent(comp, ignoreClass=1)
- IService(ret).setName(name)
- return ret
-
-
-
- def loadApplication(filename, kind, passphrase=None):
- """
- Load Application from a given file.
-
- The serialization format it was saved in should be given as
- C{kind}, and is one of C{pickle}, C{source}, C{xml} or C{python}. If
- C{passphrase} is given, the application was encrypted with the
- given passphrase.
-
- @type filename: C{str}
- @type kind: C{str}
- @type passphrase: C{str}
- """
- if kind == 'python':
- application = sob.loadValueFromFile(filename, 'application')
- else:
- application = sob.load(filename, kind)
- return application
-
-
- __all__ = ['IServiceMaker', 'IService', 'Service',
- 'IServiceCollection', 'MultiService',
- 'IProcess', 'Process', 'Application', 'loadApplication']
|