root/trunk/twisted/application/service.py

Revision 32713, 11.5 KB (checked in by exarkun, 8 months ago)

Improve IServiceCollection.addService API docs, clarifying when the method is useful

Author: exarkun
Reviewer: indigo
Fixes: #5273

IServiceCollection.addService is really an aid to IService.setServiceParent
implementations; point this out in the API documentation, so users know which
one they should be calling themselves to construct the service hierarchy.

Line 
1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4"""
5Service architecture for Twisted.
6
7Services are arranged in a hierarchy. At the leafs of the hierarchy,
8the services which actually interact with the outside world are started.
9Services can be named or anonymous -- usually, they will be named if
10there is need to access them through the hierarchy (from a parent or
11a sibling).
12
13Maintainer: Moshe Zadka
14"""
15
16from zope.interface import implements, Interface, Attribute
17
18from twisted.python.reflect import namedAny
19from twisted.python import components
20from twisted.internet import defer
21from twisted.persisted import sob
22from twisted.plugin import IPlugin
23
24
25class IServiceMaker(Interface):
26    """
27    An object which can be used to construct services in a flexible
28    way.
29
30    This interface should most often be implemented along with
31    L{twisted.plugin.IPlugin}, and will most often be used by the
32    'twistd' command.
33    """
34    tapname = Attribute(
35        "A short string naming this Twisted plugin, for example 'web' or "
36        "'pencil'. This name will be used as the subcommand of 'twistd'.")
37
38    description = Attribute(
39        "A brief summary of the features provided by this "
40        "Twisted application plugin.")
41
42    options = Attribute(
43        "A C{twisted.python.usage.Options} subclass defining the "
44        "configuration options for this application.")
45
46
47    def makeService(options):
48        """
49        Create and return an object providing
50        L{twisted.application.service.IService}.
51
52        @param options: A mapping (typically a C{dict} or
53        L{twisted.python.usage.Options} instance) of configuration
54        options to desired configuration values.
55        """
56
57
58
59class ServiceMaker(object):
60    """
61    Utility class to simplify the definition of L{IServiceMaker} plugins.
62    """
63    implements(IPlugin, IServiceMaker)
64
65    def __init__(self, name, module, description, tapname):
66        self.name = name
67        self.module = module
68        self.description = description
69        self.tapname = tapname
70
71
72    def options():
73        def get(self):
74            return namedAny(self.module).Options
75        return get,
76    options = property(*options())
77
78
79    def makeService():
80        def get(self):
81            return namedAny(self.module).makeService
82        return get,
83    makeService = property(*makeService())
84
85
86
87class IService(Interface):
88    """
89    A service.
90
91    Run start-up and shut-down code at the appropriate times.
92
93    @type name:            C{string}
94    @ivar name:            The name of the service (or None)
95    @type running:         C{boolean}
96    @ivar running:         Whether the service is running.
97    """
98
99    def setName(name):
100        """
101        Set the name of the service.
102
103        @type name: C{str}
104        @raise RuntimeError: Raised if the service already has a parent.
105        """
106
107    def setServiceParent(parent):
108        """
109        Set the parent of the service.  This method is responsible for setting
110        the C{parent} attribute on this service (the child service).
111
112        @type parent: L{IServiceCollection}
113        @raise RuntimeError: Raised if the service already has a parent
114            or if the service has a name and the parent already has a child
115            by that name.
116        """
117
118    def disownServiceParent():
119        """
120        Use this API to remove an L{IService} from an L{IServiceCollection}.
121
122        This method is used symmetrically with L{setServiceParent} in that it
123        sets the C{parent} attribute on the child.
124
125        @rtype: L{Deferred<defer.Deferred>}
126        @return: a L{Deferred<defer.Deferred>} which is triggered when the
127            service has finished shutting down. If shutting down is immediate,
128            a value can be returned (usually, C{None}).
129        """
130
131    def startService():
132        """
133        Start the service.
134        """
135
136    def stopService():
137        """
138        Stop the service.
139
140        @rtype: L{Deferred<defer.Deferred>}
141        @return: a L{Deferred<defer.Deferred>} which is triggered when the
142            service has finished shutting down. If shutting down is immediate,
143            a value can be returned (usually, C{None}).
144        """
145
146    def privilegedStartService():
147        """
148        Do preparation work for starting the service.
149
150        Here things which should be done before changing directory,
151        root or shedding privileges are done.
152        """
153
154
155class Service:
156    """
157    Base class for services.
158
159    Most services should inherit from this class. It handles the
160    book-keeping reponsibilities of starting and stopping, as well
161    as not serializing this book-keeping information.
162    """
163
164    implements(IService)
165
166    running = 0
167    name = None
168    parent = None
169
170    def __getstate__(self):
171        dict = self.__dict__.copy()
172        if dict.has_key("running"):
173            del dict['running']
174        return dict
175
176    def setName(self, name):
177        if self.parent is not None:
178            raise RuntimeError("cannot change name when parent exists")
179        self.name = name
180
181    def setServiceParent(self, parent):
182        if self.parent is not None:
183            self.disownServiceParent()
184        parent = IServiceCollection(parent, parent)
185        self.parent = parent
186        self.parent.addService(self)
187
188    def disownServiceParent(self):
189        d = self.parent.removeService(self)
190        self.parent = None
191        return d
192
193    def privilegedStartService(self):
194        pass
195
196    def startService(self):
197        self.running = 1
198
199    def stopService(self):
200        self.running = 0
201
202
203
204class IServiceCollection(Interface):
205    """
206    Collection of services.
207
208    Contain several services, and manage their start-up/shut-down.
209    Services can be accessed by name if they have a name, and it
210    is always possible to iterate over them.
211    """
212
213    def getServiceNamed(name):
214        """
215        Get the child service with a given name.
216
217        @type name: C{str}
218        @rtype: L{IService}
219        @raise KeyError: Raised if the service has no child with the
220            given name.
221        """
222
223    def __iter__():
224        """
225        Get an iterator over all child services.
226        """
227
228    def addService(service):
229        """
230        Add a child service.
231
232        Only implementations of L{IService.setServiceParent} should use this
233        method.
234
235        @type service: L{IService}
236        @raise RuntimeError: Raised if the service has a child with
237            the given name.
238        """
239
240    def removeService(service):
241        """
242        Remove a child service.
243
244        Only implementations of L{IService.disownServiceParent} should
245        use this method.
246
247        @type service: L{IService}
248        @raise ValueError: Raised if the given service is not a child.
249        @rtype: L{Deferred<defer.Deferred>}
250        @return: a L{Deferred<defer.Deferred>} which is triggered when the
251            service has finished shutting down. If shutting down is immediate,
252            a value can be returned (usually, C{None}).
253        """
254
255
256
257class MultiService(Service):
258    """
259    Straightforward Service Container.
260
261    Hold a collection of services, and manage them in a simplistic
262    way. No service will wait for another, but this object itself
263    will not finish shutting down until all of its child services
264    will finish.
265    """
266
267    implements(IServiceCollection)
268
269    def __init__(self):
270        self.services = []
271        self.namedServices = {}
272        self.parent = None
273
274    def privilegedStartService(self):
275        Service.privilegedStartService(self)
276        for service in self:
277            service.privilegedStartService()
278
279    def startService(self):
280        Service.startService(self)
281        for service in self:
282            service.startService()
283
284    def stopService(self):
285        Service.stopService(self)
286        l = []
287        services = list(self)
288        services.reverse()
289        for service in services:
290            l.append(defer.maybeDeferred(service.stopService))
291        return defer.DeferredList(l)
292
293    def getServiceNamed(self, name):
294        return self.namedServices[name]
295
296    def __iter__(self):
297        return iter(self.services)
298
299    def addService(self, service):
300        if service.name is not None:
301            if self.namedServices.has_key(service.name):
302                raise RuntimeError("cannot have two services with same name"
303                                   " '%s'" % service.name)
304            self.namedServices[service.name] = service
305        self.services.append(service)
306        if self.running:
307            # It may be too late for that, but we will do our best
308            service.privilegedStartService()
309            service.startService()
310
311    def removeService(self, service):
312        if service.name:
313            del self.namedServices[service.name]
314        self.services.remove(service)
315        if self.running:
316            # Returning this so as not to lose information from the
317            # MultiService.stopService deferred.
318            return service.stopService()
319        else:
320            return None
321
322
323
324class IProcess(Interface):
325    """
326    Process running parameters.
327
328    Represents parameters for how processes should be run.
329    """
330    processName = Attribute(
331        """
332        A C{str} giving the name the process should have in ps (or C{None}
333        to leave the name alone).
334        """)
335
336    uid = Attribute(
337        """
338        An C{int} giving the user id as which the process should run (or
339        C{None} to leave the UID alone).
340        """)
341
342    gid = Attribute(
343        """
344        An C{int} giving the group id as which the process should run (or
345        C{None} to leave the GID alone).
346        """)
347
348
349
350class Process:
351    """
352    Process running parameters.
353
354    Sets up uid/gid in the constructor, and has a default
355    of C{None} as C{processName}.
356    """
357    implements(IProcess)
358    processName = None
359
360    def __init__(self, uid=None, gid=None):
361        """
362        Set uid and gid.
363
364        @param uid: The user ID as whom to execute the process.  If
365            this is C{None}, no attempt will be made to change the UID.
366
367        @param gid: The group ID as whom to execute the process.  If
368            this is C{None}, no attempt will be made to change the GID.
369        """
370        self.uid = uid
371        self.gid = gid
372
373
374def Application(name, uid=None, gid=None):
375    """
376    Return a compound class.
377
378    Return an object supporting the L{IService}, L{IServiceCollection},
379    L{IProcess} and L{sob.IPersistable} interfaces, with the given
380    parameters. Always access the return value by explicit casting to
381    one of the interfaces.
382    """
383    ret = components.Componentized()
384    for comp in (MultiService(), sob.Persistent(ret, name), Process(uid, gid)):
385        ret.addComponent(comp, ignoreClass=1)
386    IService(ret).setName(name)
387    return ret
388
389
390
391def loadApplication(filename, kind, passphrase=None):
392    """
393    Load Application from a given file.
394
395    The serialization format it was saved in should be given as
396    C{kind}, and is one of C{pickle}, C{source}, C{xml} or C{python}. If
397    C{passphrase} is given, the application was encrypted with the
398    given passphrase.
399
400    @type filename: C{str}
401    @type kind: C{str}
402    @type passphrase: C{str}
403    """
404    if kind == 'python':
405        application = sob.loadValueFromFile(filename, 'application', passphrase)
406    else:
407        application = sob.load(filename, kind, passphrase)
408    return application
409
410
411__all__ = ['IServiceMaker', 'IService', 'Service',
412           'IServiceCollection', 'MultiService',
413           'IProcess', 'Process', 'Application', 'loadApplication']
Note: See TracBrowser for help on using the browser.