| 1 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 2 | # See LICENSE for details. |
|---|
| 3 | |
|---|
| 4 | """ |
|---|
| 5 | Service architecture for Twisted. |
|---|
| 6 | |
|---|
| 7 | Services are arranged in a hierarchy. At the leafs of the hierarchy, |
|---|
| 8 | the services which actually interact with the outside world are started. |
|---|
| 9 | Services can be named or anonymous -- usually, they will be named if |
|---|
| 10 | there is need to access them through the hierarchy (from a parent or |
|---|
| 11 | a sibling). |
|---|
| 12 | |
|---|
| 13 | Maintainer: Moshe Zadka |
|---|
| 14 | """ |
|---|
| 15 | |
|---|
| 16 | from zope.interface import implements, Interface, Attribute |
|---|
| 17 | |
|---|
| 18 | from twisted.python.reflect import namedAny |
|---|
| 19 | from twisted.python import components |
|---|
| 20 | from twisted.internet import defer |
|---|
| 21 | from twisted.persisted import sob |
|---|
| 22 | from twisted.plugin import IPlugin |
|---|
| 23 | |
|---|
| 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 | L{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. 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 | |
|---|
| 155 | class 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 | |
|---|
| 204 | class 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 | |
|---|
| 257 | class 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 | |
|---|
| 324 | class 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 | |
|---|
| 350 | class 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 | |
|---|
| 374 | def 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 | |
|---|
| 391 | def 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'] |
|---|