#!@PYTHON@ -tt

import sys
import atexit
import socket
import struct
import logging
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT

#BEGIN_VERSION_GENERATION
RELEASE_VERSION=""
REDHAT_COPYRIGHT=""
BUILD_DATE=""
#END_VERSION_GENERATION

INT4 = 4

def open_socket(options):
	try:
		if "--inet6-only" in options:
			protocol = socket.AF_INET6
		elif "--inet4-only" in options:
			protocol = socket.AF_INET
		else:
			protocol = 0
		(_, _, _, _, addr) = socket.getaddrinfo( \
				options["--ip"], options["--ipport"], protocol,
				0, socket.IPPROTO_TCP, socket.AI_PASSIVE
				)[0]
	except socket.gaierror:
		fail(EC_LOGIN_DENIED)

	conn = socket.socket()
	conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	conn.settimeout(float(options["--shell-timeout"]))
	try:
		conn.connect(addr)
	except socket.error:
		fail(EC_LOGIN_DENIED)

	return conn

def smapi_pack_string(string):
	return struct.pack("!i%ds" % (len(string)), len(string), string)

def prepare_smapi_command(options, smapi_function, additional_args):
	packet_size = 3*INT4 + len(smapi_function) + len(options["--username"]) + len(options["--password"])
	for arg in additional_args:
		packet_size += INT4 + len(arg)

	command = struct.pack("!i", packet_size)
	command += smapi_pack_string(smapi_function)
	command += smapi_pack_string(options["--username"])
	command += smapi_pack_string(options["--password"])
	for arg in additional_args:
		command += smapi_pack_string(arg)

	return command

def get_power_status(conn, options):
	del conn

	if options.get("--original-action", None) == "monitor":
		(return_code, reason_code, images_active) = \
			get_list_of_images(options, "Check_Authentication", None)

		logging.debug("Check_Authenticate (%d,%d)", return_code, reason_code)
		if return_code == 0:
			return {}
		else:
			fail(EC_LOGIN_DENIED)

	if options["--action"] == "list":
		# '*' = list all active images
		options["--plug"] = "*"

	(return_code, reason_code, images_active) = \
			get_list_of_images(options, "Image_Status_Query", options["--plug"])
	logging.debug("Image_Status_Query results are (%d,%d)", return_code, reason_code)

	if not options["--action"] == "list":
		if (return_code == 0) and (reason_code == 0):
			return "on"
		elif (return_code == 0) and (reason_code == 12):
			# We are running always with --missing-as-off because we can not check if image
			# is defined or not (look at rhbz#1188750)
			return "off"
		else:
			return "unknown"
	else:
		(return_code, reason_code, images_defined) = \
			get_list_of_images(options, "Image_Name_Query_DM", options["--username"])
		logging.debug("Image_Name_Query_DM results are (%d,%d)", return_code, reason_code)

		return dict([(i, ("", "on" if i in images_active else "off")) for i in images_defined])

