Merge lp:~adeuring/charmworld/more-heartbeat-info-2 into lp:charmworld

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: 438
Merged at revision: 455
Proposed branch: lp:~adeuring/charmworld/more-heartbeat-info-2
Merge into: lp:charmworld
Diff against target: 344 lines (+176/-16)
6 files modified
charmworld/health.py (+55/-3)
charmworld/search.py (+4/-0)
charmworld/tests/test_health.py (+88/-8)
charmworld/tests/test_search.py (+11/-0)
charmworld/views/misc.py (+6/-2)
charmworld/views/tests/test_misc.py (+12/-3)
To merge this branch: bzr merge lp:~adeuring/charmworld/more-heartbeat-info-2
Reviewer Review Type Date Requested Status
Juju Gui Bot continuous-integration Approve
Curtis Hovey (community) code Approve
Review via email: mp+193454@code.launchpad.net

Commit message

Add two more checks to the heartbeat page: Status of the ElasticSearch server ans the current bzr revision number.

Description of the change

This branch adds two more checks to the heartbeat page: Status of the ElasticSearch server ans the current bzr revision number.

When no charms are marked as new, popular of featured, the heartbeat page now shows some numbers.

There health module already had a function called check_elasticsearch() that tests if the charms index knows about at least one charm. While this is also in implicit check of the ES server, it is IMHO more a check if the _index_ is usable, so I renamed the function.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you Abel

