diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:50:17 +0000 |
commit | 86ed03f8adee56c050c73018537371c230a664a6 (patch) | |
tree | eae3d04cdf1c49848e5a671327ab38297f4acb0d /agents/vmware | |
parent | Initial commit. (diff) | |
download | fence-agents-86ed03f8adee56c050c73018537371c230a664a6.tar.xz fence-agents-86ed03f8adee56c050c73018537371c230a664a6.zip |
Adding upstream version 4.12.1.upstream/4.12.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | agents/vmware/fence_vmware.py | 336 | ||||
-rw-r--r-- | agents/vmware/fence_vmware_helper.pl | 276 | ||||
-rw-r--r-- | agents/vmware_rest/fence_vmware_rest.py | 229 | ||||
-rw-r--r-- | agents/vmware_soap/fence_vmware_soap.py | 265 | ||||
-rw-r--r-- | agents/vmware_vcloud/fence_vmware_vcloud.py | 214 |
5 files changed, 1320 insertions, 0 deletions
diff --git a/agents/vmware/fence_vmware.py b/agents/vmware/fence_vmware.py new file mode 100644 index 0000000..bc1785f --- /dev/null +++ b/agents/vmware/fence_vmware.py @@ -0,0 +1,336 @@ +#!@PYTHON@ -tt + +# +# The Following agent has been tested on: +# vmrun 2.0.0 build-116503 (from VMware Server 2.0) against: +# VMware ESX 4.0.0 +# VMware vCenter 4.0.0 +# VMware ESX 3.5 +# VMware Server 2.0.0 +# VMware ESXi 3.5 update 2 +# VMware Server 1.0.7 (works but list/status show only running VMs) +# +# VI Perl API 1.6 against: +# VMware ESX 4.0.0 +# VMware vCenter 4.0.0 +# VMware ESX 3.5 +# VMware ESXi 3.5 update 2 +# VMware Virtual Center 2.5 +# +# VMware vSphere SDK for Perl 4.0.0 against: +# VMware ESX 4.0.0 +# VMware vCenter 4.0.0 +# + +import sys, re, pexpect +import logging +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay, frun + +### CONSTANTS #### +# VMware type is ESX/ESXi/VC +VMWARE_TYPE_ESX = 0 +# VMware type is Server 1.x +VMWARE_TYPE_SERVER1 = 1 +# VMware type is Server 2.x and/or ESX 3.5 up2, ESXi 3.5 up2, VC 2.5 up2 +VMWARE_TYPE_SERVER2 = 2 + +# Minimum required version of vmrun command +VMRUN_MINIMUM_REQUIRED_VERSION = 2 + +# Default path to vmhelper command +VMHELPER_COMMAND = "fence_vmware_helper" +# Default path to vmrun command +VMRUN_COMMAND = "/usr/bin/vmrun" +# Default type of vmware +VMWARE_DEFAULT_TYPE = "esx" + +#### GLOBAL VARIABLES #### +# Internal type. One of VMWARE_TYPE_, set by #vmware_check_vmware_type +vmware_internal_type = VMWARE_TYPE_ESX + +# If ESX is disconnected, say, that VM is off (don't return previous state) +vmware_disconnected_hack = False + +### FUNCTIONS #### + +#Split string in simplified DSV format to array of items +def dsv_split(dsv_str): + delimiter_c = ':' + escape_c = '\\' + + res = [] + status = 0 + tmp_str = "" + + for x in dsv_str: + if status == 0: + if x == delimiter_c: + res.append(tmp_str) + tmp_str = "" + elif x == escape_c: + status = 1 + else: + tmp_str += x + elif status == 1: + if x == delimiter_c: + tmp_str += delimiter_c + elif x == escape_c: + tmp_str += escape_c + else: + tmp_str += escape_c+x + status = 0 + + if tmp_str != "": + res.append(tmp_str) + + return res + +# Quote string for proper existence in quoted string used for pexpect.run function +# Ex. test'this will return test'\''this. So pexpect run will really pass ' to argument +def quote_for_run(text): + dstr = '' + + for c in text: + if c == r"'": + dstr += "'\\''" + else: + dstr += c + + return dstr + +# Return string with command and additional parameters (something like vmrun -h 'host' +def vmware_prepare_command(options, add_login_params, additional_params): + res = options["--exec"] + + if add_login_params: + if vmware_internal_type == VMWARE_TYPE_ESX: + res += " --server '%s' --username '%s' --password '%s' "% (quote_for_run(options["--ip"]), + quote_for_run(options["--username"]), + quote_for_run(options["--password"])) + elif vmware_internal_type == VMWARE_TYPE_SERVER2: + res += " -h 'https://%s/sdk' -u '%s' -p '%s' -T server "% (quote_for_run(options["--ip"]), + quote_for_run(options["--username"]), + quote_for_run(options["--password"])) + elif vmware_internal_type == VMWARE_TYPE_SERVER1: + host_name_array = options["--ip"].split(':') + + res += " -h '%s' -u '%s' -p '%s' -T server1 "% (quote_for_run(host_name_array[0]), + quote_for_run(options["--username"]), + quote_for_run(options["--password"])) + if len(host_name_array) > 1: + res += "-P '%s' "% (quote_for_run(host_name_array[1])) + + if "--vmware-datacenter" in options and vmware_internal_type == VMWARE_TYPE_ESX: + res += "--datacenter '%s' "% (quote_for_run(options["--vmware-datacenter"])) + + if additional_params != "": + res += additional_params + + return res + +# Run command with timeout and parameters. Internaly uses vmware_prepare_command. Returns string +# with output from vmrun command. If something fails (command not found, exit code is not 0), fail_usage +# function is called (and never return). +def vmware_run_command(options, add_login_params, additional_params, additional_timeout): + command = vmware_prepare_command(options, add_login_params, additional_params) + + try: + logging.debug("%s\n", command) + + (res_output, res_code) = frun(command, + int(options["--shell-timeout"]) + int(options["--login-timeout"]) + additional_timeout, True) + + if res_code == None: + fail(EC_TIMED_OUT) + if res_code != 0 and add_login_params: + logging.debug("%s\n", res_output) + fail_usage("%s returned %s"% (options["--exec"], res_output)) + else: + logging.debug("%s\n", res_output) + + except pexpect.ExceptionPexpect: + fail_usage("Cannot run command %s"% (options["--exec"])) + + return res_output + +# Get outlet list with status as hash table. If you will use add_vm_name, only VM with vmname is +# returned. This is used in get_status function +def vmware_get_outlets_vi(options, add_vm_name): + outlets = {} + + if add_vm_name: + all_machines = vmware_run_command(options, True, + ("--operation status --vmname '%s'"% (quote_for_run(options["--plug"]))), 0) + else: + all_machines = vmware_run_command(options, True, "--operation list", int(options["--power-timeout"])) + + all_machines_array = all_machines.splitlines() + + for machine in all_machines_array: + machine_array = dsv_split(machine) + if len(machine_array) == 4: + if machine_array[0] in outlets: + fail_usage("Failed. More machines with same name %s found!"%(machine_array[0])) + + if vmware_disconnected_hack: + outlets[machine_array[0]] = ("", ( + ((machine_array[2].lower() in ["poweredon"]) and + (machine_array[3].lower() == "connected")) + and "on" or "off")) + else: + outlets[machine_array[0]] = ("", ((machine_array[2].lower() in ["poweredon"]) and "on" or "off")) + return outlets + +# Get outlet list with status as hash table. +def vmware_get_outlets_vix(options): + outlets = {} + + running_machines = vmware_run_command(options, True, "list", 0) + running_machines_array = running_machines.splitlines()[1:] + + if vmware_internal_type == VMWARE_TYPE_SERVER2: + all_machines = vmware_run_command(options, True, "listRegisteredVM", 0) + all_machines_array = all_machines.splitlines()[1:] + elif vmware_internal_type == VMWARE_TYPE_SERVER1: + all_machines_array = running_machines_array + + for machine in all_machines_array: + if machine != "": + outlets[machine] = ("", ((machine in running_machines_array) and "on" or "off")) + + return outlets + +def get_outlets_status(conn, options): + del conn + + if vmware_internal_type == VMWARE_TYPE_ESX: + return vmware_get_outlets_vi(options, False) + if vmware_internal_type == VMWARE_TYPE_SERVER1 or vmware_internal_type == VMWARE_TYPE_SERVER2: + return vmware_get_outlets_vix(options) + +def get_power_status(conn, options): + if vmware_internal_type == VMWARE_TYPE_ESX: + outlets = vmware_get_outlets_vi(options, True) + else: + outlets = get_outlets_status(conn, options) + + if vmware_internal_type == VMWARE_TYPE_SERVER2 or vmware_internal_type == VMWARE_TYPE_ESX: + if not options["--plug"] in outlets: + fail_usage("Failed: You have to enter existing name of virtual machine!") + else: + return outlets[options["--plug"]][1] + elif vmware_internal_type == VMWARE_TYPE_SERVER1: + return (options["--plug"] in outlets) and "on" or "off" + +def set_power_status(conn, options): + del conn + + if vmware_internal_type == VMWARE_TYPE_ESX: + additional_params = "--operation %s --vmname '%s'" % \ + ((options["--action"] == "on" and "on" or "off"), quote_for_run(options["--plug"])) + elif vmware_internal_type == VMWARE_TYPE_SERVER1 or vmware_internal_type == VMWARE_TYPE_SERVER2: + additional_params = "%s '%s'" % \ + ((options["--action"] == "on" and "start" or "stop"), quote_for_run(options["--plug"])) + if options["--action"] == "off": + additional_params += " hard" + + vmware_run_command(options, True, additional_params, int(options["--power-timeout"])) + +# Returns True, if user uses supported vmrun version (currently >=2.0.0) otherwise False. +def vmware_is_supported_vmrun_version(options): + vmware_help_str = vmware_run_command(options, False, "", 0) + version_re = re.search(r"vmrun version (\d\.(\d[\.]*)*)", vmware_help_str.lower()) + if version_re == None: + return False # Looks like this "vmrun" is not real vmrun + + version_array = version_re.group(1).split(".") + + try: + if int(version_array[0]) < VMRUN_MINIMUM_REQUIRED_VERSION: + return False + except Exception: + return False + + return True + +# Check vmware type, set vmware_internal_type to one of VMWARE_TYPE_ value and +# options["--exec"] to path (if not specified) +def vmware_check_vmware_type(options): + global vmware_internal_type + + options["--vmware_type"] = options["--vmware_type"].lower() + + if options["--vmware_type"] == "esx": + vmware_internal_type = VMWARE_TYPE_ESX + if "--exec" not in options: + options["--exec"] = VMHELPER_COMMAND + elif options["--vmware_type"] == "server2": + vmware_internal_type = VMWARE_TYPE_SERVER2 + if "--exec" not in options: + options["--exec"] = VMRUN_COMMAND + elif options["--vmware_type"] == "server1": + vmware_internal_type = VMWARE_TYPE_SERVER1 + if "--exec" not in options: + options["--exec"] = VMRUN_COMMAND + else: + fail_usage("vmware_type can be esx,server2 or server1!") + +# Main agent method +def main(): + device_opt = ["ipaddr", "login", "passwd", "secure", + "exec", "vmware_type", "vmware_datacenter"] + + atexit.register(atexit_handler) + + all_opt["secure"]["default"] = "1" + all_opt["vmware_type"]["default"] = VMWARE_DEFAULT_TYPE + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for VMWare" + docs["longdesc"] = "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.\ +\n.P\n\ +Before you can use this agent, it must be installed VI Perl Toolkit or \ +vmrun command on every node you want to make fencing.\ +\n.P\n\ +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!). \ +\n.P\n\ +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.\ +\n.P\n\ +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)." + docs["vendorurl"] = "http://www.vmware.com" + show_docs(options, docs) + + run_delay(options) + + # Check vmware type and set path + vmware_check_vmware_type(options) + + # Test user vmrun command version + if vmware_internal_type == VMWARE_TYPE_SERVER1 or vmware_internal_type == VMWARE_TYPE_SERVER2: + if not vmware_is_supported_vmrun_version(options): + fail_usage("Unsupported version of vmrun command! You must use at least version %d!" % + (VMRUN_MINIMUM_REQUIRED_VERSION)) + + # Operate the fencing device + result = fence_action(None, options, set_power_status, get_power_status, get_outlets_status) + + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/vmware/fence_vmware_helper.pl b/agents/vmware/fence_vmware_helper.pl new file mode 100644 index 0000000..a0b5cea --- /dev/null +++ b/agents/vmware/fence_vmware_helper.pl @@ -0,0 +1,276 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $ME = $0; + +END { + defined fileno STDOUT or return; + close STDOUT and return; + warn "$ME: failed to close standard output: $!\n"; + $? ||= 1; +} + +my ($RELEASE_VERSION, $REDHAT_COPYRIGHT, $BUILD_DATE); + +#BEGIN_VERSION_GENERATION +$RELEASE_VERSION=""; +$REDHAT_COPYRIGHT=""; +$BUILD_DATE=""; +#END_VERSION_GENERATION + +#### FUNCTIONS ##### +# Show error message +sub show_error { + print STDERR @_; +} + +sub my_exit { + my ($exit_code)=@_; + + # Disconnect from server + Util::disconnect(); + + exit $exit_code; +} + +# Convert one field (string) to format acceptable by DSV. This +# means replace any : with \: and \ with \\. +sub convert_field_to_dsv { + my ($input_line)=@_; + + $input_line =~ s/([\\:])/\\$1/g; + return $input_line +} + +#### Global variables ##### +# Aditional options +my %opts = ( + 'operation' => { + type => "=s", + help => "The operation to perform (on,off,list,status). " + . "Operations on/off/status require name of the virtual machine", + default => "list", + required => 0, + }, + 'vmname' => { + type => "=s", + help => "The name of the virtual machine", + required => 0, + }, + 'datacenter' => { + type => "=s", + help => "The name of the datacenter", + required => 0, + } +); + +################# +##### MAIN ###### +################# + +# Conditional use of VIRuntime +eval "use VMware::VIRuntime;"; + +if ($@) { + show_error "Please install VI Perl API package to use this tool!\nPerl error: $@"; + exit 1; +} + +# Parse options +Opts::add_options(%opts); +Opts::parse(); +Opts::validate(); + +if (!(Opts::get_option('operation')=~/^(on|off|list|status)$/i)) { + show_error "Operation should be on, off, list or status!\n"; + exit 2; +} + +my $operation=lc(Opts::get_option('operation')); + +if (($operation ne 'list') && (!defined Opts::get_option('vmname'))) { + show_error "Operation on, off, status require vmname parameter!\n"; + exit 2; +} + + +# Try connect to machine +eval { + Util::connect(); +}; + +if ($@) { + show_error "Cannot connect to server!\nVMware error:".$@; + exit 3; +} + +my ($datacenter, $datacenter_view, $vm_views,$vm); +# We are connected to machine + +# If user want's datacenter, we must first find datacenter +my %filter=(view_type => 'VirtualMachine'); + +if( defined (Opts::get_option('datacenter')) ) { + $datacenter = Opts::get_option('datacenter'); + $datacenter_view = Vim::find_entity_view(view_type => 'Datacenter', + filter => { name => $datacenter }); + if (!$datacenter_view) { + show_error "Cannot find datacenter ".$datacenter."!\n"; + + my_exit 4; + } + + $filter{'begin_entity'}=$datacenter_view; +} + +if ($operation ne 'list') { + $filter{'filter'}= {"config.name" => Opts::get_option('vmname')}; +} + +$vm_views = Vim::find_entity_views(%filter); + +my $found=0; + +# Traverse all found vm +foreach $vm(@$vm_views) { + if (($operation eq 'list') or ($operation eq 'status')) { + if (!$vm->summary->config->template) { + print convert_field_to_dsv($vm->name).":". + convert_field_to_dsv($vm->summary->config->vmPathName).":". + convert_field_to_dsv($vm->runtime->powerState->val).":". + convert_field_to_dsv($vm->runtime->connectionState->val)."\n"; + } + } elsif ($operation eq 'on') { + eval { + $vm->PowerOnVM(); + }; + + if ($@) { + # If error is SoapFault with InvalidPowerState, user maybe use some auto power on tool. + # This is not error, warning is enought. + if (ref($@) eq 'SoapFault') { + if (ref($@->detail) eq 'InvalidPowerState') { + show_error "Warning: Cannot power on vm (somebody done it before???) ".Opts::get_option('vmname'). + "!\nVMware error:".$@."\n"; + } + } else { + # Some other more serious problem + show_error "Cannot power on vm ".Opts::get_option('vmname')."!\nVMware error:".$@."\n"; + my_exit 6; + } + } + } elsif ($operation eq 'off') { + eval { + $vm->PowerOffVM(); + }; + + if ($@) { + # If error is SoapFault with InvalidPowerState, user maybe use some auto power off tool. + # This is not error, warning is enought. + if (ref($@) eq 'SoapFault') { + if (ref($@->detail) eq 'InvalidPowerState') { + show_error "Warning: Cannot power off vm (somebody done it before???) ".Opts::get_option('vmname'). + "!\nVMware error:".$@."\n"; + } + } else { + # Some other more serious problem + show_error "Cannot power off vm ".Opts::get_option('vmname')."!\nVMware error:".$@."\n"; + my_exit 6; + } + } + } else { + show_error "Operation should be on, off or list!\n"; + my_exit 2; + } + $found++; +} + +if ((!$found) && ($operation ne 'list')) { + show_error "Cannot find vm ".Opts::get_option('vmname')."!\n"; + my_exit 5; +} + +# Should be 0 -> success all, or 6 in case of error +my_exit 0; + +__END__ + +=head1 NAME + +fence_vmware_helper - Perform list of virtual machines and + poweron, poweroff of operations on virtual machines. + +=head1 SYNOPSIS + + fence_vmware_helper --operation <on|off|list|status> [options] + +=head1 DESCRIPTION + +This VI Perl command-line utility provides an interface for +seven common provisioning operations on one or more virtual +machines: powering on, powering off and listing virtual mode. + +=head1 OPTIONS + +=head2 GENERAL OPTIONS + +=over + +=item B<operation> + +Operation to be performed. One of the following: + + <on> (power on one or more virtual machines), + <off> (power off one or more virtual machines), + <list> (list virtual machines and their status) + <status> (same as list, but show only machines with vmname) + +=item B<vmname> + +Optional. Name of the virtual machine on which the +operation is to be performed. + +=item B<datacenter> + +Optional. Name of the datacenter for the virtual machine(s). +Operations will be performed on all the virtual machines under the given datacenter. + +=back + +=head1 EXAMPLES + +Power on a virtual machine + + fence_vmware_helper --username administrator --password administrator --operation on + --vmname rhel --server win1 + + fence_vmware_helper --username administrator --password administrator --operation on + --vmname rhel --server win1 --datacenter Datacenter + +Power off a virtual machine + + fence_vmware_helper --username administrator --password administrator --operation off + --vmname rhel --server win1 + + perl fence_vmware_helper --username administrator --password administrator --operation off + --vmname rhel --server win1 --datacenter Datacenter + +List of virtual machines + + fence_vmware_helper --username administrator --password administrator --server win1 + + fence_vmware_helper --username administrator --password administrator --server win1 + --operation list + +Get status of virtual machine + + fence_vmware_helper --username administrator --password administrator --server win1 + --vmname rhel --operation status + +=head1 SUPPORTED PLATFORMS + +All operations supported on ESX 3.0.1 + +All operations supported on Virtual Center 2.0.1 diff --git a/agents/vmware_rest/fence_vmware_rest.py b/agents/vmware_rest/fence_vmware_rest.py new file mode 100644 index 0000000..4b884fc --- /dev/null +++ b/agents/vmware_rest/fence_vmware_rest.py @@ -0,0 +1,229 @@ +#!@PYTHON@ -tt + +import sys +import pycurl, io, json +import logging +import atexit +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS + +if sys.version_info[0] > 2: import urllib.parse as urllib +else: import urllib + +state = {"POWERED_ON": "on", 'POWERED_OFF': "off", 'SUSPENDED': "off"} + +def get_power_status(conn, options): + try: + res = send_command(conn, "vcenter/vm?filter.names={}".format(urllib.quote(options["--plug"])))["value"] + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_STATUS) + + if len(res) == 0: + fail(EC_STATUS) + + options["id"] = res[0]["vm"] + + result = res[0]["power_state"] + + return state[result] + +def set_power_status(conn, options): + action = { + "on" : "start", + "off" : "stop" + }[options["--action"]] + + try: + send_command(conn, "vcenter/vm/{}/power/{}".format(options["id"], action), "POST") + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_STATUS) + +def get_list(conn, options): + outlets = {} + + try: + command = "vcenter/vm" + if "--filter" in options: + command = command + "?" + options["--filter"] + res = send_command(conn, command) + except Exception as e: + logging.debug("Failed: {}".format(e)) + if str(e).startswith("400"): + if options.get("--original-action") == "monitor": + return outlets + else: + logging.error("More than 1000 VMs returned. Use --filter parameter to limit which VMs to list.") + fail(EC_STATUS) + else: + fail(EC_STATUS) + + for r in res["value"]: + outlets[r["name"]] = ("", state[r["power_state"]]) + + return outlets + +def connect(opt): + conn = pycurl.Curl() + + ## setup correct URL + if "--ssl-secure" in opt or "--ssl-insecure" in opt: + conn.base_url = "https:" + else: + conn.base_url = "http:" + if "--api-path" in opt: + api_path = opt["--api-path"] + else: + api_path = "/rest" + + conn.base_url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + api_path + "/" + + ## send command through pycurl + conn.setopt(pycurl.HTTPHEADER, [ + "Accept: application/json", + ]) + + conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) + conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) + + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) + + if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) + elif "--ssl-insecure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) + + try: + result = send_command(conn, "com/vmware/cis/session", "POST") + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_LOGIN_DENIED) + + # set session id for later requests + conn.setopt(pycurl.HTTPHEADER, [ + "Accept: application/json", + "vmware-api-session-id: {}".format(result["value"]), + ]) + + return conn + +def disconnect(conn): + try: + send_command(conn, "com/vmware/cis/session", "DELETE") + except Exception as e: + logging.debug("Failed: {}".format(e)) + conn.close() + +def send_command(conn, command, method="GET"): + url = conn.base_url + command + + conn.setopt(pycurl.URL, url.encode("ascii")) + + web_buffer = io.BytesIO() + + if method == "GET": + conn.setopt(pycurl.POST, 0) + if method == "POST": + conn.setopt(pycurl.POSTFIELDS, "") + if method == "DELETE": + conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") + + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + + try: + conn.perform() + except Exception as e: + raise(e) + + rc = conn.getinfo(pycurl.HTTP_CODE) + result = web_buffer.getvalue().decode("UTF-8") + + web_buffer.close() + + if len(result) > 0: + result = json.loads(result) + + if rc != 200: + if len(result) > 0: + raise Exception("{}: {}".format(rc, + result["value"]["messages"][0]["default_message"])) + else: + raise Exception("Remote returned {} for request to {}".format(rc, url)) + + logging.debug("url: {}".format(url)) + logging.debug("method: {}".format(method)) + logging.debug("response code: {}".format(rc)) + logging.debug("result: {}\n".format(result)) + + return result + +def define_new_opts(): + all_opt["api_path"] = { + "getopt" : ":", + "longopt" : "api-path", + "help" : "--api-path=[path] The path part of the API URL", + "default" : "/rest", + "required" : "0", + "shortdesc" : "The path part of the API URL", + "order" : 2} + all_opt["filter"] = { + "getopt" : ":", + "longopt" : "filter", + "help" : "--filter=[filter] Filter to only return relevant VMs" + " (e.g. \"filter.names=node1&filter.names=node2\").", + "required" : "0", + "shortdesc" : "Filter to only return relevant VMs. It can be used to avoid " + "the agent failing when more than 1000 VMs should be returned.", + "order" : 2} + + +def main(): + device_opt = [ + "ipaddr", + "api_path", + "login", + "passwd", + "ssl", + "notls", + "web", + "port", + "filter", + ] + + atexit.register(atexit_handler) + define_new_opts() + + all_opt["shell_timeout"]["default"] = "5" + all_opt["power_wait"]["default"] = "1" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for VMware REST API" + docs["longdesc"] = """fence_vmware_rest is an I/O Fencing agent which can be \ +used with VMware API to fence virtual machines. + +NOTE: If there's more than 1000 VMs there is a filter parameter to work around \ +the API limit. See https://code.vmware.com/apis/62/vcenter-management#/VM%20/get_vcenter_vm \ +for full list of filters.""" + docs["vendorurl"] = "https://www.vmware.com" + show_docs(options, docs) + + #### + ## Fence operations + #### + run_delay(options) + + conn = connect(options) + atexit.register(disconnect, conn) + + result = fence_action(conn, options, set_power_status, get_power_status, get_list) + + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/vmware_soap/fence_vmware_soap.py b/agents/vmware_soap/fence_vmware_soap.py new file mode 100644 index 0000000..4a4ec17 --- /dev/null +++ b/agents/vmware_soap/fence_vmware_soap.py @@ -0,0 +1,265 @@ +#!@PYTHON@ -tt + +import sys +import shutil, tempfile, suds +import logging, requests +import atexit, signal +sys.path.append("@FENCEAGENTSLIBDIR@") + +from suds.client import Client +from suds.sudsobject import Property +from suds.transport.http import HttpAuthenticated +from suds.transport import Reply, TransportError +from fencing import * +from fencing import fail, fail_usage, EC_STATUS, EC_LOGIN_DENIED, EC_INVALID_PRIVILEGES, EC_WAITING_ON, EC_WAITING_OFF +from fencing import run_delay + +options_global = None +conn_global = None + +class RequestsTransport(HttpAuthenticated): + def __init__(self, **kwargs): + self.cert = kwargs.pop('cert', None) + self.verify = kwargs.pop('verify', True) + self.session = requests.Session() + # super won't work because not using new style class + HttpAuthenticated.__init__(self, **kwargs) + + def send(self, request): + self.addcredentials(request) + resp = self.session.post(request.url, data=request.message, headers=request.headers, cert=self.cert, verify=self.verify) + result = Reply(resp.status_code, resp.headers, resp.content) + return result + +def soap_login(options): + run_delay(options) + + if "--ssl-secure" in options or "--ssl-insecure" in options: + if "--ssl-insecure" in options: + import ssl + import urllib3 + if hasattr(ssl, '_create_unverified_context'): + ssl._create_default_https_context = ssl._create_unverified_context + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + verify = False + else: + verify = True + url = "https://" + else: + verify = False + url = "http://" + + url += options["--ip"] + ":" + str(options["--ipport"]) + "/sdk" + + tmp_dir = tempfile.mkdtemp() + tempfile.tempdir = tmp_dir + atexit.register(remove_tmp_dir, tmp_dir) + + try: + headers = {"Content-Type" : "text/xml;charset=UTF-8", "SOAPAction" : "vim25"} + login_timeout = int(options["--login-timeout"]) or 15 + conn = Client(url + "/vimService.wsdl", location=url, transport=RequestsTransport(verify=verify), headers=headers, timeout=login_timeout) + + mo_ServiceInstance = Property('ServiceInstance') + mo_ServiceInstance._type = 'ServiceInstance' + ServiceContent = conn.service.RetrieveServiceContent(mo_ServiceInstance) + mo_SessionManager = Property(ServiceContent.sessionManager.value) + mo_SessionManager._type = 'SessionManager' + + conn.service.Login(mo_SessionManager, options["--username"], options["--password"]) + except requests.exceptions.SSLError as ex: + fail_usage("Server side certificate verification failed: %s" % ex) + except Exception as e: + logging.error("Server side certificate verification failed: {}".format(str(e))) + fail(EC_LOGIN_DENIED) + + options["ServiceContent"] = ServiceContent + options["mo_SessionManager"] = mo_SessionManager + return conn + +def process_results(results, machines, uuid, mappingToUUID): + for m in results.objects: + info = {} + for i in m.propSet: + info[i.name] = i.val + # Prevent error KeyError: 'config.uuid' when reaching systems which P2V failed, + # since these systems don't have a valid UUID + if "config.uuid" in info: + machines[info["name"]] = (info["config.uuid"], info["summary.runtime.powerState"]) + uuid[info["config.uuid"]] = info["summary.runtime.powerState"] + mappingToUUID[m.obj.value] = info["config.uuid"] + + return (machines, uuid, mappingToUUID) + +def get_power_status(conn, options): + mo_ViewManager = Property(options["ServiceContent"].viewManager.value) + mo_ViewManager._type = "ViewManager" + + mo_RootFolder = Property(options["ServiceContent"].rootFolder.value) + mo_RootFolder._type = "Folder" + + mo_PropertyCollector = Property(options["ServiceContent"].propertyCollector.value) + mo_PropertyCollector._type = 'PropertyCollector' + + ContainerView = conn.service.CreateContainerView(mo_ViewManager, recursive=1, + container=mo_RootFolder, type=['VirtualMachine']) + mo_ContainerView = Property(ContainerView.value) + mo_ContainerView._type = "ContainerView" + + FolderTraversalSpec = conn.factory.create('ns0:TraversalSpec') + FolderTraversalSpec.name = "traverseEntities" + FolderTraversalSpec.path = "view" + FolderTraversalSpec.skip = False + FolderTraversalSpec.type = "ContainerView" + + objSpec = conn.factory.create('ns0:ObjectSpec') + objSpec.obj = mo_ContainerView + objSpec.selectSet = [FolderTraversalSpec] + objSpec.skip = True + + propSpec = conn.factory.create('ns0:PropertySpec') + propSpec.all = False + propSpec.pathSet = ["name", "summary.runtime.powerState", "config.uuid"] + propSpec.type = "VirtualMachine" + + propFilterSpec = conn.factory.create('ns0:PropertyFilterSpec') + propFilterSpec.propSet = [propSpec] + propFilterSpec.objectSet = [objSpec] + + try: + raw_machines = conn.service.RetrievePropertiesEx(mo_PropertyCollector, propFilterSpec) + except Exception as e: + logging.error("Failed: {}".format(str(e))) + fail(EC_STATUS) + + (machines, uuid, mappingToUUID) = process_results(raw_machines, {}, {}, {}) + + # Probably need to loop over the ContinueRetreive if there are more results after 1 iteration. + while hasattr(raw_machines, 'token'): + try: + raw_machines = conn.service.ContinueRetrievePropertiesEx(mo_PropertyCollector, raw_machines.token) + except Exception as e: + logging.error("Failed: {}".format(str(e))) + fail(EC_STATUS) + (more_machines, more_uuid, more_mappingToUUID) = process_results(raw_machines, {}, {}, {}) + machines.update(more_machines) + uuid.update(more_uuid) + mappingToUUID.update(more_mappingToUUID) + # Do not run unnecessary SOAP requests + if "--uuid" in options and options["--uuid"] in uuid: + break + + if ["list", "monitor"].count(options["--action"]) == 1: + return machines + else: + if "--uuid" not in options: + if options["--plug"].startswith('/'): + ## Transform InventoryPath to UUID + mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value) + mo_SearchIndex._type = "SearchIndex" + + vm = conn.service.FindByInventoryPath(mo_SearchIndex, options["--plug"]) + + try: + options["--uuid"] = mappingToUUID[vm.value] + except KeyError: + fail(EC_STATUS) + except AttributeError: + fail(EC_STATUS) + else: + ## Name of virtual machine instead of path + ## warning: if you have same names of machines this won't work correctly + try: + (options["--uuid"], _) = machines[options["--plug"]] + except KeyError: + fail(EC_STATUS) + except AttributeError: + fail(EC_STATUS) + + try: + if uuid[options["--uuid"]] == "poweredOn": + return "on" + else: + return "off" + except KeyError: + fail(EC_STATUS) + +def set_power_status(conn, options): + mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value) + mo_SearchIndex._type = "SearchIndex" + vm = conn.service.FindByUuid(mo_SearchIndex, vmSearch=1, uuid=options["--uuid"]) + + mo_machine = Property(vm.value) + mo_machine._type = "VirtualMachine" + + try: + if options["--action"] == "on": + conn.service.PowerOnVM_Task(mo_machine) + else: + conn.service.PowerOffVM_Task(mo_machine) + except suds.WebFault as ex: + if (str(ex).find("Permission to perform this operation was denied")) >= 0: + fail(EC_INVALID_PRIVILEGES) + else: + if options["--action"] == "on": + fail(EC_WAITING_ON) + else: + fail(EC_WAITING_OFF) + +def remove_tmp_dir(tmp_dir): + shutil.rmtree(tmp_dir) + +def logout(): + try: + conn_global.service.Logout(options_global["mo_SessionManager"]) + except Exception: + pass + +def signal_handler(signum, frame): + raise Exception("Signal \"%d\" received which has triggered an exit of the process." % signum) + +def main(): + global options_global + global conn_global + device_opt = ["ipaddr", "login", "passwd", "web", "ssl", "notls", "port"] + + atexit.register(atexit_handler) + atexit.register(logout) + + signal.signal(signal.SIGTERM, signal_handler) + + options_global = check_input(device_opt, process_input(device_opt)) + + ## + ## Fence agent specific defaults + ##### + docs = {} + docs["shortdesc"] = "Fence agent for VMWare over SOAP API" + docs["longdesc"] = "fence_vmware_soap is an I/O Fencing agent \ +which can be used with the virtual machines managed by VMWare products \ +that have SOAP API v4.1+. \ +\n.P\n\ +Name of virtual machine (-n / port) has to be used in inventory path \ +format (e.g. /datacenter/vm/Discovered virtual machine/myMachine). \ +In the cases when name of yours VM is unique you can use it instead. \ +Alternatively you can always use UUID to access virtual machine." + docs["vendorurl"] = "http://www.vmware.com" + show_docs(options_global, docs) + + logging.basicConfig(level=logging.INFO) + logging.getLogger('suds.client').setLevel(logging.CRITICAL) + logging.getLogger("requests").setLevel(logging.CRITICAL) + logging.getLogger("urllib3").setLevel(logging.CRITICAL) + + ## + ## Operate the fencing device + #### + conn_global = soap_login(options_global) + + result = fence_action(conn_global, options_global, set_power_status, get_power_status, get_power_status) + + ## Logout from system is done automatically via atexit() + sys.exit(result) + +if __name__ == "__main__": + main() diff --git a/agents/vmware_vcloud/fence_vmware_vcloud.py b/agents/vmware_vcloud/fence_vmware_vcloud.py new file mode 100644 index 0000000..7626b82 --- /dev/null +++ b/agents/vmware_vcloud/fence_vmware_vcloud.py @@ -0,0 +1,214 @@ +#!@PYTHON@ -tt + +import sys +import pycurl, io +import logging +import atexit +import xml.etree.ElementTree as etree +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS + +state = {"POWERED_ON": "on", 'POWERED_OFF': "off", 'SUSPENDED': "off"} + +def get_power_status(conn, options): + try: + VM = send_command(conn, "vApp/vm-{}".format(options["--plug"])) + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_STATUS) + + options["id"] = VM.attrib['href'].split('/vm-', 1)[1] + + if (VM.attrib['status'] == '3'): + return state['SUSPENDED'] + elif (VM.attrib['status'] == '4'): + return state['POWERED_ON'] + elif (VM.attrib['status'] == '8'): + return state['POWERED_OFF'] + return EC_STATUS + + +def set_power_status(conn, options): + action = { + "on" : "powerOn", + "off" : "powerOff", + "shutdown": "shutdown", + "suspend": "suspend", + "reset": "reset" + }[options["--action"]] + try: + VM = send_command(conn, "vApp/vm-{}/power/action/{}".format(options["--plug"], action), "POST") + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_STATUS) + +def get_list(conn, options): + outlets = {} + + VMsResponse = send_command(conn, "vms/query") + + for VM in VMsResponse.iter('{http://www.vmware.com/vcloud/v1.5}VMRecord'): + if '/vApp/' not in VM.attrib['href']: + continue + uuid = (VM.attrib['href'].split('/vm-', 1))[1] + outlets['['+ uuid + '] ' + VM.attrib['containerName'] + '\\' + VM.attrib['name']] = (VM.attrib['status'], state[VM.attrib['status']]) + + return outlets + +def connect(opt): + conn = pycurl.Curl() + + ## setup correct URL + if "--ssl-secure" in opt or "--ssl-insecure" in opt: + conn.base_url = "https:" + else: + conn.base_url = "http:" + + conn.base_url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + opt["--api-path"] + "/" + + ## send command through pycurl + conn.setopt(pycurl.HTTPHEADER, [ + "Accept: application/*+xml;version=1.5", + ]) + + conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) + conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) + + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) + if "--ssl-secure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) + elif "--ssl-insecure" in opt: + conn.setopt(pycurl.SSL_VERIFYPEER, 0) + conn.setopt(pycurl.SSL_VERIFYHOST, 0) + + headers = {} + try: + result = send_command(conn, "sessions", "POST", headers) + except Exception as e: + logging.debug("Failed: {}".format(e)) + fail(EC_LOGIN_DENIED) + + # set session id for later requests + conn.setopt(pycurl.HTTPHEADER, [ + "Accept: application/*+xml;version=1.5", + "x-vcloud-authorization: {}".format(headers['x-vcloud-authorization']), + ]) + + return conn + +def disconnect(conn): + send_command(conn, "session", "DELETE") + conn.close() + +def parse_headers(data): + headers = {} + data = data.split("\r\n") + for header_line in data[1:]: + if ':' not in header_line: + break + name, value = header_line.split(':', 1) + name = name.strip() + value = value.strip() + name = name.lower() + headers[name] = value + + return headers + +def send_command(conn, command, method="GET", headers={}): + url = conn.base_url + command + + conn.setopt(pycurl.URL, url.encode("ascii")) + + web_buffer = io.BytesIO() + headers_buffer = io.BytesIO() + + if method == "GET": + conn.setopt(pycurl.POST, 0) + elif method == "POST": + conn.setopt(pycurl.POSTFIELDS, "") + elif method == "DELETE": + conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") + + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + conn.setopt(pycurl.HEADERFUNCTION, headers_buffer.write) + + try: + conn.perform() + except Exception as e: + raise(e) + + rc = conn.getinfo(pycurl.HTTP_CODE) + result = web_buffer.getvalue().decode() + headers.update(parse_headers(headers_buffer.getvalue().decode())) + + headers_buffer.close() + web_buffer.close() + + if len(result) > 0: + result = etree.fromstring(result) + + if rc != 200 and rc != 202 and rc != 204: + if len(result) > 0: + raise Exception("{}: {}".format(rc, result["value"]["messages"][0]["default_message"])) + else: + raise Exception("Remote returned {} for request to {}".format(rc, url)) + + logging.debug("url: {}".format(url)) + logging.debug("method: {}".format(method)) + logging.debug("response code: {}".format(rc)) + logging.debug("result: {}\n".format(result)) + + return result + +def define_new_opts(): + all_opt["api_path"] = { + "getopt" : ":", + "longopt" : "api-path", + "help" : "--api-path=[path] The path part of the API URL", + "default" : "/api", + "required" : "0", + "shortdesc" : "The path part of the API URL", + "order" : 2} + +def main(): + device_opt = [ + "ipaddr", + "api_path", + "login", + "passwd", + "ssl", + "notls", + "web", + "port", + ] + + atexit.register(atexit_handler) + define_new_opts() + + all_opt["shell_timeout"]["default"] = "5" + all_opt["power_wait"]["default"] = "1" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for VMware vCloud Director API" + docs["longdesc"] = "fence_vmware_vcloud is an I/O Fencing agent which can be used with VMware vCloud Director API to fence virtual machines." + docs["vendorurl"] = "https://www.vmware.com" + show_docs(options, docs) + + #### + ## Fence operations + #### + run_delay(options) + + conn = connect(options) + atexit.register(disconnect, conn) + + result = fence_action(conn, options, set_power_status, get_power_status, get_list) + + sys.exit(result) + +if __name__ == "__main__": + main() |