diff --git a/agents/kdump/fence_kdump.c b/agents/kdump/fence_kdump.c index 4ab2dd9b..1c4a69ca 100644 --- a/agents/kdump/fence_kdump.c +++ b/agents/kdump/fence_kdump.c @@ -1,570 +1,570 @@ /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- * * Copyright (c) Ryan O'Hara (rohara@redhat.com) * Copyright (c) Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "options.h" #include "message.h" #include "version.h" static int verbose = 0; #define log_debug(lvl, fmt, args...) \ do { \ if (lvl <= verbose) { \ fprintf (stdout, "[debug]: " fmt, ##args); \ syslog (LOG_INFO, fmt, ##args); \ } \ } while (0); #define log_error(lvl, fmt, args...) \ do { \ if (lvl <= verbose) { \ fprintf (stderr, "[error]: " fmt, ##args); \ syslog (LOG_ERR, fmt, ##args); \ } \ } while (0); static int trim (char *str) { char *p; int len; if (!str) return (0); len = strlen (str); while (len--) { if (isspace (str[len])) { str[len] = 0; } else { break; } } for (p = str; *p && isspace (*p); p++); memmove (str, p, strlen (p) + 1); return (strlen (str)); } static int read_message (const fence_kdump_node_t *node, void *msg, int len) { int error; char addr[NI_MAXHOST]; char port[NI_MAXSERV]; struct sockaddr_storage ss; socklen_t size = sizeof (ss); error = recvfrom (node->socket, msg, len, 0, (struct sockaddr *) &ss, &size); if (error < 0) { log_error (2, "recvfrom (%s)\n", strerror (errno)); goto out; } error = getnameinfo ((struct sockaddr *) &ss, size, addr, sizeof (addr), port, sizeof (port), NI_NUMERICHOST | NI_NUMERICSERV); if (error != 0) { log_error (2, "getnameinfo (%s)\n", gai_strerror (error)); goto out; } error = strcasecmp (node->addr, addr); if (error != 0) { log_debug (1, "discard message from '%s'\n", addr); } out: return (error); } static int do_action_monitor (void) { const char cmdline_path[] = "/proc/cmdline"; FILE *procFile; size_t sz = 0; char *lines = NULL; int result = 1; procFile = fopen(cmdline_path, "r"); if (procFile == NULL) { log_error (0, "Unable to open file %s (%s)\n", cmdline_path, strerror (errno)); return 1; } while (!feof (procFile)) { ssize_t rv = getline (&lines, &sz, procFile); if ((rv != -1) && (strstr(lines, "crashkernel=") != NULL)) { result = 0; } } free (lines); fclose (procFile); return result; } static int do_action_off (const fence_kdump_opts_t *opts) { int error; fd_set rfds; fence_kdump_msg_t msg; fence_kdump_node_t *node; struct timeval timeout; if (list_empty (&opts->nodes)) { return (1); } else { node = list_first_entry (&opts->nodes, fence_kdump_node_t, list); } timeout.tv_sec = opts->timeout; timeout.tv_usec = 0; FD_ZERO (&rfds); FD_SET (node->socket, &rfds); log_debug (0, "waiting for message from '%s'\n", node->addr); for (;;) { error = select (node->socket + 1, &rfds, NULL, NULL, &timeout); if (error < 0) { log_error (2, "select (%s)\n", strerror (errno)); break; } if (error == 0) { log_debug (0, "timeout after %d seconds\n", opts->timeout); break; } if (read_message (node, &msg, sizeof (msg)) != 0) { continue; } if (msg.magic != FENCE_KDUMP_MAGIC) { log_debug (1, "invalid magic number '0x%X'\n", msg.magic); continue; } switch (msg.version) { case FENCE_KDUMP_MSGV1: log_debug (0, "received valid message from '%s'\n", node->addr); return (0); default: log_debug (1, "invalid message version '0x%X'\n", msg.version); continue; } } return (1); } static int do_action_metadata (const char *self) { fprintf (stdout, "\n"); fprintf (stdout, "\n"); fprintf (stdout, ""); - fprintf (stdout, "\\fIfence_kdump\\fP is an I/O fencing agent to be used with the kdump\n" - "crash recovery service. When the \\fIfence_kdump\\fP agent is invoked,\n" + fprintf (stdout, "fence_kdump is an I/O fencing agent to be used with the kdump\n" + "crash recovery service. When the fence_kdump agent is invoked,\n" "it will listen for a message from the failed node that acknowledges\n" "that the failed node it executing the kdump crash kernel.\n" - "Note that \\fIfence_kdump\\fP is not a replacement for traditional\n" - "fencing methods. The \\fIfence_kdump\\fP agent can only detect that a\n" + "Note that fence_kdump is not a replacement for traditional\n" + "fencing methods. The fence_kdump agent can only detect that a\n" "node has entered the kdump crash recovery service. This allows the\n" "kdump crash recovery service complete without being preempted by\n" "traditional power fencing methods.\n\n" "Note: the \"off\" action listen for message from failed node that\n" "acknowledges node has entered kdump crash recovery service. If a valid\n" "message is received from the failed node, the node is considered to be\n" "fenced and the agent returns success. Failure to receive a valid\n" "message from the failed node in the given timeout period results in\n" "fencing failure."); fprintf (stdout, "\n"); fprintf (stdout, "http://www.kernel.org/pub/linux/utils/kernel/kexec/\n"); fprintf (stdout, "\n"); fprintf (stdout, "\t\n"); - fprintf (stdout, "\t\t\n"); + fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", "Name or IP address of node to be fenced. This option is required for\n" "the \"off\" action."); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); - fprintf (stdout, "\t\t\n"); + fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", - "IP port number that the \\fIfence_kdump\\fP agent will use to listen for\n" + "IP port number that the fence_kdump agent will use to listen for\n" "messages."); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); - fprintf (stdout, "\t\t\n"); + fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", - "IP network family. Force the \\fIfence_kdump\\fP agent to use a specific\n" - "family. The value for \\fIFAMILY\\fP can be \"auto\", \"ipv4\", or\n" + "IP network family. Force the fence_kdump agent to use a specific\n" + "family. The value for FAMILY can be \"auto\", \"ipv4\", or\n" "\"ipv6\"."); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); - fprintf (stdout, "\t\t\n"); + fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", - "Fencing action to perform. The value for \\fIACTION\\fP can be either\n" + "Fencing action to perform. The value for ACTION can be either\n" "\"off\" or \"metadata\"."); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); - fprintf (stdout, "\t\t\n"); + fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", "Number of seconds to wait for message from failed node. If no message\n" - "is received within \\fITIMEOUT\\fP seconds, the \\fIfence_kdump\\fP agent\n" + "is received within TIMEOUT seconds, the fence_kdump agent\n" "returns failure."); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", "Print verbose output"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", "Print version"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t\n"); fprintf (stdout, "\t\t%s\n", "Print usage"); fprintf (stdout, "\t\n"); fprintf (stdout, "\n"); fprintf (stdout, "\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\t\n"); fprintf (stdout, "\n"); fprintf (stdout, "\n"); return (0); } static void print_usage (const char *self) { fprintf (stdout, "Usage: %s [options]\n", basename (self)); fprintf (stdout, "\n"); fprintf (stdout, "Options:\n"); fprintf (stdout, "\n"); fprintf (stdout, "%s\n", " -n, --nodename=NODE Name or IP address of node to be fenced"); fprintf (stdout, "%s\n", " -p, --ipport=PORT IP port number (default: 7410)"); fprintf (stdout, "%s\n", " -f, --family=FAMILY Network family: ([auto], ipv4, ipv6)"); fprintf (stdout, "%s\n", " -o, --action=ACTION Fencing action: ([off], monitor, metadata, validate-all)"); fprintf (stdout, "%s\n", " -t, --timeout=TIMEOUT Timeout in seconds (default: 60)"); fprintf (stdout, "%s\n", " -v, --verbose Print verbose output"); fprintf (stdout, "%s\n", " -V, --version Print version"); fprintf (stdout, "%s\n", " -h, --help Print usage"); fprintf (stdout, "\n"); return; } static int get_options_node (fence_kdump_opts_t *opts) { int error; struct addrinfo hints; fence_kdump_node_t *node; node = malloc (sizeof (fence_kdump_node_t)); if (!node) { log_error (2, "malloc (%s)\n", strerror (errno)); return (1); } memset (node, 0, sizeof (fence_kdump_node_t)); memset (&hints, 0, sizeof (hints)); hints.ai_family = opts->family; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; hints.ai_flags = AI_NUMERICSERV; strncpy (node->name, opts->nodename, sizeof (node->name) - 1); snprintf (node->port, sizeof (node->port), "%d", opts->ipport); node->info = NULL; error = getaddrinfo (node->name, node->port, &hints, &node->info); if (error != 0) { log_error (2, "getaddrinfo (%s)\n", gai_strerror (error)); free_node (node); return (1); } error = getnameinfo (node->info->ai_addr, node->info->ai_addrlen, node->addr, sizeof (node->addr), node->port, sizeof (node->port), NI_NUMERICHOST | NI_NUMERICSERV); if (error != 0) { log_error (2, "getnameinfo (%s)\n", gai_strerror (error)); free_node (node); return (1); } hints.ai_family = node->info->ai_family; hints.ai_flags |= AI_PASSIVE; freeaddrinfo (node->info); node->info = NULL; error = getaddrinfo (NULL, node->port, &hints, &node->info); if (error != 0) { log_error (2, "getaddrinfo (%s)\n", gai_strerror (error)); free_node (node); return (1); } node->socket = socket (node->info->ai_family, node->info->ai_socktype, node->info->ai_protocol); if (node->socket < 0) { log_error (2, "socket (%s)\n", strerror (errno)); free_node (node); return (1); } error = bind (node->socket, node->info->ai_addr, node->info->ai_addrlen); if (error != 0) { log_error (2, "bind (%s)\n", strerror (errno)); free_node (node); return (1); } list_add_tail (&node->list, &opts->nodes); return (0); } static void get_options (int argc, char **argv, fence_kdump_opts_t *opts) { int opt; struct option options[] = { { "nodename", required_argument, NULL, 'n' }, { "ipport", required_argument, NULL, 'p' }, { "family", required_argument, NULL, 'f' }, { "action", required_argument, NULL, 'o' }, { "timeout", required_argument, NULL, 't' }, { "verbose", optional_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, { 0, 0, 0, 0 } }; while ((opt = getopt_long (argc, argv, "n:p:f:o:t:v::Vh", options, NULL)) != EOF) { switch (opt) { case 'n': set_option_nodename (opts, optarg); break; case 'p': set_option_ipport (opts, optarg); break; case 'f': set_option_family (opts, optarg); break; case 'o': set_option_action (opts, optarg); break; case 't': set_option_timeout (opts, optarg); break; case 'v': set_option_verbose (opts, optarg); break; case 'V': print_version (argv[0]); exit (0); case 'h': print_usage (argv[0]); exit (0); default: print_usage (argv[0]); exit (1); } } verbose = opts->verbose; return; } static void get_options_stdin (fence_kdump_opts_t *opts) { char buf[1024]; char *opt; char *arg; while (fgets (buf, sizeof (buf), stdin) != 0) { if (trim (buf) == 0) { continue; } if (buf[0] == '#') { continue; } opt = buf; if ((arg = strchr (opt, '=')) != 0) { *arg = 0; arg += 1; } else { continue; } if (!strcasecmp (opt, "nodename")) { set_option_nodename (opts, arg); continue; } if (!strcasecmp (opt, "ipport")) { set_option_ipport (opts, arg); continue; } if (!strcasecmp (opt, "family")) { set_option_family (opts, arg); continue; } if (!strcasecmp (opt, "action")) { set_option_action (opts, arg); continue; } if (!strcasecmp (opt, "timeout")) { set_option_timeout (opts, arg); continue; } if (!strcasecmp (opt, "verbose")) { set_option_verbose (opts, arg); continue; } } verbose = opts->verbose; return; } int main (int argc, char **argv) { int error = 1; fence_kdump_opts_t opts; init_options (&opts); if (argc > 1) { get_options (argc, argv, &opts); } else { get_options_stdin (&opts); } openlog ("fence_kdump", LOG_CONS|LOG_PID, LOG_DAEMON); if (opts.action == FENCE_KDUMP_ACTION_OFF) { if (opts.nodename == NULL) { log_error (0, "action 'off' requires nodename\n"); exit (1); } if (get_options_node (&opts) != 0) { log_error (0, "failed to get node '%s'\n", opts.nodename); exit (1); } } if (verbose != 0) { print_options (&opts); } switch (opts.action) { case FENCE_KDUMP_ACTION_OFF: error = do_action_off (&opts); break; case FENCE_KDUMP_ACTION_METADATA: error = do_action_metadata (argv[0]); break; case FENCE_KDUMP_ACTION_MONITOR: error = do_action_monitor (); break; case FENCE_KDUMP_ACTION_VALIDATE: error = 0; break; default: break; } free_options (&opts); return (error); } diff --git a/lib/fencing.py.py b/lib/fencing.py.py index 6f2526a9..2a0b0b91 100644 --- a/lib/fencing.py.py +++ b/lib/fencing.py.py @@ -1,1521 +1,1521 @@ #!@PYTHON@ -tt import sys, getopt, time, os, uuid, pycurl, stat import pexpect, re, syslog import logging import subprocess import threading import shlex import socket import textwrap import __main__ RELEASE_VERSION = "@RELEASE_VERSION@" __all__ = ['atexit_handler', 'check_input', 'process_input', 'all_opt', 'show_docs', 'fence_login', 'fence_action', 'fence_logout'] EC_OK = 0 EC_GENERIC_ERROR = 1 EC_BAD_ARGS = 2 EC_LOGIN_DENIED = 3 EC_CONNECTION_LOST = 4 EC_TIMED_OUT = 5 EC_WAITING_ON = 6 EC_WAITING_OFF = 7 EC_STATUS = 8 EC_STATUS_HMC = 9 EC_PASSWORD_MISSING = 10 EC_INVALID_PRIVILEGES = 11 EC_FETCH_VM_UUID = 12 LOG_FORMAT = "%(asctime)-15s %(levelname)s: %(message)s" 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 Display version information and exit", "required" : "0", "shortdesc" : "Display version information and exit", "order" : 53}, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode", "required" : "0", "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}, "delay" : { "getopt" : ":", "longopt" : "delay", "type" : "second", "help" : "--delay=[seconds] Wait X seconds before fencing is started", "required" : "0", "default" : "0", "order" : 200}, "agent" : { "getopt" : "", "help" : "", "order" : 1}, "web" : { "getopt" : "", "help" : "", "order" : 1}, "force_on" : { "getopt" : "", "help" : "", "order" : 1}, "action" : { "getopt" : "o:", "longopt" : "action", "help" : "-o, --action=[action] Action: status, reboot (default), off or on", "required" : "1", "shortdesc" : "Fencing action", "default" : "reboot", "order" : 1}, "fabric_fencing" : { "getopt" : "", "help" : "", "order" : 1}, "ipaddr" : { "getopt" : "a:", "longopt" : "ip", "help" : "-a, --ip=[ip] IP address or hostname of fencing device", "required" : "1", "order" : 1}, "ipport" : { "getopt" : "u:", "longopt" : "ipport", "type" : "integer", "help" : "-u, --ipport=[port] TCP/UDP port to use for connection", "required" : "0", "shortdesc" : "TCP/UDP port to use for connection with device", "order" : 1}, "login" : { "getopt" : "l:", "longopt" : "username", "help" : "-l, --username=[name] Login name", "required" : "?", "order" : 1}, "no_login" : { "getopt" : "", "help" : "", "order" : 1}, "no_password" : { "getopt" : "", "help" : "", "order" : 1}, "no_port" : { "getopt" : "", "help" : "", "order" : 1}, "no_status" : { "getopt" : "", "help" : "", "order" : 1}, "no_on" : { "getopt" : "", "help" : "", "order" : 1}, "no_off" : { "getopt" : "", "help" : "", "order" : 1}, "telnet" : { "getopt" : "", "help" : "", "order" : 1}, "diag" : { "getopt" : "", "help" : "", "order" : 1}, "passwd" : { "getopt" : "p:", "longopt" : "password", "help" : "-p, --password=[password] Login password or passphrase", "required" : "0", "order" : 1}, "passwd_script" : { "getopt" : "S:", "longopt" : "password-script", "help" : "-S, --password-script=[script] Script to run to retrieve password", "required" : "0", "order" : 1}, "identity_file" : { "getopt" : "k:", "longopt" : "identity-file", "help" : "-k, --identity-file=[filename] Identity file (private key) for SSH", "required" : "0", "order" : 1}, "cmd_prompt" : { "getopt" : "c:", "longopt" : "command-prompt", "help" : "-c, --command-prompt=[prompt] Force Python regex for command prompt", "required" : "0", "order" : 1}, "secure" : { "getopt" : "x", "longopt" : "ssh", "help" : "-x, --ssh Use SSH connection", "required" : "0", "order" : 1}, "ssh_options" : { "getopt" : ":", "longopt" : "ssh-options", "help" : "--ssh-options=[options] SSH options to use", "required" : "0", "order" : 1}, "ssl" : { "getopt" : "z", "longopt" : "ssl", "help" : "-z, --ssl Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "ssl_insecure" : { "getopt" : "", "longopt" : "ssl-insecure", "help" : "--ssl-insecure Use SSL connection without verifying certificate", "required" : "0", "order" : 1}, "ssl_secure" : { "getopt" : "", "longopt" : "ssl-secure", "help" : "--ssl-secure Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "notls" : { "getopt" : "t", "longopt" : "notls", "help" : "-t, --notls " "Disable TLS negotiation and force SSL3.0. " "This should only be used for devices that do not support TLS1.0 and up.", "required" : "0", "order" : 1}, "tls1.0" : { "getopt" : "", "longopt" : "tls1.0", "help" : "--tls1.0 " "Disable TLS negotiation and force TLS1.0. " "This should only be used for devices that do not support TLS1.1 and up.", "required" : "0", "order" : 1}, "port" : { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[id] " "Physical plug number on device, UUID or identification of machine", "required" : "1", "order" : 1}, "switch" : { "getopt" : "s:", "longopt" : "switch", "help" : "-s, --switch=[id] Physical switch number on device", "required" : "0", "order" : 1}, "exec" : { "getopt" : "e:", "longopt" : "exec", "help" : "-e, --exec=[command] Command to execute", "required" : "0", "order" : 1}, "vmware_type" : { "getopt" : "d:", "longopt" : "vmware_type", "help" : "-d, --vmware_type=[type] Type of VMware to connect", "required" : "0", "order" : 1}, "vmware_datacenter" : { "getopt" : "s:", "longopt" : "vmware-datacenter", "help" : "-s, --vmware-datacenter=[dc] VMWare datacenter filter", "required" : "0", "order" : 2}, "snmp_version" : { "getopt" : "d:", "longopt" : "snmp-version", "help" : "-d, --snmp-version=[version] Specifies SNMP version to use (1|2c|3)", "required" : "0", "shortdesc" : "Specifies SNMP version to use", "choices" : ["1", "2c", "3"], "order" : 1}, "community" : { "getopt" : "c:", "longopt" : "community", "help" : "-c, --community=[community] Set the community string", "required" : "0", "order" : 1}, "snmp_auth_prot" : { "getopt" : "b:", "longopt" : "snmp-auth-prot", "help" : "-b, --snmp-auth-prot=[prot] Set authentication protocol (MD5|SHA)", "required" : "0", "shortdesc" : "Set authentication protocol", "choices" : ["MD5", "SHA"], "order" : 1}, "snmp_sec_level" : { "getopt" : "E:", "longopt" : "snmp-sec-level", "help" : "-E, --snmp-sec-level=[level] " "Set security level (noAuthNoPriv|authNoPriv|authPriv)", "required" : "0", "shortdesc" : "Set security level", "choices" : ["noAuthNoPriv", "authNoPriv", "authPriv"], "order" : 1}, "snmp_priv_prot" : { "getopt" : "B:", "longopt" : "snmp-priv-prot", "help" : "-B, --snmp-priv-prot=[prot] Set privacy protocol (DES|AES)", "required" : "0", "shortdesc" : "Set privacy protocol", "choices" : ["DES", "AES"], "order" : 1}, "snmp_priv_passwd" : { "getopt" : "P:", "longopt" : "snmp-priv-passwd", "help" : "-P, --snmp-priv-passwd=[pass] Set privacy protocol password", "required" : "0", "order" : 1}, "snmp_priv_passwd_script" : { "getopt" : "R:", "longopt" : "snmp-priv-passwd-script", "help" : "-R, --snmp-priv-passwd-script Script to run to retrieve privacy password", "required" : "0", "order" : 1}, "inet4_only" : { "getopt" : "4", "longopt" : "inet4-only", "help" : "-4, --inet4-only Forces agent to use IPv4 addresses only", "required" : "0", "order" : 1}, "inet6_only" : { "getopt" : "6", "longopt" : "inet6-only", "help" : "-6, --inet6-only Forces agent to use IPv6 addresses only", "required" : "0", "order" : 1}, "separator" : { "getopt" : "C:", "longopt" : "separator", "help" : "-C, --separator=[char] Separator for CSV created by 'list' operation", "default" : ",", "required" : "0", "order" : 100}, "login_timeout" : { "getopt" : ":", "longopt" : "login-timeout", "type" : "second", "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", "default" : "5", "required" : "0", "order" : 200}, "shell_timeout" : { "getopt" : ":", "longopt" : "shell-timeout", "type" : "second", "help" : "--shell-timeout=[seconds] Wait X seconds for cmd prompt after issuing command", "default" : "3", "required" : "0", "order" : 200}, "power_timeout" : { "getopt" : ":", "longopt" : "power-timeout", "type" : "second", "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", "default" : "20", "required" : "0", "order" : 200}, "power_wait" : { "getopt" : ":", "longopt" : "power-wait", "type" : "second", "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", "default" : "0", "required" : "0", "order" : 200}, "missing_as_off" : { "getopt" : "", "longopt" : "missing-as-off", "help" : "--missing-as-off Missing port returns OFF instead of failure", "required" : "0", "order" : 200}, "retry_on" : { "getopt" : ":", "longopt" : "retry-on", "type" : "integer", "help" : "--retry-on=[attempts] Count of attempts to retry power on", "default" : "1", "required" : "0", "order" : 201}, "session_url" : { "getopt" : "s:", "longopt" : "session-url", "help" : "-s, --session-url URL to connect to XenServer on", "required" : "1", "order" : 1}, "sudo" : { "getopt" : "", "longopt" : "use-sudo", "help" : "--use-sudo Use sudo (without password) when calling 3rd party software", "required" : "0", "order" : 205}, "method" : { "getopt" : "m:", "longopt" : "method", "help" : "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)", "required" : "0", "shortdesc" : "Method to fence", "default" : "onoff", "choices" : ["onoff", "cycle"], "order" : 1}, "telnet_path" : { "getopt" : ":", "longopt" : "telnet-path", "help" : "--telnet-path=[path] Path to telnet binary", "required" : "0", "default" : "@TELNET_PATH@", "order": 300}, "ssh_path" : { "getopt" : ":", "longopt" : "ssh-path", "help" : "--ssh-path=[path] Path to ssh binary", "required" : "0", "default" : "@SSH_PATH@", "order": 300}, "gnutlscli_path" : { "getopt" : ":", "longopt" : "gnutlscli-path", "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", "required" : "0", "default" : "@GNUTLSCLI_PATH@", "order": 300}, "sudo_path" : { "getopt" : ":", "longopt" : "sudo-path", "help" : "--sudo-path=[path] Path to sudo binary", "required" : "0", "default" : "@SUDO_PATH@", "order": 300}, "snmpwalk_path" : { "getopt" : ":", "longopt" : "snmpwalk-path", "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", "required" : "0", "default" : "@SNMPWALK_PATH@", "order" : 300}, "snmpset_path" : { "getopt" : ":", "longopt" : "snmpset-path", "help" : "--snmpset-path=[path] Path to snmpset binary", "required" : "0", "default" : "@SNMPSET_PATH@", "order" : 300}, "snmpget_path" : { "getopt" : ":", "longopt" : "snmpget-path", "help" : "--snmpget-path=[path] Path to snmpget binary", "required" : "0", "default" : "@SNMPGET_PATH@", "order" : 300}, "snmp": { "getopt" : "", "help" : "", "order" : 1}, "port_as_ip": { "getopt" : "", "longopt" : "port-as-ip", "help" : "--port-as-ip Make \"port/plug\" to be an alias to IP address", "required" : "0", "order" : 200}, "on_target": { "getopt" : "", "help" : "", "order" : 1}, "quiet": { "getopt" : "q", "longopt": "quiet", "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.", "required" : "0", "order" : 50} } # options which are added automatically if 'key' is encountered ("default" is always added) DEPENDENCY_OPT = { "default" : ["help", "debug", "verbose", "version", "action", "agent", \ "power_timeout", "shell_timeout", "login_timeout", "power_wait", "retry_on", \ "delay", "quiet"], "passwd" : ["passwd_script"], "sudo" : ["sudo_path"], "secure" : ["identity_file", "ssh_options", "ssh_path"], "telnet" : ["telnet_path"], "ipaddr" : ["ipport", "inet4_only", "inet6_only"], "port" : ["separator"], "ssl" : ["ssl_secure", "ssl_insecure", "gnutlscli_path"], "snmp" : ["snmp_auth_prot", "snmp_sec_level", "snmp_priv_prot", \ "snmp_priv_passwd", "snmp_priv_passwd_script", "community", \ "snmpset_path", "snmpget_path", "snmpwalk_path"] } class fspawn(pexpect.spawn): def __init__(self, options, command, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') logging.info("Running command: %s", command) pexpect.spawn.__init__(self, command, **kwargs) self.opt = options def log_expect(self, pattern, timeout): result = self.expect(pattern, timeout) logging.debug("Received: %s", self.before + self.after) return result def send(self, message): logging.debug("Sent: %s", message) return pexpect.spawn.send(self, message) # send EOL according to what was detected in login process (telnet) def send_eol(self, message): return self.send(message + self.opt["eol"]) def frun(command, timeout=30, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') return pexpect.run(command, timeout=timeout, withexitstatus=withexitstatus, events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, env=env, **kwargs) def atexit_handler(): try: sys.stdout.close() os.close(1) except IOError: logging.error("%s failed to close standard output\n", sys.argv[0]) sys.exit(EC_GENERIC_ERROR) def _add_dependency_options(options): ## Add also options which are available for every fence agent added_opt = [] for opt in options + ["default"]: if opt in DEPENDENCY_OPT: added_opt.extend([y for y in DEPENDENCY_OPT[opt] if options.count(y) == 0]) if not "port" in (options + added_opt) and \ not "nodename" in (options + added_opt) and \ "ipaddr" in (options + added_opt): added_opt.append("port_as_ip") all_opt["port"]["help"] = "-n, --plug=[ip] IP address or hostname of fencing device " \ "(together with --port-as-ip)" return added_opt def fail_usage(message="", stop=True): if len(message) > 0: logging.error("%s\n", message) if stop: logging.error("Please use '-h' for usage\n") sys.exit(EC_GENERIC_ERROR) def fail(error_code, stop=True): message = { EC_LOGIN_DENIED : "Unable to connect/login to fencing device", EC_CONNECTION_LOST : "Connection lost", EC_TIMED_OUT : "Connection timed out", EC_WAITING_ON : "Failed: Timed out waiting to power ON", EC_WAITING_OFF : "Failed: Timed out waiting to power OFF", EC_STATUS : "Failed: Unable to obtain correct plug status or plug is not available", EC_STATUS_HMC : "Failed: Either unable to obtain correct plug status, " "partition is not available or incorrect HMC version used", EC_PASSWORD_MISSING : "Failed: You have to set login password", EC_INVALID_PRIVILEGES : "Failed: The user does not have the correct privileges to do the requested action.", EC_FETCH_VM_UUID : "Failed: Can not find VM UUID by its VM name given in the parameter." }[error_code] + "\n" logging.error("%s\n", message) if stop: sys.exit(EC_GENERIC_ERROR) def usage(avail_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(key=lambda x: x[1]["order"]) for key, value in sorted_list: if len(value["help"]) != 0: print(" " + _join_wrap([value["help"]], first_indent=3)) def metadata(options, avail_opt, docs): # avail_opt has to be unique, if there are duplicities then they should be removed sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt)) if "longopt" in all_opt[key]] # Find keys that are going to replace inconsistent names mapping = dict([(opt["longopt"].replace("-", "_"), key) for (key, opt) in sorted_list if (key != opt["longopt"].replace("-", "_"))]) new_options = [(key, all_opt[mapping[key]]) for key in mapping] sorted_list.extend(new_options) sorted_list.sort(key=lambda x: (x[1]["order"], x[0])) if options["--action"] == "metadata": - docs["longdesc"] = re.sub("\.P|\.TP|\.br\n", "", docs["longdesc"]) + docs["longdesc"] = re.sub("\\\\f[BPIR]|\.P|\.TP|\.br\n", "", docs["longdesc"]) print("") print("") for (symlink, desc) in docs.get("symlink", []): print("") print("" + docs["longdesc"] + "") print("" + docs["vendorurl"] + "") print("") for (key, opt) in sorted_list: info = "" if key in all_opt: if key != all_opt[key].get('longopt', key).replace("-", "_"): info = "deprecated=\"1\"" else: info = "obsoletes=\"%s\"" % (mapping.get(key)) if "help" in opt and len(opt["help"]) > 0: if info != "": info = " " + info print("\t") default = "" if "default" in opt: default = "default=\"" + _encode_html_entities(str(opt["default"])) + "\" " mixed = opt["help"] ## split it between option and help text res = re.compile(r"^(.*?--\S+)\s+", re.IGNORECASE | re.S).search(mixed) if None != res: mixed = res.group(1) mixed = _encode_html_entities(mixed) if not "shortdesc" in opt: shortdesc = re.sub("\s\s+", " ", opt["help"][31:]) else: shortdesc = opt["shortdesc"] print("\t\t") if "choices" in opt: print("\t\t") for choice in opt["choices"]: print("\t\t\t") elif opt["getopt"].count(":") > 0: t = opt.get("type", "string") print("\t\t") else: print("\t\t") print("\t\t" + shortdesc + "") print("\t") print("") print("") (available_actions, _) = _get_available_actions(avail_opt) if "on" in available_actions: available_actions.remove("on") on_target = ' on_target="1"' if avail_opt.count("on_target") else '' print("\t" % (on_target, avail_opt.count("fabric_fencing"))) for action in available_actions: print("\t" % (action)) print("") print("") def process_input(avail_opt): avail_opt.extend(_add_dependency_options(avail_opt)) # @todo: this should be put elsewhere? os.putenv("LANG", "C") os.putenv("LC_ALL", "C") if "port_as_ip" in avail_opt: avail_opt.append("port") if len(sys.argv) > 1: opt = _parse_input_cmdline(avail_opt) else: opt = _parse_input_stdin(avail_opt) if "--port-as-ip" in opt and "--plug" in opt: opt["--ip"] = opt["--plug"] return opt ## ## This function checks input and answers if we want to have same answers ## in each of the fencing agents. It looks for possible errors and run ## password script to set a correct password ###### def check_input(device_opt, opt, other_conditions = False): device_opt.extend(_add_dependency_options(device_opt)) options = dict(opt) options["device_opt"] = device_opt _update_metadata(options) options = _set_default_values(options) options["--action"] = options["--action"].lower() ## In special cases (show help, metadata or version) we don't need to check anything ##### # OCF compatibility if options["--action"] == "meta-data": options["--action"] = "metadata" if options["--action"] in ["metadata", "manpage"] or any(k in options for k in ("--help", "--version")): return options if "--verbose" in options: logging.getLogger().setLevel(logging.DEBUG) formatter = logging.Formatter(LOG_FORMAT) ## add logging to syslog logging.getLogger().addHandler(SyslogLibHandler()) if "--quiet" not in options: ## add logging to stderr stderrHandler = logging.StreamHandler(sys.stderr) stderrHandler.setFormatter(formatter) logging.getLogger().addHandler(stderrHandler) (acceptable_actions, _) = _get_available_actions(device_opt) if 1 == device_opt.count("fabric_fencing"): acceptable_actions.extend(["enable", "disable"]) if 0 == acceptable_actions.count(options["--action"]): fail_usage("Failed: Unrecognised action '" + options["--action"] + "'") ## Compatibility layer ##### if options["--action"] == "enable": options["--action"] = "on" if options["--action"] == "disable": options["--action"] = "off" if options["--action"] == "validate-all" and not other_conditions: if not _validate_input(options, False): fail_usage("validate-all failed") sys.exit(EC_OK) else: _validate_input(options, True) if "--debug-file" in options: try: debug_file = logging.FileHandler(options["--debug-file"]) debug_file.setLevel(logging.DEBUG) debug_file.setFormatter(formatter) logging.getLogger().addHandler(debug_file) except IOError: logging.error("Unable to create file %s", options["--debug-file"]) fail_usage("Failed: Unable to create file " + options["--debug-file"]) if "--snmp-priv-passwd-script" in options: options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() if "--password-script" in options: options["--password"] = os.popen(options["--password-script"]).read().rstrip() return options ## Obtain a power status from possibly more than one plug ## "on" is returned if at least one plug is ON ###### def get_multi_power_fn(connection, options, get_power_fn): status = "off" plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug plug_status = get_power_fn(connection, options) if plug_status != "off": status = plug_status return status def async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts): plugs = options["--plugs"] if "--plugs" in options else [""] for _ in range(retry_attempts): for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug set_power_fn(connection, options) time.sleep(int(options["--power-wait"])) for _ in range(int(options["--power-timeout"])): if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: time.sleep(1) else: return True return False def sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts): success = True plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug for retry in range(retry_attempts): if sync_set_power_fn(connection, options): break if retry == retry_attempts-1: success = False time.sleep(int(options["--power-wait"])) return success def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, retry_attempts=1): if set_power_fn != None: if get_power_fn != None: return async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts) elif sync_set_power_fn != None: return sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts) return False def show_docs(options, docs=None): device_opt = options["device_opt"] if docs == None: docs = {} docs["shortdesc"] = "Fence agent" docs["longdesc"] = "" if "--help" in options: usage(device_opt) sys.exit(0) if options.get("--action", "") in ["metadata", "manpage"]: if "port_as_ip" in device_opt: device_opt.remove("separator") metadata(options, device_opt, docs) sys.exit(0) if "--version" in options: print(RELEASE_VERSION) sys.exit(0) def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None, sync_set_power_fn=None): result = 0 try: if "--plug" in options: options["--plugs"] = options["--plug"].split(",") ## Process options that manipulate fencing device ##### if (options["--action"] in ["list", "list-status"]) or \ ((options["--action"] == "monitor") and 1 == options["device_opt"].count("port") and \ 0 == options["device_opt"].count("port_as_ip")): if 0 == options["device_opt"].count("port"): print("N/A") elif get_outlet_list == None: ## @todo: exception? ## This is just temporal solution, we will remove default value ## None as soon as all existing agent will support this operation print("NOTICE: List option is not working on this device yet") else: options["--original-action"] = options["--action"] options["--action"] = "list" outlets = get_outlet_list(connection, options) options["--action"] = options["--original-action"] del options["--original-action"] ## keys can be numbers (port numbers) or strings (names of VM, UUID) for outlet_id in list(outlets.keys()): (alias, status) = outlets[outlet_id] if status is None or (not status.upper() in ["ON", "OFF"]): status = "UNKNOWN" status = status.upper() if options["--action"] == "list": print(outlet_id + options["--separator"] + alias) elif options["--action"] == "list-status": print(outlet_id + options["--separator"] + alias + options["--separator"] + status) return if options["--action"] == "monitor" and not "port" in options["device_opt"] and "no_status" in options["device_opt"]: # Unable to do standard monitoring because 'status' action is not available return 0 status = None if not "no_status" in options["device_opt"]: status = get_multi_power_fn(connection, options, get_power_fn) if status != "on" and status != "off": fail(EC_STATUS) if options["--action"] == status: if not (status == "on" and "force_on" in options["device_opt"]): print("Success: Already %s" % (status.upper())) return 0 if options["--action"] == "on": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, 1 + int(options["--retry-on"])): print("Success: Powered ON") else: fail(EC_WAITING_ON) elif options["--action"] == "off": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): print("Success: Powered OFF") else: fail(EC_WAITING_OFF) elif options["--action"] == "reboot": power_on = False if options.get("--method", "").lower() == "cycle" and reboot_cycle_fn is not None: for _ in range(1, 1 + int(options["--retry-on"])): if reboot_cycle_fn(connection, options): power_on = True break if not power_on: fail(EC_TIMED_OUT) else: if status != "off": options["--action"] = "off" if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): fail(EC_WAITING_OFF) options["--action"] = "on" try: power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, int(options["--retry-on"])) except Exception as ex: # an error occured during power ON phase in reboot # fence action was completed succesfully even in that case logging.warning("%s", str(ex)) # switch back to original action for the case it is used lateron options["--action"] = "reboot" if power_on == False: # this should not fail as node was fenced succesfully logging.error('Timed out waiting to power ON\n') print("Success: Rebooted") elif options["--action"] == "status": print("Status: " + status.upper()) if status.upper() == "OFF": result = 2 elif options["--action"] == "monitor": pass except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) except pycurl.error as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) except socket.timeout as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) return result def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )|(username: )|(User Name :)"): run_delay(options) if "eol" not in options: options["eol"] = "\r\n" if "--command-prompt" in options and type(options["--command-prompt"]) is not list: options["--command-prompt"] = [options["--command-prompt"]] try: if "--ssl" in options: conn = _open_ssl_connection(options) elif "--ssh" in options and "--identity-file" not in options: conn = _login_ssh_with_password(options, re_login_string) elif "--ssh" in options and "--identity-file" in options: conn = _login_ssh_with_identity_file(options) else: conn = _login_telnet(options, re_login_string) except pexpect.EOF as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) return conn def is_executable(path): if os.path.exists(path): stats = os.stat(path) if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK): return True return False def run_command(options, command, timeout=None, env=None, log_command=None): if timeout is None and "--power-timeout" in options: timeout = options["--power-timeout"] if timeout is not None: timeout = float(timeout) logging.info("Executing: %s\n", log_command or command) try: process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, # decodes newlines and in python3 also converts bytes to str universal_newlines=(sys.version_info[0] > 2)) except OSError: fail_usage("Unable to run %s\n" % command) thread = threading.Thread(target=process.wait) thread.start() thread.join(timeout) if thread.is_alive(): process.kill() fail(EC_TIMED_OUT, stop=(int(options.get("retry", 0)) < 1)) status = process.wait() (pipe_stdout, pipe_stderr) = process.communicate() process.stdout.close() process.stderr.close() logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) return (status, pipe_stdout, pipe_stderr) def run_delay(options, reserve=0, result=0): ## Delay is important for two-node clusters fencing ## but we do not need to delay 'status' operations ## and get us out quickly if we already know that we are gonna fail ## still wanna do something right before fencing? - reserve some time if options["--action"] in ["off", "reboot"] \ and options["--delay"] != "0" \ and result == 0 \ and reserve >= 0: time_left = 1 + int(options["--delay"]) - (time.time() - run_delay.time_start) - reserve if time_left > 0: logging.info("Delay %d second(s) before logging in to the fence device", time_left) time.sleep(time_left) # mark time when fence-agent is started run_delay.time_start = time.time() def fence_logout(conn, logout_string, sleep=0): # Logout is not required part of fencing but we should attempt to do it properly # In some cases our 'exit' command is faster and we can not close connection as it # was already closed by fencing device try: conn.send_eol(logout_string) time.sleep(sleep) conn.close() except OSError: pass except pexpect.ExceptionPexpect: pass # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is # in format a.b.c.d...z and returned dict has key only z def array_to_dict(array): return dict([[x[0].split(".")[-1], x[1]] for x in array]) ## Own logger handler that uses old-style syslog handler as otherwise everything is sourced ## from /dev/syslog class SyslogLibHandler(logging.StreamHandler): """ A handler class that correctly push messages into syslog """ def emit(self, record): syslog_level = { logging.CRITICAL:syslog.LOG_CRIT, logging.ERROR:syslog.LOG_ERR, logging.WARNING:syslog.LOG_WARNING, logging.INFO:syslog.LOG_INFO, logging.DEBUG:syslog.LOG_DEBUG, logging.NOTSET:syslog.LOG_DEBUG, }[record.levelno] msg = self.format(record) # syslos.syslog can not have 0x00 character inside or exception is thrown syslog.syslog(syslog_level, msg.replace("\x00", "\n")) return def _open_ssl_connection(options): gnutls_opts = "" ssl_opts = "" if "--notls" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" elif "--tls1.0" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION\"" # --ssl is same as the --ssl-secure; it means we want to verify certificate in these cases if "--ssl-insecure" in options: ssl_opts = "--insecure" command = '%s %s %s --crlf -p %s %s' % \ (options["--gnutlscli-path"], gnutls_opts, ssl_opts, options["--ipport"], options["--ip"]) try: conn = fspawn(options, command) except pexpect.ExceptionPexpect as ex: logging.error("%s\n", str(ex)) sys.exit(EC_GENERIC_ERROR) return conn def _login_ssh_with_identity_file(options): if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -i %s -p %s' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], \ options["--identity-file"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) result = conn.log_expect(["Enter passphrase for key '" + options["--identity-file"] + "':", \ "Are you sure you want to continue connecting (yes/no)?"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") result = conn.log_expect( ["Enter passphrase for key '" + options["--identity-file"]+"':"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 0: if "--password" in options: conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) else: fail_usage("Failed: You have to enter passphrase (-p) for identity file") return conn def _login_telnet(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.log_expect(re_login, int(options["--login-timeout"])) conn.send_eol(options["--username"]) ## automatically change end of line separator screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) if re_login.search(screen) != None: options["eol"] = "\n" conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) elif re_pass.search(screen) == None: conn.log_expect(re_pass, int(options["--shell-timeout"])) try: conn.send_eol(options["--password"]) valid_password = conn.log_expect([re_login] + \ options["--command-prompt"], int(options["--shell-timeout"])) if valid_password == 0: ## password is invalid or we have to change EOL separator options["eol"] = "\r" conn.send_eol("") screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) ## after sending EOL the fence device can either show 'Login' or 'Password' if re_login.search(conn.after + screen) != None: conn.send_eol("") conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) conn.send_eol(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) except KeyError: fail(EC_PASSWORD_MISSING) return conn def _login_ssh_with_password(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -p %s -o PubkeyAuthentication=no' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) if "telnet_over_ssh" in options: # This is for stupid ssh servers (like ALOM) which behave more like telnet # (ignore name and display login prompt) result = conn.log_expect( \ [re_login, "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") # Host identity confirm conn.log_expect(re_login, int(options["--login-timeout"])) conn.sendline(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) else: result = conn.log_expect( \ ["ssword:", "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") conn.log_expect("ssword:", int(options["--login-timeout"])) conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) return conn # # To update metadata, we change values in all_opt def _update_metadata(options): device_opt = options["device_opt"] if device_opt.count("login") and device_opt.count("no_login") == 0: all_opt["login"]["required"] = "1" else: all_opt["login"]["required"] = "0" if device_opt.count("port_as_ip"): all_opt["ipaddr"]["required"] = "0" all_opt["port"]["required"] = "0" (available_actions, default_value) = _get_available_actions(device_opt) all_opt["action"]["default"] = default_value actions_with_default = \ [x if not x == all_opt["action"]["default"] else x + " (default)" for x in available_actions] all_opt["action"]["help"] = \ "-o, --action=[action] Action: %s" % (_join_wrap(actions_with_default, last_separator=" or ")) if device_opt.count("ipport"): default_value = None default_string = None if "default" in all_opt["ipport"]: default_value = all_opt["ipport"]["default"] elif device_opt.count("web") and device_opt.count("ssl"): default_value = "80" default_string = "(default 80, 443 if --ssl option is used)" elif device_opt.count("telnet") and device_opt.count("secure"): default_value = "23" default_string = "(default 23, 22 if --ssh option is used)" else: tcp_ports = {"community" : "161", "secure" : "22", "telnet" : "23", "web" : "80", "ssl" : "443"} # all cases where next command returns multiple results are covered by previous blocks protocol = [x for x in ["community", "secure", "ssl", "web", "telnet"] if device_opt.count(x)][0] default_value = tcp_ports[protocol] if default_string is None: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use (default %s)" % \ (default_value) else: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use\n" + " "*40 + default_string def _set_default_values(options): if "ipport" in options["device_opt"]: if not "--ipport" in options: if "default" in all_opt["ipport"]: options["--ipport"] = all_opt["ipport"]["default"] elif "community" in options["device_opt"]: options["--ipport"] = "161" elif "--ssh" in options or all_opt["secure"].get("default", "0") == "1": options["--ipport"] = "22" elif "--ssl" in options or all_opt["ssl"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-secure" in options or all_opt["ssl_secure"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-insecure" in options or all_opt["ssl_insecure"].get("default", "0") == "1": options["--ipport"] = "443" elif "web" in options["device_opt"]: options["--ipport"] = "80" elif "telnet" in options["device_opt"]: options["--ipport"] = "23" if "--ipport" in options: all_opt["ipport"]["default"] = options["--ipport"] for opt in options["device_opt"]: if "default" in all_opt[opt] and not opt == "ipport": getopt_long = "--" + all_opt[opt]["longopt"] if getopt_long not in options: options[getopt_long] = all_opt[opt]["default"] return options # stop = True/False : exit fence agent when problem is encountered def _validate_input(options, stop = True): device_opt = options["device_opt"] valid_input = True if "--username" not in options and \ device_opt.count("login") and (device_opt.count("no_login") == 0): valid_input = False fail_usage("Failed: You have to set login name", stop) if device_opt.count("ipaddr") and "--ip" not in options and "--managed" not in options and "--target" not in options: valid_input = False fail_usage("Failed: You have to enter fence address", stop) if device_opt.count("no_password") == 0: if 0 == device_opt.count("identity_file"): if not ("--password" in options or "--password-script" in options): valid_input = False fail_usage("Failed: You have to enter password or password script", stop) else: if not ("--password" in options or \ "--password-script" in options or "--identity-file" in options): valid_input = False fail_usage("Failed: You have to enter password, password script or identity file", stop) if "--ssh" not in options and "--identity-file" in options: valid_input = False fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) if "--identity-file" in options and not os.path.isfile(options["--identity-file"]): valid_input = False fail_usage("Failed: Identity file " + options["--identity-file"] + " does not exist", stop) if (0 == ["list", "list-status", "monitor"].count(options["--action"])) and \ "--plug" not in options and device_opt.count("port") and \ device_opt.count("no_port") == 0 and not device_opt.count("port_as_ip"): valid_input = False fail_usage("Failed: You have to enter plug number or machine identification", stop) if "--plug" in options and len(options["--plug"].split(",")) > 1 and \ "--method" in options and options["--method"] == "cycle": valid_input = False fail_usage("Failed: Cannot use --method cycle for more than 1 plug", stop) for failed_opt in _get_opts_with_invalid_choices(options): valid_input = False fail_usage("Failed: You have to enter a valid choice for %s from the valid values: %s" % \ ("--" + all_opt[failed_opt]["longopt"], str(all_opt[failed_opt]["choices"])), stop) for failed_opt in _get_opts_with_invalid_types(options): valid_input = False if all_opt[failed_opt]["type"] == "second": fail_usage("Failed: The value you have entered for %s is not a valid time in seconds" % \ ("--" + all_opt[failed_opt]["longopt"]), stop) else: fail_usage("Failed: The value you have entered for %s is not a valid %s" % \ ("--" + all_opt[failed_opt]["longopt"], all_opt[failed_opt]["type"]), stop) return valid_input def _encode_html_entities(text): return text.replace("&", "&").replace('"', """).replace('<', "<"). \ replace('>', ">").replace("'", "'") def _prepare_getopt_args(options): getopt_string = "" longopt_list = [] for k in options: if k in all_opt and all_opt[k]["getopt"] != ":": # getopt == ":" means that opt is without short getopt, but has value getopt_string += all_opt[k]["getopt"] elif k not in all_opt: fail_usage("Parse error: unknown option '"+k+"'") if k in all_opt and "longopt" in all_opt[k]: if all_opt[k]["getopt"].endswith(":"): longopt_list.append(all_opt[k]["longopt"] + "=") else: longopt_list.append(all_opt[k]["longopt"]) return (getopt_string, longopt_list) def _parse_input_stdin(avail_opt): opt = {} name = "" mapping_longopt_names = dict([(all_opt[o].get("longopt"), o) for o in avail_opt]) 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] value = re.sub("^\"(.*)\"$", "\\1", value) if name.replace("-", "_") in mapping_longopt_names: name = mapping_longopt_names[name.replace("-", "_")] elif name.replace("_", "-") in mapping_longopt_names: name = mapping_longopt_names[name.replace("_", "-")] if avail_opt.count(name) == 0 and name in ["nodename"]: continue elif avail_opt.count(name) == 0: logging.warning("Parse error: Ignoring unknown option '%s'\n", line) continue if all_opt[name]["getopt"].endswith(":"): opt["--"+all_opt[name]["longopt"].rstrip(":")] = value elif value.lower() in ["1", "yes", "on", "true"]: opt["--"+all_opt[name]["longopt"]] = "1" else: logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) return opt def _parse_input_cmdline(avail_opt): filtered_opts = {} _verify_unique_getopt(avail_opt) (getopt_string, longopt_list) = _prepare_getopt_args(avail_opt) try: (entered_opt, left_arg) = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list) if len(left_arg) > 0: logging.warning("Unused arguments on command line: %s" % (str(left_arg))) except getopt.GetoptError as error: fail_usage("Parse error: " + error.msg) for opt in avail_opt: filtered_opts.update({opt : all_opt[opt]}) # Short and long getopt names are changed to consistent "--" + long name (e.g. --username) long_opts = {} for arg_name in list(dict(entered_opt).keys()): all_key = [key for (key, value) in list(filtered_opts.items()) \ if "--" + value.get("longopt", "") == arg_name or "-" + value.get("getopt", "").rstrip(":") == arg_name][0] long_opts["--" + filtered_opts[all_key]["longopt"]] = dict(entered_opt)[arg_name] # This test is specific because it does not apply to input on stdin if "port_as_ip" in avail_opt and not "--port-as-ip" in long_opts and "--plug" in long_opts: fail_usage("Parser error: option -n/--plug is not recognized") return long_opts # for ["John", "Mary", "Eli"] returns "John, Mary and Eli" def _join2(words, normal_separator=", ", last_separator=" and "): if len(words) <= 1: return "".join(words) else: return last_separator.join([normal_separator.join(words[:-1]), words[-1]]) def _join_wrap(words, normal_separator=", ", last_separator=" and ", first_indent=42): x = _join2(words, normal_separator, last_separator) wrapper = textwrap.TextWrapper() wrapper.initial_indent = " "*first_indent wrapper.subsequent_indent = " "*40 wrapper.width = 85 wrapper.break_on_hyphens = False wrapper.break_long_words = False wrapped_text = "" for line in wrapper.wrap(x): wrapped_text += line + "\n" return wrapped_text.lstrip().rstrip("\n") def _get_opts_with_invalid_choices(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "choices" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] if longopt in options: options[longopt] = options[longopt].upper() if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: options_failed.append(opt) return options_failed def _get_opts_with_invalid_types(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "type" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] if longopt in options: if all_opt[opt]["type"] in ["integer", "second"]: try: number = int(options["--" + all_opt[opt]["longopt"]]) except ValueError: options_failed.append(opt) return options_failed def _verify_unique_getopt(avail_opt): used_getopt = set() for opt in avail_opt: getopt_value = all_opt[opt].get("getopt", "").rstrip(":") if getopt_value and getopt_value in used_getopt: fail_usage("Short getopt for %s (-%s) is not unique" % (opt, getopt_value)) else: used_getopt.add(getopt_value) def _get_available_actions(device_opt): available_actions = ["on", "off", "reboot", "status", "list", "list-status", \ "monitor", "metadata", "manpage", "validate-all"] default_value = "reboot" if device_opt.count("fabric_fencing"): available_actions.remove("reboot") default_value = "off" if device_opt.count("no_status"): available_actions.remove("status") if device_opt.count("no_on"): available_actions.remove("on") if device_opt.count("no_off"): available_actions.remove("off") if not device_opt.count("separator"): available_actions.remove("list") available_actions.remove("list-status") if device_opt.count("diag"): available_actions.append("diag") return (available_actions, default_value) diff --git a/tests/data/metadata/fence_kdump.xml b/tests/data/metadata/fence_kdump.xml index 89a414b8..719fd44c 100644 --- a/tests/data/metadata/fence_kdump.xml +++ b/tests/data/metadata/fence_kdump.xml @@ -1,57 +1,75 @@ - -The fence_kdump agent is intended to be used with with kdump service. + +fence_kdump is an I/O fencing agent to be used with the kdump +crash recovery service. When the fence_kdump agent is invoked, +it will listen for a message from the failed node that acknowledges +that the failed node it executing the kdump crash kernel. +Note that fence_kdump is not a replacement for traditional +fencing methods. The fence_kdump agent can only detect that a +node has entered the kdump crash recovery service. This allows the +kdump crash recovery service complete without being preempted by +traditional power fencing methods. + +Note: the "off" action listen for message from failed node that +acknowledges node has entered kdump crash recovery service. If a valid +message is received from the failed node, the node is considered to be +fenced and the agent returns success. Failure to receive a valid +message from the failed node in the given timeout period results in +fencing failure. http://www.kernel.org/pub/linux/utils/kernel/kexec/ - + - Name or IP address of node to be fenced + Name or IP address of node to be fenced. This option is required for +the "off" action. - + - Port number + IP port number that the fence_kdump agent will use to listen for +messages. - + - Network family + IP network family. Force the fence_kdump agent to use a specific +family. The value for FAMILY can be "auto", "ipv4", or +"ipv6". - + - Fencing action + Fencing action to perform. The value for ACTION can be either +"off" or "metadata". - + - Timeout in seconds - - - - - Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. + Number of seconds to wait for message from failed node. If no message +is received within TIMEOUT seconds, the fence_kdump agent +returns failure. Print verbose output Print version Print usage + diff --git a/tests/data/metadata/fence_vmware.xml b/tests/data/metadata/fence_vmware.xml index e56b39f5..48fc408b 100644 --- a/tests/data/metadata/fence_vmware.xml +++ b/tests/data/metadata/fence_vmware.xml @@ -1,199 +1,199 @@ fence_vmware is an I/O Fencing agent which can be used with the VMware ESX, VMware ESXi or VMware Server to fence virtual machines. Before you can use this agent, it must be installed VI Perl Toolkit or vmrun command on every node you want to make fencing. VI Perl Toolkit is preferred for VMware ESX/ESXi and Virtual Center. Vmrun command is only solution for VMware Server 1/2 (this command will works against ESX/ESXi 3.5 up2 and VC up2 too, but not cluster aware!) and is available as part of VMware VIX API SDK package. VI Perl and VIX API SDK are both available from VMware web pages (not int RHEL repository!). -You can specify type of VMware you are connecting to with \fB-d\fP switch (or \fIvmware_type\fR for stdin). Possible values are esx, server2 and server1.Default value is esx, which will use VI Perl. With server1 and server2, vmrun command is used. +You can specify type of VMware you are connecting to with -d switch (or vmware_type for stdin). Possible values are esx, server2 and server1.Default value is esx, which will use VI Perl. With server1 and server2, vmrun command is used. After you have successfully installed VI Perl Toolkit or VIX API, you should be able to run fence_vmware_helper (part of this agent) or vmrun command. This agent supports only vmrun from version 2.0.0 (VIX API 1.6.0). http://www.vmware.com Fencing action Command to execute Identity file (private key) for SSH Forces agent to use IPv4 addresses only Forces agent to use IPv6 addresses only IP address or hostname of fencing device IP address or hostname of fencing device TCP/UDP port to use for connection with device Login name Login password or passphrase Script to run to retrieve password Login password or passphrase Script to run to retrieve password IP address or hostname of fencing device (together with --port-as-ip) IP address or hostname of fencing device (together with --port-as-ip) Use SSH connection Use SSH connection SSH options to use Login name Type of VMware to connect VMWare datacenter filter Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. Verbose mode Write debug information to given file Write debug information to given file Display version information and exit Display help and exit Wait X seconds before fencing is started Wait X seconds for cmd prompt after login Make "port/plug" to be an alias to IP address Test X seconds for status change after ON/OFF Wait X seconds after issuing ON/OFF Wait X seconds for cmd prompt after issuing command Count of attempts to retry power on Path to ssh binary