# -*- 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} @return: a L{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} @return: a L{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} @return: a L{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']