Add pause and resume actions with unit tests
This commit is contained in:
parent
f6307cbcad
commit
644d54f1c6
11
actions.yaml
Normal file
11
actions.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
pause:
|
||||
description: |
|
||||
Pause swift-proxy services.
|
||||
If the swift-proxy deployment is clustered using the hacluster charm, the
|
||||
corresponding hacluster unit on the node must first be paused as well.
|
||||
Not doing so may lead to an interruption of service.
|
||||
resume:
|
||||
description: |
|
||||
Resume swift-proxy services.
|
||||
If the swift-proxy deployment is clustered using the hacluster charm, the
|
||||
corresponding hacluster unit on the node must be resumed as well.
|
0
actions/__init__.py
Normal file
0
actions/__init__.py
Normal file
83
actions/actions.py
Executable file
83
actions/actions.py
Executable file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from charmhelpers.core.host import service_pause, service_resume
|
||||
from charmhelpers.core.hookenv import action_fail, status_set
|
||||
|
||||
from lib.swift_utils import services
|
||||
|
||||
|
||||
def get_action_parser(actions_yaml_path, action_name,
|
||||
get_services=services):
|
||||
"""Make an argparse.ArgumentParser seeded from actions.yaml definitions."""
|
||||
with open(actions_yaml_path) as fh:
|
||||
doc = yaml.load(fh)[action_name]["description"]
|
||||
parser = argparse.ArgumentParser(description=doc)
|
||||
parser.add_argument("--services", default=get_services())
|
||||
# TODO: Add arguments for params defined in the actions.yaml
|
||||
return parser
|
||||
|
||||
|
||||
def pause(args):
|
||||
"""Pause all the swift services.
|
||||
|
||||
@raises Exception if any services fail to stop
|
||||
"""
|
||||
for service in args.services:
|
||||
stopped = service_pause(service)
|
||||
if not stopped:
|
||||
raise Exception("{} didn't stop cleanly.".format(service))
|
||||
status_set(
|
||||
"maintenance", "Paused. Use 'resume' action to resume normal service.")
|
||||
|
||||
|
||||
def resume(args):
|
||||
"""Resume all the swift services.
|
||||
|
||||
@raises Exception if any services fail to start
|
||||
"""
|
||||
for service in args.services:
|
||||
started = service_resume(service)
|
||||
if not started:
|
||||
raise Exception("{} didn't start cleanly.".format(service))
|
||||
status_set("active", "")
|
||||
|
||||
|
||||
# A dictionary of all the defined actions to callables (which take
|
||||
# parsed arguments).
|
||||
ACTIONS = {"pause": pause, "resume": resume}
|
||||
|
||||
|
||||
def main(argv):
|
||||
action_name = _get_action_name()
|
||||
actions_yaml_path = _get_actions_yaml_path()
|
||||
parser = get_action_parser(actions_yaml_path, action_name)
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
action = ACTIONS[action_name]
|
||||
except KeyError:
|
||||
return "Action %s undefined" % action_name
|
||||
else:
|
||||
try:
|
||||
action(args)
|
||||
except Exception as e:
|
||||
action_fail(str(e))
|
||||
|
||||
|
||||
def _get_action_name():
|
||||
"""Return the name of the action."""
|
||||
return os.path.basename(__file__)
|
||||
|
||||
|
||||
def _get_actions_yaml_path():
|
||||
"""Return the path to actions.yaml"""
|
||||
cwd = os.path.dirname(__file__)
|
||||
return os.path.join(cwd, "..", "actions.yaml")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
1
actions/pause
Symbolic link
1
actions/pause
Symbolic link
@ -0,0 +1 @@
|
||||
actions.py
|
1
actions/resume
Symbolic link
1
actions/resume
Symbolic link
@ -0,0 +1 @@
|
||||
actions.py
|
204
unit_tests/test_actions.py
Normal file
204
unit_tests/test_actions.py
Normal file
@ -0,0 +1,204 @@
|
||||
import argparse
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
import actions.actions
|
||||
|
||||
|
||||
class CharmTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self, obj, patches):
|
||||
super(CharmTestCase, self).setUp()
|
||||
self.patches = patches
|
||||
self.obj = obj
|
||||
self.patch_all()
|
||||
|
||||
def patch(self, method):
|
||||
_m = mock.patch.object(self.obj, method)
|
||||
mocked = _m.start()
|
||||
self.addCleanup(_m.stop)
|
||||
return mocked
|
||||
|
||||
def patch_all(self):
|
||||
for method in self.patches:
|
||||
setattr(self, method, self.patch(method))
|
||||
|
||||
|
||||
class PauseTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PauseTestCase, self).setUp(
|
||||
actions.actions, ["service_pause", "status_set"])
|
||||
|
||||
class FakeArgs(object):
|
||||
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
||||
self.args = FakeArgs()
|
||||
|
||||
def test_pauses_services(self):
|
||||
"""Pause action pauses all of the Swift services."""
|
||||
pause_calls = []
|
||||
|
||||
def fake_service_pause(svc):
|
||||
pause_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_pause.side_effect = fake_service_pause
|
||||
|
||||
actions.actions.pause(self.args)
|
||||
self.assertEqual(
|
||||
pause_calls, ['swift-proxy', 'haproxy', 'memcached', 'apache2'])
|
||||
|
||||
def test_bails_out_early_on_error(self):
|
||||
"""Pause action fails early if there are errors stopping a service."""
|
||||
pause_calls = []
|
||||
|
||||
def maybe_kill(svc):
|
||||
if svc == "haproxy":
|
||||
return False
|
||||
else:
|
||||
pause_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_pause.side_effect = maybe_kill
|
||||
self.assertRaisesRegexp(
|
||||
Exception, "haproxy didn't stop cleanly.",
|
||||
actions.actions.pause, self.args)
|
||||
self.assertEqual(pause_calls, ["swift-proxy"])
|
||||
|
||||
def test_status_mode(self):
|
||||
"""Pause action sets the status to maintenance."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
state)
|
||||
|
||||
actions.actions.pause(self.args)
|
||||
self.assertEqual(status_calls, ["maintenance"])
|
||||
|
||||
def test_status_message(self):
|
||||
"""Pause action sets a status message reflecting that it's paused."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
msg)
|
||||
|
||||
actions.actions.pause(self.args)
|
||||
self.assertEqual(
|
||||
status_calls, ["Paused. "
|
||||
"Use 'resume' action to resume normal service."])
|
||||
|
||||
|
||||
class ResumeTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ResumeTestCase, self).setUp(
|
||||
actions.actions, ["service_resume", "status_set"])
|
||||
|
||||
class FakeArgs(object):
|
||||
services = ['swift-proxy', 'haproxy', 'memcached', 'apache2']
|
||||
self.args = FakeArgs()
|
||||
|
||||
def test_resumes_services(self):
|
||||
"""Resume action resumes all of the Swift services."""
|
||||
resume_calls = []
|
||||
|
||||
def fake_service_resume(svc):
|
||||
resume_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_resume.side_effect = fake_service_resume
|
||||
actions.actions.resume(self.args)
|
||||
self.assertEqual(
|
||||
resume_calls, ['swift-proxy', 'haproxy', 'memcached', 'apache2'])
|
||||
|
||||
def test_bails_out_early_on_error(self):
|
||||
"""Resume action fails early if there are errors starting a service."""
|
||||
resume_calls = []
|
||||
|
||||
def maybe_kill(svc):
|
||||
if svc == "haproxy":
|
||||
return False
|
||||
else:
|
||||
resume_calls.append(svc)
|
||||
return True
|
||||
|
||||
self.service_resume.side_effect = maybe_kill
|
||||
self.assertRaisesRegexp(
|
||||
Exception, "haproxy didn't start cleanly.",
|
||||
actions.actions.resume, self.args)
|
||||
self.assertEqual(resume_calls, ['swift-proxy'])
|
||||
|
||||
def test_status_mode(self):
|
||||
"""Resume action sets the status to maintenance."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
state)
|
||||
|
||||
actions.actions.resume(self.args)
|
||||
self.assertEqual(status_calls, ["active"])
|
||||
|
||||
def test_status_message(self):
|
||||
"""Resume action sets an empty status message."""
|
||||
status_calls = []
|
||||
self.status_set.side_effect = lambda state, msg: status_calls.append(
|
||||
msg)
|
||||
|
||||
actions.actions.resume(self.args)
|
||||
self.assertEqual(status_calls, [""])
|
||||
|
||||
|
||||
class GetActionParserTestCase(unittest.TestCase):
|
||||
|
||||
def test_definition_from_yaml(self):
|
||||
"""ArgumentParser is seeded from actions.yaml."""
|
||||
actions_yaml = tempfile.NamedTemporaryFile(
|
||||
prefix="GetActionParserTestCase", suffix="yaml")
|
||||
actions_yaml.write(yaml.dump({"foo": {"description": "Foo is bar"}}))
|
||||
actions_yaml.seek(0)
|
||||
parser = actions.actions.get_action_parser(actions_yaml.name, "foo",
|
||||
get_services=lambda: [])
|
||||
self.assertEqual(parser.description, 'Foo is bar')
|
||||
|
||||
|
||||
class MainTestCase(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MainTestCase, self).setUp(
|
||||
actions.actions, ["_get_action_name",
|
||||
"get_action_parser",
|
||||
"action_fail"])
|
||||
|
||||
def test_invokes_pause(self):
|
||||
dummy_calls = []
|
||||
|
||||
def dummy_action(args):
|
||||
dummy_calls.append(True)
|
||||
|
||||
self._get_action_name.side_effect = lambda: "foo"
|
||||
self.get_action_parser = lambda: argparse.ArgumentParser()
|
||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
||||
actions.actions.main([])
|
||||
self.assertEqual(dummy_calls, [True])
|
||||
|
||||
def test_unknown_action(self):
|
||||
"""Unknown actions aren't a traceback."""
|
||||
self._get_action_name.side_effect = lambda: "foo"
|
||||
self.get_action_parser = lambda: argparse.ArgumentParser()
|
||||
exit_string = actions.actions.main([])
|
||||
self.assertEqual("Action foo undefined", exit_string)
|
||||
|
||||
def test_failing_action(self):
|
||||
"""Actions which traceback trigger action_fail() calls."""
|
||||
dummy_calls = []
|
||||
|
||||
self.action_fail.side_effect = dummy_calls.append
|
||||
self._get_action_name.side_effect = lambda: "foo"
|
||||
|
||||
def dummy_action(args):
|
||||
raise ValueError("uh oh")
|
||||
|
||||
self.get_action_parser = lambda: argparse.ArgumentParser()
|
||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
||||
actions.actions.main([])
|
||||
self.assertEqual(dummy_calls, ["uh oh"])
|
Loading…
Reference in New Issue
Block a user