| 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 |