889 lines
19 KiB
Perl
Executable File
889 lines
19 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use experimental qw(switch say);
|
|
|
|
use VMware::VIRuntime;
|
|
use VMware::VICredStore;
|
|
use VMware::VILib;
|
|
use VMware::VIExt;
|
|
use Time::Piece;
|
|
use Time::Seconds;
|
|
use File::Path;
|
|
use Sys::CPU;
|
|
use Term::ANSIColor;
|
|
use IO::Handle;
|
|
use List::Util qw(min max);
|
|
|
|
use constant false => 0;
|
|
use constant true => 1;
|
|
|
|
# Perl configuration file
|
|
|
|
my %config;
|
|
my @config_files = (
|
|
'/etc/vn-vmware/config.my.pl',
|
|
'/etc/vn-vmware/config.pl',
|
|
'config.my.pl',
|
|
'config.pl'
|
|
);
|
|
|
|
foreach my $config_file (@config_files) {
|
|
if (-e $config_file) {
|
|
%config = do $config_file;
|
|
last;
|
|
}
|
|
}
|
|
unless (%config) {
|
|
die "Configuration file not found.";
|
|
}
|
|
|
|
# Vim configuration file
|
|
|
|
my @vi_config_files = (
|
|
'/etc/vn-vmware/visdkrc',
|
|
'visdkrc'
|
|
);
|
|
|
|
foreach my $vi_config_file (@vi_config_files) {
|
|
if (-e $vi_config_file) {
|
|
Opts::set_option('config', $vi_config_file);
|
|
}
|
|
}
|
|
|
|
my %opts = (
|
|
'operation' => {
|
|
type => "=s",
|
|
help => "Operation to perform: backup-job, clone-job, backup, rotate, clone, snapshot, migrate, test",
|
|
required => true
|
|
},
|
|
'job' => {
|
|
type => "=s",
|
|
help => "The job name"
|
|
},
|
|
'vm-name' => {
|
|
type => "=s",
|
|
variable => "VM_NAME",
|
|
help => "Name of the virtual machine"
|
|
},
|
|
'rotation-days' => {
|
|
type => "=i",
|
|
help => "Rotation days for backups",
|
|
default => 0
|
|
},
|
|
'rotation-count' => {
|
|
type => "=i",
|
|
help => "Number of backups to keep despite the rotation days",
|
|
default => 0
|
|
},
|
|
'dst-name' => {
|
|
type => "=s",
|
|
help => "Name of the new virtual machine"
|
|
},
|
|
'dst-host' => {
|
|
type => "=s",
|
|
help => "Name of the target host"
|
|
},
|
|
'dst-datastore' => {
|
|
type => "=s",
|
|
help => "Destination datastore"
|
|
},
|
|
'memory' => {
|
|
type => "=s",
|
|
help => "Memory amount in MB"
|
|
},
|
|
'num-cpus' => {
|
|
type => "=s",
|
|
help => "Number of cores",
|
|
default => 1
|
|
},
|
|
'mac' => {
|
|
type => "=s",
|
|
help => "MAC address"
|
|
},
|
|
'vnic' => {
|
|
type => "=s",
|
|
help => "NIC index",
|
|
default => 1
|
|
},
|
|
'priority' => {
|
|
type => "=s",
|
|
help => "Operation priority: highpriority, slowpriority, defaultpriority",
|
|
default => 'defaultpriority'
|
|
},
|
|
'cpu-reservation' => {
|
|
type => "=i",
|
|
help => "CPU Reservation in Mhz",
|
|
default => 0
|
|
},
|
|
'mem-reservation' => {
|
|
type => "=i",
|
|
help => "Memory reservation",
|
|
default => 0
|
|
},
|
|
'overwrite' => {
|
|
type => "",
|
|
help => "Whether to remove the destination machine if it exists; Be very careful with this option!",
|
|
default => 0
|
|
},
|
|
'poweron' => {
|
|
type => "",
|
|
help => "Whether to power on machine after operation",
|
|
default => 1
|
|
},
|
|
'snapshot-name' => {
|
|
type => "=s",
|
|
help => "Name of the snapshot",
|
|
default => "snapshot"
|
|
},
|
|
'snapshot-desc' => {
|
|
type => "=s",
|
|
help => "Snapshot description",
|
|
default => "Snapshot"
|
|
}
|
|
);
|
|
|
|
Opts::add_options(%opts);
|
|
Opts::parse();
|
|
Opts::validate();
|
|
|
|
my $server = Opts::get_option('server');
|
|
my $operation = Opts::get_option('operation');
|
|
my $job = Opts::get_option('job');
|
|
my $vm_name = Opts::get_option('vm-name');
|
|
my $rotation_days = Opts::get_option('rotation-days');
|
|
my $rotation_count = Opts::get_option('rotation-count');
|
|
my $dst_name = Opts::get_option('dst-name');
|
|
my $dst_host = Opts::get_option('dst-host');
|
|
my $dst_datastore = Opts::get_option('dst-datastore');
|
|
my $memory = Opts::get_option('memory');
|
|
my $num_cpus = Opts::get_option('num-cpus');
|
|
my $mac = Opts::get_option('mac');
|
|
my $vnic = Opts::get_option('vnic');
|
|
my $priority = Opts::get_option('priority');
|
|
my $mem_reservation = Opts::get_option('mem-reservation');
|
|
my $cpu_reservation = Opts::get_option('cpu-reservation');
|
|
my $overwrite = Opts::option_is_set('overwrite');
|
|
my $poweron = Opts::option_is_set('poweron');
|
|
my $snapshot_name = Opts::get_option('snapshot-name');
|
|
my $snapshot_desc = Opts::get_option('snapshot-desc');
|
|
|
|
my $vm;
|
|
my $log_fh;
|
|
my $time_pattern = '%Y-%m-%d_%H-%M';
|
|
my $local_backup_dir = $config{local_backup_dir};
|
|
|
|
sub log_to_file {
|
|
my ($message) = @_;
|
|
my $time = Time::Piece->new;
|
|
my $time_mark = $time->strftime('%Y-%m-%d %H:%M:%S');
|
|
print $log_fh "$time_mark $message";
|
|
}
|
|
|
|
sub log_message {
|
|
my ($message) = @_;
|
|
|
|
Util::trace(1, "$message\n");
|
|
|
|
if ($log_fh) {
|
|
log_to_file "LOG: $message\n";
|
|
}
|
|
}
|
|
|
|
sub log_error {
|
|
my ($error) = @_;
|
|
|
|
if ($error->isa('SoapFault')) {
|
|
my $detail = ref $error->detail;
|
|
$error = "SoapFault: $detail: $error->{fault_string}";
|
|
}
|
|
unless (substr($error, -1) eq "\n") {
|
|
$error .= "\n";
|
|
}
|
|
|
|
print STDERR $error;
|
|
|
|
if ($log_fh) {
|
|
log_to_file "ERR: $error";
|
|
}
|
|
}
|
|
|
|
if (exists $config{log_file}) {
|
|
open($log_fh, '>>', $config{log_file});
|
|
$log_fh->autoflush(1);
|
|
}
|
|
|
|
eval {
|
|
main();
|
|
};
|
|
if ($@) {
|
|
log_error $@;
|
|
}
|
|
|
|
if ($log_fh) {
|
|
close $log_fh;
|
|
}
|
|
|
|
sub main {
|
|
Util::connect();
|
|
log_message "Connected to $server.";
|
|
|
|
# TODO: Keep session alive on large operations
|
|
$SIG{ALRM} = sub {
|
|
alarm(60);
|
|
my $si_view = Vim::get_service_instance();
|
|
$si_view->CurrentTime();
|
|
};
|
|
alarm(1);
|
|
|
|
my $about = Vim::get_service_content()->about;
|
|
log_message "Version: $about->{apiType} $about->{version}";
|
|
|
|
eval {
|
|
unless ($operation) {
|
|
die "Operation not defined.";
|
|
}
|
|
given ($operation) {
|
|
when ('backup-job') {
|
|
backup_job();
|
|
}
|
|
when ('clone-job') {
|
|
clone_job();
|
|
}
|
|
when ('backup') {
|
|
open_machine();
|
|
backup_machine();
|
|
}
|
|
when ('rotate') {
|
|
rotate_backup();
|
|
}
|
|
when ('clone') {
|
|
open_machine();
|
|
clone_machine();
|
|
}
|
|
when ('snapshot') {
|
|
open_machine();
|
|
snapshot_machine();
|
|
}
|
|
when ('migrate') {
|
|
open_machine();
|
|
migrate_machine();
|
|
}
|
|
when ('test') {
|
|
test_operation();
|
|
}
|
|
default {
|
|
die "Unknown operation '$operation'.";
|
|
}
|
|
}
|
|
};
|
|
my $err = $@;
|
|
|
|
alarm(0);
|
|
Util::disconnect();
|
|
|
|
if ($err) {
|
|
die $err;
|
|
}
|
|
}
|
|
|
|
#--------------------------------- Operations
|
|
|
|
sub backup_job() {
|
|
unless ($job) {
|
|
die "Job not defined.";
|
|
}
|
|
unless (exists $config{backup_jobs}{$job}) {
|
|
die "Backup job '$job' doesn't exist.";
|
|
}
|
|
|
|
log_message "Backup job '$job' started.";
|
|
|
|
my $backup_job = $config{backup_jobs}{$job};
|
|
|
|
if (exists $backup_job->{rotation}) {
|
|
my $rotation_name = $backup_job->{rotation};
|
|
|
|
unless (exists $config{rotations}{$rotation_name}) {
|
|
die "Rotation '$rotation_name' not defined.";
|
|
}
|
|
|
|
my $rotation_cfg = $config{rotations}{$rotation_name};
|
|
|
|
if (exists $rotation_cfg->{count}) {
|
|
$rotation_count = $rotation_cfg->{count};
|
|
}
|
|
if (exists $rotation_cfg->{days}) {
|
|
$rotation_days = $rotation_cfg->{days};
|
|
}
|
|
}
|
|
|
|
my @machines = @{$backup_job->{machines}};
|
|
|
|
foreach my $machine (@machines) {
|
|
eval {
|
|
$vm_name = $machine;
|
|
open_machine();
|
|
backup_machine();
|
|
rotate_backup();
|
|
};
|
|
if ($@) {
|
|
log_error $@;
|
|
}
|
|
}
|
|
|
|
log_message "Backup job '$job' finished.";
|
|
}
|
|
|
|
sub clone_job() {
|
|
unless ($job) {
|
|
die "Job not defined.";
|
|
}
|
|
unless (exists $config{clone_jobs}{$job}) {
|
|
die "Clone job '$job' doesn't exist.";
|
|
}
|
|
|
|
log_message "Clone job '$job' started.";
|
|
|
|
my $clone_job = $config{clone_jobs}{$job};
|
|
|
|
$vm_name = $clone_job->{vm};
|
|
$dst_name = $clone_job->{dst_name};
|
|
$dst_host = $clone_job->{dst_host};
|
|
$dst_datastore = $clone_job->{dst_datastore};
|
|
$memory = $clone_job->{memory};
|
|
$num_cpus = $clone_job->{num_cpus};
|
|
$mac = $clone_job->{mac};
|
|
$poweron = $clone_job->{poweron};
|
|
$overwrite = $clone_job->{overwrite};
|
|
|
|
open_machine();
|
|
clone_machine();
|
|
|
|
log_message "Clone job '$job' finished.";
|
|
}
|
|
|
|
sub backup_machine() {
|
|
log_message "Backup of '$vm_name' started.";
|
|
|
|
my $time = Time::Piece->new;
|
|
my $time_mark = $time->strftime($time_pattern);
|
|
my $tmpDir = ".$time_mark";
|
|
my $backup_datastore = $config{backup_datastore};
|
|
my $ds_tmp_dir = "[$backup_datastore] $vm_name/$tmpDir";
|
|
my $local_dir = "$local_backup_dir/$vm_name";
|
|
my $local_tmp_dir = "$local_dir/$tmpDir";
|
|
my $tar_file = "$local_dir/${vm_name}_$time_mark.tar.gz";
|
|
|
|
if (-e $local_tmp_dir) {
|
|
die "Temporary backup directory already exists: $local_tmp_dir";
|
|
}
|
|
|
|
my $service_content = Vim::get_service_content();
|
|
my $file_manager = Vim::get_view(mo_ref => $service_content->fileManager);
|
|
my $dc = Vim::find_entity_view(
|
|
view_type => 'Datacenter',
|
|
filter => {'name' => $config{datacenter}}
|
|
);
|
|
|
|
log_message "Creating temporary backup directory: $ds_tmp_dir";
|
|
$file_manager->MakeDirectory(
|
|
name => $ds_tmp_dir,
|
|
datacenter => $dc,
|
|
createParentDirectories => true
|
|
);
|
|
|
|
unless (-e $local_tmp_dir) {
|
|
log_message "Aborting, removing temporary directory: $ds_tmp_dir";
|
|
$file_manager->DeleteDatastoreFile(
|
|
name => $ds_tmp_dir,
|
|
datacenter => $dc
|
|
);
|
|
|
|
die "Local backup directory is not accessible: $local_tmp_dir";
|
|
}
|
|
|
|
my $snapshot;
|
|
|
|
eval {
|
|
log_message "Copying machine files.";
|
|
|
|
my $files = $vm->layoutEx->file;
|
|
my @copy_files = (
|
|
'config',
|
|
'extendedConfig',
|
|
'nvram',
|
|
'log'
|
|
);
|
|
my @opt_files = (
|
|
'log'
|
|
);
|
|
|
|
foreach my $file (@$files) {
|
|
unless ($file->type ~~ @copy_files) {
|
|
next;
|
|
}
|
|
|
|
my $file_path = $file->name;
|
|
my $file_name = basename($file_path);
|
|
|
|
log_message $file_path;
|
|
eval {
|
|
$file_manager->CopyDatastoreFile(
|
|
sourceName => $file_path,
|
|
sourceDatacenter => $dc,
|
|
destinationName => "$ds_tmp_dir/$file_name",
|
|
destinationDatacenter => $dc
|
|
);
|
|
};
|
|
my $err = $@;
|
|
|
|
if ($err) {
|
|
if ($file->type ~~ @opt_files) {
|
|
log_message $err;
|
|
} else {
|
|
die $err
|
|
}
|
|
}
|
|
}
|
|
|
|
log_message "Creating backup snapshot.";
|
|
my $snapshot_ref = $vm->CreateSnapshot(
|
|
name => "backup",
|
|
description => "Scheduled backup",
|
|
memory => false,
|
|
quiesce => true
|
|
);
|
|
$snapshot = Vim::get_view(mo_ref => $snapshot_ref);
|
|
|
|
log_message "Copying virtual disk files.";
|
|
|
|
my $devices = $snapshot->config->hardware->device;
|
|
my $vdm = Vim::get_view(mo_ref => $service_content->virtualDiskManager);
|
|
|
|
my $i = -1;
|
|
my @copied_devices = ();
|
|
|
|
foreach my $device (@$devices) {
|
|
unless ($device->isa('VirtualDisk')) {
|
|
next;
|
|
}
|
|
|
|
$i++;
|
|
my $disk_path = $device->backing->fileName;
|
|
my $disk_file = basename($disk_path);
|
|
log_message $disk_path;
|
|
|
|
if ($disk_file ~~ @copied_devices) {
|
|
$disk_file = "${i}_$disk_file";
|
|
log_message "Duplicated disk file name, renamed to: $disk_file";
|
|
}
|
|
|
|
push @copied_devices, $disk_file;
|
|
|
|
# XXX: Not implemented by Perl vSphere SDK
|
|
#my $disk_spec = VirtualDiskSpec->new(
|
|
# adapterType => 'busLogic',
|
|
# diskType => 'thin'
|
|
#);
|
|
$vdm->CopyVirtualDisk(
|
|
sourceName => $disk_path,
|
|
sourceDatacenter => $dc,
|
|
destName => "$ds_tmp_dir/$disk_file",
|
|
destDatacenter => $dc
|
|
#destSpec => $disk_spec
|
|
);
|
|
}
|
|
|
|
log_message "Removing backup snapshot.";
|
|
$snapshot->RemoveSnapshot(
|
|
removeChildren => true,
|
|
consolidate => true
|
|
);
|
|
$snapshot = undef;
|
|
|
|
my $pigz_processes;
|
|
|
|
if (exists $config{pigz_processes}) {
|
|
$pigz_processes = $config{pigz_processes};
|
|
} else {
|
|
$pigz_processes = int(Sys::CPU::cpu_count()) - 2;
|
|
}
|
|
|
|
$pigz_processes = max(1, $pigz_processes);
|
|
my $tar_command = "tar -I \"pigz -p $pigz_processes\" -cf $tar_file -C $local_tmp_dir .";
|
|
|
|
log_message "Compressing with Gzip (using $pigz_processes processes) to TAR file.";
|
|
log_message $tar_command;
|
|
my $tar_status = system($tar_command);
|
|
|
|
unless ($tar_status == 0) {
|
|
die "An error occurred when trying to compress '$vm_name' machine files.";
|
|
}
|
|
};
|
|
|
|
my $err = $@;
|
|
if ($err) {
|
|
log_error "An error ocurred during '$vm_name' backup, aborting.";
|
|
|
|
if ($snapshot) {
|
|
log_message "Removing backup snapshot.";
|
|
$snapshot->RemoveSnapshot(
|
|
removeChildren => true,
|
|
consolidate => true
|
|
);
|
|
}
|
|
}
|
|
|
|
log_message "Removing temporary directory: $local_tmp_dir";
|
|
rmtree($local_tmp_dir);
|
|
|
|
if ($err) {
|
|
die $err;
|
|
}
|
|
|
|
log_message "Backup of '$vm_name' successfully created.";
|
|
}
|
|
|
|
sub rotate_backup() {
|
|
if ($rotation_days == 0 && $rotation_count == 0) {
|
|
return;
|
|
}
|
|
|
|
log_message "Rotating '$vm_name' backups.";
|
|
|
|
my $local_dir = "$local_backup_dir/$vm_name";
|
|
my $regex = qr/\Q$vm_name\E_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2})\.tar\.gz$/;
|
|
|
|
my @delete_files;
|
|
my $file_count = 0;
|
|
|
|
my $rotateTime = Time::Piece->new();
|
|
$rotateTime -= ONE_DAY * $rotation_days;
|
|
|
|
opendir(my $dh, $local_dir);
|
|
while (my $file = readdir($dh)) {
|
|
my @reResult;
|
|
unless (@reResult = $file =~ $regex) {
|
|
next;
|
|
}
|
|
$file_count++;
|
|
my ($fileMatch) = @reResult;
|
|
my $fileTime = Time::Piece->strptime($fileMatch, $time_pattern);
|
|
|
|
if ($fileTime < $rotateTime) {
|
|
push(@delete_files, $file);
|
|
}
|
|
}
|
|
|
|
my $delete_count = scalar(@delete_files);
|
|
my $kept_count = $file_count - $delete_count;
|
|
|
|
if ($kept_count < $rotation_count) {
|
|
@delete_files = sort @delete_files;
|
|
splice(@delete_files, -min($rotation_count - $kept_count, $delete_count));
|
|
}
|
|
|
|
$delete_count = scalar(@delete_files);
|
|
|
|
if ($delete_count > 0) {
|
|
if ($delete_count == $file_count) {
|
|
die "Rotation aborted, because is trying to remove all backups.";
|
|
}
|
|
|
|
log_message "Removing $delete_count detected old backups.";
|
|
|
|
foreach my $deleteFile (@delete_files) {
|
|
my $deleteFilePath = "$local_dir/$deleteFile";
|
|
log_message $deleteFilePath;
|
|
unlink "$deleteFilePath";
|
|
}
|
|
|
|
log_message "Old backups cleaned.";
|
|
} else {
|
|
log_message "No backups to clean.";
|
|
}
|
|
|
|
closedir($dh);
|
|
}
|
|
|
|
sub clone_machine {
|
|
log_message "Cloning '$vm_name' to '$dst_name'.";
|
|
log_message "Doing some previous checkings.";
|
|
|
|
my $dst_tmp_name = $dst_name;
|
|
|
|
my $original_vm = Vim::find_entity_view(
|
|
view_type => 'VirtualMachine',
|
|
filter => {'name' => $dst_name}
|
|
);
|
|
|
|
if ($original_vm) {
|
|
if ($overwrite ne true) {
|
|
die "Machine with same name exists.";
|
|
}
|
|
|
|
$dst_tmp_name = "$dst_name.tmp";
|
|
log_message "Machine '$dst_name' already exists, cloning to '$dst_tmp_name'.";
|
|
}
|
|
|
|
# If MAC is not especified, it is generated by VMWare
|
|
|
|
if ($mac) {
|
|
my $vm_view = Vim::find_entity_views(view_type => 'VirtualMachine');
|
|
foreach my $vm_res (@$vm_view) {
|
|
my @nics = grep { $_->isa("VirtualEthernetCard") } @{ $vm->config->hardware->device };
|
|
|
|
for my $nic (@nics) {
|
|
if ($nic->macAddress eq $mac) {
|
|
my $machineName = $vm_res->name;
|
|
die "Machine '$machineName' with same MAC exists.";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
log_message "Defining clone specifications.";
|
|
|
|
# Hostname
|
|
|
|
my $extra_conf = OptionValue->new(
|
|
key => 'guestinfo.hostname',
|
|
value => $dst_name
|
|
);
|
|
|
|
# CPU & memory
|
|
|
|
my $cpu;
|
|
my $mem_res;
|
|
my $sl = SharesLevel->new('normal');
|
|
my $sh = SharesInfo->new(level => $sl, shares => 1);
|
|
|
|
if (defined($cpu_reservation)) {
|
|
$cpu = ResourceAllocationInfo->new(
|
|
reservation => $cpu_reservation,
|
|
limit => -1,
|
|
shares => $sh
|
|
);
|
|
}
|
|
if (defined($mem_reservation)) {
|
|
$mem_res = ResourceAllocationInfo->new(
|
|
reservation => $mem_reservation,
|
|
limit => -1,
|
|
shares => $sh
|
|
);
|
|
}
|
|
|
|
# Network card
|
|
|
|
my $mac_type = 'Generated';
|
|
|
|
if (defined($mac)) {
|
|
$mac_type = 'Manual';
|
|
}
|
|
|
|
my $vnic_device;
|
|
my $devices = $vm->config->hardware->device;
|
|
my $vnic_name = "Network adapter $vnic";
|
|
|
|
foreach my $device (@$devices) {
|
|
if ($device->deviceInfo->label eq $vnic_name) {
|
|
$vnic_device = $device;
|
|
}
|
|
}
|
|
|
|
my $curr_mac = $vnic_device->macAddress;
|
|
my $network = Vim::get_view(mo_ref => $vnic_device->backing->network, properties => ['name']);
|
|
|
|
my $backing_info = VirtualEthernetCardNetworkBackingInfo->new(deviceName => $network->{'name'});
|
|
|
|
my $nic_type = ref($vnic_device);
|
|
my @nic_classes = (
|
|
'VirtualE1000',
|
|
'VirtualPCNet32',
|
|
'VirtualVmxnet2',
|
|
'VirtualVmxnet3'
|
|
);
|
|
|
|
if (not($nic_type ~~ @nic_classes)) {
|
|
die "Unable to retrieve NIC type.";
|
|
}
|
|
|
|
my $config_spec_operation = VirtualDeviceConfigSpecOperation->new('edit');
|
|
my $new_network_device = $nic_type->new(
|
|
key => $vnic_device->key,
|
|
unitNumber => $vnic_device->unitNumber,
|
|
controllerKey => $vnic_device->controllerKey,
|
|
backing => $backing_info,
|
|
addressType => $mac_type,
|
|
macAddress => $mac
|
|
);
|
|
my $vm_dev_spec = VirtualDeviceConfigSpec->new(
|
|
operation => $config_spec_operation,
|
|
device => $new_network_device
|
|
);
|
|
|
|
# Datastore
|
|
|
|
my $relocate_spec;
|
|
my $host_view = $vm->summary->runtime->host;
|
|
|
|
if (defined($dst_host)) {
|
|
$host_view = Vim::find_entity_view(
|
|
view_type => 'HostSystem',
|
|
filter => {'name' => $dst_host}
|
|
);
|
|
}
|
|
|
|
my $comp_res_view = Vim::get_view(mo_ref => $host_view->parent);
|
|
|
|
if (defined($dst_datastore)) {
|
|
my $ds_new = Vim::find_entity_view(
|
|
view_type => 'Datastore',
|
|
filter => {'name' => $dst_datastore},
|
|
properties => ['name']
|
|
);
|
|
$relocate_spec = VirtualMachineRelocateSpec->new(
|
|
pool => $comp_res_view->resourcePool,
|
|
host => $host_view,
|
|
deviceChange => [$vm_dev_spec],
|
|
datastore => $ds_new
|
|
);
|
|
} else {
|
|
$relocate_spec = VirtualMachineRelocateSpec->new(
|
|
pool => $comp_res_view->resourcePool,
|
|
host => $host_view,
|
|
deviceChange => [$vm_dev_spec]
|
|
);
|
|
}
|
|
|
|
# Gathering specifications and cloning
|
|
|
|
log_message "Cloning machine.";
|
|
my $clone_spec = VirtualMachineCloneSpec->new(
|
|
powerOn => false,
|
|
template => false,
|
|
location => $relocate_spec
|
|
);
|
|
my $vm_clone_ref = $vm->CloneVM(
|
|
folder => $vm->parent,
|
|
name => $dst_tmp_name,
|
|
spec => $clone_spec
|
|
);
|
|
my $vm_clone = Vim::get_view(mo_ref => $vm_clone_ref);
|
|
|
|
my $change_spec = VirtualMachineConfigSpec->new(
|
|
memoryMB => $memory,
|
|
numCPUs => $num_cpus,
|
|
cpuAllocation => $cpu,
|
|
memoryAllocation => $mem_res,
|
|
extraConfig => [$extra_conf]
|
|
);
|
|
$vm_clone->ReconfigVM(spec => $change_spec);
|
|
|
|
if ($original_vm) {
|
|
destroy_machine($original_vm);
|
|
|
|
log_message "Renaming '$dst_tmp_name' to '$dst_name'.";
|
|
$vm_clone->Rename(newName => $dst_name);
|
|
}
|
|
|
|
if ($poweron) {
|
|
log_message "Powering on '$dst_name'.";
|
|
$vm_clone->PowerOnVM();
|
|
}
|
|
|
|
log_message "Clone '$dst_name' of '$vm_name' successfully created.";
|
|
}
|
|
|
|
sub snapshot_machine() {
|
|
log_message "Creating snapshot of '$vm_name'.";
|
|
|
|
$vm->CreateSnapshot(
|
|
name => $snapshot_name,
|
|
description => $snapshot_desc,
|
|
memory => 0,
|
|
quiesce => 0
|
|
);
|
|
|
|
log_message "Snapshot of '$vm_name' successfully created.";
|
|
}
|
|
|
|
sub migrate_machine {
|
|
log_message "Migrating '$vm_name' to $dst_host.";
|
|
|
|
unless ($priority) {
|
|
$priority = "highPriority";
|
|
}
|
|
my $host_view = Vim::find_entity_view(
|
|
view_type => "HostSystem",
|
|
filter => {name => $dst_host}
|
|
);
|
|
|
|
$vm->SuspendVM();
|
|
|
|
if (defined($dst_datastore)) {
|
|
my $datastore_view = Vim::find_entity_view(
|
|
view_type => 'Datastore',
|
|
filter => {'name' => $dst_datastore},
|
|
properties => ['name']
|
|
);
|
|
my $spec = VirtualMachineRelocateSpec->new(
|
|
datastore => $datastore_view,
|
|
host => $host_view
|
|
);
|
|
$vm->RelocateVM_Task(
|
|
spec => $spec,
|
|
priority => VirtualMachineMovePriority->new($priority)
|
|
);
|
|
}
|
|
|
|
$vm->MigrateVM(
|
|
pool => $vm->resourcePool,
|
|
host => $host_view,
|
|
priority => VirtualMachineMovePriority->new('defaultPriority')
|
|
);
|
|
$vm->PowerOnVM();
|
|
|
|
log_message "Migration of '$vm_name' successfull.";
|
|
}
|
|
|
|
sub test_operation() {
|
|
if ($vm_name) {
|
|
open_machine();
|
|
}
|
|
log_message "Test operation, doing nothing.";
|
|
}
|
|
|
|
#--------------------------------- Utils
|
|
|
|
sub open_machine() {
|
|
unless ($vm_name) {
|
|
die "Machine name not set.";
|
|
}
|
|
|
|
$vm = Vim::find_entity_view(
|
|
view_type => 'VirtualMachine',
|
|
filter => {name => $vm_name}
|
|
);
|
|
|
|
unless ($vm) {
|
|
die "Machine '$vm_name' not found.";
|
|
}
|
|
|
|
log_message "Found machine '$vm_name'.";
|
|
}
|
|
|
|
sub destroy_machine {
|
|
my ($destroy_vm) = @_;
|
|
|
|
log_message "Deleting machine '$destroy_vm->{name}'.";
|
|
|
|
if ($destroy_vm->runtime->powerState->val eq 'poweredOn') {
|
|
$destroy_vm->PowerOffVM();
|
|
}
|
|
|
|
$destroy_vm->Destroy();
|
|
}
|