Merge ~mthaddon/juju-upgrader/+git/juju-upgrader:juju-status into juju-upgrader:master

Proposed by Tom Haddon
Status: Merged
Approved by: Joel Sing
Approved revision: 8f977f385d1c13622ae9df1b3cc38696809b4652
Merged at revision: bbd6639f059e1a189eaf436dc6fa2102828b2e28
Proposed branch: ~mthaddon/juju-upgrader/+git/juju-upgrader:juju-status
Merge into: juju-upgrader:master
Diff against target: 896 lines (+651/-209)
4 files modified
juju_status.py (+142/-0)
unit_tests/test_juju_statistics.py (+3/-209)
unit_tests/test_juju_status.py (+288/-0)
unit_tests/testdata/test_statuses.py (+218/-0)
Reviewer Review Type Date Requested Status
Joel Sing (community) +1 Approve
Tom Haddon Needs Resubmitting
Review via email: mp+335714@code.launchpad.net

Commit message

Add juju status and unit tests

Description of the change

Add juju_status and unit tests

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Tom Haddon (mthaddon) wrote :

This doesn't include unit tests for check_model function, which largely just calls other functions tested elsewhere.

Revision history for this message
Joel Sing (jsing) wrote :

LGTM, but see comments.

review: Approve (+1)
Revision history for this message
Tom Haddon (mthaddon) wrote :

