This repository has been archived on 2024-07-12. You can view files and clone it, but cannot push or open issues or pull requests.
vn-vmware/vn-vmware.pl

1238 lines
27 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 File::Copy 'move';
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.local.pl',
'/etc/vn-vmware/config.pl',
'config.local.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: none, backup, backup-job, rotate, restore, replicate, init, clone, clone-job, snapshot, migrate",
required => true
},
'job' => {
type => "=s",
help => "The job name"
},
'vm-name' => {
type => "=s",
variable => "VM_NAME",
help => "Name of the virtual machine"
},
'passphrase-file' => {
type => "=s",
help => "Encrypt backup with passed passphrase file using gpg"
},
'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
},
'archive-regex' => {
type => "=s",
help => "Regular expression used to archive and exclude backups from rotation"
},
'backup-dir' => {
type => "=s",
help => "Local directory where backups are stored",
default => '.'
},
'storage-class' => {
type => "=s",
help => "The storage class",
default => 'default'
},
'archive-class' => {
type => "=s",
help => "The archive storage class",
default => 'archive'
},
'backup-file' => {
type => "=s",
help => "The path to the backup file that will be restored"
},
'restore-dir' => {
type => "=s",
help => "Directory where backups are be restored",
default => '.'
},
'replicate-dir' => {
type => "=s",
help => "Directory where backup directory is replicated"
},
'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
},
'test' => {
type => "",
help => "Test mode, don't perform actions",
default => 0
},
'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 @config_options = (
'backup-dir',
'storage-class',
'archive-class',
'passphrase-file',
'restore-dir',
'replicate-dir'
);
foreach my $config_option (@config_options) {
my $underscore = $config_option;
$underscore =~ s/-/_/;
if (!Opts::option_is_set($config_option) && exists $config{$underscore}) {
Opts::set_option($config_option, $config{$underscore});
}
}
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 $passphrase_file = Opts::get_option('passphrase-file');
my $rotation_days = Opts::get_option('rotation-days');
my $rotation_count = Opts::get_option('rotation-count');
my $archive_regex = Opts::get_option('archive-regex');
my $backup_dir = Opts::get_option('backup-dir');
my $storage_class = Opts::get_option('storage-class');
my $archive_class = Opts::get_option('archive-class');
my $backup_file = Opts::get_option('backup-file');
my $restore_dir = Opts::get_option('restore-dir');
my $replicate_dir = Opts::get_option('replicate-dir');
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 $test = Opts::option_is_set('test');
my $snapshot_name = Opts::get_option('snapshot-name');
my $snapshot_desc = Opts::get_option('snapshot-desc');
my $vm;
my $log_fh;
my $archive_fn;
my $backup_disks;
my $time_pattern = '%Y-%m-%d_%H-%M';
my $secure_file = "$backup_dir/.keepme";
sub stringify_message {
my ($message) = @_;
if ($message->isa('SoapFault')) {
my $detail = ref $message->detail;
$message = "SoapFault: $detail: $message->{fault_string}";
}
unless (substr($message, -1) eq "\n") {
$message .= "\n";
}
return ($message);
}
sub log_to_file {
my ($message) = @_;
if ($log_fh) {
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) = @_;
$message = stringify_message($message);
Util::trace(1, $message);
log_to_file "LOG: $message";
}
sub log_error {
my ($error) = @_;
$error = stringify_message($error);
print STDERR $error;
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 {
if ($test) {
log_message "Test mode enabled, all actions will be simulated.";
}
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') {
open_machine();
backup_machine();
}
when ('backup-job') {
backup_job();
}
when ('rotate') {
rotate_backup();
}
when ('restore') {
restore_backup();
}
when ('replicate') {
replicate_backups();
}
when ('init') {
init_backup_dir();
}
when ('clone') {
open_machine();
clone_machine();
}
when ('clone-job') {
clone_job();
}
when ('snapshot') {
open_machine();
snapshot_machine();
}
when ('migrate') {
open_machine();
migrate_machine();
}
when ('none') {
no_operation();
}
default {
die "Unknown operation '$operation'.";
}
}
};
my $err = $@;
alarm(0);
Util::disconnect();
if ($err) {
die $err;
}
}
#--------------------------------- Operations
sub backup_machine() {
log_message "Backup of '$vm_name' started.";
my $time = Time::Piece->new;
my $time_mark = $time->strftime($time_pattern);
my $tmp_dir = ".$time_mark";
my $backup_datastore = $config{backup_datastore};
my $ds_tmp_dir = "[$backup_datastore] $storage_class/$vm_name/$tmp_dir";
my $local_dir = "$backup_dir/$storage_class/$vm_name";
my $local_tmp_dir = "$local_dir/$tmp_dir";
my $tar_file = "$local_dir/${vm_name}_$time_mark.tar.gz";
if (defined($passphrase_file)) {
$tar_file = "$tar_file.gpg";
}
my $tmp_file = "$tar_file.tmp";
if (-e $local_tmp_dir) {
die "Temporary backup directory already exists: $local_tmp_dir";
}
if (-e $tar_file) {
die "Backup file already exists: $tar_file";
}
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";
unless ($test) {
$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 {
unless ($test) {
$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 $config;
unless ($test) {
my $snapshot_ref = $vm->CreateSnapshot(
name => "backup",
description => "Virtual machine backup",
memory => false,
quiesce => true
);
$snapshot = Vim::get_view(mo_ref => $snapshot_ref);
$config = $snapshot->config;
} else {
$config = $vm->config;
}
log_message "Copying virtual disk files.";
my $devices = $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);
my $disk_uuid = $device->backing->uuid;
if (defined($backup_disks) && !($disk_uuid ~~ @$backup_disks)) {
log_message "[$disk_uuid] $disk_path (Ignored)";
next;
}
log_message "[$disk_uuid] $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'
#);
# FIXME: Workaround for InvalidDiskFormat error
my $attempt = 0;
my $copied = false;
do {
$attempt++;
eval {
unless ($test) {
$vdm->CopyVirtualDisk(
sourceName => $disk_path,
sourceDatacenter => $dc,
destName => "$ds_tmp_dir/$disk_file",
destDatacenter => $dc
#destSpec => $disk_spec
);
}
$copied = true;
};
my $copy_err = $@;
if ($copy_err) {
if ($attempt >= 3) {
die $copy_err;
}
log_message $copy_err;
log_message "Error while copying disk, sleeping some time and trying again.";
sleep(10);
}
} while (!$copied);
}
log_message "Removing backup snapshot.";
if ($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());
}
$pigz_processes = max(1, $pigz_processes);
my $tar_command = "tar -I \"pigz -p $pigz_processes\" --create --sparse -C \"$local_tmp_dir\" .";
if (defined($passphrase_file)) {
my $gpg_command = "gpg -c --passphrase-file \"$passphrase_file\" --batch --yes -o \"$tmp_file\"";
$tar_command = "$tar_command | $gpg_command";
} else {
$tar_command = "$tar_command -f \"$tmp_file\"";
}
log_message "Compressing with Gzip (using $pigz_processes processes) to TAR file.";
log_message $tar_command;
my $priority = getpriority(0, 0);
setpriority(0, 0, $priority + 10);
unless ($test) {
my $tar_status = system($tar_command);
unless ($tar_status == 0) {
die "An error occurred when trying to compress '$vm_name' machine files.";
}
}
setpriority(0, 0, $priority);
unless ($test) {
move($tmp_file, $tar_file);
}
};
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
);
}
unless ($test) {
if (-e $tar_file) {
unlink $tar_file;
}
if (-e $tmp_file) {
unlink $tmp_file;
}
}
}
log_message "Removing temporary directory: $local_tmp_dir";
unless ($test) {
rmtree($local_tmp_dir);
}
if ($err) {
die $err;
}
log_message "Backup of '$vm_name' successfully created.";
}
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};
my @machines = @{$backup_job->{machines}};
my $rotation_name;
if (exists $backup_job->{rotation}) {
$rotation_name = $backup_job->{rotation};
} elsif (exists $config{rotation}) {
$rotation_name = $config{rotation};
}
if (defined ($rotation_name)) {
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};
}
if (exists $rotation_cfg->{storage_class}) {
$storage_class = $rotation_cfg->{storage_class};
}
if (exists $rotation_cfg->{archive_regex}) {
$archive_regex = $rotation_cfg->{archive_regex};
$archive_regex = qr/$archive_regex/;
}
if (exists $rotation_cfg->{archive_fn}) {
$archive_fn = $rotation_cfg->{archive_fn};
}
}
foreach my $machine (@machines) {
eval {
if (ref($machine) eq 'HASH') {
$vm_name = $machine->{name};
$backup_disks = $machine->{disks};
} else {
$vm_name = $machine;
$backup_disks = undef;
}
open_machine();
backup_machine();
rotate_backup();
};
if ($@) {
log_error $@;
}
}
log_message "Backup job '$job' finished.";
}
sub rotate_backup() {
if ($rotation_days == 0 && $rotation_count == 0) {
log_message "No rotation days or count, aborting.";
return;
}
log_message "Rotating '$vm_name' backups.";
my $local_dir = "$backup_dir/$storage_class/$vm_name";
my $regex = qr/^\Q$vm_name\E_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2})/;
my $now = Time::Piece->new;
my @archive_files;
my @delete_files;
my $file_count = 0;
my $rotate_time = Time::Piece->new();
$rotate_time -= ONE_DAY * $rotation_days;
opendir(my $dh, $local_dir);
while (my $file = readdir($dh)) {
my @re_result;
unless (@re_result = $file =~ $regex) {
next;
}
my ($file_match) = @re_result;
my $file_time = Time::Piece->strptime($file_match, $time_pattern);
my $archive =
(defined($archive_regex) && $file_match =~ $archive_regex)
|| (defined($archive_fn) && $archive_fn->($file_time, $now));
if ($archive) {
push(@archive_files, $file);
next;
}
$file_count++;
if ($file_time < $rotate_time) {
push(@delete_files, $file);
}
}
my $archive_count = scalar(@archive_files);
my $archive_dir = "$backup_dir/$archive_class";
my $archive_vm_dir = "$archive_dir/$vm_name";
if ($archive_count > 0) {
log_message "Archiving $archive_count backups.";
unless (-e $archive_dir) {
log_message "Creating archive directory: $archive_dir";
unless ($test) {
mkdir $archive_dir;
}
}
unless (-e $archive_vm_dir) {
unless ($test) {
mkdir $archive_vm_dir;
}
}
foreach my $archive_file (@archive_files) {
log_message "$archive_file";
unless ($test) {
move("$local_dir/$archive_file", "$archive_vm_dir/$archive_file");
}
}
log_message "Some backups archived.";
}
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 $delete_file (@delete_files) {
my $delete_file_path = "$local_dir/$delete_file";
log_message $delete_file_path;
unless ($test) {
unlink "$delete_file_path";
}
}
log_message "Old backups cleaned.";
} else {
log_message "No backups to clean.";
}
closedir($dh);
}
sub restore_backup() {
log_message "Restoring backup file '$backup_file'.";
unless (-e $backup_file) {
die "Backup file doesn't exists: $backup_file"
}
unless (defined($restore_dir)) {
die "Restore directory not defined."
}
unless (-e $restore_dir) {
die "Restore directory doesn't exists: $restore_dir"
}
my ($end_dir) = fileparse($backup_file, qr/\..*/);
my $tar_dir = "$restore_dir/$end_dir";
my $tar_command = "tar xz -C \"$tar_dir\"";
if ($backup_file =~ qr/\.gpg$/) {
my $gpg_command = "gpg --decrypt --passphrase-file \"$passphrase_file\" --batch --yes \"$backup_file\"";
$tar_command = "$gpg_command | $tar_command";
} else {
$tar_command = "$tar_command -f \"$backup_file\"";
}
log_message $tar_command;
unless ($test) {
mkdir($tar_dir, 0755);
my $tar_status = system($tar_command);
unless ($tar_status == 0) {
die "An error occurred restoring backup $backup_file";
}
}
log_message "Backup restored successfully.";
}
sub replicate_backups() {
log_message "Replicating backups to '$replicate_dir'.";
unless (-e $secure_file) {
die "Invalid source directory. Is it mounted and initialized?";
}
unless (-e $replicate_dir) {
die "Replicate dir doesn't exists: $replicate_dir";
}
my $rsync_params = '-rltmD --delete-after --include="*.tar.gz" --include="*.tar.gz.gpg" --include="*/" --exclude="*"';
my $rsync_command = "rsync $rsync_params \"$backup_dir/\" \"$replicate_dir\"";
log_message $rsync_command;
unless ($test) {
my $rsync_status = system($rsync_command);
unless ($rsync_status == 0) {
die "An error occurred while replicating backups.";
}
}
log_message "Backups replicated successfully.";
}
sub init_backup_dir() {
log_message "Initializing backup directory '$backup_dir'.";
if (-e $secure_file) {
die "Backup directory already initialized.";
}
my $secure_content = "Don't delete me! I'm used to be sure this directory is mounted before running rsync\n";
unless ($test) {
my $secure_fh;
open($secure_fh, '>>', $secure_file);
print $secure_fh $secure_content;
close($secure_fh);
}
log_message "Backup directory initalized.";
}
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}
);
unless ($host_view) {
die "Host '$dst_host' not found in cluster.";
}
}
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']
);
unless ($ds_new) {
die "Datastore '$dst_datastore' not found.";
}
$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 $vm_clone;
my $clone_spec = VirtualMachineCloneSpec->new(
powerOn => false,
template => false,
location => $relocate_spec
);
unless ($test) {
my $vm_clone_ref = $vm->CloneVM(
folder => $vm->parent,
name => $dst_tmp_name,
spec => $clone_spec
);
$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'.";
unless ($test) {
$vm_clone->Rename(newName => $dst_name);
}
}
if ($poweron) {
log_message "Powering on '$dst_name'.";
unless ($test) {
$vm_clone->PowerOnVM();
}
}
log_message "Clone '$dst_name' of '$vm_name' successfully created.";
}
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 snapshot_machine() {
log_message "Creating snapshot of '$vm_name'.";
unless ($test) {
$vm->CreateSnapshot(
name => $snapshot_name,
description => $snapshot_desc,
memory => false,
quiesce => false
);
}
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}
);
unless ($test) {
$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
);
unless ($test) {
$vm->RelocateVM_Task(
spec => $spec,
priority => VirtualMachineMovePriority->new($priority)
);
}
}
unless ($test) {
$vm->MigrateVM(
pool => $vm->resourcePool,
host => $host_view,
priority => VirtualMachineMovePriority->new('defaultPriority')
);
$vm->PowerOnVM();
}
log_message "Migration of '$vm_name' successfull.";
}
sub no_operation() {
if ($vm_name) {
open_machine();
}
log_message "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}'.";
unless ($test) {
if ($destroy_vm->runtime->powerState->val eq 'poweredOn') {
$destroy_vm->PowerOffVM();
}
$destroy_vm->Destroy();
}
}