Add pause and resume actions with unit tests

This commit is contained in:
Adam Collard 2015-09-07 16:55:10 +01:00
parent 4c186cb5b8
commit 57a90e72ac
6 changed files with 300 additions and 0 deletions

11
actions.yaml Normal file
View 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
View File

83
actions/actions.py Executable file
View 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
View File

@ -0,0 +1 @@
actions.py

1
actions/resume Symbolic link
View File

@ -0,0 +1 @@
actions.py

204
unit_tests/test_actions.py Normal file
View 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"])