Merge ~mthaddon/juju-upgrader/+git/juju-upgrader:juju-statistics into juju-upgrader:master
- Git
- lp:~mthaddon/juju-upgrader/+git/juju-upgrader
- juju-statistics
- Merge into master
Proposed by
Tom Haddon
Status: | Merged |
---|---|
Approved by: | Tom Haddon |
Approved revision: | c38721719d6b67bf51f69c0cb30d2a75fb468540 |
Merged at revision: | 62695b0894f71e62fbfaa461eafc22fbe9d5ba4c |
Proposed branch: | ~mthaddon/juju-upgrader/+git/juju-upgrader:juju-statistics |
Merge into: | juju-upgrader:master |
Diff against target: |
545 lines (+533/-0) 2 files modified
juju_statistics.py (+79/-0) unit_tests/test_juju_statistics.py (+454/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joel Sing (community) | +1 | Approve | |
Stuart Bishop (community) | Approve | ||
Review via email: mp+335695@code.launchpad.net |
Commit message
Add juju_statistics with unit tests
Description of the change
Add juju_statistics with unit tests
To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Revision history for this message
Stuart Bishop (stub) wrote : | # |
All looks good. Some taste things, not necessarily recommended to take on board.
review:
Approve
Revision history for this message
Joel Sing (jsing) wrote : | # |
LGTM with some minor comments.
review:
Approve
(+1)
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 62695b0894f71e6
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/juju_statistics.py b/juju_statistics.py |
2 | new file mode 100644 |
3 | index 0000000..12bde95 |
4 | --- /dev/null |
5 | +++ b/juju_statistics.py |
6 | @@ -0,0 +1,79 @@ |
7 | +# Copyright (c) 2017, 2018 Canonical Ltd |
8 | +# License: GPLv3 |
9 | +# Authors: Paul Gear <paul.gear@canonical.com> |
10 | +# Tom Haddon <tom.haddon@canonical.com> |
11 | + |
12 | +import yaml |
13 | + |
14 | +import juju_utils |
15 | + |
16 | + |
17 | +class Statistics: |
18 | + |
19 | + def __init__(self): |
20 | + self.totals = {} |
21 | + self.summary = {} |
22 | + |
23 | + def has_stats(self): |
24 | + return len(self.totals) > 0 or len(self.summary) > 0 |
25 | + |
26 | + def add_model(self, model): |
27 | + self._increment_data('models') |
28 | + |
29 | + # count model breakdowns |
30 | + self._increment_summary('life', 'model', juju_utils.get_model_life(model)) |
31 | + self._increment_summary('status', 'model', juju_utils.get_model_state(model)) |
32 | + |
33 | + def add_status(self, status): |
34 | + # count model versions |
35 | + self._increment_summary('version', 'model', juju_utils.get_model_status_version(status)) |
36 | + |
37 | + # count machines, incl. agent status & version |
38 | + machines = status.get('machines', []) |
39 | + self._increment_data('machines', len(machines)) |
40 | + for m in machines: |
41 | + self._increment_status_sections('machines', machines[m]) |
42 | + |
43 | + # count applications & units |
44 | + apps = status.get('applications', []) |
45 | + self._increment_data('applications', len(apps)) |
46 | + for a in apps: |
47 | + # primary units |
48 | + units = apps[a].get('units', []) |
49 | + self._increment_data('units', len(units)) |
50 | + for u in units: |
51 | + self._increment_status_sections('units', units[u]) |
52 | + |
53 | + # subordinate units |
54 | + subordinates = units[u].get('subordinates', []) |
55 | + self._increment_data('units', len(subordinates)) |
56 | + for s in subordinates: |
57 | + self._increment_status_sections('units', subordinates[s]) |
58 | + |
59 | + def _increment_status_sections(self, section, obj): |
60 | + juju_status = obj.get('juju-status', {}) |
61 | + self._increment_summary('life', section, juju_status.get('life', None)) |
62 | + self._increment_summary('status', section, juju_status.get('current', None)) |
63 | + self._increment_summary('version', section, juju_status.get('version', None)) |
64 | + |
65 | + def _increment_data(self, key, i=1): |
66 | + self._increment_dict(self.totals, key, i) |
67 | + |
68 | + def _increment_summary(self, section, key, val, i=1): |
69 | + if val is None: |
70 | + return |
71 | + self.summary.setdefault(section, {}) |
72 | + self.summary[section].setdefault(key, {}) |
73 | + self._increment_dict(self.summary[section][key], val, i) |
74 | + |
75 | + def _increment_dict(self, obj, key, val): |
76 | + if key not in obj: |
77 | + obj[key] = val |
78 | + else: |
79 | + obj[key] += val |
80 | + |
81 | + def report(self): |
82 | + output = [''] |
83 | + output.append(yaml.dump(self.totals, default_flow_style=False)) |
84 | + output.append(yaml.dump(self.summary, default_flow_style=False)) |
85 | + return "\n".join(output) |
86 | diff --git a/unit_tests/test_juju_statistics.py b/unit_tests/test_juju_statistics.py |
87 | new file mode 100644 |
88 | index 0000000..fa16ffd |
89 | --- /dev/null |
90 | +++ b/unit_tests/test_juju_statistics.py |
91 | @@ -0,0 +1,454 @@ |
92 | +#!/usr/bin/python3 |
93 | + |
94 | +# Copyright (c) 2018 Canonical Ltd |
95 | +# License: GPLv3 |
96 | +# Author: Tom Haddon <tom.haddon@canonical.com> |
97 | +# |
98 | +# Unit tests for juju_statistics.py. |
99 | +# |
100 | + |
101 | +import textwrap |
102 | +import unittest |
103 | + |
104 | +import juju_statistics |
105 | + |
106 | + |
107 | +class TestJujuStatistics(unittest.TestCase): |
108 | + |
109 | + def setUp(self): |
110 | + self.stats = juju_statistics.Statistics() |
111 | + |
112 | + def test___init__(self): |
113 | + self.assertEqual(self.stats.totals, {}) |
114 | + self.assertEqual(self.stats.summary, {}) |
115 | + |
116 | + def test_has_stats(self): |
117 | + # No stats for a newly created Statistics object. |
118 | + self.assertEqual(len(self.stats.totals), 0) |
119 | + self.assertEqual(len(self.stats.summary), 0) |
120 | + self.assertFalse(self.stats.has_stats()) |
121 | + # Now confirm if we populate either data or summary that we have |
122 | + # stats. |
123 | + self.stats.totals = {'key': 'value'} |
124 | + self.assertTrue(self.stats.has_stats()) |
125 | + self.stats.totals = {} |
126 | + self.assertFalse(self.stats.has_stats()) |
127 | + self.stats.summary = {'key': 'value'} |
128 | + self.assertTrue(self.stats.has_stats()) |
129 | + self.stats.summary = {} |
130 | + self.assertFalse(self.stats.has_stats()) |
131 | + |
132 | + def test_add_model(self): |
133 | + test_model_obj1 = { |
134 | + 'life': 'alive', |
135 | + 'status': {'current': 'available'}, |
136 | + } |
137 | + self.stats.add_model(test_model_obj1) |
138 | + self.assertEqual(self.stats.totals, {'models': 1}) |
139 | + expected_summary = { |
140 | + 'life': { |
141 | + 'model': { |
142 | + 'alive': 1, |
143 | + } |
144 | + }, |
145 | + 'status': { |
146 | + 'model': { |
147 | + 'available': 1, |
148 | + } |
149 | + } |
150 | + } |
151 | + self.assertEqual(self.stats.summary, expected_summary) |
152 | + test_model_obj2 = { |
153 | + 'life': 'alive', |
154 | + 'status': {'current': 'idle'}, |
155 | + } |
156 | + self.stats.add_model(test_model_obj2) |
157 | + self.assertEqual(self.stats.totals, {'models': 2}) |
158 | + expected_summary = { |
159 | + 'life': { |
160 | + 'model': { |
161 | + 'alive': 2, |
162 | + } |
163 | + }, |
164 | + 'status': { |
165 | + 'model': { |
166 | + 'available': 1, |
167 | + 'idle': 1, |
168 | + } |
169 | + } |
170 | + } |
171 | + self.assertEqual(self.stats.summary, expected_summary) |
172 | + |
173 | + def test_add_status(self): |
174 | + self.stats.add_status({}) |
175 | + self.assertEqual(self.stats.totals, {'applications': 0, 'machines': 0}) |
176 | + self.assertEqual(self.stats.summary, {}) |
177 | + test_status_obj = { |
178 | + "model": { |
179 | + "name": "mojo-dot-canonical-dot-com", |
180 | + "controller": "prodstack", |
181 | + "cloud": "prodstack", |
182 | + "region": "prodstack", |
183 | + "version": "2.2.8", |
184 | + }, |
185 | + "machines": { |
186 | + "0": { |
187 | + "juju-status": { |
188 | + "current": "started", |
189 | + "since": "02 Jan 2018 20:23:12Z", |
190 | + "version": "2.2.8", |
191 | + }, |
192 | + "dns-name": "162.213.33.51", |
193 | + "ip-addresses": ["162.213.33.51", "10.0.0.10"], |
194 | + "instance-id": "68da26e2-9593-4ca7-9718-cda2dff6ceb1", |
195 | + "machine-status": { |
196 | + "current": "running", |
197 | + "message": "ACTIVE", |
198 | + "since": "30 Sep 2016 17:04:30Z", |
199 | + }, |
200 | + "series": "xenial", |
201 | + "hardware": "arch=amd64 cores=1 mem=2048M root-disk=10240M availability-zone=nova", |
202 | + } |
203 | + }, |
204 | + "applications": { |
205 | + "apache2": { |
206 | + "charm": "local:xenial/apache2-0", |
207 | + "series": "xenial", |
208 | + "os": "ubuntu", |
209 | + "charm-origin": "local", |
210 | + "charm-name": "apache2", |
211 | + "charm-rev": 0, |
212 | + "exposed": True, |
213 | + "application-status": { |
214 | + "current": "unknown", |
215 | + "since": "30 Sep 2016 17:07:54Z", |
216 | + }, |
217 | + "relations": { |
218 | + "juju-info": ["content-fetcher", "livepatch", "landscape"], |
219 | + "nrpe-external-master": ["nrpe"], |
220 | + }, |
221 | + "units": { |
222 | + "apache2/0": { |
223 | + "workload-status": { |
224 | + "current": "unknown", |
225 | + "since": "30 Sep 2016 17:07:54Z", |
226 | + }, |
227 | + "juju-status": { |
228 | + "current": "idle", |
229 | + "since": "04 Jan 2018 09:10:28Z", |
230 | + "version": "2.2.8", |
231 | + }, |
232 | + "leader": True, |
233 | + "machine": "0", |
234 | + "open-ports": ["80/tcp", "443/tcp"], |
235 | + "public-address": "162.213.33.51", |
236 | + "subordinates": { |
237 | + "content-fetcher/0": { |
238 | + "workload-status": { |
239 | + "current": "unknown", |
240 | + "since": "30 Sep 2016 17:28:40Z", |
241 | + }, |
242 | + "juju-status": { |
243 | + "current": "idle", |
244 | + "since": "04 Jan 2018 09:15:47Z", |
245 | + "version": "2.2.8", |
246 | + }, |
247 | + "leader": True, |
248 | + "upgrading-from": "local:xenial/content-fetcher-0", |
249 | + "public-address": "162.213.33.51", |
250 | + }, |
251 | + "livepatch/0": { |
252 | + "workload-status": { |
253 | + "current": "active", |
254 | + "message": "Effective kernel 4.4.0-103-generic", |
255 | + "since": "04 Jan 2018 09:14:48Z", |
256 | + }, |
257 | + "juju-status": { |
258 | + "current": "idle", |
259 | + "since": "04 Jan 2018 09:14:48Z", |
260 | + "version": "2.2.8", |
261 | + }, |
262 | + "leader": True, |
263 | + "upgrading-from": "local:xenial/livepatch-5", |
264 | + "public-address": "162.213.33.51", |
265 | + }, |
266 | + "landscape/1": { |
267 | + "workload-status": { |
268 | + "current": "active", |
269 | + "message": "System successfully registered", |
270 | + "since": "02 Jan 2018 20:24:58Z", |
271 | + }, |
272 | + "juju-status": { |
273 | + "current": "idle", |
274 | + "since": "04 Jan 2018 09:10:41Z", |
275 | + "version": "2.2.8", |
276 | + }, |
277 | + "leader": True, |
278 | + "upgrading-from": "local:xenial/landscape-client-23", |
279 | + "public-address": "162.213.33.51", |
280 | + }, |
281 | + "nrpe/0": { |
282 | + "workload-status": { |
283 | + "current": "unknown", |
284 | + "since": "30 Sep 2016 17:29:04Z", |
285 | + }, |
286 | + "juju-status": { |
287 | + "current": "idle", |
288 | + "since": "04 Jan 2018 09:16:01Z", |
289 | + "version": "2.2.8", |
290 | + }, |
291 | + "leader": True, |
292 | + "upgrading-from": "local:xenial/nrpe-0", |
293 | + "public-address": "162.213.33.51", |
294 | + } |
295 | + } |
296 | + } |
297 | + } |
298 | + }, |
299 | + "content-fetcher": { |
300 | + "charm": "local:xenial/content-fetcher-0", |
301 | + "series": "xenial", |
302 | + "os": "ubuntu", |
303 | + "charm-origin": "local", |
304 | + "charm-name": "content-fetcher", |
305 | + "charm-rev": 0, |
306 | + "exposed": False, |
307 | + "application-status": { |
308 | + "current": "unknown", |
309 | + "since": "30 Sep 2016 17:28:40Z", |
310 | + }, |
311 | + "relations": { |
312 | + "general-info": ["apache2"], |
313 | + }, |
314 | + "subordinate-to": ["apache2"], |
315 | + }, |
316 | + "livepatch": { |
317 | + "charm": "local:xenial/livepatch-5", |
318 | + "series": "xenial", |
319 | + "os": "ubuntu", |
320 | + "charm-origin": "local", |
321 | + "charm-name": "livepatch", |
322 | + "charm-rev": 5, |
323 | + "exposed": False, |
324 | + "application-status": { |
325 | + "current": "active", |
326 | + "message": "Effective kernel 4.4.0-103-generic", |
327 | + "since": "04 Jan 2018 09:14:48Z", |
328 | + }, |
329 | + "relations": { |
330 | + "general-info": ["apache2"], |
331 | + }, |
332 | + "subordinate-to": ["apache2"], |
333 | + "version": "1.2.31", |
334 | + }, |
335 | + "landscape": { |
336 | + "charm": "local:xenial/landscape-client-23", |
337 | + "series": "xenial", |
338 | + "os": "ubuntu", |
339 | + "charm-origin": "local", |
340 | + "charm-name": "landscape-client", |
341 | + "charm-rev": 23, |
342 | + "exposed": False, |
343 | + "application-status": { |
344 | + "current": "active", |
345 | + "message": "System successfully registered", |
346 | + "since": "02 Jan 2018 20:24:58Z", |
347 | + }, |
348 | + "relations": { |
349 | + "container": ["apache2"], |
350 | + }, |
351 | + "subordinate-to": ["apache2"], |
352 | + }, |
353 | + "nrpe": { |
354 | + "charm": "local:xenial/nrpe-0", |
355 | + "series": "xenial", |
356 | + "os": "ubuntu", |
357 | + "charm-origin": "local", |
358 | + "charm-name": "nrpe", |
359 | + "charm-rev": 0, |
360 | + "exposed": False, |
361 | + "application-status": { |
362 | + "current": "unknown", |
363 | + "since": "30 Sep 2016 17:29:04Z", |
364 | + }, |
365 | + "relations": { |
366 | + "nrpe-external-master": ["apache2"], |
367 | + }, |
368 | + "subordinate-to": ["apache2"], |
369 | + }, |
370 | + "ubuntu": { |
371 | + "charm": "cs:ubuntu-10", |
372 | + "series": "xenial", |
373 | + "os": "ubuntu", |
374 | + "charm-origin": "jujucharms", |
375 | + "charm-name": "ubuntu", |
376 | + "charm-rev": 10, |
377 | + "exposed": False, |
378 | + "application-status": { |
379 | + "current": "waiting", |
380 | + "message": "waiting for machine", |
381 | + "since": "24 Apr 2017 17:41:29Z", |
382 | + } |
383 | + } |
384 | + } |
385 | + } |
386 | + self.stats.add_status(test_status_obj) |
387 | + self.assertEqual(self.stats.totals, {'units': 5, 'applications': 6, 'machines': 1}) |
388 | + expected_summary = { |
389 | + 'status': { |
390 | + 'units': { |
391 | + 'idle': 5, |
392 | + }, |
393 | + 'machines': { |
394 | + 'started': 1, |
395 | + } |
396 | + }, |
397 | + 'version': { |
398 | + 'model': { |
399 | + '2.2.8': 1, |
400 | + }, |
401 | + 'units': { |
402 | + '2.2.8': 5, |
403 | + }, |
404 | + 'machines': { |
405 | + '2.2.8': 1, |
406 | + } |
407 | + } |
408 | + } |
409 | + self.assertEqual(self.stats.summary, expected_summary) |
410 | + |
411 | + def test__increment_dict(self): |
412 | + test_dict = {} |
413 | + self.stats._increment_dict(test_dict, 'models', 1) |
414 | + self.assertEqual(test_dict, {'models': 1}) |
415 | + self.stats._increment_dict(test_dict, 'models', 10) |
416 | + self.assertEqual(test_dict, {'models': 11}) |
417 | + |
418 | + def test__increment_data(self): |
419 | + self.stats._increment_data('models') |
420 | + self.assertEqual(self.stats.totals, {'models': 1}) |
421 | + self.stats._increment_data('models') |
422 | + self.assertEqual(self.stats.totals, {'models': 2}) |
423 | + self.stats._increment_data('units', 10) |
424 | + self.assertEqual(self.stats.totals, {'models': 2, 'units': 10}) |
425 | + self.stats._increment_data('units', 2) |
426 | + self.assertEqual(self.stats.totals, {'models': 2, 'units': 12}) |
427 | + |
428 | + def test__increment_summary(self): |
429 | + self.stats._increment_summary('life', 'machines', 'alive') |
430 | + self.assertEqual(self.stats.summary, {'life': {'machines': {'alive': 1}}}) |
431 | + self.stats._increment_summary('life', 'machines', 'alive') |
432 | + self.assertEqual(self.stats.summary, {'life': {'machines': {'alive': 2}}}) |
433 | + self.stats._increment_summary('life', 'machines', 'alive', 10) |
434 | + self.assertEqual(self.stats.summary, {'life': {'machines': {'alive': 12}}}) |
435 | + self.stats._increment_summary('life', 'machines', None) |
436 | + self.assertEqual(self.stats.summary, {'life': {'machines': {'alive': 12}}}) |
437 | + self.stats._increment_summary('life', 'machines', 'dead') |
438 | + self.assertEqual(self.stats.summary, {'life': {'machines': {'alive': 12, 'dead': 1}}}) |
439 | + |
440 | + def test__increment_status_sections(self): |
441 | + test_machine_status_obj1 = { |
442 | + 'juju-status': { |
443 | + 'life': 'alive', |
444 | + 'version': '2.2.8', |
445 | + } |
446 | + } |
447 | + self.stats._increment_status_sections('machines', test_machine_status_obj1) |
448 | + expected_summary = { |
449 | + 'life': { |
450 | + 'machines': { |
451 | + 'alive': 1, |
452 | + } |
453 | + }, |
454 | + 'version': { |
455 | + 'machines': { |
456 | + '2.2.8': 1, |
457 | + } |
458 | + } |
459 | + } |
460 | + self.assertEqual(self.stats.summary, expected_summary) |
461 | + test_machine_status_obj2 = { |
462 | + 'juju-status': { |
463 | + 'life': 'dead', |
464 | + 'version': '2.2.8', |
465 | + } |
466 | + } |
467 | + self.stats._increment_status_sections('machines', test_machine_status_obj2) |
468 | + expected_summary = { |
469 | + 'life': { |
470 | + 'machines': { |
471 | + 'alive': 1, |
472 | + 'dead': 1, |
473 | + } |
474 | + }, |
475 | + 'version': { |
476 | + 'machines': { |
477 | + '2.2.8': 2, |
478 | + } |
479 | + } |
480 | + } |
481 | + self.assertEqual(self.stats.summary, expected_summary) |
482 | + |
483 | + def test_report(self): |
484 | + self.stats.totals = { |
485 | + 'applications': 940, |
486 | + 'machines': 559, |
487 | + 'models': 117, |
488 | + 'units': 2494, |
489 | + } |
490 | + self.stats.summary = { |
491 | + 'life': { |
492 | + 'model': { |
493 | + 'alive': 177, |
494 | + } |
495 | + }, |
496 | + 'status': { |
497 | + 'machines': { |
498 | + 'started': 559, |
499 | + }, |
500 | + 'model': { |
501 | + 'available': 177, |
502 | + }, |
503 | + 'units': { |
504 | + 'executing': 13, |
505 | + 'idle': 2481, |
506 | + }, |
507 | + 'version': { |
508 | + 'machines': { |
509 | + '2.2.8': 559, |
510 | + }, |
511 | + 'model': { |
512 | + '2.2.8': 177, |
513 | + }, |
514 | + 'units': { |
515 | + '2.2.8': 2494, |
516 | + } |
517 | + } |
518 | + } |
519 | + } |
520 | + expected_report = textwrap.dedent(""" |
521 | + applications: 940 |
522 | + machines: 559 |
523 | + models: 117 |
524 | + units: 2494 |
525 | + |
526 | + life: |
527 | + model: |
528 | + alive: 177 |
529 | + status: |
530 | + machines: |
531 | + started: 559 |
532 | + model: |
533 | + available: 177 |
534 | + units: |
535 | + executing: 13 |
536 | + idle: 2481 |
537 | + version: |
538 | + machines: |
539 | + 2.2.8: 559 |
540 | + model: |
541 | + 2.2.8: 177 |
542 | + units: |
543 | + 2.2.8: 2494 |
544 | + """) |
545 | + self.assertEqual(self.stats.report(), expected_report) |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.