Author: arkurth
Date: Thu Sep 18 19:38:54 2014
New Revision: 1626057

URL: http://svn.apache.org/r1626057
Log:
VCL-685

Updated VIM_SSH.pm::_services_restart and _check_service_pid. Occasionally '1' 
was returned as the PID of a service and the code had been writing this to the 
.pid file. This caused problems. Added a check for a valid value. Added 
additional services to check. 

Updated VMware.pm::is_vm_registered to accept datastore formatted paths.

Updated VMware.pm::delete_vm to remove the VM's parent directory if it was 
named after the VM.

Added VIM_SSH.pm::vm_suspend for a script I wrote to migrate VMs. This 
subroutine may be used at some point in the future.

Updated VMware.pm::get_datastore_info to strip away the leading 'ds://' in the 
vmdk's URL if it was present. This allows paths containing the URL to be used.

Updated vSphere_SDK.pm::copy_virtual_disk to delete the source VM it creates 
during a cloning operation if the clone fails.

VCL-724
Added VMware.pm::check_multiextent and vSphere_SDK.pm::is_multiextent_disabled. 
These are called by the copy/move_vmdk subroutines to detect if a 2gbsparse 
vmdk operation fails because multiextent is not enabled on the host.


Modified:
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm?rev=1626057&r1=1626056&r2=1626057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm 
(original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm Thu 
Sep 18 19:38:54 2014
@@ -166,7 +166,7 @@ sub _run_vim_cmd {
                return;
        }
        
-       my $timeout_seconds = shift || 30;
+       my $timeout_seconds = shift || 60;
        
        my $vmhost_computer_name = 
$self->vmhost_os->data->get_computer_short_name();
        
@@ -255,11 +255,6 @@ sub _services_restart {
        
        my $vmhost_computer_name = 
$self->vmhost_os->data->get_computer_short_name();
        
-       # Check if the PID files for the following services are correct
-       $self->_check_service_pid('hostd-worker', 
'/var/run/vmware/vmware-hostd.PID');
-       $self->_check_service_pid('sfcb-vmware_bas', 
'/var/run/vmware/vicimprovider.PID');
-       
-       my $services_command = "services.sh restart";
        my $semaphore = 
$self->get_semaphore("$vmhost_computer_name-vmware_services_restart", 0);
        if (!$semaphore) {
                notify($ERRORS{'OK'}, 0, "unable to obtain semaphore, another 
process is likely running '$services_command' on $vmhost_computer_name, 
sleeping for 30 seconds and then proceeding");
@@ -267,8 +262,26 @@ sub _services_restart {
                return 1;
        }
        
+       my $check_services = {
+               'hostd-worker' => '/var/run/vmware/vmware-hostd.PID',
+               'sfcb-vmware_bas' => '/var/run/vmware/vicimprovider.PID',
+               'vmkdevmgr' => '/var/run/vmware/vmkdevmgr.pid',
+               'vmkeventd' => '/var/run/vmware/vmkeventd.pid',
+               'vmsyslogd' => '/var/run/vmware/vmsyslogd.pid',
+               'rhttpproxy-work' => '/var/run/vmware/vmware-rhttpproxy.PID',
+               'vpxa-worker' => '/var/run/vmware/vmware-vpxa.PID',
+       };
+       
+       # Check if the PID files for the following services are correct
+       for my $service_name (keys %$check_services) {
+               my $pid_file_path = $check_services->{$service_name};
+               $self->_check_service_pid($service_name, $pid_file_path);
+       }
+       
+       my $services_command = "services.sh restart";
+       
        notify($ERRORS{'DEBUG'}, 0, "restarting VMware services on 
$vmhost_computer_name");
-       my ($services_exit_status, $services_output) = 
$self->vmhost_os->execute($services_command, 1, 90);
+       my ($services_exit_status, $services_output) = 
$self->vmhost_os->execute($services_command, 1, 120);
        if (!defined($services_output)) {
                notify($ERRORS{'WARNING'}, 0, "failed to run command on VM host 
$vmhost_computer_name: $services_command");
                return;
@@ -322,11 +335,15 @@ sub _check_service_pid {
        }
        else {
                ($running_pid) = "@$ps_output" =~ /(\d+)/g;
-               if ($running_pid) {
+               if (!$running_pid) {
+                       notify($ERRORS{'DEBUG'}, 0, "parent $process_name PID 
is not running");
+                       return;
+               }
+               elsif ($running_pid > 1) {
                        notify($ERRORS{'DEBUG'}, 0, "retrieved parent 
$process_name PID: $running_pid");
                }
                else {
-                       notify($ERRORS{'WARNING'}, 0, "unable to determine 
parent $process_name PID, command: '$ps_command', output:\n" . join("\n", 
@$ps_output));
+                       notify($ERRORS{'WARNING'}, 0, "parent $process_name PID 
not valid: $running_pid, command: '$ps_command', output:\n" . join("\n", 
@$ps_output));
                        return;
                }
        }
@@ -428,7 +445,7 @@ sub _get_vm_list {
                my $vmx_normal_path = $self->_get_normal_path($vmx_file_path);
                if (!$vmx_normal_path) {
                        notify($ERRORS{'WARNING'}, 0, "unable to determine 
normal path: $vmx_file_path");
-                       return;
+                       #return;
                }
                
                $vms{$vm_id} = $vmx_normal_path;
@@ -469,7 +486,7 @@ sub _get_vm_id {
        }
        
        for my $vm_id (keys %$vm_list) {
-               return $vm_id if ($vmx_file_path eq $vm_list->{$vm_id});
+               return $vm_id if ($vm_list->{$vm_id} && $vmx_file_path eq 
$vm_list->{$vm_id});
        }
        
        notify($ERRORS{'WARNING'}, 0, "unable to determine VM ID, vmx file is 
not registered: $vmx_file_path, registered VMs:\n" . format_data($vm_list));
@@ -1207,7 +1224,7 @@ sub vm_power_off {
        # Get the task ID
        my @task_ids = $self->_get_task_ids($vmx_file_path, 'powerOff');
        if (!@task_ids) {
-               notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the 
task created to power on the VM");
+               notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the 
task created to power off the VM");
                return;
        }
        
@@ -1224,6 +1241,84 @@ sub vm_power_off {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 vm_suspend
+
+ Parameters  : $vmx_file_path
+ Returns     : boolean
+ Description : Powers off the VM indicated by the vmx file path argument.
+
+=cut
+
+sub vm_suspend {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       # Get the vmx file path argument
+       my $vmx_file_path = shift;
+       if (!$vmx_file_path) {
+               notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not 
supplied");
+               return;
+       }
+       
+       # Check if the VM is already powered off
+       my $vm_power_state = $self->get_vm_power_state($vmx_file_path);
+       if ($vm_power_state) {
+               if ($vm_power_state =~ /off/i) {
+                       notify($ERRORS{'DEBUG'}, 0, "VM is already powered off: 
$vmx_file_path");
+                       return 1;
+               }
+               elsif ($vm_power_state =~ /suspend/i) {
+                       notify($ERRORS{'DEBUG'}, 0, "VM is already suspended: 
$vmx_file_path");
+                       return 1;
+               }
+       }
+       
+       # Get the VM ID
+       my $vm_id = $self->_get_vm_id($vmx_file_path);
+       if (!defined($vm_id)) {
+               notify($ERRORS{'WARNING'}, 0, "unable to power off VM because 
VM ID could not be determined");
+               return;
+       }
+       
+       my $vim_cmd_arguments = "vmsvc/power.suspend $vm_id";
+       my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 
400);
+       return if !$output;
+       
+       # Expected output if the VM was not previously suspended:
+       # Suspending VM:
+       
+       # Expected output if the VM was previously suspended or powered off:
+       # Suspending VM:
+       # Suspend failed
+       
+       if (!grep(/Suspending VM/i, @$output)) {
+               notify($ERRORS{'WARNING'}, 0, "unexpected output returned while 
attempting to suspend VM $vmx_file_path, VIM command arguments: 
'$vim_cmd_arguments', output:\n" . join("\n", @$output));
+               return;
+       }
+
+       # Get the task ID
+       my @task_ids = $self->_get_task_ids($vmx_file_path, 'suspend');
+       if (!@task_ids) {
+               notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the 
task created to suspend the VM");
+               return;
+       }
+       
+       # Wait for the task to complete
+       if ($self->_wait_for_task($task_ids[0])) {
+               notify($ERRORS{'OK'}, 0, "suspended VM: $vmx_file_path");
+               return 1;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to suspend VM: 
$vmx_file_path, the vim power off task did not complete successfully, vim-cmd 
$vim_cmd_arguments output:\n" . join("\n", @$output));
+               return;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 vm_register
 
  Parameters  : $vmx_file_path

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=1626057&r1=1626056&r2=1626057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm 
(original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm Thu 
Sep 18 19:38:54 2014
@@ -1317,7 +1317,7 @@ sub get_vmhost_api_object {
                return 0;
        }
        notify($ERRORS{'DEBUG'}, 0, "loaded VMware control module: 
$api_perl_package");
-       
+
        # Create an API object to control the VM host and VMs
        my $api;
        eval { $api = ($api_perl_package)->new({data_structure => $self->data,
@@ -4153,12 +4153,12 @@ sub is_vm_registered {
                notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not 
specified and default vmx file path could not be determined");            
                return;
        }
-       $vmx_file_path = normalize_file_path($vmx_file_path);
+       $vmx_file_path = $self->_get_normal_path($vmx_file_path);
        
        my @registered_vmx_file_paths = $self->api->get_registered_vms();
        for my $registered_vmx_file_path (@registered_vmx_file_paths) {
-               $registered_vmx_file_path = 
normalize_file_path($registered_vmx_file_path);
-               if ($vmx_file_path eq $registered_vmx_file_path) {
+               $registered_vmx_file_path = 
$self->_get_normal_path($registered_vmx_file_path);
+               if ($registered_vmx_file_path && $vmx_file_path eq 
$registered_vmx_file_path) {
                        notify($ERRORS{'DEBUG'}, 0, "VM is registered: 
$vmx_file_path");
                        return 1;
                }
@@ -5305,7 +5305,11 @@ sub delete_vm {
        
        # Get the vmx info
        my $vmx_info = $self->get_vmx_info($vmx_file_path);
-       if (!$vmx_info) {
+       my $vmx_directory_path;
+       if ($vmx_info) {
+               $vmx_directory_path = $vmx_info->{vmx_directory_path};
+       }
+       else {
                # Failed to retrieve vmx info
                # VM may have been registered but the vmx file/directory was 
deleted
                # Check if the vmx file exists
@@ -5316,12 +5320,19 @@ sub delete_vm {
                        return;
                }
                
-               notify($ERRORS{'OK'}, 0, "deleted VM, successfully unregistered 
VM but it vmx directory was not deleted because the vmx file does not exist: 
$vmx_file_path");
-               return 1;
+               # Check if the vmx parent directory is named after the vmx file
+               # If so, it's safe to delete the directory
+               my $vmx_file_base_name = 
$self->_get_file_base_name($vmx_file_path);
+               $vmx_directory_path = 
$self->_get_parent_directory_normal_path($vmx_file_path) || '';
+               if ($vmx_directory_path && $vmx_directory_path =~ 
/\/$vmx_file_base_name$/) {
+                       notify($ERRORS{'OK'}, 0, "deleted VM, successfully 
unregistered VM, vmx file does not exist: $vmx_file_path, deleting vmx parent 
directory: $vmx_directory_path, directory name matches the vmx file base name: 
$vmx_file_base_name");
+               }
+               else {
+                       notify($ERRORS{'OK'}, 0, "deleted VM, successfully 
unregistered VM but it vmx directory was not deleted because the vmx file does 
not exist: $vmx_file_path, vmx parent directory name ($vmx_directory_path) does 
not match the vmx file base name: $vmx_file_base_name");
+                       return 1;
+               }
        }
        
-       my $vmx_directory_path = $vmx_info->{vmx_directory_path};
-       
        # Delete the vmx directory
        my $attempt = 0;
        my $attempt_limit = 5;
@@ -5356,7 +5367,11 @@ sub delete_vm {
                }
                
                # Check if the directory containing the vmdk is shared among 
different VMs or dedicated to the VM being deleted
-               if ($self->is_vmdk_directory_shared($vmdk_directory_path)) {
+               my $vmdk_directory_shared = 
$self->is_vmdk_directory_shared($vmdk_directory_path);
+               if (!defined($vmdk_directory_shared)) {
+                       notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be 
deleted, unable to determine if vmdk appears to be shared: 
$vmdk_directory_path");
+               }
+               elsif ($vmdk_directory_shared) {
                        # Directory is shared, entire directory can't be deleted
                        notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be 
deleted because the vmdk appears to be shared: $vmdk_directory_path");
                        
@@ -5521,7 +5536,7 @@ sub copy_vmdk {
        my $vmhost_product_name = $self->get_vmhost_product_name();
        
        # Get the arguments
-       my ($source_vmdk_file_path, $destination_vmdk_file_path, 
$virtual_disk_type) = @_;
+       my ($source_vmdk_file_path, $destination_vmdk_file_path, 
$destination_virtual_disk_type) = @_;
        if (!$source_vmdk_file_path || !$destination_vmdk_file_path) {
                notify($ERRORS{'WARNING'}, 0, "source and destination vmdk file 
path arguments were not specified");
                return;
@@ -5546,12 +5561,12 @@ sub copy_vmdk {
        my $destination_reference_vmx_file_path = 
"$destination_directory_path/$destination_reference_vmx_file_name";
        
        # Set the default virtual disk type if the argument was not specified
-       if (!$virtual_disk_type) {
+       if (!$destination_virtual_disk_type) {
                if ($vmhost_product_name =~ /esx/i) {
-                       $virtual_disk_type = 'thin';
+                       $destination_virtual_disk_type = 'thin';
                }
                else {
-                       $virtual_disk_type = '2gbsparse';
+                       $destination_virtual_disk_type = '2gbsparse';
                }
        }
        
@@ -5577,7 +5592,7 @@ sub copy_vmdk {
        my $end_time;
        # Attempt to use the API's copy_virtual_disk subroutine
        if ($self->api->can('copy_virtual_disk')) {
-               my $copied_destination_vmdk_file_path = 
$self->api->copy_virtual_disk($source_vmdk_file_path, 
$destination_vmdk_file_path, $virtual_disk_type);
+               my $copied_destination_vmdk_file_path = 
$self->api->copy_virtual_disk($source_vmdk_file_path, 
$destination_vmdk_file_path, $destination_virtual_disk_type);
                if ($copied_destination_vmdk_file_path) {
                        $end_time = time;
                        $copied_destination_vmdk_file_path = 
$self->_get_normal_path($copied_destination_vmdk_file_path);
@@ -5615,6 +5630,14 @@ sub copy_vmdk {
        }
        
        if (!$end_time) {
+               # If the source disk is 2gb sparse, make sure multiextent is 
loaded
+               my $source_virtual_disk_type = 
$self->api->get_virtual_disk_type($source_vmdk_file_path);
+               if ($source_virtual_disk_type =~ /sparse/i || 
$destination_virtual_disk_type =~ /sparse/) {
+                       if (!$self->check_multiextent()) {
+                               notify($ERRORS{'WARNING'}, 0, "copy will likely 
fail, multiextent kernel module is disabled on VM host $vmhost_name");
+                       }
+               }
+               
                # Create the destination directory
                if 
(!$self->vmhost_os->create_directory($destination_directory_path)) {
                        notify($ERRORS{'WARNING'}, 0, "unable to copy vmdk, 
destination directory could not be created on VM host $vmhost_name: 
$destination_directory_path");
@@ -5622,8 +5645,8 @@ sub copy_vmdk {
                }
                
                # Try to use vmkfstools
-               my $command = "vmkfstools -i \"$source_vmdk_file_path\" 
\"$destination_vmdk_file_path\" -d $virtual_disk_type";
-               notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk 
using vmkfstools, disk type: $virtual_disk_type:\n'$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
+               my $command = "vmkfstools -i \"$source_vmdk_file_path\" 
\"$destination_vmdk_file_path\" -d $destination_virtual_disk_type";
+               notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk 
using vmkfstools, disk type: 
$destination_virtual_disk_type:\n'$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
                
                $start_time = time;
                my ($exit_status, $output) = 
$self->vmhost_os->execute($command, 1, 7200);
@@ -5672,7 +5695,7 @@ sub copy_vmdk {
                }
                else {
                        $end_time = time;
-                       notify($ERRORS{'OK'}, 0, "copied virtual disk on VM 
host using vmkfstools, destination disk type: 
$virtual_disk_type:\n'$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
+                       notify($ERRORS{'OK'}, 0, "copied virtual disk on VM 
host using vmkfstools, destination disk type: 
$destination_virtual_disk_type:\n'$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
                }
        }
        
@@ -5944,6 +5967,10 @@ sub move_vmdk {
                return;
        }
        
+       # Normalize the file paths
+       $source_vmdk_file_path = 
$self->_get_normal_path($source_vmdk_file_path) || return;
+       $destination_vmdk_file_path = 
$self->_get_normal_path($destination_vmdk_file_path) || return;
+       
        # Make sure the source vmdk file exists
        if (!$self->vmhost_os->file_exists($source_vmdk_file_path)) {
                notify($ERRORS{'WARNING'}, 0, "source vmdk file path does not 
exist: $source_vmdk_file_path");
@@ -5958,8 +5985,10 @@ sub move_vmdk {
        
        notify($ERRORS{'DEBUG'}, 0, "attempting to move vmdk: 
'$source_vmdk_file_path' --> '$destination_vmdk_file_path'");
        
+       my $source_vmdk_directory_path = 
$self->_get_parent_directory_normal_path($source_vmdk_file_path);
+       
        # Determine the destination vmdk directory path and create the directory
-       my ($destination_vmdk_directory_path) = $destination_vmdk_file_path =~ 
/(.+)\/[^\/]+\.vmdk$/;
+       my $destination_vmdk_directory_path = 
$self->_get_parent_directory_normal_path($destination_vmdk_file_path);
        if (!$destination_vmdk_directory_path) {
                notify($ERRORS{'WARNING'}, 0, "unable to determine destination 
vmdk directory path from vmdk file path: $destination_vmdk_file_path");
                return;
@@ -5976,6 +6005,12 @@ sub move_vmdk {
        
        # Check if the VM host OS object implements an execute subroutine and 
attempt to run vmware-vdiskmanager
        if ($self->vmhost_os->can("execute")) {
+               # If the source disk is 2gb sparse, make sure multiextent is 
loaded
+               my $source_virtual_disk_type = 
$self->api->get_virtual_disk_type($source_vmdk_file_path);
+               if ($source_virtual_disk_type =~ /sparse/i) {
+                       $self->check_multiextent();
+               }
+               
                # Try vmware-vdiskmanager
                notify($ERRORS{'OK'}, 0, "attempting to move vmdk file using 
vmware-vdiskmanager: $source_vmdk_file_path --> $destination_vmdk_file_path");
                my $vdisk_command = "vmware-vdiskmanager -n 
\"$source_vmdk_file_path\" \"$destination_vmdk_file_path\"";
@@ -5984,6 +6019,15 @@ sub move_vmdk {
                        notify($ERRORS{'WARNING'}, 0, "failed to execute 
'vmware-vdiskmanager' command on VM host to move vmdk file:\n$vdisk_command");
                }
                elsif (grep(/success/i, @$vdisk_output)) {
+                       # Check if the source directory still exists and 
contains files
+                       my @source_directory_files = 
$self->vmhost_os->find_files($source_vmdk_directory_path, '*');
+                       if (@source_directory_files) {
+                               notify($ERRORS{'DEBUG'}, 0, "source directory 
will not be deleted, it still contains files: $source_vmdk_directory_path\n" . 
join("\n", @source_directory_files));
+                       }
+                       else {
+                               notify($ERRORS{'DEBUG'}, 0, "source directory 
is empty, attempting to delete: $source_vmdk_directory_path");
+                               
$self->vmhost_os->delete_file($source_vmdk_directory_path);
+                       }
                        notify($ERRORS{'OK'}, 0, "moved vmdk file by executing 
'vmware-vdiskmanager' command on VM host:\ncommand: $vdisk_command\noutput: " . 
join("\n", @$vdisk_output));
                        return 1;
                }
@@ -5994,7 +6038,6 @@ sub move_vmdk {
                        notify($ERRORS{'WARNING'}, 0, "failed to execute 
'vmware-vdiskmanager' command on VM host to move vmdk 
file:\n$vdisk_command\noutput:\n" . join("\n", @$vdisk_output));
                }
                
-               
                # Try vmkfstools
                notify($ERRORS{'DEBUG'}, 0, "attempting to move vmdk file using 
vmkfstools: $source_vmdk_file_path --> $destination_vmdk_file_path");
                my $vmkfs_command = "vmkfstools -E \"$source_vmdk_file_path\" 
\"$destination_vmdk_file_path\"";
@@ -6015,6 +6058,15 @@ sub move_vmdk {
                        notify($ERRORS{'WARNING'}, 0, "failed to move vmdk file 
using vmkfstools, destination file does not exist: '$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
                }
                else {
+                       # Check if the source directory still exists and 
contains files
+                       my @source_directory_files = 
$self->vmhost_os->find_files($source_vmdk_directory_path, '*');
+                       if (@source_directory_files) {
+                               notify($ERRORS{'DEBUG'}, 0, "source directory 
will not be deleted, it still contains files: $source_vmdk_directory_path\n" . 
join("\n", @source_directory_files));
+                       }
+                       else {
+                               notify($ERRORS{'DEBUG'}, 0, "source directory 
is empty, attempting to delete: $source_vmdk_directory_path");
+                               
$self->vmhost_os->delete_file($source_vmdk_directory_path);
+                       }
                        notify($ERRORS{'OK'}, 0, "moved vmdk file using 
vmkfstools: '$source_vmdk_file_path' --> '$destination_vmdk_file_path'");
                        return 1;
                }
@@ -6026,13 +6078,6 @@ sub move_vmdk {
        # Unable to move vmdk file using any VMware utilities or APIs
        # Attempt to manually move the files
        
-       # Determine the source vmdk directory path
-       my ($source_vmdk_directory_path) = $source_vmdk_file_path =~ 
/(.+)\/[^\/]+\.vmdk$/;
-       if (!$source_vmdk_directory_path) {
-               notify($ERRORS{'WARNING'}, 0, "unable to determine source vmdk 
directory path from vmdk file path: $source_vmdk_file_path");
-               return;
-       }
-       
        # Determine the source vmdk file name
        my ($source_vmdk_file_name) = $source_vmdk_file_path =~ 
/\/([^\/]+\.vmdk)$/;
        if (!$source_vmdk_file_name) {
@@ -6169,12 +6214,85 @@ sub move_vmdk {
                return;
        }
        
+       # Check if the source directory still exists and contains files
+       my @source_directory_files = 
$self->vmhost_os->find_files($source_vmdk_directory_path, '*');
+       if (@source_directory_files) {
+               notify($ERRORS{'DEBUG'}, 0, "source directory will not be 
deleted, it still contains files: $source_vmdk_directory_path\n" . join("\n", 
@source_directory_files));
+       }
+       else {
+               notify($ERRORS{'DEBUG'}, 0, "source directory is empty, 
attempting to delete: $source_vmdk_directory_path");
+               $self->vmhost_os->delete_file($source_vmdk_directory_path);
+       }
+       
        notify($ERRORS{'OK'}, 0, "moved vmdk file: '$source_vmdk_file_path' --> 
'$destination_vmdk_file_path'");
        return 1;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 check_multiextent
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Checks if the multiextent kernel module is loaded on the VM
+               host. This is required to operate on 2GB sparse vmdk files. If
+               not loaded, an attempt is made to load it.
+
+=cut
+
+sub check_multiextent {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my $vmhost_hostname = $self->data->get_vmhost_hostname();
+       
+       my $list_command = 'vmkload_mod -l | grep multiextent';
+       my ($list_exit_status, $list_output) = 
$self->vmhost_os->execute($list_command);
+       if (!defined($list_output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
determine if multiextent kernel module is loaded on $vmhost_hostname");
+               return;
+       }
+       elsif (grep(/^multiextent/, @$list_output)) {
+               notify($ERRORS{'DEBUG'}, 0, "multiextent kernel module is 
loaded on $vmhost_hostname");
+               return 1;
+       }
+       else {
+               notify($ERRORS{'DEBUG'}, 0, "multiextent kernel module is not 
loaded on $vmhost_hostname, attempting to load it");
+       }
+       
+       my $load_command = 'vmkload_mod multiextent';
+       my ($load_exit_status, $load_output) = 
$self->vmhost_os->execute($load_command);
+       if (!defined($load_output)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to execute command to 
load multiextent kernel module on $vmhost_hostname");
+       }
+       elsif (grep(/loaded successfully/, @$load_output)) {
+               notify($ERRORS{'DEBUG'}, 0, "loaded multiextent kernel module 
on $vmhost_hostname");
+               return 1;
+       }
+       elsif (grep(/already loaded/, @$load_output)) {
+               notify($ERRORS{'DEBUG'}, 0, "multiextent kernel module already 
loaded on $vmhost_hostname");
+               return 1;
+       }
+       else {
+               notify($ERRORS{'WARNING'}, 0, "failed to load multiextent 
kernel module on $vmhost_hostname, exit status: $load_exit_status, output:\n" . 
join("\n", @$load_output));
+       }
+       
+       notify($ERRORS{'CRITICAL'}, 0, "multiextent kernel module is disabled 
on VM host $vmhost_hostname, operations on 2GB sparse virtual disk files will 
fail\n" .
+               '*' x 100 . "\n" .
+               "DO THE FOLLOWING TO FIX THIS PROBLEM:\n" .
+               "Enable the module by running the following command on each 
VMware host: 'vmkload_mod -u multiextent'\n" .
+               "Add a line containing 'vmkload_mod -u multiextent' to 
/etc/rc.local.d/local.sh on each ESXi host\n" .
+               '*' x 100
+       );
+       
+       return 0;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 power_on
 
  Parameters  : $vmx_file_path (optional)
@@ -6499,11 +6617,18 @@ sub get_datastore_info {
                notify($ERRORS{'WARNING'}, 0, "failed to retrieve datastore 
info from " . ref($self->api) . " API object");
                return;
        }
-       else {
-               notify($ERRORS{'DEBUG'}, 0, "retrieved datastore info from VM 
host: " . join(", ", sort keys %$datastore_info));
-               $self->{datastore_info} = $datastore_info;
-               return $datastore_info;
+       
+       for my $datastore_name (keys %$datastore_info) {
+               # URL may be in the format: 
'ds:///vmfs/volumes/51938b70-d1df1a73-459a-3640b58306bb/'
+               # Remove the ds:// from the beginning
+               if ($datastore_info->{$datastore_name}{url}) {
+                       $datastore_info->{$datastore_name}{url} =~ 
s/^.+\/vmfs/\/vmfs/;
+               }
        }
+
+       notify($ERRORS{'DEBUG'}, 0, "retrieved datastore info from VM host: " . 
join(", ", sort keys %$datastore_info));
+       $self->{datastore_info} = $datastore_info;
+       return $datastore_info;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -6798,7 +6923,7 @@ sub _get_datastore_name {
        # Get the datastore information
        my $datastore_info = $self->get_datastore_info() || return;
        my @datastore_normal_paths;
-       
+
        # Loop through the datastores, check if the path begins with the 
datastore path
        for my $datastore_name (keys(%{$datastore_info})) {
                my $datastore_normal_path = 
$datastore_info->{$datastore_name}{normal_path};
@@ -6809,7 +6934,7 @@ sub _get_datastore_name {
                $datastore_normal_path = 
normalize_file_path($datastore_normal_path);
                
                my $datastore_url = $datastore_info->{$datastore_name}{url};
-               $datastore_url = normalize_file_path($datastore_url);
+               $datastore_url = normalize_file_path($datastore_url) || '';
                
                if ($path =~ 
/^($datastore_name|\[$datastore_name\]|$datastore_normal_path|$datastore_url)(\s|\/|$)/)
 {
                        return $datastore_name;

Modified: 
vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm
URL: 
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm?rev=1626057&r1=1626056&r2=1626057&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm 
(original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm 
Thu Sep 18 19:38:54 2014
@@ -685,7 +685,11 @@ sub copy_virtual_disk {
                # Delete the destination directory path previously created
                $self->delete_file($destination_directory_path);
                
-               if ($copy_virtual_disk_fault =~ /No space left/i) {
+               if ($source_disk_type =~ /sparse/i && $copy_virtual_disk_fault 
=~ /FileNotFound/ && $self->is_multiextent_disabled()) {
+                       notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on 
VM host $vmhost_name using CopyVirtualDisk function, likely because multiextent 
kernel module is disabled");
+                       return;
+               }
+               elsif ($copy_virtual_disk_fault =~ /No space left/i) {
                        # Check if the output indicates there is not enough 
space to copy the vmdk
                        # Output will contain:
                        #    Fault string: A general system error occurred: No 
space left on device
@@ -854,6 +858,7 @@ EOF
                return;
        }
        
+       my $source_vm_vmx_path = $source_vm_view->config->files->vmPathName;
        
        # Create the specification for cloning the VM
        my $clone_spec = VirtualMachineCloneSpec->new(
@@ -884,36 +889,54 @@ EOF
        
        # Clone the temporary VM, thus creating a copy of its virtual disk
        notify($ERRORS{'DEBUG'}, 0, "attempting to clone VM: $source_vm_name 
--> $clone_vm_name\nclone VM directory path: '$clone_vm_directory_path'");
+       my $clone_vm;
        my $clone_vm_view;
        eval {
-               my $clone_vm = $source_vm_view->CloneVM(
+               $clone_vm = $source_vm_view->CloneVM(
                        folder => $vm_folder_view,
                        name => $clone_vm_name,
                        spec => $clone_spec
                );
-               if ($clone_vm) {
-                       $clone_vm_view = Vim::get_view(mo_ref => $clone_vm);
-                       notify($ERRORS{'DEBUG'}, 0, "cloned VM: $source_vm_name 
--> $clone_vm_name");
+       };
+       
+       if ($clone_vm) {
+               $clone_vm_view = Vim::get_view(mo_ref => $clone_vm);
+               notify($ERRORS{'DEBUG'}, 0, "cloned VM: $source_vm_name --> 
$clone_vm_name");
+       }
+       else {
+               if (my $fault = $@) {
+                       if ($source_disk_type =~ /sparse/i && $fault =~ 
/FileNotFound/ && $self->is_multiextent_disabled()) {
+                               notify($ERRORS{'WARNING'}, 0, "failed to clone 
VM on VM host $vmhost_name, likely because multiextent kernel module is 
disabled");
+                       }
+                       elsif ($fault =~ /No space left/i) {
+                               # Check if the output indicates there is not 
enough space to copy the vmdk
+                               # Output will contain:
+                               #    Fault string: A general system error 
occurred: No space left on device
+                               #    Fault detail: SystemError
+                               notify($ERRORS{'CRITICAL'}, 0, "failed to clone 
VM on VM host $vmhost_name, no space is left on the destination device: 
'$destination_path'\nerror:\n$fault");
+                       }
+                       else {
+                               notify($ERRORS{'WARNING'}, 0, "failed to clone 
VM on VM host $vmhost_name: '$source_path' --> 
'$destination_path'\nerror:\n$fault");
+                       }
                }
                else {
                        notify($ERRORS{'WARNING'}, 0, "failed to clone VM: 
$source_vm_name --> $clone_vm_name");
-                       return;
                }
-       };
-       if (my $fault = $@) {
-               if ($fault =~ /No space left/i) {
-                       # Check if the output indicates there is not enough 
space to copy the vmdk
-                       # Output will contain:
-                       #    Fault string: A general system error occurred: No 
space left on device
-                       #    Fault detail: SystemError
-                       notify($ERRORS{'CRITICAL'}, 0, "failed to copy vmdk on 
VM host $vmhost_name, no space is left on the destination device: 
'$destination_path'\nerror:\n$fault");
-                       return;
+               
+               # Delete the source VM which could not be cloned
+               if (!$source_vm_vmx_path) {
+                       notify($ERRORS{'WARNING'}, 0, "source VM not deleted, 
unable to determine vmx file path");
+               }
+               elsif ($source_vm_vmx_path !~ /\.vmx$/i) {
+                       notify($ERRORS{'WARNING'}, 0, "source VM not deleted, 
vmPathName does not end with '.vmx': $source_vm_vmx_path");
                }
                else {
-                       notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on 
VM host $vmhost_name: '$source_path' --> '$destination_path'\nerror:\n$fault");
+                       $self->delete_vm($source_vm_vmx_path);
                }
+               
                return;
        }
+       
 
        notify($ERRORS{'DEBUG'}, 0, "deleting source VM: $source_vm_name");
        $self->vm_unregister($source_vm_view);
@@ -1029,9 +1052,13 @@ sub move_virtual_disk {
        if (my $fault = $@) {
                # Get the source file info
                my $source_file_info = 
$self->_get_file_info($source_path)->{$source_path};
+               my $source_disk_type = $source_file_info->{diskType};
                
                # A FileNotFound fault will be generated if the source vmdk 
file exists but there is a problem with it
-               if ($fault->isa('SoapFault') && ref($fault->detail) eq 
'FileNotFound' && defined($source_file_info->{type}) && 
$source_file_info->{type} !~ /vmdisk/i) {
+               if ($source_disk_type =~ /sparse/i && $fault =~ /FileNotFound/ 
&& $self->is_multiextent_disabled()) {
+                       notify($ERRORS{'WARNING'}, 0, "failed to move 
$source_disk_type virtual disk on VM host $vmhost_name, likely because 
multiextent kernel module is disabled");
+               }
+               elsif ($fault->isa('SoapFault') && ref($fault->detail) eq 
'FileNotFound' && defined($source_file_info->{type}) && 
$source_file_info->{type} !~ /vmdisk/i) {
                        notify($ERRORS{'WARNING'}, 0, "failed to move virtual 
disk on VM host $vmhost_name, source file is either not a virtual disk file or 
there is a problem with its configuration, check the 'Extent description' 
section of the vmdk file: '$source_path'\nsource file info:\n" . 
format_data($source_file_info));
                }
                elsif ($fault =~ /No space left/i) {
@@ -1950,7 +1977,13 @@ sub file_exists {
        }
        
        # Get and check the file path argument
-       my $file_path = $self->_get_datastore_path(shift) || return;
+       my $file_path_argument = shift;
+       
+       my $file_path = $self->_get_datastore_path($file_path_argument);
+       if (!$file_path) {
+               notify($ERRORS{'WARNING'}, 0, "unable to determine if file 
exists: $file_path_argument, datastore path could not be determined");
+               return;
+       }
        
        # Check if the path argument is the root of a datastore
        if ($file_path =~ /^\[(.+)\]$/) {
@@ -2673,6 +2706,30 @@ sub _get_cluster_view {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 _get_host_system_views
+
+ Parameters  : none
+ Returns     : array of vSphere SDK HostSystem view object
+ Description : Retrieves an array of vSphere SDK HostSystem view objects. There
+               may be multiple if vCenter is used.
+
+=cut
+
+sub _get_host_system_views {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       return @{$self->{host_system_views}} if $self->{host_system_views};
+       my @host_system_views = @{Vim::find_entity_views(view_type => 
'HostSystem')};
+       $self->{host_system_views} = \@host_system_views;
+       return @host_system_views;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 _get_host_system_view
 
  Parameters  : 
@@ -2698,7 +2755,7 @@ sub _get_host_system_view {
        
        my $vmhost_name = $self->data->get_vmhost_short_name();
        
-       my @host_system_views = @{Vim::find_entity_views(view_type => 
'HostSystem')};
+       my @host_system_views = $self->_get_host_system_views();
        if (!scalar(@host_system_views)) {
                notify($ERRORS{'WARNING'}, 0, "failed to retrieve HostSystem 
views");
                return;
@@ -3749,6 +3806,77 @@ sub add_ethernet_adapter {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 is_multiextent_disabled
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Checks if the multiextent kernel module is loaded on all hosts.
+               This is required to operate on 2GB sparse vmdk files.
+
+=cut
+
+sub is_multiextent_disabled {
+       my $self = shift;
+       if (ref($self) !~ /VCL::Module/i) {
+               notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
+               return;
+       }
+       
+       my @host_system_views = $self->_get_host_system_views();
+       if (!scalar(@host_system_views)) {
+               notify($ERRORS{'WARNING'}, 0, "failed to retrieve HostSystem 
views");
+               return;
+       }
+       
+       my $multiextent_disabled = 0;
+       my $multiextent_info = {};
+       HOST: for my $host_system_view (@host_system_views) {
+               my $host_system_name = $host_system_view->{name};
+               
+               my $kernel_module_system = Vim::get_view(mo_ref => 
$host_system_view->configManager->kernelModuleSystem);
+               if (!$kernel_module_system) {
+                       notify($ERRORS{'WARNING'}, 0, "unable to determine if 
multiextent kernel module is enabled on $host_system_name, kernelModuleSystem 
could not be retrieved");
+                       $multiextent_info->{$host_system_name} = 'unknown';
+                       next;
+               }
+               
+               my $kernel_modules = $kernel_module_system->QueryModules;
+               if (!$kernel_modules) {
+                       notify($ERRORS{'WARNING'}, 0, "unable to determine if 
multiextent kernel module is enabled on $host_system_name, kernelModuleSystem 
failed to query modules");
+                       $multiextent_info->{$host_system_name} = 'unknown';
+                       next;
+               }
+               
+               for my $kernel_module (@$kernel_modules) {
+                       my $kernel_module_name = $kernel_module->name;
+                       if ($kernel_module_name eq 'multiextent') {
+                               $multiextent_info->{$host_system_name} = 
'loaded';
+                               next HOST;
+                       }
+               }
+               
+               $multiextent_info->{$host_system_name} = 'not loaded';
+               $multiextent_disabled = 1;
+       }
+       
+       if ($multiextent_disabled) {
+               notify($ERRORS{'CRITICAL'}, 0, "multiextent kernel module is 
disabled on ESXi hosts, operations on sparse virtual disk files will continue 
to fail:\n" . format_data($multiextent_info) . "\n" .
+                       '*' x 100 . "\n" .
+                       "DO THE FOLLOWING TO FIX THIS PROBLEM:\n" .
+                       "Enable the module by running the following command on 
each ESXi host: 'vmkload_mod -u multiextent'\n" .
+                       "Add a line containing 'vmkload_mod -u multiextent' to 
/etc/rc.local.d/local.sh on each ESXi host\n" .
+                       '*' x 100
+               );
+               return 1;
+       }
+       else {
+               notify($ERRORS{'DEBUG'}, 0, "multiextent kernel module is not 
disabled:\n" . format_data($multiextent_info));
+               return 0;
+       }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 1;
 __END__
 


Reply via email to