Merge lp:~bjornt/landscape-charm/workload-status into lp:~landscape/landscape-charm/trunk

Proposed by Björn Tillenius
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
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.

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Fail
Revno: 351
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3044/

review: Needs Fixing (test results)
Revision history for this message
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).

review: Needs Information
Revision history for this message
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.

Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 352
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3055/

review: Approve (test results)
353. By Björn Tillenius

Merge trunk.

Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Fail
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3057/

review: Needs Fixing (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Fail
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3060/

review: Needs Fixing (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 353
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3061/

review: Approve (test results)
Revision history for this message
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).

review: Approve
354. By Björn Tillenius

Rename final_status to current_status.

355. By Björn Tillenius

Rename 'valid_status' to 'required_status'.

Revision history for this message
Björn Tillenius (bjornt) :
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Fail
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3070/

review: Needs Fixing (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Fail
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3071/

review: Needs Fixing (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 355
Branch: lp:~bjornt/landscape-charm/workload-status
Jenkins: https://ci.lscape.net/job/latch-test/3072/

review: Approve (test results)
Revision history for this message
Chris Glass (tribaal) wrote :

Looks good! +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/action.py'
--- lib/action.py 2015-06-05 12:00:05 +0000
+++ lib/action.py 2015-12-15 12:32:20 +0000
@@ -1,5 +1,3 @@
1import os.path
2
3from charmhelpers.core import hookenv1from charmhelpers.core import hookenv
42
5from lib.error import CharmError3from lib.error import CharmError
@@ -8,7 +6,13 @@
86
97
10class Action(Hook):8class Action(Hook):
11 """Juju action abstraction, providing dependency injection for testing."""9 """Juju action abstraction, providing dependency injection for testing.
10
11 @ivar required_status: The status the unit has to be in when the action
12 is executed.
13 """
14
15 required_status = None
1216
13 def __call__(self):17 def __call__(self):
14 """18 """
@@ -17,6 +21,13 @@
17 If _run() returns a value, set it using action_set().21 If _run() returns a value, set it using action_set().
18 If _run() throws a CharmError, fail using action.fail().22 If _run() throws a CharmError, fail using action.fail().
19 """23 """
24 if self.required_status is not None:
25 status, _ = self._hookenv.status_get()
26 if status != self.required_status:
27 self._hookenv.action_fail(
28 "This action can only be called on a unit in status "
29 "'{}'.".format(self.required_status))
30 return
20 self._hookenv.log("Running action %s" % type(self).__name__)31 self._hookenv.log("Running action %s" % type(self).__name__)
21 try:32 try:
22 return_values = self._run()33 return_values = self._run()
@@ -29,6 +40,8 @@
29class MaintenanceAction(Action):40class MaintenanceAction(Action):
30 """Action that only runs when in maintenance mode."""41 """Action that only runs when in maintenance mode."""
3142
43 required_status = "maintenance"
44
32 def __init__(self, hookenv=hookenv, paths=default_paths):45 def __init__(self, hookenv=hookenv, paths=default_paths):
33 """46 """
34 @param hookenv: The charm-helpers C{hookenv} module, will be replaced47 @param hookenv: The charm-helpers C{hookenv} module, will be replaced
@@ -37,14 +50,3 @@
37 """50 """
38 self._hookenv = hookenv51 self._hookenv = hookenv
39 self._paths = paths52 self._paths = paths
40
41 def __call__(self):
42 """Invoke the action.
43
44 @return: An integer with the exit code for the hook.
45 """
46 if not os.path.exists(self._paths.maintenance_flag()):
47 self._hookenv.action_fail(
48 "This action can only be called on a unit in paused state.")
49 return
50 super(MaintenanceAction, self).__call__()
5153
=== modified file 'lib/callbacks/scripts.py'
--- lib/callbacks/scripts.py 2015-09-29 10:26:00 +0000
+++ lib/callbacks/scripts.py 2015-12-15 12:32:20 +0000
@@ -69,6 +69,8 @@
69 self._hookenv = hookenv69 self._hookenv = hookenv
7070
71 def __call__(self, manager, service_name, event_name):71 def __call__(self, manager, service_name, event_name):
72 current_status, current_status_message = self._hookenv.status_get()
73 action_status_message = ""
72 action = event_name74 action = event_name
73 if event_name == "start":75 if event_name == "start":
74 # XXX the 'start' event in the services framework is called after76 # XXX the 'start' event in the services framework is called after
@@ -89,8 +91,18 @@
89 elif hook_name == "db-relation-changed":91 elif hook_name == "db-relation-changed":
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):
91 return93 return
94 if action == "restart":
95 action_status_message = "Restarting services."
96 if current_status == "unknown":
97 # If the status is unknown, it means that the services
98 # have not been started yet.
99 action_status_message = "Starting services."
100 if current_status == "maintenance":
101 return
92102
103 self._hookenv.status_set("maintenance", action_status_message)
93 self._run(LSCTL, (action,))104 self._run(LSCTL, (action,))
105 self._hookenv.status_set("active", "")
94106
95 def _need_restart_config_changed(self):107 def _need_restart_config_changed(self):
96 """Check whether we need to restart after a config change.108 """Check whether we need to restart after a config change.
97109
=== modified file 'lib/callbacks/tests/test_scripts.py'
--- lib/callbacks/tests/test_scripts.py 2015-09-29 10:26:00 +0000
+++ lib/callbacks/tests/test_scripts.py 2015-12-15 12:32:20 +0000
@@ -93,6 +93,53 @@
93 self.assertEqual(93 self.assertEqual(
94 ["/usr/bin/lsctl", "restart"], self.subprocess.calls[0][0])94 ["/usr/bin/lsctl", "restart"], self.subprocess.calls[0][0])
9595
96 def test_start_unknown_status(self):
97 """
98 If the 'lsctl' script is invoked with the 'restart' action when
99 the workload status is 'unknown', the status while restarting
100 the services will way 'starting services' and the final status
101 will be 'active'.
102 """
103 self.callback(self.manager, "landscape", "start")
104 self.assertEqual(("active", ""), self.hookenv.status_get())
105 self.assertEqual(
106 [{"status": "unknown", "message": ""},
107 {"status": "maintenance", "message": "Starting services."},
108 {"status": "active", "message": ""}],
109 self.hookenv.statuses)
110
111 def test_start_active_status(self):
112 """
113 If the 'lsctl' script is invoked with the 'restart' action when
114 the workload status is 'active', the status while restarting
115 the services will way 'restarting services' and the final status
116 will be 'active' with no status message.
117 """
118 self.hookenv.statuses = [{"status": "active", "message": "Something."}]
119 self.callback(self.manager, "landscape", "start")
120 self.assertEqual(("active", ""), self.hookenv.status_get())
121 self.assertEqual(
122 [{"status": "active", "message": "Something."},
123 {"status": "maintenance", "message": "Restarting services."},
124 {"status": "active", "message": ""}],
125 self.hookenv.statuses)
126
127 def test_start_maintenance_status(self):
128 """
129 If the 'lsctl' script is invoked with the 'restart' action when
130 the workload status is 'maintenance', the services won't be
131 restarted and the workload status won't be changed.
132 """
133 self.hookenv.statuses = [
134 {"status": "maintenance", "message": "Doing maintenance."}]
135 self.callback(self.manager, "landscape", "start")
136 self.assertEqual(
137 ("maintenance", "Doing maintenance."), self.hookenv.status_get())
138 self.assertEqual(
139 [{"status": "maintenance", "message": "Doing maintenance."}],
140 self.hookenv.statuses)
141 self.assertEqual([], self.subprocess.calls)
142
96 def test_stop(self):143 def test_stop(self):
97 """144 """
98 The 'lsctl' script is invoked with the 'stop' action if the event name145 The 'lsctl' script is invoked with the 'stop' action if the event name
99146
=== modified file 'lib/pause.py'
--- lib/pause.py 2015-06-03 08:55:54 +0000
+++ lib/pause.py 2015-12-15 12:32:20 +0000
@@ -14,4 +14,6 @@
14 self._subprocess = subprocess14 self._subprocess = subprocess
1515
16 def _run(self):16 def _run(self):
17 self._hookenv.status_set("maintenance", "Stopping services.")
17 self._subprocess.check_call((LSCTL, "stop"))18 self._subprocess.check_call((LSCTL, "stop"))
19 self._hookenv.status_set("maintenance", "Services stopped.")
1820
=== modified file 'lib/resume.py'
--- lib/resume.py 2015-06-16 07:51:03 +0000
+++ lib/resume.py 2015-12-15 12:32:20 +0000
@@ -25,9 +25,15 @@
25 self._subprocess = subprocess25 self._subprocess = subprocess
2626
27 def _run(self):27 def _run(self):
28 self._hookenv.status_set("maintenance", "Starting services.")
28 start_output = self._subprocess.check_output((LSCTL, "start"))29 start_output = self._subprocess.check_output((LSCTL, "start"))
29 try:30 try:
30 self._subprocess.check_output((LSCTL, "status"))31 self._subprocess.check_output((LSCTL, "status"))
31 except subprocess.CalledProcessError as status_error:32 except subprocess.CalledProcessError as status_error:
33 self._hookenv.status_set("maintenance", "Stopping services.")
32 self._subprocess.call((LSCTL, "stop"))34 self._subprocess.call((LSCTL, "stop"))
35 self._hookenv.status_set(
36 "maintenance", "Services failed to start.")
33 raise ProcessesNotStartedError(start_output, status_error.output)37 raise ProcessesNotStartedError(start_output, status_error.output)
38 else:
39 self._hookenv.status_set("active", "")
3440
=== modified file 'lib/tests/stubs.py'
--- lib/tests/stubs.py 2015-11-03 15:20:36 +0000
+++ lib/tests/stubs.py 2015-12-15 12:32:20 +0000
@@ -17,6 +17,7 @@
17 self.relations = {}17 self.relations = {}
18 self.action_fails = []18 self.action_fails = []
19 self.action_sets = []19 self.action_sets = []
20 self.statuses = [{"status": "unknown", "message": ""}]
2021
21 # We should disable implicit saving since it runs at charm exit using22 # We should disable implicit saving since it runs at charm exit using
22 # globals :(23 # globals :(
@@ -95,6 +96,13 @@
95 self.action_gets.append(key)96 self.action_gets.append(key)
96 return "%s-value" % key97 return "%s-value" % key
9798
99 def status_get(self):
100 current_status = self.statuses[-1]
101 return current_status["status"], current_status["message"]
102
103 def status_set(self, workload_state, message):
104 self.statuses.append({"status": workload_state, "message": message})
105
98106
99class FetchStub(object):107class FetchStub(object):
100 """Provide a testable stub for C{charmhelpers.fetch}."""108 """Provide a testable stub for C{charmhelpers.fetch}."""
101109
=== modified file 'lib/tests/test_action.py'
--- lib/tests/test_action.py 2015-06-05 12:02:34 +0000
+++ lib/tests/test_action.py 2015-12-15 12:32:20 +0000
@@ -1,5 +1,3 @@
1import os
2
3from lib.action import Action, MaintenanceAction1from lib.action import Action, MaintenanceAction
4from lib.error import CharmError2from lib.error import CharmError
53
@@ -52,6 +50,31 @@
52 action()50 action()
53 self.assertEqual(["no go"], self.hookenv.action_fails)51 self.assertEqual(["no go"], self.hookenv.action_fails)
5452
53 def test_run_required_status(self):
54 """
55 If required_status is set, the action will be executed if the
56 current status is the same.
57 """
58 action = DummyAction(hookenv=self.hookenv)
59 action.required_status = "active"
60 self.hookenv.status_set("active", "")
61 action()
62 self.assertTrue(action.executed)
63
64 def test_run_invalid_status(self):
65 """
66 If required_status is set, the action won't be executed if the
67 current status is different.
68 """
69 action = DummyAction(hookenv=self.hookenv)
70 action.required_status = "active"
71 self.hookenv.status_set("maintenance", "")
72 action()
73 self.assertFalse(action.executed)
74 self.assertEqual(
75 ["This action can only be called on a unit in status 'active'."],
76 self.hookenv.action_fails)
77
5578
56class DummyMaintenanceAction(MaintenanceAction):79class DummyMaintenanceAction(MaintenanceAction):
57 executed = False80 executed = False
@@ -69,25 +92,25 @@
6992
70 def test_run(self):93 def test_run(self):
71 """Calling a dummy hook runs only with maintenance flag set."""94 """Calling a dummy hook runs only with maintenance flag set."""
7295 self.hookenv.status_set("maintenance", "")
73 open(self.paths.maintenance_flag(), "w")
74 self.addCleanup(os.remove, self.paths.maintenance_flag())
75
76 action = DummyMaintenanceAction(96 action = DummyMaintenanceAction(
77 hookenv=self.hookenv, paths=self.paths)97 hookenv=self.hookenv, paths=self.paths)
7898
79 action()99 action()
80 self.assertTrue(action.executed)100 self.assertTrue(action.executed)
81101
82 def test_run_without_maintenance_flag(self):102 def test_run_without_maintenance_status(self):
83 """103 """
84 When maintenance flag file is absent, maintenance hooks are no-ops.104 If the workload status isn't 'maintenance', the maintenance
85 """105 action won't be executed.
106 """
107 self.hookenv.status_set("active", "")
86 action = DummyMaintenanceAction(108 action = DummyMaintenanceAction(
87 hookenv=self.hookenv, paths=self.paths)109 hookenv=self.hookenv, paths=self.paths)
88110
89 action()111 action()
90 self.assertFalse(action.executed)112 self.assertFalse(action.executed)
91 self.assertEqual(113 self.assertEqual(
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 "
115 "'maintenance'."],
93 self.hookenv.action_fails)116 self.hookenv.action_fails)
94117
=== modified file 'lib/tests/test_migrate_schema.py'
--- lib/tests/test_migrate_schema.py 2015-06-01 10:39:56 +0000
+++ lib/tests/test_migrate_schema.py 2015-12-15 12:32:20 +0000
@@ -1,5 +1,3 @@
1import os
2
3from lib.migrate_schema import MigrateSchemaAction1from lib.migrate_schema import MigrateSchemaAction
4from lib.paths import SCHEMA_SCRIPT2from lib.paths import SCHEMA_SCRIPT
5from lib.tests.helpers import HookenvTest3from lib.tests.helpers import HookenvTest
@@ -20,8 +18,7 @@
20 """18 """
21 The MigrateSchemaAction calls the schema script.19 The MigrateSchemaAction calls the schema script.
22 """20 """
23 open(self.paths.maintenance_flag(), "w")21 self.hookenv.status_set("maintenance", "")
24 self.addCleanup(os.remove, self.paths.maintenance_flag())
2522
26 action = MigrateSchemaAction(23 action = MigrateSchemaAction(
27 hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess)24 hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess)
@@ -30,8 +27,11 @@
3027
31 def test_run_without_maintenance_flag(self):28 def test_run_without_maintenance_flag(self):
32 """29 """
33 The MigrateSchemaAction calls the schema script.30 The MigrateSchemaAction doesn't call the schema script if the
31 unit is in an 'active' state.
34 """32 """
33 self.hookenv.status_set("active", "")
34
35 action = MigrateSchemaAction(35 action = MigrateSchemaAction(
36 hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess)36 hookenv=self.hookenv, paths=self.paths, subprocess=self.subprocess)
37 action()37 action()
3838
=== modified file 'lib/tests/test_pause.py'
--- lib/tests/test_pause.py 2015-06-01 08:57:04 +0000
+++ lib/tests/test_pause.py 2015-12-15 12:32:20 +0000
@@ -21,3 +21,17 @@
21 self.action()21 self.action()
22 self.assertEqual(22 self.assertEqual(
23 [(("/usr/bin/lsctl", "stop"), {})], self.subprocess.calls)23 [(("/usr/bin/lsctl", "stop"), {})], self.subprocess.calls)
24
25 def test_run_status(self):
26 """
27 The workload status is changed to 'maintenance' while stopping
28 the services and after the services have been stopped.
29 """
30 self.action()
31 self.assertEqual(
32 ("maintenance", "Services stopped."), self.hookenv.status_get())
33 self.assertEqual(
34 [{"status": "unknown", "message": ""},
35 {"status": "maintenance", "message": "Stopping services."},
36 {"status": "maintenance", "message": "Services stopped."}],
37 self.hookenv.statuses)
2438
=== modified file 'lib/tests/test_resume.py'
--- lib/tests/test_resume.py 2015-06-16 07:51:03 +0000
+++ lib/tests/test_resume.py 2015-12-15 12:32:20 +0000
@@ -1,5 +1,3 @@
1import os
2
3from lib.tests.helpers import HookenvTest1from lib.tests.helpers import HookenvTest
4from lib.tests.rootdir import RootDir2from lib.tests.rootdir import RootDir
5from lib.tests.stubs import SubprocessStub3from lib.tests.stubs import SubprocessStub
@@ -23,8 +21,7 @@
23 enabling the cron service so that Landscape cron jobs can start21 enabling the cron service so that Landscape cron jobs can start
24 running again.22 running again.
25 """23 """
26 open(self.paths.maintenance_flag(), "w")24 self.hookenv.status_set("maintenance", "")
27 self.addCleanup(os.remove, self.paths.maintenance_flag())
2825
29 action = ResumeAction(26 action = ResumeAction(
30 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)27 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)
@@ -34,10 +31,29 @@
34 (("/usr/bin/lsctl", "status"), {})],31 (("/usr/bin/lsctl", "status"), {})],
35 self.subprocess.calls)32 self.subprocess.calls)
3633
34 def test_run_status(self):
35 """
36 The status is set to 'maintenance' while starting the services
37 and is set to 'active' after the services have been started.
38 """
39 self.hookenv.status_set("maintenance", "")
40
41 action = ResumeAction(
42 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)
43 action()
44 self.assertEqual(("active", ""), self.hookenv.status_get())
45 self.assertEqual(
46 [{"status": "unknown", "message": ""},
47 {"status": "maintenance", "message": ""},
48 {"status": "maintenance", "message": "Starting services."},
49 {"status": "active", "message": ""}],
50 self.hookenv.statuses)
51
37 def test_run_without_maintenance_flag(self):52 def test_run_without_maintenance_flag(self):
38 """53 """
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.
40 """55 """
56 self.hookenv.status_set("active", "")
41 action = ResumeAction(57 action = ResumeAction(
42 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)58 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)
43 action()59 action()
@@ -53,17 +69,58 @@
53 The unit is stopped again, to ensure that the 'resume' action69 The unit is stopped again, to ensure that the 'resume' action
54 can run again after the problems have been addressed.70 can run again after the problems have been addressed.
55 """71 """
56 open(self.paths.maintenance_flag(), "w")72 self.hookenv.status_set("maintenance", "")
57 self.addCleanup(os.remove, self.paths.maintenance_flag())73
58 self.subprocess.add_fake_executable(74 self.subprocess.add_fake_executable(
59 LSCTL, args=["start"], stdout="start output")75 LSCTL, args=["start"], stdout="start output")
60 self.subprocess.add_fake_executable(76 self.subprocess.add_fake_executable(
61 LSCTL, args=["status"], stdout="status failure", return_code=3)77 LSCTL, args=["status"], stdout="status failure", return_code=3)
62 self.subprocess.add_fake_executable(LSCTL, args=["stop"])78 self.subprocess.add_fake_executable(LSCTL, args=["stop"])
6379
64 action = ResumeAction(80 action = ResumeAction(
65 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)81 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)
66 action()82 action()
83 self.assertEqual(
84 ["Some services failed to start.\n\nstart output\n\n"
85 "status failure"],
86 self.hookenv.action_fails)
87 self.assertEqual(
88 [(("/usr/bin/lsctl", "start"), {}),
89 (("/usr/bin/lsctl", "status"), {}),
90 (("/usr/bin/lsctl", "stop"), {})],
91 self.subprocess.calls)
92
93 def test_run_fail_status(self):
94 """
95 If the action fails, the final status is put back to
96 maintenance, with a message saying that services failed to
97 start.
98
99 XXX: The final status probably should be 'error', but it's not
100 clear on how to recover from that.
101 """
102 self.hookenv.status_set("maintenance", "")
103
104 self.subprocess.add_fake_executable(
105 LSCTL, args=["start"], stdout="start output")
106 self.subprocess.add_fake_executable(
107 LSCTL, args=["status"], stdout="status failure", return_code=3)
108 self.subprocess.add_fake_executable(LSCTL, args=["stop"])
109
110 action = ResumeAction(
111 hookenv=self.hookenv, subprocess=self.subprocess, paths=self.paths)
112 action()
113 self.assertEqual(
114 ("maintenance", "Services failed to start."),
115 self.hookenv.status_get())
116 self.assertEqual(
117 [{"status": "unknown", "message": ""},
118 {"status": "maintenance", "message": ""},
119 {"status": "maintenance", "message": "Starting services."},
120 {"status": "maintenance", "message": "Stopping services."},
121 {"status": "maintenance",
122 "message": "Services failed to start."}],
123 self.hookenv.statuses)
67 self.assertEqual(124 self.assertEqual(
68 ["Some services failed to start.\n\nstart output\n\n"125 ["Some services failed to start.\n\nstart output\n\n"
69 "status failure"],126 "status failure"],
70127
=== modified file 'lib/tests/test_upgrade.py'
--- lib/tests/test_upgrade.py 2015-11-03 13:58:50 +0000
+++ lib/tests/test_upgrade.py 2015-12-15 12:32:20 +0000
@@ -1,5 +1,3 @@
1import os
2
3from lib.tests.helpers import HookenvTest1from lib.tests.helpers import HookenvTest
4from lib.tests.rootdir import RootDir2from lib.tests.rootdir import RootDir
5from lib.tests.stubs import FetchStub3from lib.tests.stubs import FetchStub
@@ -19,9 +17,7 @@
19 The UpgradeAction refreshes package indexes and upgrades17 The UpgradeAction refreshes package indexes and upgrades
20 landscape-server package.18 landscape-server package.
21 """19 """
2220 self.hookenv.status_set("maintenance", "")
23 open(self.paths.maintenance_flag(), "w")
24 self.addCleanup(os.remove, self.paths.maintenance_flag())
2521
26 self.hookenv.config()["source"] = "ppa:my-ppa"22 self.hookenv.config()["source"] = "ppa:my-ppa"
27 action = UpgradeAction(23 action = UpgradeAction(
@@ -41,8 +37,10 @@
4137
42 def test_run_without_maintenance_flag(self):38 def test_run_without_maintenance_flag(self):
43 """39 """
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
41 action is a no-op.
45 """42 """
43 self.hookenv.status_set("active", "")
4644
47 action = UpgradeAction(45 action = UpgradeAction(
48 hookenv=self.hookenv, fetch=self.fetch, paths=self.paths)46 hookenv=self.hookenv, fetch=self.fetch, paths=self.paths)
4947
=== modified file 'tests/basic/test_actions.py'
--- tests/basic/test_actions.py 2015-07-09 11:17:22 +0000
+++ tests/basic/test_actions.py 2015-12-15 12:32:20 +0000
@@ -3,7 +3,7 @@
3"""3"""
44
5from helpers import IntegrationTest5from helpers import IntegrationTest
6from layers import OneLandscapeUnitLayer6from layers import OneLandscapeUnitLayer, TwoLandscapeUnitsLayer
77
88
9class ActionsTest(IntegrationTest):9class ActionsTest(IntegrationTest):
@@ -60,3 +60,22 @@
6060
61 # Logging in should now work.61 # Logging in should now work.
62 self.environment.login("foo@bar", "bar")62 self.environment.login("foo@bar", "bar")
63
64
65class ActionsMultipleUnitsTest(IntegrationTest):
66
67 layer = TwoLandscapeUnitsLayer
68
69 def setUp(self):
70 super(ActionsMultipleUnitsTest, self).setUp()
71 [self.non_leader] = self.layer.non_leaders
72
73 def test_non_leader_pause_resume(self):
74 """
75 The non-leader unit can be paused and later resumed.
76 """
77 result = self.environment.pause_landscape(unit=self.non_leader)
78 self.assertEqual("completed", result["status"])
79
80 result = self.environment.resume_landscape(unit=self.non_leader)
81 self.assertEqual("completed", result["status"])

Subscribers

People subscribed via source and target branches