Merge lp:~adam-collard/charm-helpers/amulet-action-helpers into lp:charm-helpers

Proposed by Adam Collard
Status: Merged
Merged at revision: 428
Proposed branch: lp:~adam-collard/charm-helpers/amulet-action-helpers
Merge into: lp:charm-helpers
Diff against target: 145 lines (+119/-0)
2 files modified
charmhelpers/contrib/amulet/utils.py (+29/-0)
tests/contrib/amulet/test_utils.py (+90/-0)
To merge this branch: bzr merge lp:~adam-collard/charm-helpers/amulet-action-helpers
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Review via email: mp+268117@code.launchpad.net

Description of the change

Add helpers to contrib.amulet.utils for interacting with actions.

To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

Looks good, thanks a lot!

+1

(note: for some reason I had to recreate the .venvs for the tests to pass - but I suspect it's a fluke.)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/amulet/utils.py'
2--- charmhelpers/contrib/amulet/utils.py 2015-08-12 13:33:22 +0000
3+++ charmhelpers/contrib/amulet/utils.py 2015-08-14 17:21:23 +0000
4@@ -15,9 +15,11 @@
5 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
6
7 import io
8+import json
9 import logging
10 import os
11 import re
12+import subprocess
13 import sys
14 import time
15
16@@ -551,3 +553,30 @@
17 return 'Dicts within list are not identical'
18
19 return None
20+
21+ def run_action(self, unit_sentry, action,
22+ _check_output=subprocess.check_output):
23+ """Run the named action on a given unit sentry.
24+
25+ _check_output parameter is used for dependency injection.
26+
27+ @return action_id.
28+ """
29+ unit_id = unit_sentry.info["unit_name"]
30+ command = ["juju", "action", "do", "--format=json", unit_id, action]
31+ self.log.info("Running command: %s\n" % " ".join(command))
32+ output = _check_output(command, universal_newlines=True)
33+ data = json.loads(output)
34+ action_id = data[u'Action queued with id']
35+ return action_id
36+
37+ def wait_on_action(self, action_id, _check_output=subprocess.check_output):
38+ """Wait for a given action, returning if it completed or not.
39+
40+ _check_output parameter is used for dependency injection.
41+ """
42+ command = ["juju", "action", "fetch", "--format=json", "--wait=0",
43+ action_id]
44+ output = _check_output(command, universal_newlines=True)
45+ data = json.loads(output)
46+ return data.get(u"status") == "completed"
47
48=== added file 'tests/contrib/amulet/__init__.py'
49=== modified file 'tests/contrib/amulet/test_utils.py'
50--- tests/contrib/amulet/test_utils.py 2015-08-12 14:08:25 +0000
51+++ tests/contrib/amulet/test_utils.py 2015-08-14 17:21:23 +0000
52@@ -103,3 +103,93 @@
53 result = self.utils.validate_services_by_name(
54 {self.sentry_unit: ["foo"]})
55 self.assertIsNotNone(result)
56+
57+
58+class RunActionTestCase(unittest.TestCase):
59+
60+ def setUp(self):
61+ self.utils = AmuletUtils()
62+ self.sentry_unit = FakeSentry()
63+
64+ def test_request_json_output(self):
65+ """Juju is called with --format=json, to guarantee output format."""
66+ output_calls = []
67+
68+ def fake_check_output(call, **kwargs):
69+ output_calls.append(call)
70+ return '{"Action queued with id": "action-id"}'
71+
72+ self.utils.run_action(
73+ self.sentry_unit, "foo", _check_output=fake_check_output)
74+ call, = output_calls
75+ self.assertIn("--format=json", call)
76+
77+ def test_returns_action_id(self):
78+ """JSON output is parsed and returns action_id."""
79+
80+ def fake_check_output(call, **kwargs):
81+ return '{"Action queued with id": "action-id"}'
82+
83+ self.assertEqual("action-id", self.utils.run_action(
84+ self.sentry_unit, "foo", _check_output=fake_check_output))
85+
86+
87+class WaitActionTestCase(unittest.TestCase):
88+
89+ def setUp(self):
90+ self.utils = AmuletUtils()
91+ self.sentry_unit = FakeSentry()
92+
93+ def test_request_json_output(self):
94+ """Juju is called with --format=json, to guarantee output format."""
95+ output_calls = []
96+
97+ def fake_check_output(call, **kwargs):
98+ output_calls.append(call)
99+ return '{"status": "completed"}'
100+
101+ self.utils.wait_on_action(
102+ "action-id", _check_output=fake_check_output)
103+ call, = output_calls
104+ self.assertIn("--format=json", call)
105+
106+ def test_request_indefinitely(self):
107+ """Juju with --wait=0, to block until a result is available."""
108+ output_calls = []
109+
110+ def fake_check_output(call, **kwargs):
111+ output_calls.append(call)
112+ return '{"status": "completed"}'
113+
114+ self.utils.wait_on_action(
115+ "action-id", _check_output=fake_check_output)
116+ call, = output_calls
117+ self.assertIn("--wait=0", call)
118+
119+ def test_returns_true_if_completed(self):
120+ """JSON output is parsed and returns True if the action completed."""
121+ def fake_check_output(call, **kwargs):
122+ return '{"status": "completed"}'
123+
124+ self.assertTrue(self.utils.wait_on_action(
125+ "action-id", _check_output=fake_check_output))
126+
127+ def test_returns_false_if_still_running(self):
128+ """
129+ JSON output is parsed and returns False if the action is still running.
130+ """
131+ def fake_check_output(call, **kwargs):
132+ return '{"status": "running"}'
133+
134+ self.assertFalse(self.utils.wait_on_action(
135+ "action-id", _check_output=fake_check_output))
136+
137+ def test_returns_false_if_no_status(self):
138+ """
139+ JSON output is parsed and returns False if there is no action status.
140+ """
141+ def fake_check_output(call, **kwargs):
142+ return '{"status": "running"}'
143+
144+ self.assertFalse(self.utils.wait_on_action(
145+ "action-id", _check_output=fake_check_output))

Subscribers

People subscribed via source and target branches