review: Approve (code)
Revision history for this message
Juju Gui Bot (juju-gui-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/health.py'
2--- charmworld/health.py 2013-10-30 13:20:01 +0000
3+++ charmworld/health.py 2013-10-31 16:32:26 +0000
4@@ -1,7 +1,10 @@
5 # Copyright 2013 Canonical Ltd. This software is licensed under the
6 # GNU Affero General Public License version 3 (see the file LICENSE).
7
8+from bzrlib.branch import Branch
9+from bzrlib.transport import get_transport
10 from collections import namedtuple
11+from os.path import dirname
12
13
14 Check = namedtuple('check', 'name status remark')
15@@ -38,7 +41,7 @@
16 return _check_collection_size(request, 'bundles')
17
18
19-def check_elasticsearch(request):
20+def check_charm_index(request):
21 """Return a Check of the elasticsearch charms index."""
22 status = FAIL
23 try:
24@@ -68,7 +71,12 @@
25 status = PASS
26 remark = 'Interesting has new, popular, and featured.'
27 else:
28- remark = 'Interesting is missing new, popular, or featured.'
29+ remark = (
30+ 'Interesting is missing new (found {0}), popular '
31+ '(found {1}), or featured (found {2}).')
32+ remark = remark.format(
33+ len(interesting['new']), len(interesting['popular']),
34+ len(interesting['featured']))
35 except:
36 remark = 'The API2 is not available.'
37 finally:
38@@ -89,7 +97,12 @@
39 status = PASS
40 remark = 'Interesting has new, popular, and featured.'
41 else:
42- remark = 'Interesting is missing new, popular, or featured.'
43+ remark = (
44+ 'Interesting is missing new (found {0}), popular '
45+ '(found {1}), or featured (found {2}).')
46+ remark = remark.format(
47+ len(interesting['new']), len(interesting['popular']),
48+ len(interesting['featured']))
49 except:
50 remark = 'The API3 is not available.'
51 finally:
52@@ -133,3 +146,42 @@
53 finally:
54 check = Check('Ingest queue sizes', status, remark)
55 return check
56+
57+
58+def check_bzr_revision(request):
59+ """Return the current bzr revision."""
60+ status = FAIL
61+ try:
62+ branch_dir = dirname(dirname(__file__))
63+ transport = get_transport(branch_dir)
64+ branch = Branch.open_from_transport(transport)
65+ remark = str(branch.revno())
66+ status = PASS
67+ except:
68+ remark = "Can't retrieve revision number."
69+ finally:
70+ check = Check('BZR revision', status, remark)
71+ return check
72+
73+
74+def check_elasticsearch_status(request):
75+ """Check the health status of the ElasticSearch server."""
76+ status = FAIL
77+ try:
78+ health = request.index_client.health()
79+ remark = (
80+ 'status: {status}, '
81+ 'nodes: {number_of_nodes}, '
82+ 'active primary shards: {active_primary_shards}, '
83+ 'active shards: {active_shards}, '
84+ 'initializing shards: {initializing_shards}, '
85+ 'relocating shards: {relocating_shards}'
86+ )
87+ remark = remark.format(**health)
88+ if health['status'] in ('green', 'yellow'):
89+ status = PASS
90+ except Exception:
91+ remark = "Can't retrieve the ES server's health status."
92+ finally:
93+ check = Check('ElasticSearch server', status, remark)
94+ return check
95
96=== modified file 'charmworld/search.py'
97--- charmworld/search.py 2013-09-16 21:14:06 +0000
98+++ charmworld/search.py 2013-10-31 16:32:26 +0000
99@@ -729,6 +729,10 @@
100 copy.index_bundles(bundles)
101 return copy
102
103+ def health(self):
104+ """The health status of the ES server."""
105+ return self._client.health(self.index_name)
106+
107
108 def reindex(index_client, charms=None):
109 """Reindex documents with the current mapping.
110
111=== modified file 'charmworld/tests/test_health.py'
112--- charmworld/tests/test_health.py 2013-10-30 13:20:01 +0000
113+++ charmworld/tests/test_health.py 2013-10-31 16:32:26 +0000
114@@ -7,9 +7,11 @@
115 check_api2,
116 check_api3,
117 check_bundles_collection,
118+ check_bzr_revision,
119 check_charms_collection,
120 check_collections,
121- check_elasticsearch,
122+ check_charm_index,
123+ check_elasticsearch_status,
124 check_ingest_queues,
125 )
126 from charmworld.models import FeaturedSource
127@@ -70,25 +72,25 @@
128 check = check_bundles_collection(request)
129 self.assertCheck(check, 'bundles collection', 'Fail')
130
131- def test_check_elasticsearch_pass(self):
132+ def test_check_charm_index_pass(self):
133 charm_data = factory.makeCharm(self.db)[1]
134 self.use_index_client()
135 self.index_client.index_charm(charm_data)
136- check = check_elasticsearch(self.getRequest())
137+ check = check_charm_index(self.getRequest())
138 remark = '1 matches found'
139 self.assertCheck(check, 'charms index', 'Pass', remark)
140
141- def test_check_elasticsearch_missing_data_fail(self):
142+ def test_check_charm_index_missing_data_fail(self):
143 self.use_index_client()
144 request = self.getRequest()
145- check = check_elasticsearch(request)
146+ check = check_charm_index(request)
147 remark = 'There are no matches. Is ingest running?'
148 self.assertCheck(check, 'charms index', 'Fail', remark)
149
150- def test_check_elasticsearch_no_index_fail(self):
151+ def test_check_charm_index_no_index_fail(self):
152 request = self.getRequest()
153 # The index is not setup.
154- check = check_elasticsearch(request)
155+ check = check_charm_index(request)
156 self.assertCheck(check, 'charms index', 'Fail')
157
158 def _check_api_pass(self, function, version):
159@@ -106,7 +108,9 @@
160 self.index_client.index_charm(charm_data)
161 # There are no featured or downloaded charms.
162 check = function(self.getRequest())
163- remark = 'Interesting is missing new, popular, or featured.'
164+ remark = (
165+ 'Interesting is missing new (found 1), popular (found 1), or '
166+ 'featured (found 0).')
167 self.assertCheck(check, 'API%s interesting' % version, 'Fail', remark)
168
169 def _check_api_no_matches_fail(self, function, version):
170@@ -181,3 +185,79 @@
171 self.assertCheck(
172 check, 'Ingest queue sizes', 'Fail',
173 'Cannot query the ingest queues.')
174+
175+ def test_check_bzr_revision(self):
176+ request = self.getRequest()
177+ check = check_bzr_revision(request)
178+ self.assertCheck(check, 'BZR revision', 'Pass')
179+ # check.remark is an integer: No exception is raised.
180+ int(check.remark)
181+
182+ def test_check_bzr_revision_error(self):
183+ request = self.getRequest()
184+ from charmworld import health
185+ with patch.object(health, 'get_transport',
186+ new_callable=self.makeFailCallable):
187+ check = check_bzr_revision(request)
188+ remark = "Can't retrieve revision number."
189+ self.assertCheck(check, 'BZR revision', 'Fail', remark)
190+
191+ def test_check_elasticsearch_status(self):
192+ self.use_index_client()
193+ request = self.getRequest()
194+ check = check_elasticsearch_status(request)
195+ self.assertCheck(check, 'ElasticSearch server', 'Pass')
196+ self.assertTrue(check.remark.startswith('status: green, nodes:'))
197+
198+ def test_check_elasticsearch_status_es_yellow_status(self):
199+ index_client = self.use_index_client()
200+
201+ def make_health_method():
202+ def health():
203+ return {
204+ 'status': 'yellow',
205+ 'number_of_nodes': 1,
206+ 'active_primary_shards': 0,
207+ 'active_shards': 1,
208+ 'initializing_shards': 0,
209+ 'relocating_shards': 0,
210+ }
211+ return health
212+
213+ with patch.object(index_client, 'health',
214+ new_callable=make_health_method):
215+ request = self.getRequest()
216+ check = check_elasticsearch_status(request)
217+ self.assertCheck(check, 'ElasticSearch server', 'Pass')
218+ self.assertTrue(check.remark.startswith('status: yellow, nodes:'))
219+
220+ def test_check_elasticsearch_status_es_red_status(self):
221+ index_client = self.use_index_client()
222+
223+ def make_health_method():
224+ def health():
225+ return {
226+ 'status': 'red',
227+ 'number_of_nodes': 1,
228+ 'active_primary_shards': 0,
229+ 'active_shards': 1,
230+ 'initializing_shards': 0,
231+ 'relocating_shards': 0,
232+ }
233+ return health
234+
235+ with patch.object(index_client, 'health',
236+ new_callable=make_health_method):
237+ request = self.getRequest()
238+ check = check_elasticsearch_status(request)
239+ self.assertCheck(check, 'ElasticSearch server', 'Fail')
240+ self.assertTrue(check.remark.startswith('status: red, nodes:'))
241+
242+ def test_check_elasticsearch_status_es_access_failing(self):
243+ index_client = self.use_index_client()
244+ with patch.object(index_client, 'health',
245+ new_callable=self.makeFailCallable):
246+ request = self.getRequest()
247+ check = check_elasticsearch_status(request)
248+ remark = "Can't retrieve the ES server's health status."
249+ self.assertCheck(check, 'ElasticSearch server', 'Fail', remark)
250
251=== modified file 'charmworld/tests/test_search.py'
252--- charmworld/tests/test_search.py 2013-10-24 17:24:43 +0000
253+++ charmworld/tests/test_search.py 2013-10-31 16:32:26 +0000
254@@ -904,6 +904,17 @@
255 after = self.index_client.get_mapping()
256 self.assertEqual(before, after)
257
258+ def test_health(self):
259+ # ElasticSearchClient.health() returns the health status of
260+ # the index.
261+ status = self.index_client.health()
262+ expected_keys = set((
263+ 'active_primary_shards', 'active_shards', 'cluster_name',
264+ 'initializing_shards', 'number_of_data_nodes',
265+ 'number_of_nodes', 'relocating_shards', 'status', 'timed_out',
266+ 'unassigned_shards'))
267+ self.assertEqual(expected_keys, set(status))
268+
269
270 class TestIndexingBundles(TestCase):
271
272
273=== modified file 'charmworld/views/misc.py'
274--- charmworld/views/misc.py 2013-10-30 13:20:01 +0000
275+++ charmworld/views/misc.py 2013-10-31 16:32:26 +0000
276@@ -10,9 +10,11 @@
277 check_api2,
278 check_api3,
279 check_bundles_collection,
280+ check_bzr_revision,
281+ check_charm_index,
282 check_charms_collection,
283 check_collections,
284- check_elasticsearch,
285+ check_elasticsearch_status,
286 check_ingest_queues,
287 )
288
289@@ -53,9 +55,11 @@
290 checks = []
291 checks.append(check_charms_collection(request))
292 checks.append(check_bundles_collection(request))
293- checks.append(check_elasticsearch(request))
294+ checks.append(check_charm_index(request))
295 checks.append(check_api2(request))
296 checks.append(check_api3(request))
297 checks.append(check_collections(request))
298 checks.append(check_ingest_queues(request))
299+ checks.append(check_bzr_revision(request))
300+ checks.append(check_elasticsearch_status(request))
301 return {'checks': checks}
302
303=== modified file 'charmworld/views/tests/test_misc.py'
304--- charmworld/views/tests/test_misc.py 2013-10-30 13:20:01 +0000
305+++ charmworld/views/tests/test_misc.py 2013-10-31 16:32:26 +0000
306@@ -127,7 +127,7 @@
307 response = heartbeat(request)
308
309 checks = response['checks']
310- self.assertEqual(7, len(checks))
311+ self.assertEqual(9, len(checks))
312 remark = '1 charms found'
313 self.assertCheck(checks[0], 'charms collection', 'Pass', remark)
314 remark = '1 bundles found'
315@@ -141,14 +141,19 @@
316 self.assertCheck(checks[5], 'MongoDB collections', 'Pass', remark)
317 remark = 'queued charms: 0, queued baskets: 0.'
318 self.assertCheck(checks[6], 'Ingest queue sizes', 'Pass', remark)
319+ self.assertCheck(checks[7], 'BZR revision', 'Pass')
320+ self.assertCheck(checks[8], 'ElasticSearch server', 'Pass')
321
322 def test_checks_fail(self):
323 # When services or data are not available, the checks fail.
324 # No setup is performed, creating a case where services and data
325 # are not available
326- response = heartbeat(self.getRequest())
327+ from charmworld import health
328+ with patch.object(health, 'get_transport',
329+ new_callable=self.makeFailCallable):
330+ response = heartbeat(self.getRequest())
331 checks = response['checks']
332- self.assertEqual(7, len(checks))
333+ self.assertEqual(9, len(checks))
334 remark = 'There are no charms. Is ingest running?'
335 self.assertCheck(checks[0], 'charms collection', 'Fail', remark)
336 remark = 'There are no bundles. Is ingest running?'
337@@ -165,3 +170,7 @@
338 self.assertCheck(checks[5], 'MongoDB collections', 'Fail', remark)
339 remark = 'queued charms: 0, queued baskets: 0.'
340 self.assertCheck(checks[6], 'Ingest queue sizes', 'Pass', remark)
341+ remark = "Can't retrieve revision number."
342+ self.assertCheck(checks[7], 'BZR revision', 'Fail', remark)
343+ remark = "Can't retrieve the ES server's health status."
344+ self.assertCheck(checks[8], 'ElasticSearch server', 'Fail', remark)

Subscribers

People subscribed via source and target branches

to all changes: