#!/usr/bin/python

# The Following Agent Has Been Tested On:
#
# Virsh 0.3.3 on RHEL 5.2 with xen-3.0.3-51
#

import sys, time, random, os, atexit, getopt, re

#BEGIN_VERSION_GENERATION
RELEASE_VERSION="3.1.6"
BUILD_DATE="(built Mon Oct 24 12:14:08 UTC 2011)"
REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved."
#END_VERSION_GENERATION

all_opt = {
	"help"    : {
		"getopt" : "h",
		"longopt" : "help",
		"help" : "-h, --help                     Display this help and exit",
		"required" : "0",
		"shortdesc" : "Display help and exit",
		"order" : 54 },
	"version" : { 
		"getopt" : "V",
		"longopt" : "version",
		"help" : "-V, --version                  Output version information and exit",
		"required" : "0",
		"shortdesc" : "Display version information and exit",
		"order" : 53 },
	"quiet"   : {
		"getopt" : "q",
		"help" : "",
		"order" : 50 },
	"verbose" : {
		"getopt" : "v",
		"longopt" : "verbose",
		"help" : "-v, --verbose                  Verbose mode",
		"required" : "0",
		"shortdesc" : "Verbose mode",
		"order" : 51 },
	"debug" : {
		"getopt" : "D:",
		"longopt" : "debug-file", 
		"help" : "-D, --debug-file=[debugfile]   Debugging to output file",
		"required" : "0",
		"shortdesc" : "Write debug information to given file",
		"order" : 52 },
	"random_sleep_range": {
		"getopt" : "R:",
		"required" : "0",
		"longopt" : "random_sleep_range",
		"help" : "--random_sleep-range=[seconds] Issue a sleep between 1 and [seconds]. Used for testing.",
		"shortdesc" : "Issue a sleep between 1 and [seconds]",
		"order" : 1 },
	"mode": {
		"getopt" : "M:",
		"longopt" : "mode",
		"required" : "0",
		"help" : "--mode=(pass|fail|random). Used for testing.",
		"shortdesc" : "Should operations always pass, always fail or fail at random",
		"order" : 1 },
	"delay" : {
		"getopt" : "f:",
		"longopt" : "delay",
		"help" : "--delay [seconds]              Wait X seconds before fencing is started",
		"required" : "0",
		"shortdesc" : "Wait X seconds before fencing is started",
		"default" : "0",
		"order" : 200 },
	"action" : {
		"getopt" : "o:",
		"longopt" : "action",
		"help" : "-o, --action=[action]          Action: status, list, reboot (default), off or on",
		"required" : "1",
		"shortdesc" : "Fencing Action",
		"default" : "reboot",
		"order" : 1 },
	"port" : {
		"getopt" : "n:",
		"longopt" : "plug",
		"help" : "-n, --plug=[id]                Physical plug number on device or\n" + 
                "                                        name of virtual machine",
		"required" : "1",
		"shortdesc" : "Physical plug number or name of virtual machine",
		"order" : 1 },
	"switch" : {
		"getopt" : "s:",
		"longopt" : "switch",
		"help" : "-s, --switch=[id]              Physical switch number on device",
		"required" : "0",
		"shortdesc" : "Physical switch number on device",
		"order" : 1 },
	"nodeid" : {
		"getopt" : "i:",
		"longopt" : "nodeid",
		"help" : "-i, --nodeid                    corosync id of fence victim",
		"required" : "0",
		"shortdesc" : "The corosync id of fence victim",
		"order" : 1},
	"uuid" : {
		"getopt" : "U:",
		"longopt" : "uuid",
		"help" : "-U, --uuid                     UUID of the VM to fence.",
		"required" : "0",
		"shortdesc" : "The UUID of the virtual machine to fence.",
		"order" : 1},
	"mock_dynamic_hosts" : {
		"getopt" : "H:",
		"longopt" : "mock_dynamic_hosts",
		"help" : "-H, --mock_dynamic_hosts=[hostlist]         List of hosts we can fence.",
		"required" : "0",
		"shortdesc" : "A list of hosts we can fence.",
		"order" : 1}
}

