summaryrefslogtreecommitdiffstats
path: root/tests/fence_testing.py
blob: 14cfe0cc41ee4b6c0194d45986d0b7a18e5e1feb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
""" 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 ("agent" in config), "Fence agent has to be defined"
	final_command = config["agent"]
	stdin_values = None

	for opt in list(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, verbose = False):
	""" 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 "command" in action, "Action %s need to have defined 'command'"% (action_file)
		assert "return_code" in action, "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 = "printf \"" + current_stdin_options + "\" | " + current_command

		if verbose == False:
			result = os.system(current_command + " &> /dev/null")
		else:
			print(current_command)
			result = os.system(current_command)
		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)))