summaryrefslogtreecommitdiffstats
path: root/agents/vmware
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:50:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:50:17 +0000
commit86ed03f8adee56c050c73018537371c230a664a6 (patch)
treeeae3d04cdf1c49848e5a671327ab38297f4acb0d /agents/vmware
parentInitial commit. (diff)
downloadfence-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.py336
-rw-r--r--agents/vmware/fence_vmware_helper.pl276
-rw-r--r--agents/vmware_rest/fence_vmware_rest.py229
-rw-r--r--agents/vmware_soap/fence_vmware_soap.py265
-rw-r--r--agents/vmware_vcloud/fence_vmware_vcloud.py214
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()