def show_docs(options, docs = None):
	device_opt = options["device_opt"]

	if docs == None:
		docs = { }
		docs["shortdesc"] = "Fence agent"
		docs["longdesc"] = ""
	
	## Process special options (and exit)
	#####
	if options.has_key("-h"): 
		usage(device_opt)
		sys.exit(0)

	if options.has_key("-o") and options["-o"].lower() == "metadata":
		sys.stderr.write("asked for fence_dummy metadata\n")
		metadata(device_opt, options, docs)
		sys.exit(0)

	if options.has_key("-V"):
		print __main__.RELEASE_VERSION, __main__.BUILD_DATE
		print __main__.REDHAT_COPYRIGHT
		sys.exit(0)

def usage(avail_opt):
	global all_opt

	print "Usage:"
	print "\t" + os.path.basename(sys.argv[0]) + " [options]"
	print "Options:"

	sorted_list = [ (key, all_opt[key]) for key in avail_opt ]
	sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"]))

	for key, value in sorted_list:
		if len(value["help"]) != 0:
			print "   " + value["help"]

def metadata(avail_opt, options, docs):
	global all_opt

	sorted_list = [ (key, all_opt[key]) for key in avail_opt ]
	sorted_list.sort(lambda x, y: cmp(x[1]["order"], y[1]["order"]))

	print "<?xml version=\"1.0\" ?>"
	print "<resource-agent name=\"" + os.path.basename(sys.argv[0]) + "\" shortdesc=\"" + docs["shortdesc"] + "\" >"
	print "<longdesc>" + docs["longdesc"] + "</longdesc>"
	if docs.has_key("vendorurl"):
		print "<vendor-url>" + docs["vendorurl"] + "</vendor-url>"
	print "<parameters>"
	for option, value in sorted_list:
		if all_opt[option].has_key("shortdesc"):
			print "\t<parameter name=\"" + option + "\" unique=\"0\" required=\"" + all_opt[option]["required"] + "\">"

			default = ""
			if all_opt[option].has_key("default"):
				default = "default=\""+str(all_opt[option]["default"])+"\""
			elif options.has_key("-" + all_opt[option]["getopt"][:-1]):
				if options["-" + all_opt[option]["getopt"][:-1]]:
					try:
						default = "default=\"" + options["-" + all_opt[option]["getopt"][:-1]] + "\""
					except TypeError:
						## @todo/@note: Currently there is no clean way how to handle lists
						## we can create a string from it but we can't set it on command line
						default = "default=\"" + str(options["-" + all_opt[option]["getopt"][:-1]]) +"\""
			elif options.has_key("-" + all_opt[option]["getopt"]):
				default = "default=\"true\" "

			mixed = all_opt[option]["help"]
			## split it between option and help text
			res = re.compile("^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed)
			if (None != res):
				mixed = res.group(1)
			mixed = mixed.replace("<", "&lt;").replace(">", "&gt;")
			print "\t\t<getopt mixed=\"" + mixed + "\" />"

			if all_opt[option]["getopt"].count(":") > 0:
				print "\t\t<content type=\"string\" "+default+" />"
			else:
				print "\t\t<content type=\"boolean\" "+default+" />"
				
			print "\t\t<shortdesc lang=\"en\">" + all_opt[option]["shortdesc"] + "</shortdesc>"
			print "\t</parameter>"
	print "</parameters>"
	print "<actions>"
	if avail_opt.count("io_fencing") == 0:
		print "\t<action name=\"on\" on_target=\"1\" />"
		print "\t<action name=\"off\" />"
		print "\t<action name=\"reboot\" />"
	else:
		print "\t<action name=\"enable\" />"
		print "\t<action name=\"disable\" />"	

	print "\t<action name=\"status\" />"
	print "\t<action name=\"monitor\" />"
	print "\t<action name=\"metadata\" />"
	print "\t<action name=\"list\" />"
	print "</actions>"
	print "</resource-agent>"

def process_input(avail_opt):
	global all_opt

	##
	## Set standard environment
	#####
	os.putenv("LANG", "C")
	os.putenv("LC_ALL", "C")

	##
	## Prepare list of options for getopt
	#####
	getopt_string = ""
	longopt_list = [ ]
	for k in avail_opt:
		if all_opt.has_key(k):
			getopt_string += all_opt[k]["getopt"]
		else:
			fail_usage("Parse error: unknown option '"+k+"'")

		if all_opt.has_key(k) and all_opt[k].has_key("longopt"):
			if all_opt[k]["getopt"].endswith(":"):
				longopt_list.append(all_opt[k]["longopt"] + "=")
			else:
				longopt_list.append(all_opt[k]["longopt"])

	## Compatibility layer
	if avail_opt.count("module_name") == 1:
		getopt_string += "n:"
		longopt_list.append("plug=")
	
	##
	## Read options from command line or standard input
	#####
	if len(sys.argv) > 1:
		try:
			opt, args = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list)
		except getopt.GetoptError, error:
			fail_usage("Parse error: " + error.msg)

		## Transform longopt to short one which are used in fencing agents
		#####
		old_opt = opt
		opt = { }
		for o in dict(old_opt).keys():
			if o.startswith("--"):
				for x in all_opt.keys():
					if all_opt[x].has_key("longopt") and "--" + all_opt[x]["longopt"] == o:
						opt["-" + all_opt[x]["getopt"].rstrip(":")] = dict(old_opt)[o]
			else:
				opt[o] = dict(old_opt)[o]

		## Compatibility Layer
		#####
		z = dict(opt)
		if z.has_key("-T") == 1:
			z["-o"] = "status"
		if z.has_key("-n") == 1:
			z["-m"] = z["-n"]

		opt = z
		##
		#####
	else:
		opt = { }
		name = ""
		for line in sys.stdin.readlines():
			line = line.strip()
			if ((line.startswith("#")) or (len(line) == 0)):
				continue

			(name, value) = (line + "=").split("=", 1)
			value = value[:-1]

			## Compatibility Layer
			######
			if name == "option":
				name = "action"

			##
			######
			if avail_opt.count(name) == 0:
				sys.stderr.write("Parse error: Ignoring unknown option '"+line+"'\n")
				continue

			if all_opt[name]["getopt"].endswith(":"):
				opt["-"+all_opt[name]["getopt"].rstrip(":")] = value
			elif ((value == "1") or (value.lower() == "yes") or (value.lower() == "on") or (value.lower() == "true")):
				opt["-"+all_opt[name]["getopt"]] = "1"
	return opt

