You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2014/09/18 21:38:55 UTC

svn commit: r1626057 - in /vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware: VIM_SSH.pm VMware.pm vSphere_SDK.pm

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__