The VMware perl SDK utilities have a script to evacuate an ESX server to another server, but it is painfully slow since it is done serially. I modified the script to use threads and queue up 6 moves (VMotions) at a time. The script also has an option to place the ESX server into maintenance mode after it is evacuated.
Since this script bypasses the DRS evacuation which is done from the GUI when you place a system into maintenance mode, it appears to be quite a bit faster (maybe 50% faster given my initial tests?). Feel free to post comments with your experience using the script. Obviously you'll need a working install of the VMware perl 1.5 perl modules (SDK) to use this script.
Right -click and select "Save..." to download perl source.
#!/usr/bin/perl -w
#
# This scripts allows users to move all running vm's from one host to another.
# Modified by Darren Patterson to be multithreaded and include an option to place the
# source ESX server into maintenance mode after the evac finishes.
use strict;
use warnings;
use threads;
use threads::shared;
use VMware::VIM25Runtime;
use VMware::VILib;
# MAX number of concurrently running evac processes.
my $MAXT = 6;
# seconds to sleep while checking for completed threads.
my $SLEEPSIZE = 8;
my %opts = (
src => {
type => "=s",
help => "Host to evacuate",
required => 1,
},
dst => {
type => "=s",
help => "Destination host for VMs",
required => 1,
},
maint => {
type => "",
help => "Place the source host that is evacuating into maintenance mode",
required => 0,
},
);
# read/validate options and connect to the server
Opts::add_options(%opts);
Opts::parse();
Opts::validate();
Util::connect();
# get source host view
my $src_host_name = Opts::get_option('src');
my $src_host_view = Vim::find_entity_view(view_type => 'HostSystem',
filter => { name => $src_host_name });
if (!$src_host_view) {
die "Source host '$src_host_name' not found\n";
}
# get destination host view
my $dst_host_name = Opts::get_option('dst');
# the destination host view must be a shared variable for threads to see
my $dst_host_view = Vim::find_entity_view(view_type => 'HostSystem',
filter => { name => $dst_host_name });
if (! $dst_host_view) {
die "Destiation host '$dst_host_name' not found\n";
}
# get all VM's under src host
my $vm_views = Vim::find_entity_views(view_type => 'VirtualMachine',
begin_entity => $src_host_view);
# migrate all vm's
#print "Starting threads...\n";
my @results : shared;
my $inc = 0;
foreach (@$vm_views) {
threads->new(\&evac, $dst_host_view, $_);
#print "Started thread: " . $t->tid . "\n";
$inc++;
while (($inc - ($#results+1) >= $MAXT) && (@$vm_views > $inc)) {
#print "wait thread(s) done: ".$inc." - ".($#results+1)." >= $MAXT\n";
sleep($SLEEPSIZE);
}
}
print "Waiting for all VMotion threads to finish...\n";
sleep ($SLEEPSIZE) while ($#results+1 < @$vm_views);
my $return;
foreach (@results) {
if (/returned 2$/) {
$return = 2;
print STDERR "An error was returned by a VMotion thread.\n";
}
}
# Note: Specifically don't join threads to avoid double free segfault.
# Let perl do the cleanup on exit.
#
# Loop through all the threads
# foreach my $thr (threads->list) {
# Don't join the main thread or ourselves
# if ($thr->tid && !threads::equal($thr, threads->self)) {
# $thr->join if (defined($thr) && print "joined thread: ".$thr->tid."\n");
# }
#}
# it may still be possible to enter maintenance mode even though an error
# may have happened earlier
if (Opts::get_option('maint')) {
print "Putting ".$src_host_name." into maintenance mode...\n";
$return = enter_maintenance($src_host_view);
}
print "done\n";
# disconnect from the server
Util::disconnect();
return $return;
sub enter_maintenance {
my $target_host = shift;
eval {
$target_host->EnterMaintenanceMode(timeout => 0);
print "\nHost '" . $target_host->name
. "' entered maintenance mode successfully\n";
return 0;
};
if ($@) {
if (ref($@) eq 'SoapFault') {
if (ref($@->detail) eq 'InvalidState') {
print "\nThe enter_maintenancemode operation".
" is not allowed in the current state";
}
elsif (ref($@->detail) eq 'Timedout') {
print "\nOperation timed out\n";
}
elsif (ref($@->detail) eq 'HostNotConnected') {
print "\nUnable to communicate with the"
. " remote host, since it is disconnected.\n";
}
else {
print "\nHost cannot be entered into maintenance mode \n" . $@. "";
}
}
else {
print "\nHost cannot be entered into maintenance mode \n" . $@. "";
}
return 2;
}
}
sub evac {
my $dst_host_view = shift;
my $vm = shift;
eval {
print "Starting VMotion for ".$vm->name."\n";
$vm->MigrateVM(host => $dst_host_view,
priority => VirtualMachineMovePriority->new('defaultPriority'),
state => VirtualMachinePowerState->new('poweredOn'));
print "VM " . $vm->name . " evacuated successfully.\n";
push (@results, (threads->self)->tid." returned 0");
return 0;
};
if ($@ && $vm->runtime->powerState->val eq 'poweredOn') {
# unexpected error
print "Unable to evacuate VM '" . $vm->name . "'\n";
print "Reason: " . $@ . "\n\n";
push (@results, (threads->self)->tid." returned 2");
return 2;
}
}
##############################################################################
# Documentation
##############################################################################
=head1 NAME
vmevac - evacuate an ESX server's VM guests to another ESX server
=head1 SYNOPSIS
B B<--src> I B<--dst> I [B<--maint>]
=head1 DESCRIPTION
Evacuate an ESX server's VM guests to another ESX server.
=head1 OPTIONS
=over 4
=item B<-h>, B<--help>
Print out help.
=item B<--dst> I
The destination ESX server to relocate VM guests to.
=item B<--maint>
Place the source ESX server into maintenance mode after evacuation.
=item B<--src> I
The souce ESX server to evacuate VM guests from.
=back
=head1 EXAMPLES
vmevac --src hal07.stanford.edu --dst hal08.stanford.edu
vmevac --src hal07.stanford.edu --dst hal08.stanford.edu --maint