esxi-control
esxi-control
Source : http://blog.peacon.co.uk/esxi-control-pl-script-vm-actions-on-free-licensed-esxi/
This is not my code / Ce n’est pas mon code
#!/usr/bin/perl -w
#
###############################################################################################################################
#
# esxi-control.pl
#
# Provides command-line control over virtual machines for ESX(i) 4, including free-licensed version.
#
###############################################################################################################################
#
# Usage:
#
# List registered VMs:
# esxi-control --server [hostname] --username [username] --password [password] --action list
#
# List Datastores:
# esxi-control --server [hostname] --username [username] --password [password] --action list-datastores
#
# List Recent Tasks:
# esxi-control --server [hostname] --username [username] --password [password] --action list-tasks
#
# Shutdown the ESXi Host:
# esxi-control --server [hostname] --username [username] --password [password] --action host-shutdown
#
# Restart the ESXi Host:
# esxi-control --server [hostname] --username [username] --password [password] --action host-reboot
#
# Get ESXi Host Power Management Policy:
# esxi-control --server [hostname] --username [username] --password [password] --action host-get-power-policy
#
# Change ESXi Host Power Management Policy:
# esxi-control --server [hostname] --username [username] --password [password] --action host-set-power-policy
# --policy [high-performance|balanced|low-power|custom]
#
# VM actions:
# esxi-control --server [hostname] --username [username] --password [password]
# --action [poweroff|poweron|reset|restart|shutdown|suspend|
# create-snapshot|revert-to-snapshot|delete-all-snapshots|remove-snapshot]
# --snapshot [snapshot-name] [--remove-child-snapshots]
# --vmname [vmname]
#
# File copy:
# esxi-control --server [hostname] --username [username] --password [password] --action copy-file --sourcefile [filename] --destfile [filename]
#
# File delete:
# esxi-control --server [hostname] --username [username] --password [password] --action delete-file --file [filename]
#
# Storage Functions:
#
# esxi-control --server [hostname] --username [username] --password [password] --action host-rescanvmfs
# esxi-control --server [hostname] --username [username] --password [password] --action host-rescanallhba
#
# where [action] is:
# create-snapshot to take a snapshot, named "auto-snapshot", unless
# --snapshotname [name] is specified
# copy-file to copy [sourcefile] to [destfile]
# delete-all-snapshots to remove all snapshots currently on a VM
# delete-file to delete [file] (use with caution!)
# list to list the registered VMs
# list-datastores to list the datastores and their status
# list-tasks to list the status of any current actions
# host-get-power-policy to list the ESXi host power management policy currently in use
# host-reboot to reboot the ESXi host (use with caution!)
# host-rescanallhba to rescan all HBAs on the host
# host-rescanvmfs to rescan VMFS on the host
# host-shutdown to shutdown the ESXi host (use with caution!)
# host-set-power-policy to set the ESXi host power management policy (high-performance,balanced,low-power or custom)
# poweroff to power off a VM (forcefully)
# poweron to power on or resume, a VM
# reset to forcefully reset a guest
# restart to request guest restart (needs vmware tools installed on the guest)
# remove-snapshot to remove the snapshot specified by name, and optionally child snaps on that
# (specify --remove-child-snapshots)
# revert-to-snapshot to revert to current snapshot,
# or a named snapshot if --snapshot [snapshot-name] is specified
# shutdown to shut down a VM gracefully (needs vmware tools installed on the guest)
# suspend to suspend the VM
#
#
# Examples of file paths:
# [PERC5-1TB] ESX-Lab/vyatta-VC5/vyatta-VC5.vmdk
# [SATA-1TB] Backups/vyatta.backup/vyatta-VC5.vmdk
#
# [vmname] is not needed for list- and host- options.
# [destfile] is not needed for the delete-file option.
#
##############################################################################################################################
#
# Version 1.29
#
# - New snapshot functionality added:
# - create-snapshot - --snapshotname [name] can now be added to specify the snapshot name
# - remove-snapshot - to remove the snapshot specified by name,
# or optionally child snaps on that (specify --remove-child-snapshots)
# - revert-to-snapshot - to revert to the snapshot specified by name
#
# Note that vmware allows multiple snapshots to be created with the same name: therefore, use of this script
# REQUIRES diligence in snapshot naming (on the target VM) to be sure that the intended snapshot is reverted
# or deleted.
#
#
# Version 1.28
#
# - Fixed a bug in new task progress monitoring logic that prevented the routine exiting when a task returned an error
# status until the task disapeared from the list
#
# Version 1.27
#
# - New task progress monitoring logic using $task_view->ViewBase::update_view_data();
#
#
# Version 1.26
#
# - No fixes since last version
# - Added host-rescanvmfs and host-rescanallhba functions
#
#
# Version 1.25
#
# - No fixes since last version
# - Consolidated coding in &controlVM()
# - Added reset and restart vm actions
#
#
# Version 1.24
#
# - No fixes since last version.
# - Added host-reboot command.
#
#
# Version 1.23
#
# Fixes since last version:
#
# - Changed error handling in monitorStatus function
#
#
# Version 1.22
#
# Fixes since last version:
#
# - removed a spurious line-feed when running tasks with status checking
#
#
# Version 1.21
#
# Fixes since last version:
#
# - corrected the hostname used by functions
# - added host power management setting functions
# - fixed reporting of guest shutdown task
#
##############################################################################################################################
use strict;
use warnings;
use Term::ANSIColor;
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Cookies;
use Data::Dumper;
use VMware::VIRuntime;
use VMware::VILib;
use XML::Parser;
# define command line parameters
my %opts = (
'action' => {
type => "=s",
help => "The action requested:".
"\n - create-snapshot to take a snapshot, named 'autosnapshot',".
"\n unless --snapshot [snapshot-name] is specified".
"\n - delete-all-snaphots to remove ALL snapshots from the specied VM".
"\n - delete-file ** DELETES A FILE WITHOUT CONFIRMATION **".
"\n - file-copy to copy a file".
"\n - list to list the registered VMs".
"\n - list-datastores to list datastores".
"\n - list-tasks to list tasks".
"\n - host-reboot to reboot the ESXi host (use with caution!)".
"\n - host-rescanvmfs to rescan VMFS on the host".
"\n - host-rescanallhba to rescan all HBAs on the host".
"\n - host-shutdown to shutdown the ESXi host (use with caution!)".
"\n - poweroff to power off a VM (forcefully)".
"\n - poweron to power on or resume a VM".
"\n - reset to forcefully reset a VM".
"\n - restart to restart a VM via OS (needs vmware tools)".
"\n - remove-snaphot to remove a snapshot from the specied VM,".
"\n specified by --snapshot [snapshot-name]".
"\n use --remove-child-snapshots to also remove".
"\n any childs of that snapshot".
"\n - revert-to-snapshot to revert to current snapshot, or a named".
"\n snapshot specified by --snapshot [snapshot-name]".
"\n - shutdown to shut down a VM gracefully (needs vmware tools)".
"\n - suspend to suspend a VM",
required => 1,
},
'snapshotname' => {
type => "=s",
help => "For snapshot actions, the name of the snapshot",
required => 0,
},
'remove-child-snapshots' => {
type => "",
help => "When removing snapshots, include this flag to also remove any children of that snapshot",
required => 0,
default => 0,
},
'vmname' => {
type => "=s",
help => "For VM actions, the name of the vm",
required => 0,
},
'sourcefile' => {
type => "=s",
help => "For file copy action, the full path & name of the file to copy",
required => 0,
},
'destfile' => {
type => "=s",
help => "For file copy action, the full path & name of the file to create",
required => 0,
},
'file' => {
type => "=s",
help => "For file delete action, the full path & name of the file to delete",
required => 0,
},
'policy' => {
type => "=s",
help => "For host-set-power-policy, the policy to apply",
required => 0,
},
'logfile' => {
type => "=s",
help => "Name of logfile to use",
required => 0,
default => 'esxi-control.log',
},
);
# read and validate command-line parameters
Opts::add_options(%opts);
Opts::parse();
Opts::validate();
Util::connect();
# Global vars
my @hostlist;
my ($host_view,$request,$message,$response,$retval,$cookie);
my ($action,$snapshotname,$removeChildren,$vmname,$sourcefile,$destfile,$file,$policy,$logfile,$vmid);
my ($username,$password,$hostname);
# Get command-line data
$action = Opts::get_option("action");
$vmname = Opts::get_option("vmname");
$snapshotname = Opts::get_option("snapshotname");
$removeChildren = Opts::get_option("remove-child-snapshots");
$sourcefile = Opts::get_option("sourcefile");
$destfile = Opts::get_option("destfile");
$file = Opts::get_option("file");
$policy = Opts::get_option("policy");
$logfile = Opts::get_option("logfile");
$username = Opts::get_option("username");
$password = Opts::get_option("password");
$hostname = Opts::get_option("server");
my $LOG_FILE = $logfile;
print "\nesxi-control - a perl script to control VMs for free-licensed ESXi\n\n";
# establish a view to the host...
$host_view = Vim::find_entity_view(view_type => 'HostSystem');
if ($action eq "list") {
print "\nListing VMs...\n";
&listVMs($host_view);
}
else {
if ($action eq "list-datastores") {
print "\nListing datastores...\n";
&listDataStores($host_view);
} elsif ($action eq "list-tasks") {
print "\nListing running tasks...\n";
&listTasks($host_view);
} elsif ( ($action eq "copy-file") || ($action eq "delete-file") ) {
&fileAction($host_view);
} elsif ( ($action eq "poweroff") || ($action eq "poweron") ||
($action eq "reset") || ($action eq "restart") ||
($action eq "shutdown") || ($action eq "suspend") ||
($action eq "create-snapshot") || ($action eq "delete-all-snapshots") ||
($action eq "remove-snapshot") || ($action eq "revert-to-snapshot") ) {
&controlVM($host_view);
} elsif ($action eq "host-shutdown") {
&hostShutdown($host_view);
} elsif ($action eq "host-reboot") {
&hostReboot($host_view);
} elsif ($action eq "host-get-power-policy") {
&hostGetPowerPolicy($host_view);
} elsif ($action eq "host-set-power-policy") {
&hostSetPowerPolicy($host_view);
} elsif ( ($action eq "host-rescanallhba") || ($action eq "host-rescanvmfs") ) {
&hostRescan($host_view);
} elsif ($action eq "host-get-perf-counters") {
&hostGetPerfData($host_view);
} else {
&writelog(" No actions were performed: invalid command.");
}
}
Util::disconnect();
############################################################################
# MAIN SUB-ROUTINES; THESE DO THE WORK
############################################################################
sub listVMs {
# Prints a list of registered VM on the screen.
# Does not log it's use.
#
my ($host_view) = @_;
my $vms = Vim::get_views(mo_ref_array => $host_view->vm, properties => ['name','runtime.powerState','guest.toolsRunningStatus']);
my $totalVMs = 0;
foreach(@$vms) {
$totalVMs += 1;
print "VM: ".$_->{'name'}.", ".$_->{'runtime.powerState'}->val.", "
.$_->{'guest.toolsRunningStatus'}.", ".$_->{'mo_ref'}->value."\n";
}
print "\nTotal ".$totalVMs." VMs found.\n\n"
}
sub listDataStores {
# Prints a list of datastores and their status
# Does not log it's use.
#
my ($host_view) = @_;
my $DSs = Vim::get_views(mo_ref_array => $host_view->datastore);
my $totalDSs = 0;
my $Accessible = "";
my $capacity = 0;
my $free = 0;
foreach(@$DSs) {
$totalDSs += 1;
if($_->summary->accessible) { $Accessible = "Accessible"; }
else { $Accessible = "Inaccessible"; }
$capacity = (($_->summary->capacity)/1073741824);
$free = (($_->summary->freeSpace)/1073741824);
print $_->summary->name.", ".$Accessible.", ".$_->summary->type.
", Size:".&commify(int($capacity))."GB, Free:".&commify(int($free))."GB\n";
}
print "\n".$totalDSs." datastores listed.\n\n"
}
sub listTasks {
# Prints a list of active tasks and their status
# Does not log it's use.
#
my ($host_view) = @_;
my $status = 'running';
my $si_moref = ManagedObjectReference->new(type => 'ServiceInstance', value => 'ServiceInstance');
my $si_view = Vim::get_view(mo_ref => $si_moref);
my $sc_view;
my $tm_view;
$sc_view = $si_view->RetrieveServiceContent();
$tm_view = Vim::get_view (mo_ref =>$sc_view->taskManager);
my $totalTasks = 0;
my $Task;
if (defined $tm_view->recentTask) {
foreach (@{$tm_view->recentTask}) {
$totalTasks += 1;
$Task = Vim::get_view(mo_ref => $_);
print "\nKey: ".$Task->info->key."\n";
print " - Task : ". $Task->info->descriptionId."\n";
print " - Status: ". $Task->info->state->val;
if (defined $Task->info->progress) {
print " (".$Task->info->progress."% complete)";
}
print "\n";
}
}
print "\n".$totalTasks." tasks reported.\n\n"
}
sub hostShutdown {
# shuts down the ESXi host. The host will use the VM startup and shutdown configuration per the vSphere
# client.
my ($host_view) = @_;
my ($message,$response,$retval,$cookie);
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested host shutdown on host $hostname");
# we use $actions to check if the specified action was valid
my $actions = 0;
my $taskKey;
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
# action message
$message = createShutdownMessage();
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
} else {
&writelog(" Did not get confirmation back from $hostname.");
}
} else {
&writelog(" Requested host shutdown failed - could not connect to $hostname.");
}
}
sub hostReboot {
# Reboots the ESXi host. The host will use the VM startup and shutdown configuration per the vSphere
# client.
my ($host_view) = @_;
my ($message,$response,$retval,$cookie);
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested host reboot on host $hostname");
# we use $actions to check if the specified action was valid
my $actions = 0;
my $taskKey;
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
# action message
$message = createRebootMessage();
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
} else {
&writelog(" Did not get confirmation back from $hostname.");
}
} else {
&writelog(" Requested host reboot failed - could not connect to $hostname.");
}
}
sub hostGetPerfData {
# lists VM performance data
# doesn't log it as a read-only event
my ($host_view) = @_;
my $content = Vim::get_service_content();
my $perfManager = Vim::get_view(mo_ref => $content->perfManager);
my $counter = $perfManager->perfCounter;
print "Performance Info: \n";
foreach (@$counter) {
print $_->description."\n";
}
}
sub hostGetPowerPolicy {
# lists the power management policy currently in use
# doesn't log it as a read-only event
my ($host_view) = @_;
my $hardwareSystem = Vim::get_view(mo_ref => $host_view->configManager->powerSystem);
print "Current power management policy: ";
print $hardwareSystem->info->currentPolicy->key;
print " (".$hardwareSystem->info->currentPolicy->shortName.")\n";
}
sub hostSetPowerPolicy {
# sets the power management policy currently in use
my ($host_view) = @_;
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested host power management policy change on host $hostname");
# check specified policy was valid and map it to the code used in the SOAP call
my $policyCode = 0;
if ($policy eq "high-performance") { $policyCode = 1; }
else { if ($policy eq "balanced") { $policyCode = 2; }
else { if ($policy eq "low-power") { $policyCode = 3; }
else { if ($policy eq "custom") { $policyCode = 4; } } } }
if ($policyCode == 0) {
&writelog(" Invalid power policy specified (".$policy.")");
} else {
my $hardwareSystem = Vim::get_view(mo_ref => $host_view->configManager->powerSystem);
$hardwareSystem->ConfigurePowerPolicy( key => $policyCode );
# Doesn't return a result so re-query to check the action was completed
$hardwareSystem = Vim::get_view(mo_ref => $host_view->configManager->powerSystem);
if ($policyCode == $hardwareSystem->info->currentPolicy->key) { $retval = "1" }
else { $retval = "0" }
if($retval eq 1) {
&writelog(" Policy on ".$hostname." set to ".$hardwareSystem->info->currentPolicy->shortName.".");
} else {
&writelog(" Policy on ".$hostname." could not be updated.");
}
}
}
sub fileAction {
# Performs file actions - copy and delete
my ($host_view) = @_;
my ($message,$response,$retval,$cookie);
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested file action $action on host $hostname");
# we use $actions to check if the specified action was valid
my $actions = 0;
my $taskKey;
# check the actions (copy/delete)
if ($action eq "copy-file") {
# user requested to copy a file
# operation will succeed only if source exists and is not locked, and
# if destination path exists
&writelog(" Attempting to copy:");
&writelog(" $sourcefile");
&writelog(" to: $destfile.");
# perform the task!
$actions += 1;
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
# action message depending on whether VMDK or not
if ($sourcefile =~ m/.vmdk/i) {
# VMDK file
$message = createVMDKCopyMessage();
} else {
$message = createFileCopyMessage();
}
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
if (&monitorStatus($taskKey) == 1) {
&writelog(" Task did not complete successfully.");
} else {
&writelog(" Task complete successfully.");
}
} else {
&writelog(" Did not get confirmation back from $hostname");
}
}
}
if ($action eq "delete-file") {
# user requested deleting a file - hope they meant it!
&writelog(" Attempting to delete $file.");
# perform the task!
$actions += 1;
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
# action message depending on whether VMDK or not
if ($file =~ m/.vmdk/i) {
# VMDK file
$message = createVMDKDeleteMessage();
} else {
$message = createFileDeleteMessage();
}
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
if (&monitorStatus($taskKey) == 1) {
&writelog(" Task did not complete successfully.");
} else {
&writelog(" Task complete successfully.");
}
} else {
&writelog(" Did not get confirmation back from $hostname");
}
}
}
# now report on status
if($actions==0) {
&writelog(" No actions were performed: invalid command.");
}
else {
if($retval ne 1) {
&writelog(" Requested action failed.");
}
}
}
sub controlVM {
# Performs the specified control action on the named VM. Logs this fact.
#
# Cycles through the registered VMs to try an match against the specified name
# to pull out the VMID.
#
my ($host_view) = @_;
my $vms = Vim::get_views(mo_ref_array => $host_view->vm, properties => ['name','runtime.powerState','guest.toolsRunningStatus','snapshot']);
my ($message,$response,$retval,$cookie);
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested $action of $vmname on host $hostname");
# we'll use vmid to check if we found the requested vm at the end
$vmid = 0;
# and actions to check if the specified action was valid
my $actions = 0;
my $taskKey;
# now try to do the action (power/snapshot etc)
foreach(@$vms) {
if ($_->{'name'} eq $vmname) {
# found the VM, now check action is valid
if ( ($action eq "poweroff") || ($action eq "poweron") ||
($action eq "reset") || ($action eq "restart") ||
($action eq "shutdown") || ($action eq "suspend") ||
($action eq "create-snapshot") || ($action eq "delete-all-snapshots") ||
($action eq "remove-snapshot") ||($action eq "revert-to-snapshot") ) {
$vmid = $_->{'mo_ref'}->value;
&writelog(" Attempting $action on VM $vmname, VMID $vmid");
# check vm state is compatible with requested action
$retval = 1;
if ($action eq "poweron") {
# user requested poweron, check the VM is not already on
if ($_->{'runtime.powerState'}->val eq "poweredOn") {
&writelog(" Error: VM $vmname is already powered on.");
$retval = 0;
}
} elsif ($_->{'runtime.powerState'}->val ne "poweredOn") {
# user requested an action that needs the VM powered on
&writelog(" Error: VM $vmname must be powered on to request $action");
$retval = 0;
}
# check vmware tools are running for tasks requiring them
if ( ($retval eq 1) && ( ($action eq "restart") || ($action eq "shutdown") ) ) {
if ($_->{'guest.toolsRunningStatus'} ne "guestToolsRunning") {
&writelog(" Error: VM $vmname does not have vmware tools running.");
$retval = 0;
}
}
if ($retval eq 1) {
# VM is in a compatible state - perform the task
$actions += 1;
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
$message = "";
# action message - based on requested action
if ($action eq "poweroff") {
$message = createVMPowerOffMessage();
} elsif ($action eq "poweron") {
$message = createVMPowerOnMessage();
} elsif ($action eq "reset") {
$message = createVMResetMessage();
} elsif ($action eq "restart") {
$message = createVMRestartMessage();
} elsif ($action eq "shutdown") {
$message = createVMShutdownMessage();
} elsif ($action eq "suspend") {
$message = createVMSuspendMessage();
} elsif ($action eq "create-snapshot") {
# check if snapshot name was given
if ($snapshotname) {
$message = createVMSnapShotMessage($snapshotname);
} else {
$message = createVMSnapShotMessage("auto-snapshot");
}
} elsif ($action eq "delete-all-snapshots") {
$message = createVMDeleteAllSnapShotMessage();
} elsif ($action eq "remove-snapshot") {
# check that snapshot name was given
if ($snapshotname) {
# find the snapshot internal ID
# first check there are snapshots on the VM
if ($_->{'snapshot'}) {
my $snapshotid = findSnapShotID($_->{'snapshot'},$snapshotname );
if ($snapshotid) {
&writelog(" Found snapshot $snapshotname - will use ".($vmid."-snapshot-".$snapshotid));
if ($removeChildren) { &writelog(" Will remove child snapshots too"); }
$message = createVMSnapShotRemoveMessage(($vmid."-snapshot-".$snapshotid),$removeChildren);
} else {
&writelog(" Couldn't find a snapshot named $snapshotname");
}
} else {
&writelog(" VM has no snapshots.");
}
} else {
# no snapshot name given - cannot continue
&writelog(" Snapshot name not given - nothing to do");
}
} elsif ($action eq "revert-to-snapshot") {
# check if a snapshot name was given
if ($snapshotname) {
# find the snapshot internal ID
# first check there are snapshots on the VM
if ($_->{'snapshot'}) {
my $snapshotid = findSnapShotID($_->{'snapshot'},$snapshotname );
if ($snapshotid) {
&writelog(" Found snapshot $snapshotname - will use ".($vmid."-snapshot-".$snapshotid));
$message = createVMSnapShotRevertMessage(($vmid."-snapshot-".$snapshotid));
} else {
&writelog(" Couldn't find a snapshot named $snapshotname");
}
} else {
&writelog(" VM has no snapshots.");
}
} else {
# no snapshot name given - revert to current
&writelog(" Snapshot name not given - reverting to current");
$message = createVMSnapShotRevertToCurrentMessage();
}
}
if ($message) {
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
unless (($action eq "restart") || ($action eq "shutdown")) {
if (&monitorStatus($taskKey) == 1) {
&writelog(" Task did not complete successfully.");
} else {
&writelog(" Task complete successfully.");
}
}
} else {
&writelog(" Did not get confirmation back from $hostname");
}
} else {
&writelog(" Command encountered an error.");
}
}
}
}
}
}
# now report on status
if($vmid==0) {
&writelog(" No actions were performed: VM not found.");
} elsif ($actions==0) {
&writelog(" No actions were performed: Invalid action requested.");
} elsif ($retval ne 1) {
&writelog(" Requested action failed.");
}
}
sub hostRescan {
my ($host_view) = @_;
my ($message,$response,$retval,$cookie, $taskKey);
&writelog("esxi-control called ".&giveMeDate('DMY')." at ".&giveMeDate('HMS'));
&writelog(" Requested $action on host $hostname");
$message = &createHelloMessage($username,$password);
$response = &sendRequest($hostname,$message);
$retval = checkReponse($response);
if($retval eq 1) {
# grab cookie
$cookie = &extractCookie($response);
# action message - based on requested action
if ($action eq "host-rescanvmfs") {
$message = createRescanVmfsMessage();
} elsif ($action eq "host-rescanallhba") {
$message = createRescanAllHbaMessage();
}
$response = &sendRequest($hostname,$message,$cookie);
$retval = checkReponse($response);
$taskKey = &getTaskKey($response->content);
}
if($retval eq 1) {
&writelog(" Request was accepted by $hostname.");
} else {
&writelog(" Did not get confirmation back from $hostname");
}
}
#############################################################
#
# Snapshot tree trawler follows
#
#############################################################
sub findSnapShotID {
my ($vmsnapshot, $searchstr) = @_;
my $id = 0;
my $found = 0;
foreach (@{$vmsnapshot->rootSnapshotList}) {
$id = checkSnaps($_, $searchstr);
if ($id) {
$found = $id;
}
}
return $found;
}
sub checkSnaps {
my ($snapshotTree, $searchstr) = @_;
my $found = 0;
if ($snapshotTree->name eq $searchstr) {
# found what we're looking for; return the id
$found = $snapshotTree->id;
} else {
# keep looking - recurse through the tree of snaps
if ($snapshotTree->childSnapshotList) {
# loop through any children that may exist
foreach (@{$snapshotTree->childSnapshotList}) {
$found = checkSnaps($_, $searchstr);
if ($found) {
return $found;
}
}
}
}
return $found;
}
#############################################################
#
# Functions to provide the required SOAP requests
#
#############################################################
sub sendRequest {
my ($host,$msg,$cookie) = @_;
my $host_to_connect = "https://" . $host . "/sdk";
my $userAgent = LWP::UserAgent->new(agent => 'VMware VI Client/4.1.0');
my $request = HTTP::Request->new(POST => $host_to_connect);
$request->header(SOAPAction => '"urn:internalvim25/4.1"');
$request->content($msg);
$request->content_type("text/xml; charset=utf-8");
if(defined($cookie)) {
$cookie->add_cookie_header($request);
}
my $rsp = $userAgent->request($request);
}
sub createHelloMessage {
my ($user,$pass) = @_;
my $msg = <<SOAP_HELLO_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="urn:internalvim25">
<_this xsi:type="SessionManager" type="SessionManager"
serverGuid="">ha-sessionmgr</_this>
<userName>$user</userName>
<password>$pass</password>
<locale>en_US</locale>
</Login>
</soap:Body>
</soap:Envelope>
SOAP_HELLO_MESSAGE
return $msg;
}
sub createShutdownMessage {
my $msg = <<SOAP_SHUTDOWN_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ShutdownHost_Task xmlns="urn:internalvim25">
<_this xsi:type="HostSystem" type="HostSystem" serverGuid="">ha-host</_this>
<force>true</force>
</ShutdownHost_Task>
</soap:Body>
</soap:Envelope>
SOAP_SHUTDOWN_MESSAGE
return $msg;
}
sub createRebootMessage {
my $msg = <<SOAP_SHUTDOWN_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RebootHost_Task xmlns="urn:internalvim25">
<_this xsi:type="HostSystem" type="HostSystem" serverGuid="">ha-host</_this>
<force>true</force>
</RebootHost_Task>
</soap:Body>
</soap:Envelope>
SOAP_SHUTDOWN_MESSAGE
return $msg;
}
sub createVMDKDeleteMessage {
my $msg = <<SOAP_VMFILEDELETEMESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Delete_Task xmlns="urn:internalvim25">
<_this xsi:type="FileManager" type="FileManager" serverGuid="">ha-nfc-file-manager</_this>
<datacenter type="Datacenter" serverGuid="">ha-datacenter</datacenter>
<datastorePath>$file</datastorePath>
<fileType>VirtualDisk</fileType>
</Delete_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMFILEDELETEMESSAGE
return $msg;
}
sub createFileDeleteMessage {
my $msg = <<SOAP_VMFILEDELETEMESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Delete_Task xmlns="urn:internalvim25">
<_this xsi:type="FileManager" type="FileManager" serverGuid="">ha-nfc-file-manager</_this>
<datacenter type="Datacenter" serverGuid="">ha-datacenter</datacenter>
<datastorePath>$file</datastorePath>
<fileType>File</fileType>
</Delete_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMFILEDELETEMESSAGE
return $msg;
}
sub createFileCopyMessage {
my $msg = <<SOAP_VMFILECOPYMESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Copy_Task xmlns="urn:internalvim25">
<_this xsi:type="FileManager" type="FileManager" serverGuid="">ha-nfc-file-manager</_this>
<sourceDatacenter type="Datacenter" serverGuid="">ha-datacenter</sourceDatacenter>
<sourcePath>$sourcefile</sourcePath>
<destinationPath>$destfile</destinationPath>
<force>false</force>
<fileType>File</fileType>
</Copy_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMFILECOPYMESSAGE
return $msg;
}
sub createVMDKCopyMessage {
my $msg = <<SOAP_VMFILECOPYMESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Copy_Task xmlns="urn:internalvim25">
<_this xsi:type="FileManager" type="FileManager" serverGuid="">ha-nfc-file-manager</_this>
<sourceDatacenter type="Datacenter" serverGuid="">ha-datacenter</sourceDatacenter>
<sourcePath>$sourcefile</sourcePath>
<destinationPath>$destfile</destinationPath>
<force>false</force>
<fileType>VirtualDisk</fileType>
</Copy_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMFILECOPYMESSAGE
return $msg;
}
sub createVMDeleteAllSnapShotMessage {
my $msg = <<SOAP_VMDELETEALLSNAPSHOT_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RemoveAllSnapshots_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</RemoveAllSnapshots_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMDELETEALLSNAPSHOT_MESSAGE
return $msg;
}
sub createVMSnapShotMessage {
my ($name) = @_;
my $msg = <<SOAP_VMSNAPSHOT_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<CreateSnapshot_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
<name>$name</name>
<description></description>
<memory>false</memory>
<quiesce>true</quiesce>
</CreateSnapshot_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMSNAPSHOT_MESSAGE
return $msg;
}
sub createVMSnapShotRevertMessage {
my ($vmsnapshotid) = @_;
my $msg = <<SOAP_VMSNAPSHOTREVERT_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RevertToSnapshot_Task xmlns="urn:internalvim25">
<_this xsi:type="ManagedObjectReference" type="VirtualMachineSnapshot" serverGuid="">$vmsnapshotid</_this>
</RevertToSnapshot_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMSNAPSHOTREVERT_MESSAGE
return $msg;
}
sub createVMSnapShotRemoveMessage {
my ($vmsnapshotid, $removeChildren) = @_;
my $removeChildrenStr = "false";
if ($removeChildren) { $removeChildrenStr = "true"; }
my $msg = <<SOAP_VMSNAPSHOTREMOVE_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RemoveSnapshot_Task xmlns="urn:internalvim25">
<_this xsi:type="ManagedObjectReference" type="VirtualMachineSnapshot" serverGuid="">$vmsnapshotid</_this>
<removeChildren>$removeChildrenStr</removeChildren>
</RemoveSnapshot_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMSNAPSHOTREMOVE_MESSAGE
return $msg;
}
sub createVMSnapShotRevertToCurrentMessage {
my $msg = <<SOAP_VMSNAPSHOTREREVERTTOCURRENT_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RevertToCurrentSnapshot_Task xmlns="urn:internalvim25">
<_this xsi:type="ManagedObjectReference" type="VirtualMachine" serverGuid="">$vmid</_this>
</RevertToCurrentSnapshot_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMSNAPSHOTREREVERTTOCURRENT_MESSAGE
return $msg;
}
sub createVMResetMessage {
my $msg = <<SOAP_VMRESET_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ResetVM_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</ResetVM_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMRESET_MESSAGE
return $msg;
}
sub createVMRestartMessage {
my $msg = <<SOAP_VMRESTART_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RebootGuest xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</RebootGuest>
</soap:Body>
</soap:Envelope>
SOAP_VMRESTART_MESSAGE
return $msg;
}
sub createVMShutdownMessage {
my $msg = <<SOAP_VMSHUTDOWN_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ShutdownGuest xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</ShutdownGuest>
</soap:Body>
</soap:Envelope>
SOAP_VMSHUTDOWN_MESSAGE
return $msg;
}
sub createVMSuspendMessage {
my $msg = <<SOAP_VMSUSPEND_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SuspendVM_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</SuspendVM_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMSUSPEND_MESSAGE
return $msg;
}
sub createVMPowerOnMessage {
my $msg = <<SOAP_VMPOWERON_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PowerOnVM_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</PowerOnVM_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMPOWERON_MESSAGE
return $msg;
}
sub createVMPowerOffMessage {
my $msg = <<SOAP_VMPOWERON_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PowerOffVM_Task xmlns="urn:internalvim25">
<_this xsi:type="VirtualMachine" type="VirtualMachine" serverGuid="">$vmid</_this>
</PowerOffVM_Task>
</soap:Body>
</soap:Envelope>
SOAP_VMPOWERON_MESSAGE
return $msg;
}
sub createRescanAllHbaMessage {
my $msg = <<SOAP_RESCANHBA_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RescanAllHba xmlns="urn:internalvim25">
<_this xsi:type="ManagedObjectReference" type="HostStorageSystem" serverGuid="">storageSystem</_this>
</RescanAllHba>
</soap:Body>
</soap:Envelope>
SOAP_RESCANHBA_MESSAGE
return $msg;
}
sub createRescanVmfsMessage {
my $msg = <<SOAP_RESCANVMFS_MESSAGE;
<soap:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<RescanVmfs xmlns="urn:internalvim25">
<_this xsi:type="ManagedObjectReference" type="HostStorageSystem" serverGuid="">storageSystem</_this>
</RescanVmfs>
</soap:Body>
</soap:Envelope>
SOAP_RESCANVMFS_MESSAGE
return $msg;
}
sub monitorStatus {
my ($taskKey) = @_;
my $continue = 0;
my $taskpc = "";
my $taskstate = "";
my $outputneeded = 1;
my ($info, $Task, $taskRef);
my $error = 0;
print " Waiting for task ".$taskKey.":\n";
# First find the task ref object since we only have the text key so far
my $si_moref = ManagedObjectReference->new(type => 'ServiceInstance', value => 'ServiceInstance');
my $si_view = Vim::get_view(mo_ref => $si_moref);
my $sc_view = $si_view->RetrieveServiceContent();
my $tm_view = Vim::get_view(mo_ref =>$sc_view->taskManager);
if (defined $tm_view->recentTask) {
foreach (@{$tm_view->recentTask}) {
$Task = Vim::get_view(mo_ref => $_);
if ($Task->info->key eq $taskKey) {
$taskRef = $_;
$continue = 1;
}
}
}
if (!$continue) {
print " Error: Couldn't find task in the recent tasks list\n";
return 1;
}
$| = 1; # Turn off buffering on STDOUT (otherwise \r doesn't work as stdout is line-buffered)
sleep 3; # give any quick tasks a moment to run, to avoid hitting a 15-second wait below
# Next poll the host periodically and display the task status as we go.
my $task_view = Vim::get_view(mo_ref => $taskRef);
while ($continue) {
$info = $task_view->info;
if ($info->state->val eq 'success') {
print "\r - status: ".$info->state->val." \n";
my $message = " Final status was ".$info->state->val;
if (defined $info->progress) {
$message = $message ." (".$info->progress."%)";
}
&writelog($message);
$continue = 0;
} elsif ($info->state->val eq 'error') {
if (defined $info->error->localizedMessage) {
print "\n";
&writelog (" Task returned error - ".$info->error->localizedMessage);
}
$error = 1;
$continue = 0;
} else {
# No error, task is running
if (defined $info->progress) {
if ($info->progress ne $taskpc) {
# update the status line
$taskstate = $info->state->val;
print "\r - status: ".$taskstate;
if (defined $info->progress) {
$taskpc = $info->progress;
print " (".$taskpc."%)";
} else {
print " "; #ensure old status doesn't show through new
}
$outputneeded = 0;
}
}
}
if ($continue) {
# wait a bit then refresh the view, unless we are done
sleep 14;
$task_view->ViewBase::update_view_data();
}
}
$| = 0; # Set STDOUT back to line-buffered mode
return $error;
}
#####################################################################
#
# Helpers - cookie grab etc
#
#####################################################################
sub extractCookie {
my ($rsp) = @_;
my $cookie_jar = HTTP::Cookies->new;
$cookie_jar->extract_cookies($rsp);
return $cookie_jar;
}
sub checkReponse {
my ($resp) = @_;
my $ret = -1;
if($resp->code == 200) {
return 1;
} else {
# print "\n".$resp->error_as_HTML."\n";
return $ret;
}
}
sub getTaskKey {
my ($resp) = @_;
my $start = index($resp,"<returnval type=\"Task\">") +23;
my $end = index($resp,"</returnval>");
my $len = $end - $start;
my $key;
if ($len) {
$key = substr($resp, $start, $len);
}
else {
$key = "";
}
return $key;
}
# Subroutine to process the input file
sub processFile {
my ($hostlist) = @_;
my $HANDLE;
open (HANDLE, $hostlist) or die("ERROR: Can not locate \"$hostlist\" input file!\n");
my @lines = <HANDLE>;
my @errorArray;
my $line_no = 0;
close(HANDLE);
foreach my $line (@lines) {
$line_no++;
&TrimSpaces($line);
if($line) {
if($line =~ /^\s*:|:\s*$/){
print "Error in Parsing File at line: $line_no\n";
print "Continuing to the next line\n";
next;
}
my $host = $line;
&TrimSpaces($host);
push @hostlist,$host;
}
}
}
sub TrimSpaces {
foreach (@_) {
s/^\s+|\s*$//g
}
}
#####################################################################
#
# Helpers - generic code eg log writing, get date
#
#####################################################################
sub writelog {
# Appends specified text to the log file
# also displays on screen
print "@_\n";
open (LOGFILE, ">>$LOG_FILE");
print LOGFILE "@_\n";
close (LOGFILE);
}
sub giveMeDate {
my ($date_format) = @_;
my %dttime = ();
my $my_time;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
### begin_: initialize DateTime number formats
$dttime{year } = sprintf "%04d",($year + 1900); ## four digits to specify the year
$dttime{mon } = sprintf "%02d",($mon + 1); ## zeropad months
$dttime{mday } = sprintf "%02d",$mday; ## zeropad day of the month
$dttime{wday } = sprintf "%02d",$wday + 1; ## zeropad day of week; sunday = 1;
$dttime{yday } = sprintf "%02d",$yday; ## zeropad nth day of the year
$dttime{hour } = sprintf "%02d",$hour; ## zeropad hour
$dttime{min } = sprintf "%02d",$min; ## zeropad minutes
$dttime{sec } = sprintf "%02d",$sec; ## zeropad seconds
$dttime{isdst} = $isdst;
if($date_format eq 'MDYHMS') {
$my_time = "$dttime{mon}-$dttime{mday}-$dttime{year} $dttime{hour}:$dttime{min}:$dttime{sec}";
}
elsif ($date_format eq 'YMD') {
$my_time = "$dttime{year}-$dttime{mon}-$dttime{mday}";
}
elsif ($date_format eq 'DMY') {
$my_time = "$dttime{mday}-$dttime{mon}-$dttime{year}";
}
elsif ($date_format eq 'HMS') {
$my_time = "$dttime{hour}:$dttime{min}:$dttime{sec}";
}
return $my_time;
}
sub commify {
local($_) = shift;
1 while s/^(-?\d+)(\d{3})/$1,$2/;
return $_;
}
####################
## END OF SCRIPT
####################