From 0886ca800604fa1db013446af27ef715e7658952 Mon Sep 17 00:00:00 2001 From: Marek 'marx' Grac <mgrac@redhat.com> Date: Sun, 20 Dec 2009 19:33:17 +0100 Subject: [PATCH] fencing: New fence agent for VMWare Current version of vmware fence agent (techpreview) is not cluster aware. New fence agent should support VMware ESX/ESXi/VC/Server 1/Server 2 and be cluster aware. That will require that user must install VI Perl or VIX API to every node which do fencing. Resolve: rhbz#548577 --- fence/agents/lib/fencing.py.py | 51 ++-- fence/agents/vmware/Makefile | 17 +- fence/agents/vmware/fence_vmware.pl | 337 ---------------------- fence/agents/vmware/fence_vmware.py | 426 ++++++++++++++++++++-------- fence/agents/vmware/fence_vmware_helper.pl | 276 ++++++++++++++++++ 5 files changed, 619 insertions(+), 488 deletions(-) delete mode 100755 fence/agents/vmware/fence_vmware.pl mode change 100755 => 100644 fence/agents/vmware/fence_vmware.py create mode 100644 fence/agents/vmware/fence_vmware_helper.pl diff --git a/fence/agents/lib/fencing.py.py b/fence/agents/lib/fencing.py.py index 647011a..711e17d 100644 --- a/fence/agents/lib/fencing.py.py +++ b/fence/agents/lib/fencing.py.py @@ -224,22 +224,27 @@ all_opt = { "help" : "", "order" : 1, "obsolete" : "use -o status instead" }, - "vmipaddr" : { - "getopt" : "A:", - "help" : "-A <ip> IP address or hostname of managed VMware ESX (default localhost)", - "order" : 2 }, - "vmlogin" : { - "getopt" : "L:", - "help" : "-L <name> VMware ESX management login name", - "order" : 2 }, - "vmpasswd" : { - "getopt" : "P:", - "help" : "-P <password> VMware ESX management login password", - "order" : 2 }, - "vmpasswd_script" : { - "getopt" : "B:", - "help" : "-B <script> Script to run to retrieve VMware ESX management password", - "order" : 2 }, + "exec" : { + "getopt" : "e:", + "longopt" : "exec", + "help" : "-e, --exec=<command> Command to execute", + "required" : "0", + "shortdesc" : "Command to execute", + "order" : 1 }, + "vmware_type" : { + "getopt" : "d:", + "longopt" : "vmware_type", + "help" : "-d, --vmware_type=<type> Type of VMware to connect", + "required" : "0", + "shortdesc" : "Type of VMware to connect", + "order" : 1 }, + "vmware_datacenter" : { + "getopt" : "s:", + "longopt" : "vmware_datacenter", + "help" : "-s, --vmware_datacenter=<dc> VMWare datacenter filter", + "required" : "0", + "shortdesc" : "Show only machines in specified datacenter", + "order" : 2 }, "snmp_version" : { "getopt" : "d:", "longopt" : "snmp-version", @@ -656,20 +661,6 @@ def check_input(device_opt, opt): if options.has_key("-R"): options["-P"] = os.popen(options["-R"]).read().rstrip() - ## VMware - ####### - if options.has_key("-B"): - options["-P"] = os.popen(options["-B"]).read().rstrip() - - if (device_opt.count("vmlogin") and (not options.has_key("-L"))): - fail_usage("Failed: You have to set login name for VMware ESX management console") - - if (options.has_key("-L") and (not (options.has_key("-P") or options.has_key("-B")))): - fail_usage("Failed: You have to enter password or password script for VMware ESX management console") - - if (["list", "monitor"].count(options["-o"])==0 and (options.has_key("-L") and (not (options.has_key("-n"))))): - fail_usage("Failed: You have to enter virtual machine name") - if options.has_key("-u") == False: if options.has_key("-x"): options["-u"] = 22 diff --git a/fence/agents/vmware/Makefile b/fence/agents/vmware/Makefile index ea0a207..f0259b8 100644 --- a/fence/agents/vmware/Makefile +++ b/fence/agents/vmware/Makefile @@ -14,10 +14,13 @@ SOURCE= fence_vmware.py TARGET= fence_vmware +SOURCE2= fence_vmware_helper.pl +TARGET2= fence_vmware_helper + top_srcdir=../.. include ${top_srcdir}/make/defines.mk -all: $(TARGET) +all: $(TARGET) $(TARGET2) $(TARGET): $(SOURCE) : > $(TARGET) @@ -28,12 +31,22 @@ $(TARGET): $(SOURCE) awk -v p=0 "(\$$1 ~ /#END_VERSION_GENERATION/){p = 1} {if(p==1)print}" $(SOURCE) >> $(TARGET) chmod +x $(TARGET) +$(TARGET2): $(SOURCE2) + : > $(TARGET2) + awk "{print}(\$$1 ~ /#BEGIN_VERSION_GENERATION/){exit 0}" $(SOURCE2) >> $(TARGET2) + echo "FENCE_RELEASE_NAME=\"${RELEASE}\";" >> $(TARGET2) + ${top_srcdir}/scripts/define2var ${top_srcdir}/config/copyright.cf sh REDHAT_COPYRIGHT >> $(TARGET2) + echo "BUILD_DATE=\"(built `date`)\";" >> $(TARGET2) + awk -v p=0 "(\$$1 ~ /#END_VERSION_GENERATION/){p = 1} {if(p==1)print}" $(SOURCE2) >> $(TARGET2) + chmod +x $(TARGET2) + install: all if [ ! -d ${sbindir} ]; then \ install -d ${sbindir}; \ fi install -m755 ${TARGET} ${sbindir} + install -m755 ${TARGET2} ${sbindir} clean: - rm -f $(TARGET) + rm -f $(TARGET) $(TARGET2) diff --git a/fence/agents/vmware/fence_vmware.pl b/fence/agents/vmware/fence_vmware.pl deleted file mode 100755 index 3c08312..0000000 --- a/fence/agents/vmware/fence_vmware.pl +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/perl - -############################################################################### -############################################################################### -## -## Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. -## Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. -## -## This copyrighted material is made available to anyone wishing to use, -## modify, copy, or redistribute it subject to the terms and conditions -## of the GNU General Public License v.2. -## -## VMWare ESX Server fencing support by Zach Lowry <zach@zachlowry.net> -## -############################################################################### -############################################################################### - -use strict; -use Getopt::Std; -use VMware::VmPerl; -use VMware::VmPerl::VM; -use VMware::VmPerl::Server; -use VMware::VmPerl::ConnectParams; -use vars qw( $opt_L $opt_v $opt_V $opt_h $opt_T $opt_n $opt_o $opt_p $opt_P $opt_S $opt_l $opt_a $opt_q $vm_product $vm_platform $vm_build $vm_version_major $vm_version_minor $vm_version_revision ); - -# Get the program name from $0 and strip directory names -$_=$0; -s/.*\///; -my $pname = $_; - -# Change these if the text returned by your equipment is different. -# Test by running script with options -t -v and checking /tmp/vmlog - -my $immediate = 'immediate'; # # Or 'delayed' - action string prefix on menu - -my $max_open_tries = 3; # How many attempts to make. -my $open_wait = 5; # Seconds to wait between each attempt -my $debuglog = '/tmp/apclog';# Location of debugging log when in verbose mode -my $powerop_mode = VM_POWEROP_MODE_TRYSOFT; -$opt_o = 'Reboot'; # Default fence action. - - -my $logged_in = 0; - -my $vm = VMware::VmPerl::VM::new(); -my $server = VMware::VmPerl::Server::new(); - -# WARNING!! Do not add code bewteen "#BEGIN_VERSION_GENERATION" and -# "#END_VERSION_GENERATION" It is generated by the Makefile - -#BEGIN_VERSION_GENERATION -#END_VERSION_GENERATION - -sub usage -{ - print "Usage:\n"; - print "\n"; - print "$pname [options]\n"; - print "\n"; - print "Options:\n"; - print " -a <ip>:<port> IP address or hostname of VMware ESX Server\n"; - print " -h usage\n"; - print " -l <name> Login name\n"; - print " -p <string> Login password\n"; - print " -S <path> Script to run to retrieve login password\n"; - print " -n <name> Name of VM to change \n"; - print " -o <string> Action: Reboot (default), Off or On\n"; - print " -q quiet mode\n"; - print " -T Test mode (cancels action)\n"; - print " -V version\n"; - print " -v Log to file /tmp/vmlog\n"; - print " -L List VMs on Server\n"; - - exit 0; -} - -sub fail -{ - my ($msg)=@_; - print $msg."\n" unless defined $opt_q; - - if (defined $vm) - { - # make sure we don't get stuck in a loop due to errors - - logout() if $logged_in; - undef $vm; - } - exit 1; -} - -sub fail_usage -{ - my ($msg)=@_; - print STDERR $msg."\n" if $msg; - print STDERR "Please use '-h' for usage.\n"; - exit 1; -} - -sub version -{ - print "$pname $FENCE_RELEASE_NAME $BUILD_DATE\n"; - print "$REDHAT_COPYRIGHT\n" if ( $REDHAT_COPYRIGHT ); - exit 0; -} - - -sub login -{ - my $connect_params = VMware::VmPerl::ConnectParams::new($opt_a, $opt_P, $opt_l, $opt_p); - for (my $i=0; $i<$max_open_tries; $i++) - { - if (defined $opt_L) { - if (! $server->connect($connect_params)) { - my ($error_number, $error_string) = $server->get_last_error(); - fail "$error_number, $error_string"; - } - return; - } else { - if (! $vm->connect($connect_params, $opt_n)) { - my ($error_number, $error_string) = $vm->get_last_error(); - fail "$error_number, $error_string"; - } - return; - } - } - if (defined $opt_L) { - fail "failed: connect failed: ". $server->get_last_error() ."\n"; - } else { - fail "failed: connect failed: ". $vm->get_last_error() ."\n"; - } -} - -# Determine if the switch is a working state. Also check to make sure that -# the switch has been specified in the case that there are slave switches -# present. This assumes that we are at the main menu. -sub identify_vmware -{ - if (! defined $opt_L) { - $vm_product = $vm->get_product_info(VM_PRODINFO_PRODUCT); - $vm_platform = $vm->get_product_info(VM_PRODINFO_PLATFORM); - $vm_build = $vm->get_product_info(VM_PRODINFO_BUILD); - $vm_version_major = $vm->get_product_info(VM_PRODINFO_VERSION_MAJOR); - $vm_version_minor = $vm->get_product_info(VM_PRODINFO_VERSION_MINOR); - $vm_version_revision = $vm->get_product_info(VM_PRODINFO_VERSION_REVISION); - } -} - - -sub logout -{ - undef $vm, $server; -} - - -sub action -{ - if (defined $opt_L) { - foreach my $name ($server->registered_vm_names()) { - print "$name\n"; - } - logout(); - - exit 0; - } - if (defined $opt_T) { - print "success: test outlet $opt_n $opt_o\n" unless defined $opt_q; - logout(); - - exit 0; - } elsif ( $vm->is_connected() ) { - my $cur_state = $vm->get_execution_state(); - if ( $opt_o =~ /Reboot/i ) { - $vm->reset($powerop_mode); - } elsif ( $opt_o =~ /On/i ) { - $vm->start($powerop_mode); - } elsif ( $opt_o =~ /Off/i ) { - $vm->stop($powerop_mode); - } - if ($_) { - my ($error_number, $error_string) = $vm->get_last_error(); - if ($error_number != 0) { - fail "$error_number, $error_string"; - } else { - print "success: outlet $opt_n $opt_o\n" unless defined $opt_q; - } - logout(); - - exit 0; - } - } - - fail "failed: unrecognised action response\n"; -} - - -sub get_options_stdin -{ - my $opt; - my $line = 0; - my $in; - while( defined($in = <>) ) - { - $_ = $in; - chomp; - - # strip leading and trailing whitespace - s/^\s*//; - s/\s*$//; - - # skip comments - next if /^#/; - - $line+=1; - $opt=$_; - next unless $opt; - - my ($name,$val)=split /\s*=\s*/, $opt; - - if ( $name eq "" ) - { - print STDERR "parse error: illegal name in option $line\n"; - exit 2; - } - # DO NOTHING -- this field is used by fenced - elsif ($name eq "agent" ) - { - } - elsif ($name eq "ipaddr" ) - { - $opt_a = $val; - } - elsif ($name eq "login" ) - { - $opt_l = $val; - } - elsif ($name eq "option" ) - { - $opt_o = $val; - } - elsif ($name eq "passwd" ) - { - $opt_p = $val; - } - elsif ($name eq "passwd_script" ) - { - $opt_S = $val; - } - elsif ($name eq "port" ) - { - $opt_n = $val; - } - elsif ($name eq "switch" ) - { - $opt_P = $val; - } - elsif ($name eq "test" ) - { - $opt_T = $val; - } - elsif ($name eq "verbose" ) - { - $opt_v = $val; - } - # Excess name/vals will fail - else - { - fail "parse error: unknown option \"$opt\""; - } - } -} - - -sub connect_error -{ - fail "failed: connect returned: ".$vm->get_last_error()."\n"; -} - - -### MAIN ####################################################### - -if (@ARGV > 0) { - getopts("a:hl:n:o:p:S:qTvVL") || fail_usage ; - - usage if defined $opt_h; - version if defined $opt_V; - - fail_usage "Unkown parameter." if (@ARGV > 0); - - fail_usage "No '-a' flag specified." unless defined $opt_a; - fail_usage "No '-n' flag specified." unless defined $opt_n or defined $opt_L; - fail_usage "No '-l' flag specified." unless defined $opt_l; - - if (defined $opt_S) { - $pwd_script_out = `$opt_S`; - chomp($pwd_script_out); - if ($pwd_script_out) { - $opt_p = $pwd_script_out; - } - } - - fail_usage "No '-p' or '-S' flag specified." unless defined $opt_p; - fail_usage "Unrecognized action '$opt_o' for '-o' flag" - unless $opt_o =~ /^(Off|On|Reboot)$/i; - - ($opt_a, $opt_P) = split(/:/, $opt_a); - fail_usage "No port number specified." unless defined $opt_P; - -} else { - get_options_stdin(); - - fail "failed: no IP address" unless defined $opt_a; - fail "failed: no vm name" unless defined $opt_n; - fail "failed: no login name" unless defined $opt_l; - - if (defined $opt_S) { - $pwd_script_out = `$opt_S`; - chomp($pwd_script_out); - if ($pwd_script_out) { - $opt_p = $pwd_script_out; - } - } - - fail "failed: no password" unless defined $opt_p; - fail "failed: unrecognized action: $opt_o" - unless $opt_o =~ /^(Off|On|Reboot)$/i; -} - -&login; - -&identify_vmware; - -&action; - -exit 0; - - diff --git a/fence/agents/vmware/fence_vmware.py b/fence/agents/vmware/fence_vmware.py old mode 100755 new mode 100644 index a6ce9ef..75fd887 --- a/fence/agents/vmware/fence_vmware.py +++ b/fence/agents/vmware/fence_vmware.py @@ -1,153 +1,341 @@ #!/usr/bin/python -## -## Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. -## -## The Following Agent Has Been Tested On VMware ESX 3.5 and VMware Server 1.0.7 -## -##### +# +# 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, exceptions -sys.path.append("/usr/lib/fence") +sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * #BEGIN_VERSION_GENERATION -FENCE_RELEASE_NAME="" +RELEASE_VERSION="VMware Agent using VI Perl API and/or VIX vmrun command" REDHAT_COPYRIGHT="" BUILD_DATE="" #END_VERSION_GENERATION -VMWARE_COMMAND="/usr/bin/vmware-cmd" -COMMAND_PROMPT_REG="\[PEXPECT\]\$ " -COMMAND_PROMPT_NEW="[PEXPECT]\$ " +### 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(str): + dstr='' + + for c in str: + 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["-e"] + + if (add_login_params): + if (vmware_internal_type==VMWARE_TYPE_ESX): + res+=" --server '%s' --username '%s' --password '%s' "%(quote_for_run(options["-a"]), + quote_for_run(options["-l"]), + quote_for_run(options["-p"])) + elif (vmware_internal_type==VMWARE_TYPE_SERVER2): + res+=" -h 'https://%s/sdk' -u '%s' -p '%s' -T server "%(quote_for_run(options["-a"]), + quote_for_run(options["-l"]), + quote_for_run(options["-p"])) + elif (vmware_internal_type==VMWARE_TYPE_SERVER1): + host_name_array=options["-a"].split(':') + + res+=" -h '%s' -u '%s' -p '%s' -T server1 "%(quote_for_run(host_name_array[0]), + quote_for_run(options["-l"]), + quote_for_run(options["-p"])) + if (len(host_name_array)>1): + res+="-P '%s' "%(quote_for_run(host_name_array[1])) + + if ((options.has_key("-s")) and (vmware_internal_type==VMWARE_TYPE_ESX)): + res+="--datacenter '%s' "%(quote_for_run(options["-s"])) + + if (additional_params!=""): + res+=additional_params + + return res + +# Log message if user set verbose option +def vmware_log(options, message): + if options["log"] >= LOG_MODE_VERBOSE: + options["debug_fh"].write(message+"\n") + +# 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) -# Start comunicating after login. Prepare good environment. -def start_communication(conn, options): - conn.sendline ("PS1='"+COMMAND_PROMPT_NEW+"'"); - conn.log_expect(options,COMMAND_PROMPT_REG,SHELL_TIMEOUT) - -# Prepare command line for vmware-cmd with parameters. -def prepare_cmdline(conn,options,add_vm_name): - cmd_line=VMWARE_COMMAND+" -H '"+options["-A"]+"' -U '"+options["-L"]+"' -P '"+options["-P"]+"'" - if (add_vm_name): - cmd_line+=" '"+options["-n"]+"'" - - if options.has_key("-v"): - cmd_line+=" -v" - - return cmd_line - -def get_power_status(conn, options): - result = "" try: - start_communication(conn,options) - - cmd_line=prepare_cmdline(conn,options,True) - - cmd_line+=" getstate" + vmware_log(options,command) - conn.sendline(cmd_line) - - conn.log_expect(options,COMMAND_PROMPT_REG,SHELL_TIMEOUT) - status_err = re.search("vmcontrol\ error\ ([-+]?\d+)\:(.*)",conn.before.lower()) - if (status_err!=None): - fail_usage("VMware error "+status_err.group(1)+": "+status_err.group(2)) - - status = re.search("getstate\(\)\ =\ on",conn.before.lower()) - - result=(status==None and "off" or "on") - - except pexpect.EOF: - fail(EC_CONNECTION_LOST) - except pexpect.TIMEOUT: - fail(EC_TIMED_OUT) - - return result - -def get_outlet_list(conn,options): - result={} - - try: - start_communication(conn,options) + (res_output,res_code)=pexpect.run(command,int(options["-Y"])+int(options["-y"])+additional_timeout,True) - cmd_line=prepare_cmdline(conn,options,False) - cmd_line+=" -l" + if (res_code==None): + fail(EC_TIMED_OUT) + if ((res_code!=0) and (add_login_params)): + vmware_log(options,res_output) + fail_usage("%s returned %s"%(options["-e"],res_output)) + else: + vmware_log(options,res_output) - conn.sendline(cmd_line) - - conn.log_expect(options,COMMAND_PROMPT_REG,SHELL_TIMEOUT) - status_err = re.search("vmcontrol\ error\ ([-+]?\d+)\:(.*)",conn.before.lower()) - if (status_err!=None): - fail_usage("VMware error "+status_err.group(1)+": "+status_err.group(2)) - - lines=conn.before.splitlines() + except pexpect.ExceptionPexpect: + fail_usage("Cannot run command %s"%(options["-e"])) - for line in lines[(options.has_key("-v") and 3 or 1):-1]: - if (line!=""): - result[line]=("","") + return res_output - except pexpect.EOF: - fail(EC_CONNECTION_LOST) - except pexpect.TIMEOUT: - fail(EC_TIMED_OUT) +# 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(conn, options, add_vm_name): + outlets={} - return result + if (add_vm_name): + all_machines=vmware_run_command(options,True,("--operation status --vmname '%s'"%(quote_for_run(options["-n"]))),0) + else: + all_machines=vmware_run_command(options,True,"--operation list",int(options["-g"])) + + 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(conn,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): + if (vmware_internal_type==VMWARE_TYPE_ESX): + return vmware_get_outlets_vi(conn,options,False) + if ((vmware_internal_type==VMWARE_TYPE_SERVER1) or (vmware_internal_type==VMWARE_TYPE_SERVER2)): + return vmware_get_outlets_vix(conn,options) + +def get_power_status(conn,options): + if (vmware_internal_type==VMWARE_TYPE_ESX): + outlets=vmware_get_outlets_vi(conn,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["-n"] in outlets)): + fail_usage("Failed: You have to enter existing name of virtual machine!") + else: + return outlets[options["-n"]][1] + elif (vmware_internal_type==VMWARE_TYPE_SERVER1): + return ((options["-n"] in outlets) and "on" or "off") def set_power_status(conn, options): - try: - start_communication(conn,options) + if (vmware_internal_type==VMWARE_TYPE_ESX): + additional_params="--operation %s --vmname '%s'"%((options["-o"]=="on" and "on" or "off"),quote_for_run(options["-n"])) + elif ((vmware_internal_type==VMWARE_TYPE_SERVER1) or (vmware_internal_type==VMWARE_TYPE_SERVER2)): + additional_params="%s '%s'"%((options["-o"]=="on" and "start" or "stop"),quote_for_run(options["-n"])) + if (options["-o"]=="off"): + additional_params+=" hard" - cmd_line=prepare_cmdline(conn,options,True) + vmware_run_command(options,True,additional_params,int(options["-g"])) - cmd_line+=" "+(options["-o"]=="on" and "start" or "stop hard") +# 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("vmrun version (\d\.(\d[\.]*)*)",vmware_help_str.lower()) + if (version_re==None): + return False # Looks like this "vmrun" is not real vmrun - conn.sendline(cmd_line) + version_array=version_re.group(1).split(".") - conn.log_expect(options,COMMAND_PROMPT_REG,POWER_TIMEOUT) - - except pexpect.EOF: - fail(EC_CONNECTION_LOST) - except pexpect.TIMEOUT: - fail(EC_TIMED_OUT) - + try: + if (int(version_array[0])<VMRUN_MINIMUM_REQUIRED_VERSION): + return False + except Exception: + return False + + return True + +# Define new options +def vmware_define_defaults(): + all_opt["vmware_type"]["default"]=VMWARE_DEFAULT_TYPE + +# Check vmware type, set vmware_internal_type to one of VMWARE_TYPE_ value and +# options["-e"] to path (if not specified) +def vmware_check_vmware_type(options): + global vmware_internal_type + + options["-d"]=options["-d"].lower() + + if (options["-d"]=="esx"): + vmware_internal_type=VMWARE_TYPE_ESX + if (not options.has_key("-e")): + options["-e"]=VMHELPER_COMMAND + elif (options["-d"]=="server2"): + vmware_internal_type=VMWARE_TYPE_SERVER2 + if (not options.has_key("-e")): + options["-e"]=VMRUN_COMMAND + elif (options["-d"]=="server1"): + vmware_internal_type=VMWARE_TYPE_SERVER1 + if (not options.has_key("-e")): + options["-e"]=VMRUN_COMMAND + else: + fail_usage("vmware_type can be esx,server2 or server1!") + +# Main agent method def main(): - device_opt = [ "help", "version", "agent", "quiet", "verbose", "debug", - "action", "ipaddr", "login", "passwd", "passwd_script", - "secure", "identity_file", "test" , "vmipaddr", "vmlogin", - "vmpasswd", "port", "vmpasswd_script", "separator" ] + device_opt = [ "help", "version", "agent", "quiet", "verbose", "debug", + "action", "ipaddr", "login", "passwd", "passwd_script", + "test", "port", "separator", "exec", "vmware_type", + "vmware_datacenter", "secure", + "power_timeout", "shell_timeout", "login_timeout", "power_wait" ] atexit.register(atexit_handler) + vmware_define_defaults() + options = check_input(device_opt, process_input(device_opt)) - ## - ## Fence agent specific defaults - ##### - if 0 == options.has_key("-c"): - options["-c"] = "\$ " - - if 0 == options.has_key("-A"): - options["-A"] = "localhost" - - options["-x"] = 1 - - show_docs(options) - ## - ## Operate the fencing device - #### - conn = fence_login(options) - fence_action(conn, options, set_power_status, get_power_status, get_outlet_list) - - ## - ## Logout from system - ###### - try: - conn.sendline("logout") - conn.close() - except exceptions.OSError: - pass - except pexpect.ExceptionPexpect: - pass + # Default is secure connection + options["-x"] = 1 + + 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)." + show_docs(options, docs) + + # 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/fence/agents/vmware/fence_vmware_helper.pl b/fence/agents/vmware/fence_vmware_helper.pl new file mode 100644 index 0000000..a0b5cea --- /dev/null +++ b/fence/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 -- 1.6.0.6