One question inline about where to put the code (I agree directly in the home dir isn't the best).

Will push up other changes shortly.

Revision history for this message
Joel Sing (jsing) :
Revision history for this message
Tom Haddon (mthaddon) :
Revision history for this message
Tom Haddon (mthaddon) wrote :

Have updated based on discussions about where we should copy code to on machines. PTAL.

review: Needs Resubmitting
Revision history for this message
Joel Sing (jsing) wrote :

Just a couple of minor things.

review: Approve (+1)
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Merge proposal is approved, but source repository revision has changed, setting status to needs review.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision bbd6639f059e1a189eaf436dc6fa2102828b2e28

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/juju_status.py b/juju_status.py
2new file mode 100644
3index 0000000..d096ddd
4--- /dev/null
5+++ b/juju_status.py
6@@ -0,0 +1,142 @@
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 glob
13+import json
14+import os.path
15+import subprocess
16+import sys
17+
18+import juju_utils
19+
20+
21+def _get_model_details(status):
22+ m = status.get('model', {})
23+ return {
24+ "model": m.get('name', None),
25+ "model-version": m.get('version', None),
26+ }
27+
28+
29+def _get_juju_agent_status(obj):
30+ if 'juju-status' in obj:
31+ obj['juju-status']['agent-time'] = juju_utils.parse_time('since', obj['juju-status'])
32+ return obj['juju-status']
33+ else:
34+ return {}
35+
36+
37+def _get_unit(name, obj, status=None, machine=None, machine_version=None):
38+ unit = {
39+ 'unit': name,
40+ }
41+ unit.update(_get_juju_agent_status(obj))
42+ if status is not None:
43+ unit.update(_get_model_details(status))
44+ if machine is not None and machine_version is not None:
45+ unit['machine'] = machine
46+ unit['machine-version'] = machine_version
47+ return unit
48+
49+
50+def _get_machines(status):
51+ if 'machines' in status:
52+ for m in status['machines']:
53+ yield _get_unit(m, status['machines'][m], status)
54+
55+
56+def _get_units(status, machine):
57+ if 'applications' in status:
58+ apps = status['applications']
59+ for a in apps:
60+ if 'units' in apps[a]:
61+ units = apps[a]['units']
62+ for u in units:
63+ m = units[u].get('machine', None)
64+ if m == machine['unit']:
65+ machine_version = machine['version']
66+ yield _get_unit(u, units[u], status, m, machine_version)
67+ if 'subordinates' in units[u]:
68+ for s in units[u]['subordinates']:
69+ yield _get_unit(s, units[u]['subordinates'][s], status, m, machine_version)
70+
71+
72+def _get_model_machines(status):
73+ """Construct a list of machines in the model, where each machine is composed of an array of units.
74+ The first unit in the array is the machine itself, the remainder are the application units."""
75+ machines = []
76+ for m in _get_machines(status):
77+ machines.append([m] + list(_get_units(status, m)))
78+ return machines
79+
80+
81+def _run(command, obj=None):
82+ juju_utils.debug('Running: ' + ' '.join(command))
83+ if obj is not None:
84+ stdin = json.dumps(obj).encode()
85+ else:
86+ stdin = None
87+
88+ proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
89+ try:
90+ output, errors = proc.communicate(input=stdin, timeout=60)
91+ except subprocess.TimeoutExpired:
92+ proc.kill()
93+ output, errors = proc.communicate()
94+ if len(output):
95+ return json.loads(output.decode())
96+ else:
97+ return None
98+
99+
100+def _script_paths(location):
101+ loc = os.path.dirname(location)
102+ for path in glob.glob(os.path.join(loc, 'juju-*')):
103+ yield path
104+ for path in glob.glob(os.path.join(loc, 'juju_*.py')):
105+ yield path
106+
107+
108+def check_model(status, formatter, fix=False, restart=False, ssh=True, show_all=False):
109+ """Check/fix agents on all machines in the model according to the parameters.
110+ Return the number of unhealthy units.
111+ """
112+ allmachines = _get_model_machines(status)
113+ count = 0
114+
115+ for units in sorted(allmachines, key=lambda x: int(x[0]['unit'])):
116+ machinenum = units[0]['unit']
117+ units[0]['fix'] = fix
118+ units[0]['restart'] = restart
119+
120+ # pre-fill known unit fields
121+ for u in units:
122+ u['statustime'] = juju_utils.format_time('agent-time', u)
123+ u['cause'] = 'NOT CHECKED'
124+
125+ # copy scripts to machine, ssh to machine, execute script with machine data as input
126+ if ssh and not juju_utils.is_good_machine(units, status):
127+ _run(['juju', 'ssh', machinenum, 'mkdir', '-p', '~/juju-upgrader'])
128+ # Clean up before we copy files for the upgrade.
129+ _run(['juju', 'ssh', machinenum, 'rm', '-f', '~/juju-upgrader/*'])
130+ _run(['juju', 'scp'] + list(_script_paths(sys.argv[0])) + [machinenum + ':~/juju-upgrader/'])
131+ result = _run(['juju', 'ssh', '--pty=false', machinenum, 'sudo ~/juju-upgrader/juju-machine-check'], units)
132+ if result is None:
133+ # some error occurred while running the script - there should be output from run() already
134+ break
135+ else:
136+ juju_utils.debug('Skipped ssh to machine ' + machinenum)
137+ result = units
138+
139+ for u in result:
140+ u['logtime'] = juju_utils.format_time('logfile-time', u)
141+ u['proctime'] = juju_utils.format_time('start-time', u)
142+ good = juju_utils.is_good_unit(u, status)
143+ if show_all or not good:
144+ formatter.emit_record(u)
145+ if not good:
146+ count += 1
147+
148+ return count
149diff --git a/unit_tests/test_juju_statistics.py b/unit_tests/test_juju_statistics.py
150index fa16ffd..ccaa96f 100644
151--- a/unit_tests/test_juju_statistics.py
152+++ b/unit_tests/test_juju_statistics.py
153@@ -12,6 +12,8 @@ import unittest
154
155 import juju_statistics
156
157+from unit_tests.testdata import test_statuses
158+
159
160 class TestJujuStatistics(unittest.TestCase):
161
162@@ -83,215 +85,7 @@ class TestJujuStatistics(unittest.TestCase):
163 self.stats.add_status({})
164 self.assertEqual(self.stats.totals, {'applications': 0, 'machines': 0})
165 self.assertEqual(self.stats.summary, {})
166- test_status_obj = {
167- "model": {
168- "name": "mojo-dot-canonical-dot-com",
169- "controller": "prodstack",
170- "cloud": "prodstack",
171- "region": "prodstack",
172- "version": "2.2.8",
173- },
174- "machines": {
175- "0": {
176- "juju-status": {
177- "current": "started",
178- "since": "02 Jan 2018 20:23:12Z",
179- "version": "2.2.8",
180- },
181- "dns-name": "162.213.33.51",
182- "ip-addresses": ["162.213.33.51", "10.0.0.10"],
183- "instance-id": "68da26e2-9593-4ca7-9718-cda2dff6ceb1",
184- "machine-status": {
185- "current": "running",
186- "message": "ACTIVE",
187- "since": "30 Sep 2016 17:04:30Z",
188- },
189- "series": "xenial",
190- "hardware": "arch=amd64 cores=1 mem=2048M root-disk=10240M availability-zone=nova",
191- }
192- },
193- "applications": {
194- "apache2": {
195- "charm": "local:xenial/apache2-0",
196- "series": "xenial",
197- "os": "ubuntu",
198- "charm-origin": "local",
199- "charm-name": "apache2",
200- "charm-rev": 0,
201- "exposed": True,
202- "application-status": {
203- "current": "unknown",
204- "since": "30 Sep 2016 17:07:54Z",
205- },
206- "relations": {
207- "juju-info": ["content-fetcher", "livepatch", "landscape"],
208- "nrpe-external-master": ["nrpe"],
209- },
210- "units": {
211- "apache2/0": {
212- "workload-status": {
213- "current": "unknown",
214- "since": "30 Sep 2016 17:07:54Z",
215- },
216- "juju-status": {
217- "current": "idle",
218- "since": "04 Jan 2018 09:10:28Z",
219- "version": "2.2.8",
220- },
221- "leader": True,
222- "machine": "0",
223- "open-ports": ["80/tcp", "443/tcp"],
224- "public-address": "162.213.33.51",
225- "subordinates": {
226- "content-fetcher/0": {
227- "workload-status": {
228- "current": "unknown",
229- "since": "30 Sep 2016 17:28:40Z",
230- },
231- "juju-status": {
232- "current": "idle",
233- "since": "04 Jan 2018 09:15:47Z",
234- "version": "2.2.8",
235- },
236- "leader": True,
237- "upgrading-from": "local:xenial/content-fetcher-0",
238- "public-address": "162.213.33.51",
239- },
240- "livepatch/0": {
241- "workload-status": {
242- "current": "active",
243- "message": "Effective kernel 4.4.0-103-generic",
244- "since": "04 Jan 2018 09:14:48Z",
245- },
246- "juju-status": {
247- "current": "idle",
248- "since": "04 Jan 2018 09:14:48Z",
249- "version": "2.2.8",
250- },
251- "leader": True,
252- "upgrading-from": "local:xenial/livepatch-5",
253- "public-address": "162.213.33.51",
254- },
255- "landscape/1": {
256- "workload-status": {
257- "current": "active",
258- "message": "System successfully registered",
259- "since": "02 Jan 2018 20:24:58Z",
260- },
261- "juju-status": {
262- "current": "idle",
263- "since": "04 Jan 2018 09:10:41Z",
264- "version": "2.2.8",
265- },
266- "leader": True,
267- "upgrading-from": "local:xenial/landscape-client-23",
268- "public-address": "162.213.33.51",
269- },
270- "nrpe/0": {
271- "workload-status": {
272- "current": "unknown",
273- "since": "30 Sep 2016 17:29:04Z",
274- },
275- "juju-status": {
276- "current": "idle",
277- "since": "04 Jan 2018 09:16:01Z",
278- "version": "2.2.8",
279- },
280- "leader": True,
281- "upgrading-from": "local:xenial/nrpe-0",
282- "public-address": "162.213.33.51",
283- }
284- }
285- }
286- }
287- },
288- "content-fetcher": {
289- "charm": "local:xenial/content-fetcher-0",
290- "series": "xenial",
291- "os": "ubuntu",
292- "charm-origin": "local",
293- "charm-name": "content-fetcher",
294- "charm-rev": 0,
295- "exposed": False,
296- "application-status": {
297- "current": "unknown",
298- "since": "30 Sep 2016 17:28:40Z",
299- },
300- "relations": {
301- "general-info": ["apache2"],
302- },
303- "subordinate-to": ["apache2"],
304- },
305- "livepatch": {
306- "charm": "local:xenial/livepatch-5",
307- "series": "xenial",
308- "os": "ubuntu",
309- "charm-origin": "local",
310- "charm-name": "livepatch",
311- "charm-rev": 5,
312- "exposed": False,
313- "application-status": {
314- "current": "active",
315- "message": "Effective kernel 4.4.0-103-generic",
316- "since": "04 Jan 2018 09:14:48Z",
317- },
318- "relations": {
319- "general-info": ["apache2"],
320- },
321- "subordinate-to": ["apache2"],
322- "version": "1.2.31",
323- },
324- "landscape": {
325- "charm": "local:xenial/landscape-client-23",
326- "series": "xenial",
327- "os": "ubuntu",
328- "charm-origin": "local",
329- "charm-name": "landscape-client",
330- "charm-rev": 23,
331- "exposed": False,
332- "application-status": {
333- "current": "active",
334- "message": "System successfully registered",
335- "since": "02 Jan 2018 20:24:58Z",
336- },
337- "relations": {
338- "container": ["apache2"],
339- },
340- "subordinate-to": ["apache2"],
341- },
342- "nrpe": {
343- "charm": "local:xenial/nrpe-0",
344- "series": "xenial",
345- "os": "ubuntu",
346- "charm-origin": "local",
347- "charm-name": "nrpe",
348- "charm-rev": 0,
349- "exposed": False,
350- "application-status": {
351- "current": "unknown",
352- "since": "30 Sep 2016 17:29:04Z",
353- },
354- "relations": {
355- "nrpe-external-master": ["apache2"],
356- },
357- "subordinate-to": ["apache2"],
358- },
359- "ubuntu": {
360- "charm": "cs:ubuntu-10",
361- "series": "xenial",
362- "os": "ubuntu",
363- "charm-origin": "jujucharms",
364- "charm-name": "ubuntu",
365- "charm-rev": 10,
366- "exposed": False,
367- "application-status": {
368- "current": "waiting",
369- "message": "waiting for machine",
370- "since": "24 Apr 2017 17:41:29Z",
371- }
372- }
373- }
374- }
375+ test_status_obj = test_statuses.MOJO_DOT_CANONICAL_DOT_COM_STATUS
376 self.stats.add_status(test_status_obj)
377 self.assertEqual(self.stats.totals, {'units': 5, 'applications': 6, 'machines': 1})
378 expected_summary = {
379diff --git a/unit_tests/test_juju_status.py b/unit_tests/test_juju_status.py
380new file mode 100644
381index 0000000..44bb686
382--- /dev/null
383+++ b/unit_tests/test_juju_status.py
384@@ -0,0 +1,288 @@
385+#!/usr/bin/python3
386+
387+# Copyright (c) 2018 Canonical Ltd
388+# License: GPLv3
389+# Author: Tom Haddon <tom.haddon@canonical.com>
390+#
391+# Unit tests for juju_status.py.
392+#
393+
394+import mock
395+import unittest
396+import sys
397+
398+import juju_status
399+
400+from unit_tests.testdata import test_statuses
401+
402+
403+class TestJujuStatus(unittest.TestCase):
404+
405+ def setUp(self):
406+ self.status = test_statuses.MOJO_DOT_CANONICAL_DOT_COM_STATUS
407+
408+ def test__get_model_details(self):
409+ expected_model_details = {'model': 'mojo-dot-canonical-dot-com', 'model-version': '2.2.8'}
410+ self.assertEqual(juju_status._get_model_details(self.status), expected_model_details)
411+
412+ def test__get_machines(self):
413+ expected_machines = {
414+ 'unit': '0',
415+ 'version': '2.2.8',
416+ 'since': '02 Jan 2018 20:23:12Z',
417+ 'model': 'mojo-dot-canonical-dot-com',
418+ 'model-version': '2.2.8',
419+ 'agent-time': 1514920992.0,
420+ 'current': 'started',
421+ }
422+ # Since this is a generator, list-ify it first so we can see results.
423+ self.assertEqual(list(juju_status._get_machines(self.status)), [expected_machines])
424+
425+ def test__get_juju_agent_status(self):
426+ expected_juju_agent_status = {
427+ 'agent-time': 1515053428.0,
428+ 'current': 'idle',
429+ 'since': '04 Jan 2018 09:10:28Z',
430+ 'version': '2.2.8',
431+ }
432+ self.assertEqual(juju_status._get_juju_agent_status(
433+ self.status['applications']['apache2']['units']['apache2/0']), expected_juju_agent_status)
434+ # Confirm if we pass an object that doesn't contain 'juju-status' we
435+ # get an empty dictionary.
436+ self.assertEqual(juju_status._get_juju_agent_status({'huhu-status': 'OK'}), {})
437+
438+ def test__get_unit(self):
439+ # Passing all arguments.
440+ actual_unit = juju_status._get_unit(
441+ 'apache2/0',
442+ self.status['applications']['apache2']['units']['apache2/0'],
443+ self.status,
444+ '0',
445+ '2.2.8',
446+ )
447+ expected_unit = {
448+ 'agent-time': 1515053428.0,
449+ 'current': 'idle',
450+ 'machine': '0',
451+ 'machine-version': '2.2.8',
452+ 'model': 'mojo-dot-canonical-dot-com',
453+ 'model-version': '2.2.8',
454+ 'since': '04 Jan 2018 09:10:28Z',
455+ 'unit': 'apache2/0',
456+ 'version': '2.2.8',
457+ }
458+ self.assertEqual(actual_unit, expected_unit)
459+ # Not passing machine or machine version.
460+ actual_unit = juju_status._get_unit(
461+ 'apache2/0',
462+ self.status['applications']['apache2']['units']['apache2/0'],
463+ self.status)
464+ expected_unit = {
465+ 'agent-time': 1515053428.0,
466+ 'current': 'idle',
467+ 'model': 'mojo-dot-canonical-dot-com',
468+ 'model-version': '2.2.8',
469+ 'since': '04 Jan 2018 09:10:28Z',
470+ 'unit': 'apache2/0',
471+ 'version': '2.2.8',
472+ }
473+ self.assertEqual(actual_unit, expected_unit)
474+ # Not passing machine (should be treated as same as not passing
475+ # machine or machine version).
476+ actual_unit = juju_status._get_unit(
477+ 'apache2/0',
478+ self.status['applications']['apache2']['units']['apache2/0'],
479+ self.status,
480+ machine_version='2.2.8',
481+ )
482+ self.assertEqual(actual_unit, expected_unit)
483+ # Not passing machine version (should be treated as same as not
484+ # passing machine or machine version).
485+ actual_unit = juju_status._get_unit(
486+ 'apache2/0',
487+ self.status['applications']['apache2']['units']['apache2/0'],
488+ self.status,
489+ '0',
490+ )
491+ self.assertEqual(actual_unit, expected_unit)
492+ # Not passing status, machine or machine version.
493+ actual_unit = juju_status._get_unit(
494+ 'apache2/0',
495+ self.status['applications']['apache2']['units']['apache2/0'])
496+ expected_unit = {
497+ 'agent-time': 1515053428.0,
498+ 'current': 'idle',
499+ 'since': '04 Jan 2018 09:10:28Z',
500+ 'unit': 'apache2/0',
501+ 'version': '2.2.8',
502+ }
503+ self.assertEqual(actual_unit, expected_unit)
504+
505+ def test__get_units(self):
506+ expected_units = [
507+ {
508+ 'machine-version': '2.2.8',
509+ 'machine': '0',
510+ 'model': 'mojo-dot-canonical-dot-com',
511+ 'current': 'idle',
512+ 'model-version': '2.2.8',
513+ 'since': '04 Jan 2018 09:10:41Z',
514+ 'version': '2.2.8',
515+ 'unit': 'landscape/1',
516+ 'agent-time': 1515053441.0,
517+ },
518+ {
519+ 'model-version': '2.2.8',
520+ 'unit': 'content-fetcher/0',
521+ 'model': 'mojo-dot-canonical-dot-com',
522+ 'machine-version': '2.2.8',
523+ 'version': '2.2.8',
524+ 'agent-time': 1515053747.0,
525+ 'machine': '0',
526+ 'since': '04 Jan 2018 09:15:47Z',
527+ 'current': 'idle',
528+ },
529+ {
530+ 'machine': '0',
531+ 'current': 'idle',
532+ 'unit': 'nrpe/0',
533+ 'since': '04 Jan 2018 09:16:01Z',
534+ 'agent-time': 1515053761.0,
535+ 'version': '2.2.8',
536+ 'machine-version': '2.2.8',
537+ 'model': 'mojo-dot-canonical-dot-com',
538+ 'model-version': '2.2.8',
539+ },
540+ {
541+ 'agent-time': 1515053428.0,
542+ 'model': 'mojo-dot-canonical-dot-com',
543+ 'machine': '0',
544+ 'model-version': '2.2.8',
545+ 'current': 'idle',
546+ 'machine-version': '2.2.8',
547+ 'unit': 'apache2/0',
548+ 'since': '04 Jan 2018 09:10:28Z',
549+ 'version': '2.2.8',
550+ },
551+ {
552+ 'model': 'mojo-dot-canonical-dot-com',
553+ 'machine-version': '2.2.8',
554+ 'current': 'idle',
555+ 'machine': '0',
556+ 'version': '2.2.8',
557+ 'agent-time': 1515053688.0,
558+ 'model-version': '2.2.8',
559+ 'since': '04 Jan 2018 09:14:48Z',
560+ 'unit': 'livepatch/0',
561+ }
562+ ]
563+ machine = {
564+ 'unit': '0',
565+ 'version': '2.2.8',
566+ 'since': '02 Jan 2018 20:23:12Z',
567+ 'model': 'mojo-dot-canonical-dot-com',
568+ 'model-version': '2.2.8',
569+ 'agent-time': 1514920992.0,
570+ 'current': 'started'}
571+ # Since this is a generator, list-ify it first so we can see results.
572+ actual_units = list(juju_status._get_units(self.status, machine))
573+ for unit in expected_units:
574+ self.assertTrue(unit in actual_units)
575+ self.assertEqual(len(expected_units), len(actual_units))
576+
577+ def test__get_model_machines(self):
578+ expected_model_machines = [
579+ [
580+ {
581+ 'agent-time': 1514920992.0,
582+ 'since': '02 Jan 2018 20:23:12Z',
583+ 'model': 'mojo-dot-canonical-dot-com',
584+ 'unit': '0',
585+ 'model-version': '2.2.8',
586+ 'current': 'started',
587+ 'version': '2.2.8',
588+ },
589+ {
590+ 'agent-time': 1515053428.0,
591+ 'since': '04 Jan 2018 09:10:28Z',
592+ 'machine': '0',
593+ 'model': 'mojo-dot-canonical-dot-com',
594+ 'machine-version': '2.2.8',
595+ 'unit': 'apache2/0',
596+ 'model-version': '2.2.8',
597+ 'current': 'idle',
598+ 'version': '2.2.8',
599+ },
600+ {
601+ 'agent-time': 1515053441.0,
602+ 'since': '04 Jan 2018 09:10:41Z',
603+ 'machine': '0',
604+ 'model': 'mojo-dot-canonical-dot-com',
605+ 'machine-version': '2.2.8',
606+ 'unit': 'landscape/1',
607+ 'model-version': '2.2.8',
608+ 'current': 'idle',
609+ 'version': '2.2.8',
610+ },
611+ {
612+ 'agent-time': 1515053761.0,
613+ 'since': '04 Jan 2018 09:16:01Z',
614+ 'machine': '0',
615+ 'model': 'mojo-dot-canonical-dot-com',
616+ 'machine-version': '2.2.8',
617+ 'unit': 'nrpe/0',
618+ 'model-version': '2.2.8',
619+ 'current': 'idle',
620+ 'version': '2.2.8',
621+ },
622+ {
623+ 'agent-time': 1515053747.0,
624+ 'since': '04 Jan 2018 09:15:47Z',
625+ 'machine': '0',
626+ 'model': 'mojo-dot-canonical-dot-com',
627+ 'machine-version': '2.2.8',
628+ 'unit': 'content-fetcher/0',
629+ 'model-version': '2.2.8',
630+ 'current': 'idle',
631+ 'version': '2.2.8',
632+ },
633+ {
634+ 'agent-time': 1515053688.0,
635+ 'since': '04 Jan 2018 09:14:48Z',
636+ 'machine': '0',
637+ 'model': 'mojo-dot-canonical-dot-com',
638+ 'machine-version': '2.2.8',
639+ 'unit': 'livepatch/0',
640+ 'model-version': '2.2.8',
641+ 'current': 'idle',
642+ 'version': '2.2.8',
643+ }
644+ ]
645+ ]
646+ actual_model_machines = juju_status._get_model_machines(self.status)
647+ self.assertEqual(len(actual_model_machines), len(expected_model_machines))
648+ for index, model_machine in enumerate(expected_model_machines):
649+ self.assertEqual(len(actual_model_machines[index]), len(expected_model_machines[index]))
650+ for model in expected_model_machines[index]:
651+ self.assertTrue(model in actual_model_machines[index])
652+
653+ @mock.patch('subprocess.Popen')
654+ def test__run(self, _popen):
655+ class Proc(object):
656+ def communicate(self, input=None, timeout=None):
657+ return b'{"test": 1234}', 0
658+
659+ proc = Proc()
660+ _popen.return_value = proc
661+ result = juju_status._run("ls")
662+ self.assertEqual(result, {"test": 1234})
663+
664+ def test__script_paths(self):
665+ expected_scripts = [
666+ 'juju_status.py',
667+ 'juju_statistics.py',
668+ 'juju_formatter.py',
669+ 'juju_diagnostics.py',
670+ 'juju_utils.py',
671+ ]
672+ self.assertEqual(list(juju_status._script_paths(sys.argv[0])), expected_scripts)
673diff --git a/unit_tests/testdata/test_statuses.py b/unit_tests/testdata/test_statuses.py
674new file mode 100644
675index 0000000..10fe22e
676--- /dev/null
677+++ b/unit_tests/testdata/test_statuses.py
678@@ -0,0 +1,218 @@
679+#!/usr/bin/python3
680+
681+# Copyright (c) 2018 Canonical Ltd
682+# License: GPLv3
683+# Author: Tom Haddon <tom.haddon@canonical.com>
684+#
685+# Test data for juju-upgrader.
686+#
687+
688+MOJO_DOT_CANONICAL_DOT_COM_STATUS = {
689+ "model": {
690+ "name": "mojo-dot-canonical-dot-com",
691+ "controller": "prodstack",
692+ "cloud": "prodstack",
693+ "region": "prodstack",
694+ "version": "2.2.8",
695+ },
696+ "machines": {
697+ "0": {
698+ "juju-status": {
699+ "current": "started",
700+ "since": "02 Jan 2018 20:23:12Z",
701+ "version": "2.2.8",
702+ },
703+ "dns-name": "162.213.33.51",
704+ "ip-addresses": ["162.213.33.51", "10.0.0.10"],
705+ "instance-id": "68da26e2-9593-4ca7-9718-cda2dff6ceb1",
706+ "machine-status": {
707+ "current": "running",
708+ "message": "ACTIVE",
709+ "since": "30 Sep 2016 17:04:30Z",
710+ },
711+ "series": "xenial",
712+ "hardware": "arch=amd64 cores=1 mem=2048M root-disk=10240M availability-zone=nova",
713+ }
714+ },
715+ "applications": {
716+ "apache2": {
717+ "charm": "local:xenial/apache2-0",
718+ "series": "xenial",
719+ "os": "ubuntu",
720+ "charm-origin": "local",
721+ "charm-name": "apache2",
722+ "charm-rev": 0,
723+ "exposed": True,
724+ "application-status": {
725+ "current": "unknown",
726+ "since": "30 Sep 2016 17:07:54Z",
727+ },
728+ "relations": {
729+ "juju-info": ["content-fetcher", "livepatch", "landscape"],
730+ "nrpe-external-master": ["nrpe"],
731+ },
732+ "units": {
733+ "apache2/0": {
734+ "workload-status": {
735+ "current": "unknown",
736+ "since": "30 Sep 2016 17:07:54Z",
737+ },
738+ "juju-status": {
739+ "current": "idle",
740+ "since": "04 Jan 2018 09:10:28Z",
741+ "version": "2.2.8",
742+ },
743+ "leader": True,
744+ "machine": "0",
745+ "open-ports": ["80/tcp", "443/tcp"],
746+ "public-address": "162.213.33.51",
747+ "subordinates": {
748+ "content-fetcher/0": {
749+ "workload-status": {
750+ "current": "unknown",
751+ "since": "30 Sep 2016 17:28:40Z",
752+ },
753+ "juju-status": {
754+ "current": "idle",
755+ "since": "04 Jan 2018 09:15:47Z",
756+ "version": "2.2.8",
757+ },
758+ "leader": True,
759+ "upgrading-from": "local:xenial/content-fetcher-0",
760+ "public-address": "162.213.33.51",
761+ },
762+ "livepatch/0": {
763+ "workload-status": {
764+ "current": "active",
765+ "message": "Effective kernel 4.4.0-103-generic",
766+ "since": "04 Jan 2018 09:14:48Z",
767+ },
768+ "juju-status": {
769+ "current": "idle",
770+ "since": "04 Jan 2018 09:14:48Z",
771+ "version": "2.2.8",
772+ },
773+ "leader": True,
774+ "upgrading-from": "local:xenial/livepatch-5",
775+ "public-address": "162.213.33.51",
776+ },
777+ "landscape/1": {
778+ "workload-status": {
779+ "current": "active",
780+ "message": "System successfully registered",
781+ "since": "02 Jan 2018 20:24:58Z",
782+ },
783+ "juju-status": {
784+ "current": "idle",
785+ "since": "04 Jan 2018 09:10:41Z",
786+ "version": "2.2.8",
787+ },
788+ "leader": True,
789+ "upgrading-from": "local:xenial/landscape-client-23",
790+ "public-address": "162.213.33.51",
791+ },
792+ "nrpe/0": {
793+ "workload-status": {
794+ "current": "unknown",
795+ "since": "30 Sep 2016 17:29:04Z",
796+ },
797+ "juju-status": {
798+ "current": "idle",
799+ "since": "04 Jan 2018 09:16:01Z",
800+ "version": "2.2.8",
801+ },
802+ "leader": True,
803+ "upgrading-from": "local:xenial/nrpe-0",
804+ "public-address": "162.213.33.51",
805+ }
806+ }
807+ }
808+ }
809+ },
810+ "content-fetcher": {
811+ "charm": "local:xenial/content-fetcher-0",
812+ "series": "xenial",
813+ "os": "ubuntu",
814+ "charm-origin": "local",
815+ "charm-name": "content-fetcher",
816+ "charm-rev": 0,
817+ "exposed": False,
818+ "application-status": {
819+ "current": "unknown",
820+ "since": "30 Sep 2016 17:28:40Z",
821+ },
822+ "relations": {
823+ "general-info": ["apache2"],
824+ },
825+ "subordinate-to": ["apache2"],
826+ },
827+ "livepatch": {
828+ "charm": "local:xenial/livepatch-5",
829+ "series": "xenial",
830+ "os": "ubuntu",
831+ "charm-origin": "local",
832+ "charm-name": "livepatch",
833+ "charm-rev": 5,
834+ "exposed": False,
835+ "application-status": {
836+ "current": "active",
837+ "message": "Effective kernel 4.4.0-103-generic",
838+ "since": "04 Jan 2018 09:14:48Z",
839+ },
840+ "relations": {
841+ "general-info": ["apache2"],
842+ },
843+ "subordinate-to": ["apache2"],
844+ "version": "1.2.31",
845+ },
846+ "landscape": {
847+ "charm": "local:xenial/landscape-client-23",
848+ "series": "xenial",
849+ "os": "ubuntu",
850+ "charm-origin": "local",
851+ "charm-name": "landscape-client",
852+ "charm-rev": 23,
853+ "exposed": False,
854+ "application-status": {
855+ "current": "active",
856+ "message": "System successfully registered",
857+ "since": "02 Jan 2018 20:24:58Z",
858+ },
859+ "relations": {
860+ "container": ["apache2"],
861+ },
862+ "subordinate-to": ["apache2"],
863+ },
864+ "nrpe": {
865+ "charm": "local:xenial/nrpe-0",
866+ "series": "xenial",
867+ "os": "ubuntu",
868+ "charm-origin": "local",
869+ "charm-name": "nrpe",
870+ "charm-rev": 0,
871+ "exposed": False,
872+ "application-status": {
873+ "current": "unknown",
874+ "since": "30 Sep 2016 17:29:04Z",
875+ },
876+ "relations": {
877+ "nrpe-external-master": ["apache2"],
878+ },
879+ "subordinate-to": ["apache2"],
880+ },
881+ "ubuntu": {
882+ "charm": "cs:ubuntu-10",
883+ "series": "xenial",
884+ "os": "ubuntu",
885+ "charm-origin": "jujucharms",
886+ "charm-name": "ubuntu",
887+ "charm-rev": 10,
888+ "exposed": False,
889+ "application-status": {
890+ "current": "waiting",
891+ "message": "waiting for machine",
892+ "since": "24 Apr 2017 17:41:29Z",
893+ }
894+ }
895+ }
896+ }

Subscribers

People subscribed via source and target branches