You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

service.py 12KB

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