12221.16.16
by Andrew Bennetts
Reorder some imports and update copyright years as requested by Aaron's review. |
1 |
# Copyright 2011 Canonical Ltd. This software is licensed under the
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
3 |
||
4 |
"""Utilities for graceful shutdown of Twisted services."""
|
|
5 |
||
6 |
__metaclass__ = type |
|
7 |
__all__ = [ |
|
12221.16.10
by Andrew Bennetts
Add some tests for ConnTrackingFactoryWrapper. |
8 |
'ConnTrackingFactoryWrapper', |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
9 |
'ShutdownCleanlyService', |
10 |
'ServerAvailableResource', |
|
11 |
'OrderedMultiService', |
|
12 |
]
|
|
13 |
||
14 |
||
12221.16.13
by Andrew Bennetts
Add make_web_status_service function to simplify the tac file. |
15 |
from twisted.application import service, strports |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
16 |
from twisted.protocols.policies import WrappingFactory |
17 |
from twisted.internet.defer import ( |
|
12221.16.16
by Andrew Bennetts
Reorder some imports and update copyright years as requested by Aaron's review. |
18 |
Deferred, |
19 |
gatherResults, |
|
20 |
inlineCallbacks, |
|
21 |
maybeDeferred, |
|
22 |
)
|
|
12221.16.13
by Andrew Bennetts
Add make_web_status_service function to simplify the tac file. |
23 |
from twisted.web import resource, server |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
24 |
from zope.interface import implements |
25 |
||
12221.16.10
by Andrew Bennetts
Add some tests for ConnTrackingFactoryWrapper. |
26 |
|
27 |
class ConnTrackingFactoryWrapper(WrappingFactory): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
28 |
"""A factory decorator that tracks the current connections made by this
|
29 |
factory.
|
|
30 |
"""
|
|
31 |
||
32 |
def __init__(self, wrappedFactory): |
|
33 |
"""Constructor.
|
|
34 |
||
35 |
See WrappingFactory.__init__.
|
|
36 |
"""
|
|
37 |
WrappingFactory.__init__(self, wrappedFactory) |
|
38 |
self.allConnectionsGone = None |
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
39 |
|
40 |
def isAvailable(self): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
41 |
"""Has this factory been stopped yet?"""
|
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
42 |
return self.allConnectionsGone is None |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
43 |
|
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
44 |
def stopFactory(self): |
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
45 |
"""See WrappingFactory.stopFactory."""
|
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
46 |
WrappingFactory.stopFactory(self) |
47 |
self.allConnectionsGone = Deferred() |
|
12221.16.10
by Andrew Bennetts
Add some tests for ConnTrackingFactoryWrapper. |
48 |
if len(self.protocols) == 0: |
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
49 |
self.allConnectionsGone.callback(None) |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
50 |
|
51 |
def unregisterProtocol(self, p): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
52 |
"""See WrappingFactory.unregisterProtocol."""
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
53 |
WrappingFactory.unregisterProtocol(self, p) |
54 |
if len(self.protocols) == 0: |
|
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
55 |
if self.allConnectionsGone is not None: |
56 |
self.allConnectionsGone.callback(None) |
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
57 |
|
58 |
||
59 |
class ShutdownCleanlyService(service.MultiService): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
60 |
"""A MultiService that doesn't stop until all connections of its factories
|
61 |
are closed.
|
|
62 |
||
63 |
This allows delaying a twistd process exiting until all clients have
|
|
64 |
disconnected from a server, for instance.
|
|
65 |
"""
|
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
66 |
|
12221.16.13
by Andrew Bennetts
Add make_web_status_service function to simplify the tac file. |
67 |
def __init__(self, factories): |
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
68 |
"""Constructor.
|
69 |
||
12221.16.18
by Andrew Bennetts
Trivial tweaks to shrink the lint report. |
70 |
:param factories: A collection of ConnTrackingFactoryWrapper
|
71 |
instances.
|
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
72 |
"""
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
73 |
self.factories = factories |
74 |
service.MultiService.__init__(self) |
|
75 |
||
76 |
def stopService(self): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
77 |
"""See service.MultiService.stopService."""
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
78 |
d = maybeDeferred(service.MultiService.stopService, self) |
79 |
return d.addCallback(self._cbServicesStopped) |
|
80 |
||
81 |
def _cbServicesStopped(self, ignored): |
|
12221.16.14
by Andrew Bennetts
Remove allConnectionsDone method, just use stopFactory. |
82 |
return gatherResults([f.allConnectionsGone for f in self.factories]) |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
83 |
|
84 |
||
85 |
class ServerAvailableResource(resource.Resource): |
|
12221.16.18
by Andrew Bennetts
Trivial tweaks to shrink the lint report. |
86 |
"""A Resource indicating if a service is available for new connections.
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
87 |
|
88 |
A 200 response code (OK) indicates the service is available, and a 503
|
|
89 |
(Service Not Available) indicates the service is shutting down and no new
|
|
90 |
connections will be accepted.
|
|
91 |
||
92 |
This resource accepts both HEAD and GET requests. If the request is a GET
|
|
93 |
this resource also reports the number of connections and their peer
|
|
94 |
addresses in a human-friendly text/plain body.
|
|
95 |
"""
|
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
96 |
|
97 |
def __init__(self, tracked_factories): |
|
98 |
resource.Resource.__init__(self) |
|
99 |
self.tracked_factories = tracked_factories |
|
100 |
||
12221.16.9
by Andrew Bennetts
Avoid wasting effort generating a body for HEAD requests. |
101 |
def _render_common(self, request): |
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
102 |
service_available = True |
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
103 |
for tracked in self.tracked_factories: |
104 |
if not tracked.isAvailable(): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
105 |
service_available = False |
106 |
if service_available: |
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
107 |
request.setResponseCode(200) |
108 |
else: |
|
109 |
request.setResponseCode(503) |
|
110 |
request.setHeader('Content-Type', 'text/plain') |
|
12221.16.9
by Andrew Bennetts
Avoid wasting effort generating a body for HEAD requests. |
111 |
|
112 |
def render_GET(self, request): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
113 |
"""Handler for GET requests. See resource.Resource.render."""
|
114 |
service_available = self._render_common(request) |
|
12221.16.11
by Andrew Bennetts
Add tests for ServiceAvailableResource and OrderedMultiService. |
115 |
# Generate a bit of text for humans' benefit.
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
116 |
tracked_connections = set() |
117 |
for tracked in self.tracked_factories: |
|
118 |
tracked_connections.update(tracked.protocols) |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
119 |
if service_available: |
120 |
state_text = 'Available' |
|
121 |
else: |
|
122 |
state_text = 'Unavailable' |
|
12221.16.11
by Andrew Bennetts
Add tests for ServiceAvailableResource and OrderedMultiService. |
123 |
return '%s\n\n%d connections: \n\n%s\n' % ( |
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
124 |
state_text, len(tracked_connections), |
12221.16.8
by Andrew Bennetts
Tweak web page text. |
125 |
'\n'.join( |
126 |
[str(c.transport.getPeer()) for c in tracked_connections])) |
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
127 |
|
12221.16.9
by Andrew Bennetts
Avoid wasting effort generating a body for HEAD requests. |
128 |
def render_HEAD(self, request): |
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
129 |
"""Handler for HEAD requests. See resource.Resource.render."""
|
12221.16.9
by Andrew Bennetts
Avoid wasting effort generating a body for HEAD requests. |
130 |
self._render_common(request) |
131 |
return '' |
|
132 |
||
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
133 |
|
134 |
class OrderedMultiService(service.MultiService): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
135 |
"""A MultiService that guarantees start and stop order.
|
136 |
||
137 |
Services are started in the order they are attached, and stopped in in
|
|
138 |
reverse order (waiting for each to stop before stopping the next).
|
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
139 |
"""
|
140 |
||
141 |
implements(service.IServiceCollection) |
|
142 |
||
143 |
@inlineCallbacks
|
|
144 |
def stopService(self): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
145 |
"""See service.MultiService.stopService."""
|
12221.16.18
by Andrew Bennetts
Trivial tweaks to shrink the lint report. |
146 |
# intentionally skip MultiService.stopService
|
147 |
service.Service.stopService(self) |
|
12221.16.4
by Andrew Bennetts
Move new stuff to new lp.services.twistedsupport.gracefulshutdown module, add OrderedMultiService. |
148 |
while self.services: |
149 |
svc = self.services.pop() |
|
150 |
yield maybeDeferred(svc.stopService) |
|
151 |
||
12221.16.13
by Andrew Bennetts
Add make_web_status_service function to simplify the tac file. |
152 |
|
153 |
def make_web_status_service(strport, tracking_factories): |
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
154 |
"""Make a web site of ServerAvailableResource on a given port.
|
155 |
||
156 |
See daemons/sftp.tac for an example use.
|
|
157 |
||
12221.16.18
by Andrew Bennetts
Trivial tweaks to shrink the lint report. |
158 |
:param strport: a strport describing the port the web service should
|
159 |
listen on.
|
|
12221.16.15
by Andrew Bennetts
Docstrings and cosmetic code changes requested by Aaron's review. |
160 |
:param tracking_factories: a collection of ConnTrackingFactoryWrapper
|
161 |
instances.
|
|
162 |
:returns: a service.Service
|
|
163 |
"""
|
|
12221.16.13
by Andrew Bennetts
Add make_web_status_service function to simplify the tac file. |
164 |
server_available_resource = ServerAvailableResource(tracking_factories) |
165 |
web_root = resource.Resource() |
|
166 |
web_root.putChild('', server_available_resource) |
|
167 |
web_factory = server.Site(web_root) |
|
168 |
return strports.service(strport, web_factory) |