Merge lp:~james-w/conn-check/split into lp:conn-check

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: 16
Merged at revision: 16
Proposed branch: lp:~james-w/conn-check/split
Merge into: lp:conn-check
Diff against target: 2210 lines (+1039/-984)
8 files modified
conn_check/__init__.py (+3/-938)
conn_check/check_impl.py (+325/-0)
conn_check/checks.py (+310/-0)
conn_check/main.py (+181/-0)
conn_check/patterns.py (+153/-0)
demo.yaml (+1/-1)
setup.py (+1/-1)
tests.py (+65/-44)
To merge this branch: bzr merge lp:~james-w/conn-check/split
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Review via email: mp+228217@code.launchpad.net

Commit message

Refactor to split up conn_check.py.

Description of the change

Hi,

Split up the big file.

Thanks,

James

To post a comment you must log in.
Revision history for this message
James Westby (james-w) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'conn_check/__init__.py'
2--- conn_check/__init__.py 2014-07-24 21:09:27 +0000
3+++ conn_check/__init__.py 2014-07-24 22:10:31 +0000
4@@ -3,41 +3,6 @@
5 """
6
7 import os
8-import re
9-import sys
10-import time
11-import glob
12-from pkg_resources import resource_stream
13-import traceback
14-import urlparse
15-import yaml
16-
17-from argparse import ArgumentParser
18-from threading import Thread
19-
20-from OpenSSL import SSL
21-from OpenSSL.crypto import load_certificate, FILETYPE_PEM
22-
23-from twisted.internet import epollreactor
24-epollreactor.install()
25-
26-from twisted.internet import reactor, ssl
27-from twisted.internet.defer import (
28- returnValue,
29- inlineCallbacks,
30- maybeDeferred,
31- DeferredList,
32- Deferred)
33-from twisted.internet.error import DNSLookupError, TimeoutError
34-from twisted.internet.abstract import isIPAddress
35-from twisted.internet.protocol import (
36- DatagramProtocol,
37- Protocol,
38- ClientCreator)
39-from twisted.python.failure import Failure
40-from twisted.python.threadpool import ThreadPool
41-from twisted.web.client import Agent
42-
43
44 def get_version_string():
45 return open(os.path.join(os.path.dirname(__file__),
46@@ -47,909 +12,9 @@
47 def get_version():
48 return get_version_string().split('.')
49
50+
51 __version__ = get_version_string()
52
53
54-CONNECT_TIMEOUT = 10
55-CA_CERTS = []
56-
57-for certFileName in glob.glob("/etc/ssl/certs/*.pem"):
58- # There might be some dead symlinks in there, so let's make sure it's real.
59- if os.path.exists(certFileName):
60- data = open(certFileName).read()
61- x509 = load_certificate(FILETYPE_PEM, data)
62- # Now, de-duplicate in case the same cert has multiple names.
63- CA_CERTS.append(x509)
64-
65-
66-class VerifyingContextFactory(ssl.CertificateOptions):
67-
68- def __init__(self, verify, caCerts, verifyCallback=None):
69- ssl.CertificateOptions.__init__(self, verify=verify,
70- caCerts=caCerts,
71- method=SSL.SSLv23_METHOD)
72- self.verifyCallback = verifyCallback
73-
74- def _makeContext(self):
75- context = ssl.CertificateOptions._makeContext(self)
76- if self.verifyCallback is not None:
77- context.set_verify(
78- SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
79- self.verifyCallback)
80- return context
81-
82-
83-def maybeDeferToThread(f, *args, **kwargs):
84- """
85- Call the function C{f} using a thread from the given threadpool and return
86- the result as a Deferred.
87-
88- @param f: The function to call. May return a deferred.
89- @param *args: positional arguments to pass to f.
90- @param **kwargs: keyword arguments to pass to f.
91-
92- @return: A Deferred which fires a callback with the result of f, or an
93- errback with a L{twisted.python.failure.Failure} if f throws an
94- exception.
95- """
96- threadpool = reactor.getThreadPool()
97-
98- d = Deferred()
99-
100- def realOnResult(result):
101- if not isinstance(result, Failure):
102- reactor.callFromThread(d.callback, result)
103- else:
104- reactor.callFromThread(d.errback, result)
105-
106- def onResult(success, result):
107- assert success
108- assert isinstance(result, Deferred)
109- result.addBoth(realOnResult)
110-
111- threadpool.callInThreadWithCallback(onResult, maybeDeferred,
112- f, *args, **kwargs)
113-
114- return d
115-
116-
117-class Check(object):
118- """Abstract base class for objects embodying connectivity checks."""
119-
120- def check(self, pattern, results):
121- """Run this check, if it matches the pattern.
122-
123- If the pattern matches, and this is a leaf node in the check tree,
124- implementations of Check.check should call
125- results.notify_start, then either results.notify_success or
126- results.notify_failure.
127- """
128- raise NotImplementedError("%r.check not implemented" % type(self))
129-
130- def skip(self, pattern, results):
131- """Indicate that this check has been skipped.
132-
133- If the pattern matches and this is a leaf node in the check tree,
134- implementations of Check.skip should call results.notify_skip.
135- """
136- raise NotImplementedError("%r.skip not implemented" % type(self))
137-
138-
139-class Pattern(object):
140- """Abstract base class for patterns used to select subsets of checks."""
141-
142- def assume_prefix(self, prefix):
143- """Return an equivalent pattern with the given prefix baked in.
144-
145- For example, if self.matches("bar") is True, then
146- self.assume_prefix("foo").matches("foobar") will be True.
147- """
148- return PrefixPattern(prefix, self)
149-
150- def failed(self):
151- """Return True if the pattern cannot match any string.
152-
153- This is mainly used so we can bail out early when recursing into
154- check trees.
155- """
156- return not self.prefix_matches("")
157-
158- def prefix_matches(self, partial_name):
159- """Return True if the partial name (a prefix) is a potential match."""
160- raise NotImplementedError("%r.prefix_matches not implemented" %
161- type(self))
162-
163- def matches(self, name):
164- """Return True if the given name matches."""
165- raise NotImplementedError("%r.match not implemented" %
166- type(self))
167-
168-
169-class ResultTracker(object):
170- """Base class for objects which report or record check results."""
171-
172- def notify_start(self, name, info):
173- """Register the start of a check."""
174-
175- def notify_skip(self, name):
176- """Register a check being skipped."""
177-
178- def notify_success(self, name, duration):
179- """Register a successful check."""
180-
181- def notify_failure(self, name, info, exc_info, duration):
182- """Register the failure of a check."""
183-
184-
185-class PrefixResultWrapper(ResultTracker):
186- """ResultWrapper wrapper which adds a prefix to recorded results."""
187-
188- def __init__(self, wrapped, prefix):
189- """Initialize an instance."""
190- super(PrefixResultWrapper, self).__init__()
191- self.wrapped = wrapped
192- self.prefix = prefix
193-
194- def make_name(self, name):
195- """Make a name by prepending the prefix."""
196- return "%s%s" % (self.prefix, name)
197-
198- def notify_skip(self, name):
199- """Register a check being skipped."""
200- self.wrapped.notify_skip(self.make_name(name))
201-
202- def notify_start(self, name, info):
203- """Register the start of a check."""
204- self.wrapped.notify_start(self.make_name(name), info)
205-
206- def notify_success(self, name, duration):
207- """Register success."""
208- self.wrapped.notify_success(self.make_name(name), duration)
209-
210- def notify_failure(self, name, info, exc_info, duration):
211- """Register failure."""
212- self.wrapped.notify_failure(self.make_name(name),
213- info, exc_info, duration)
214-
215-
216-class FailureCountingResultWrapper(ResultTracker):
217- """ResultWrapper wrapper which counts failures."""
218-
219- def __init__(self, wrapped):
220- """Initialize an instance."""
221- super(FailureCountingResultWrapper, self).__init__()
222- self.wrapped = wrapped
223- self.failure_count = 0
224-
225- def notify_skip(self, name):
226- """Register a check being skipped."""
227- self.wrapped.notify_skip(name)
228-
229- def notify_start(self, name, info):
230- """Register the start of a check."""
231- self.failure_count += 1
232- self.wrapped.notify_start(name, info)
233-
234- def notify_success(self, name, duration):
235- """Register success."""
236- self.failure_count -= 1
237- self.wrapped.notify_success(name, duration)
238-
239- def notify_failure(self, name, info, exc_info, duration):
240- """Register failure."""
241- self.wrapped.notify_failure(name, info, exc_info, duration)
242-
243- def any_failed(self):
244- """Return True if any checks using this wrapper failed so far."""
245- return self.failure_count > 0
246-
247-
248-class FailedPattern(Pattern):
249- """Patterns that always fail to match."""
250-
251- def assume_prefix(self, prefix):
252- """Return an equivalent pattern with the given prefix baked in."""
253- return FAILED_PATTERN
254-
255- def prefix_matches(self, partial_name):
256- """Return True if the partial name matches."""
257- return False
258-
259- def matches(self, name):
260- """Return True if the complete name matches."""
261- return False
262-
263-
264-FAILED_PATTERN = FailedPattern()
265-
266-
267-PATTERN_TOKEN_RE = re.compile(r'\*|[^*]+')
268-
269-
270-def tokens_to_partial_re(tokens):
271- """Convert tokens to a regular expression for matching prefixes."""
272-
273- def token_to_re(token):
274- """Convert tokens to (begin, end, alt_end) triples."""
275- if token == '*':
276- return (r'(?:.*', ')?', ')')
277- else:
278- chars = list(token)
279- begin = "".join(["(?:" + re.escape(c) for c in chars])
280- end = "".join([")?" for c in chars])
281- return (begin, end, end)
282-
283- subexprs = map(token_to_re, tokens)
284- if len(subexprs) > 0:
285- # subexpressions like (.*)? aren't accepted, so we may have to use
286- # an alternate closing form for the last (innermost) subexpression
287- (begin, _, alt_end) = subexprs[-1]
288- subexprs[-1] = (begin, alt_end, alt_end)
289- return re.compile("".join([se[0] for se in subexprs] +
290- [se[1] for se in reversed(subexprs)] +
291- [r'\Z']))
292-
293-
294-def tokens_to_re(tokens):
295- """Convert tokens to a regular expression for exact matching."""
296-
297- def token_to_re(token):
298- """Convert tokens to simple regular expressions."""
299- if token == '*':
300- return r'.*'
301- else:
302- return re.escape(token)
303-
304- return re.compile("".join(map(token_to_re, tokens) + [r'\Z']))
305-
306-
307-class SimplePattern(Pattern):
308- """Pattern that matches according to the given pattern expression."""
309-
310- def __init__(self, pattern):
311- """Initialize an instance."""
312- super(SimplePattern, self).__init__()
313- tokens = PATTERN_TOKEN_RE.findall(pattern)
314- self.partial_re = tokens_to_partial_re(tokens)
315- self.full_re = tokens_to_re(tokens)
316-
317- def prefix_matches(self, partial_name):
318- """Return True if the partial name matches."""
319- return self.partial_re.match(partial_name) is not None
320-
321- def matches(self, name):
322- """Return True if the complete name matches."""
323- return self.full_re.match(name) is not None
324-
325-
326-class PrefixPattern(Pattern):
327- """Pattern that assumes a previously given prefix."""
328-
329- def __init__(self, prefix, pattern):
330- """Initialize an instance."""
331- super(PrefixPattern, self).__init__()
332- self.prefix = prefix
333- self.pattern = pattern
334-
335- def assume_prefix(self, prefix):
336- """Return an equivalent pattern with the given prefix baked in."""
337- return PrefixPattern(self.prefix + prefix, self.pattern)
338-
339- def prefix_matches(self, partial_name):
340- """Return True if the partial name matches."""
341- return self.pattern.prefix_matches(self.prefix + partial_name)
342-
343- def matches(self, name):
344- """Return True if the complete name matches."""
345- return self.pattern.matches(self.prefix + name)
346-
347-
348-class SumPattern(Pattern):
349- """Pattern that matches if at least one given pattern matches."""
350-
351- def __init__(self, patterns):
352- """Initialize an instance."""
353- super(SumPattern, self).__init__()
354- self.patterns = patterns
355-
356- def prefix_matches(self, partial_name):
357- """Return True if the partial name matches."""
358- for pattern in self.patterns:
359- if pattern.prefix_matches(partial_name):
360- return True
361- return False
362-
363- def matches(self, name):
364- """Return True if the complete name matches."""
365- for pattern in self.patterns:
366- if pattern.matches(name):
367- return True
368- return False
369-
370-
371-class ConditionalCheck(Check):
372- """A Check that skips unless the given predicate is true at check time."""
373-
374- def __init__(self, wrapped, predicate):
375- """Initialize an instance."""
376- super(ConditionalCheck, self).__init__()
377- self.wrapped = wrapped
378- self.predicate = predicate
379-
380- def check(self, pattern, result):
381- """Skip the check."""
382- if self.predicate():
383- return self.wrapped.check(pattern, result)
384- else:
385- self.skip(pattern, result)
386-
387- def skip(self, pattern, result):
388- """Skip the check."""
389- self.wrapped.skip(pattern, result)
390-
391-
392-class FunctionCheck(Check):
393- """A Check which takes a check function."""
394-
395- def __init__(self, name, check, info=None, blocking=False):
396- """Initialize an instance."""
397- super(FunctionCheck, self).__init__()
398- self.name = name
399- self.info = info
400- self.check_fn = check
401- self.blocking = blocking
402-
403- @inlineCallbacks
404- def check(self, pattern, results):
405- """Call the check function."""
406- if not pattern.matches(self.name):
407- returnValue(None)
408- results.notify_start(self.name, self.info)
409- start = time.time()
410- try:
411- if self.blocking:
412- result = yield maybeDeferToThread(self.check_fn)
413- else:
414- result = yield maybeDeferred(self.check_fn)
415- results.notify_success(self.name, time.time() - start)
416- returnValue(result)
417- except Exception:
418- results.notify_failure(self.name, self.info,
419- sys.exc_info(), time.time() - start)
420-
421- def skip(self, pattern, results):
422- """Record the skip."""
423- if not pattern.matches(self.name):
424- return
425- results.notify_skip(self.name)
426-
427-
428-class MultiCheck(Check):
429- """A composite check comprised of multiple subchecks."""
430-
431- def __init__(self, subchecks, strategy):
432- """Initialize an instance."""
433- super(MultiCheck, self).__init__()
434- self.subchecks = list(subchecks)
435- self.strategy = strategy
436-
437- def check(self, pattern, results):
438- """Run subchecks using the strategy supplied at creation time."""
439- return self.strategy(self.subchecks, pattern, results)
440-
441- def skip(self, pattern, results):
442- """Skip subchecks."""
443- for subcheck in self.subchecks:
444- subcheck.skip(pattern, results)
445-
446-
447-class PrefixCheckWrapper(Check):
448- """Runs a given check, adding a prefix to its name.
449-
450- This works by wrapping the pattern and result tracker objects
451- passed to .check and .skip.
452- """
453-
454- def __init__(self, wrapped, prefix):
455- """Initialize an instance."""
456- super(PrefixCheckWrapper, self).__init__()
457- self.wrapped = wrapped
458- self.prefix = prefix
459-
460- def do_subcheck(self, subcheck, pattern, results):
461- """Do a subcheck if the pattern could still match."""
462- pattern = pattern.assume_prefix(self.prefix)
463- if not pattern.failed():
464- results = PrefixResultWrapper(wrapped=results,
465- prefix=self.prefix)
466- return subcheck(pattern, results)
467-
468- def check(self, pattern, results):
469- """Run the check, prefixing results."""
470- return self.do_subcheck(self.wrapped.check, pattern, results)
471-
472- def skip(self, pattern, results):
473- """Skip checks, prefixing results."""
474- self.do_subcheck(self.wrapped.skip, pattern, results)
475-
476-
477-@inlineCallbacks
478-def sequential_strategy(subchecks, pattern, results):
479- """Run subchecks sequentially, skipping checks after the first failure.
480-
481- This is most useful when the failure of one check in the sequence
482- would imply the failure of later checks -- for example, it probably
483- doesn't make sense to run an SSL check if the basic TCP check failed.
484-
485- Use sequential_check to create a meta-check using this strategy.
486- """
487- local_results = FailureCountingResultWrapper(wrapped=results)
488- failed = False
489- for subcheck in subchecks:
490- if failed:
491- subcheck.skip(pattern, local_results)
492- else:
493- yield maybeDeferred(subcheck.check, pattern, local_results)
494- if local_results.any_failed():
495- failed = True
496-
497-
498-def parallel_strategy(subchecks, pattern, results):
499- """A strategy which runs the given subchecks in parallel.
500-
501- Most checks can potentially block for long periods, and shouldn't have
502- interdependencies, so it makes sense to run them in parallel to
503- shorten the overall run time.
504-
505- Use parallel_check to create a meta-check using this strategy.
506- """
507- deferreds = [maybeDeferred(subcheck.check, pattern, results)
508- for subcheck in subchecks]
509- return DeferredList(deferreds)
510-
511-
512-def parallel_check(subchecks):
513- """Return a check that runs the given subchecks in parallel."""
514- return MultiCheck(subchecks=subchecks, strategy=parallel_strategy)
515-
516-
517-def sequential_check(subchecks):
518- """Return a check that runs the given subchecks in sequence."""
519- return MultiCheck(subchecks=subchecks, strategy=sequential_strategy)
520-
521-
522-def add_check_prefix(*args):
523- """Return an equivalent check with the given prefix prepended to its name.
524-
525- The final argument should be a check; the remaining arguments are treated
526- as name components and joined with the check name using periods as
527- separators. For example, if the name of a check is "baz", then:
528-
529- add_check_prefix("foo", "bar", check)
530-
531- ...will return a check with the effective name "foo.bar.baz".
532- """
533- args = list(args)
534- check = args.pop(-1)
535- path = ".".join(args)
536- return PrefixCheckWrapper(wrapped=check, prefix="%s." % (path,))
537-
538-
539-def make_check(name, check, info=None, blocking=False):
540- """Make a check object from a function."""
541- return FunctionCheck(name=name, check=check, info=info, blocking=blocking)
542-
543-
544-def guard_check(check, predicate):
545- """Wrap a check so that it is skipped unless the predicate is true."""
546- return ConditionalCheck(wrapped=check, predicate=predicate)
547-
548-
549-class TCPCheckProtocol(Protocol):
550-
551- def connectionMade(self):
552- self.transport.loseConnection()
553-
554-
555-@inlineCallbacks
556-def do_tcp_check(host, port, ssl=False, ssl_verify=True):
557- """Generic connection check function."""
558- if not isIPAddress(host):
559- try:
560- ip = yield reactor.resolve(host, timeout=(1, CONNECT_TIMEOUT))
561- except DNSLookupError:
562- raise ValueError("dns resolution failed")
563- else:
564- ip = host
565- creator = ClientCreator(reactor, TCPCheckProtocol)
566- try:
567- if ssl:
568- context = VerifyingContextFactory(ssl_verify, CA_CERTS)
569- yield creator.connectSSL(ip, port, context,
570- timeout=CONNECT_TIMEOUT)
571- else:
572- yield creator.connectTCP(ip, port, timeout=CONNECT_TIMEOUT)
573- except TimeoutError:
574- if ip == host:
575- raise ValueError("timed out")
576- else:
577- raise ValueError("timed out connecting to %s" % ip)
578-
579-
580-def make_tcp_check(host, port, **kwargs):
581- """Return a check for TCP connectivity."""
582- return make_check("tcp.{}:{}".format(host, port), lambda: do_tcp_check(host, port),
583- info="%s:%s" % (host, port))
584-
585-
586-def make_ssl_check(host, port, verify=True, **kwargs):
587- """Return a check for SSL setup."""
588- return make_check("ssl.{}:{}".format(host, port),
589- lambda: do_tcp_check(host, port, ssl=True,
590- ssl_verify=verify),
591- info="%s:%s" % (host, port))
592-
593-
594-class UDPCheckProtocol(DatagramProtocol):
595-
596- def __init__(self, host, port, send, expect, deferred=None):
597- self.host = host
598- self.port = port
599- self.send = send
600- self.expect = expect
601- self.deferred = deferred
602-
603- def _finish(self, success, result):
604- if not (self.delayed.cancelled or self.delayed.called):
605- self.delayed.cancel()
606- if self.deferred is not None:
607- if success:
608- self.deferred.callback(result)
609- else:
610- self.deferred.errback(result)
611- self.deferred = None
612-
613- def startProtocol(self):
614- self.transport.write(self.send, (self.host, self.port))
615- self.delayed = reactor.callLater(CONNECT_TIMEOUT,
616- self._finish,
617- False, TimeoutError())
618-
619- def datagramReceived(self, datagram, addr):
620- if datagram == self.expect:
621- self._finish(True, True)
622- else:
623- self._finish(False, ValueError("unexpected reply"))
624-
625-
626-@inlineCallbacks
627-def do_udp_check(host, port, send, expect):
628- """Generic connection check function."""
629- if not isIPAddress(host):
630- try:
631- ip = yield reactor.resolve(host, timeout=(1, CONNECT_TIMEOUT))
632- except DNSLookupError:
633- raise ValueError("dns resolution failed")
634- else:
635- ip = host
636- deferred = Deferred()
637- protocol = UDPCheckProtocol(host, port, send, expect, deferred)
638- reactor.listenUDP(0, protocol)
639- try:
640- yield deferred
641- except TimeoutError:
642- if ip == host:
643- raise ValueError("timed out")
644- else:
645- raise ValueError("timed out waiting for %s" % ip)
646-
647-
648-def make_udp_check(host, port, send, expect, **kwargs):
649- """Return a check for UDP connectivity."""
650- return make_check("udp.{}:{}".format(host, port),
651- lambda: do_udp_check(host, port, send, expect),
652- info="%s:%s" % (host, port))
653-
654-
655-def extract_host_port(url):
656- parsed = urlparse.urlparse(url)
657- host = parsed.hostname
658- port = parsed.port
659- scheme = parsed.scheme
660- if not scheme:
661- scheme = 'http'
662- if port is None:
663- if scheme == 'https':
664- port = 443
665- else:
666- port = 80
667- return host, port, scheme
668-
669-
670-def make_http_check(url, method='GET', expected_code=200, **kwargs):
671- subchecks = []
672- host, port, scheme = extract_host_port(url)
673- subchecks.append(make_tcp_check(host, port))
674- if scheme == 'https':
675- subchecks.append(make_ssl_check(host, port))
676-
677- @inlineCallbacks
678- def do_request():
679- agent = Agent(reactor)
680- response = yield agent.request(method, url)
681- if response.code != expected_code:
682- raise RuntimeError(
683- "Unexpected response code: {}".format(response.code))
684-
685- subchecks.append(make_check('{}.{}'.format(method, url), do_request,
686- info='{} {}'.format(method, url)))
687- return sequential_check(subchecks)
688-
689-
690-def make_amqp_check(host, port, username, password, use_ssl=True, vhost="/", **kwargs):
691- """Return a check for AMQP connectivity."""
692- from txamqp.protocol import AMQClient
693- from txamqp.client import TwistedDelegate
694- from txamqp.spec import load as load_spec
695-
696- subchecks = []
697- subchecks.append(make_tcp_check(host, port))
698-
699- if use_ssl:
700- subchecks.append(make_ssl_check(host, port, verify=False))
701-
702- @inlineCallbacks
703- def do_auth():
704- """Connect and authenticate."""
705- delegate = TwistedDelegate()
706- spec = load_spec(resource_stream('conn_check', 'amqp0-8.xml'))
707- creator = ClientCreator(reactor, AMQClient,
708- delegate, vhost, spec)
709- client = yield creator.connectTCP(host, port, timeout=CONNECT_TIMEOUT)
710- yield client.authenticate(username, password)
711-
712- subchecks.append(make_check("auth", do_auth,
713- info="user %s" % (username,),))
714- return sequential_check(subchecks)
715-
716-
717-def make_postgres_check(host, port, username, password, database, **kwargs):
718- """Return a check for Postgres connectivity."""
719-
720- import psycopg2
721- subchecks = []
722- connect_kw = {'host': host, 'user': username, 'database': database}
723-
724- if host[0] != '/':
725- connect_kw['port'] = port
726- subchecks.append(make_tcp_check(host, port))
727-
728- if password is not None:
729- connect_kw['password'] = password
730-
731- def check_auth():
732- """Try to establish a postgres connection and log in."""
733- conn = psycopg2.connect(**connect_kw)
734- conn.close()
735-
736- subchecks.append(make_check("auth", check_auth,
737- info="user %s" % (username,),
738- blocking=True))
739- return sequential_check(subchecks)
740-
741-
742-def make_redis_check(host, port, password=None, **kwargs):
743- """Make a check for the configured redis server."""
744- import txredis
745- subchecks = []
746- subchecks.append(make_tcp_check(host, port))
747-
748- @inlineCallbacks
749- def do_connect():
750- """Connect and authenticate.
751- """
752- client_creator = ClientCreator(reactor, txredis.client.RedisClient)
753- client = yield client_creator.connectTCP(host=host, port=port,
754- timeout=CONNECT_TIMEOUT)
755-
756- if password is None:
757- ping = yield client.ping()
758- if not ping:
759- raise RuntimeError("failed to ping redis")
760- else:
761- resp = yield client.auth(password)
762- if resp != 'OK':
763- raise RuntimeError("failed to auth to redis")
764-
765- connect_info = "connect with auth" if password is not None else "connect"
766- subchecks.append(make_check(connect_info, do_connect))
767- return add_check_prefix('redis', sequential_check(subchecks))
768-
769-
770-CHECKS = {
771- 'tcp': {
772- 'fn': make_tcp_check,
773- 'args': ['host', 'port'],
774- },
775- 'ssl': {
776- 'fn': make_ssl_check,
777- 'args': ['host', 'port'],
778- },
779- 'udp': {
780- 'fn': make_udp_check,
781- 'args': ['host', 'port', 'send', 'expect'],
782- },
783- 'http': {
784- 'fn': make_http_check,
785- 'args': ['url'],
786- },
787- 'amqp': {
788- 'fn': make_amqp_check,
789- 'args': ['host', 'port', 'username', 'password'],
790- },
791- 'postgres': {
792- 'fn': make_postgres_check,
793- 'args': ['host', 'port', 'username', 'password', 'database'],
794- },
795- 'redis': {
796- 'fn': make_redis_check,
797- 'args': ['host', 'port'],
798- },
799-}
800-
801-
802-def check_from_description(check_description):
803- _type = check_description['type']
804- check = CHECKS.get(_type, None)
805- if check is None:
806- raise AssertionError("Unknown check type: {}, available checks: {}".format(
807- _type, CHECKS.keys()))
808- for arg in check['args']:
809- if arg not in check_description:
810- raise AssertionError('{} missing from check: {}'.format(arg,
811- check_description))
812- res = check['fn'](**check_description)
813- return res
814-
815-
816-def build_checks(check_descriptions):
817- subchecks = map(check_from_description, check_descriptions)
818- return parallel_check(subchecks)
819-
820-
821-@inlineCallbacks
822-def run_checks(checks, pattern, results):
823- """Make and run all the pertinent checks."""
824- try:
825- yield checks.check(pattern, results)
826- finally:
827- reactor.stop()
828-
829-
830-class TimestampOutput(object):
831-
832- def __init__(self, output):
833- self.start = time.time()
834- self.output = output
835-
836- def write(self, data):
837- self.output.write("%.3f: %s" % (time.time() - self.start, data))
838-
839-
840-class ConsoleOutput(ResultTracker):
841- """Displays check results."""
842-
843- def __init__(self, output, verbose, show_tracebacks, show_duration):
844- """Initialize an instance."""
845- super(ConsoleOutput, self).__init__()
846- self.output = output
847- self.verbose = verbose
848- self.show_tracebacks = show_tracebacks
849- self.show_duration = show_duration
850-
851- def format_duration(self, duration):
852- if not self.show_duration:
853- return ""
854- return " (%.3f ms)" % duration
855-
856- def notify_start(self, name, info):
857- """Register the start of a check."""
858- if self.verbose:
859- if info:
860- info = " (%s)" % (info,)
861- self.output.write("Starting %s%s...\n" % (name, info or ''))
862-
863- def notify_skip(self, name):
864- """Register a check being skipped."""
865- self.output.write("SKIPPING %s\n" % (name,))
866-
867- def notify_success(self, name, duration):
868- """Register a success."""
869- self.output.write("OK %s%s\n" % (
870- name, self.format_duration(duration)))
871-
872- def notify_failure(self, name, info, exc_info, duration):
873- """Register a failure."""
874- message = str(exc_info[1]).split("\n")[0]
875- if info:
876- message = "(%s): %s" % (info, message)
877- self.output.write("FAILED %s%s: %s\n" % (
878- name, self.format_duration(duration), message))
879- if self.show_tracebacks:
880- formatted = traceback.format_exception(exc_info[0],
881- exc_info[1],
882- exc_info[2],
883- None)
884- lines = "".join(formatted).split("\n")
885- if len(lines) > 0 and len(lines[-1]) == 0:
886- lines.pop()
887- indented = "\n".join([" %s" % (line,) for line in lines])
888- self.output.write("%s\n" % (indented,))
889-
890-
891-def main(*args):
892- """Parse arguments, then build and run checks in a reactor."""
893- parser = ArgumentParser()
894- parser.add_argument("config_file",
895- help="Config file specifying the checks to run.")
896- parser.add_argument("patterns", nargs='*',
897- help="Patterns to filter the checks.")
898- parser.add_argument("-v", "--verbose", dest="verbose",
899- action="store_true", default=False,
900- help="Show additional status")
901- parser.add_argument("-d", "--duration", dest="show_duration",
902- action="store_true", default=False,
903- help="Show duration")
904- parser.add_argument("-t", "--tracebacks", dest="show_tracebacks",
905- action="store_true", default=False,
906- help="Show tracebacks on failure")
907- parser.add_argument("--validate", dest="validate",
908- action="store_true", default=False,
909- help="Only validate the config file, don't run checks.")
910- options = parser.parse_args(list(args))
911-
912- if options.patterns:
913- pattern = SumPattern(map(SimplePattern, options.patterns))
914- else:
915- pattern = SimplePattern("*")
916-
917- def make_daemon_thread(*args, **kw):
918- """Create a daemon thread."""
919- thread = Thread(*args, **kw)
920- thread.daemon = True
921- return thread
922-
923- threadpool = ThreadPool(minthreads=1)
924- threadpool.threadFactory = make_daemon_thread
925- reactor.threadpool = threadpool
926- reactor.callWhenRunning(threadpool.start)
927-
928- output = sys.stdout
929- if options.show_duration:
930- output = TimestampOutput(output)
931-
932- results = ConsoleOutput(output=output,
933- show_tracebacks=options.show_tracebacks,
934- show_duration=options.show_duration,
935- verbose=options.verbose)
936- results = FailureCountingResultWrapper(results)
937- with open(options.config_file) as f:
938- descriptions = yaml.load(f)
939- checks = build_checks(descriptions)
940- if not options.validate:
941- reactor.callWhenRunning(run_checks, checks, pattern, results)
942-
943- reactor.run()
944-
945- if results.any_failed():
946- return 1
947- else:
948- return 0
949-
950-
951-def run():
952- exit(main(*sys.argv[1:]))
953-
954-
955-if __name__ == '__main__':
956- run()
957+from twisted.internet import epollreactor
958+epollreactor.install()
959
960=== added file 'conn_check/check_impl.py'
961--- conn_check/check_impl.py 1970-01-01 00:00:00 +0000
962+++ conn_check/check_impl.py 2014-07-24 22:10:31 +0000
963@@ -0,0 +1,325 @@
964+import sys
965+import time
966+
967+from twisted.internet import reactor
968+from twisted.internet.defer import (
969+ returnValue,
970+ inlineCallbacks,
971+ maybeDeferred,
972+ DeferredList,
973+ Deferred)
974+from twisted.python.failure import Failure
975+
976+
977+def maybeDeferToThread(f, *args, **kwargs):
978+ """
979+ Call the function C{f} using a thread from the given threadpool and return
980+ the result as a Deferred.
981+
982+ @param f: The function to call. May return a deferred.
983+ @param *args: positional arguments to pass to f.
984+ @param **kwargs: keyword arguments to pass to f.
985+
986+ @return: A Deferred which fires a callback with the result of f, or an
987+ errback with a L{twisted.python.failure.Failure} if f throws an
988+ exception.
989+ """
990+ threadpool = reactor.getThreadPool()
991+
992+ d = Deferred()
993+
994+ def realOnResult(result):
995+ if not isinstance(result, Failure):
996+ reactor.callFromThread(d.callback, result)
997+ else:
998+ reactor.callFromThread(d.errback, result)
999+
1000+ def onResult(success, result):
1001+ assert success
1002+ assert isinstance(result, Deferred)
1003+ result.addBoth(realOnResult)
1004+
1005+ threadpool.callInThreadWithCallback(onResult, maybeDeferred,
1006+ f, *args, **kwargs)
1007+
1008+ return d
1009+
1010+
1011+class Check(object):
1012+ """Abstract base class for objects embodying connectivity checks."""
1013+
1014+ def check(self, pattern, results):
1015+ """Run this check, if it matches the pattern.
1016+
1017+ If the pattern matches, and this is a leaf node in the check tree,
1018+ implementations of Check.check should call
1019+ results.notify_start, then either results.notify_success or
1020+ results.notify_failure.
1021+ """
1022+ raise NotImplementedError("%r.check not implemented" % type(self))
1023+
1024+ def skip(self, pattern, results):
1025+ """Indicate that this check has been skipped.
1026+
1027+ If the pattern matches and this is a leaf node in the check tree,
1028+ implementations of Check.skip should call results.notify_skip.
1029+ """
1030+ raise NotImplementedError("%r.skip not implemented" % type(self))
1031+
1032+
1033+class ConditionalCheck(Check):
1034+ """A Check that skips unless the given predicate is true at check time."""
1035+
1036+ def __init__(self, wrapped, predicate):
1037+ """Initialize an instance."""
1038+ super(ConditionalCheck, self).__init__()
1039+ self.wrapped = wrapped
1040+ self.predicate = predicate
1041+
1042+ def check(self, pattern, result):
1043+ """Skip the check."""
1044+ if self.predicate():
1045+ return self.wrapped.check(pattern, result)
1046+ else:
1047+ self.skip(pattern, result)
1048+
1049+ def skip(self, pattern, result):
1050+ """Skip the check."""
1051+ self.wrapped.skip(pattern, result)
1052+
1053+
1054+class ResultTracker(object):
1055+ """Base class for objects which report or record check results."""
1056+
1057+ def notify_start(self, name, info):
1058+ """Register the start of a check."""
1059+
1060+ def notify_skip(self, name):
1061+ """Register a check being skipped."""
1062+
1063+ def notify_success(self, name, duration):
1064+ """Register a successful check."""
1065+
1066+ def notify_failure(self, name, info, exc_info, duration):
1067+ """Register the failure of a check."""
1068+
1069+
1070+class PrefixResultWrapper(ResultTracker):
1071+ """ResultWrapper wrapper which adds a prefix to recorded results."""
1072+
1073+ def __init__(self, wrapped, prefix):
1074+ """Initialize an instance."""
1075+ super(PrefixResultWrapper, self).__init__()
1076+ self.wrapped = wrapped
1077+ self.prefix = prefix
1078+
1079+ def make_name(self, name):
1080+ """Make a name by prepending the prefix."""
1081+ return "%s%s" % (self.prefix, name)
1082+
1083+ def notify_skip(self, name):
1084+ """Register a check being skipped."""
1085+ self.wrapped.notify_skip(self.make_name(name))
1086+
1087+ def notify_start(self, name, info):
1088+ """Register the start of a check."""
1089+ self.wrapped.notify_start(self.make_name(name), info)
1090+
1091+ def notify_success(self, name, duration):
1092+ """Register success."""
1093+ self.wrapped.notify_success(self.make_name(name), duration)
1094+
1095+ def notify_failure(self, name, info, exc_info, duration):
1096+ """Register failure."""
1097+ self.wrapped.notify_failure(self.make_name(name),
1098+ info, exc_info, duration)
1099+
1100+
1101+class FailureCountingResultWrapper(ResultTracker):
1102+ """ResultWrapper wrapper which counts failures."""
1103+
1104+ def __init__(self, wrapped):
1105+ """Initialize an instance."""
1106+ super(FailureCountingResultWrapper, self).__init__()
1107+ self.wrapped = wrapped
1108+ self.failure_count = 0
1109+
1110+ def notify_skip(self, name):
1111+ """Register a check being skipped."""
1112+ self.wrapped.notify_skip(name)
1113+
1114+ def notify_start(self, name, info):
1115+ """Register the start of a check."""
1116+ self.failure_count += 1
1117+ self.wrapped.notify_start(name, info)
1118+
1119+ def notify_success(self, name, duration):
1120+ """Register success."""
1121+ self.failure_count -= 1
1122+ self.wrapped.notify_success(name, duration)
1123+
1124+ def notify_failure(self, name, info, exc_info, duration):
1125+ """Register failure."""
1126+ self.wrapped.notify_failure(name, info, exc_info, duration)
1127+
1128+ def any_failed(self):
1129+ """Return True if any checks using this wrapper failed so far."""
1130+ return self.failure_count > 0
1131+
1132+
1133+class FunctionCheck(Check):
1134+ """A Check which takes a check function."""
1135+
1136+ def __init__(self, name, check, info=None, blocking=False):
1137+ """Initialize an instance."""
1138+ super(FunctionCheck, self).__init__()
1139+ self.name = name
1140+ self.info = info
1141+ self.check_fn = check
1142+ self.blocking = blocking
1143+
1144+ @inlineCallbacks
1145+ def check(self, pattern, results):
1146+ """Call the check function."""
1147+ if not pattern.matches(self.name):
1148+ returnValue(None)
1149+ results.notify_start(self.name, self.info)
1150+ start = time.time()
1151+ try:
1152+ if self.blocking:
1153+ result = yield maybeDeferToThread(self.check_fn)
1154+ else:
1155+ result = yield maybeDeferred(self.check_fn)
1156+ results.notify_success(self.name, time.time() - start)
1157+ returnValue(result)
1158+ except Exception:
1159+ results.notify_failure(self.name, self.info,
1160+ sys.exc_info(), time.time() - start)
1161+
1162+ def skip(self, pattern, results):
1163+ """Record the skip."""
1164+ if not pattern.matches(self.name):
1165+ return
1166+ results.notify_skip(self.name)
1167+
1168+
1169+class MultiCheck(Check):
1170+ """A composite check comprised of multiple subchecks."""
1171+
1172+ def __init__(self, subchecks, strategy):
1173+ """Initialize an instance."""
1174+ super(MultiCheck, self).__init__()
1175+ self.subchecks = list(subchecks)
1176+ self.strategy = strategy
1177+
1178+ def check(self, pattern, results):
1179+ """Run subchecks using the strategy supplied at creation time."""
1180+ return self.strategy(self.subchecks, pattern, results)
1181+
1182+ def skip(self, pattern, results):
1183+ """Skip subchecks."""
1184+ for subcheck in self.subchecks:
1185+ subcheck.skip(pattern, results)
1186+
1187+
1188+class PrefixCheckWrapper(Check):
1189+ """Runs a given check, adding a prefix to its name.
1190+
1191+ This works by wrapping the pattern and result tracker objects
1192+ passed to .check and .skip.
1193+ """
1194+
1195+ def __init__(self, wrapped, prefix):
1196+ """Initialize an instance."""
1197+ super(PrefixCheckWrapper, self).__init__()
1198+ self.wrapped = wrapped
1199+ self.prefix = prefix
1200+
1201+ def do_subcheck(self, subcheck, pattern, results):
1202+ """Do a subcheck if the pattern could still match."""
1203+ pattern = pattern.assume_prefix(self.prefix)
1204+ if not pattern.failed():
1205+ results = PrefixResultWrapper(wrapped=results,
1206+ prefix=self.prefix)
1207+ return subcheck(pattern, results)
1208+
1209+ def check(self, pattern, results):
1210+ """Run the check, prefixing results."""
1211+ return self.do_subcheck(self.wrapped.check, pattern, results)
1212+
1213+ def skip(self, pattern, results):
1214+ """Skip checks, prefixing results."""
1215+ self.do_subcheck(self.wrapped.skip, pattern, results)
1216+
1217+
1218+@inlineCallbacks
1219+def sequential_strategy(subchecks, pattern, results):
1220+ """Run subchecks sequentially, skipping checks after the first failure.
1221+
1222+ This is most useful when the failure of one check in the sequence
1223+ would imply the failure of later checks -- for example, it probably
1224+ doesn't make sense to run an SSL check if the basic TCP check failed.
1225+
1226+ Use sequential_check to create a meta-check using this strategy.
1227+ """
1228+ local_results = FailureCountingResultWrapper(wrapped=results)
1229+ failed = False
1230+ for subcheck in subchecks:
1231+ if failed:
1232+ subcheck.skip(pattern, local_results)
1233+ else:
1234+ yield maybeDeferred(subcheck.check, pattern, local_results)
1235+ if local_results.any_failed():
1236+ failed = True
1237+
1238+
1239+def parallel_strategy(subchecks, pattern, results):
1240+ """A strategy which runs the given subchecks in parallel.
1241+
1242+ Most checks can potentially block for long periods, and shouldn't have
1243+ interdependencies, so it makes sense to run them in parallel to
1244+ shorten the overall run time.
1245+
1246+ Use parallel_check to create a meta-check using this strategy.
1247+ """
1248+ deferreds = [maybeDeferred(subcheck.check, pattern, results)
1249+ for subcheck in subchecks]
1250+ return DeferredList(deferreds)
1251+
1252+
1253+def parallel_check(subchecks):
1254+ """Return a check that runs the given subchecks in parallel."""
1255+ return MultiCheck(subchecks=subchecks, strategy=parallel_strategy)
1256+
1257+
1258+def sequential_check(subchecks):
1259+ """Return a check that runs the given subchecks in sequence."""
1260+ return MultiCheck(subchecks=subchecks, strategy=sequential_strategy)
1261+
1262+
1263+def add_check_prefix(*args):
1264+ """Return an equivalent check with the given prefix prepended to its name.
1265+
1266+ The final argument should be a check; the remaining arguments are treated
1267+ as name components and joined with the check name using periods as
1268+ separators. For example, if the name of a check is "baz", then:
1269+
1270+ add_check_prefix("foo", "bar", check)
1271+
1272+ ...will return a check with the effective name "foo.bar.baz".
1273+ """
1274+ args = list(args)
1275+ check = args.pop(-1)
1276+ path = ".".join(args)
1277+ return PrefixCheckWrapper(wrapped=check, prefix="%s." % (path,))
1278+
1279+
1280+def make_check(name, check, info=None, blocking=False):
1281+ """Make a check object from a function."""
1282+ return FunctionCheck(name=name, check=check, info=info, blocking=blocking)
1283+
1284+
1285+def guard_check(check, predicate):
1286+ """Wrap a check so that it is skipped unless the predicate is true."""
1287+ return ConditionalCheck(wrapped=check, predicate=predicate)
1288+
1289
1290=== added file 'conn_check/checks.py'
1291--- conn_check/checks.py 1970-01-01 00:00:00 +0000
1292+++ conn_check/checks.py 2014-07-24 22:10:31 +0000
1293@@ -0,0 +1,310 @@
1294+import glob
1295+import os
1296+from pkg_resources import resource_stream
1297+import urlparse
1298+
1299+from OpenSSL import SSL
1300+from OpenSSL.crypto import load_certificate, FILETYPE_PEM
1301+
1302+from twisted.internet import reactor, ssl
1303+from twisted.internet.error import DNSLookupError, TimeoutError
1304+from twisted.internet.abstract import isIPAddress
1305+from twisted.internet.defer import (
1306+ Deferred,
1307+ inlineCallbacks,
1308+ )
1309+from twisted.internet.protocol import (
1310+ ClientCreator,
1311+ DatagramProtocol,
1312+ Protocol,
1313+ )
1314+from twisted.web.client import Agent
1315+
1316+from .check_impl import (
1317+ add_check_prefix,
1318+ make_check,
1319+ sequential_check,
1320+ )
1321+
1322+
1323+CONNECT_TIMEOUT = 10
1324+CA_CERTS = []
1325+
1326+
1327+for certFileName in glob.glob("/etc/ssl/certs/*.pem"):
1328+ # There might be some dead symlinks in there, so let's make sure it's real.
1329+ if os.path.exists(certFileName):
1330+ data = open(certFileName).read()
1331+ x509 = load_certificate(FILETYPE_PEM, data)
1332+ # Now, de-duplicate in case the same cert has multiple names.
1333+ CA_CERTS.append(x509)
1334+
1335+
1336+class TCPCheckProtocol(Protocol):
1337+
1338+ def connectionMade(self):
1339+ self.transport.loseConnection()
1340+
1341+
1342+class VerifyingContextFactory(ssl.CertificateOptions):
1343+
1344+ def __init__(self, verify, caCerts, verifyCallback=None):
1345+ ssl.CertificateOptions.__init__(self, verify=verify,
1346+ caCerts=caCerts,
1347+ method=SSL.SSLv23_METHOD)
1348+ self.verifyCallback = verifyCallback
1349+
1350+ def _makeContext(self):
1351+ context = ssl.CertificateOptions._makeContext(self)
1352+ if self.verifyCallback is not None:
1353+ context.set_verify(
1354+ SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
1355+ self.verifyCallback)
1356+ return context
1357+
1358+
1359+@inlineCallbacks
1360+def do_tcp_check(host, port, ssl=False, ssl_verify=True):
1361+ """Generic connection check function."""
1362+ if not isIPAddress(host):
1363+ try:
1364+ ip = yield reactor.resolve(host, timeout=(1, CONNECT_TIMEOUT))
1365+ except DNSLookupError:
1366+ raise ValueError("dns resolution failed")
1367+ else:
1368+ ip = host
1369+ creator = ClientCreator(reactor, TCPCheckProtocol)
1370+ try:
1371+ if ssl:
1372+ context = VerifyingContextFactory(ssl_verify, CA_CERTS)
1373+ yield creator.connectSSL(ip, port, context,
1374+ timeout=CONNECT_TIMEOUT)
1375+ else:
1376+ yield creator.connectTCP(ip, port, timeout=CONNECT_TIMEOUT)
1377+ except TimeoutError:
1378+ if ip == host:
1379+ raise ValueError("timed out")
1380+ else:
1381+ raise ValueError("timed out connecting to %s" % ip)
1382+
1383+
1384+def make_tcp_check(host, port, **kwargs):
1385+ """Return a check for TCP connectivity."""
1386+ return make_check("tcp.{}:{}".format(host, port), lambda: do_tcp_check(host, port),
1387+ info="%s:%s" % (host, port))
1388+
1389+
1390+def make_ssl_check(host, port, verify=True, **kwargs):
1391+ """Return a check for SSL setup."""
1392+ return make_check("ssl.{}:{}".format(host, port),
1393+ lambda: do_tcp_check(host, port, ssl=True,
1394+ ssl_verify=verify),
1395+ info="%s:%s" % (host, port))
1396+
1397+
1398+class UDPCheckProtocol(DatagramProtocol):
1399+
1400+ def __init__(self, host, port, send, expect, deferred=None):
1401+ self.host = host
1402+ self.port = port
1403+ self.send = send
1404+ self.expect = expect
1405+ self.deferred = deferred
1406+
1407+ def _finish(self, success, result):
1408+ if not (self.delayed.cancelled or self.delayed.called):
1409+ self.delayed.cancel()
1410+ if self.deferred is not None:
1411+ if success:
1412+ self.deferred.callback(result)
1413+ else:
1414+ self.deferred.errback(result)
1415+ self.deferred = None
1416+
1417+ def startProtocol(self):
1418+ self.transport.write(self.send, (self.host, self.port))
1419+ self.delayed = reactor.callLater(CONNECT_TIMEOUT,
1420+ self._finish,
1421+ False, TimeoutError())
1422+
1423+ def datagramReceived(self, datagram, addr):
1424+ if datagram == self.expect:
1425+ self._finish(True, True)
1426+ else:
1427+ self._finish(False, ValueError("unexpected reply"))
1428+
1429+
1430+@inlineCallbacks
1431+def do_udp_check(host, port, send, expect):
1432+ """Generic connection check function."""
1433+ if not isIPAddress(host):
1434+ try:
1435+ ip = yield reactor.resolve(host, timeout=(1, CONNECT_TIMEOUT))
1436+ except DNSLookupError:
1437+ raise ValueError("dns resolution failed")
1438+ else:
1439+ ip = host
1440+ deferred = Deferred()
1441+ protocol = UDPCheckProtocol(host, port, send, expect, deferred)
1442+ reactor.listenUDP(0, protocol)
1443+ try:
1444+ yield deferred
1445+ except TimeoutError:
1446+ if ip == host:
1447+ raise ValueError("timed out")
1448+ else:
1449+ raise ValueError("timed out waiting for %s" % ip)
1450+
1451+
1452+def make_udp_check(host, port, send, expect, **kwargs):
1453+ """Return a check for UDP connectivity."""
1454+ return make_check("udp.{}:{}".format(host, port),
1455+ lambda: do_udp_check(host, port, send, expect),
1456+ info="%s:%s" % (host, port))
1457+
1458+
1459+def extract_host_port(url):
1460+ parsed = urlparse.urlparse(url)
1461+ host = parsed.hostname
1462+ port = parsed.port
1463+ scheme = parsed.scheme
1464+ if not scheme:
1465+ scheme = 'http'
1466+ if port is None:
1467+ if scheme == 'https':
1468+ port = 443
1469+ else:
1470+ port = 80
1471+ return host, port, scheme
1472+
1473+
1474+def make_http_check(url, method='GET', expected_code=200, **kwargs):
1475+ subchecks = []
1476+ host, port, scheme = extract_host_port(url)
1477+ subchecks.append(make_tcp_check(host, port))
1478+ if scheme == 'https':
1479+ subchecks.append(make_ssl_check(host, port))
1480+
1481+ @inlineCallbacks
1482+ def do_request():
1483+ agent = Agent(reactor)
1484+ response = yield agent.request(method, url)
1485+ if response.code != expected_code:
1486+ raise RuntimeError(
1487+ "Unexpected response code: {}".format(response.code))
1488+
1489+ subchecks.append(make_check('{}.{}'.format(method, url), do_request,
1490+ info='{} {}'.format(method, url)))
1491+ return sequential_check(subchecks)
1492+
1493+
1494+def make_amqp_check(host, port, username, password, use_ssl=True, vhost="/", **kwargs):
1495+ """Return a check for AMQP connectivity."""
1496+ from txamqp.protocol import AMQClient
1497+ from txamqp.client import TwistedDelegate
1498+ from txamqp.spec import load as load_spec
1499+
1500+ subchecks = []
1501+ subchecks.append(make_tcp_check(host, port))
1502+
1503+ if use_ssl:
1504+ subchecks.append(make_ssl_check(host, port, verify=False))
1505+
1506+ @inlineCallbacks
1507+ def do_auth():
1508+ """Connect and authenticate."""
1509+ delegate = TwistedDelegate()
1510+ spec = load_spec(resource_stream('conn_check', 'amqp0-8.xml'))
1511+ creator = ClientCreator(reactor, AMQClient,
1512+ delegate, vhost, spec)
1513+ client = yield creator.connectTCP(host, port, timeout=CONNECT_TIMEOUT)
1514+ yield client.authenticate(username, password)
1515+
1516+ subchecks.append(make_check("auth", do_auth,
1517+ info="user %s" % (username,),))
1518+ return sequential_check(subchecks)
1519+
1520+
1521+def make_postgres_check(host, port, username, password, database, **kwargs):
1522+ """Return a check for Postgres connectivity."""
1523+
1524+ import psycopg2
1525+ subchecks = []
1526+ connect_kw = {'host': host, 'user': username, 'database': database}
1527+
1528+ if host[0] != '/':
1529+ connect_kw['port'] = port
1530+ subchecks.append(make_tcp_check(host, port))
1531+
1532+ if password is not None:
1533+ connect_kw['password'] = password
1534+
1535+ def check_auth():
1536+ """Try to establish a postgres connection and log in."""
1537+ conn = psycopg2.connect(**connect_kw)
1538+ conn.close()
1539+
1540+ subchecks.append(make_check("auth", check_auth,
1541+ info="user %s" % (username,),
1542+ blocking=True))
1543+ return sequential_check(subchecks)
1544+
1545+
1546+def make_redis_check(host, port, password=None, **kwargs):
1547+ """Make a check for the configured redis server."""
1548+ import txredis
1549+ subchecks = []
1550+ subchecks.append(make_tcp_check(host, port))
1551+
1552+ @inlineCallbacks
1553+ def do_connect():
1554+ """Connect and authenticate.
1555+ """
1556+ client_creator = ClientCreator(reactor, txredis.client.RedisClient)
1557+ client = yield client_creator.connectTCP(host=host, port=port,
1558+ timeout=CONNECT_TIMEOUT)
1559+
1560+ if password is None:
1561+ ping = yield client.ping()
1562+ if not ping:
1563+ raise RuntimeError("failed to ping redis")
1564+ else:
1565+ resp = yield client.auth(password)
1566+ if resp != 'OK':
1567+ raise RuntimeError("failed to auth to redis")
1568+
1569+ connect_info = "connect with auth" if password is not None else "connect"
1570+ subchecks.append(make_check(connect_info, do_connect))
1571+ return add_check_prefix('redis', sequential_check(subchecks))
1572+
1573+
1574+CHECKS = {
1575+ 'tcp': {
1576+ 'fn': make_tcp_check,
1577+ 'args': ['host', 'port'],
1578+ },
1579+ 'ssl': {
1580+ 'fn': make_ssl_check,
1581+ 'args': ['host', 'port'],
1582+ },
1583+ 'udp': {
1584+ 'fn': make_udp_check,
1585+ 'args': ['host', 'port', 'send', 'expect'],
1586+ },
1587+ 'http': {
1588+ 'fn': make_http_check,
1589+ 'args': ['url'],
1590+ },
1591+ 'amqp': {
1592+ 'fn': make_amqp_check,
1593+ 'args': ['host', 'port', 'username', 'password'],
1594+ },
1595+ 'postgres': {
1596+ 'fn': make_postgres_check,
1597+ 'args': ['host', 'port', 'username', 'password', 'database'],
1598+ },
1599+ 'redis': {
1600+ 'fn': make_redis_check,
1601+ 'args': ['host', 'port'],
1602+ },
1603+}
1604
1605=== added file 'conn_check/main.py'
1606--- conn_check/main.py 1970-01-01 00:00:00 +0000
1607+++ conn_check/main.py 2014-07-24 22:10:31 +0000
1608@@ -0,0 +1,181 @@
1609+from argparse import ArgumentParser
1610+import sys
1611+from threading import Thread
1612+import time
1613+import traceback
1614+import yaml
1615+
1616+from twisted.internet import reactor
1617+from twisted.internet.defer import (
1618+ inlineCallbacks,
1619+ )
1620+from twisted.python.threadpool import ThreadPool
1621+
1622+from .check_impl import (
1623+ FailureCountingResultWrapper,
1624+ parallel_check,
1625+ ResultTracker,
1626+ )
1627+from .checks import CHECKS
1628+from .patterns import (
1629+ SimplePattern,
1630+ SumPattern,
1631+ )
1632+
1633+
1634+def check_from_description(check_description):
1635+ _type = check_description['type']
1636+ check = CHECKS.get(_type, None)
1637+ if check is None:
1638+ raise AssertionError("Unknown check type: {}, available checks: {}".format(
1639+ _type, CHECKS.keys()))
1640+ for arg in check['args']:
1641+ if arg not in check_description:
1642+ raise AssertionError('{} missing from check: {}'.format(arg,
1643+ check_description))
1644+ res = check['fn'](**check_description)
1645+ return res
1646+
1647+
1648+def build_checks(check_descriptions):
1649+ subchecks = map(check_from_description, check_descriptions)
1650+ return parallel_check(subchecks)
1651+
1652+
1653+@inlineCallbacks
1654+def run_checks(checks, pattern, results):
1655+ """Make and run all the pertinent checks."""
1656+ try:
1657+ yield checks.check(pattern, results)
1658+ finally:
1659+ reactor.stop()
1660+
1661+
1662+class TimestampOutput(object):
1663+
1664+ def __init__(self, output):
1665+ self.start = time.time()
1666+ self.output = output
1667+
1668+ def write(self, data):
1669+ self.output.write("%.3f: %s" % (time.time() - self.start, data))
1670+
1671+
1672+class ConsoleOutput(ResultTracker):
1673+ """Displays check results."""
1674+
1675+ def __init__(self, output, verbose, show_tracebacks, show_duration):
1676+ """Initialize an instance."""
1677+ super(ConsoleOutput, self).__init__()
1678+ self.output = output
1679+ self.verbose = verbose
1680+ self.show_tracebacks = show_tracebacks
1681+ self.show_duration = show_duration
1682+
1683+ def format_duration(self, duration):
1684+ if not self.show_duration:
1685+ return ""
1686+ return " (%.3f ms)" % duration
1687+
1688+ def notify_start(self, name, info):
1689+ """Register the start of a check."""
1690+ if self.verbose:
1691+ if info:
1692+ info = " (%s)" % (info,)
1693+ self.output.write("Starting %s%s...\n" % (name, info or ''))
1694+
1695+ def notify_skip(self, name):
1696+ """Register a check being skipped."""
1697+ self.output.write("SKIPPING %s\n" % (name,))
1698+
1699+ def notify_success(self, name, duration):
1700+ """Register a success."""
1701+ self.output.write("OK %s%s\n" % (
1702+ name, self.format_duration(duration)))
1703+
1704+ def notify_failure(self, name, info, exc_info, duration):
1705+ """Register a failure."""
1706+ message = str(exc_info[1]).split("\n")[0]
1707+ if info:
1708+ message = "(%s): %s" % (info, message)
1709+ self.output.write("FAILED %s%s: %s\n" % (
1710+ name, self.format_duration(duration), message))
1711+ if self.show_tracebacks:
1712+ formatted = traceback.format_exception(exc_info[0],
1713+ exc_info[1],
1714+ exc_info[2],
1715+ None)
1716+ lines = "".join(formatted).split("\n")
1717+ if len(lines) > 0 and len(lines[-1]) == 0:
1718+ lines.pop()
1719+ indented = "\n".join([" %s" % (line,) for line in lines])
1720+ self.output.write("%s\n" % (indented,))
1721+
1722+
1723+def main(*args):
1724+ """Parse arguments, then build and run checks in a reactor."""
1725+ parser = ArgumentParser()
1726+ parser.add_argument("config_file",
1727+ help="Config file specifying the checks to run.")
1728+ parser.add_argument("patterns", nargs='*',
1729+ help="Patterns to filter the checks.")
1730+ parser.add_argument("-v", "--verbose", dest="verbose",
1731+ action="store_true", default=False,
1732+ help="Show additional status")
1733+ parser.add_argument("-d", "--duration", dest="show_duration",
1734+ action="store_true", default=False,
1735+ help="Show duration")
1736+ parser.add_argument("-t", "--tracebacks", dest="show_tracebacks",
1737+ action="store_true", default=False,
1738+ help="Show tracebacks on failure")
1739+ parser.add_argument("--validate", dest="validate",
1740+ action="store_true", default=False,
1741+ help="Only validate the config file, don't run checks.")
1742+ options = parser.parse_args(list(args))
1743+
1744+ if options.patterns:
1745+ pattern = SumPattern(map(SimplePattern, options.patterns))
1746+ else:
1747+ pattern = SimplePattern("*")
1748+
1749+ def make_daemon_thread(*args, **kw):
1750+ """Create a daemon thread."""
1751+ thread = Thread(*args, **kw)
1752+ thread.daemon = True
1753+ return thread
1754+
1755+ threadpool = ThreadPool(minthreads=1)
1756+ threadpool.threadFactory = make_daemon_thread
1757+ reactor.threadpool = threadpool
1758+ reactor.callWhenRunning(threadpool.start)
1759+
1760+ output = sys.stdout
1761+ if options.show_duration:
1762+ output = TimestampOutput(output)
1763+
1764+ results = ConsoleOutput(output=output,
1765+ show_tracebacks=options.show_tracebacks,
1766+ show_duration=options.show_duration,
1767+ verbose=options.verbose)
1768+ results = FailureCountingResultWrapper(results)
1769+ with open(options.config_file) as f:
1770+ descriptions = yaml.load(f)
1771+ checks = build_checks(descriptions)
1772+ if not options.validate:
1773+ reactor.callWhenRunning(run_checks, checks, pattern, results)
1774+
1775+ reactor.run()
1776+
1777+ if results.any_failed():
1778+ return 1
1779+ else:
1780+ return 0
1781+
1782+
1783+def run():
1784+ exit(main(*sys.argv[1:]))
1785+
1786+
1787+if __name__ == '__main__':
1788+ run()
1789+
1790
1791=== added file 'conn_check/patterns.py'
1792--- conn_check/patterns.py 1970-01-01 00:00:00 +0000
1793+++ conn_check/patterns.py 2014-07-24 22:10:31 +0000
1794@@ -0,0 +1,153 @@
1795+import re
1796+
1797+class Pattern(object):
1798+ """Abstract base class for patterns used to select subsets of checks."""
1799+
1800+ def assume_prefix(self, prefix):
1801+ """Return an equivalent pattern with the given prefix baked in.
1802+
1803+ For example, if self.matches("bar") is True, then
1804+ self.assume_prefix("foo").matches("foobar") will be True.
1805+ """
1806+ return PrefixPattern(prefix, self)
1807+
1808+ def failed(self):
1809+ """Return True if the pattern cannot match any string.
1810+
1811+ This is mainly used so we can bail out early when recursing into
1812+ check trees.
1813+ """
1814+ return not self.prefix_matches("")
1815+
1816+ def prefix_matches(self, partial_name):
1817+ """Return True if the partial name (a prefix) is a potential match."""
1818+ raise NotImplementedError("%r.prefix_matches not implemented" %
1819+ type(self))
1820+
1821+ def matches(self, name):
1822+ """Return True if the given name matches."""
1823+ raise NotImplementedError("%r.match not implemented" %
1824+ type(self))
1825+
1826+
1827+class FailedPattern(Pattern):
1828+ """Patterns that always fail to match."""
1829+
1830+ def assume_prefix(self, prefix):
1831+ """Return an equivalent pattern with the given prefix baked in."""
1832+ return FAILED_PATTERN
1833+
1834+ def prefix_matches(self, partial_name):
1835+ """Return True if the partial name matches."""
1836+ return False
1837+
1838+ def matches(self, name):
1839+ """Return True if the complete name matches."""
1840+ return False
1841+
1842+
1843+FAILED_PATTERN = FailedPattern()
1844+
1845+
1846+PATTERN_TOKEN_RE = re.compile(r'\*|[^*]+')
1847+
1848+
1849+def tokens_to_partial_re(tokens):
1850+ """Convert tokens to a regular expression for matching prefixes."""
1851+
1852+ def token_to_re(token):
1853+ """Convert tokens to (begin, end, alt_end) triples."""
1854+ if token == '*':
1855+ return (r'(?:.*', ')?', ')')
1856+ else:
1857+ chars = list(token)
1858+ begin = "".join(["(?:" + re.escape(c) for c in chars])
1859+ end = "".join([")?" for c in chars])
1860+ return (begin, end, end)
1861+
1862+ subexprs = map(token_to_re, tokens)
1863+ if len(subexprs) > 0:
1864+ # subexpressions like (.*)? aren't accepted, so we may have to use
1865+ # an alternate closing form for the last (innermost) subexpression
1866+ (begin, _, alt_end) = subexprs[-1]
1867+ subexprs[-1] = (begin, alt_end, alt_end)
1868+ return re.compile("".join([se[0] for se in subexprs] +
1869+ [se[1] for se in reversed(subexprs)] +
1870+ [r'\Z']))
1871+
1872+
1873+def tokens_to_re(tokens):
1874+ """Convert tokens to a regular expression for exact matching."""
1875+
1876+ def token_to_re(token):
1877+ """Convert tokens to simple regular expressions."""
1878+ if token == '*':
1879+ return r'.*'
1880+ else:
1881+ return re.escape(token)
1882+
1883+ return re.compile("".join(map(token_to_re, tokens) + [r'\Z']))
1884+
1885+
1886+class SimplePattern(Pattern):
1887+ """Pattern that matches according to the given pattern expression."""
1888+
1889+ def __init__(self, pattern):
1890+ """Initialize an instance."""
1891+ super(SimplePattern, self).__init__()
1892+ tokens = PATTERN_TOKEN_RE.findall(pattern)
1893+ self.partial_re = tokens_to_partial_re(tokens)
1894+ self.full_re = tokens_to_re(tokens)
1895+
1896+ def prefix_matches(self, partial_name):
1897+ """Return True if the partial name matches."""
1898+ return self.partial_re.match(partial_name) is not None
1899+
1900+ def matches(self, name):
1901+ """Return True if the complete name matches."""
1902+ return self.full_re.match(name) is not None
1903+
1904+
1905+class PrefixPattern(Pattern):
1906+ """Pattern that assumes a previously given prefix."""
1907+
1908+ def __init__(self, prefix, pattern):
1909+ """Initialize an instance."""
1910+ super(PrefixPattern, self).__init__()
1911+ self.prefix = prefix
1912+ self.pattern = pattern
1913+
1914+ def assume_prefix(self, prefix):
1915+ """Return an equivalent pattern with the given prefix baked in."""
1916+ return PrefixPattern(self.prefix + prefix, self.pattern)
1917+
1918+ def prefix_matches(self, partial_name):
1919+ """Return True if the partial name matches."""
1920+ return self.pattern.prefix_matches(self.prefix + partial_name)
1921+
1922+ def matches(self, name):
1923+ """Return True if the complete name matches."""
1924+ return self.pattern.matches(self.prefix + name)
1925+
1926+
1927+class SumPattern(Pattern):
1928+ """Pattern that matches if at least one given pattern matches."""
1929+
1930+ def __init__(self, patterns):
1931+ """Initialize an instance."""
1932+ super(SumPattern, self).__init__()
1933+ self.patterns = patterns
1934+
1935+ def prefix_matches(self, partial_name):
1936+ """Return True if the partial name matches."""
1937+ for pattern in self.patterns:
1938+ if pattern.prefix_matches(partial_name):
1939+ return True
1940+ return False
1941+
1942+ def matches(self, name):
1943+ """Return True if the complete name matches."""
1944+ for pattern in self.patterns:
1945+ if pattern.matches(name):
1946+ return True
1947+ return False
1948
1949=== modified file 'demo.yaml'
1950--- demo.yaml 2014-07-24 19:30:30 +0000
1951+++ demo.yaml 2014-07-24 22:10:31 +0000
1952@@ -14,5 +14,5 @@
1953 host: 127.0.0.1
1954 port: 6379
1955 password: foobared
1956-- type: url
1957+- type: http
1958 url: https://login.ubuntu.com/
1959
1960=== modified file 'setup.py'
1961--- setup.py 2014-07-24 15:20:04 +0000
1962+++ setup.py 2014-07-24 22:10:31 +0000
1963@@ -27,7 +27,7 @@
1964 include_package_data=True,
1965 entry_points={
1966 'console_scripts': [
1967- 'conn-check = conn_check:run',
1968+ 'conn-check = conn_check.main:run',
1969 ],
1970 },
1971 license='GPL3',
1972
1973=== modified file 'tests.py'
1974--- tests.py 2014-07-24 21:09:27 +0000
1975+++ tests.py 2014-07-24 22:10:31 +0000
1976@@ -3,7 +3,28 @@
1977
1978 from testtools import matchers
1979
1980-import conn_check
1981+from conn_check.check_impl import (
1982+ FunctionCheck,
1983+ MultiCheck,
1984+ parallel_strategy,
1985+ PrefixCheckWrapper,
1986+ sequential_strategy,
1987+ )
1988+from conn_check.checks import (
1989+ CHECKS,
1990+ extract_host_port,
1991+ make_amqp_check,
1992+ make_http_check,
1993+ make_postgres_check,
1994+ make_redis_check,
1995+ make_ssl_check,
1996+ make_tcp_check,
1997+ make_udp_check,
1998+ )
1999+from conn_check.main import (
2000+ build_checks,
2001+ check_from_description,
2002+ )
2003
2004
2005 class FunctionCheckMatcher(testtools.Matcher):
2006@@ -15,7 +36,7 @@
2007
2008 def match(self, matchee):
2009 checks = []
2010- checks.append(matchers.IsInstance(conn_check.FunctionCheck))
2011+ checks.append(matchers.IsInstance(FunctionCheck))
2012 checks.append(matchers.Annotate(
2013 "name doesn't match",
2014 matchers.AfterPreprocessing(operator.attrgetter('name'),
2015@@ -43,7 +64,7 @@
2016
2017 def match(self, matchee):
2018 checks = []
2019- checks.append(matchers.IsInstance(conn_check.MultiCheck))
2020+ checks.append(matchers.IsInstance(MultiCheck))
2021 checks.append(matchers.AfterPreprocessing(operator.attrgetter('strategy'),
2022 matchers.Is(self.strategy)))
2023 checks.append(matchers.AfterPreprocessing(operator.attrgetter('subchecks'),
2024@@ -58,40 +79,40 @@
2025 class ExtractHostPortTests(testtools.TestCase):
2026
2027 def test_basic(self):
2028- self.assertEqual(conn_check.extract_host_port('http://localhost:80/'),
2029+ self.assertEqual(extract_host_port('http://localhost:80/'),
2030 ('localhost', 80, 'http'))
2031
2032 def test_no_scheme(self):
2033- self.assertEqual(conn_check.extract_host_port('//localhost/'),
2034+ self.assertEqual(extract_host_port('//localhost/'),
2035 ('localhost', 80, 'http'))
2036
2037 def test_no_port_http(self):
2038- self.assertEqual(conn_check.extract_host_port('http://localhost/'),
2039+ self.assertEqual(extract_host_port('http://localhost/'),
2040 ('localhost', 80, 'http'))
2041
2042 def test_no_port_https(self):
2043- self.assertEqual(conn_check.extract_host_port('https://localhost/'),
2044+ self.assertEqual(extract_host_port('https://localhost/'),
2045 ('localhost', 443, 'https'))
2046
2047
2048 class ConnCheckTest(testtools.TestCase):
2049
2050 def test_make_tcp_check(self):
2051- result = conn_check.make_tcp_check('localhost', 8080)
2052+ result = make_tcp_check('localhost', 8080)
2053 self.assertThat(result, FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2054
2055 def test_make_ssl_check(self):
2056- result = conn_check.make_ssl_check('localhost', 8080, verify=True)
2057+ result = make_ssl_check('localhost', 8080, verify=True)
2058 self.assertThat(result, FunctionCheckMatcher('ssl.localhost:8080', 'localhost:8080'))
2059
2060 def test_make_udp_check(self):
2061- result = conn_check.make_udp_check('localhost', 8080, 'foo', 'bar')
2062+ result = make_udp_check('localhost', 8080, 'foo', 'bar')
2063 self.assertThat(result, FunctionCheckMatcher('udp.localhost:8080', 'localhost:8080'))
2064
2065 def test_make_http_check(self):
2066- result = conn_check.make_http_check('http://localhost/')
2067+ result = make_http_check('http://localhost/')
2068 self.assertThat(result,
2069- MultiCheckMatcher(strategy=conn_check.sequential_strategy,
2070+ MultiCheckMatcher(strategy=sequential_strategy,
2071 subchecks=[
2072 FunctionCheckMatcher('tcp.localhost:80', 'localhost:80'),
2073 FunctionCheckMatcher('GET.http://localhost/', 'GET http://localhost/')
2074@@ -99,9 +120,9 @@
2075 ))
2076
2077 def test_make_http_check_https(self):
2078- result = conn_check.make_http_check('https://localhost/')
2079+ result = make_http_check('https://localhost/')
2080 self.assertThat(result,
2081- MultiCheckMatcher(strategy=conn_check.sequential_strategy,
2082+ MultiCheckMatcher(strategy=sequential_strategy,
2083 subchecks=[
2084 FunctionCheckMatcher('tcp.localhost:443', 'localhost:443'),
2085 FunctionCheckMatcher('ssl.localhost:443', 'localhost:443'),
2086@@ -110,10 +131,10 @@
2087 ))
2088
2089 def test_make_amqp_check(self):
2090- result = conn_check.make_amqp_check('localhost', 8080, 'foo',
2091- 'bar', use_ssl=True, vhost='/')
2092- self.assertIsInstance(result, conn_check.MultiCheck)
2093- self.assertIs(result.strategy, conn_check.sequential_strategy)
2094+ result = make_amqp_check('localhost', 8080, 'foo',
2095+ 'bar', use_ssl=True, vhost='/')
2096+ self.assertIsInstance(result, MultiCheck)
2097+ self.assertIs(result.strategy, sequential_strategy)
2098 self.assertEqual(len(result.subchecks), 3)
2099 self.assertThat(result.subchecks[0],
2100 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2101@@ -122,20 +143,20 @@
2102 self.assertThat(result.subchecks[2], FunctionCheckMatcher('auth', 'user foo'))
2103
2104 def test_make_amqp_check_no_ssl(self):
2105- result = conn_check.make_amqp_check('localhost', 8080, 'foo',
2106- 'bar', use_ssl=False, vhost='/')
2107- self.assertIsInstance(result, conn_check.MultiCheck)
2108- self.assertIs(result.strategy, conn_check.sequential_strategy)
2109+ result = make_amqp_check('localhost', 8080, 'foo',
2110+ 'bar', use_ssl=False, vhost='/')
2111+ self.assertIsInstance(result, MultiCheck)
2112+ self.assertIs(result.strategy, sequential_strategy)
2113 self.assertEqual(len(result.subchecks), 2)
2114 self.assertThat(result.subchecks[0],
2115 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2116 self.assertThat(result.subchecks[1], FunctionCheckMatcher('auth', 'user foo'))
2117
2118 def test_make_postgres_check(self):
2119- result = conn_check.make_postgres_check('localhost', 8080,'foo',
2120- 'bar', 'test')
2121- self.assertIsInstance(result, conn_check.MultiCheck)
2122- self.assertIs(result.strategy, conn_check.sequential_strategy)
2123+ result = make_postgres_check('localhost', 8080,'foo',
2124+ 'bar', 'test')
2125+ self.assertIsInstance(result, MultiCheck)
2126+ self.assertIs(result.strategy, sequential_strategy)
2127 self.assertEqual(len(result.subchecks), 2)
2128 self.assertThat(result.subchecks[0],
2129 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2130@@ -143,33 +164,33 @@
2131 FunctionCheckMatcher('auth', 'user foo', blocking=True))
2132
2133 def test_make_postgres_check_local_socket(self):
2134- result = conn_check.make_postgres_check('/local.sock', 8080,'foo',
2135- 'bar', 'test')
2136- self.assertIsInstance(result, conn_check.MultiCheck)
2137- self.assertIs(result.strategy, conn_check.sequential_strategy)
2138+ result = make_postgres_check('/local.sock', 8080,'foo',
2139+ 'bar', 'test')
2140+ self.assertIsInstance(result, MultiCheck)
2141+ self.assertIs(result.strategy, sequential_strategy)
2142 self.assertEqual(len(result.subchecks), 1)
2143 self.assertThat(result.subchecks[0],
2144 FunctionCheckMatcher('auth', 'user foo', blocking=True))
2145
2146 def test_make_redis_check(self):
2147- result = conn_check.make_redis_check('localhost', 8080)
2148- self.assertIsInstance(result, conn_check.PrefixCheckWrapper)
2149+ result = make_redis_check('localhost', 8080)
2150+ self.assertIsInstance(result, PrefixCheckWrapper)
2151 self.assertEqual(result.prefix, 'redis.')
2152 wrapped = result.wrapped
2153- self.assertIsInstance(wrapped, conn_check.MultiCheck)
2154- self.assertIs(wrapped.strategy, conn_check.sequential_strategy)
2155+ self.assertIsInstance(wrapped, MultiCheck)
2156+ self.assertIs(wrapped.strategy, sequential_strategy)
2157 self.assertEqual(len(wrapped.subchecks), 2)
2158 self.assertThat(wrapped.subchecks[0],
2159 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2160 self.assertThat(wrapped.subchecks[1], FunctionCheckMatcher('connect', None))
2161
2162 def test_make_redis_check_with_password(self):
2163- result = conn_check.make_redis_check('localhost', 8080, 'foobar')
2164- self.assertIsInstance(result, conn_check.PrefixCheckWrapper)
2165+ result = make_redis_check('localhost', 8080, 'foobar')
2166+ self.assertIsInstance(result, PrefixCheckWrapper)
2167 self.assertEqual(result.prefix, 'redis.')
2168 wrapped = result.wrapped
2169- self.assertIsInstance(wrapped, conn_check.MultiCheck)
2170- self.assertIs(wrapped.strategy, conn_check.sequential_strategy)
2171+ self.assertIsInstance(wrapped, MultiCheck)
2172+ self.assertIs(wrapped.strategy, sequential_strategy)
2173 self.assertEqual(len(wrapped.subchecks), 2)
2174 self.assertThat(wrapped.subchecks[0],
2175 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2176@@ -178,28 +199,28 @@
2177
2178 def test_check_from_description_unknown_type(self):
2179 e = self.assertRaises(AssertionError,
2180- conn_check.check_from_description, {'type': 'foo'})
2181+ check_from_description, {'type': 'foo'})
2182 self.assertEqual(
2183 str(e),
2184- "Unknown check type: foo, available checks: {}".format(conn_check.CHECKS.keys()))
2185+ "Unknown check type: foo, available checks: {}".format(CHECKS.keys()))
2186
2187 def test_check_from_description_missing_arg(self):
2188 description = {'type': 'tcp'}
2189 e = self.assertRaises(AssertionError,
2190- conn_check.check_from_description, description)
2191+ check_from_description, description)
2192 self.assertEqual(
2193 str(e),
2194 "host missing from check: {}".format(description))
2195
2196 def test_check_from_description_makes_check(self):
2197 description = {'type': 'tcp', 'host': 'localhost', 'port': '8080'}
2198- result = conn_check.check_from_description(description)
2199+ result = check_from_description(description)
2200 self.assertThat(result,
2201 FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080'))
2202
2203 def test_build_checks(self):
2204 description = [{'type': 'tcp', 'host': 'localhost', 'port': '8080'}]
2205- result = conn_check.build_checks(description)
2206+ result = build_checks(description)
2207 self.assertThat(result,
2208- MultiCheckMatcher(strategy=conn_check.parallel_strategy,
2209+ MultiCheckMatcher(strategy=parallel_strategy,
2210 subchecks=[FunctionCheckMatcher('tcp.localhost:8080', 'localhost:8080')]))

Subscribers

People subscribed via source and target branches