~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
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)