You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2015/04/22 21:59:25 UTC
svn commit: r1675483 [2/3] - in /vcl/trunk/managementnode/lib/VCL:
DataStructure.pm Module.pm Module/Provisioning/VMware/VIM_SSH.pm
Module/Provisioning/VMware/VMware.pm utils.pm
Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm?rev=1675483&r1=1675482&r2=1675483&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm Wed Apr 22 19:59:24 2015
@@ -62,6 +62,8 @@ use IO::File;
use Fcntl qw(:DEFAULT :flock);
use File::Temp qw( tempfile );
use List::Util qw( max );
+use Storable qw(dclone);
+use Term::ANSIColor 2.00 qw(:constants colored);
use VCL::utils;
@@ -284,13 +286,14 @@ sub initialize {
my $vmhost_lastcheck_time = $vmhost_data->get_computer_lastcheck_time(0);
my $vmhost_computer_id = $self->data->get_vmhost_computer_id();
my $vmprofile_name = $self->data->get_vmhost_profile_name();
+ my $vmprofile_password = $self->data->get_vmhost_profile_password(0);
my $vmware_api;
notify($ERRORS{'DEBUG'}, 0, "VM profile assigned to $vmhost_computer_name: $vmprofile_name");
# Create an API object which will be used to control the VM (register, power on, etc.)
- if (($vmware_api = $self->get_vmhost_api_object($VSPHERE_SDK_PACKAGE)) && !$vmware_api->is_restricted()) {
+ if ($vmprofile_password && ($vmware_api = $self->get_vmhost_api_object($VSPHERE_SDK_PACKAGE)) && !$vmware_api->is_restricted()) {
notify($ERRORS{'DEBUG'}, 0, "vSphere SDK object will be used to control VM host $vmhost_computer_name");
$self->set_vmhost_os($vmware_api);
@@ -1402,18 +1405,18 @@ sub remove_existing_vms {
# get_vmx_file_paths() will return all vmx files it finds under the base directory path and all registered vmx files
# It's possible for a vmx file to be registered that resided on some other datastore
if ($vmx_file_path !~ /^$vmx_base_directory_path/) {
- notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file '$vmx_file_path' because it does not begin with the base directory path: '$vmx_base_directory_path'");
+ #notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file '$vmx_file_path' because it does not begin with the base directory path: '$vmx_base_directory_path'");
next;
}
# Check if the vmx directory name matches the naming convention VCL would use for the computer
my $vmx_file_path_computer_name = $self->_get_vmx_file_path_computer_name($vmx_file_path);
if (!$vmx_file_path_computer_name) {
- notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file $vmx_file_name, the computer name could not be determined from the directory name");
+ #notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file $vmx_file_name, the computer name could not be determined from the directory name");
next;
}
elsif ($vmx_file_path_computer_name ne $computer_name) {
- notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file: $vmx_file_name, the directory computer name '$vmx_file_path_computer_name' does not match the reservation computer name '$computer_name'");
+ #notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file: $vmx_file_name, the directory computer name '$vmx_file_path_computer_name' does not match the reservation computer name '$computer_name'");
next;
}
else {
@@ -1435,11 +1438,11 @@ sub remove_existing_vms {
# Check if the directory name matches the naming convention VCL would use for the computer
my $orphaned_computer_name = $self->_get_vmx_file_path_computer_name($orphaned_vmx_file_path);
if (!$orphaned_computer_name) {
- notify($ERRORS{'DEBUG'}, 0, "ignoring existing file path '$orphaned_vmx_file_path', the computer name could not be determined from the directory name");
+ #notify($ERRORS{'DEBUG'}, 0, "ignoring existing file path '$orphaned_vmx_file_path', the computer name could not be determined from the directory name");
next;
}
elsif ($orphaned_computer_name ne $computer_name) {
- notify($ERRORS{'DEBUG'}, 0, "ignoring existing file: '$orphaned_vmx_file_path', the directory computer name '$orphaned_computer_name' does not match the reservation computer name '$computer_name'");
+ #notify($ERRORS{'DEBUG'}, 0, "ignoring existing file: '$orphaned_vmx_file_path', the directory computer name '$orphaned_computer_name' does not match the reservation computer name '$computer_name'");
next;
}
else {
@@ -1578,6 +1581,7 @@ sub prepare_vmx {
"mem.hotadd" => "TRUE",
"msg.autoAnswer" => "TRUE", # tries to automatically answer all questions that may occur at boot-time.
#"mks.enable3d" => "TRUE",
+ #"mks.gl.allowBlacklistedDrivers" => "TRUE",
"numvcpus" => "$vm_cpu_count",
"powerType.powerOff" => "soft",
"powerType.powerOn" => "hard",
@@ -2840,9 +2844,7 @@ sub get_vmx_file_path {
my $reservation_id = $self->data->get_reservation_id();
- if ($reservation_id) {
- return $ENV{vmx_file_path} if $ENV{vmx_file_path};
- }
+ return $self->{vmx_file_path} if $self->{vmx_file_path};
my $vmx_base_directory_path = $self->get_vmx_base_directory_path();
if (!$vmx_base_directory_path) {
@@ -2857,7 +2859,7 @@ sub get_vmx_file_path {
}
my $vmx_file_path = "$vmx_base_directory_path/$vmx_directory_name/$vmx_directory_name.vmx";
- $ENV{vmx_file_path} = $vmx_file_path;
+ $self->{vmx_file_path} = $vmx_file_path;
notify($ERRORS{'OK'}, 0, "determined vmx file path: $vmx_file_path");
return $vmx_file_path;
}
@@ -2888,18 +2890,17 @@ sub get_vmx_base_directory_path {
# If set, parse the path to return the directory name preceding the vmx file name and directory name
# /<vmx base directory path>/<vmx directory name>/<vmx file name>
- my $reservation_id = $self->data->get_reservation_id();
+ my $vmhost_short_name = $self->data->get_vmhost_short_name();
+ my $vmhost_hostname = $self->data->get_vmhost_hostname();
- if ($reservation_id) {
- if ($ENV{vmx_file_path}) {
- ($vmx_base_directory_path) = $ENV{vmx_file_path} =~ /(.+)\/[^\/]+\/[^\/]+.vmx$/i;
- if ($vmx_base_directory_path) {
- return $vmx_base_directory_path;
- }
- else {
- notify($ERRORS{'WARNING'}, 0, "vmx base directory path could not be determined from vmx file path: '$ENV{vmx_file_path}'");
- return;
- }
+ if ($self->{vmx_file_path}) {
+ ($vmx_base_directory_path) = $self->{vmx_file_path} =~ /(.+)\/[^\/]+\/[^\/]+.vmx$/i;
+ if ($vmx_base_directory_path) {
+ return $vmx_base_directory_path;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "vmx base directory path could not be determined from vmx file path: '$self->{vmx_file_path}'");
+ return;
}
}
@@ -2917,14 +2918,129 @@ sub get_vmx_base_directory_path {
# -datastore path: [vcl-datastore]
# -datastore name: vcl-datastore
my $vmx_base_directory_normal_path = $self->_get_normal_path($vmx_base_directory_path);
- if ($vmx_base_directory_normal_path) {
- notify($ERRORS{'DEBUG'}, 0, "determined vmx base directory path: $vmx_base_directory_normal_path");
- return $vmx_base_directory_normal_path;
+ if (!$vmx_base_directory_normal_path) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine the vmx base directory path, failed to convert path configured in the VM profile to a normal path: $vmx_base_directory_path");
+ return;
+ }
+
+ # Check if a directory exists under the vmx base directory named after the VM host
+ # If one exists, use it instead of the directory configured in the VM profile
+ if ($self->vmhost_os->file_exists("$vmx_base_directory_normal_path/$vmhost_hostname", 'd')) {
+ $vmx_base_directory_normal_path = "$vmx_base_directory_normal_path/$vmhost_hostname";
+ notify($ERRORS{'DEBUG'}, 0, "directory named after the VM host under vmx base directory path will be used: $vmx_base_directory_normal_path");
}
else {
- notify($ERRORS{'WARNING'}, 0, "unable to determine the vmx base directory path, failed to convert path configured in the VM profile to a normal path: $vmx_base_directory_path");
+ if ($vmhost_hostname ne $vmhost_short_name) {
+ if ($self->vmhost_os->file_exists("$vmx_base_directory_normal_path/$vmhost_short_name", 'd')) {
+ $vmx_base_directory_normal_path = "$vmx_base_directory_normal_path/$vmhost_short_name";
+ notify($ERRORS{'DEBUG'}, 0, "directory named after the VM host under vmx base directory path will be used: $vmx_base_directory_normal_path");
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "directory named '$vmhost_hostname' or '$vmhost_short_name' does not exist under the vmx base directory path: $vmx_base_directory_normal_path");
+ }
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "directory named '$vmhost_hostname' does not exist under the vmx base directory path: $vmx_base_directory_normal_path");
+ }
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "determined vmx base directory path: $vmx_base_directory_normal_path");
+ return $vmx_base_directory_normal_path;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_vmx_url_base_directory_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the path on the VM host under which the vmx directory is
+ located.
+ Example:
+ /vmfs/volumes/local-datastore
+
+=cut
+
+sub get_vmx_url_base_directory_path {
+ my $self = shift;
+ if (ref($self) !~ /vmware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $base_directory_path = $self->get_vmx_base_directory_path();
+ if (!$base_directory_path) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine vmx URL base directory path, failed to retrieve vmx base directory path");
+ return;
+ }
+
+ my $datastore_root_url = $self->_get_datastore_url($base_directory_path);
+ if (!$datastore_root_url) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine vmx URL base directory path, failed to retrieve URL for base directory path: '$base_directory_path'");
+ return;
+ }
+
+ my $datastore_name = $self->_get_datastore_name($base_directory_path);
+ if (!$datastore_name) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine vmx URL base directory path, failed to retrieve datastore name for base directory path: '$base_directory_path'");
+ return;
+ }
+
+ # Replace the datastore name with the URL
+ my $url_base_directory_path = $base_directory_path;
+ $url_base_directory_path =~ s/\/$datastore_name(\/|$)/\/$datastore_root_url$1/;
+
+ notify($ERRORS{'DEBUG'}, 0, "determined vmx URL base directory path:\n" .
+ "vmx base directory path: $base_directory_path\n" .
+ "vmx url base directory path: $url_base_directory_path"
+ );
+ return $url_base_directory_path
+
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_vmx_intermediate_directory_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the path on the VM host under which all of the VM vmx
+ directories reside, with the datastore section removed. This will
+ return an empty string if the vmx base directory path is the root
+ of a datastore. Example:
+
+ get_vmx_base_directory_path:
+ '/vmfs/volumes/datastore1/VMs/vmhost2'
+ get_vmx_intermediate_directory_path:
+ 'VMs/vmhost2'
+
+ get_vmx_base_directory_path:
+ '/vmfs/volumes/datastore1'
+ get_vmx_intermediate_directory_path:
+ ''
+
+=cut
+
+sub get_vmx_intermediate_directory_path {
+ my $self = shift;
+ if (ref($self) !~ /vmware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $vmx_directory_path = $self->get_vmx_directory_path();
+ if (!$vmx_directory_path) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine vmx intermediate directory path, failed to retrieve vmx directory path");
return;
}
+
+ my $intermediate_directory_path = $vmx_directory_path;
+ $intermediate_directory_path =~ s/^\/vmfs\/volumes\/[^\/]+\/?//ig;
+ notify($ERRORS{'DEBUG'}, 0, "determined vmx intermediate directory path:\n" .
+ "vmx directory path: $vmx_directory_path\n" .
+ "vmx intermediate directory path: $intermediate_directory_path"
+ );
+ return $intermediate_directory_path || '';
}
#/////////////////////////////////////////////////////////////////////////////
@@ -2947,24 +3063,21 @@ sub get_vmx_directory_name {
}
my $vmx_directory_name;
- my $reservation_id = $self->data->get_reservation_id();
-
# Check if vmx_file_path environment variable has been set
# If set, parse the path to return the directory name preceding the vmx file name
# /<vmx base directory path>/<vmx directory name>/<vmx file name>
- if ($reservation_id) {
- if ($ENV{vmx_file_path}) {
- ($vmx_directory_name) = $ENV{vmx_file_path} =~ /([^\/]+)\/[^\/]+.vmx$/i;
- if ($vmx_directory_name) {
- return $vmx_directory_name;
- }
- else {
- notify($ERRORS{'WARNING'}, 0, "vmx directory name could not be determined from vmx file path: '$ENV{vmx_file_path}'");
- return;
- }
+ if ($self->{vmx_file_path}) {
+ ($vmx_directory_name) = $self->{vmx_file_path} =~ /([^\/]+)\/[^\/]+.vmx$/i;
+ if ($vmx_directory_name) {
+ return $vmx_directory_name;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "vmx directory name could not be determined from vmx file path: '$self->{vmx_file_path}'");
+ return;
}
- }
+ }
+
# Get the computer name
my $computer_short_name = $self->data->get_computer_short_name();
if (!$computer_short_name) {
@@ -3071,7 +3184,7 @@ sub get_vmx_file_name {
Parameters : $vmx_file_path
Returns : boolean
- Description : Sets the vmx path into %ENV so that the default values are
+ Description : Sets the vmx path into $self so that the default values are
overridden when the various get_vmx_ subroutines are called. This
is useful when a base image is being captured. The vmx file does
not need to be in the expected directory nor does it need to be
@@ -3103,16 +3216,16 @@ sub set_vmx_file_path {
return;
}
- $ENV{vmx_file_path} = $vmx_file_path_argument;
+ $self->{vmx_file_path} = $vmx_file_path_argument;
# Check all of the vmx file path components
if ($self->check_file_paths('vmx')) {
# Set the vmx_file_path environment variable
- notify($ERRORS{'OK'}, 0, "set overridden vmx file path: '$vmx_file_path_argument'");
+ notify($ERRORS{'OK'}, 0, "set overridden vmx file path: '$vmx_file_path_argument'\n$self->{vmx_file_path}");
return 1;
}
else {
- delete $ENV{vmx_file_path};
+ delete $self->{vmx_file_path};
notify($ERRORS{'WARNING'}, 0, "failed to set overridden vmx file path: '$vmx_file_path_argument'");
return;
}
@@ -3158,7 +3271,7 @@ sub get_vmdk_file_path {
return;
}
- return $ENV{vmdk_file_path} if $ENV{vmdk_file_path};
+ return $self->{vmdk_file_path} if $self->{vmdk_file_path};
# Get the information contained within the vmx file
my $vmx_file_path = $self->get_vmx_file_path();
@@ -3292,13 +3405,13 @@ sub get_vmdk_base_directory_path {
# Check if vmdk_file_path environment variable has been set
# If set, parse the path to return the directory name preceding the vmdk file name and directory name
# /<vmdk base directory path>/<vmdk directory name>/<vmdk file name>
- if (!$ignore_cached_path && $ENV{vmdk_file_path}) {
- ($vmdk_base_directory_path) = $ENV{vmdk_file_path} =~ /(.+)\/[^\/]+\/[^\/]+.vmdk$/i;
+ if (!$ignore_cached_path && $self->{vmdk_file_path}) {
+ ($vmdk_base_directory_path) = $self->{vmdk_file_path} =~ /(.+)\/[^\/]+\/[^\/]+.vmdk$/i;
if ($vmdk_base_directory_path) {
return $vmdk_base_directory_path;
}
else {
- notify($ERRORS{'WARNING'}, 0, "vmdk base directory path could not be determined from vmdk file path: '$ENV{vmdk_file_path}'");
+ notify($ERRORS{'WARNING'}, 0, "vmdk base directory path could not be determined from vmdk file path: '$self->{vmdk_file_path}'");
return;
}
}
@@ -3434,13 +3547,13 @@ sub get_vmdk_directory_name {
# Check if vmdk_file_path environment variable has been set
# If set, parse the path to return the directory name preceding the vmdk file name
# /<vmdk base directory path>/<vmdk directory name>/<vmdk file name>
- if ($ENV{vmdk_file_path}) {
- my ($vmdk_directory_name) = $ENV{vmdk_file_path} =~ /([^\/]+)\/[^\/]+.vmdk$/i;
+ if ($self->{vmdk_file_path}) {
+ my ($vmdk_directory_name) = $self->{vmdk_file_path} =~ /([^\/]+)\/[^\/]+.vmdk$/i;
if ($vmdk_directory_name) {
return $vmdk_directory_name;
}
else {
- notify($ERRORS{'WARNING'}, 0, "vmdk directory name could not be determined from vmdk file path: '$ENV{vmdk_file_path}'");
+ notify($ERRORS{'WARNING'}, 0, "vmdk directory name could not be determined from vmdk file path: '$self->{vmdk_file_path}'");
return;
}
}
@@ -3536,13 +3649,13 @@ sub get_vmdk_directory_path {
# Check if vmdk_file_path environment variable has been set
# If set, parse the path to return the directory name preceding the vmdk file name
# /<vmdk base directory path>/<vmdk directory name>/<vmdk file name>
- if ($ENV{vmdk_file_path}) {
- my ($vmdk_directory_path) = $ENV{vmdk_file_path} =~ /(.+)\/[^\/]+.vmdk$/i;
+ if ($self->{vmdk_file_path}) {
+ my ($vmdk_directory_path) = $self->{vmdk_file_path} =~ /(.+)\/[^\/]+.vmdk$/i;
if ($vmdk_directory_path) {
return $vmdk_directory_path;
}
else {
- notify($ERRORS{'WARNING'}, 0, "vmdk directory name could not be determined from vmdk file path: '$ENV{vmdk_file_path}'");
+ notify($ERRORS{'WARNING'}, 0, "vmdk directory name could not be determined from vmdk file path: '$self->{vmdk_file_path}'");
return;
}
}
@@ -3704,7 +3817,7 @@ sub get_vmdk_file_name {
Parameters : $vmx_file_path
Returns :
- Description : Sets the vmdk path into %ENV so that the default values are
+ Description : Sets the vmdk path into $self so that the default values are
overridden when the various get_vmdk_... subroutines are called.
This is useful for base image imaging reservations if the
code detects the vmdk path is not in the expected place.
@@ -3725,7 +3838,7 @@ sub set_vmdk_file_path {
return;
}
- $vmdk_file_path_argument = normalize_file_path($vmdk_file_path_argument);
+ $vmdk_file_path_argument = $self->_get_normal_path($vmdk_file_path_argument);
# Make sure the vmdk file path format is valid
if ($vmdk_file_path_argument !~ /^\/.+\/.+\/[^\/]+\.vmdk$/i) {
@@ -3733,7 +3846,7 @@ sub set_vmdk_file_path {
return;
}
- $ENV{vmdk_file_path} = $vmdk_file_path_argument;
+ $self->{vmdk_file_path} = $vmdk_file_path_argument;
# Check all of the vmdk file path components
if ($self->check_file_paths('vmdk')) {
@@ -3742,7 +3855,7 @@ sub set_vmdk_file_path {
return 1;
}
else {
- delete $ENV{vmdk_file_path};
+ delete $self->{vmdk_file_path};
notify($ERRORS{'WARNING'}, 0, "failed to set overridden vmdk file path: '$vmdk_file_path_argument'");
return;
}
@@ -3782,6 +3895,8 @@ sub check_file_paths {
$check_paths_string .= "vmx base directory path: '" . ($self->get_vmx_base_directory_path() || $undefined_string) . "'\n";
$check_paths_string .= "vmx directory name: '" . ($self->get_vmx_directory_name() || $undefined_string) . "'\n";
$check_paths_string .= "vmx file name: '" . ($self->get_vmx_file_name() || $undefined_string) . "'\n";
+ $check_paths_string .= "vmx datastore URL path: '" . ($self->_get_datastore_root_url_path($self->get_vmx_file_path()) || $undefined_string) . "'\n";
+ $check_paths_string .= "vmx datastore URL: '" . ($self->_get_datastore_url($self->get_vmx_file_path()) || $undefined_string) . "'\n";
}
if ($file_type !~ /vmx/i) {
@@ -3791,12 +3906,14 @@ sub check_file_paths {
$check_paths_string .= "vmdk directory name: '" . ($self->get_vmdk_directory_name() || $undefined_string) . "'\n";
$check_paths_string .= "vmdk file name: '" . ($self->get_vmdk_file_name() || $undefined_string) . "'\n";
$check_paths_string .= "vmdk file prefix: '" . ($self->get_vmdk_file_prefix() || $undefined_string) . "'\n";
- $check_paths_string .= "dedicated vmdk file path: '" . ($self->get_vmdk_file_path_dedicated() || $undefined_string) . "'\n";
- $check_paths_string .= "dedicated vmdk directory path: '" . ($self->get_vmdk_directory_path_dedicated() || $undefined_string) . "'\n";
- $check_paths_string .= "dedicated vmdk directory name: '" . ($self->get_vmdk_directory_name_dedicated() || $undefined_string) . "'\n";
- $check_paths_string .= "shared vmdk file path: '" . ($self->get_vmdk_file_path_shared() || $undefined_string) . "'\n";
- $check_paths_string .= "shared vmdk directory path: '" . ($self->get_vmdk_directory_path_shared() || $undefined_string) . "'\n";
- $check_paths_string .= "shared vmdk directory name: '" . ($self->get_vmdk_directory_name_shared() || $undefined_string) . "'\n";
+ $check_paths_string .= "dedicated vmdk file path: '" . ($self->get_vmdk_file_path_dedicated() || $undefined_string) . "'\n";
+ $check_paths_string .= "dedicated vmdk directory path: '" . ($self->get_vmdk_directory_path_dedicated() || $undefined_string) . "'\n";
+ $check_paths_string .= "dedicated vmdk directory name: '" . ($self->get_vmdk_directory_name_dedicated() || $undefined_string) . "'\n";
+ $check_paths_string .= "shared vmdk file path: '" . ($self->get_vmdk_file_path_shared() || $undefined_string) . "'\n";
+ $check_paths_string .= "shared vmdk directory path: '" . ($self->get_vmdk_directory_path_shared() || $undefined_string) . "'\n";
+ $check_paths_string .= "shared vmdk directory name: '" . ($self->get_vmdk_directory_name_shared() || $undefined_string) . "'\n";
+ $check_paths_string .= "vmdk datastore URL path: '" . ($self->_get_datastore_root_url_path($self->get_vmdk_file_path()) || $undefined_string) . "'\n";
+ $check_paths_string .= "vmdk datastore URL: '" . ($self->_get_datastore_url($self->get_vmdk_file_path()) || $undefined_string) . "'\n";
}
if ($check_paths_string =~ /$undefined_string/) {
@@ -3998,7 +4115,7 @@ sub is_vm_dedicated {
return;
}
- return $ENV{vm_dedicated} if defined $ENV{vm_dedicated};
+ return $self->{vm_dedicated} if defined $self->{vm_dedicated};
my $vm_dedicated = 0;
@@ -4026,8 +4143,8 @@ sub is_vm_dedicated {
notify($ERRORS{'DEBUG'}, 0, "VM disk mode does not need to be dedicated");
}
- $ENV{vm_dedicated} = $vm_dedicated;
- return $ENV{vm_dedicated};
+ $self->{vm_dedicated} = $vm_dedicated;
+ return $self->{vm_dedicated};
}
#/////////////////////////////////////////////////////////////////////////////
@@ -4231,11 +4348,10 @@ sub get_image_size {
Parameters : $image_name (optional)
Returns : integer
- Description : Returns the size of the image in bytes. If the vmdk file path
- argument is not supplied and the VM disk type in the VM profile
- is set to localdisk, the size of the image in the image
- repository on the management node is checked. Otherwise, the size
- of the image in the vmdk directory on the VM host is checked.
+ Description : Returns the size of the image in bytes. If the VM profile
+ repository path is defined, an attempt is first made to retrieve
+ the size from the repository. Otherwise, the size of the image in
+ the vmdk directory on the VM host is checked.
=cut
@@ -4247,7 +4363,6 @@ sub get_image_size_bytes {
}
my $vmhost_name = $self->data->get_vmhost_short_name() || return;
- my $vmprofile_vmdisk = $self->data->get_vmhost_profile_vmdisk() || return;
my $management_node_hostname = $self->data->get_management_node_short_name() || 'management node';
my $vmdk_base_directory_path_shared = $self->get_vmdk_base_directory_path_shared() || return;
@@ -4265,7 +4380,7 @@ sub get_image_size_bytes {
if ($repository_vmdk_base_directory_path) {
my $repository_search_path = "$repository_vmdk_base_directory_path/$image_name/$image_name*.vmdk";
- notify($ERRORS{'DEBUG'}, 0, "VM profile vmdisk is set to '$vmprofile_vmdisk', attempting to retrieve image size from image repository");
+ notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve image size from image repository");
if ($self->is_repository_mounted_on_vmhost()) {
notify($ERRORS{'DEBUG'}, 0, "checking size of image in image repository mounted on VM host: $vmhost_name:$repository_vmdk_base_directory_path");
@@ -5043,11 +5158,11 @@ sub get_vmx_file_paths {
# Get a list of all the vmx files under the normal vmx base directory
my @found_vmx_paths = $self->vmhost_os->find_files($vmx_base_directory_path, "*.vmx");
- notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@found_vmx_paths) . " vmx files under $vmx_base_directory_path\n" . join("\n", sort @found_vmx_paths));
+ #notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@found_vmx_paths) . " vmx files under $vmx_base_directory_path\n" . join("\n", sort @found_vmx_paths));
# Get a list of the registered VMs in case a VM is registered and the vmx file does not reside under the normal vmx base directory
my @registered_vmx_paths = $self->api->get_registered_vms();
- notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@registered_vmx_paths) . " registered vmx files\n" . join("\n", sort @registered_vmx_paths));
+ #notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@registered_vmx_paths) . " registered vmx files\n" . join("\n", sort @registered_vmx_paths));
my %vmx_file_paths = map { $_ => 1 } (@found_vmx_paths, @registered_vmx_paths);
my @all_vmx_paths = sort keys %vmx_file_paths;
@@ -6891,6 +7006,55 @@ sub _get_datastore_root_url_path {
#/////////////////////////////////////////////////////////////////////////////
+=head2 _get_datastore_url
+
+ Parameters : $path
+ Returns : string
+ Description : Parses the path argument and determines its datastore root path
+ in normal form.
+ '/vmfs/volumes/datastore1/folder/file.txt' --> '895cdc05-11c0ee8f'
+ '[datastore1] folder/file.txt' --> '895cdc05-11c0ee8f'
+
+=cut
+
+sub _get_datastore_url {
+ my $self = shift;
+ if (ref($self) !~ /module/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ # Get the path argument
+ my $path = shift;
+ if (!$path) {
+ notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+ return;
+ }
+
+ my $datastore_name = $self->_get_datastore_name($path);
+ if (!$datastore_name) {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root URL, unable to determine datastore name: $path");
+ return;
+ }
+
+ # Get the datastore information
+ my $datastore_info = $self->get_datastore_info();
+ if (!$datastore_info) {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root URL, unable to retrieve datastore information");
+ return;
+ }
+ if (!$datastore_info->{$datastore_name}{url}) {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root URL, datstore info does not contain a 'url' key:\n" . format_data($datastore_info->{$datastore_name}));
+ return;
+ }
+
+ my $url = $datastore_info->{$datastore_name}{url};
+ $url =~ s/.*\/([^\/]+)$/$1/g;
+ return $url;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
=head2 _get_normal_path
Parameters : $path
@@ -6945,6 +7109,62 @@ sub _get_normal_path {
#/////////////////////////////////////////////////////////////////////////////
+=head2 _get_url_path
+
+ Parameters : $path
+ Returns : string
+ Description : Converts a path which may contain a normal datastore name to a
+ path containing the datastore's URL.
+ /vmfs/volumes/mydatastore/mypath --> /vmfs/volumes/52fe7333-0ab121b2-0d96-e41f13ca0f14/mypath
+ [mydatastore] mypath --> /vmfs/volumes/52fe7333-0ab121b2-0d96-e41f13ca0f14/mypath
+
+=cut
+
+sub _get_url_path {
+ my $self = shift;
+ if (ref($self) !~ /module/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ # Get the path argument
+ my $path_argument = shift;
+ if (!$path_argument) {
+ notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+ return;
+ }
+
+ my $normal_path = $self->_get_normal_path($path_argument);
+ if (!$normal_path) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine URL path, normal path could not be determined, returning path argument: '$path_argument'");
+ return $path_argument;
+ }
+
+ my $datastore_url = $self->_get_datastore_url($normal_path);
+ if (!$datastore_url) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine URL for datastore of path argument: '$path_argument', returning normal path: '$normal_path'");
+ return $normal_path;
+ }
+
+ my $url_path = $normal_path;
+ $url_path =~ s/^(\/vmfs\/volumes\/)[^\/]+(\/|$)/$1$datastore_url$2/;
+
+ if ($url_path eq $normal_path) {
+ notify($ERRORS{'WARNING'}, 0, "URL path is the same as the normal path: $url_path, conversion from normal path to URL path may have failed, returning normal path:\n" .
+ "path argument: $path_argument\n" .
+ "normal path: $normal_path\n" .
+ "datastore URL: $datastore_url"
+ );
+ return $normal_path;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "converted path to URL path: '$path_argument' --> '$url_path'");
+ return $url_path;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
=head2 _get_datastore_name
Parameters : $path
@@ -7756,7 +7976,7 @@ sub setup_vm_host_operations {
#For testing:
#my $vmhost_id = 599;
- my $vmhost_computer_name = $management_node_vmhost_info->{$vmhost_id}{computer}{hostname};
+ my $vmhost_computer_name = $management_node_vmhost_info->{$vmhost_id}{computer}{SHORTNAME};
push @{$ENV{setup_path}}, $vmhost_computer_name;
@@ -7767,7 +7987,7 @@ sub setup_vm_host_operations {
}
else {
print "\nCreating provisioning object to control $vmhost_computer_name...";
- $vmhost_provisioner = $self->create_object('VCL::Module::Provisioning::VMware::VMware', {vmhost_id => $vmhost_id});
+ $vmhost_provisioner = $self->create_object('VCL::Module::Provisioning::VMware::VMware', {vmhost_identifier => $vmhost_id});
if (!$vmhost_provisioner) {
print "\nERROR: Failed to create provisioning object to control $vmhost_computer_name.\n";
return;
@@ -7790,6 +8010,7 @@ sub setup_vm_host_operations {
setup_print_break('.');
my $datastore_operations_menu = {
+ 'Migrate VM to another host' => \&setup_migrate_vm,
'Purge deleted and unused images from virtual disk datastore' => \&setup_purge_datastore_images,
'Purge deleted and unused images from repository datastore' => \&setup_purge_repository_images,
};
@@ -7807,13 +8028,16 @@ sub setup_vm_host_operations {
# "parent_menu_names" => [],
# "sub_ref" => \&setup_purge_repository_images,
#};
+ #my $datastore_operations_choice = {
+ # "name" => "Migrate VM to another host",
+ # "parent_menu_names" => [],
+ # "sub_ref" => \&setup_migrate_vm,
+ #};
my $datastore_operations_choice_name = $datastore_operations_choice->{name};
my $datastore_operations_choice_sub_ref = $datastore_operations_choice->{sub_ref};
push @{$ENV{setup_path}}, $datastore_operations_choice_name;
- &$datastore_operations_choice_sub_ref($vmhost_provisioner);
-
- return 1;
+ return &$datastore_operations_choice_sub_ref($vmhost_provisioner);
}
#/////////////////////////////////////////////////////////////////////////////
@@ -8361,6 +8585,973 @@ sub get_datastore_imagerevision_names {
}
#/////////////////////////////////////////////////////////////////////////////
+
+=head2 configure_vmhost_ssh_keys
+
+ Parameters : none
+ Returns : boolean
+ Description : Creates SSH private and public key files on the VM host to allow
+ the root account on the host to authenticate to other hosts. This
+ subroutine does not configure SSH keys related to the management
+ node. It is used to configure SSH for VM host to VM host
+ communication.
+
+=cut
+
+sub configure_vmhost_ssh_keys {
+ my $self = shift;
+ if (ref($self) !~ /VMware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $management_node_name = $self->data->get_management_node_short_name();
+ my $vmhost_name = $self->data->get_vmhost_short_name();
+
+ my $local_private_key_file_path = "/tmp/$vmhost_name.key";
+ my $local_public_key_file_path = "$local_private_key_file_path.pub";
+
+ my $vmhost_private_key_file_path = "/.ssh/id_rsa";
+ my $vmhost_public_key_file_path = "$vmhost_private_key_file_path.pub";
+
+ # Delete local files previously created
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+
+ my $vmware_product_name = $self->get_vmhost_product_name();
+
+ if ($self->vmhost_os->file_exists($vmhost_private_key_file_path)) {
+ notify($ERRORS{'DEBUG'}, 0, "private key file already exists on $vmhost_name: $vmhost_private_key_file_path");
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "private key file does not already exist on $vmhost_name: $vmhost_private_key_file_path");
+
+ # Private key does not exist on VM host
+ if ($self->vmhost_os->file_exists($vmhost_public_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "public key file exists on $vmhost_name but private key file does not, deleting public key file: $vmhost_public_key_file_path");
+ if (!$self->vmhost_os->delete_file($vmhost_public_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete orphaned public key file on VM host $vmhost_name: $vmhost_public_key_file_path");
+ return;
+ }
+ }
+
+ if ($vmware_product_name =~ /4\./) {
+ # Create the private key
+ my $command = "/bin/dropbearkey -t rsa -f $vmhost_private_key_file_path -s 768";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to create SSH private key on VM host $vmhost_name: $command");
+ return;
+ }
+ elsif ($exit_status ne 0) {
+ notify($ERRORS{'WARNING'}, 0, "failed to create SSH private key on VM host, exit status: $exit_status, command: $command, output:\n" . join("\n", @$output));
+ return;
+ }
+ # Make sure the newly created file exists
+ if (!$self->vmhost_os->file_exists($vmhost_private_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "attempted to create SSH private key file on VM host $vmhost_name but file still does not exist: $vmhost_private_key_file_path, command: $command, output:\n" . join("\n", @$output));
+ return;
+ }
+ }
+ else {
+ # Generate a new SSH key for the VM host
+ if (!$self->mn_os->generate_ssh_key_files($local_private_key_file_path, 'rsa', 1024, $vmhost_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to generate private SSH key file for VM host $vmhost_name");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ return;
+ }
+
+ # Copy files to the VM host
+ if (!$self->vmhost_os->copy_file_to($local_private_key_file_path, $vmhost_private_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to copy private key file from $management_node_name to VM host $vmhost_name: $local_private_key_file_path --> $vmhost_private_key_file_path");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ return;
+ }
+ if (!$self->vmhost_os->copy_file_to($local_public_key_file_path, $vmhost_public_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to copy public key file from $management_node_name to VM host $vmhost_name: $local_public_key_file_path --> $vmhost_public_key_file_path");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ return;
+ }
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "created SSH private key file on VM host $vmhost_name: $vmhost_private_key_file_path");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ }
+
+ # Check if the public key exists
+ if ($self->vmhost_os->file_exists($vmhost_public_key_file_path)) {
+ notify($ERRORS{'DEBUG'}, 0, "private and public SSH key files already exist on VM host $vmhost_name");
+ return 1;
+ }
+
+ # Attempt to retrieve the private key file from the VM host
+ notify($ERRORS{'DEBUG'}, 0, "public key file does not exist on VM host $vmhost_name: $vmhost_public_key_file_path");
+
+ my $public_key_string;
+ if ($vmware_product_name =~ /4\./) {
+ # Retrieve the public key from the private key
+ my $command = "/bin/dropbearkey -f $vmhost_private_key_file_path -y";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve SSH public key from VM host $vmhost_name: $command");
+ return;
+ }
+ elsif ($exit_status ne 0) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve SSH public key from VM host $vmhost_name, exit status: $exit_status, command: $command, output:\n" . join("\n", @$output));
+ return;
+ }
+
+ # Locate the output line beginning with 'ssh-'
+ ($public_key_string) = grep(/^ssh-/, @$output);
+ if ($public_key_string) {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved SSH public key from VM host $vmhost_name: $public_key_string");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve SSH public key from VM host $vmhost_name, command: $command, output does not contain a line beginning with 'ssh-':\n" . join("\n", @$output));
+ return;
+ }
+
+ if ($self->vmhost_os->create_text_file($vmhost_public_key_file_path, $public_key_string)) {
+ notify($ERRORS{'OK'}, 0, "created SSH public key file on VM host $vmhost_name: $vmhost_public_key_file_path");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to create SSH public key file on VM host $vmhost_name: $vmhost_public_key_file_path");
+ return;
+ }
+ }
+ else {
+ if (!$self->vmhost_os->copy_file_from($vmhost_private_key_file_path, $local_private_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve private key file from VM host $vmhost_name: $vmhost_private_key_file_path");
+ return;
+ }
+
+ # Generate local public key file from private key retrieved from VM host
+ if (!$self->mn_os->create_ssh_public_key_file($local_private_key_file_path, $local_public_key_file_path, $vmhost_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to create public key file on $management_node_name from VM host $vmhost_name private key file: $local_public_key_file_path");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ return;
+ }
+
+ # Copy the public key file to the VM host
+ if (!$self->vmhost_os->copy_file_to($local_public_key_file_path, $vmhost_public_key_file_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to copy newly created public key file from $management_node_name to VM host $vmhost_name: $local_public_key_file_path --> $vmhost_public_key_file_path");
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ return;
+ }
+
+ $self->mn_os->delete_file("$local_private_key_file_path*");
+ }
+
+ notify($ERRORS{'OK'}, 0, "configured SSH keys on VM host $vmhost_name");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 copy_vmhost_ssh_public_key_to_another_host
+
+ Parameters : $destination_vmhost_os
+ Returns : boolean
+ Description : Retrieves the public key from the VM host (/.ssh/id_rsa.pub) and
+ adds it to the authorized_keys file on the destination VM host.
+
+=cut
+
+sub copy_vmhost_ssh_public_key_to_another_host {
+ my ($self, $destination) = @_;
+ if (ref($self) !~ /VMware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+ elsif (!$destination) {
+ notify($ERRORS{'WARNING'}, 0, "destination VM host OS object argument was not supplied");
+ return;
+ }
+ elsif (ref($destination) !~ /VMware/i) {
+ notify($ERRORS{'WARNING'}, 0, "destination VM host OS object is not a VMware module: " . ref($destination));
+ return;
+ }
+
+ my $management_node_name = $self->data->get_management_node_short_name();
+ my $vmhost_computer_name = $self->data->get_vmhost_short_name();
+ my $destination_vmhost_computer_name = $destination->vmhost_os->data->get_computer_short_name();
+
+ my $destination_vmware_product_name = $destination->get_vmhost_product_name();
+ my $vmhost_authorized_keys_file_path;
+ if ($destination_vmware_product_name =~ /4\./) {
+ $vmhost_authorized_keys_file_path = "/.ssh/authorized_keys";
+ }
+ else {
+ $vmhost_authorized_keys_file_path = "/etc/ssh/keys-root/authorized_keys";
+ }
+
+ my $public_key_file_path = "/.ssh/id_rsa.pub";
+
+ # Get the VM host's public key
+ my $public_key_string = $self->vmhost_os->get_file_contents($public_key_file_path);
+ if ($public_key_string) {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_computer_name public SSH key:\n$public_key_string");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of public SSH key file from VM host $vmhost_computer_name");
+ return;
+ }
+
+ # Get the contents of the destination VM host's authorized_keys file
+ my $destination_authorized_keys_file_contents = $destination->vmhost_os->get_file_contents($vmhost_authorized_keys_file_path);
+ if ($destination_authorized_keys_file_contents) {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved contents of $vmhost_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name:\n$destination_authorized_keys_file_contents");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $vmhost_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name");
+ return;
+ }
+
+ # Remove comments from public key for comparison
+ my $public_key_string_cleaned = $public_key_string;
+ $public_key_string_cleaned =~ s/^\s*(ssh-\w+\s+[^\s=]+).*/$1/g;
+ my @destination_authorized_keys_file_lines = split(/\n+/, $destination_authorized_keys_file_contents);
+ for my $destination_authorized_keys_file_line (@destination_authorized_keys_file_lines) {
+ $destination_authorized_keys_file_line =~ s/^(ssh-\w+\s+[^\s=]+).*/$1/g;
+ if ($destination_authorized_keys_file_line eq $public_key_string_cleaned) {
+ notify($ERRORS{'DEBUG'}, 0, "$vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name already contains the VM host's $vmhost_computer_name public SSH key:\n$public_key_string");
+ return 1;
+ }
+ }
+
+ # Public key was not found in destination's authorized_keys file, attempt to add it
+ if ($destination->vmhost_os->append_text_file($vmhost_authorized_keys_file_path, $public_key_string)) {
+ notify($ERRORS{'OK'}, 0, "added VM host $vmhost_computer_name public SSH key to $vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name:\n$public_key_string");
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to add VM host $vmhost_computer_name public SSH key to $vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name");
+ return;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 copy_file_to_another_host
+
+ Parameters : $source, $source_file_path, $destination, $destination_file_path
+ Returns : boolean
+ Description : Copies a file from one VM host to another. The $source and
+ $destination arguments should be fully initialized VMware.pm
+ objects.
+
+=cut
+
+sub copy_file_to_another_host {
+ my ($source, $source_file_path, $destination, $destination_file_path) = @_;
+ if (!$source || ref($source) !~ /VMware/i) {
+ notify($ERRORS{'WARNING'}, 0, "source VM host argument is not a VMware module object");
+ return;
+ }
+ elsif (!$source_file_path) {
+ notify($ERRORS{'WARNING'}, 0, "source VM host file path argument was not provided");
+ return;
+ }
+ elsif (!$destination || ref($destination) !~ /VMware/i) {
+ notify($ERRORS{'WARNING'}, 0, "destination VM host argument is not a VMware module object");
+ return;
+ }
+ elsif (!$destination_file_path) {
+ notify($ERRORS{'WARNING'}, 0, "destination VM host file path argument was not provided");
+ return;
+ }
+
+ my $source_vmhost_computer_name = $source->vmhost_os->data->get_computer_short_name(0);
+ my $destination_vmhost_computer_name = $destination->vmhost_os->data->get_computer_short_name(0);
+ my $destination_connection_target = determine_remote_connection_target($destination_vmhost_computer_name);
+
+ my $command = "scp -i /.ssh/id_rsa $source_file_path $destination_connection_target:$destination_file_path";
+ my ($exit_status, $output) = $source->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to copy file from: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path");
+ return;
+ }
+ elsif ($exit_status ne 0 || grep(/^scp:/, @$output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to copy file: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'OK'}, 0, "copied file: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path\ncommand: $command\noutput:\n" . join("\n", @$output));
+ return 1;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 setup_migrate_vm
+
+ Parameters : $source_vmhost_provisioner
+ Returns : boolean
+ Description : Presents the vcld -setup menu for migrating a VM from one host to
+ another.
+
+=cut
+
+sub setup_migrate_vm {
+ my $self = shift;
+ if (ref($self) !~ /VMware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $source_vmhost_computer_name = $self->data->get_vmhost_short_name();
+ my $source_vmhost_id = $self->data->get_vmhost_id();
+
+ my $source_assigned_vm_info = get_vmhost_assigned_vm_info($source_vmhost_id);
+
+ #print "\nSelect VMs to be migrated off of $source_vmhost_computer_name:\n";
+ #my $vm_computer_id = setup_get_hash_multiple_choice($source_assigned_vm_info, 'SHORTNAME', 'currentimagerevision-imagename') || return;
+
+ my @vm_computer_ids = setup_get_hash_multiple_choice($source_assigned_vm_info,
+ {
+ 'title' => "Select VMs to be migrated off of $source_vmhost_computer_name",
+ 'display_keys' => ['{SHORTNAME}', ' - ', '{currentimagerevision}{imagename}', ' (', '{state}{name}', ')'],
+ }
+ );
+ return unless @vm_computer_ids;
+
+ # Get the list of VM hosts assigned to this management node
+ # Create a deep copy clone of the hash reference before deleting source VM host key from hash
+ # Otherwise, the result of get_management_node_vmhost_info would be altered for other callers
+ my $management_node_vmhost_info = dclone(get_management_node_vmhost_info());
+
+ # Display VM hosts other than the source, delete the source VM host ID key from the hash
+ delete $management_node_vmhost_info->{$source_vmhost_id};
+
+ print "\nSelect the destination VM host:\n";
+ my $destination_vmhost_id = setup_get_hash_choice($management_node_vmhost_info, 'hostname', 'vmprofile_profilename') || return;
+ my $destination_vmhost_computer_name = $management_node_vmhost_info->{$destination_vmhost_id}{computer}{SHORTNAME};
+ print "Destination VM host: $destination_vmhost_computer_name (VM host ID: $destination_vmhost_id)\n";
+
+ for my $vm_computer_id (@vm_computer_ids) {
+ setup_print_break('.');
+ my $vm_computer_name = $source_assigned_vm_info->{$vm_computer_id}{SHORTNAME};
+ print colored("Attempting to migrate $vm_computer_name from $source_vmhost_computer_name to $destination_vmhost_computer_name", 'BOLD CYAN');
+ print "\n";
+ if ($self->migrate_vm($vm_computer_id, $destination_vmhost_id)) {
+ print colored("Successfully migrated $vm_computer_name from $source_vmhost_computer_name to $destination_vmhost_computer_name", 'BOLD GREEN');
+ print "\n";
+ }
+ else {
+ print colored("Failed to migrate $vm_computer_name from $source_vmhost_computer_name to $destination_vmhost_computer_name, check $LOGFILE for more information", 'BOLD YELLOW ON_RED');
+ print "\n";
+ return;
+ }
+ }
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 migrate_vm
+
+ Parameters : $vm_identifier, $destination_vmhost_identifier
+ Returns : boolean
+ Description : Migrates a VM from the host the VM is assigned to to another
+ host.
+
+=cut
+
+sub migrate_vm {
+ my $self = shift;
+ if (ref($self) !~ /VMware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+ my ($vm_identifier, $destination_vmhost_identifier) = @_;
+ if (!defined($vm_identifier)) {
+ notify($ERRORS{'WARNING'}, 0, "VM identifier argument was not supplied");
+ return;
+ }
+ elsif (!defined($destination_vmhost_identifier)) {
+ notify($ERRORS{'WARNING'}, 0, "destination VM host identifier argument was not supplied");
+ return;
+ }
+
+ if ($SETUP_MODE) {
+ no warnings 'redefine';
+ *notify = sub {
+ my ($type, $log, $message) = @_;
+
+ my $calling_subroutine = (caller(1))[3];
+ if ($type == $ERRORS{'WARNING'}) {
+ print colored("WARNING: $message", 'BOLD YELLOW');
+ print "\n";
+ }
+ elsif ($type == $ERRORS{'CRITICAL'}) {
+ print colored("ERROR: $message", 'BOLD YELLOW ON_RED');
+ print "\n";
+ }
+ else {
+ if ($calling_subroutine =~ /migrate_vm/) {
+ if ($type == $ERRORS{'DEBUG'}) {
+ print colored("$message", 'WHITE');
+ }
+ elsif ($type == $ERRORS{'OK'}) {
+ print colored($message, 'WHITE');
+ }
+ print "\n";
+ }
+ else {
+ VCL::utils::notify($type, $log, $message);
+ }
+ }
+ };
+ }
+
+ my $management_node_name = $self->data->get_management_node_short_name();
+ my $provisioning_object_type = ref($self);
+
+ #...........................................................................
+ # Get the computer info for the VM to be migrated
+ my $vm_data = $self->create_datastructure_object({computer_identifier => $vm_identifier});
+ if (!$vm_data) {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_identifier, failed to create DataStructure object for VM");
+ return;
+ }
+
+ my $vm_computer_id = $vm_data->get_computer_id();
+ my $vm_computer_name = $vm_data->get_computer_short_name();
+ my $source_vmhost_id = $vm_data->get_vmhost_id(0);
+ my $source_vmhost_computer_id = $vm_data->get_vmhost_computer_id(0);
+ my $source_vmhost_computer_name = $vm_data->get_vmhost_short_name(0);
+
+ # Make sure VM is assigned to a VM host
+ if (!$source_vmhost_id) {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, VM host ID is not set for computer");
+ return;
+ }
+
+ ## Check if VM is responding
+ #my $vm_os_responding = $vm_os->is_ssh_responding();
+ #if (!$vm_os_responding) {
+ # if ($SETUP_MODE) {
+ # notify($ERRORS{'WARNING'}, 0, "$vm_computer_name is not responding to SSH");
+ # if (!setup_confirm("Continue to migrate the VM?", "N")) {
+ # return;
+ # }
+ # }
+ #}
+
+ # Determine the OS perl package to use to control the VM and create an OS object
+ notify($ERRORS{'DEBUG'}, 0, "attempting to log in to $vm_computer_name and determine OS currently loaded");
+ my $vm_os_perl_package = VCL::Module::OS::get_os_perl_package($vm_computer_name);
+ if ($vm_os_perl_package) {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved OS currently loaded on $vm_computer_name, $vm_os_perl_package module will be used");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine OS currently loaded on $vm_computer_name");
+ return;
+ }
+ my $vm_os = VCL::Module::create_object($vm_os_perl_package, $vm_data);
+ if ($vm_os) {
+ notify($ERRORS{'OK'}, 0, "created object to control VM $vm_computer_name ($vm_os_perl_package)");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to create $vm_os_perl_package object to control VM $vm_computer_name");
+ return;
+ }
+ if ($SETUP_MODE && $vm_os->can("initialize")) {
+ if (!$vm_os->initialize()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize " . ref($vm_os) . " object for VM $vm_computer_name");
+ return;
+ }
+ }
+
+ #...........................................................................
+ # Create an OS object for the source VM host
+ my $source_vmhost_os = $self->create_vmhost_os_object($source_vmhost_id);
+ if ($source_vmhost_os) {
+ notify($ERRORS{'OK'}, 0, "created OS object to control source VM host: $source_vmhost_computer_name (VM host computer ID: $source_vmhost_computer_id)");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, failed to create OS object to control source VM host: $source_vmhost_computer_name (VM host computer ID: $source_vmhost_computer_id)");
+ return;
+ }
+ if ($SETUP_MODE && $source_vmhost_os->can("initialize")) {
+ if (!$source_vmhost_os->initialize()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize " . ref($source_vmhost_os) . " OS object for source VM host");
+ return;
+ }
+ }
+
+ # Create a provisioning object for the source VM host
+ my $source = $self->create_object(
+ $provisioning_object_type,
+ { computer_identifier => $vm_computer_id, vmhost_identifier => $source_vmhost_id },
+ { vmhost_os => $source_vmhost_os }
+ );
+ if ($source) {
+ notify($ERRORS{'OK'}, 0, "created $provisioning_object_type object for source VM host: $source_vmhost_computer_name (VM host ID: $source_vmhost_id)");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to create $provisioning_object_type object for source VM host: $source_vmhost_computer_name (VM host ID: $source_vmhost_id)");
+ return;
+ }
+ if ($SETUP_MODE && $source->can("initialize")) {
+ if (!$source->initialize()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize " . ref($source) . " provisioning object for source VM host");
+ return;
+ }
+ }
+
+ $source->set_os($vm_os);
+ $vm_os->set_provisioner($source);
+
+ #...........................................................................
+ # Create an OS object for the destination VM host
+ my $destination_vmhost_os = $self->create_vmhost_os_object($destination_vmhost_identifier);
+ if (!$destination_vmhost_os) {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, failed to create OS object to control destination VM host: $destination_vmhost_identifier");
+ return;
+ }
+ if ($SETUP_MODE && $destination_vmhost_os->can("initialize")) {
+ if (!$destination_vmhost_os->initialize()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize " . ref($destination_vmhost_os) . " OS object for destination VM host");
+ return;
+ }
+ }
+
+ my $destination_vmhost_computer_id = $destination_vmhost_os->data->get_computer_id(0);
+ my $destination_vmhost_computer_name = $destination_vmhost_os->data->get_computer_short_name(0);
+ notify($ERRORS{'OK'}, 0, "created OS object to control destination VM host: $destination_vmhost_computer_name");
+
+ # Create a provisioning object for the destination VM host
+ my $destination = $self->create_object(
+ $provisioning_object_type,
+ { computer_identifier => $vm_computer_id, vmhost_identifier => $destination_vmhost_identifier },
+ { vmhost_os => $destination_vmhost_os }
+ );
+ if ($destination) {
+ notify($ERRORS{'OK'}, 0, "created $provisioning_object_type object for destination VM host: $destination_vmhost_identifier");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to create $provisioning_object_type object for destination VM host: $destination_vmhost_identifier");
+ return;
+ }
+ if ($SETUP_MODE && $destination->can("initialize")) {
+ if (!$destination->initialize()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize " . ref($destination) . " provisioning object for destination VM host");
+ return;
+ }
+ }
+
+ my $destination_vmhost_id = $destination->data->get_vmhost_id();
+
+ #...........................................................................
+ # Make sure the source and destination VM hosts are different
+ if ($source_vmhost_id == $destination_vmhost_id) {
+ notify($ERRORS{'WARNING'}, 0, "migration failed, $vm_computer_name is already assigned to VM host identified by the destination argument: $destination_vmhost_identifier (VM host computer name: $destination_vmhost_computer_name, VM host ID: $destination_vmhost_id)");
+ return;
+ }
+
+ # Configure host to host SSH
+ $source->configure_vmhost_ssh_keys() || return;
+ $source->copy_vmhost_ssh_public_key_to_another_host($destination) || return;
+
+
+ # Find the .vmx file on the source VM host
+ my @source_vmx_file_paths = $source->api->get_registered_vms();
+ my @matching_source_vmx_file_paths = grep(/\/$vm_computer_name\_[^\/]*\.vmx$/i, @source_vmx_file_paths);
+ if (!@matching_source_vmx_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, did not find a matching .vmx file on source VM host $source_vmhost_computer_name:\n" . join("\n", @source_vmx_file_paths));
+ return;
+ }
+ elsif (scalar(@matching_source_vmx_file_paths) > 1) {
+ notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, found multiple matching .vmx files on source VM host $source_vmhost_computer_name:\n" . join("\n", @matching_source_vmx_file_paths));
+ return;
+ }
+ my $source_vmx_file_path = $matching_source_vmx_file_paths[0];
+ notify($ERRORS{'DEBUG'}, 0, "found matching .vmx file on source VM host $source_vmhost_computer_name: $source_vmx_file_path");
+ $source->set_vmx_file_path($source_vmx_file_path);
+
+ # Possible TODO: if problems occur using VMware's suspend/resume, try OS's hibernate
+ # Figure out if VMware's suspend or the guest OS's hibernate should be used
+ # Check if source vmx contains any values known to cause problems with VMware's suspend/resume
+ my $source_vmx_info = $source->get_vmx_info($source_vmx_file_path);
+ my $suspend_method = 'vmware';
+ #my $problematic_suspend_parameters = {
+ # 'mks.enable3d' => 'true',
+ # 'svga.yes3d' => 'true',
+ #};
+ #for my $problematic_suspend_parameter (sort keys %$problematic_suspend_parameters) {
+ # my $problematic_suspend_value = $problematic_suspend_parameters->{$problematic_suspend_parameter};
+ # my ($source_vmx_parameter) = grep { $_ =~ /$problematic_suspend_parameter/i } sort keys %$source_vmx_info;
+ # if (!$source_vmx_parameter) {
+ # notify($ERRORS{'DEBUG'}, 0, "source vmx file does not contain the parameter: $problematic_suspend_parameter");
+ # next;
+ # }
+ #
+ # my $source_vmx_value = $source_vmx_info->{$source_vmx_parameter};
+ # notify($ERRORS{'DEBUG'}, 0, "source vmx file contains the parameter: $problematic_suspend_parameter = $source_vmx_value");
+ # if ($source_vmx_value =~ /^$problematic_suspend_value$/i) {
+ # notify($ERRORS{'DEBUG'}, 0, "source VM vmx file contains $source_vmx_parameter=$source_vmx_value, computer may not be able to start on destination VM host if VMware's suspend/resume method is used, checking if guest OS's hibernate method may be used");
+ # $suspend_method = 'os';
+ # last;
+ # }
+ #}
+
+ ## Perform additional checks if VMware's suspend/resume can't be used
+ #if ($suspend_method eq 'os') {
+ # # Check if the VM OS object implements a hibernate subroutine
+ # if (!$vm_os->can('hibernate')) {
+ # notify($ERRORS{'WARNING'}, 0, "unable to migrate $vm_computer_name, VMware suspend/resume cannot be used and $vm_os_perl_package module does not implement a 'hibernate' subroutine");
+ # return;
+ # }
+ #}
+ #notify($ERRORS{'DEBUG'}, 0, "source VM suspend/hibernate method: " . ($suspend_method eq 'vmware' ? 'VMware suspend' : 'guest OS hibernate'));
+
+ # Construct destination vmx file path
+ my $source_vmx_directory_name = $source->get_vmx_directory_name();
+ my $source_vmx_file_name = $source->get_vmx_file_name();
+ my $destination_vmx_base_directory_path = $destination->get_vmx_base_directory_path();
+ my $destination_vmx_file_path = "$destination_vmx_base_directory_path/$source_vmx_directory_name/$source_vmx_file_name";
+ $destination->set_vmx_file_path($destination_vmx_file_path);
+
+ # Needed for search/replace
+ my $source_vmx_base_directory_path = $source->get_vmx_base_directory_path();
+ my $source_vmx_base_directory_url_path = $source->_get_url_path($source_vmx_base_directory_path);
+ my $destination_vmx_base_directory_url_path = $destination->_get_url_path($destination_vmx_base_directory_path);
+
+ # Needed to locate files to copy
+ my $source_vmx_directory_path = $source->get_vmx_directory_path();
+
+ # Needed to verify destination VM doesn't exist
+ my $destination_vmx_directory_path = $destination->get_vmx_directory_path();
+
+ # Check if sure source and destination directories are different
+ my $source_vmx_directory_url_path = $source->_get_url_path($source_vmx_directory_path);
+ my $destination_vmx_directory_url_path = $destination->_get_url_path($destination_vmx_directory_path);
+ my $same_vmx_directory = ($source_vmx_directory_url_path eq $destination_vmx_directory_url_path ? 1 : 0);
+
+ # Create a snapshot of the source
+ # This is to reduce the amount of data to copy while the VM is hibernating
+ if (!$same_vmx_directory) {
+ notify($ERRORS{'DEBUG'}, 0, "attempting to create snapshot of $vm_computer_name on $source_vmhost_computer_name");
+ $source->snapshot() || return;
+ notify($ERRORS{'OK'}, 0, "created snapshot of $vm_computer_name on $source_vmhost_computer_name");
+ }
+
+ # Figure out the parent .vmdk file being used by the source VM
+ my @source_vmdk_file_paths = $self->api->get_vm_virtual_disk_file_paths($source_vmx_file_path);
+ if (!@source_vmdk_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate VM, source vmdk file paths could not be retrieved");
+ return;
+ }
+
+ # VM may have multiple virtual disks
+ my @source_primary_vmdk_file_paths = @{$source_vmdk_file_paths[0]};
+ if (!@source_primary_vmdk_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate VM, source primary vmdk file paths could not be determined from virtual disk file path info:\n" . format_data(\@source_primary_vmdk_file_paths));
+ return;
+ }
+
+ # The first file path should be the master/golden vmdk
+ my $source_master_vmdk_file_path = $source_primary_vmdk_file_paths[0];
+ notify($ERRORS{'DEBUG'}, 0, "determined source master vmdk file path: $source_master_vmdk_file_path");
+
+ # Set the vmdk file path in the source VMware object
+ $source->set_vmdk_file_path($source_master_vmdk_file_path);
+
+ # The last file path is actively being used by the VM
+ my $source_active_vmdk_file_path = $source_primary_vmdk_file_paths[-1];
+ my $source_active_vmdk_file_base_name = $self->_get_file_base_name($source_active_vmdk_file_path);
+ notify($ERRORS{'DEBUG'}, 0, "determined source active vmdk file path: $source_active_vmdk_file_path");
+
+ # Construct destination vmdk file path
+ my $source_vmdk_directory_name = $source->get_vmdk_directory_name();
+ my $source_vmdk_file_name = $source->get_vmdk_file_name();
+ my $destination_vmdk_base_directory_path = $destination->get_vmdk_base_directory_path();
+ my $destination_vmdk_file_path = "$destination_vmdk_base_directory_path/$source_vmdk_directory_name/$source_vmdk_file_name";
+
+ # Set the vmdk file path in the destination VMware object
+ $destination->set_vmdk_file_path($destination_vmdk_file_path);
+
+ # Needed for search/replace
+ my $source_vmdk_base_directory_path = $source->get_vmdk_base_directory_path();
+ my $source_vmdk_base_directory_url_path = $source->_get_url_path($source_vmdk_base_directory_path);
+ my $destination_vmdk_base_directory_url_path = $destination->_get_url_path($destination_vmdk_base_directory_path);
+
+ # Check if source and destination vmdk directories are different
+ my $source_vmdk_directory_path = $source->get_vmdk_directory_path();
+ my $source_vmdk_directory_url_path = $source->_get_url_path($source_vmdk_directory_path);
+ my $destination_vmdk_directory_path = $destination->get_vmdk_directory_path();
+ my $destination_vmdk_directory_url_path = $destination->_get_url_path($destination_vmdk_directory_path);
+ my $same_vmdk_directory = ($source_vmdk_directory_url_path eq $destination_vmdk_directory_url_path ? 1 : 0);
+
+ # Copy the parent vmdk to the correct location on the destination
+ # This may fail if vmdk doesn't exist on destination datastore or repository
+ notify($ERRORS{'DEBUG'}, 0, "copying destination master vmdk if necessary: $destination_vmdk_file_path");
+ $destination->prepare_vmdk() || return;
+
+ # Create the destination directory
+ if ($same_vmx_directory) {
+ notify($ERRORS{'DEBUG'}, 0, "directory does not need to be created on destination VM host $destination_vmhost_computer_name because source VM host is using the same directory: $destination_vmx_directory_path");
+ }
+ else {
+ if ($destination->vmhost_os->file_exists($destination_vmx_directory_path)) {
+ notify($ERRORS{'WARNING'}, 0, "directory already exists on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path, attempting to delete directory");
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) || return;
+ }
+
+ if ($destination->vmhost_os->create_directory($destination_vmx_directory_path)) {
+ notify($ERRORS{'OK'}, 0, "created directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to create directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path");
+ return;
+ }
+ }
+
+ # Get a list of all files in the source VM directory
+ # Check each file:
+ # build list of files that can only be copied after VM hibernates: @source_active_file_paths
+ # build list of files that need to be modified: @destination_edit_file_paths
+ my @source_vmx_directory_file_paths = $source->vmhost_os->find_files($source_vmx_directory_path, '*');
+ my @source_active_file_paths;
+ my @destination_edit_file_paths;
+ if (!@source_vmx_directory_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, no files were found in source vmx directory on $source_vmhost_computer_name: $source_vmx_directory_path");
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+ for my $source_file_path (@source_vmx_directory_file_paths) {
+ my $source_file_name = $self->_get_file_name($source_file_path);
+ my $destination_file_path = "$destination_vmx_directory_path/$source_file_name";
+
+ # Ignore these files, they aren't required on the destination in order for the VM to run
+ if ($source_file_path =~ /(\.log|vmx~|\.vswp|\.lck)/) {
+ #notify($ERRORS{'DEBUG'}, 0, "file will not be copied: $source_file_name");
+ next;
+ }
+
+ # Keep list of files which contain datastore names/paths specific to the source VM host
+ # These will be searched/replaced later on
+ if ($source_file_path !~ /(-delta|\.vmss)/) {
+ push @destination_edit_file_paths, $destination_file_path;
+ }
+
+ if (!$same_vmx_directory) {
+ # Keep list of files in use by the source VM which is still running
+ # These will be copied after the VM hibernates
+ if ($source_file_path =~ /$source_active_vmdk_file_base_name[\.-].*vmdk$/) {
+ push @source_active_file_paths, $source_file_path;
+ notify($ERRORS{'DEBUG'}, 0, "file is actively being used by the source VM, will be copied after source VM is suspended: $source_file_name");
+ next;
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "copying file to destination: $destination_vmhost_computer_name:$destination_file_path");
+ if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) {
+ #notify($ERRORS{'OK'}, 0, "copied file to destination: $destination_vmhost_computer_name:$destination_file_path");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file from source to destination VM host: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path");
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path);
+ return;
+ }
+ }
+ }
+
+
+ # Suspend/hibernate the source VM - the amount of time the VM is unavailable should be minimized
+ # Do as much as possible before this step
+ # Keep track of how long the VM is inaccessible
+ my $hibernate_start_time = time;
+ if ($suspend_method eq 'vmware') {
+ notify($ERRORS{'DEBUG'}, 0, "attempting to suspend $vm_computer_name on source VM host $source_vmhost_computer_name");
+ if ($self->api->vm_suspend($source_vmx_file_path)) {
+ notify($ERRORS{'OK'}, 0, "suspended $vm_computer_name on source VM host $source_vmhost_computer_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to suspend source VM");
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "attempting to hibernate $vm_computer_name's guest OS");
+ if ($vm_os->hibernate()) {
+ notify($ERRORS{'OK'}, 0, "hibernated $vm_computer_name's guest OS");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to hibernate VM's guest OS");
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+ }
+
+ # Update computer.vmhostid
+ # Do this before hibernating - it would be more difficult to revert things if the update were to fail after a successful migration
+ if (update_computer_vmhost_id($vm_computer_id, $destination_vmhost_id)) {
+ notify($ERRORS{'OK'}, 0, "updated VM host $vm_computer_name is assigned to in the database (VM host ID: $destination_vmhost_id)");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to update computer.vmhostid column in database");
+ migrate_revert_source($source, $vm_os);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path);
+ return;
+ }
+
+ # Copy the files that were actively being used by the source VM
+ if (!$same_vmx_directory) {
+ for my $source_file_path (@source_active_file_paths) {
+ my $file_name = $self->_get_file_name($source_file_path);
+ my $destination_file_path = "$destination_vmx_directory_path/$file_name";
+ if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) {
+ notify($ERRORS{'OK'}, 0, "copied file to destination VM host: $destination_vmhost_computer_name:$destination_file_path");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file which was actively being used by the VM from source to destination VM host after the VM hibernated: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path");
+ migrate_revert_source($source, $vm_os);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path);
+ return;
+ }
+ }
+ }
+
+ # Update files on destination which have paths specific to the source VM host
+ my $file_replacements = {};
+ if (!$same_vmx_directory) {
+ $file_replacements->{$source_vmx_base_directory_path} = $destination_vmx_base_directory_path;
+ $file_replacements->{$source_vmx_base_directory_url_path} = $destination_vmx_base_directory_url_path;
+ }
+ if (!$same_vmdk_directory) {
+ $file_replacements->{$source_vmdk_base_directory_path} = $destination_vmdk_base_directory_path;
+ $file_replacements->{$source_vmdk_base_directory_url_path} = $destination_vmdk_base_directory_url_path;
+ };
+ SOURCE_PATTERN: for my $source_pattern (sort keys %$file_replacements) {
+ my $destination_pattern = $file_replacements->{$source_pattern};
+ next if ($source_pattern eq $destination_pattern);
+ notify($ERRORS{'DEBUG'}, 0, "updating files on destination VM host $destination_vmhost_computer_name, pattern: $source_pattern --> $destination_pattern");
+ for my $destination_file_path (@destination_edit_file_paths) {
+ my $sed_command = "sed -i -e \"s|$source_vmx_base_directory_path|$destination_vmx_base_directory_path|g\" $destination_file_path";
+ my ($sed_exit_status, $sed_output) = $destination->vmhost_os->execute($sed_command);
+ if (!defined($sed_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to execute command on destination VM host $destination_vmhost_computer_name: $sed_command");
+ migrate_revert_source($source, $vm_os);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+ elsif (grep(/sed:/, @$sed_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to update file on destination VM host $destination_vmhost_computer_name, exit status: $sed_exit_status\ncommand:\n$sed_command\noutput:\n" . join("\n", @$sed_output));
+ migrate_revert_source($source, $vm_os);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+ else {
+ #notify($ERRORS{'OK'}, 0, "updated file on $destination_vmhost_computer_name: $destination_file_path");
+ }
+ }
+ }
+
+ # Register the VM on the destination VM host
+ notify($ERRORS{'DEBUG'}, 0, "registering VM on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path");
+ if ($destination->api->vm_register($destination_vmx_file_path)) {
+ notify($ERRORS{'OK'}, 0, "registered VM on destination VM host $destination_vmhost_computer_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to register VM on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path");
+ migrate_revert_source($source, $vm_os);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+
+ # Power on the VM on the destination VM host
+ notify($ERRORS{'DEBUG'}, 0, "powering on $vm_computer_name on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path");
+ if ($destination->api->vm_power_on($destination_vmx_file_path)) {
+ notify($ERRORS{'OK'}, 0, "powered on $vm_computer_name on $destination_vmhost_computer_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to power on VM on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path");
+ migrate_revert_source($source, $vm_os);
+ $destination->api->vm_unregister($destination_vmx_file_path);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+
+ # Wait for the destination VM to respond
+ notify($ERRORS{'DEBUG'}, 0, "waiting for $vm_computer_name to respond to SSH on destination VM host $destination_vmhost_computer_name");
+ if ($vm_os->wait_for_ssh(300, 3)) {
+ notify($ERRORS{'OK'}, 0, "$vm_computer_name is responding to SSH on destination VM host $destination_vmhost_computer_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, VM never responded on destination VM host $destination_vmhost_computer_name");
+ migrate_revert_source($source, $vm_os);
+ $destination->api->vm_unregister($destination_vmx_file_path);
+ $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory);
+ return;
+ }
+
+ my $hibernate_duration = (time - $hibernate_start_time);
+
+ # Remove the original VM from the source VM host
+ if ($same_vmx_directory) {
+ $source->api->vm_unregister($source_vmx_file_path);
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "deleting original VM from $source_vmhost_computer_name: $source_vmx_file_path");
+ $source->delete_vm($source_vmx_file_path);
+ notify($ERRORS{'OK'}, 0, "deleted original VM from $source_vmhost_computer_name: $source_vmx_file_path");
+ }
+
+ notify($ERRORS{'OK'}, 0, "migration of $vm_computer_name complete: $source_vmhost_computer_name --> $destination_vmhost_computer_name, hibernation duration: $hibernate_duration seconds");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 migrate_revert_source
+
+ Parameters : $source, $vm_os
+ Returns : boolean
+ Description : Called if a VM migration fails. Powers the original VM back on
+ and reverts the computer.vmhostid value for the VM.
+
+=cut
+
+sub migrate_revert_source {
+ my ($source, $vm_os) = @_;
+ if (!$source || !ref($source) || ref($source) !~ /VMware/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "first argument is not a VMware object reference");
+ return;
+ }
+ elsif (!$vm_os || !ref($vm_os) || ref($vm_os) !~ /VCL::Module::OS/i) {
+ notify($ERRORS{'CRITICAL'}, 0, "third argument is not an OS object reference");
+ return;
+ }
+
+ my $vm_computer_id = $vm_os->data->get_computer_id();
+ my $vm_computer_name = $vm_os->data->get_computer_short_name();
+ my $source_vmhost_computer_name = $source->vmhost_os->data->get_computer_short_name();
+ my $source_vmhost_id = $source->data->get_vmhost_id();
+
+ # Power the source VM back on
+ if (!$source->power_on()) {
+ notify($ERRORS{'CRITICAL'}, 0, "migration failed, failed to power $vm_computer_name back on after it hibernated on source VM host $source_vmhost_computer_name");
+ return;
+ }
+
+ # Wait for the source VM to respond
+ if (!$vm_os->wait_for_ssh(300, 5)) {
+ notify($ERRORS{'CRITICAL'}, 0, "migration failed, source VM $vm_computer_name never responded after it was powered back on after hibernation on $source_vmhost_computer_name");
+ return;
+ }
+
+ # Change computer.vmhostid back to the source VM host
+ if (!update_computer_vmhost_id($vm_computer_id, $source_vmhost_id)) {
+ notify($ERRORS{'CRITICAL'}, 0, "migration failed, failed to set VM host ID of $vm_computer_name back to source VM host ID: $source_vmhost_id");
+ return;
+ }
+
+ notify($ERRORS{'OK'}, 0, "reverted VM $vm_computer_name on source VM host $source_vmhost_computer_name");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
1;
__END__