Merge lp:~elachuni/txstatsd/straight into lp:txstatsd
- straight
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Anthony Lenton |
Approved revision: | 101 |
Merged at revision: | 99 |
Proposed branch: | lp:~elachuni/txstatsd/straight |
Merge into: | lp:txstatsd |
Diff against target: |
318 lines (+169/-109) 3 files modified
txstatsd/client.py (+11/-109) txstatsd/protocol.py (+131/-0) txstatsd/tests/test_client.py (+27/-0) |
To merge this branch: | bzr merge lp:~elachuni/txstatsd/straight |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sidnei da Silva | Approve | ||
Review via email: mp+114879@code.launchpad.net |
Commit message
Made it possible to use the non-twisted client code even if twisted is unavailable.
Description of the change
This branch makes it possible to import the non-twisted client code without having twisted installed.
It also adds a test that ensures this can be installed, by marking everything twisted-ish as unavailable and then reloading the txstats client and metrics modules.
The code works as intended afaict, and the test fails if you add a random import twisted.something anywhere in txstatsd.client or txstatsd.metrics, but I'm still unsure if it's the best way to test. I tried a couple of permutations with import hooks, but that was even messier.
Anthony Lenton (elachuni) wrote : | # |
Thanks Sidnei!
Fixes pushed up in rev. 100, marking as approved
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote : | # |
The attempt to merge lp:~elachuni/txstatsd/straight into lp:txstatsd failed. Below is the output from the failed tests.
txstatsd.
TestDistinct
test_all ... [OK]
TestDistinctM
test_reports ... [OK]
TestHash
test_chi_square ... [SKIPPED]
test_hash_chars ... [OK]
TestPlugin
test_factory ... [OK]
TestZeros
test_zeros ... [OK]
txstatsd.
TestHistogram
test_
test_
test_
txstatsd.
TestDeriveMet
test_fastpoll ... [OK]
test_interface ... [OK]
txstatsd.
TestConditions
test_above ... [OK]
test_
test_below ... [OK]
test_
test_between ... [OK]
TestFactory
test_configure ... [OK]
test_
TestMetric
test_clear ... [OK]
test_count_all ... [OK]
test_
test_
test_reports ... [OK]
TestMetricLinear
test_
TestParsing
test_parse ... [OK]
txstatsd.
TestBlankTime
test_count ... [OK]
test_max ... [OK]
test_mean ... [OK]
test_min ... [OK]
test_no_values ... [OK]
test_
test_rate ... [OK]
test_std_dev ... [OK]
TestTim...
- 101. By Anthony Lenton
-
Avoid using assertIsNone in tests, as the trial version on tarmac doesn't have that available.
Preview Diff
1 | === modified file 'txstatsd/client.py' | |||
2 | --- txstatsd/client.py 2012-06-28 17:29:26 +0000 | |||
3 | +++ txstatsd/client.py 2012-07-13 18:01:22 +0000 | |||
4 | @@ -21,118 +21,20 @@ | |||
5 | 21 | 21 | ||
6 | 22 | import socket | 22 | import socket |
7 | 23 | 23 | ||
12 | 24 | from twisted.internet.defer import inlineCallbacks, returnValue | 24 | try: |
13 | 25 | from twisted.internet.protocol import DatagramProtocol | 25 | import twisted |
14 | 26 | from twisted.python import log | 26 | except ImportError: |
15 | 27 | 27 | # If twisted is missing, still provide the non-twisted client | |
16 | 28 | pass | ||
17 | 29 | else: | ||
18 | 30 | from txstatsd.protocol import ( | ||
19 | 31 | StatsDClientProtocol, | ||
20 | 32 | TwistedStatsDClient, | ||
21 | 33 | ) | ||
22 | 34 | |||
23 | 28 | from txstatsd.hashing import ConsistentHashRing | 35 | from txstatsd.hashing import ConsistentHashRing |
24 | 29 | 36 | ||
25 | 30 | 37 | ||
26 | 31 | class StatsDClientProtocol(DatagramProtocol): | ||
27 | 32 | """A Twisted-based implementation of the StatsD client protocol. | ||
28 | 33 | |||
29 | 34 | Data is sent via UDP to a StatsD server for aggregation. | ||
30 | 35 | """ | ||
31 | 36 | |||
32 | 37 | def __init__(self, client): | ||
33 | 38 | self.client = client | ||
34 | 39 | |||
35 | 40 | def startProtocol(self): | ||
36 | 41 | """Connect to destination host.""" | ||
37 | 42 | self.client.connect(self.transport) | ||
38 | 43 | |||
39 | 44 | def stopProtocol(self): | ||
40 | 45 | """Connection was lost.""" | ||
41 | 46 | self.client.disconnect() | ||
42 | 47 | |||
43 | 48 | |||
44 | 49 | class TwistedStatsDClient(object): | ||
45 | 50 | |||
46 | 51 | def __init__(self, host, port, | ||
47 | 52 | connect_callback=None, | ||
48 | 53 | disconnect_callback=None, | ||
49 | 54 | resolver_errback=None): | ||
50 | 55 | """ | ||
51 | 56 | Build a connection that reports to the endpoint (on C{host} and | ||
52 | 57 | C{port}) using UDP. | ||
53 | 58 | |||
54 | 59 | @param host: The StatsD server host. | ||
55 | 60 | @param port: The StatsD server port. | ||
56 | 61 | @param resolver_errback: The errback to invoke should | ||
57 | 62 | issues occur resolving the supplied C{host}. | ||
58 | 63 | @param connect_callback: The callback to invoke on connection. | ||
59 | 64 | @param disconnect_callback: The callback to invoke on disconnection. | ||
60 | 65 | """ | ||
61 | 66 | from twisted.internet import reactor | ||
62 | 67 | |||
63 | 68 | self.reactor = reactor | ||
64 | 69 | |||
65 | 70 | @inlineCallbacks | ||
66 | 71 | def resolve(host): | ||
67 | 72 | self.host = yield reactor.resolve(host) | ||
68 | 73 | returnValue(self.host) | ||
69 | 74 | |||
70 | 75 | self.original_host = host | ||
71 | 76 | self.host = None | ||
72 | 77 | self.resolver = resolve(host) | ||
73 | 78 | if resolver_errback is None: | ||
74 | 79 | self.resolver.addErrback(log.err) | ||
75 | 80 | else: | ||
76 | 81 | self.resolver.addErrback(resolver_errback) | ||
77 | 82 | |||
78 | 83 | self.port = port | ||
79 | 84 | self.connect_callback = connect_callback | ||
80 | 85 | self.disconnect_callback = disconnect_callback | ||
81 | 86 | |||
82 | 87 | self.transport = None | ||
83 | 88 | |||
84 | 89 | def __str__(self): | ||
85 | 90 | return "%s:%d" % (self.original_host, self.port) | ||
86 | 91 | |||
87 | 92 | @inlineCallbacks | ||
88 | 93 | def connect(self, transport=None): | ||
89 | 94 | """Connect to the StatsD server.""" | ||
90 | 95 | host = yield self.resolver | ||
91 | 96 | if host is not None: | ||
92 | 97 | self.transport = transport | ||
93 | 98 | if self.transport is not None: | ||
94 | 99 | if self.connect_callback is not None: | ||
95 | 100 | self.connect_callback() | ||
96 | 101 | |||
97 | 102 | def disconnect(self): | ||
98 | 103 | """Disconnect from the StatsD server.""" | ||
99 | 104 | if self.disconnect_callback is not None: | ||
100 | 105 | self.disconnect_callback() | ||
101 | 106 | self.transport = None | ||
102 | 107 | |||
103 | 108 | def write(self, data, callback=None): | ||
104 | 109 | """Send the metric to the StatsD server. | ||
105 | 110 | |||
106 | 111 | @param data: The data to be sent. | ||
107 | 112 | @param callback: The callback to which the result should be sent. | ||
108 | 113 | B{Note}: The C{callback} will be called in the C{reactor} | ||
109 | 114 | thread, and not in the thread of the original caller. | ||
110 | 115 | """ | ||
111 | 116 | self.reactor.callFromThread(self._write, data, callback) | ||
112 | 117 | |||
113 | 118 | def _write(self, data, callback): | ||
114 | 119 | """Send the metric to the StatsD server. | ||
115 | 120 | |||
116 | 121 | @param data: The data to be sent. | ||
117 | 122 | @param callback: The callback to which the result should be sent. | ||
118 | 123 | @raise twisted.internet.error.MessageLengthError: If the size of data | ||
119 | 124 | is too large. | ||
120 | 125 | """ | ||
121 | 126 | if self.host is not None and self.transport is not None: | ||
122 | 127 | try: | ||
123 | 128 | bytes_sent = self.transport.write(data, (self.host, self.port)) | ||
124 | 129 | if callback is not None: | ||
125 | 130 | callback(bytes_sent) | ||
126 | 131 | except (OverflowError, TypeError, socket.error, socket.gaierror): | ||
127 | 132 | if callback is not None: | ||
128 | 133 | callback(None) | ||
129 | 134 | |||
130 | 135 | |||
131 | 136 | class UdpStatsDClient(object): | 38 | class UdpStatsDClient(object): |
132 | 137 | 39 | ||
133 | 138 | def __init__(self, host=None, port=None): | 40 | def __init__(self, host=None, port=None): |
134 | 139 | 41 | ||
135 | === added file 'txstatsd/protocol.py' | |||
136 | --- txstatsd/protocol.py 1970-01-01 00:00:00 +0000 | |||
137 | +++ txstatsd/protocol.py 2012-07-13 18:01:22 +0000 | |||
138 | @@ -0,0 +1,131 @@ | |||
139 | 1 | # Copyright (C) 2011-2012 Canonical Services Ltd | ||
140 | 2 | # | ||
141 | 3 | # Permission is hereby granted, free of charge, to any person obtaining | ||
142 | 4 | # a copy of this software and associated documentation files (the | ||
143 | 5 | # "Software"), to deal in the Software without restriction, including | ||
144 | 6 | # without limitation the rights to use, copy, modify, merge, publish, | ||
145 | 7 | # distribute, sublicense, and/or sell copies of the Software, and to | ||
146 | 8 | # permit persons to whom the Software is furnished to do so, subject to | ||
147 | 9 | # the following conditions: | ||
148 | 10 | # | ||
149 | 11 | # The above copyright notice and this permission notice shall be | ||
150 | 12 | # included in all copies or substantial portions of the Software. | ||
151 | 13 | # | ||
152 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
153 | 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
154 | 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
155 | 17 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
156 | 18 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
157 | 19 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
158 | 20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
159 | 21 | |||
160 | 22 | import socket | ||
161 | 23 | |||
162 | 24 | from twisted.internet.defer import inlineCallbacks, returnValue | ||
163 | 25 | from twisted.internet.protocol import DatagramProtocol | ||
164 | 26 | from twisted.python import log | ||
165 | 27 | |||
166 | 28 | |||
167 | 29 | class StatsDClientProtocol(DatagramProtocol): | ||
168 | 30 | """A Twisted-based implementation of the StatsD client protocol. | ||
169 | 31 | |||
170 | 32 | Data is sent via UDP to a StatsD server for aggregation. | ||
171 | 33 | """ | ||
172 | 34 | |||
173 | 35 | def __init__(self, client): | ||
174 | 36 | self.client = client | ||
175 | 37 | |||
176 | 38 | def startProtocol(self): | ||
177 | 39 | """Connect to destination host.""" | ||
178 | 40 | self.client.connect(self.transport) | ||
179 | 41 | |||
180 | 42 | def stopProtocol(self): | ||
181 | 43 | """Connection was lost.""" | ||
182 | 44 | self.client.disconnect() | ||
183 | 45 | |||
184 | 46 | |||
185 | 47 | class TwistedStatsDClient(object): | ||
186 | 48 | |||
187 | 49 | def __init__(self, host, port, | ||
188 | 50 | connect_callback=None, | ||
189 | 51 | disconnect_callback=None, | ||
190 | 52 | resolver_errback=None): | ||
191 | 53 | """ | ||
192 | 54 | Build a connection that reports to the endpoint (on C{host} and | ||
193 | 55 | C{port}) using UDP. | ||
194 | 56 | |||
195 | 57 | @param host: The StatsD server host. | ||
196 | 58 | @param port: The StatsD server port. | ||
197 | 59 | @param resolver_errback: The errback to invoke should | ||
198 | 60 | issues occur resolving the supplied C{host}. | ||
199 | 61 | @param connect_callback: The callback to invoke on connection. | ||
200 | 62 | @param disconnect_callback: The callback to invoke on disconnection. | ||
201 | 63 | """ | ||
202 | 64 | from twisted.internet import reactor | ||
203 | 65 | |||
204 | 66 | self.reactor = reactor | ||
205 | 67 | |||
206 | 68 | @inlineCallbacks | ||
207 | 69 | def resolve(host): | ||
208 | 70 | self.host = yield reactor.resolve(host) | ||
209 | 71 | returnValue(self.host) | ||
210 | 72 | |||
211 | 73 | self.original_host = host | ||
212 | 74 | self.host = None | ||
213 | 75 | self.resolver = resolve(host) | ||
214 | 76 | if resolver_errback is None: | ||
215 | 77 | self.resolver.addErrback(log.err) | ||
216 | 78 | else: | ||
217 | 79 | self.resolver.addErrback(resolver_errback) | ||
218 | 80 | |||
219 | 81 | self.port = port | ||
220 | 82 | self.connect_callback = connect_callback | ||
221 | 83 | self.disconnect_callback = disconnect_callback | ||
222 | 84 | |||
223 | 85 | self.transport = None | ||
224 | 86 | |||
225 | 87 | def __str__(self): | ||
226 | 88 | return "%s:%d" % (self.original_host, self.port) | ||
227 | 89 | |||
228 | 90 | @inlineCallbacks | ||
229 | 91 | def connect(self, transport=None): | ||
230 | 92 | """Connect to the StatsD server.""" | ||
231 | 93 | host = yield self.resolver | ||
232 | 94 | if host is not None: | ||
233 | 95 | self.transport = transport | ||
234 | 96 | if self.transport is not None: | ||
235 | 97 | if self.connect_callback is not None: | ||
236 | 98 | self.connect_callback() | ||
237 | 99 | |||
238 | 100 | def disconnect(self): | ||
239 | 101 | """Disconnect from the StatsD server.""" | ||
240 | 102 | if self.disconnect_callback is not None: | ||
241 | 103 | self.disconnect_callback() | ||
242 | 104 | self.transport = None | ||
243 | 105 | |||
244 | 106 | def write(self, data, callback=None): | ||
245 | 107 | """Send the metric to the StatsD server. | ||
246 | 108 | |||
247 | 109 | @param data: The data to be sent. | ||
248 | 110 | @param callback: The callback to which the result should be sent. | ||
249 | 111 | B{Note}: The C{callback} will be called in the C{reactor} | ||
250 | 112 | thread, and not in the thread of the original caller. | ||
251 | 113 | """ | ||
252 | 114 | self.reactor.callFromThread(self._write, data, callback) | ||
253 | 115 | |||
254 | 116 | def _write(self, data, callback): | ||
255 | 117 | """Send the metric to the StatsD server. | ||
256 | 118 | |||
257 | 119 | @param data: The data to be sent. | ||
258 | 120 | @param callback: The callback to which the result should be sent. | ||
259 | 121 | @raise twisted.internet.error.MessageLengthError: If the size of data | ||
260 | 122 | is too large. | ||
261 | 123 | """ | ||
262 | 124 | if self.host is not None and self.transport is not None: | ||
263 | 125 | try: | ||
264 | 126 | bytes_sent = self.transport.write(data, (self.host, self.port)) | ||
265 | 127 | if callback is not None: | ||
266 | 128 | callback(bytes_sent) | ||
267 | 129 | except (OverflowError, TypeError, socket.error, socket.gaierror): | ||
268 | 130 | if callback is not None: | ||
269 | 131 | callback(None) | ||
270 | 0 | 132 | ||
271 | === modified file 'txstatsd/tests/test_client.py' | |||
272 | --- txstatsd/tests/test_client.py 2012-06-28 17:29:26 +0000 | |||
273 | +++ txstatsd/tests/test_client.py 2012-07-13 18:01:22 +0000 | |||
274 | @@ -20,11 +20,15 @@ | |||
275 | 20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
276 | 21 | """Tests for the various client classes.""" | 21 | """Tests for the various client classes.""" |
277 | 22 | 22 | ||
278 | 23 | import sys | ||
279 | 23 | from twisted.internet import reactor | 24 | from twisted.internet import reactor |
280 | 24 | from twisted.internet.defer import inlineCallbacks, Deferred | 25 | from twisted.internet.defer import inlineCallbacks, Deferred |
281 | 25 | from twisted.python import log | 26 | from twisted.python import log |
282 | 26 | from twisted.trial.unittest import TestCase | 27 | from twisted.trial.unittest import TestCase |
283 | 27 | 28 | ||
284 | 29 | import txstatsd.client | ||
285 | 30 | import txstatsd.metrics.metric | ||
286 | 31 | import txstatsd.metrics.metrics | ||
287 | 28 | from txstatsd.metrics.metric import Metric | 32 | from txstatsd.metrics.metric import Metric |
288 | 29 | from txstatsd.client import ( | 33 | from txstatsd.client import ( |
289 | 30 | StatsDClientProtocol, TwistedStatsDClient, UdpStatsDClient, | 34 | StatsDClientProtocol, TwistedStatsDClient, UdpStatsDClient, |
290 | @@ -128,6 +132,29 @@ | |||
291 | 128 | # setblocking(0) is the same as settimeout(0.0). | 132 | # setblocking(0) is the same as settimeout(0.0). |
292 | 129 | self.assertEqual(client.socket.gettimeout(), 0.0) | 133 | self.assertEqual(client.socket.gettimeout(), 0.0) |
293 | 130 | 134 | ||
294 | 135 | def test_udp_client_can_be_imported_without_twisted(self): | ||
295 | 136 | """Ensure that the twisted-less client can be used without twisted.""" | ||
296 | 137 | unloaded = [(name, mod) for (name, mod) in sys.modules.items() | ||
297 | 138 | if 'twisted' in name] | ||
298 | 139 | def restore_modules(): | ||
299 | 140 | for name, mod in unloaded: | ||
300 | 141 | sys.modules[name] = mod | ||
301 | 142 | reload(txstatsd.client) | ||
302 | 143 | reload(txstatsd.metrics.metrics) | ||
303 | 144 | reload(txstatsd.metrics.metric) | ||
304 | 145 | self.addCleanup(restore_modules) | ||
305 | 146 | |||
306 | 147 | # Mark everything twistedish as unavailable | ||
307 | 148 | for name, mod in unloaded: | ||
308 | 149 | sys.modules[name] = None | ||
309 | 150 | |||
310 | 151 | reload(txstatsd.client) | ||
311 | 152 | reload(txstatsd.metrics.metrics) | ||
312 | 153 | reload(txstatsd.metrics.metric) | ||
313 | 154 | for mod in sys.modules: | ||
314 | 155 | if 'twisted' in mod: | ||
315 | 156 | self.assertTrue(sys.modules[mod] is None) | ||
316 | 157 | |||
317 | 131 | 158 | ||
318 | 132 | class TestConsistentHashingClient(TestCase): | 159 | class TestConsistentHashingClient(TestCase): |
319 | 133 | 160 |
[1] Please add the license header to protocol.py
[2] The restore_modules cleanup should reload() the modules again after restoring the unloaded twisted modules.
Once you fix those, set a commit message and flip the MP to approved, and tarmac will take care of landing it.
Thanks!