#!/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); if (defined($backup_disks) && !($disk_file ~~ @$backup_disks)) { log_message "$disk_path (Ignored)"; next; } 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' #); # 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} ); } 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 $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(); } }