Merge lp:~elachuni/txstatsd/straight into lp:txstatsd

Proposed by Anthony Lenton
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
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.

To post a comment you must log in.
Revision history for this message
Sidnei da Silva (sidnei) wrote :

[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!

review: Approve
Revision history for this message
Anthony Lenton (elachuni) wrote :

Thanks Sidnei!
Fixes pushed up in rev. 100, marking as approved

Revision history for this message
Ubuntu One Server Tarmac Bot (ubuntuone-server-tarmac) wrote :
Download full text (19.6 KiB)

The attempt to merge lp:~elachuni/txstatsd/straight into lp:txstatsd failed. Below is the output from the failed tests.

txstatsd.tests.metrics.test_distinct
  TestDistinct
    test_all ... [OK]
  TestDistinctMetricReporter
    test_reports ... [OK]
  TestHash
    test_chi_square ... [SKIPPED]
    test_hash_chars ... [OK]
  TestPlugin
    test_factory ... [OK]
  TestZeros
    test_zeros ... [OK]
txstatsd.tests.metrics.test_histogrammetric
  TestHistogramReporterMetric
    test_histogram_histogram ... [OK]
    test_histogram_of_numbers_1_through_10000 ... [OK]
    test_histogram_with_zero_recorded_values ... [OK]
txstatsd.tests.metrics.test_metermetric
  TestDeriveMetricReporter
    test_fastpoll ... [OK]
    test_interface ... [OK]
txstatsd.tests.metrics.test_sli
  TestConditions
    test_above ... [OK]
    test_above_linear ... [OK]
    test_below ... [OK]
    test_below_linear ... [OK]
    test_between ... [OK]
  TestFactory
    test_configure ... [OK]
    test_configure_linear ... [OK]
  TestMetric
    test_clear ... [OK]
    test_count_all ... [OK]
    test_count_error ... [OK]
    test_count_threshold ... [OK]
    test_reports ... [OK]
  TestMetricLinear
    test_count_threshold ... [OK]
  TestParsing
    test_parse ... [OK]
txstatsd.tests.metrics.test_timermetric
  TestBlankTimerMetric
    test_count ... [OK]
    test_max ... [OK]
    test_mean ... [OK]
    test_min ... [OK]
    test_no_values ... [OK]
    test_percentiles ... [OK]
    test_rate ... [OK]
    test_std_dev ... [OK]
  TestTim...

lp:~elachuni/txstatsd/straight updated
101. By Anthony Lenton

Avoid using assertIsNone in tests, as the trial version on tarmac doesn't have that available.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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
6 import socket
7
8-from twisted.internet.defer import inlineCallbacks, returnValue
9-from twisted.internet.protocol import DatagramProtocol
10-from twisted.python import log
11-
12+try:
13+ import twisted
14+except ImportError:
15+ # If twisted is missing, still provide the non-twisted client
16+ pass
17+else:
18+ from txstatsd.protocol import (
19+ StatsDClientProtocol,
20+ TwistedStatsDClient,
21+ )
22+
23 from txstatsd.hashing import ConsistentHashRing
24
25
26-class StatsDClientProtocol(DatagramProtocol):
27- """A Twisted-based implementation of the StatsD client protocol.
28-
29- Data is sent via UDP to a StatsD server for aggregation.
30- """
31-
32- def __init__(self, client):
33- self.client = client
34-
35- def startProtocol(self):
36- """Connect to destination host."""
37- self.client.connect(self.transport)
38-
39- def stopProtocol(self):
40- """Connection was lost."""
41- self.client.disconnect()
42-
43-
44-class TwistedStatsDClient(object):
45-
46- def __init__(self, host, port,
47- connect_callback=None,
48- disconnect_callback=None,
49- resolver_errback=None):
50- """
51- Build a connection that reports to the endpoint (on C{host} and
52- C{port}) using UDP.
53-
54- @param host: The StatsD server host.
55- @param port: The StatsD server port.
56- @param resolver_errback: The errback to invoke should
57- issues occur resolving the supplied C{host}.
58- @param connect_callback: The callback to invoke on connection.
59- @param disconnect_callback: The callback to invoke on disconnection.
60- """
61- from twisted.internet import reactor
62-
63- self.reactor = reactor
64-
65- @inlineCallbacks
66- def resolve(host):
67- self.host = yield reactor.resolve(host)
68- returnValue(self.host)
69-
70- self.original_host = host
71- self.host = None
72- self.resolver = resolve(host)
73- if resolver_errback is None:
74- self.resolver.addErrback(log.err)
75- else:
76- self.resolver.addErrback(resolver_errback)
77-
78- self.port = port
79- self.connect_callback = connect_callback
80- self.disconnect_callback = disconnect_callback
81-
82- self.transport = None
83-
84- def __str__(self):
85- return "%s:%d" % (self.original_host, self.port)
86-
87- @inlineCallbacks
88- def connect(self, transport=None):
89- """Connect to the StatsD server."""
90- host = yield self.resolver
91- if host is not None:
92- self.transport = transport
93- if self.transport is not None:
94- if self.connect_callback is not None:
95- self.connect_callback()
96-
97- def disconnect(self):
98- """Disconnect from the StatsD server."""
99- if self.disconnect_callback is not None:
100- self.disconnect_callback()
101- self.transport = None
102-
103- def write(self, data, callback=None):
104- """Send the metric to the StatsD server.
105-
106- @param data: The data to be sent.
107- @param callback: The callback to which the result should be sent.
108- B{Note}: The C{callback} will be called in the C{reactor}
109- thread, and not in the thread of the original caller.
110- """
111- self.reactor.callFromThread(self._write, data, callback)
112-
113- def _write(self, data, callback):
114- """Send the metric to the StatsD server.
115-
116- @param data: The data to be sent.
117- @param callback: The callback to which the result should be sent.
118- @raise twisted.internet.error.MessageLengthError: If the size of data
119- is too large.
120- """
121- if self.host is not None and self.transport is not None:
122- try:
123- bytes_sent = self.transport.write(data, (self.host, self.port))
124- if callback is not None:
125- callback(bytes_sent)
126- except (OverflowError, TypeError, socket.error, socket.gaierror):
127- if callback is not None:
128- callback(None)
129-
130-
131 class UdpStatsDClient(object):
132
133 def __init__(self, host=None, port=None):
134
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+# Copyright (C) 2011-2012 Canonical Services Ltd
140+#
141+# Permission is hereby granted, free of charge, to any person obtaining
142+# a copy of this software and associated documentation files (the
143+# "Software"), to deal in the Software without restriction, including
144+# without limitation the rights to use, copy, modify, merge, publish,
145+# distribute, sublicense, and/or sell copies of the Software, and to
146+# permit persons to whom the Software is furnished to do so, subject to
147+# the following conditions:
148+#
149+# The above copyright notice and this permission notice shall be
150+# included in all copies or substantial portions of the Software.
151+#
152+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
153+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
154+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
155+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
156+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
157+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
158+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
159+
160+import socket
161+
162+from twisted.internet.defer import inlineCallbacks, returnValue
163+from twisted.internet.protocol import DatagramProtocol
164+from twisted.python import log
165+
166+
167+class StatsDClientProtocol(DatagramProtocol):
168+ """A Twisted-based implementation of the StatsD client protocol.
169+
170+ Data is sent via UDP to a StatsD server for aggregation.
171+ """
172+
173+ def __init__(self, client):
174+ self.client = client
175+
176+ def startProtocol(self):
177+ """Connect to destination host."""
178+ self.client.connect(self.transport)
179+
180+ def stopProtocol(self):
181+ """Connection was lost."""
182+ self.client.disconnect()
183+
184+
185+class TwistedStatsDClient(object):
186+
187+ def __init__(self, host, port,
188+ connect_callback=None,
189+ disconnect_callback=None,
190+ resolver_errback=None):
191+ """
192+ Build a connection that reports to the endpoint (on C{host} and
193+ C{port}) using UDP.
194+
195+ @param host: The StatsD server host.
196+ @param port: The StatsD server port.
197+ @param resolver_errback: The errback to invoke should
198+ issues occur resolving the supplied C{host}.
199+ @param connect_callback: The callback to invoke on connection.
200+ @param disconnect_callback: The callback to invoke on disconnection.
201+ """
202+ from twisted.internet import reactor
203+
204+ self.reactor = reactor
205+
206+ @inlineCallbacks
207+ def resolve(host):
208+ self.host = yield reactor.resolve(host)
209+ returnValue(self.host)
210+
211+ self.original_host = host
212+ self.host = None
213+ self.resolver = resolve(host)
214+ if resolver_errback is None:
215+ self.resolver.addErrback(log.err)
216+ else:
217+ self.resolver.addErrback(resolver_errback)
218+
219+ self.port = port
220+ self.connect_callback = connect_callback
221+ self.disconnect_callback = disconnect_callback
222+
223+ self.transport = None
224+
225+ def __str__(self):
226+ return "%s:%d" % (self.original_host, self.port)
227+
228+ @inlineCallbacks
229+ def connect(self, transport=None):
230+ """Connect to the StatsD server."""
231+ host = yield self.resolver
232+ if host is not None:
233+ self.transport = transport
234+ if self.transport is not None:
235+ if self.connect_callback is not None:
236+ self.connect_callback()
237+
238+ def disconnect(self):
239+ """Disconnect from the StatsD server."""
240+ if self.disconnect_callback is not None:
241+ self.disconnect_callback()
242+ self.transport = None
243+
244+ def write(self, data, callback=None):
245+ """Send the metric to the StatsD server.
246+
247+ @param data: The data to be sent.
248+ @param callback: The callback to which the result should be sent.
249+ B{Note}: The C{callback} will be called in the C{reactor}
250+ thread, and not in the thread of the original caller.
251+ """
252+ self.reactor.callFromThread(self._write, data, callback)
253+
254+ def _write(self, data, callback):
255+ """Send the metric to the StatsD server.
256+
257+ @param data: The data to be sent.
258+ @param callback: The callback to which the result should be sent.
259+ @raise twisted.internet.error.MessageLengthError: If the size of data
260+ is too large.
261+ """
262+ if self.host is not None and self.transport is not None:
263+ try:
264+ bytes_sent = self.transport.write(data, (self.host, self.port))
265+ if callback is not None:
266+ callback(bytes_sent)
267+ except (OverflowError, TypeError, socket.error, socket.gaierror):
268+ if callback is not None:
269+ callback(None)
270
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 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
276 """Tests for the various client classes."""
277
278+import sys
279 from twisted.internet import reactor
280 from twisted.internet.defer import inlineCallbacks, Deferred
281 from twisted.python import log
282 from twisted.trial.unittest import TestCase
283
284+import txstatsd.client
285+import txstatsd.metrics.metric
286+import txstatsd.metrics.metrics
287 from txstatsd.metrics.metric import Metric
288 from txstatsd.client import (
289 StatsDClientProtocol, TwistedStatsDClient, UdpStatsDClient,
290@@ -128,6 +132,29 @@
291 # setblocking(0) is the same as settimeout(0.0).
292 self.assertEqual(client.socket.gettimeout(), 0.0)
293
294+ def test_udp_client_can_be_imported_without_twisted(self):
295+ """Ensure that the twisted-less client can be used without twisted."""
296+ unloaded = [(name, mod) for (name, mod) in sys.modules.items()
297+ if 'twisted' in name]
298+ def restore_modules():
299+ for name, mod in unloaded:
300+ sys.modules[name] = mod
301+ reload(txstatsd.client)
302+ reload(txstatsd.metrics.metrics)
303+ reload(txstatsd.metrics.metric)
304+ self.addCleanup(restore_modules)
305+
306+ # Mark everything twistedish as unavailable
307+ for name, mod in unloaded:
308+ sys.modules[name] = None
309+
310+ reload(txstatsd.client)
311+ reload(txstatsd.metrics.metrics)
312+ reload(txstatsd.metrics.metric)
313+ for mod in sys.modules:
314+ if 'twisted' in mod:
315+ self.assertTrue(sys.modules[mod] is None)
316+
317
318 class TestConsistentHashingClient(TestCase):
319

Subscribers

People subscribed via source and target branches