root / trunk / twisted / application / service.py

Revision 25377, 11.0 kB (checked in by thijs, 8 months ago)

Merge doc-diff-service-671: Document the differences between t.a.service.IService.disownServiceParent and t.a.service.IServiceCollection.removeService.

Author: thijs
Reviewer: exarkun, therve
Fixes: #671

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