diff options
Diffstat (limited to 'agents/vmware')
-rw-r--r-- | agents/vmware/fence_vmware.py | 336 | ||||
-rw-r--r-- | agents/vmware/fence_vmware_helper.pl | 276 |
2 files changed, 612 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 |