~ubuntu-branches/ubuntu/precise/ubuntu-sso-client/precise

« back to all changes in this revision

Viewing changes to ubuntu_sso/utils/tcpactivation.py

Tags: upstream-1.3.2
ImportĀ upstreamĀ versionĀ 1.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
 
 
3
# Author: Alejandro J. Cura <alecu@canonical.com>
 
4
#
 
5
# Copyright 2011 Canonical Ltd.
 
6
#
 
7
# This program is free software: you can redistribute it and/or modify it
 
8
# under the terms of the GNU General Public License version 3, as published
 
9
# by the Free Software Foundation.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but
 
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
14
# PURPOSE.  See the GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along
 
17
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
"""tcpactivation: start a process if nothing listening in a given port."""
 
20
 
 
21
import subprocess
 
22
 
 
23
from twisted.internet import defer, protocol, reactor
 
24
 
 
25
LOCALHOST = "127.0.0.1"
 
26
DELAY_BETWEEN_CHECKS = 0.1
 
27
NUMBER_OF_CHECKS = 600
 
28
 
 
29
# twisted uses a different coding convention
 
30
# pylint: disable=C0103,W0232
 
31
 
 
32
 
 
33
def async_sleep(delay):
 
34
    """Fire the returned deferred after some specified delay."""
 
35
    d = defer.Deferred()
 
36
    # pylint: disable=E1101
 
37
    reactor.callLater(delay, d.callback, None)
 
38
    return d
 
39
 
 
40
 
 
41
class AlreadyStartedError(Exception):
 
42
    """The instance was already started."""
 
43
 
 
44
 
 
45
class ActivationTimeoutError(Exception):
 
46
    """Timeout while trying to start the instance."""
 
47
 
 
48
 
 
49
class NullProtocol(protocol.Protocol):
 
50
    """A protocol that drops the connection."""
 
51
 
 
52
    def connectionMade(self):
 
53
        """Just drop the connection."""
 
54
        self.transport.loseConnection()
 
55
 
 
56
 
 
57
class PortDetectFactory(protocol.ClientFactory):
 
58
    """Will detect if something is listening in a given port."""
 
59
 
 
60
    def __init__(self):
 
61
        """Initialize this instance."""
 
62
        self.d = defer.Deferred()
 
63
 
 
64
    def is_listening(self):
 
65
        """A deferred that will become True if something is listening."""
 
66
        return self.d
 
67
 
 
68
    def buildProtocol(self, addr):
 
69
        """Connected."""
 
70
        if not self.d.called:
 
71
            self.d.callback(True)
 
72
        return NullProtocol()
 
73
 
 
74
    def clientConnectionLost(self, connector, reason):
 
75
        """The connection was lost."""
 
76
        if not self.d.called:
 
77
            self.d.callback(False)
 
78
 
 
79
    def clientConnectionFailed(self, connector, reason):
 
80
        """The connection failed."""
 
81
        if not self.d.called:
 
82
            self.d.callback(False)
 
83
 
 
84
 
 
85
class ActivationConfig(object):
 
86
    """The configuration for tcp activation."""
 
87
 
 
88
    def __init__(self, service_name, command_line, port):
 
89
        """Initialize this instance."""
 
90
        self.service_name = service_name
 
91
        self.command_line = command_line
 
92
        self.port = port
 
93
 
 
94
 
 
95
class ActivationDetector(object):
 
96
    """Base class to detect if the service is running."""
 
97
 
 
98
    def __init__(self, config):
 
99
        """Initialize this instance."""
 
100
        self.config = config
 
101
 
 
102
    @defer.inlineCallbacks
 
103
    def is_already_running(self):
 
104
        """Check if the instance is already running."""
 
105
        factory = PortDetectFactory()
 
106
        # pylint: disable=E1101
 
107
        reactor.connectTCP(LOCALHOST, self.config.port, factory)
 
108
        result = yield factory.is_listening()
 
109
        defer.returnValue(result)
 
110
 
 
111
 
 
112
class ActivationClient(ActivationDetector):
 
113
    """A client for tcp activation."""
 
114
 
 
115
    # a classwide lock, so the server is started only once
 
116
    lock = defer.DeferredLock()
 
117
 
 
118
    @defer.inlineCallbacks
 
119
    def _wait_server_active(self):
 
120
        """Wait till the server is active."""
 
121
        for _ in xrange(NUMBER_OF_CHECKS):
 
122
            is_running = yield self.is_already_running()
 
123
            if is_running:
 
124
                defer.returnValue(None)
 
125
            yield async_sleep(DELAY_BETWEEN_CHECKS)
 
126
        raise ActivationTimeoutError()
 
127
 
 
128
    def _spawn_server(self):
 
129
        """Start running the server process."""
 
130
        # Without using close_fds=True, strange things happen
 
131
        # with logging on windows. More information at
 
132
        # http://bugs.python.org/issue4749
 
133
        subprocess.Popen(self.config.command_line, close_fds=True)
 
134
 
 
135
    @defer.inlineCallbacks
 
136
    def _do_get_active_port(self):
 
137
        """Get the port for the running instance, starting it if needed."""
 
138
        is_running = yield self.is_already_running()
 
139
        if not is_running:
 
140
            self._spawn_server()
 
141
            yield self._wait_server_active()
 
142
        defer.returnValue(self.config.port)
 
143
 
 
144
    @defer.inlineCallbacks
 
145
    def get_active_port(self):
 
146
        """Serialize the requests to _do_get_active_port."""
 
147
        yield self.lock.acquire()
 
148
        try:
 
149
            result = yield self._do_get_active_port()
 
150
            defer.returnValue(result)
 
151
        finally:
 
152
            self.lock.release()
 
153
 
 
154
 
 
155
class ActivationInstance(ActivationDetector):
 
156
    """A tcp activation server instance."""
 
157
 
 
158
    @defer.inlineCallbacks
 
159
    def get_port(self):
 
160
        """Get the port to run this service or fail if already started."""
 
161
        is_running = yield self.is_already_running()
 
162
        if is_running:
 
163
            raise AlreadyStartedError()
 
164
        defer.returnValue(self.config.port)