def set_power_status(conn, options):
	conn = open_socket(options)

	packet = None
	if options["--action"] == "on":
		packet = prepare_smapi_command(options, "Image_Activate", [options["--plug"]])
	elif options["--action"] == "off":
		packet = prepare_smapi_command(options, "Image_Deactivate", [options["--plug"], "IMMED"])
	conn.send(packet)

	request_id = struct.unpack("!i", conn.recv(INT4))[0]
	(output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
	logging.debug("Image_(De)Activate results are (%d,%d)", return_code, reason_code)

	conn.close()
	return

def get_list_of_images(options, command, data_as_plug):
	conn = open_socket(options)

	if data_as_plug is None:
		packet = prepare_smapi_command(options, command, [])
	else:
		packet = prepare_smapi_command(options, command, [data_as_plug])

	conn.send(packet)

	request_id = struct.unpack("!i", conn.recv(INT4))[0]
	(output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
	images = set()

	if output_len > 3*INT4:
		array_len = struct.unpack("!i", conn.recv(INT4))[0]
		data = ""

		while True:
			read_data = conn.recv(1024, socket.MSG_WAITALL)
			data += read_data
			if array_len == len(data):
				break
			elif not read_data:
				logging.error("Failed: Not enough data read from socket")
				fail(EC_TIMED_OUT)

		parsed_len = 0
		while parsed_len < array_len:
			string_len = struct.unpack("!i", data[parsed_len:parsed_len+INT4])[0]
			parsed_len += INT4
			image_name = struct.unpack("!%ds" % (string_len), data[parsed_len:parsed_len+string_len])[0]
			parsed_len += string_len
			images.add(image_name)

	conn.close()
	return (return_code, reason_code, images)

def main():
	device_opt = ["ipaddr", "login", "passwd", "port", "method", "missing_as_off"]

	atexit.register(atexit_handler)

	all_opt["ipport"]["default"] = "44444"
	all_opt["shell_timeout"]["default"] = "5.0"
	all_opt["missing_as_off"]["default"] = "1"
	options = check_input(device_opt, process_input(device_opt), other_conditions=True)

	if len(options.get("--plug", "")) > 8:
		fail_usage("Failed: Name of image can not be longer than 8 characters")

	if options["--action"] == "validate-all":
		sys.exit(0)

	docs = {}
	docs["shortdesc"] = "Fence agent for use with z/VM Virtual Machines"
	docs["longdesc"] = """The fence_zvm agent is intended to be used with with z/VM SMAPI service via TCP/IP

To  use this agent the z/VM SMAPI service needs to be configured to allow the virtual machine running this agent to connect to it and issue
the image_recycle operation.  This involves updating the VSMWORK1 AUTHLIST VMSYS:VSMWORK1. file. The entry should look something similar to
this:

Column 1                   Column 66                Column 131

   |                          |                        |
   V                          V                        V

XXXXXXXX                      ALL                      IMAGE_OPERATIONS

Where XXXXXXX is the name of the virtual machine used in the authuser field of the request.
"""
	docs["vendorurl"] = "http://www.ibm.com"
	show_docs(options, docs)

	run_delay(options)
	result = fence_action(None, options, set_power_status, get_power_status, get_power_status)
	sys.exit(result)

if __name__ == "__main__":
	main()

[((8, 0, 8, 38), 'sys.path.append', 'sys.path.append', ({(8, 16, 8, 37): '"""@FENCEAGENTSLIBDIR@"""'}, {}), "('@FENCEAGENTSLIBDIR@')", False, 'import sys\n'), ((35, 8, 35, 23), 'socket.socket', 'socket.socket', ({}, {}), '()', False, 'import socket\n'), ((53, 11, 53, 41), 'struct.pack', 'struct.pack', ({(53, 23, 53, 27): '"""!i"""', (53, 29, 53, 40): 'packet_size'}, {}), "('!i', packet_size)", False, 'import struct\n'), ((81, 1, 81, 82), 'logging.debug', 'logging.debug', ({(81, 15, 81, 55): '"""Image_Status_Query results are (%d,%d)"""', (81, 57, 81, 68): 'return_code', (81, 70, 81, 81): 'reason_code'}, {}), "('Image_Status_Query results are (%d,%d)', return_code,\n    reason_code)", False, 'import logging\n'), ((111, 1, 111, 82), 'logging.debug', 'logging.debug', ({(111, 15, 111, 55): '"""Image_(De)Activate results are (%d,%d)"""', (111, 57, 111, 68): 'return_code', (111, 70, 111, 81): 'reason_code'}, {}), "('Image_(De)Activate results are (%d,%d)', return_code,\n    reason_code)", False, 'import logging\n'), ((157, 1, 157, 32), 'atexit.register', 'atexit.register', ({(157, 17, 157, 31): 'atexit_handler'}, {}), '(atexit_handler)', False, 'import atexit\n'), ((190, 1, 190, 19), 'fencing.run_delay', 'run_delay', ({(190, 11, 190, 18): 'options'}, {}), '(options)', False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n'), ((192, 1, 192, 17), 'sys.exit', 'sys.exit', ({(192, 10, 192, 16): 'result'}, {}), '(result)', False, 'import sys\n'), ((69, 2, 69, 71), 'logging.debug', 'logging.debug', ({(69, 16, 69, 44): '"""Check_Authenticate (%d,%d)"""', (69, 46, 69, 57): 'return_code', (69, 59, 69, 70): 'reason_code'}, {}), "('Check_Authenticate (%d,%d)', return_code, reason_code)", False, 'import logging\n'), ((95, 2, 95, 84), 'logging.debug', 'logging.debug', ({(95, 16, 95, 57): '"""Image_Name_Query_DM results are (%d,%d)"""', (95, 59, 95, 70): 'return_code', (95, 72, 95, 83): 'reason_code'}, {}), "('Image_Name_Query_DM results are (%d,%d)', return_code,\n    reason_code)", False, 'import logging\n'), ((165, 2, 165, 73), 'fencing.fail_usage', 'fail_usage', ({(165, 13, 165, 72): '"""Failed: Name of image can not be longer than 8 characters"""'}, {}), "('Failed: Name of image can not be longer than 8 characters')", False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n'), ((168, 2, 168, 13), 'sys.exit', 'sys.exit', ({(168, 11, 168, 12): '(0)'}, {}), '(0)', False, 'import sys\n'), ((28, 23, 31, 5), 'socket.getaddrinfo', 'socket.getaddrinfo', ({(29, 4, 29, 19): "options['--ip']", (29, 21, 29, 40): "options['--ipport']", (29, 42, 29, 50): 'protocol', (30, 4, 30, 5): '(0)', (30, 7, 30, 25): 'socket.IPPROTO_TCP', (30, 27, 30, 44): 'socket.AI_PASSIVE'}, {}), "(options['--ip'], options['--ipport'], protocol, 0,\n    socket.IPPROTO_TCP, socket.AI_PASSIVE)", False, 'import socket\n'), ((33, 2, 33, 23), 'fencing.fail', 'fail', ({(33, 7, 33, 22): 'EC_LOGIN_DENIED'}, {}), '(EC_LOGIN_DENIED)', False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n'), ((41, 2, 41, 23), 'fencing.fail', 'fail', ({(41, 7, 41, 22): 'EC_LOGIN_DENIED'}, {}), '(EC_LOGIN_DENIED)', False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n'), ((73, 3, 73, 24), 'fencing.fail', 'fail', ({(73, 8, 73, 23): 'EC_LOGIN_DENIED'}, {}), '(EC_LOGIN_DENIED)', False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n'), ((145, 16, 145, 69), 'struct.unpack', 'struct.unpack', ({(145, 30, 145, 34): '"""!i"""', (145, 36, 145, 68): 'data[parsed_len:parsed_len + INT4]'}, {}), "('!i', data[parsed_len:parsed_len + INT4])", False, 'import struct\n'), ((147, 16, 147, 92), 'struct.unpack', 'struct.unpack', ({(147, 30, 147, 51): "('!%ds' % string_len)", (147, 53, 147, 91): 'data[parsed_len:parsed_len + string_len]'}, {}), "('!%ds' % string_len, data[parsed_len:parsed_len + string_len])", False, 'import struct\n'), ((140, 4, 140, 61), 'logging.error', 'logging.error', ({(140, 18, 140, 60): '"""Failed: Not enough data read from socket"""'}, {}), "('Failed: Not enough data read from socket')", False, 'import logging\n'), ((141, 4, 141, 22), 'fencing.fail', 'fail', ({(141, 9, 141, 21): 'EC_TIMED_OUT'}, {}), '(EC_TIMED_OUT)', False, 'from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT\n')]