|
|
|
@ -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";
|
|
|
|
|
}
|