Gitweb:
http://git.fedorahosted.org/git/?p=fence-agents.git;a=commitdiff;h=d88728...
Commit: d8872849a6a45537ffe70c5643187f3753e19ddd
Parent: 710a56b931e6431526419d13cfb095d691a6cf84
Author: Marek 'marx' Grac <mgrac(a)redhat.com>
AuthorDate: Fri Nov 23 15:42:51 2012 +0100
Committer: Marek 'marx' Grac <mgrac(a)redhat.com>
CommitterDate: Thu Feb 7 21:46:35 2013 +0100
fencing: Testing tool and dummy fence agent
New testing tool for repeating sequences of action on given devices is ready. It should
improve testing process of fence agents and simplify it. So more volunteers can test
their
devices.
New fence agent "dummy" is used for testing fencing library as it does not
require
any hardware/software.
---
configure.ac | 1 +
fence/agents/dummy/Makefile.am | 17 ++++
fence/agents/dummy/fence_dummy.py | 59 +++++++++++++
tests/actions.d/power-on-off.cfg | 2 +
tests/actions.d/sleep.cfg | 2 +
tests/actions.d/status.cfg | 2 +
tests/devices.d/dummy-with_action.cfg | 9 ++
tests/devices.d/dummy.cfg | 3 +
tests/devices.d/invalid-missing_option.cfg | 4 +
tests/devices.d/true-with_action.cfg | 8 ++
tests/devices.d/true.cfg | 8 ++
tests/fence_testing.py | 122 ++++++++++++++++++++++++++++
tests/fence_testing_test.py | 70 ++++++++++++++++
tests/test.py | 21 +++++
14 files changed, 328 insertions(+), 0 deletions(-)
diff --git a/configure.ac b/configure.ac
index e30581c..de4124d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -258,6 +258,7 @@ AC_CONFIG_FILES([Makefile
fence/agents/cpint/Makefile
fence/agents/drac/Makefile
fence/agents/drac5/Makefile
+ fence/agents/dummy/Makefile
fence/agents/eaton_snmp/Makefile
fence/agents/egenera/Makefile
fence/agents/eps/Makefile
diff --git a/fence/agents/dummy/Makefile.am b/fence/agents/dummy/Makefile.am
new file mode 100644
index 0000000..11405eb
--- /dev/null
+++ b/fence/agents/dummy/Makefile.am
@@ -0,0 +1,17 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+TARGET = fence_dummy
+
+SRC = $(TARGET).py
+
+EXTRA_DIST = $(SRC)
+
+sbin_SCRIPTS = $(TARGET)
+
+man_MANS = $(TARGET).8
+
+include $(top_srcdir)/make/fencebuild.mk
+include $(top_srcdir)/make/fenceman.mk
+
+clean-local: clean-man
+ rm -f $(TARGET)
diff --git a/fence/agents/dummy/fence_dummy.py b/fence/agents/dummy/fence_dummy.py
new file mode 100644
index 0000000..ccf7f20
--- /dev/null
+++ b/fence/agents/dummy/fence_dummy.py
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+import sys, re, pexpect, exceptions
+sys.path.append("@FENCEAGENTSLIBDIR@")
+from fencing import *
+
+#BEGIN_VERSION_GENERATION
+RELEASE_VERSION="New Dummy Agent - test release on steroids"
+REDHAT_COPYRIGHT=""
+BUILD_DATE=""
+#END_VERSION_GENERATION
+
+def get_power_status(conn, options):
+ try:
+ status_file = open(options["--status-file"], 'r')
+ except:
+ return "off"
+
+ status = status_file.read()
+ status_file.close()
+
+ return status.lower()
+
+def set_power_status(conn, options):
+ if not (options["--action"] in [ "on", "off" ]):
+ return
+
+ status_file = open(options["--status-file"], 'w')
+ status_file.write(options["--action"])
+ status_file.close()
+
+def main():
+ device_opt = [ "no_password", "status_file" ]
+
+ atexit.register(atexit_handler)
+
+ all_opt["status_file"] = {
+ "getopt" : "s:",
+ "longopt" : "status-file",
+ "help":"--status-file=<file> Name of file that holds
current status",
+ "required" : "0",
+ "shortdesc" : "File with status",
+ "default" : "/tmp/fence_dummy.status",
+ "order": 1
+ }
+
+ options = check_input(device_opt, process_input(device_opt))
+
+ docs = { }
+ docs["shortdesc"] = "Dummy fence agent"
+ docs["longdesc"] = "fence_dummy"
+ docs["vendorurl"] = "http://www.example.com"
+ show_docs(options, docs)
+
+ result = fence_action(None, options, set_power_status, get_power_status, None)
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/actions.d/power-on-off.cfg b/tests/actions.d/power-on-off.cfg
new file mode 100644
index 0000000..d9df57a
--- /dev/null
+++ b/tests/actions.d/power-on-off.cfg
@@ -0,0 +1,2 @@
+name = "Power ON & OFF"
+actions = [ { "command" : "on", "return_code" :
"^0$" }, { "command" : "status", "return_code" :
"^0$" }, { "command" : "off", "return_code" :
"^0$" }, { "command" : "status", "return_code" :
"^2$" } ]
diff --git a/tests/actions.d/sleep.cfg b/tests/actions.d/sleep.cfg
new file mode 100644
index 0000000..c0fad72
--- /dev/null
+++ b/tests/actions.d/sleep.cfg
@@ -0,0 +1,2 @@
+name = "Pure Sleep"
+actions = [ { "command" : "sleep(1)", "return_code" :
"^0$" }, { "command" : "sleep(3)", "return_code" :
"^0$" }, { "command" : "sleep(5)", "return_code" :
"^0$" } ]
diff --git a/tests/actions.d/status.cfg b/tests/actions.d/status.cfg
new file mode 100644
index 0000000..760f94b
--- /dev/null
+++ b/tests/actions.d/status.cfg
@@ -0,0 +1,2 @@
+name = "Simple Status"
+actions = [ { "command" : "status", "return_code" :
"^[02]$" }, { "command" : "sleep(1)",
"return_code" : "^0$" } ]
diff --git a/tests/devices.d/dummy-with_action.cfg
b/tests/devices.d/dummy-with_action.cfg
new file mode 100644
index 0000000..5550c36
--- /dev/null
+++ b/tests/devices.d/dummy-with_action.cfg
@@ -0,0 +1,9 @@
+name = "Dummy fence device configuration"
+agent = "/bin/true"
+[options]
+ login = "foo", "--username", "-l"
+ passwd = "bar", "--password", "-p"
+ ipaddr = "fence.example.com", "--ip", "-a"
+ port = "1", "--plug"
+ action = "status", "--action", "-o"
+
diff --git a/tests/devices.d/dummy.cfg b/tests/devices.d/dummy.cfg
new file mode 100644
index 0000000..d912475
--- /dev/null
+++ b/tests/devices.d/dummy.cfg
@@ -0,0 +1,3 @@
+name = "Dummy fence device configuration"
+agent = "/home/marx/GIT/fence-agents/fence/agents/dummy/fence_dummy"
+[options]
diff --git a/tests/devices.d/invalid-missing_option.cfg
b/tests/devices.d/invalid-missing_option.cfg
new file mode 100644
index 0000000..0805fdc
--- /dev/null
+++ b/tests/devices.d/invalid-missing_option.cfg
@@ -0,0 +1,4 @@
+name = "Invalid device definition: Both short and long options are missing"
+agent = "/bin/true"
+[options]
+ login = "foo"
diff --git a/tests/devices.d/true-with_action.cfg b/tests/devices.d/true-with_action.cfg
new file mode 100644
index 0000000..d95c9fe
--- /dev/null
+++ b/tests/devices.d/true-with_action.cfg
@@ -0,0 +1,8 @@
+name = "Dummy fence device configuration"
+agent = "/bin/true"
+[options]
+ login = [ "foo", "--username", "-l" ]
+ passwd = [ "bar", "--password", "-p" ]
+ ipaddr = [ "fence.example.com", "--ip", "-a" ]
+ port = [ "1", "--plug" ]
+ action = [ "status", "--action", "-o" ]
diff --git a/tests/devices.d/true.cfg b/tests/devices.d/true.cfg
new file mode 100644
index 0000000..4ba0aa5
--- /dev/null
+++ b/tests/devices.d/true.cfg
@@ -0,0 +1,8 @@
+name = "Dummy fence device configuration"
+agent = "/bin/true"
+[options]
+ login = [ "foo", "--username", "-l" ]
+ passwd = [ "bar", "--password", "-p" ]
+ ipaddr = [ "fence.example.com", "--ip", "-a" ]
+ port = [ "1", "--plug" ]
+
diff --git a/tests/fence_testing.py b/tests/fence_testing.py
new file mode 100755
index 0000000..566872e
--- /dev/null
+++ b/tests/fence_testing.py
@@ -0,0 +1,122 @@
+""" Library for fence agents testing via predefined scenarios
"""
+from configobj import ConfigObj
+import re, sys, os
+
+EC_CONFIG_FAIL = 1
+
+def _prepare_command(agent_file, method):
+ """ Parse configuration of fence device and prepare (command + STDIN
values) to execute.
+
+ Fence device configuration is used to generate a command which can be executed.
+ Because fence agents supports several options how to enter data, we can select
+ from three different methods ("stdin", "getopt" - short options,
"longopt").
+ When method "stdin" is used then this function will generate also text which
should
+ be entered on STDIN instead of command itself.
+
+ Example of agent definition:
+ name = "Dummy fence device configuration"
+ agent = "/bin/true"
+ [options]
+ login = [ "foo", "--username", "-l" ]
+ passwd = [ "bar", "--password", "-p" ]
+ ipaddr = [ "fence.example.com", "--ip", "-a" ]
+ port = [ "1", "--plug" ]
+ """
+ assert (method in ["stdin", "getopt", "longopt"]),
"Invalid method entered"
+
+ config = ConfigObj(agent_file, unrepr = True)
+
+ assert (config.has_key("agent")), "Fence agent has to be defined"
+ final_command = config["agent"]
+ stdin_values = None
+
+ for opt in config["options"].keys():
+ assert isinstance(config["options"][opt], list), "Option %s have to have
at least value and longopt"% (opt)
+ assert len(config["options"][opt]) >= 2, "Option %s have to have at
least value and longopt"% (opt)
+ value = config["options"][opt][0]
+ if opt == "action":
+ ## ignore action as it is not part of fence device definition
+ continue
+
+ if method == "stdin":
+ option = opt
+ if stdin_values == None:
+ stdin_values = ""
+ stdin_values += option + "=" + value + "\n"
+ elif method == "longopt":
+ option = config["options"][opt][1]
+ final_command += " " + option + " " + value
+ elif method == "getopt":
+ if len(config["options"][opt]) == (2 + 1):
+ option = config["options"][opt][2]
+ else:
+ option = config["options"][opt][1]
+ final_command += " " + option + " " + value
+
+ return (final_command, stdin_values)
+
+def test_action(agent, action_file, method):
+ """ Run defined sequence of actions on a given fence agent.
+
+ This function will run one set of test on a fence agent. Test itself consists of
+ sequence of action and expected return codes. User can select from actions supported
+ by fence agent (on, off, reboot, list, status, monitor) and sleep(X) command where X
+ is in seconds and determine the length of pause between commands. Each action has to
+ have defined regular expression which define acceptable return codes from fence agent
+ or sleep.
+
+ Example of action configuration file:
+ name = "Simple Status"
+ actions = [ { "command" : "status", "return_code" :
"^[02]$" }, { "command" : "sleep(1)",
"return_code" : "^0$" } ]
+ """
+ re_sleep_command = re.compile('sleep\(([0-9]+)\)', re.IGNORECASE)
+ config = ConfigObj(action_file, unrepr = True)
+
+ (command, stdin_options) = _prepare_command(agent, method)
+
+ for action in config["actions"]:
+ assert action.has_key("command"), "Action %s need to have defined
'command'"% (action_file)
+ assert action.has_key("return_code"), "Command %s (in %s) need to have
'return_code' defined"% (action_file, action["command"])
+
+ sleep_wait = None
+ current_command = None
+ current_stdin_options = None
+
+ if not (action["command"] in [ "status", "reboot",
"on", "off", "list", "monitor" ]):
+ is_sleep = re.search(re_sleep_command, action["command"])
+ if is_sleep != None:
+ sleep_wait = is_sleep.group(1)
+ else:
+ sys.stderr.write("ERROR: %s contains unsupported action
\"%s\"\n"% (action_file, action["command"]))
+ sys.exit(1)
+
+ if sleep_wait != None:
+ current_command = "/bin/sleep " + sleep_wait
+ current_stdin_options = None
+ else:
+ current_command = command
+ current_stdin_options = stdin_options
+
+ if method == "stdin":
+ if current_stdin_options == None:
+ current_stdin_options = ""
+ current_stdin_options += "action=%s"% (action["command"])
+ elif method == "longopt":
+ current_command += " --action=%s"% (action["command"])
+ elif method == "getopt":
+ current_command += " -o %s"% (action["command"])
+
+ # @note: Broken pipe can occur here and I'm not sure why - non-deterministic
+ if method == "stdin" and sleep_wait == None:
+ current_command = "/bin/echo -e \"" + current_stdin_options +
"\" | " + current_command
+
+ result = os.system(current_command + " &> /dev/null")
+ exitcode = (result >> 8) & 0xFF
+
+ is_valid_result_code = re.search(action["return_code"], str(exitcode),
re.IGNORECASE)
+
+ if is_valid_result_code == None:
+ print "TEST FAILED: %s failed on %s when using (%s)\n"% (agent, action_file,
method)
+ print "TEST INFO: %s returns %s\n"% (action["command"],
str(exitcode))
+ return
+ print "TEST PASSED: %s worked on %s (%s)\n"% (agent, action_file, method)
diff --git a/tests/fence_testing_test.py b/tests/fence_testing_test.py
new file mode 100755
index 0000000..368ff70
--- /dev/null
+++ b/tests/fence_testing_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+import unittest
+import fence_testing
+
+class TestPrepareCommand(unittest.TestCase):
+ DEVICE_MISSING_OPTION = "devices.d/invalid-missing_option.cfg"
+ DEVICE_CORRECT = "devices.d/true.cfg"
+ DEVICE_CORRECT_WITH_ACTION = "devices.d/true-with_action.cfg"
+
+ def test_missing_device(self):
+ self.assertRaises(fence_testing._prepare_command, None, "getopt")
+
+ def test_missing_option(self):
+ self.assertRaises(AssertionError, fence_testing._prepare_command,
self.DEVICE_MISSING_OPTION, "stdin")
+
+ def test_valid_methods(self):
+ fence_testing._prepare_command(self.DEVICE_CORRECT, "getopt")
+ fence_testing._prepare_command(self.DEVICE_CORRECT, "longopt")
+ fence_testing._prepare_command(self.DEVICE_CORRECT, "stdin")
+
+ def test_invalid_method(self):
+ self.assertRaises(AssertionError, fence_testing._prepare_command, self.DEVICE_CORRECT,
"invalid")
+
+ def test_is_action_ignored(self):
+ (command1, _) = fence_testing._prepare_command(self.DEVICE_CORRECT,
"getopt")
+ (command2, _) = fence_testing._prepare_command(self.DEVICE_CORRECT_WITH_ACTION,
"getopt")
+ self.assertEquals(command1, command2)
+
+ def test_is_stdin_empty(self):
+ (_, stdin) = fence_testing._prepare_command(self.DEVICE_CORRECT, "getopt")
+ self.assertEquals(None, stdin)
+ (_, stdin) = fence_testing._prepare_command(self.DEVICE_CORRECT, "longopt")
+ self.assertEquals(None, stdin)
+
+ def test_prepared_command_getopt(self):
+ ## Test also fallback to longopt if short is not present
+ (command, _) = fence_testing._prepare_command(self.DEVICE_CORRECT, "getopt")
+ self.assertEquals("/bin/true -l foo -p bar -a
fence.example.com --plug 1",
command)
+
+ def test_prepared_command_longopt(self):
+ (command, _) = fence_testing._prepare_command(self.DEVICE_CORRECT,
"longopt")
+ self.assertEquals("/bin/true --username foo --password bar --ip
fence.example.com
--plug 1", command)
+
+ def test_prepared_command_stdin(self):
+ (command, stdin) = fence_testing._prepare_command(self.DEVICE_CORRECT,
"stdin")
+ self.assertEquals("/bin/true", command)
+ self.assertEquals("login=foo\npasswd=bar\nipaddr=fence.example.com\nport=1\n",
stdin)
+
+class TestTestAction(unittest.TestCase):
+ def test_valid_actions(self):
+ pass
+
+ def test_invalid_actions(self):
+ pass
+
+ def test_valid_return_code(self):
+ pass
+
+ def test_invalid_return_code(self):
+ pass
+
+ def test_valid_re_contains(self):
+ pass
+
+ def test_invalid_re_contains(self):
+ pass
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test.py b/tests/test.py
new file mode 100755
index 0000000..8e82ed9
--- /dev/null
+++ b/tests/test.py
@@ -0,0 +1,21 @@
+#!/usr/bin/python
+
+from fence_testing import test_action
+
+def main():
+ ## @todo: utility1 - run single 'agent' 'action' 'method'
+ ## @todo: utility2 - run complex tests (using utility1?) -> file with test suites
+
+ AGENTDEF = "devices.d/true.cfg"
+ DUMMYDEF = "devices.d/dummy.cfg"
+
+ ACT_STATUS = "actions.d/status.cfg"
+ ACT_ONOFF = "actions.d/power-on-off.cfg"
+
+# test_action(AGENTDEF, ACTIONDEF, "stdin")
+# test_action(AGENTDEF, ACTIONDEF, "getopt")
+ test_action(DUMMYDEF, ACT_STATUS, "getopt")
+ test_action(DUMMYDEF, ACT_ONOFF, "getopt")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file