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