commit d0022ba3ce39a12a90d304533f2f0063613f2773 Author: Juan Date: Mon Aug 13 20:50:48 2018 +0200 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..557c0f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.my.pl \ No newline at end of file diff --git a/config.pl b/config.pl new file mode 100644 index 0000000..e25662d --- /dev/null +++ b/config.pl @@ -0,0 +1,62 @@ +# Configuration file, be careful to respect the Perl syntax. +# Don't modify this file, copy it to config.my.pl and make your changes there. + +# The log file path +log_file = '/var/log/vn-vmware.log', + +# Hostname or IP address of vCenter host +hostname => 'vcenter', + +# The user used to connect to vCenter +user => 'root', + +# Credentials file where user password is stored +credentials_file => '/root/.vmware/credstore/vicredentials.xml', + +# The datacenter name +datacenter => 'datacenter1', + +# The datastore name where backups must be stored +backup_datastore => 'backups', + +# The local directory where backups datastore folder is mounted +local_backup_dir => '/mnt/vm-backups', + +# Backup schedules +schedules => { + schedule1 => { + machines => ['machine1', 'machine2'], + rotation => 'weekly' + }, + schedule2 => { + machines => ['machine3', 'machine4'], + rotation => 'daily' + } +}, + +# Backup rotation configuration +rotations => { + weekly => { + days => 21, + count => 4 + }, + daily => { + days => 31, + count => 31 + } +}, + +# Cloning configuration +clone => { + machine => { + vm => 'src-machine', + dst_name => 'dst-machine', + dst_host => '127.0.0.1', + dst_datastore => 'datastore1', + memory => 4084, + num_cpus => 2, + mac => '00:00:00:00:00:01', + poweron => 1, + overwrite => 1 + } +} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..e06bc5f --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +vn-vmware (2.0.34) stable; urgency=low + + * Initial Release. + + -- Juan Ferrer Toribio Wed, 19 Aug 2015 12:00:00 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..9d15543 --- /dev/null +++ b/debian/control @@ -0,0 +1,17 @@ +Source: vn-vmware +Priority: optional +Maintainer: Juan Ferrer Toribio +Build-Depends: build-essential, debhelper +Standards-Version: 3.9.3 +Section: misc +Homepage: https://verdnatura.es +Vcs-Git: https://git.verdnatura.es/vn-vmware + +Package: vn-vmware +Architecture: all +Depends: perl, tar +Suggests: pigz +Section: misc +Priority: optional +Description: Maintenance scripts for VMWare + Set of mantinance scripts for VMWare. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..9cdb6fc --- /dev/null +++ b/debian/copyright @@ -0,0 +1,24 @@ +Format: http://dep.debian.net/deps/dep5 +Name: vn-vmware +Source: https://git.verdnatura.es/vn-vmware + +Files: * +Copyright: 2011-2015 Juan Ferrer Toribio +License: GPL-3.0+ + +License: GPL-3.0+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General Public + License can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/cron.d b/debian/cron.d new file mode 100755 index 0000000..ec0720c --- /dev/null +++ b/debian/cron.d @@ -0,0 +1,12 @@ +# Clone and backup of virtual machines + +MAILFROM="bacula@verdnatura.es" +MAILTO="sysadmin@verdnatura.es" + +30 21 * * * root vn-vmware.pl --operation cloning --job testDb +01 01 02 * * root vn-vmware.pl --operation cloning --job monthlyDb + +00 04 * * * root vn-vmware.pl --operation backuping --job db +00 01 * * sat root vn-vmware.pl --operation backuping --job friday +00 01 * * sun root vn-vmware.pl --operation backuping --job saturday +00 01 * * mon root vn-vmware.pl --operation backuping --job sunday diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..b8bf86d --- /dev/null +++ b/debian/install @@ -0,0 +1,2 @@ +config.pl etc/vn-vmware +vn-vmware.pl usr/share/vn-vmware \ No newline at end of file diff --git a/debian/links b/debian/links new file mode 100644 index 0000000..19f90eb --- /dev/null +++ b/debian/links @@ -0,0 +1 @@ +usr/share/vn-vmware.pl usr/bin/vn-vmware.pl \ No newline at end of file diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..fdf1120 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +service cron restart diff --git a/debian/postrm b/debian/postrm new file mode 100755 index 0000000..fdf1120 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +service cron restart diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..abde6ef --- /dev/null +++ b/debian/rules @@ -0,0 +1,5 @@ +#!/usr/bin/make -f + +%: + dh $@ + diff --git a/vn-vmware.pl b/vn-vmware.pl new file mode 100755 index 0000000..4ad3e2d --- /dev/null +++ b/vn-vmware.pl @@ -0,0 +1,804 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use VMware::VIRuntime; +use VMware::VICredStore; +use VMware::VILib; +use VMware::VIExt; +use Date::Parse; +use Date::Format; +use Time::Piece; +use Time::Seconds; +use File::Path; +use Net::SMTP; +use Term::ANSIColor; +use Net::OpenSSH; +use XML::LibXML; +use feature qw(switch say); + +use constant false => 0; +use constant true => 1; + +my %opts = ( + operation => { + type => "=s", + help => "Operation to perform: backuping, cloning,rotate, backup, clone, snapshot, migrate", + required => true + }, + vmname => { + type => "=s", + variable => "vmname", + help => "Name of the virtual machine" + }, + job => { + type => "=s", + help => "The job name" + }, + rotation_days => { + type => "=i", + help => "Rotation days for backups", + default => 0 + }, + rotation_count => { + type => "=i", + help => "Number of backups to keep despite the rotation", + default => 5 + }, + 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 + }, + cbt_size => { + type => "=i", + help => "CBT size" + }, + cbt => { + type => "", + help => "Whether to enable CBT" + }, + overwrite => { + type => "", + help => "Whether to remove the virtual machine if there is one with the same name", + 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 $vmname = Opts::get_option('vmname'); +my $operation = Opts::get_option('operation'); +my $job = Opts::get_option('job'); +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 $cbt_size = Opts::get_option('cbt_size'); +my $cbt = Opts::option_is_set('cbt'); +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 $log_fh; +my $vm; +my $remote_host; +my $vm_datastore; +my $time_pattern = '%Y-%m-%d_%H-%M'; +my $local_backup_dir = %config{local_backup_dir}; +my $backup_datastore = %config{backup_datastore}; +my $vcenter_host = %config{hostname}; +my $username = %config{user}; +my %config; + +my $config_files = ( + 'config.my.pl', + 'config.pl', + '/etc/vn-vmware/config.my.pl', + '/etc/vn-vmware/config.pl' +); + +foreach my $config_file (@$config_files) { + if (-e $config_file) { + %config = do $config_file; + last; + } +} + +eval { + &main(); +}; +if (my $err = $@) { + print STDERR color("red").$err.color("reset"); + log $err; +} + +sub main { + $log_fh = STDOUT; + + VMware::VICredStore::init(filename => %config{credentials_file}); + my $password = VMware::VICredStore::get_password(server => $vcenter_host, username => $username); + my $url = "https://$vcenter_host/sdk/vimService"; + + unless (defined ($password)) { + die "Password not found $vcenter_host"; + } + + eval { + Vim::login( + service_url => $url, + user_name => $username, + password => $password + ); + }; + if ($@) { + die "Cannot connect to $vcenter_host"; + } + + log "Connected to $vcenter_host"; + + eval { + + given ($operation) { + when ('backuping') { + &open_log(); + &backuping(); + } + when ('cloning') { + &open_log(); + &cloning(); + } + when ('rotate') { + &rotate_backup(); + } + when ('backup') { + &open_machine(); + &backup_machine(); + } + when ('clone') { + &open_machine(); + &clone_machine(); + } + when ('snapshot') { + &open_machine(); + &snapshot_machine(); + } + when ('migrate') { + &open_machine(); + &migrate_machine(); + } + } + }; + my $err = $@; + + Vim::logout(); + close $log_fh; + + if ($err) { + die $err; + } +} + +#--------------------------------- Operations + +sub backuping() { + log "Backup job '$job' started"; + + my %schedule = %config{schedules}{$job}; + + my %rotation_cfg = %config{rotations}{%schedule{rotation}} + $rotation_count = %rotation_cfg{count}; + $rotation_days = %rotation_cfg{days}; + + my @machines = %schedule{machines}; + + foreach $vmname (@$machines) { + &open_machine(); + &backup_machine(); + &rotate_backup(); + } + + log "Backup job '$job' finished"; +} + +sub cloning() { + log "Clone job '$job' started"; + + my %cfg = %config{clone}{$job}; + + $vmname = %cfg{vm}; + $dst_name = %cfg{dst_name}; + $dst_host = %cfg{dst_host}; + $dst_datastore = %cfg{dst_datastore}; + $memory = %cfg{memory}; + $num_cpus = %cfg{num_cpus}; + $mac = %cfg{mac}; + $poweron = %cfg{poweron}; + $overwrite = %cfg{overwrite}; + + &open_machine(); + &clone_machine(); + + log "Clone job '$job' finished"; +} + +sub backup_machine() { + log "Backup of '$vmname' started"; + + my $time = Time::Piece->new; + my $timeMark = $time->strftime($time_pattern); + my $tmpDir = ".$timeMark"; + my $dsBackupPath = "[$backup_datastore] $vmname/$tmpDir"; + my $local_dir = "$local_backup_dir/$vmname"; + my $localTmpDir = "$local_dir/$tmpDir"; + my $tarFile = "$local_dir/$vmname-$timeMark.tar.gz"; + + if (-e $localTmpDir) { + die "Backup directory already exists: $localTmpDir"; + } + + my $serviceContent = Vim::get_service_content(); + my $fileManager = Vim::get_view(mo_ref => $serviceContent->fileManager); + my $dc = Vim::find_entity_view( + view_type => "Datacenter", + filter => {'name' => $datacenter} + ); + + $fileManager->MakeDirectory( + name => $dsBackupPath, + datacenter => $dc, + createParentDirectories => true + ); + + eval { + $fileManager->CopyDatastoreFile( + sourceName => $vm->config->files->vmPathName, + sourceDatacenter => $dc, + destinationName => "$dsBackupPath/$vmname.vmx", + destinationDatacenter => $dc + ); + + my $vdm = Vim::get_view(mo_ref => $serviceContent->virtualDiskManager); + my $devices = $vm->config->hardware->device; + + $vm->RemoveAllSnapshots(); + $vm->CreateSnapshot( + name => "backup", + description => "Scheduled backup", + memory => 0, + quiesce => 0 + ); + + foreach my $device (@$devices) { + if ($device->isa('VirtualDisk')) { + my $diskPath = $device->backing->fileName; + my $diskFile = basename($diskPath); + + $vdm->CopyVirtualDisk( + sourceName => $diskPath, + sourceDatacenter => $dc, + destName => "$dsBackupPath/$diskFile", + destDatacenter => $dc + ); + } + } + + $vm->RemoveAllSnapshots(); + + system("tar -I pigz -cf $tarFile -C $localTmpDir ."); + }; + my $err = $@; + + rmtree($localTmpDir); + + if ($err) { + die $err; + } + + log "Backup of '$vmname' successfully created"; +} + +sub rotate_backup() { + log "Rotating '$vmname' backups"; + + use List::Util qw[min]; + + my $local_dir = "$local_backup_dir/$vmname"; + + if ($rotation_days == 0 && $rotation_count == 0) { + return; + } + + my $regex = qr/\Q$vmname\E_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2})\.tar\.gz$/; + + my @deleteFiles; + my $fileCount = 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; + } + $fileCount++; + my ($fileMatch) = @reResult; + my $fileTime = Time::Piece->strptime($fileMatch, $time_pattern); + + if ($fileTime < $rotateTime) { + push(@deleteFiles, $file); + } + } + + my $removeCount = scalar(@deleteFiles); + my $keptCount = $fileCount - $removeCount; + + if ($keptCount < $rotation_count) { + @deleteFiles = sort @deleteFiles; + splice(@deleteFiles, -min($rotation_count - $keptCount, $removeCount)); + } + + my $removeCount = scalar(@deleteFiles); + + if ($removeCount == $fileCount) { + die "Rotation aborted, because is trying to remove all backups"; + } + foreach my $deleteFile (@deleteFiles) { + log "Removing $deleteFile (Not done until script is tested for a while)"; + #unlink $deleteFile; + } + + if (scalar(@deleteFiles) == 0) { + log "No backups to clean"; + } else { + log "$removeCount backups cleaned"; + } + + closedir($dh); +} + +sub clone_machine { + log "Cloning '$vmname' to '$dst_name'"; + + &check_datastore(); + + my $vmOriginal = Vim::find_entity_view( + view_type => 'VirtualMachine', + filter => {'name' => $dst_name} + ); + + if ($vmOriginal) { + if ($overwrite) { + &set_power_state($vmOriginal, "poweredOff"); + sleep(20); + $vmOriginal->Destroy(); + } else { + die "Machine with same name exists" + } + } + + # If MAC is not especified, it is generated by VMWare + + if ($mac) { + my $vmView = Vim::find_entity_views(view_type => 'VirtualMachine'); + foreach my $vmRes (@$vmView) { + my @nics = grep { $_->isa("VirtualEthernetCard") } @{ $vm->config->hardware->device }; + + for my $nic (@nics) { + if ($nic->macAddress eq $mac) { + my $machineName = $vmRes->name; + die "Machine '$machineName' with same MAC exists" + } + } + } + } + + my $relocateSpec; + my $macType = "Generated"; + my $host_view = $vm->summary->runtime->host; + + if (defined($mac)) { + $macType = "Manual" + } + 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'] + ); + $relocateSpec = VirtualMachineRelocateSpec->new( + pool => $comp_res_view->resourcePool, + host => $host_view, + datastore => $ds_new + ); + } else { + $relocateSpec = VirtualMachineRelocateSpec->new( + pool => $comp_res_view->resourcePool, + host => $host_view + ); + } + + 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 $currMac = $vnic_device->macAddress; + my $network = Vim::get_view(mo_ref => $vnic_device->backing->network, properties => ['name']); + + my $config_spec_operation = VirtualDeviceConfigSpecOperation->new('edit'); + my $backing_info = VirtualEthernetCardNetworkBackingInfo->new(deviceName => $network->{'name'}); + + my $nicType = ref($vnic_device); + my @nicClasses = ( + 'VirtualE1000', + 'VirtualPCNet32', + 'VirtualVmxnet2', + 'VirtualVmxnet3' + ); + + if (not($nicType ~~ @nicClasses)) { + Util::disconnect(); + die "Unable to retrieve NIC type"; + } + + my $newNetworkDevice = $nicType->new( + key => $vnic_device->key, + unitNumber => $vnic_device->unitNumber, + controllerKey => $vnic_device->controllerKey, + backing => $backing_info, + addressType => $macType, + macAddress => $mac + ); + + my $sl = SharesLevel->new('normal'); + my $sh = SharesInfo->new(level => $sl, shares => 1); + + my $mem_res = ResourceAllocationInfo->new( + reservation => $mem_reservation, + limit => -1, + shares => $sh + ); + my $cpu = ResourceAllocationInfo->new( + reservation => $cpu_reservation, + limit => -1, + shares => $sh + ); + + my $guest_id = ($vm->guest->guestId =~ m/^other/) ? "debian6_64Guest" : $vm->guest->guestId; + my $vm_dev_spec = VirtualDeviceConfigSpec->new(device => $newNetworkDevice, operation => $config_spec_operation); + my $extra_conf = OptionValue->new(key => "guestinfo.hostname", value => $dst_name); + + my $changeSpec = VirtualMachineConfigSpec->new( + deviceChange => [$vm_dev_spec], + name => $dst_name, + memoryMB => $memory, + numCPUs => $num_cpus, + guestId => $guest_id, + cpuAllocation => $cpu, + memoryAllocation => $mem_res, + extraConfig => [$extra_conf] + ); + my $cloneSpec = VirtualMachineCloneSpec->new( + powerOn => $poweron, + template => 0, + location => $relocateSpec, + config => $changeSpec + ); + + $vm->CloneVM( + folder => $vm->parent, + name => $dst_name, + spec => $cloneSpec + ); + + log "Clone '$dst_name' of '$vmname' successfully created"; +} + +sub snapshot_machine() { + log "Creating snapshot of '$vmname' with CBT $cbt"; + + if ($cbt) { + if ($vm->capability->changeTrackingSupported) { + &setCBT(true); + } else { + die "CBT not supported"; + } + + my $vmbackup = Vim::find_entity_view( + view_type => 'VirtualMachine', + filter => {'name' => $dst_name} + ); + my $devices = $vmbackup->config->hardware->device; + my $vm_dst_datastore; + + foreach (@$devices) { + if ($_->isa('VirtualDisk')) { + my $label = $_->deviceInfo->label; + my $diskName = $_->backing->fileName; + my $disk_string .= "\t" . $label . " = " . $diskName . "\n"; + my @sp = split(/\s+/,$diskName); + my @sp1 = split(/[\/]/,$sp[1]); + $vm_dst_datastore = $sp1[0]; + } + } + + # Obtains the ESXI host where machine is located + + my $vm_ds = Vim::get_views(mo_ref_array => $vm->get_property('datastore')); + $vm_datastore = join(',', map($_->get_property('name'), @{$vm_ds})); + + my $vmFolder = "/vmfs/volumes/$vm_datastore/$vmname"; + + my $command = "cd $vmFolder && ".q[ls -lhr *-0*-ctk.vmdk|head -n1 |awk '{print $9}' |cut -c].(length($vmname)+2)."-".(length($vmname)+7); + my $fic_snap = &execute_ssh_command($command); + + if (length($fic_snap) > 0) { + $command = "cd $vmFolder && ".q[ls -lhr *-0*-delta.vmdk|head -n1 |awk '{print $5}' | sed '$s/...$//']; + my $tamano_snp = &execute_ssh_command($command); + + if (($tamano_snp + 0) >= $cbt_size) { + &set_power_state($vmbackup, "poweredOff"); + + $command = "scp -i /etc/ssh/ssh_host_dsa_key $vmFolder/$vmname.vmdk root@"."$dst_host:/vmfs/volumes/$dst_datastore/$vm_dst_datastore"; + &execute_ssh_command($command); + + &create_snapshot($vm); + &create_snapshot($vmbackup); + + my $fichero = "$vmname-".substr($fic_snap,0,length($fic_snap)-1)."*.vmdk"; + log "Copying snapshot $fichero is less or equal than $cbt_size with a size of ".length($fic_snap).""; + $command = "scp -i /etc/ssh/ssh_host_dsa_key $vmFolder/$fichero root@"."$dst_host:/vmfs/volumes/$dst_datastore/$vm_dst_datastore"; + &execute_ssh_command($command); + + # Deletes all snapshots + + $vm->RemoveAllSnapshots(); + &create_snapshot($vm); + } else { + log "No snapshot is made since the size of $vmname-$fic_snap-delta.vmdk is less than $cbt_size"; + } + } else { + log "Creating first snapshot"; + &create_snapshot($vm); + } + } else { + &create_snapshot($vm); + } + + log "Snapshot of '$vmname' successfully created"; +} + +sub migrate_machine { + log "Migrating '$vmname' 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 "Migration of '$vmname' successfull"; +} + +#--------------------------------- Utils + +sub open_machine() { + $vm = Vim::find_entity_view( + view_type => 'VirtualMachine', + filter => {name => $vmname} + ); + + unless ($vm) { + die "Machine '$vmname' not found"; + } + + my $vm_remote_host = $vm->runtime->host; + $remote_host = Vim::get_view(mo_ref => $vm_remote_host)->name; + my $datastores = eval {$vm->{datastore} || []}; + my $dsview = Vim::get_views(mo_ref_array => $datastores, properties => ['name']); + + foreach (@$dsview) { + $vm_datastore = $_->{name}; + } + + log "Found machine '$vmname' at host $remote_host at datastore '$vm_datastore'"; +} + +sub open_log { + open($log_fh, '>', %config{log_file}); +} + +sub log { + my ($message) = @_; + my $time = Time::Piece->new; + my $timeMark = $time->strftime('%Y-%m-%d %H:%M:%S'); + print $log_fh "$timeMark : $message\n"; +} + +sub setCBT { + my ($enable) = @_; + if ($vm->config->changeTrackingEnabled eq $enable) { + return; + } + eval { + log "Switching CBT to $enable"; + my $spec = Vim::VirtualMachineConfigSpec->new(changeTrackingEnabled => $enable); + my $task = $vm->ReconfigVM_Task(spec => $spec); + }; + $cbt = $enable; +} + +sub create_snapshot { + my ($vmsnapshot) = @_; + + my $spec = VirtualMachineConfigSpec->new(changeTrackingEnabled => $cbt); + $vmsnapshot->ReconfigVM_Task(spec => $spec); + + $vmsnapshot->CreateSnapshot( + name => $snapshot_name, + description => $snapshot_desc, + memory => 0, + quiesce => 0 + ); +} + +sub execute_ssh_command { + my ($command) = @_; + my $promptEnd = '/\w+[\$\%\#\>]\s{0,1}$/o'; + my $ssh = Net::OpenSSH->new($remote_host) or die "Cannot connect to $dst_host via SSH"; + log "SSH: $remote_host: $command"; + my $result = $ssh->capture($command); + log "SSH: Result: $result"; + $ssh->system('exit'); + return substr($result, 0, 200); +} + +sub set_power_state { + my ($vmPower, $state) = @_; + + eval { + if ($vmPower->runtime->powerState->val ne $state) { + given ($state) { + when ('poweredOff') { + $vmPower->ShutdownGuest(); + log "Turning off ".$vmPower->name; + } + when ('poweredOn') { + $vmPower->PowerOnVM(); + log "Turning on ".$vmPower->name; + } + } + sleep(50); + } + }; +} + +sub check_datastore { + my $host_view = $vm->summary->runtime->host; + + if (defined ($dst_host)) { + $host_view = Vim::find_entity_view( + view_type => 'HostSystem', + filter => {name => $dst_host} + ); + } + + foreach my $moref ( @{ $host_view->datastore } ) { + my $ds_view = Vim::get_view (mo_ref => $moref); + if ($ds_view->name eq $dst_datastore) { + return; + } + } + + die "Datastore '$dst_datastore' does not exist"; +}