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__


Reply via email to