~launchpad-pqm/launchpad/devel

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)