Mercurial > hg > config
comparison python/check_endpoints.py @ 778:9b75dc884c78
add check_endpoints script
author | Jeff Hammel <k0scist@gmail.com> |
---|---|
date | Wed, 13 Jul 2016 16:09:11 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
777:ef6731a4ad86 | 778:9b75dc884c78 |
---|---|
1 #!/usr/bin/env python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 monitor custom endpoints form a JSON file | |
6 """ | |
7 | |
8 # imports | |
9 import argparse | |
10 import json | |
11 import os | |
12 import requests | |
13 import statuspage | |
14 import sys | |
15 import time | |
16 | |
17 # What we want but I'm not sure if it works in python 2.6 | |
18 # http://stackoverflow.com/questions/6921699/can-i-get-json-to-load-into-an-ordereddict-in-python | |
19 | |
20 | |
21 def main(args=sys.argv[1:]): | |
22 """CLI""" | |
23 | |
24 # parse command line | |
25 parser = argparse.ArgumentParser(description=__doc__) | |
26 parser.add_argument('input', | |
27 type=argparse.FileType('r'), | |
28 help="input JSON file") | |
29 parser.add_argument('--customer', dest='customer', nargs='+', | |
30 help="customer to act on") | |
31 parser.add_argument('--list-customers', dest='list_customers', | |
32 action='store_true', default=False, | |
33 help="list customers and return") | |
34 parser.add_argument('--list-endpoints', dest='list_endpoints', | |
35 action='store_true', default=False, | |
36 help="list endpoints and exit") | |
37 parser.add_argument('--failures', dest='output_failures', | |
38 action='store_true', default=False, | |
39 help="output failures only") | |
40 parser.add_argument('--successes', dest='output_successes', | |
41 action='store_true', default=False, | |
42 help="output successes only") | |
43 parser.add_argument('--timeout', dest='timeout', | |
44 type=float, default=60, | |
45 help="request timeout in seconds [DEFAULT: %(default)s]") | |
46 parser.add_argument('--verify', dest='verify', | |
47 action='store_true', default=False, | |
48 help="verify HTTPS requests") | |
49 parser.add_argument('--statuspage', '--statuspage-io', dest='statuspage_io', | |
50 nargs=2, metavar=('PAGE_ID', 'API_KEY'), | |
51 help="post status to statuspage.io") | |
52 options = parser.parse_args(args) | |
53 | |
54 # sanity | |
55 if options.output_failures and options.output_successes: | |
56 parser.error("Cannot use both --failures and --successes") | |
57 | |
58 if not options.verify: | |
59 # disable insecure warnings: | |
60 # http://stackoverflow.com/questions/27981545/suppress-insecurerequestwarning-unverified-https-request-is-being-made-in-pytho | |
61 requests.packages.urllib3.disable_warnings() | |
62 | |
63 # load input file JSON | |
64 data = json.load(options.input) | |
65 | |
66 # check for conformity | |
67 message = "Expected a dict with a list of URLs" | |
68 if not isinstance(data, dict): | |
69 parser.error(message) | |
70 if not all([isinstance(value, list) | |
71 for value in data.values()]): | |
72 parser.error(message) | |
73 | |
74 if options.list_customers: | |
75 # list customers and exit | |
76 print (json.dumps(sorted(data.keys()), | |
77 indent=2)) | |
78 return | |
79 | |
80 if options.customer: | |
81 # choose data only for subset of customers | |
82 missing = [customer for customer in options.customer | |
83 if customer not in data] | |
84 if missing: | |
85 parser.error("Customers not found: {0}".format(', '.join(missing))) | |
86 data = dict([(customer, data[customer]) | |
87 for customer in options.customer]) | |
88 | |
89 if options.list_endpoints: | |
90 # list all endpoints and exit | |
91 print (json.dumps(sorted(sum(data.values(), [])), indent=2)) | |
92 return | |
93 | |
94 # hit endpoints | |
95 results = {} | |
96 for customer, endpoints in data.items(): | |
97 results[customer] = [] | |
98 for endpoint in endpoints: | |
99 data = {'url': endpoint, 'success': False} | |
100 try: | |
101 start = time.time() | |
102 response = requests.get(endpoint, | |
103 verify=options.verify, | |
104 timeout=options.timeout) | |
105 end = time.time() | |
106 data['status_code'] = response.status_code | |
107 data['reason'] = response.reason | |
108 response.raise_for_status() | |
109 data['success'] = True | |
110 except requests.HTTPError as e: | |
111 end = time.time() | |
112 pass # we already have the data we need | |
113 except requests.exceptions.SSLError as e: | |
114 end = time.time() | |
115 data['reason'] = "SSL Error: {0}".format(e) | |
116 except (requests.ConnectionError, requests.Timeout) as e: | |
117 # despite what it says at | |
118 # http://stackoverflow.com/questions/16511337/correct-way-to-try-except-using-python-requests-module | |
119 # both `requests.Timeout` and `requests.ConnectionError` seem to fall to this point | |
120 # so we descriminate for either | |
121 end = time.time() | |
122 | |
123 try: | |
124 args = e.args[0].reason.args | |
125 if len(args) == 2: | |
126 message = args[-1] | |
127 elif len(args) == 1: | |
128 message = args[0].split(':', 1)[-1] | |
129 except AttributeError: | |
130 # Protocol error | |
131 message = "{0} {1}".format(e.args[0].args[0], | |
132 e.args[0].args[-1].args[-1]) | |
133 data['reason'] = message | |
134 data['duration'] = end - start | |
135 results[customer].append(data) | |
136 | |
137 # obtain successes + failures | |
138 failures = {} | |
139 successes = {} | |
140 for customer, result in results.items(): | |
141 for item in result: | |
142 append_to = successes if item['success'] else failures | |
143 append_to.setdefault(customer, []).append(item) | |
144 | |
145 # serialize results | |
146 if options.statuspage_io: | |
147 api = statuspage.StatuspageIO(*options.statuspage_io) | |
148 | |
149 # get existing components | |
150 components = api.components() | |
151 | |
152 # figure out what components we need to make | |
153 # we use the customer name as a key so we will go from there | |
154 name_map = dict([(component['name'], component) | |
155 for component in components]) | |
156 id_map = {} | |
157 for customer, data in results.items(): | |
158 | |
159 for item in data: | |
160 name = item['url'] | |
161 if 'code' in item: | |
162 description = "{code} {reason}".format(**item) | |
163 else: | |
164 description = item['reason'] | |
165 endpoint_component = name_map.get(name) | |
166 if endpoint_component is None: | |
167 try: | |
168 endpoint_component = api.create_component(name, description) | |
169 except Exception as e: | |
170 import pdb; pdb.set_trace() | |
171 | |
172 # determine status | |
173 if item['success']: | |
174 status = "operational" | |
175 else: | |
176 status = "operational" # TODO: you could be better | |
177 api.update_component(endpoint_component['id'], | |
178 description=description, | |
179 status=status) | |
180 else: | |
181 if options.output_failures: | |
182 output_data = failures | |
183 elif options.output_successes: | |
184 output_data = successes | |
185 else: | |
186 output_data = results | |
187 print (json.dumps(output_data, sort_keys=True, indent=2)) | |
188 | |
189 # exit with appropriate code | |
190 sys.exit(1 if failures else 0) | |
191 | |
192 if __name__ == '__main__': | |
193 main() |