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