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