Merge lp:~bjornt/landscape-charm/workload-status into lp:~landscape/landscape-charm/trunk
- workload-status
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Björn Tillenius | ||||
Approved revision: | 355 | ||||
Merged at revision: | 340 | ||||
Proposed branch: | lp:~bjornt/landscape-charm/workload-status | ||||
Merge into: | lp:~landscape/landscape-charm/trunk | ||||
Diff against target: |
530 lines (+240/-52) 12 files modified
lib/action.py (+16/-14) lib/callbacks/scripts.py (+12/-0) lib/callbacks/tests/test_scripts.py (+47/-0) lib/pause.py (+2/-0) lib/resume.py (+6/-0) lib/tests/stubs.py (+8/-0) lib/tests/test_action.py (+34/-11) lib/tests/test_migrate_schema.py (+5/-5) lib/tests/test_pause.py (+14/-0) lib/tests/test_resume.py (+72/-15) lib/tests/test_upgrade.py (+4/-6) tests/basic/test_actions.py (+20/-1) |
||||
To merge this branch: | bzr merge lp:~bjornt/landscape-charm/workload-status | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Glass (community) | Approve | ||
🤖 Landscape Builder | test results | Approve | |
Free Ekanayaka (community) | Approve | ||
Review via email: mp+280123@code.launchpad.net |
Commit message
Initial go at adding workload status to the charm.
It currently sets the unit to active when the Landscape services are
running and maintenance when the are not running. It doesn't yet handle
when something is in an error state or when the unit is blocked.
The resume action is also changed to look at the workload status instead
of the maintenance flag, which solves the problem where non-leader units
couldn't be resumed.
Description of the change
Initial go at adding workload status to the charm.
It currently sets the unit to active when the Landscape services are
running and maintenance when the are not running. It doesn't yet handle
when something is in an error state or when the unit is blocked.
The resume action is also changed to look at the workload status instead
of the maintenance flag, which solves the problem where non-leader units
couldn't be resumed.
I also had to sync charmhelpers to be able to set workload status, which
required some test changes to cope with it.
🤖 Landscape Builder (landscape-builder) wrote : | # |
Free Ekanayaka (free.ekanayaka) wrote : | # |
Could we back out the charmhelpers upgrade from this branch? As far as I'm concerned it can even be a trivial as long as unit/integration tests pass (I'm assuming there's not too much breakage in our own code, not sure it's the case).
Björn Tillenius (bjornt) wrote : | # |
Sure, I've committed the charmhelpers change in trunk. I didn't do that before, since I couldn't run the integration tests. But I finally managed to work around the problem and managed to get a successful run.
- 352. By Björn Tillenius
-
Merge trunk.
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Success
Revno: 352
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
- 353. By Björn Tillenius
-
Merge trunk.
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Success
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
Free Ekanayaka (free.ekanayaka) wrote : | # |
+1
Just a question about whether the final status set by the LSCtl callback can be anything else than "active" (see the comment below).
- 354. By Björn Tillenius
-
Rename final_status to current_status.
- 355. By Björn Tillenius
-
Rename 'valid_status' to 'required_status'.
Björn Tillenius (bjornt) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Success
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/
Preview Diff
1 | === modified file 'lib/action.py' | |||
2 | --- lib/action.py 2015-06-05 12:00:05 +0000 | |||
3 | +++ lib/action.py 2015-12-15 12:32:20 +0000 | |||
4 | @@ -1,5 +1,3 @@ | |||
5 | 1 | import os.path | ||
6 | 2 | |||
7 | 3 | from charmhelpers.core import hookenv | 1 | from charmhelpers.core import hookenv |
8 | 4 | 2 | ||
9 | 5 | from lib.error import CharmError | 3 | from lib.error import CharmError |
10 | @@ -8,7 +6,13 @@ | |||
11 | 8 | 6 | ||
12 | 9 | 7 | ||
13 | 10 | class Action(Hook): | 8 | class Action(Hook): |
15 | 11 | """Juju action abstraction, providing dependency injection for testing.""" | 9 | """Juju action abstraction, providing dependency injection for testing. |
16 | 10 | |||
17 | 11 | @ivar required_status: The status the unit has to be in when the action | ||
18 | 12 | is executed. | ||
19 | 13 | """ | ||
20 | 14 | |||
21 | 15 | required_status = None | ||
22 | 12 | 16 | ||
23 | 13 | def __call__(self): | 17 | def __call__(self): |
24 | 14 | """ | 18 | """ |
25 | @@ -17,6 +21,13 @@ | |||
26 | 17 | If _run() returns a value, set it using action_set(). | 21 | If _run() returns a value, set it using action_set(). |
27 | 18 | If _run() throws a CharmError, fail using action.fail(). | 22 | If _run() throws a CharmError, fail using action.fail(). |
28 | 19 | """ | 23 | """ |
29 | 24 | if self.required_status is not None: | ||
30 | 25 | status, _ = self._hookenv.status_get() | ||
31 | 26 | if status != self.required_status: | ||
32 | 27 | self._hookenv.action_fail( | ||
33 | 28 | "This action can only be called on a unit in status " | ||
34 | 29 | "'{}'.".format(self.required_status)) | ||
35 | 30 | return | ||
36 | 20 | self._hookenv.log("Running action %s" % type(self).__name__) | 31 | self._hookenv.log("Running action %s" % type(self).__name__) |
37 | 21 | try: | 32 | try: |
38 | 22 | return_values = self._run() | 33 | return_values = self._run() |
39 | @@ -29,6 +40,8 @@ | |||
40 | 29 | class MaintenanceAction(Action): | 40 | class MaintenanceAction(Action): |
41 | 30 | """Action that only runs when in maintenance mode.""" | 41 | """Action that only runs when in maintenance mode.""" |
42 | 31 | 42 | ||
43 | 43 | required_status = "maintenance" | ||
44 | 44 | |||
45 | 32 | def __init__(self, hookenv=hookenv, paths=default_paths): | 45 | def __init__(self, hookenv=hookenv, paths=default_paths): |
46 | 33 | """ | 46 | """ |
47 | 34 | @param hookenv: The charm-helpers C{hookenv} module, will be replaced | 47 | @param hookenv: The charm-helpers C{hookenv} module, will be replaced |
48 | @@ -37,14 +50,3 @@ | |||
49 | 37 | """ | 50 | """ |
50 | 38 | self._hookenv = hookenv | 51 | self._hookenv = hookenv |
51 | 39 | self._paths = paths | 52 | self._paths = paths |
52 | 40 | |||
53 | 41 | def __call__(self): | ||
54 | 42 | """Invoke the action. | ||
55 | 43 | |||
56 | 44 | @return: An integer with the exit code for the hook. | ||
57 | 45 | """ | ||
58 | 46 | if not os.path.exists(self._paths.maintenance_flag()): | ||
59 | 47 | self._hookenv.action_fail( | ||
60 | 48 | "This action can only be called on a unit in paused state.") | ||
61 | 49 | return | ||
62 | 50 | super(MaintenanceAction, self).__call__() | ||
63 | 51 | 53 | ||
64 | === modified file 'lib/callbacks/scripts.py' | |||
65 | --- lib/callbacks/scripts.py 2015-09-29 10:26:00 +0000 | |||
66 | +++ lib/callbacks/scripts.py 2015-12-15 12:32:20 +0000 | |||
67 | @@ -69,6 +69,8 @@ | |||
68 | 69 | self._hookenv = hookenv | 69 | self._hookenv = hookenv |
69 | 70 | 70 | ||
70 | 71 | def __call__(self, manager, service_name, event_name): | 71 | def __call__(self, manager, service_name, event_name): |
71 | 72 | current_status, current_status_message = self._hookenv.status_get() | ||
72 | 73 | action_status_message = "" | ||
73 | 72 | action = event_name | 74 | action = event_name |
74 | 73 | if event_name == "start": | 75 | if event_name == "start": |
75 | 74 | # XXX the 'start' event in the services framework is called after | 76 | # XXX the 'start' event in the services framework is called after |
76 | @@ -89,8 +91,18 @@ | |||
77 | 89 | elif hook_name == "db-relation-changed": | 91 | elif hook_name == "db-relation-changed": |
78 | 90 | if not self._need_restart_db_relation_changed(db_new, db_old): | 92 | if not self._need_restart_db_relation_changed(db_new, db_old): |
79 | 91 | return | 93 | return |
80 | 94 | if action == "restart": | ||
81 | 95 | action_status_message = "Restarting services." | ||
82 | 96 | if current_status == "unknown": | ||
83 | 97 | # If the status is unknown, it means that the services | ||
84 | 98 | # have not been started yet. | ||
85 | 99 | action_status_message = "Starting services." | ||
86 | 100 | if current_status == "maintenance": | ||
87 | 101 | return | ||
88 | 92 | 102 | ||
89 | 103 | self._hookenv.status_set("maintenance", action_status_message) | ||
90 | 93 | self._run(LSCTL, (action,)) | 104 | self._run(LSCTL, (action,)) |
91 | 105 | self._hookenv.status_set("active", "") | ||
92 | 94 | 106 | ||
93 | 95 | def _need_restart_config_changed(self): | 107 | def _need_restart_config_changed(self): |
94 | 96 | """Check whether we need to restart after a config change. | 108 | """Check whether we need to restart after a config change. |
95 | 97 | 109 | ||
96 | === modified file 'lib/callbacks/tests/test_scripts.py' | |||
97 | --- lib/callbacks/tests/test_scripts.py 2015-09-29 10:26:00 +0000 | |||
98 | +++ lib/callbacks/tests/test_scripts.py 2015-12-15 12:32:20 +0000 | |||
99 | @@ -93,6 +93,53 @@ | |||
100 | 93 | self.assertEqual( | 93 | self.assertEqual( |
101 | 94 | ["/usr/bin/lsctl", "restart"], self.subprocess.calls[0][0]) | 94 | ["/usr/bin/lsctl", "restart"], self.subprocess.calls[0][0]) |
102 | 95 | 95 | ||
103 | 96 | def test_start_unknown_status(self): | ||
104 | 97 | """ | ||
105 | 98 | If the 'lsctl' script is invoked with the 'restart' action when | ||
106 | 99 | the workload status is 'unknown', the status while restarting | ||
107 | 100 | the services will way 'starting services' and the final status | ||
108 | 101 | will be 'active'. | ||
109 | 102 | """ | ||
110 | 103 | self.callback(self.manager, "landscape", "start") | ||
111 | 104 | self.assertEqual(("active", ""), self.hookenv.status_get()) | ||
112 | 105 | self.assertEqual( | ||
113 | 106 | [{"status": "unknown", "message": ""}, | ||
114 | 107 | {"status": "maintenance", "message": "Starting services."}, | ||
115 | 108 | {"status": "active", "message": ""}], | ||
116 | 109 | self.hookenv.statuses) | ||
117 | 110 | |||
118 | 111 | def test_start_active_status(self): | ||
119 | 112 | """ | ||
120 | 113 | If the 'lsctl' script is invoked with the 'restart' action when | ||
121 | 114 | the workload status is 'active', the status while restarting | ||
122 | 115 | the services will way 'restarting services' and the final status | ||
123 | 116 | will be 'active' with no status message. | ||
124 | 117 | """ | ||
125 | 118 | self.hookenv.statuses = [{"status": "active", "message": "Something."}] | ||
126 | 119 | self.callback(self.manager, "landscape", "start") | ||
127 | 120 | self.assertEqual(("active", ""), self.hookenv.status_get()) | ||
128 | 121 | self.assertEqual( | ||
129 | 122 | [{"status": "active", "message": "Something."}, | ||
130 | 123 | {"status": "maintenance", "message": "Restarting services."}, | ||
131 | 124 | {"status": "active", "message": ""}], | ||
132 | 125 | self.hookenv.statuses) | ||
133 | 126 | |||
134 | 127 | def test_start_maintenance_status(self): | ||
135 | 128 | """ | ||
136 | 129 | If the 'lsctl' script is invoked with the 'restart' action when | ||
137 | 130 | the workload status is 'maintenance', the services won't be | ||
138 | 131 | restarted and the workload status won't be changed. | ||
139 | 132 | """ | ||
140 | 133 | self.hookenv.statuses = [ | ||
141 | 134 | {"status": "maintenance", "message": "Doing maintenance."}] | ||
142 | 135 | self.callback(self.manager, "landscape", "start") | ||
143 | 136 | self.assertEqual( | ||
144 | 137 | ("maintenance", "Doing maintenance."), self.hookenv.status_get()) | ||
145 | 138 | self.assertEqual( | ||
146 | 139 | [{"status": "maintenance", "message": "Doing maintenance."}], | ||
147 | 140 | self.hookenv.statuses) | ||
148 | 141 | self.assertEqual([], self.subprocess.calls) | ||
149 | 142 | |||
150 | 96 | def test_stop(self): | 143 | def test_stop(self): |
151 | 97 | """ | 144 | """ |
152 | 98 | The 'lsctl' script is invoked with the 'stop' action if the event name | 145 | The 'lsctl' script is invoked with the 'stop' action if the event name |
153 | 99 | 146 | ||
154 | === modified file 'lib/pause.py' | |||
155 | --- lib/pause.py 2015-06-03 08:55:54 +0000 | |||
156 | +++ lib/pause.py 2015-12-15 12:32:20 +0000 | |||
157 | @@ -14,4 +14,6 @@ | |||
158 | 14 | self._subprocess = subprocess | 14 | self._subprocess = subprocess |
159 | 15 | 15 | ||
160 | 16 | def _run(self): | 16 | def _run(self): |
161 | 17 | self._hookenv.status_set("maintenance", "Stopping services.") | ||
162 | 17 | self._subprocess.check_call((LSCTL, "stop")) | 18 | self._subprocess.check_call((LSCTL, "stop")) |
163 | 19 | self._hookenv.status_set("maintenance", "Services stopped.") | ||
164 | 18 | 20 | ||
165 | === modified file 'lib/resume.py' | |||
166 | --- lib/resume.py 2015-06-16 07:51:03 +0000 | |||
167 | +++ lib/resume.py 2015-12-15 12:32:20 +0000 | |||
168 | @@ -25,9 +25,15 @@ | |||
169 | 25 | self._subprocess = subprocess | 25 | self._subprocess = subprocess |
170 | 26 | 26 | ||
171 | 27 | def _run(self): | 27 | def _run(self): |
172 | 28 | self._hookenv.status_set("maintenance", "Starting services.") | ||
173 | 28 | start_output = self._subprocess.check_output((LSCTL, "start")) | 29 | start_output = self._subprocess.check_output((LSCTL, "start")) |
174 | 29 | try: | 30 | try: |
175 | 30 | self._subprocess.check_output((LSCTL, "status")) | 31 | self._subprocess.check_output((LSCTL, "status")) |
176 | 31 | except subprocess.CalledProcessError as status_error: | 32 | except subprocess.CalledProcessError as status_error: |
177 | 33 | self._hookenv.status_set("maintenance", "Stopping services.") | ||
178 | 32 | self._subprocess.call((LSCTL, "stop")) | 34 | self._subprocess.call((LSCTL, "stop")) |
179 | 35 | self._hookenv.status_set( | ||
180 | 36 | "maintenance", "Services failed to start.") | ||
181 | 33 | raise ProcessesNotStartedError(start_output, status_error.output) | 37 | raise ProcessesNotStartedError(start_output, status_error.output) |
182 | 38 | else: | ||
183 | 39 | self._hookenv.status_set("active", "") | ||
184 | 34 | 40 | ||
185 | === modified file 'lib/tests/stubs.py' | |||
186 | --- lib/tests/stubs.py 2015-11-03 15:20:36 +0000 | |||
187 | +++ lib/tests/stubs.py 2015-12-15 12:32:20 +0000 | |||
188 | @@ -17,6 +17,7 @@ | |||
189 | 17 | self.relations = {} | 17 | self.relations = {} |
190 | 18 | self.action_fails = [] | 18 | self.action_fails = [] |
191 | 19 | self.action_sets = [] | 19 | self.action_sets = [] |
192 | 20 | self.statuses = [{"status": "unknown", "message": ""}] | ||
193 | 20 | 21 | ||
194 | 21 | # We should disable implicit saving since it runs at charm exit using | 22 | # We should disable implicit saving since it runs at charm exit using |
195 | 22 | # globals :( | 23 | # globals :( |
196 | @@ -95,6 +96,13 @@ | |||
197 | 95 | self.action_gets.append(key) | 96 | self.action_gets.append(key) |
198 | 96 | return "%s-value" % key | 97 | return "%s-value" % key |
199 | 97 | 98 | ||
200 | 99 | def status_get(self): | ||
201 | 100 | current_status = self.statuses[-1] | ||
202 | 101 | return current_status["status"], current_status["message"] | ||
203 | 102 | |||
204 | 103 | def status_set(self, workload_state, message): | ||
205 | 104 | self.statuses.append({"status": workload_state, "message": message}) | ||
206 | 105 | |||
207 | 98 | 106 | ||
208 | 99 | class FetchStub(object): | 107 | class FetchStub(object): |
209 | 100 | """Provide a testable stub for C{charmhelpers.fetch}.""" | 108 | """Provide a testable stub for C{charmhelpers.fetch}.""" |
210 | 101 | 109 | ||
211 | === modified file 'lib/tests/test_action.py' | |||
212 | --- lib/tests/test_action.py 2015-06-05 12:02:34 +0000 | |||
213 | +++ lib/tests/test_action.py 2015-12-15 12:32:20 +0000 | |||
214 | @@ -1,5 +1,3 @@ | |||
215 | 1 | import os | ||
216 | 2 | |||
217 | 3 | from lib.action import Action, MaintenanceAction | 1 | from lib.action import Action, MaintenanceAction |
218 | 4 | from lib.error import CharmError | 2 | from lib.error import CharmError |
219 | 5 | 3 | ||
220 | @@ -52,6 +50,31 @@ | |||
221 | 52 | action() | 50 | action() |
222 | 53 | self.assertEqual(["no go"], self.hookenv.action_fails) | 51 | self.assertEqual(["no go"], self.hookenv.action_fails) |
223 | 54 | 52 | ||
224 | 53 | def test_run_required_status(self): | ||
225 | 54 | """ | ||
226 | 55 | If required_status is set, the action will be executed if the | ||
227 | 56 | current status is the same. | ||
228 | 57 | """ | ||
229 | 58 | action = DummyAction(hookenv=self.hookenv) | ||
230 | 59 | action.required_status = "active" | ||
231 | 60 | self.hookenv.status_set("active", "") | ||
232 | 61 | action() | ||
233 | 62 | self.assertTrue(action.executed) | ||
234 | 63 | |||
235 | 64 | def test_run_invalid_status(self): | ||
236 | 65 | """ | ||
237 | 66 | If required_status is set, the action won't be executed if the | ||
238 | 67 | current status is different. | ||
239 | 68 | """ | ||
240 | 69 | action = DummyAction(hookenv=self.hookenv) | ||
241 | 70 | action.required_status = "active" | ||
242 | 71 | self.hookenv.status_set("maintenance", "") | ||
243 | 72 | action() | ||
244 | 73 | self.assertFalse(action.executed) | ||
245 | 74 | self.assertEqual( | ||
246 | 75 | ["This action can only be called on a unit in status 'active'."], | ||
247 | 76 | self.hookenv.action_fails) | ||
248 | 77 | |||
249 | 55 | 78 | ||
250 | 56 | class DummyMaintenanceAction(MaintenanceAction): | 79 | class DummyMaintenanceAction(MaintenanceAction): |
251 | 57 | executed = False | 80 | executed = False |
252 | @@ -69,25 +92,25 @@ | |||
253 | 69 | 92 | ||
254 | 70 | def test_run(self): | 93 | def test_run(self): |
255 | 71 | """Calling a dummy hook runs only with maintenance flag set.""" | 94 | """Calling a dummy hook runs only with maintenance flag set.""" |
260 | 72 | 95 | self.hookenv.status_set("maintenance", "") | |
257 | 73 | open(self.paths.maintenance_flag(), "w") | ||
258 | 74 | self.addCleanup(os.remove, self.paths.maintenance_flag()) | ||
259 | 75 | |||
261 | 76 | action = DummyMaintenanceAction( | 96 | action = DummyMaintenanceAction( |
262 | 77 | hookenv=self.hookenv, paths=self.paths) | 97 | hookenv=self.hookenv, paths=self.paths) |
263 | 78 | 98 | ||
264 | 79 | action() | 99 | action() |
265 | 80 | self.assertTrue(action.executed) | 100 | self.assertTrue(action.executed) |
266 | 81 | 101 | ||
271 | 82 | def test_run_without_maintenance_flag(self): | 102 | def test_run_without_maintenance_status(self): |
272 | 83 | """ | 103 | """ |
273 | 84 | When maintenance flag file is absent, maintenance hooks are no-ops. | 104 | If the workload status isn't 'maintenance', the maintenance |
274 | 85 | """ | 105 | action won't be executed. |
275 | 106 | """ | ||
276 | 107 | self.hookenv.status_set("active", "") | ||
277 | 86 | action = DummyMaintenanceAction( | 108 | action = DummyMaintenanceAction( |
278 | 87 | hookenv=self.hookenv, paths=self.paths) | 109 | hookenv=self.hookenv, paths=self.paths) |
279 | 88 | 110 | ||
280 | 89 | action() | 111 | action() |
281 | 90 | self.assertFalse(action.executed) | 112 | self.assertFalse(action.executed) |
282 | 91 | self.assertEqual( | 113 | self.assertEqual( |
284 | 92 | ["This action can only be called on a unit in paused state."], | 114 | ["This action can only be called on a unit in status " |
285 | 115 | "'maintenance'."], | ||
286 | 93 | self.hookenv.action_fails) | 116 | self.hookenv.action_fails) |
287 | 94 | 117 | ||
288 | === modified file 'lib/tests/test_migrate_schema.py' | |||
289 | --- lib/tests/test_migrate_schema.py 2015-06-01 10:39:56 +0000 | |||
290 | +++ lib/tests/test_migrate_schema.py 2015-12-15 12:32:20 +0000 | |||
291 | @@ -1,5 +1,3 @@ | |||
292 | 1 | import os | ||
293 | 2 | |||
294 | 3 | from lib.migrate_schema import MigrateSchemaAction | 1 | from lib.migrate_schema import MigrateSchemaAction |
295 | 4 | from lib.paths import SCHEMA_SCRIPT | 2 | from lib.paths import SCHEMA_SCRIPT |
296 | 5 | from lib.tests.helpers import HookenvTest | 3 | from lib.tests.helpers import HookenvTest |
297 | @@ -20,8 +18,7 @@ | |||
298 | 20 | """ | 18 | """ |
299 | 21 | The MigrateSchemaAction calls the schema script. | 19 | The MigrateSchemaAction calls the schema script. |
300 | 22 | """ | 20 | """ |
303 | 23 | open(self.paths.maintenance_flag(), "w") | 21 | self.hookenv.status_set("maintenance", "") |
302 | 24 | self.addCleanup(os.remove, self.paths.maintenance_flag()) | ||
304 | 25 | 22 | ||
305 | 26 | action = MigrateSchemaAction( | 23 | action = MigrateSchemaAction( |
306 | 27 | hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess) | 24 | hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess) |
307 | @@ -30,8 +27,11 @@ | |||
308 | 30 | 27 | ||
309 | 31 | def test_run_without_maintenance_flag(self): | 28 | def test_run_without_maintenance_flag(self): |
310 | 32 | """ | 29 | """ |
312 | 33 | The MigrateSchemaAction calls the schema script. | 30 | The MigrateSchemaAction doesn't call the schema script if the |
313 | 31 | unit is in an 'active' state. | ||
314 | 34 | """ | 32 | """ |
315 | 33 | self.hookenv.status_set("active", "") | ||
316 | 34 | |||
317 | 35 | action = MigrateSchemaAction( | 35 | action = MigrateSchemaAction( |
318 | 36 | hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess) | 36 | hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess) |
319 | 37 | action() | 37 | action() |
320 | 38 | 38 | ||
321 | === modified file 'lib/tests/test_pause.py' | |||
322 | --- lib/tests/test_pause.py 2015-06-01 08:57:04 +0000 | |||
323 | +++ lib/tests/test_pause.py 2015-12-15 12:32:20 +0000 | |||
324 | @@ -21,3 +21,17 @@ | |||
325 | 21 | self.action() | 21 | self.action() |
326 | 22 | self.assertEqual( | 22 | self.assertEqual( |
327 | 23 | [(("/usr/bin/lsctl", "stop"), {})], self.subprocess.calls) | 23 | [(("/usr/bin/lsctl", "stop"), {})], self.subprocess.calls) |
328 | 24 | |||
329 | 25 | def test_run_status(self): | ||
330 | 26 | """ | ||
331 | 27 | The workload status is changed to 'maintenance' while stopping | ||
332 | 28 | the services and after the services have been stopped. | ||
333 | 29 | """ | ||
334 | 30 | self.action() | ||
335 | 31 | self.assertEqual( | ||
336 | 32 | ("maintenance", "Services stopped."), self.hookenv.status_get()) | ||
337 | 33 | self.assertEqual( | ||
338 | 34 | [{"status": "unknown", "message": ""}, | ||
339 | 35 | {"status": "maintenance", "message": "Stopping services."}, | ||
340 | 36 | {"status": "maintenance", "message": "Services stopped."}], | ||
341 | 37 | self.hookenv.statuses) | ||
342 | 24 | 38 | ||
343 | === modified file 'lib/tests/test_resume.py' | |||
344 | --- lib/tests/test_resume.py 2015-06-16 07:51:03 +0000 | |||
345 | +++ lib/tests/test_resume.py 2015-12-15 12:32:20 +0000 | |||
346 | @@ -1,5 +1,3 @@ | |||
347 | 1 | import os | ||
348 | 2 | |||
349 | 3 | from lib.tests.helpers import HookenvTest | 1 | from lib.tests.helpers import HookenvTest |
350 | 4 | from lib.tests.rootdir import RootDir | 2 | from lib.tests.rootdir import RootDir |
351 | 5 | from lib.tests.stubs import SubprocessStub | 3 | from lib.tests.stubs import SubprocessStub |
352 | @@ -23,8 +21,7 @@ | |||
353 | 23 | enabling the cron service so that Landscape cron jobs can start | 21 | enabling the cron service so that Landscape cron jobs can start |
354 | 24 | running again. | 22 | running again. |
355 | 25 | """ | 23 | """ |
358 | 26 | open(self.paths.maintenance_flag(), "w") | 24 | self.hookenv.status_set("maintenance", "") |
357 | 27 | self.addCleanup(os.remove, self.paths.maintenance_flag()) | ||
359 | 28 | 25 | ||
360 | 29 | action = ResumeAction( | 26 | action = ResumeAction( |
361 | 30 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) | 27 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) |
362 | @@ -34,10 +31,29 @@ | |||
363 | 34 | (("/usr/bin/lsctl", "status"), {})], | 31 | (("/usr/bin/lsctl", "status"), {})], |
364 | 35 | self.subprocess.calls) | 32 | self.subprocess.calls) |
365 | 36 | 33 | ||
366 | 34 | def test_run_status(self): | ||
367 | 35 | """ | ||
368 | 36 | The status is set to 'maintenance' while starting the services | ||
369 | 37 | and is set to 'active' after the services have been started. | ||
370 | 38 | """ | ||
371 | 39 | self.hookenv.status_set("maintenance", "") | ||
372 | 40 | |||
373 | 41 | action = ResumeAction( | ||
374 | 42 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) | ||
375 | 43 | action() | ||
376 | 44 | self.assertEqual(("active", ""), self.hookenv.status_get()) | ||
377 | 45 | self.assertEqual( | ||
378 | 46 | [{"status": "unknown", "message": ""}, | ||
379 | 47 | {"status": "maintenance", "message": ""}, | ||
380 | 48 | {"status": "maintenance", "message": "Starting services."}, | ||
381 | 49 | {"status": "active", "message": ""}], | ||
382 | 50 | self.hookenv.statuses) | ||
383 | 51 | |||
384 | 37 | def test_run_without_maintenance_flag(self): | 52 | def test_run_without_maintenance_flag(self): |
385 | 38 | """ | 53 | """ |
386 | 39 | When no maintenance flag file is present, resume action is a no-op. | 54 | When no maintenance flag file is present, resume action is a no-op. |
387 | 40 | """ | 55 | """ |
388 | 56 | self.hookenv.status_set("active", "") | ||
389 | 41 | action = ResumeAction( | 57 | action = ResumeAction( |
390 | 42 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) | 58 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) |
391 | 43 | action() | 59 | action() |
392 | @@ -53,17 +69,58 @@ | |||
393 | 53 | The unit is stopped again, to ensure that the 'resume' action | 69 | The unit is stopped again, to ensure that the 'resume' action |
394 | 54 | can run again after the problems have been addressed. | 70 | can run again after the problems have been addressed. |
395 | 55 | """ | 71 | """ |
407 | 56 | open(self.paths.maintenance_flag(), "w") | 72 | self.hookenv.status_set("maintenance", "") |
408 | 57 | self.addCleanup(os.remove, self.paths.maintenance_flag()) | 73 | |
409 | 58 | self.subprocess.add_fake_executable( | 74 | self.subprocess.add_fake_executable( |
410 | 59 | LSCTL, args=["start"], stdout="start output") | 75 | LSCTL, args=["start"], stdout="start output") |
411 | 60 | self.subprocess.add_fake_executable( | 76 | self.subprocess.add_fake_executable( |
412 | 61 | LSCTL, args=["status"], stdout="status failure", return_code=3) | 77 | LSCTL, args=["status"], stdout="status failure", return_code=3) |
413 | 62 | self.subprocess.add_fake_executable(LSCTL, args=["stop"]) | 78 | self.subprocess.add_fake_executable(LSCTL, args=["stop"]) |
414 | 63 | 79 | ||
415 | 64 | action = ResumeAction( | 80 | action = ResumeAction( |
416 | 65 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) | 81 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) |
417 | 66 | action() | 82 | action() |
418 | 83 | self.assertEqual( | ||
419 | 84 | ["Some services failed to start.\n\nstart output\n\n" | ||
420 | 85 | "status failure"], | ||
421 | 86 | self.hookenv.action_fails) | ||
422 | 87 | self.assertEqual( | ||
423 | 88 | [(("/usr/bin/lsctl", "start"), {}), | ||
424 | 89 | (("/usr/bin/lsctl", "status"), {}), | ||
425 | 90 | (("/usr/bin/lsctl", "stop"), {})], | ||
426 | 91 | self.subprocess.calls) | ||
427 | 92 | |||
428 | 93 | def test_run_fail_status(self): | ||
429 | 94 | """ | ||
430 | 95 | If the action fails, the final status is put back to | ||
431 | 96 | maintenance, with a message saying that services failed to | ||
432 | 97 | start. | ||
433 | 98 | |||
434 | 99 | XXX: The final status probably should be 'error', but it's not | ||
435 | 100 | clear on how to recover from that. | ||
436 | 101 | """ | ||
437 | 102 | self.hookenv.status_set("maintenance", "") | ||
438 | 103 | |||
439 | 104 | self.subprocess.add_fake_executable( | ||
440 | 105 | LSCTL, args=["start"], stdout="start output") | ||
441 | 106 | self.subprocess.add_fake_executable( | ||
442 | 107 | LSCTL, args=["status"], stdout="status failure", return_code=3) | ||
443 | 108 | self.subprocess.add_fake_executable(LSCTL, args=["stop"]) | ||
444 | 109 | |||
445 | 110 | action = ResumeAction( | ||
446 | 111 | hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths) | ||
447 | 112 | action() | ||
448 | 113 | self.assertEqual( | ||
449 | 114 | ("maintenance", "Services failed to start."), | ||
450 | 115 | self.hookenv.status_get()) | ||
451 | 116 | self.assertEqual( | ||
452 | 117 | [{"status": "unknown", "message": ""}, | ||
453 | 118 | {"status": "maintenance", "message": ""}, | ||
454 | 119 | {"status": "maintenance", "message": "Starting services."}, | ||
455 | 120 | {"status": "maintenance", "message": "Stopping services."}, | ||
456 | 121 | {"status": "maintenance", | ||
457 | 122 | "message": "Services failed to start."}], | ||
458 | 123 | self.hookenv.statuses) | ||
459 | 67 | self.assertEqual( | 124 | self.assertEqual( |
460 | 68 | ["Some services failed to start.\n\nstart output\n\n" | 125 | ["Some services failed to start.\n\nstart output\n\n" |
461 | 69 | "status failure"], | 126 | "status failure"], |
462 | 70 | 127 | ||
463 | === modified file 'lib/tests/test_upgrade.py' | |||
464 | --- lib/tests/test_upgrade.py 2015-11-03 13:58:50 +0000 | |||
465 | +++ lib/tests/test_upgrade.py 2015-12-15 12:32:20 +0000 | |||
466 | @@ -1,5 +1,3 @@ | |||
467 | 1 | import os | ||
468 | 2 | |||
469 | 3 | from lib.tests.helpers import HookenvTest | 1 | from lib.tests.helpers import HookenvTest |
470 | 4 | from lib.tests.rootdir import RootDir | 2 | from lib.tests.rootdir import RootDir |
471 | 5 | from lib.tests.stubs import FetchStub | 3 | from lib.tests.stubs import FetchStub |
472 | @@ -19,9 +17,7 @@ | |||
473 | 19 | The UpgradeAction refreshes package indexes and upgrades | 17 | The UpgradeAction refreshes package indexes and upgrades |
474 | 20 | landscape-server package. | 18 | landscape-server package. |
475 | 21 | """ | 19 | """ |
479 | 22 | 20 | self.hookenv.status_set("maintenance", "") | |
477 | 23 | open(self.paths.maintenance_flag(), "w") | ||
478 | 24 | self.addCleanup(os.remove, self.paths.maintenance_flag()) | ||
480 | 25 | 21 | ||
481 | 26 | self.hookenv.config()["source"] = "ppa:my-ppa" | 22 | self.hookenv.config()["source"] = "ppa:my-ppa" |
482 | 27 | action = UpgradeAction( | 23 | action = UpgradeAction( |
483 | @@ -41,8 +37,10 @@ | |||
484 | 41 | 37 | ||
485 | 42 | def test_run_without_maintenance_flag(self): | 38 | def test_run_without_maintenance_flag(self): |
486 | 43 | """ | 39 | """ |
488 | 44 | When maintenance flag file is absent, upgrade action is a no-op. | 40 | If the unit is not in the 'maintenance' state, the upgrade |
489 | 41 | action is a no-op. | ||
490 | 45 | """ | 42 | """ |
491 | 43 | self.hookenv.status_set("active", "") | ||
492 | 46 | 44 | ||
493 | 47 | action = UpgradeAction( | 45 | action = UpgradeAction( |
494 | 48 | hookenv=self.hookenv, fetch=self.fetch, paths=self.paths) | 46 | hookenv=self.hookenv, fetch=self.fetch, paths=self.paths) |
495 | 49 | 47 | ||
496 | === modified file 'tests/basic/test_actions.py' | |||
497 | --- tests/basic/test_actions.py 2015-07-09 11:17:22 +0000 | |||
498 | +++ tests/basic/test_actions.py 2015-12-15 12:32:20 +0000 | |||
499 | @@ -3,7 +3,7 @@ | |||
500 | 3 | """ | 3 | """ |
501 | 4 | 4 | ||
502 | 5 | from helpers import IntegrationTest | 5 | from helpers import IntegrationTest |
504 | 6 | from layers import OneLandscapeUnitLayer | 6 | from layers import OneLandscapeUnitLayer, TwoLandscapeUnitsLayer |
505 | 7 | 7 | ||
506 | 8 | 8 | ||
507 | 9 | class ActionsTest(IntegrationTest): | 9 | class ActionsTest(IntegrationTest): |
508 | @@ -60,3 +60,22 @@ | |||
509 | 60 | 60 | ||
510 | 61 | # Logging in should now work. | 61 | # Logging in should now work. |
511 | 62 | self.environment.login("foo@bar", "bar") | 62 | self.environment.login("foo@bar", "bar") |
512 | 63 | |||
513 | 64 | |||
514 | 65 | class ActionsMultipleUnitsTest(IntegrationTest): | ||
515 | 66 | |||
516 | 67 | layer = TwoLandscapeUnitsLayer | ||
517 | 68 | |||
518 | 69 | def setUp(self): | ||
519 | 70 | super(ActionsMultipleUnitsTest, self).setUp() | ||
520 | 71 | [self.non_leader] = self.layer.non_leaders | ||
521 | 72 | |||
522 | 73 | def test_non_leader_pause_resume(self): | ||
523 | 74 | """ | ||
524 | 75 | The non-leader unit can be paused and later resumed. | ||
525 | 76 | """ | ||
526 | 77 | result = self.environment.pause_landscape(unit=self.non_leader) | ||
527 | 78 | self.assertEqual("completed", result["status"]) | ||
528 | 79 | |||
529 | 80 | result = self.environment.resume_landscape(unit=self.non_leader) | ||
530 | 81 | self.assertEqual("completed", result["status"]) |
Command: make ci-test /ci.lscape. net/job/ latch-test/ 3044/
Result: Fail
Revno: 351
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https:/