From f5401fc8dad9a39da0a279b7d55149dc0ebcba26 Mon Sep 17 00:00:00 2001 From: Marek 'marx' Grac Date: Mon, 5 Jan 2015 10:15:36 +0100 Subject: [PATCH] fence_zvmip: Port fence agent to fencing library This rewrite adds a correct result codes, support for 'list' action and autogenerated manual page. The original code is still part of codebase as new agent requires additional testing. --- fence/agents/lib/fencing.py.py | 4 + fence/agents/zvm/Makefile.am | 26 ++---- fence/agents/zvm/fence_zvmip.8 | 92 ------------------- fence/agents/zvm/fence_zvmip.py | 172 ++++++++++++++++++++++++++++++++++++ 5 files changed, 310 insertions(+), 149 deletions(-) delete mode 100644 fence/agents/zvm/fence_zvmip.8 create mode 100644 fence/agents/zvm/fence_zvmip.py diff --git a/fence/agents/lib/fencing.py.py b/fence/agents/lib/fencing.py.py index a8ea448..83bbfcd 100644 --- a/fence/agents/lib/fencing.py.py +++ b/fence/agents/lib/fencing.py.py @@ -7,6 +7,7 @@ import subprocess import threading import shlex import exceptions +import socket import __main__ ## do not add code here. @@ -805,6 +806,9 @@ def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_lis except pycurl.error, ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) + except socket.timeout, ex: + logging.error("%s\n", str(ex)) + fail(EC_TIMED_OUT) return result diff --git a/fence/agents/zvm/Makefile.am b/fence/agents/zvm/Makefile.am index 62eb862..a29754d 100644 --- a/fence/agents/zvm/Makefile.am +++ b/fence/agents/zvm/Makefile.am @@ -2,26 +2,16 @@ MAINTAINERCLEANFILES = Makefile.in TARGET = fence_zvmip -sbin_PROGRAMS = fence_zvm fence_zvmip +SRC = $(TARGET).py -noinst_HEADERS = fence_zvm.h +EXTRA_DIST = $(SRC) -fence_zvm_SOURCES = fence_zvm.c -fence_zvm_CFLAGS = -D_GNU_SOURCE +sbin_SCRIPTS = $(TARGET) -fence_zvmip_SOURCES = fence_zvmip.c -fence_zvmip_CFLAGS = -D_GNU_SOURCE +man_MANS = $(TARGET).8 -dist_man_MANS = fence_zvm.8 fence_zvmip.8 +FENCE_TEST_ARGS = -l test -p test -a test -n 1 -#include $(top_srcdir)/make/fencemanc.mk - -clean-local: - rm -f $(sbin_PROGRAMS) - -FENCE_TEST_ARGS = -n test -a test -p test -u test - -include $(top_srcdir)/make/agentccheck.mk - -# we do not test fence_zvm because it can be compiled only on specific architecture -check: xml-check.fence_zvmip delay-check.fence_zvmip \ No newline at end of file +include $(top_srcdir)/make/fencebuild.mk +include $(top_srcdir)/make/fenceman.mk +include $(top_srcdir)/make/agentpycheck.mk diff --git a/fence/agents/zvm/fence_zvmip.8 b/fence/agents/zvm/fence_zvmip.8 deleted file mode 100644 index 6b01425..0000000 --- a/fence/agents/zvm/fence_zvmip.8 +++ /dev/null @@ -1,92 +0,0 @@ -.TH fence_zvmip 8 - -.SH NAME -fence_zvmip - Power Fencing agent for GFS on System z z/VM Clusters using IP interface to SMAPI - -.SH SYNOPSIS -.B -fence_zvmip -[\fIOPTION\fR]... - -.SH DESCRIPTION -fence_zvmip is a Power Fencing agent used on a GFS virtual machine in a System z z/VM cluster. -It uses the TCP/IP SMAPI interface to recycle an active image. - -fence_zvmip accepts options on the command line as well as from stdin. -fence_node sends the options through stdin when it execs the agent. -fence_zvmip can be run by itself with command line options which is useful -for testing. - -Vendor URL: http://www.sinenomine.net - -.SH OPTIONS -.TP -\fB-o --action\fP -Fencing action: "off" - deactivate virtual machine; "on" - activate virtual machine; "metadata" - display device metadata" - describe fence agent parameters; "status" - state of virtual machine -.TP -\fB--delay\fP \fIseconds\fP -Time to delay fencing action in seconds -.TP -\fB-n --plug\fP \fItarget\fP -Name of target virtual machine to fence -.TP -\fB-h --help\fP -Print out a help message describing available options, then exit. -.TP -\fB-a --ip\fP \fIsmapi Server\fP -Host name or IP address of SMAPI server -.TP -\fB-u --username\fP \fISMAPI authorized user\fP -Name of an authorized SMAPI user -.TP -\fB-p --password\fP \fISMAPI authorized user's password\fP -Password of the authorized SMAPI user -.TP -\fB-t --timeout\fP \fIRecycle timeout\fP -Amount of \fIgrace\fP time to give the virtual machine to shutdown cleanly before being -forcibly terminated. Currently this is ignored. -.TP -\fB-h --help\fP -Display usage information - -.SH STDIN PARAMETERS -.TP -\fIagent = < param >\fP -This option is used by fence_node(8) and is ignored by fence_zvmip. -.TP -\fIaction = < action >\fP -Fencing action: "off" - deactivate virtual machine; "on" - activate virtual machine; "metadata" - display device metadata" - describe fence agent parameters; "status" - state of virtual machine -.TP -\fIplug = < plug >\fP -Name of virtual machine to recycle. -.TP -\fIipaddr = < server host name or IP address >\fP -Host name or IP address of SMAPI server -.TP -\fIlogin = < SMAPI authorized user >\fP -Name of an authorized SMAPI user -.TP -\fIpasswd = < SMAPI authorized user's password >\fP -Password of the authorized SMAPI user -.TP -\fItimeout = < shutdown timeout >\fP -Amount of \fIgrace\fP time to give the virtual machine to shutdown cleanly before being -forcibly terminated. Currently this is ignored. - -.SH SEE ALSO -fence(8), fenced(8), fence_node(8) - -.SH NOTES -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: - -.nf -Column 1 Column 66 Column 131 -| | | -V V V -XXXXXXXX ALL IMAGE_OPERATIONS -.fi - -Where XXXXXXX is the name of the virtual machine used in the authuser field of the request. diff --git a/fence/agents/zvm/fence_zvmip.py b/fence/agents/zvm/fence_zvmip.py new file mode 100644 index 0000000..63f7fa7 --- /dev/null +++ b/fence/agents/zvm/fence_zvmip.py @@ -0,0 +1,172 @@ +#!/usr/bin/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 + + # '*' = list all active images + (return_code, reason_code, images_active) = \ + get_list_of_images(options, "Image_Status_Query", "*") + logging.debug("Image_Status_Query results are (%d,%d)", return_code, reason_code) + (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) + + if ["list", "monitor"].count(options["--action"]) == 1: + return dict([(i, ("", "on" if i in images_active else "off")) for i in images_defined]) + else: + status = "error" + if options["--plug"].upper() in images_defined: + if options["--plug"].upper() in images_active: + status = "on" + else: + status = "off" + return status + +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) + + 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"] + + atexit.register(atexit_handler) + + all_opt["ipport"]["default"] = "44444" + all_opt["shell_timeout"]["default"] = "5.0" + options = check_input(device_opt, process_input(device_opt)) + + if len(options.get("--plug", "")) > 8: + fail_usage("Failed: Name of image can not be longer than 8 characters") + + 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() -- 1.9.3