1    	#!@PYTHON@ -tt
2    	
3    	import sys
4    	import stat
5    	import re
6    	import os
7    	import time
8    	import logging
9    	import atexit
10   	import ctypes
11   	sys.path.append("@FENCEAGENTSLIBDIR@")
12   	from fencing import fail_usage, run_command, atexit_handler, check_input, process_input, show_docs
13   	from fencing import fence_action, all_opt, run_delay
14   	
15   	def get_status(conn, options):
16   		del conn
17   		status = "off"
18   		for dev in options["devices"]:
19   			is_block_device(dev)
20   			if options["--plug"] in get_registration_keys(options, dev):
21   				status = "on"
22   			else:
23   				logging.debug("No registration for key "\
24   					+ options["--plug"] + " on device " + dev + "\n")
25   	
26   		if options["--action"] == "monitor":
27   			dev_read(options)
28   	
29   		return status
30   	
31   	
32   	def set_status(conn, options):
33   		del conn
34   		count = 0
35   		if options["--action"] == "on":
36   			for dev in options["devices"]:
37   				is_block_device(dev)
38   	
39   				register_dev(options, dev)
40   				if options["--plug"] not in get_registration_keys(options, dev):
41   					count += 1
42   					logging.debug("Failed to register key "\
43   						+ options["--plug"] + " on device " + dev + "\n")
44   					continue
45   				dev_write(options, dev)
46   	
47   				if get_reservation_key(options, dev) is None \
48   				and not reserve_dev(options, dev) \
49   				and get_reservation_key(options, dev) is None:
50   					count += 1
51   					logging.debug("Failed to create reservation (key="\
52   						+ options["--plug"] + ", device=" + dev + ")\n")
53   	
54   		else:
55   			dev_keys = dev_read(options)
56   	
57   			for dev in options["devices"]:
58   				is_block_device(dev)
59   	
60   				if options["--plug"] in get_registration_keys(options, dev):
61   					preempt_abort(options, dev_keys[dev], dev)
62   	
63   			for dev in options["devices"]:
64   				if options["--plug"] in get_registration_keys(options, dev):
65   					count += 1
66   					logging.debug("Failed to remove key "\
67   						+ options["--plug"] + " on device " + dev + "\n")
68   					continue
69   	
70   				if not get_reservation_key(options, dev):
71   					count += 1
72   					logging.debug("No reservation exists on device " + dev + "\n")
73   		if count:
74   			logging.error("Failed to verify " + str(count) + " device(s)")
75   			sys.exit(1)
76   	
77   	
78   	# run command, returns dict, ret["rc"] = exit code; ret["out"] = output;
79   	# ret["err"] = error
80   	def run_cmd(options, cmd):
81   		ret = {}
82   	
83   		if "--use-sudo" in options:
84   			prefix = options["--sudo-path"] + " "
85   		else:
86   			prefix = ""
87   	
88   		(ret["rc"], ret["out"], ret["err"]) = run_command(options,
89   								    prefix + cmd)
90   		ret["out"] = "".join([i for i in ret["out"] if i is not None])
91   		ret["err"] = "".join([i for i in ret["err"] if i is not None])
92   		return ret
93   	
94   	
95   	# check if device exist and is block device
96   	def is_block_device(dev):
97   		if not os.path.exists(dev):
98   			fail_usage("Failed: device \"" + dev + "\" does not exist")
99   		if not stat.S_ISBLK(os.stat(dev).st_mode):
100  			fail_usage("Failed: device \"" + dev + "\" is not a block device")
101  	
102  	# cancel registration
103  	def preempt_abort(options, host, dev):
104  		cmd = options["--mpathpersist-path"] + " -o --preempt-abort --prout-type=5 --param-rk=" + host +" --param-sark=" + options["--plug"] +" -d " + dev
105  		return not bool(run_cmd(options, cmd)["rc"])
106  	
107  	def register_dev(options, dev):
108  		cmd = options["--mpathpersist-path"] + " -o --register --param-sark=" + options["--plug"] + " -d " + dev
109  		#cmd return code != 0 but registration can be successful
110  		return not bool(run_cmd(options, cmd)["rc"])
111  	
112  	def reserve_dev(options, dev):
113  		cmd = options["--mpathpersist-path"] + " -o --reserve --prout-type=5 --param-rk=" + options["--plug"] + " -d " + dev
114  		return not bool(run_cmd(options, cmd)["rc"])
115  	
116  	def get_reservation_key(options, dev):
117  		cmd = options["--mpathpersist-path"] + " -i -r -d " + dev
118  		out = run_cmd(options, cmd)
119  		if out["rc"]:
120  			fail_usage('Cannot get reservation key on device "' + dev
121  	                        + '": ' + out["err"])
122  		match = re.search(r"\s+key\s*=\s*0x(\S+)\s+", out["out"], re.IGNORECASE)
123  		return match.group(1) if match else None
124  	
125  	def get_registration_keys(options, dev, fail=True):
126  		keys = []
127  		cmd = options["--mpathpersist-path"] + " -i -k -d " + dev
128  		out = run_cmd(options, cmd)
129  		if out["rc"]:
130  			fail_usage('Cannot get registration keys on device "' + dev
131  	                        + '": ' + out["err"], fail)
132  			if not fail:
133  				return []
134  		for line in out["out"].split("\n"):
135  			match = re.search(r"\s+0x(\S+)\s*", line)
136  			if match:
137  				keys.append(match.group(1))
138  		return keys
139  	
140  	def dev_write(options, dev):
141  		file_path = options["--store-path"] + "/mpath.devices"
142  	
143  		if not os.path.isdir(options["--store-path"]):
144  			os.makedirs(options["--store-path"])
145  	
146  		try:
147  			store_fh = open(file_path, "a+")
148  		except IOError:
149  			fail_usage("Failed: Cannot open file \""+ file_path + "\"")
150  		store_fh.seek(0)
151  		out = store_fh.read()
152  		if not re.search(r"^{}\s+{}$".format(dev, options["--plug"]), out, flags=re.MULTILINE):
153  			store_fh.write(dev + "\t" + options["--plug"] + "\n")
154  		store_fh.close()
155  	
156  	def dev_read(options, fail=True):
157  		dev_key = {}
158  		file_path = options["--store-path"] + "/mpath.devices"
159  		try:
160  			store_fh = open(file_path, "r")
161  		except IOError:
162  			if fail:
163  				fail_usage("Failed: Cannot open file \"" + file_path + "\"")
164  			else:
165  				return None
166  		# get not empty lines from file
167  		for (device, key) in [line.strip().split() for line in store_fh if line.strip()]:
168  			dev_key[device] = key
169  		store_fh.close()
170  		return dev_key
171  	
172  	def mpath_check_get_options(options):
173  		try:
174  			f = open("/etc/sysconfig/stonith", "r")
175  		except IOError:
176  			return options
177  	
178  		match = re.findall(r"^\s*(\S*)\s*=\s*(\S*)\s*", "".join(f.readlines()), re.MULTILINE)
179  	
180  		for m in match:
181  			options[m[0].lower()] = m[1].lower()
182  	
183  		f.close()
184  	
185  		return options
186  	
187  	def mpath_check(hardreboot=False):
188  		if len(sys.argv) >= 3 and sys.argv[1] == "repair":
189  			return int(sys.argv[2])
190  		options = {}
191  		options["--mpathpersist-path"] = "/usr/sbin/mpathpersist"
192  		options["--store-path"] = "@STORE_PATH@"
193  		options["--power-timeout"] = "5"
194  		options["retry"] = "0"
195  		options["retry-sleep"] = "1"
196  		options = mpath_check_get_options(options)
197  		if "verbose" in options and options["verbose"] == "yes":
CID (unavailable; MK=77f356d8468259291d89e68a472ea539) (#1 of 1): Excessive log level (SIGMA.debug_logging_enabled):
(1) Event Sigma main event: The Python application has been configured to create excessive logs using a `DEBUG` log level. Excessive logging can expose sensitive information in log files.
(2) Event remediation: The log level of a production Python application should be set to `ERROR`, `WARN`, or `INFO`, instead of `DEBUG`.
198  			logging.getLogger().setLevel(logging.DEBUG)
199  		devs = dev_read(options, fail=False)
200  		if not devs:
201  			if "--suppress-errors" not in options:
202  				logging.error("No devices found")
203  			return 0
204  		for dev, key in list(devs.items()):
205  			for n in range(int(options["retry"]) + 1):
206  				if n > 0:
207  					logging.debug("retry: " + str(n) + " of " + options["retry"])
208  				if key in get_registration_keys(options, dev, fail=False):
209  					logging.debug("key " + key + " registered with device " + dev)
210  					return 0
211  				else:
212  					logging.debug("key " + key + " not registered with device " + dev)
213  	
214  				if n < int(options["retry"]):
215  					time.sleep(float(options["retry-sleep"]))
216  		logging.debug("key " + key + " registered with any devices")
217  	
218  		if hardreboot == True:
219  			libc = ctypes.cdll['libc.so.6']
220  			libc.reboot(0x1234567)
221  		return 2
222  	
223  	def define_new_opts():
224  		all_opt["devices"] = {
225  			"getopt" : "d:",
226  			"longopt" : "devices",
227  			"help" : "-d, --devices=[devices]        List of devices to use for current operation",
228  			"required" : "0",
229  			"shortdesc" : "List of devices to use for current operation. Devices can \
230  	be comma or space separated list of device-mapper multipath devices (eg. /dev/mapper/3600508b400105df70000e00000ac0000 or /dev/mapper/mpath1). \
231  	Each device must support SCSI-3 persistent reservations.",
232  			"order": 1
233  		}
234  		all_opt["key"] = {
235  			"getopt" : "k:",
236  			"longopt" : "key",
237  			"help" : "-k, --key=[key]                Replaced by -n, --plug",
238  			"required" : "0",
239  			"shortdesc" : "Replaced by port/-n/--plug",
240  			"order": 1
241  		}
242  		all_opt["suppress-errors"] = {
243  			"getopt" : "",
244  			"longopt" : "suppress-errors",
245  			"help" : "--suppress-errors              Suppress error log. Suppresses error logging when run from the watchdog service before pacemaker starts.",
246  			"required" : "0",
247  			"shortdesc" : "Error log suppression.",
248  			"order": 4
249  	        }
250  		all_opt["mpathpersist_path"] = {
251  			"getopt" : ":",
252  			"longopt" : "mpathpersist-path",
253  			"help" : "--mpathpersist-path=[path]     Path to mpathpersist binary",
254  			"required" : "0",
255  			"shortdesc" : "Path to mpathpersist binary",
256  			"default" : "@MPATH_PATH@",
257  			"order": 200
258  		}
259  		all_opt["store_path"] = {
260  			"getopt" : ":",
261  			"longopt" : "store-path",
262  			"help" : "--store-path=[path]            Path to directory containing cached keys",
263  			"required" : "0",
264  			"shortdesc" : "Path to directory where fence agent can store information",
265  			"default" : "@STORE_PATH@",
266  			"order": 200
267  		}
268  	
269  	def main():
270  		atexit.register(atexit_handler)
271  	
272  		device_opt = ["no_login", "no_password", "devices", "key", "sudo", \
273  		        "fabric_fencing", "on_target", "store_path", \
274  			"suppress-errors", "mpathpersist_path", "force_on", "port", "no_port"]
275  	
276  		define_new_opts()
277  	
278  		all_opt["port"]["required"] = "0"
279  		all_opt["port"]["help"] = "-n, --plug=[key]               Key to use for the current operation"
280  		all_opt["port"]["shortdesc"] = "Key to use for the current operation. \
281  	This key should be unique to a node and have to be written in \
282  	/etc/multipath.conf. For the \"on\" action, the key specifies the key use to \
283  	register the local node. For the \"off\" action, this key specifies the key to \
284  	be removed from the device(s)."
285  	
286  		# fence_mpath_check
287  		if os.path.basename(sys.argv[0]) == "fence_mpath_check":
288  			sys.exit(mpath_check())
289  		elif os.path.basename(sys.argv[0]) == "fence_mpath_check_hardreboot":
290  			sys.exit(mpath_check(hardreboot=True))
291  	
292  		options = check_input(device_opt, process_input(device_opt), other_conditions=True)
293  	
294  		# hack to remove list/list-status actions which are not supported
295  		options["device_opt"] = [ o for o in options["device_opt"] if o != "separator" ]
296  	
297  		# workaround to avoid regressions
298  		if "--key" in options:
299  			options["--plug"] = options["--key"]
300  			del options["--key"]
301  		elif "--help" not in options and options["--action"] in ["off", "on", \
302  		     "reboot", "status", "validate-all"] and "--plug" not in options:
303  			stop_after_error = False if options["--action"] == "validate-all" else True
304  			fail_usage("Failed: You have to enter plug number or machine identification", stop_after_error)
305  	
306  		docs = {}
307  		docs["shortdesc"] = "Fence agent for multipath persistent reservation"
308  		docs["longdesc"] = "fence_mpath is an I/O Fencing agent that uses SCSI-3 \
309  	persistent reservations to control access multipath devices. Underlying \
310  	devices must support SCSI-3 persistent reservations (SPC-3 or greater) as \
311  	well as the \"preempt-and-abort\" subcommand.\nThe fence_mpath agent works by \
312  	having a unique key for each node that has to be set in /etc/multipath.conf. \
313  	Once registered, a single node will become the reservation holder \
314  	by creating a \"write exclusive, registrants only\" reservation on the \
315  	device(s). The result is that only registered nodes may write to the \
316  	device(s). When a node failure occurs, the fence_mpath agent will remove the \
317  	key belonging to the failed node from the device(s). The failed node will no \
318  	longer be able to write to the device(s). A manual reboot is required.\
319  	\n.P\n\
320  	When used as a watchdog device you can define e.g. retry=1, retry-sleep=2 and \
321  	verbose=yes parameters in /etc/sysconfig/stonith if you have issues with it \
322  	failing."
323  		docs["vendorurl"] = "https://www.sourceware.org/dm/"
324  		show_docs(options, docs)
325  	
326  		run_delay(options)
327  	
328  		# Input control BEGIN
329  		if options["--action"] == "validate-all":
330  			sys.exit(0)
331  	
332  		if not ("--devices" in options and options["--devices"]):
333  			fail_usage("Failed: No devices found")
334  	
335  		options["devices"] = [d for d in re.split(r"\s*,\s*|\s+", options["--devices"].strip()) if d]
336  		options["--plug"] = re.sub(r"^0x0*|^0+", "", options.get("--plug", ""))
337  		# Input control END
338  	
339  		result = fence_action(None, options, set_status, get_status)
340  		sys.exit(result)
341  	
342  	if __name__ == "__main__":
343  		main()
344