def atexit_handler():
	try:
		sys.stdout.close()
		os.close(1)
	except IOError:
		sys.stderr.write("%s failed to close standard output\n"%(sys.argv[0]))
		sys.exit(1)

def main():
    global all_opt
    device_opt = all_opt.keys()

    ## Defaults for fence agent
    docs = { }
    docs["shortdesc"] = "Dummy fence agent"
    docs["longdesc"] = "fence_dummy is a fake fencing agent which reports success based on it's mode (pass|fail|random) without doing anything."

    atexit.register(atexit_handler)
    options = process_input(device_opt)
    options["device_opt"] = device_opt
    show_docs(options, docs)

    # random sleep for testing
    if options.has_key("-f"):
        val = int(options["-f"])
        sys.stderr.write("delay sleep for %d seconds\n" % val)
        time.sleep(val)

    if options.has_key("-R"):
        val = int(options["-R"])
        ran = random.randint(1, val)
        sys.stderr.write("random sleep for %d seconds\n" % ran)
        time.sleep(ran)

    if options.has_key("-o") and (options["-o"] == "monitor"):
        sys.stderr.write("fence_dummy monitor called\n")
        sys.exit(0)
    
    if options.has_key("-o") and (options["-o"] == "list"):
        sys.stderr.write("fence_dummy action (list) called\n")
        if options.has_key("-H"):
            print options["-H"]
        else:
            sys.stderr.write("were asked for hostlist but attribute mock_dynamic_hosts wasn't set\n")

    if options.has_key("-M"):
        if options["-M"] == "pass":
            sys.exit(0)
        elif options["-M"] == "fail":
            sys.exit(1)

    sys.exit(random.randint(0, 1))

if __name__ == "__main__":
    main()
