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 2010/09/13 20:19:38 UTC

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

Author: arkurth
Date: Mon Sep 13 18:19:38 2010
New Revision: 996628

URL: http://svn.apache.org/viewvc?rev=996628&view=rev
Log:
VCL-298
VMware.pm:
Added code to node_status to check if the OS module's post_load tasks have run if the computer is responding and return 'POST_LOAD' if the computer is up but the tasks have not been run.

Changed how vmx directory is named if the VM is persistent to match the nonpersistent name. This allows a nonpersistent reservation a preloaded VM if that VM was configured for persistent mode.

Added post_maintenance_action subroutine.  This allows VM's to be removed from a host via the website.

vSphere_SDK.pm:
Renamed several subs which are private to the module to begin with an underscore to separate them from other subs.

Added check in copy_virtual_disk if the adapter type argument isn't specified, the source vmdk is configured to use IDE, and the VM host is using ESX. This is overridden to use lsiLogic.

Added code to delete_file to check if the path specified is the root of a datastore.  A critical warning is generated if this occurs and nothing is deleted.

VIM_SSH.pm:
Added code to detect the name of the vim-cmd executable installed on the VM host.  VMware Server 2.x uses vmware-vim-cmd whereas ESXi uses vim-cmd.

Fixed bug in vm_unregister().  It was passing the vmx path rather than the VM ID to vim-cmd.

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

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm?rev=996628&r1=996627&r2=996628&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VIM_SSH.pm Mon Sep 13 18:19:38 2010
@@ -114,6 +114,32 @@ sub initialize {
 		}
 	}
 	
+	# Determine which VIM executable is installed on the VM host
+	my $command = 'vim-cmd ; vmware-vim-cmd';
+	my ($exit_status, $output) = $self->vmhost_os->execute($command);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to determine which VIM executable is available on the VM host");
+		return;
+	}
+	elsif (!grep(/vmsvc/, @$output)) {
+		# String 'vmsvc' does not exist in the output, neither of the commands worked
+		notify($ERRORS{'DEBUG'}, 0, "failed to determine which VIM executable is available on the VM host, output:\n" . join("\n", @$output));
+		return;
+	}
+	elsif (grep(/: vim-cmd:.*not found/i, @$output)) {
+		# Output contains the line: 'vim-cmd: command not found'
+		$self->{vim_cmd} = 'vmware-vim-cmd';
+	}
+	elsif (grep(/: vmware-vim-cmd:.*not found/i, @$output)) {
+		# Output contains the line: 'vmware-vim-cmd: command not found'
+		$self->{vim_cmd} = 'vim-cmd';
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to determine which VIM executable is available on the VM host, output:\n" . join("\n", @$output));
+		return;
+	}
+	notify($ERRORS{'DEBUG'}, 0, "VIM executable available on VM host: $self->{vim_cmd}");
+	
 	notify($ERRORS{'DEBUG'}, 0, ref($self) . " object initialized");
 	return 1;
 }
@@ -124,7 +150,7 @@ sub initialize {
 
  Parameters  : $vim_arguments
  Returns     : array ($exit_status, $output)
- Description : Runs vim-cmd on the VMware host.
+ Description : Runs VIM command on the VMware host.
 
 =cut
 
@@ -143,12 +169,19 @@ sub _run_vim_cmd {
 	
 	my $vmhost_computer_name = $self->vmhost_os->data->get_computer_short_name();
 	
-	my $command = "vim-cmd $vim_arguments";
+	my $command = "$self->{vim_cmd} $vim_arguments";
 	
 	my $attempt = 0;
-	my $attempt_limit = 5;
+	my $attempt_limit = 3;
+	my $wait_seconds = 2;
 	
 	while ($attempt++ < $attempt_limit) {
+		if ($attempt > 1) {
+			# Wait before making next attempt
+			notify($ERRORS{'OK'}, 0, "sleeping $wait_seconds seconds before making attempt $attempt/$attempt_limit");
+			sleep $wait_seconds;
+		}
+		
 		my ($exit_status, $output) = $self->vmhost_os->execute($command);
 		if (!defined($output)) {
 			notify($ERRORS{'WARNING'}, 0, "attempt $attempt/$attempt_limit: failed to run VIM command on VM host $vmhost_computer_name: $command");
@@ -157,19 +190,13 @@ sub _run_vim_cmd {
 			notify($ERRORS{'OK'}, 0, "attempt $attempt/$attempt_limit: failed to connect to VM host $vmhost_computer_name to run command: $command, output:\n" . join("\n", @$output));
 		}
 		else {
-			# vim-cmd command was executed
+			# VIM command command was executed
 			notify($ERRORS{'DEBUG'}, 0, "attempt $attempt/$attempt_limit: executed command on VM host $vmhost_computer_name: $command") if ($attempt > 1);
 			return ($exit_status, $output);
 		}
-		
-		# Wait before making next attempt
-		# Progressively wait longer in case VM host is under heavy load
-		my $wait_seconds = ($attempt * 2);
-		notify($ERRORS{'OK'}, 0, "sleeping $wait_seconds seconds before making next vim-cmd attempt");
-		sleep $wait_seconds;
 	}
 	
-	notify($ERRORS{'WARNING'}, 0, "failed to run vim-cmd on VM host $vmhost_computer_name, made $attempt_limit attempts: $command");
+	notify($ERRORS{'WARNING'}, 0, "failed to run VIM command on VM host $vmhost_computer_name: '$command', made $attempt_limit attempts");
 	return;
 }
 
@@ -201,7 +228,7 @@ sub _get_vm_list {
 	# 496  vm-ark-mcnc-9 (nonpersistent: vmwarewinxp-base234-v12)        [nfs-datastore] vm-ark-mcnc-9_234-v12/vm-ark-mcnc-9_234-v12.vmx   winXPProGuest   vmx-04      
 	# 512  vm-ark-mcnc-10 (nonpersistent: vmwarelinux-centosbase1617-v1) [nfs-datastore] vm-ark-mcnc-10_1617-v1/vm-ark-mcnc-10_1617-v1.vmx otherLinuxGuest vmx-04
 	if (!grep(/Vmid\s+Name/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine VM IDs, unexpected output returned from vim-cmd $vim_cmd_arguments:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unable to determine VM IDs, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -214,7 +241,7 @@ sub _get_vm_list {
 		
 		# Make sure the vmx file path was parsed
 		if (!$vmx_file_path) {
-			notify($ERRORS{'WARNING'}, 0, "unable to determine vmx file path from vim-cmd $vim_cmd_arguments output line: $line");
+			notify($ERRORS{'WARNING'}, 0, "unable to determine vmx file path, VIM command arguments: '$vim_cmd_arguments', output line: $line");
 			return;
 		}
 		
@@ -378,7 +405,7 @@ sub _get_vm_summary {
 	#   overallStatus = "green",
 	# }
 	if (!grep(/vim\.vm\.Summary/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to retrieve VM summary, unexpected output returned from vim-cmd $vim_cmd_arguments:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unable to retrieve VM summary, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -437,7 +464,7 @@ sub _get_datastore_names {
 	#	},
 	# ]
 	if (!grep(/vim\.Datastore\.Summary/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore names, unexpected output returned from vim-cmd $vim_cmd_arguments:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore names, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -500,7 +527,7 @@ sub _get_datastore_normal_path {
 	#	type = "NFS",
 	# }
 	if (!grep(/vim\.Datastore\.Summary/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore normal path, unexpected output returned from vim-cmd $vim_cmd_arguments:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore normal path, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -661,7 +688,7 @@ sub _get_task_id {
 	# ]
 	
 	if (!grep(/ManagedObjectReference/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -676,7 +703,7 @@ sub _get_task_id {
 	
 	# Check if a matching task was found
 	if (!$task_id) {
-		notify($ERRORS{'WARNING'}, 0, "no recent $task_type tasks for VM $vm_id, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "no recent $task_type tasks for VM $vm_id, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -763,7 +790,7 @@ sub _get_task_info {
 		return;
 	}
 	elsif (!grep(/vim.TaskInfo/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, VIM command arguments: '$vim_cmd_arguments' output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -936,7 +963,7 @@ sub get_vm_power_state {
 		return 'suspended';
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to determine power state of $vmx_file_path, 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to determine power state of $vmx_file_path, VIM command arguments: '$vim_cmd_arguments' output:\n" . join("\n", @$output));
 		return;
 	}
 }
@@ -994,7 +1021,7 @@ sub vm_power_on {
 		return 1;
 	}
 	elsif (!grep(/Powering on VM/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power on VM $vmx_file_path, 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power on VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 	
@@ -1069,7 +1096,7 @@ sub vm_power_off {
 		return 1;
 	}
 	elsif (!grep(/Powering off VM/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power off VM $vmx_file_path, 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power off VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output));
 		return;
 	}
 
@@ -1121,6 +1148,7 @@ sub vm_register {
 		return 1;
 	}
 	
+	$vmx_file_path =~ s/\\* /\\ /g;
 	my $vim_cmd_arguments = "solo/registervm \"$vmx_file_path\"";
 	my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments);
 	return if !$output;
@@ -1141,11 +1169,11 @@ sub vm_register {
 	
 	# Check to make sure the VM is registered
 	if ($self->is_vm_registered($vmx_file_path)) {
-		notify($ERRORS{'OK'}, 0, "registered VM: $vmx_file_path");
+		notify($ERRORS{'OK'}, 0, "registered VM: '$vmx_file_path'");
 		return 1;
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "failed to register VM: $vmx_file_path, it does not appear in the list of registered VMs");
+		notify($ERRORS{'WARNING'}, 0, "failed to register VM: '$vmx_file_path'");
 		return;
 	}
 }
@@ -1189,7 +1217,13 @@ sub vm_unregister {
 		}
 	}
 	
-	my $vim_cmd_arguments = "vmsvc/unregister \"$vmx_file_path\"";
+	my $vm_id = $self->_get_vm_id($vmx_file_path);
+	if (!defined($vm_id)) {
+		notify($ERRORS{'OK'}, 0, "unable to unregister VM because VM ID could not be determined for vmx path: $vmx_file_path");
+		return;
+	}
+	
+	my $vim_cmd_arguments = "vmsvc/unregister $vm_id";
 	my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments);
 	return if !$output;
 	
@@ -1201,17 +1235,17 @@ sub vm_unregister {
 	# }
 	
 	if (grep(/fault/i, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vmx_file_path\ncommand: vim-cmd $vim_cmd_arguments\noutput:\n" . join("\n", @$output));
+		notify($ERRORS{'WARNING'}, 0, "failed to unregister VM $vm_id: $vmx_file_path\nVIM command arguments: '$vim_cmd_arguments'\noutput:\n" . join("\n", @$output));
 		return;
 	}
 	
 	# Check to make sure the VM is not registered
 	if (!$self->is_vm_registered($vmx_file_path)) {
-		notify($ERRORS{'OK'}, 0, "unregistered VM: $vmx_file_path");
+		notify($ERRORS{'OK'}, 0, "unregistered VM: $vmx_file_path (ID: $vm_id)");
 		return 1;
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vmx_file_path, it still appears to be registered");
+		notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vmx_file_path  (ID: $vm_id), it still appears to be registered");
 		return;
 	}
 }
@@ -1254,6 +1288,10 @@ sub get_virtual_disk_type {
 	}
 	
 	my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path);
+	if (!$vmdk_directory_datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path");
+		return;
+	}
 	
 	my $vim_cmd_arguments = "hostsvc/datastorebrowser/disksearch \"$vmdk_directory_datastore_path\"";
 	my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments);
@@ -1374,8 +1412,12 @@ sub get_virtual_disk_controller_type {
 	}
 	
 	my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path);
+	if (!$vmdk_directory_datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path");
+		return;
+	}
 	
-	my $vim_cmd_arguments = "hostsvc/datastorebrowser/search 0 \"$vmdk_directory_datastore_path\"";
+	my $vim_cmd_arguments = "hostsvc/datastorebrowser/searchsubfolders 0 \"$vmdk_directory_datastore_path\"";
 	my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments);
 	return if !$output;
 	
@@ -1518,8 +1560,12 @@ sub get_virtual_disk_hardware_version {
 	}
 	
 	my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path);
+	if (!$vmdk_directory_datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path");
+		return;
+	}
 	
-	my $vim_cmd_arguments = "hostsvc/datastorebrowser/search 0 \"$vmdk_directory_datastore_path\"";
+	my $vim_cmd_arguments = "hostsvc/datastorebrowser/searchsubfolders 0 \"$vmdk_directory_datastore_path\"";
 	my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments);
 	return if !$output;
 	
@@ -1670,28 +1716,137 @@ sub copy_virtual_disk {
 		return;
 	}
 	
-	# Run vmkfstools to copy the virtual disk
-	my $command = "vmkfstools -i \"$source_path\" \"$destination_path\" -d $destination_disk_type";
-	notify($ERRORS{'DEBUG'}, 0, "attempting to execute command on VM host: $command");
-	my ($exit_status, $output) = $self->vmhost_os->execute($command);
+	# vmware-vdiskmanager disk types:
+	# 0 - single growable virtual disk, no separate descriptor file (copy time: 1:04)
+	#     createType="monolithicSparse"
+	#     4.2G disk_0.vmdk
+	#     4.2G total
+	# 1 - growable virtual disk split in 2GB files (copy time: 1:08)
+	#     createType="twoGbMaxExtentSparse"
+	#     691  disk_1.vmdk
+	#     2.0G disk_1-s001.vmdk
+	#     904M disk_1-s002.vmdk
+	#     16M  disk_1-s003.vmdk
+	#     203M disk_1-s004.vmdk
+	#     398M disk_1-s005.vmdk
+	#     623M disk_1-s006.vmdk
+	#     21M  disk_1-s007.vmdk
+	#     128K disk_1-s008.vmdk
+	#     4.2G total
+	# 2 - preallocated virtual disk (copy time: 12:33)
+	#     createType="monolithicFlat"
+	#     429 disk_2.vmdk
+	#     14G disk_2-flat.vmdk
+	#     15G total
+	# 3 - preallocated virtual disk split in 2GB files (copy time: 4:06)
+	#     createType="twoGbMaxExtentFlat"
+	#     688 disk_3.vmdk
+	#     2.0G disk_3-f001.vmdk
+	#     2.0G disk_3-f002.vmdk
+	#     2.0G disk_3-f003.vmdk
+	#     2.0G disk_3-f004.vmdk
+	#     2.0G disk_3-f005.vmdk
+	#     2.0G disk_3-f006.vmdk
+	#     2.0G disk_3-f007.vmdk
+	#     1.8M disk_3-f008.vmdk
+	#     15G total
+	# 4 : preallocated ESX-type virtual disk (copy time: 10:00)
+	#     createType="vmfs"
+	#     419 disk_4.vmdk
+	#     14G disk_4-flat.vmdk
+	#     15G total
+	# 5 : compressed disk optimized for streaming (copy time: 3:21)
+	#     createType="streamOptimized"
+	#     2.5G disk_5.vmdk
+	#     2.5G total
+	my $vdisk_type;
+	if ($destination_disk_type =~ /thin/i) {
+		$vdisk_type = 0;
+	}
+	elsif ($destination_disk_type =~ /2gbsparse/i) {
+		$vdisk_type = 1;
+	}
+	else {
+		$vdisk_type = 4;
+	}
 	
-	# Expected output:
-	# Destination disk format: VMFS thin-provisioned
-	# Cloning disk '/vmfs/volumes/nfs-datastore/vmwarewinxp-base234-v12/vmwarewinxp-base234-v12.vmdk'...
-	# Clone: 0% done.Clone: 1% done. ... Clone: 98% done.Clone: 99% done.Clone: 100% done.
-
-	if (!defined($output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run command on VM host: $command");
-		return;
+	my $success = 0;
+	my $start_time;
+	my $end_time;
+	
+	# Try vmware-vdiskmanager
+	notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk using 'vmware-vdiskmanager', disk type: $destination_disk_type ($vdisk_type)");
+	my $vdisk_command = "vmware-vdiskmanager -r \"$source_path\" -t $vdisk_type \"$destination_path\"";
+	$start_time = time;
+	my ($vdisk_exit_status, $vdisk_output) = $self->vmhost_os->execute($vdisk_command);
+	if (!defined($vdisk_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute 'vmware-vdiskmanager' command on VM host to copy vmdk file:\n$vdisk_command");
+	}
+	elsif (grep(/success/i, @$vdisk_output)) {
+		$end_time = time;
+		$success = 1;
+		notify($ERRORS{'OK'}, 0, "copied vmdk file by executing 'vmware-vdiskmanager' on VM host: '$source_path' --> '$destination_path'");
 	}
-	elsif (!grep(/100\% done/, @$output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to copy virtual disk, output does not contain '100% done', command: $command, output:\n" . join("\n", @$output));
-		return;
+	elsif (grep(/not found/i, @$vdisk_output)) {
+		notify($ERRORS{'DEBUG'}, 0, "unable to copy vmdk using 'vmware-vdiskmanager' because the command is not available on VM host");
 	}
 	else {
-		notify($ERRORS{'OK'}, 0, "copied virtual disk: '$source_path' --> '$destination_path', destination disk type: $destination_disk_type");
-		return 1;
+		notify($ERRORS{'WARNING'}, 0, "failed to execute 'vmware-vdiskmanager' on VM host to copy vmdk file:\n$vdisk_command\noutput:\n" . join("\n", @$vdisk_output));
 	}
+	
+	if (!$success){
+		# Run vmkfstools to copy the virtual disk
+		my $vmkfstools_command = "vmkfstools -i \"$source_path\" \"$destination_path\" -d $destination_disk_type";
+		notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk using 'vmkfstools', disk type: $destination_disk_type");
+		$start_time = time;
+		my ($vmkfstools_exit_status, $vmkfstools_output) = $self->vmhost_os->execute($vmkfstools_command);
+		
+		# Expected output:
+		# Destination disk format: VMFS thin-provisioned
+		# Cloning disk '/vmfs/volumes/nfs-datastore/vmwarewinxp-base234-v12/vmwarewinxp-base234-v12.vmdk'...
+		# Clone: 0% done.Clone: 1% done. ... Clone: 98% done.Clone: 99% done.Clone: 100% done.
+	
+		if (!defined($vmkfstools_output)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to run command on VM host: $vmkfstools_command");
+			return;
+		}
+		elsif (!grep(/100\% done/, @$vmkfstools_output)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to copy virtual disk, output does not contain '100% done', command: $vmkfstools_command, output:\n" . join("\n", @$vmkfstools_output));
+			return;
+		}
+		else {
+			$end_time = time;
+			$success = 1;
+			notify($ERRORS{'OK'}, 0, "copied virtual disk by executing 'vmkfstools' on VM host: '$source_path' --> '$destination_path'");
+		}
+	}
+	
+	my $duration_seconds = ($end_time - $start_time);
+	my $minutes = ($duration_seconds / 60);
+	$minutes =~ s/\..*//g;
+	my $seconds = ($duration_seconds - ($minutes * 60));
+	$seconds = "0$seconds" if length($seconds) == 1;
+	
+	my $search_path = $destination_path;
+	$search_path =~ s/\.vmdk$//g;
+	$search_path .= '*.vmdk';
+	my $image_size_bytes = $self->vmhost_os->get_file_size($search_path);
+	
+	my $bytes_per_second = ($image_size_bytes / $duration_seconds);
+	my $bits_per_second = ($image_size_bytes * 8 / $duration_seconds);
+	my $mb_per_second = ($image_size_bytes / $duration_seconds / 1024 / 1024);
+	my $mbit_per_second = ($image_size_bytes * 8 / $duration_seconds / 1024 / 1024);
+	my $gbyte_per_minute = ($image_size_bytes / $duration_seconds / 1024 / 1024 / 1024 * 60);
+	
+	notify($ERRORS{'OK'}, 0, "copied vmdk: '$source_path' --> '$destination_path'\n" .
+			 "total bytes: " . format_number($image_size_bytes) . "\n" .
+			 "time to copy: $minutes:$seconds (" . format_number($duration_seconds) . " seconds)\n" .
+			 "B/s: " . format_number($bytes_per_second) . "\n" .
+			 "b/s: " . format_number($bits_per_second) . "\n" .
+			 "MB/s: " . format_number($mb_per_second, 2) . "\n" .
+			 "Mb/s: " . format_number($mbit_per_second, 2) . "\n" .
+			 "GB/m: " . format_number($gbyte_per_minute, 2));
+	return 1;
 }
 
 #/////////////////////////////////////////////////////////////////////////////

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm?rev=996628&r1=996627&r2=996628&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm Mon Sep 13 18:19:38 2010
@@ -478,17 +478,15 @@ sub capture {
 		notify($ERRORS{'WARNING'}, 0, "failed to complete OS module's pre_capture tasks");
 		return;
 	}
-	
-	# Power off the VM if it's not already off
-	my $vm_power_state = $self->api->get_vm_power_state($vmx_file_path_capture);
-	if (!defined($vm_power_state)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to retrieve the power state of the VM being captured after the OS module's pre_capture tasks were completed");
-		return;
-	}
-	elsif ($vm_power_state !~ /off/i) {
+
+	# Wait for the VM to power off
+	# This OS module may initiate a shutdown and immediately return
+	if (!$self->wait_for_power_off(600)) {
+		notify($ERRORS{'WARNING'}, 0, "VM $computer_name has not powered off after the OS module's pre_capture tasks were completed, waited $shutdown_wait_seconds seconds, powering off VM forcefully");
+		
 		if ($self->api->vm_power_off($vmx_file_path_capture)) {
-			# Sleep for 5 seconds to make sure the power off is complete
-			sleep 5;
+			# Sleep for 10 seconds to make sure the power off is complete
+			sleep 10;
 		}
 		else {
 			notify($ERRORS{'WARNING'}, 0, "failed to power off the VM being captured after the OS module's pre_capture tasks were completed, VM power state: $vm_power_state");
@@ -703,10 +701,13 @@ sub get_active_vmx_file_path {
 =head2 node_status
 
  Parameters  : none
- Returns     : string -- 'READY' or 'RELOAD'
+ Returns     : string -- 'READY', 'POST_LOAD', or 'RELOAD'
  Description : Checks the status of a VM. 'READY' is returned if the VM is
-               registered, powered on, accessible via SSH, and the image loaded
-               matches the requested image.  'RELOAD' is returned otherwise.
+               accessible via SSH, the virtual disk mode is persistent if
+               necessary, the image loaded matches the requested image, and the
+               OS module's post-load tasks have run. 'POST_LOAD' is returned if
+               the VM only needs to have the OS module's post-load tasks run
+               before it is ready. 'RELOAD' is returned otherwise.
 
 =cut
 
@@ -718,25 +719,8 @@ sub node_status {
 	}
 	
 	my $computer_name = $self->data->get_computer_short_name();
-	
-	# Check if the VM is registered
-	if ($self->is_vm_registered()) {
-		notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is registered");
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is not registered, returning 'RELOAD'");
-		return {'status' => 'RELOAD'};
-	}
-	
-	# Check if the VM is powered on
-	my $power_status = $self->power_status();
-	if ($power_status && $power_status =~/on/i) {
-		notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is powered on");
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "VM $computer_name is not powered on, returning 'RELOAD'");
-		return {'status' => 'RELOAD'};
-	}
+	my $image_name = $self->data->get_image_name();
+	my $vm_persistent = $self->is_vm_persistent();
 	
 	# Check if SSH is available
 	if ($self->os->is_ssh_responding()) {
@@ -747,23 +731,82 @@ sub node_status {
 		return {'status' => 'RELOAD'};
 	}
 	
-	# Get the contents of currentimage.txt
+	# Get the contents of currentimage.txt and check if currentimage.txt matches the requested image name
 	my $current_image_name = $self->os->get_current_image_name();
 	if (!$current_image_name) {
 		notify($ERRORS{'DEBUG'}, 0, "unable to retrieve image name from currentimage.txt on VM $computer_name, returning 'RELOAD'");
 		return {'status' => 'RELOAD'};
 	}
-	
-	# Check if currentimage.txt matches the requested image name
-	my $image_name = $self->data->get_image_name();
-	if ($current_image_name eq $image_name) {
-		notify($ERRORS{'DEBUG'}, 0, "currentimage.txt image ($current_image_name) matches requested image name ($image_name) on VM $computer_name, returning 'READY'");
-		return {'status' => 'READY'};
+	elsif ($current_image_name eq $image_name) {
+		notify($ERRORS{'DEBUG'}, 0, "currentimage.txt image ($current_image_name) matches requested image name ($image_name) on VM $computer_name");
 	}
 	else {
 		notify($ERRORS{'DEBUG'}, 0, "currentimage.txt image ($current_image_name) does not match requested image name ($image_name) on VM $computer_name, returning 'RELOAD'");
 		return {'status' => 'RELOAD'};
 	}
+	
+	# If the VM should be persistent, make sure the VM already loaded is persistent
+	if ($vm_persistent) {
+		# Determine the vmx file path actively being used by the VM
+		my $vmx_file_path = $self->get_active_vmx_file_path();
+		if (!$vmx_file_path) {
+			notify($ERRORS{'WARNING'}, 0, "failed to determine the vmx file path actively being used by $computer_name, returning 'RELOAD'");
+			return {'status' => 'RELOAD'};
+		}
+	
+		# Set the vmx file path in this object so that it overrides the default value that would normally be constructed
+		if (!$self->set_vmx_file_path($vmx_file_path)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to set the vmx file to the path that was determined to be in use by the VM: $vmx_file_path, returning 'RELOAD'");
+			return {'status' => 'RELOAD'};
+		}
+		
+		# Get the information contained within the vmx file
+		my $vmx_info = $self->get_vmx_info($vmx_file_path);
+		
+		# Get the vmdk info from the vmx info
+		my @vmdk_identifiers = keys %{$vmx_info->{vmdk}};
+		if (!@vmdk_identifiers) {
+			notify($ERRORS{'WARNING'}, 0, "did not find vmdk file in vmx info ({vmdk} key), returning 'RELOAD':\n" . format_data($vmx_info));
+			return {'status' => 'RELOAD'};
+		}
+		elsif (scalar(@vmdk_identifiers) > 1) {
+			notify($ERRORS{'WARNING'}, 0, "found multiple vmdk files in vmx info ({vmdk} keys), returning 'RELOAD':\n" . format_data($vmx_info));
+			return {'status' => 'RELOAD'};
+		}
+		
+		# Get the vmdk file path from the vmx information
+		my $vmdk_file_path = $vmx_info->{vmdk}{$vmdk_identifiers[0]}{vmdk_file_path};
+		if (!$vmdk_file_path) {
+			notify($ERRORS{'WARNING'}, 0, "vmdk file path was not found in the vmx file info, returning 'RELOAD':\n" . format_data($vmx_info));
+			return {'status' => 'RELOAD'};
+		}
+		notify($ERRORS{'DEBUG'}, 0, "vmdk file path used by the VM already loaded: $vmdk_file_path");
+		
+		# Get the vmdk mode from the vmx information and make sure it's persistent
+		my $vmdk_mode = $vmx_info->{vmdk}{$vmdk_identifiers[0]}{mode};
+		if (!$vmdk_mode) {
+			notify($ERRORS{'WARNING'}, 0, "vmdk mode was not found in the vmx info, returning 'RELOAD':\n" . format_data($vmx_info));
+			return {'status' => 'RELOAD'};
+		}
+		
+		if ($vmdk_mode !~ /^(independent-)?persistent/i) {
+			notify($ERRORS{'OK'}, 0, "mode of vmdk already loaded is not persistent: $vmdk_mode, returning 'RELOAD'");
+			return {'status' => 'RELOAD'};
+		}
+		notify($ERRORS{'DEBUG'}, 0, "mode of vmdk already loaded is valid: $vmdk_mode");
+	}
+	
+	# Check if the OS post_load tasks have run
+	if ($self->os->get_vcld_post_load_status()) {
+		notify($ERRORS{'DEBUG'}, 0, "OS module post_load tasks have been completed on VM $computer_name");
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "OS module post_load tasks have not been completed on VM $computer_name, returning 'POST_LOAD'");
+		return {'status' => 'POST_LOAD'};
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "returning 'READY'");
+	return {'status' => 'READY'};
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -1437,13 +1480,14 @@ sub prepare_vmdk {
 	#}
 	
 	# Check if the VM is persistent, if so, attempt to copy files locally from the nonpersistent directory if they exist
-	if ($is_vm_persistent) {
-		
+	if ($is_vm_persistent && $self->vmhost_os->file_exists($host_vmdk_file_path_nonpersistent)) {
+		# Attempt to use the API's copy_virtual_disk subroutine
 		if ($self->api->can('copy_virtual_disk') && $self->api->copy_virtual_disk($host_vmdk_file_path_nonpersistent, $host_vmdk_file_path)) {
 			notify($ERRORS{'OK'}, 0, "copied vmdk files from nonpersistent to persistent directory on VM host");
 			return $self->check_vmdk_disk_type();
 		}
 		else {
+			# Unable to use the API's copy_virtual_disk subroutine, use VM host OS's copy_file subroutine
 			my $host_vmdk_directory_path_nonpersistent = $self->get_vmdk_directory_path_nonpersistent() || return;
 			
 			my $vmdk_file_prefix = $self->get_vmdk_file_prefix() || return;
@@ -1559,7 +1603,7 @@ sub check_vmdk_disk_type {
 	
 	# Retrieve the virtual disk type from the API object
 	my $virtual_disk_type = $self->api->get_virtual_disk_type($vmdk_file_path);
-	if (!$vmware_product_name) {
+	if (!$virtual_disk_type) {
 		notify($ERRORS{'DEBUG'}, 0, "skipping vmdk disk type check because virtual disk type could not be retrieved from the API object");
 		return 1;
 	}
@@ -1770,7 +1814,8 @@ sub get_vmx_directory_name_persistent {
 		return;
 	}
 	
-	return "$vmx_directory_name_nonpersistent\_$request_id";
+	return $vmx_directory_name_nonpersistent;
+	#return "$vmx_directory_name_nonpersistent\_$request_id";
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -2713,18 +2758,24 @@ sub is_vm_registered {
 	
 	# Get the vmx file path
 	# Use the argument if one was supplied
-	my $vmx_file_path = shift || $self->get_vmx_file_path() || return;
+	my $vmx_file_path = shift || $self->get_vmx_file_path();
+	if (!$vmx_file_path) {
+		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);
 	
 	my @registered_vmx_file_paths = $self->api->get_registered_vms();
-	
-	if (grep { $_ eq $vmx_file_path } @registered_vmx_file_paths) {
-		notify($ERRORS{'DEBUG'}, 0, "VM is registered: $vmx_file_path");
-		return 1;
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "VM is not registered: $vmx_file_path");
-		return 0;
+	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) {
+			notify($ERRORS{'DEBUG'}, 0, "VM is registered: $vmx_file_path");
+			return 1;
+		}
 	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "VM is not registered: '$vmx_file_path', registered paths:\n" . join("\n", @registered_vmx_file_paths));
+	return 0;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -3411,7 +3462,7 @@ sub delete_vm {
 				 disk mode: $vmdk_mode");
 		
 		if ($vmdk_mode =~ /^(independent-)?persistent/) {
-			notify($ERRORS{'DEBUG'}, 0, "mode of vmdk file: $vmdk_mode, existing vmdk directory will be deleted");
+			notify($ERRORS{'DEBUG'}, 0, "mode of vmdk file: $vmdk_mode, attempting to delete vmdk directory: $vmdk_directory_path");
 			$self->vmhost_os->delete_file($vmdk_directory_path) || return;
 		}
 		else {
@@ -3979,6 +4030,55 @@ sub get_vmhost_product_name {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 post_maintenance_action
+
+ Parameters  : none
+ Returns     : boolean
+ Description : 
+
+=cut
+
+sub post_maintenance_action {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_id = $self->data->get_computer_id();
+	my $computer_short_name = $self->data->get_computer_short_name();
+	my $vmhost_hostname = $self->data->get_vmhost_hostname();
+	
+	my $vmx_file_path = $self->get_vmx_file_path();
+	if (!$vmx_file_path) {
+		notify($ERRORS{'WARNING'}, 0, "vmx file path could not be determined");
+		return;
+	}
+	
+	# Delete the existing VM from the VM host
+	if ($self->vmhost_os->file_exists($vmx_file_path)) {
+		if (!$self->delete_vm($vmx_file_path)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to delete VM on VM host $vmhost_hostname: $vmx_file_path");
+			return;
+		}
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "vmx file does not exist on the VM host $vmhost_hostname: $vmx_file_path");
+	}
+	
+	if (switch_vmhost_id($computer_id, 'NULL')) {
+		notify($ERRORS{'OK'}, 0, "set vmhostid to NULL for for VM $computer_short_name");
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to set the vmhostid to NULL for VM $computer_short_name");
+		return;
+	}
+	
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 1;
 __END__
 

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm?rev=996628&r1=996627&r2=996628&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm Mon Sep 13 18:19:38 2010
@@ -57,6 +57,7 @@ use warnings;
 use diagnostics;
 use English qw( -no_match_vars );
 use File::Temp qw( tempdir );
+use List::Util qw( max );
 
 use VCL::utils;
 
@@ -96,7 +97,7 @@ sub get_registered_vms {
 	
 	my @vmx_paths;
 	for my $vm (@vms) {
-		push @vmx_paths, $self->get_normal_path($vm->summary->config->vmPathName) || return;
+		push @vmx_paths, $self->_get_normal_path($vm->summary->config->vmPathName) || return;
 	}
 	
 	notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@vmx_paths) . " registered VMs:\n" . join("\n", @vmx_paths));
@@ -123,7 +124,7 @@ sub vm_register {
 	}
 	
 	# Get the vmx path argument and convert it to a datastore path
-	my $vmx_path = $self->get_datastore_path(shift) || return;
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
 	
 	my $host_view = VIExt::get_host_view(1) || return;
 	my $datacenter = Vim::find_entity_view (view_type => 'Datacenter') || return;
@@ -179,7 +180,7 @@ sub vm_unregister {
 	}
 	
 	# Get the vmx path argument and convert it to a datastore path
-	my $vmx_path = $self->get_datastore_path(shift) || return;
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
 	
 	# Override the die handler
 	local $SIG{__DIE__} = sub{};
@@ -224,7 +225,7 @@ sub vm_power_on {
 	}
 	
 	# Get the vmx path argument and convert it to a datastore path
-	my $vmx_path = $self->get_datastore_path(shift) || return;
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
 	
 	# Override the die handler
 	local $SIG{__DIE__} = sub{};
@@ -274,7 +275,7 @@ sub vm_power_off {
 	}
 	
 	# Get the vmx path argument and convert it to a datastore path
-	my $vmx_path = $self->get_datastore_path(shift) || return;
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
 	
 	# Override the die handler because fileManager may call it
 	local $SIG{__DIE__} = sub{};
@@ -327,7 +328,7 @@ sub get_vm_power_state {
 	}
 	
 	# Get the vmx path argument and convert it to a datastore path
-	my $vmx_path = $self->get_datastore_path(shift) || return;
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
 	
 	# Override the die handler because fileManager may call it
 	local $SIG{__DIE__} = sub{};
@@ -440,8 +441,8 @@ sub copy_virtual_disk {
 	}
 	
 	# Get the source and destination path arguments in the datastore path format
-	my $source_path = $self->get_datastore_path(shift) || return;
-	my $destination_path = $self->get_datastore_path(shift) || return;
+	my $source_path = $self->_get_datastore_path(shift) || return;
+	my $destination_path = $self->_get_datastore_path(shift) || return;
 	
 	# Get the adapter type and disk type arguments if they were specified
 	# If not specified, set the default values
@@ -450,12 +451,21 @@ sub copy_virtual_disk {
 	
 	# If the adapter type was not specified, retrieve it from the source vmdk file
 	if (!$destination_adapter_type) {
+		my $vmware_product_name = $self->get_vmware_product_name();
 		$destination_adapter_type = $self->get_virtual_disk_controller_type($source_path);
 		
-		if (!$destination_adapter_type) {
+		if (!$vmware_product_name) {
+			notify($ERRORS{'WARNING'}, 0, "destination adapter type argument was not specifed and unable to determine VMware product name contains ESX, using lsiLogic");
+			$destination_adapter_type = 'lsiLogic';
+		}
+		elsif (!$destination_adapter_type) {
 			notify($ERRORS{'WARNING'}, 0, "destination adapter type argument was not specifed and unable to retrieve adapter type from source vmdk file: $source_path, using lsiLogic");
 			$destination_adapter_type = 'lsiLogic';
 		}
+		elsif ($vmware_product_name =~ /esx/i && $destination_adapter_type =~ /ide/i) {
+			notify($ERRORS{'OK'}, 0, "VMware product is '$vmware_product_name' and adapter type from source vmdk file is '$destination_adapter_type', using lsiLogic");
+			$destination_adapter_type = 'lsiLogic';
+		}
 	}
 	
 	# Check the adapter type argument, the string must match exactly or the copy will fail
@@ -481,7 +491,7 @@ sub copy_virtual_disk {
 	my $virtual_disk_manager = Vim::get_view(mo_ref => $service_content->{virtualDiskManager}) || return;
 	
 	# Get the destination partent directory path and create the directory
-	my $destination_directory_path = $self->get_parent_directory_datastore_path($destination_path) || return;
+	my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_path) || return;
 	$self->create_directory($destination_directory_path) || return;
 	
 	# Create a virtual disk spec object
@@ -565,8 +575,8 @@ sub move_virtual_disk {
 	}
 	
 	# Get the source path argument in datastore path format
-	my $source_path = $self->get_datastore_path(shift) || return;
-	my $destination_path = $self->get_datastore_path(shift) || return;
+	my $source_path = $self->_get_datastore_path(shift) || return;
+	my $destination_path = $self->_get_datastore_path(shift) || return;
 	
 	# Make sure the source path ends with .vmdk
 	if ($source_path !~ /\.vmdk$/i || $destination_path !~ /\.vmdk$/i) {
@@ -587,7 +597,7 @@ sub move_virtual_disk {
 	}
 	
 	# Get the destination parent directory path, make sure it exists
-	my $destination_parent_directory_path = $self->get_parent_directory_datastore_path($destination_path) || return;
+	my $destination_parent_directory_path = $self->_get_parent_directory_datastore_path($destination_path) || return;
 	$self->create_directory($destination_parent_directory_path) || return;
 	
 	# Check if a virtual disk manager object is available
@@ -680,7 +690,7 @@ sub create_nfs_datastore {
 	my $datastore_device = "$remote_host:$remote_path";
 	
 	# Get the existing datastore summaries
-	my $datastore_summaries = $self->get_datastore_summaries();
+	my $datastore_summaries = $self->_get_datastore_summaries();
 	for my $check_datastore_name (keys(%$datastore_summaries)) {
 		my $check_datastore_type = $datastore_summaries->{$check_datastore_name}{type};
 		
@@ -788,7 +798,7 @@ sub get_virtual_disk_controller_type {
 	}
 	
 	# Get the vmdk file path argument
-	my $vmdk_file_path = $self->get_datastore_path(shift) || return;
+	my $vmdk_file_path = $self->_get_datastore_path(shift) || return;
 	if ($vmdk_file_path !~ /\.vmdk$/) {
 		notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path");
 		return;
@@ -823,7 +833,7 @@ sub get_virtual_disk_controller_type {
 		$return_controller_type = $controller_type;
 	}
 	
-	notify($ERRORS{'DEBUG'}, 0, "retrieved controllerType value from vmdk file info: $return_controller_type($controller_type)");
+	notify($ERRORS{'DEBUG'}, 0, "retrieved controllerType value from vmdk file info: $return_controller_type ($controller_type)");
 	return $return_controller_type;
 }
 
@@ -852,7 +862,7 @@ sub get_virtual_disk_type {
 	}
 	
 	# Get the vmdk file path argument
-	my $vmdk_file_path = $self->get_datastore_path(shift) || return;
+	my $vmdk_file_path = $self->_get_datastore_path(shift) || return;
 	if ($vmdk_file_path !~ /\.vmdk$/) {
 		notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path");
 		return;
@@ -899,7 +909,7 @@ sub get_virtual_disk_hardware_version {
 	}
 	
 	# Get the vmdk file path argument
-	my $vmdk_file_path = $self->get_datastore_path(shift) || return;
+	my $vmdk_file_path = $self->_get_datastore_path(shift) || return;
 	if ($vmdk_file_path !~ /\.vmdk$/) {
 		notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path");
 		return;
@@ -1105,7 +1115,7 @@ sub create_directory {
 	}
 	
 	# Get and check the directory path argument
-	my $directory_path = $self->get_datastore_path(shift) || return;
+	my $directory_path = $self->_get_datastore_path(shift) || return;
 	
 	# Check if the directory already exists
 	return 1 if $self->file_exists($directory_path);
@@ -1160,7 +1170,24 @@ sub delete_file {
 	}
 	
 	# Get and check the file path argument
-	my $path = $self->get_datastore_path(shift) || return;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
+	
+	my $datastore_path = $self->_get_datastore_path($path_argument);
+	if (!$datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "failed to convert path argument to datastore path: $path_argument");
+		return;
+	}
+	
+	# Sanity check, make sure the file path argument is not the root of a datastore
+	# Otherwise everything in the datastore would be deleted
+	if ($datastore_path =~ /^\[.+\]$/) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called with the file path argument pointing to the root of a datastore, this would cause all datastore contents to be deleted\nfile path argument: '$path_argument'\ndatastore path: '$datastore_path'");
+		return;
+	}
 	
 	# Get a fileManager object
 	my $service_content = Vim::get_service_content() || return;
@@ -1170,19 +1197,20 @@ sub delete_file {
 	local $SIG{__DIE__} = sub{};
 
 	# Attempt to delete the file
-	eval { $file_manager->DeleteDatastoreFile(name => $path); };
+	notify($ERRORS{'OK'}, 0, "attempting to delete file: $datastore_path");
+	eval { $file_manager->DeleteDatastoreFile(name => $datastore_path); };
 	if ($@) {
 		if ($@->isa('SoapFault') && ref($@->detail) eq 'FileNotFound') {
-			notify($ERRORS{'DEBUG'}, 0, "file does not exist: $path");
+			notify($ERRORS{'DEBUG'}, 0, "file does not exist: $datastore_path");
 			return 1;
 		}
 		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to delete file: $path, error:\n$@");
+			notify($ERRORS{'WARNING'}, 0, "failed to delete file: $datastore_path, error:\n$@");
 			return;
 		}
 	}
 	else {
-		notify($ERRORS{'OK'}, 0, "file was deleted: $path");
+		notify($ERRORS{'OK'}, 0, "deleted file: $datastore_path");
 		return 1;
 	}
 }
@@ -1207,14 +1235,14 @@ sub copy_file {
 	}
 	
 	# Get and check the file path arguments
-	my $source_file_path = $self->get_datastore_path(shift) || return;
-	my $destination_file_path = $self->get_datastore_path(shift) || return;
+	my $source_file_path = $self->_get_datastore_path(shift) || return;
+	my $destination_file_path = $self->_get_datastore_path(shift) || return;
 	
 	# Get the VM host name
 	my $computer_node_name = $self->data->get_computer_node_name() || return;
 	
 	# Get the destination directory path and create the directory if it doesn't exit
-	my $destination_directory_path = $self->get_parent_directory_datastore_path($destination_file_path) || return;
+	my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return;
 	$self->create_directory($destination_directory_path) || return;
 	
 	# Get a fileManager object
@@ -1274,8 +1302,8 @@ sub copy_file_to {
 	}
 	
 	# Get the source and destination arguments
-	my $source_file_path = $self->get_normal_path(shift) || return;
-	my $destination_file_path = $self->get_datastore_path(shift) || return;
+	my $source_file_path = normalize_file_path(shift) || return;
+	my $destination_file_path = $self->_get_datastore_path(shift) || return;
 	
 	# Make sure the source file exists on the management node
 	if (!-f $source_file_path) {
@@ -1284,7 +1312,7 @@ sub copy_file_to {
 	}
 	
 	# Make sure the destination directory path exists
-	my $destination_directory_path = $self->get_parent_directory_datastore_path($destination_file_path) || return;
+	my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return;
 	$self->create_directory($destination_directory_path) || return;
 	sleep 2;
 	
@@ -1292,8 +1320,8 @@ sub copy_file_to {
 	my $computer_node_name = $self->data->get_computer_node_name() || return;
 	
 	# Get the destination datastore name and relative datastore path
-	my $destination_datastore_name = $self->get_datastore_name($destination_file_path);
-	my $destination_relative_datastore_path = $self->get_relative_datastore_path($destination_file_path);
+	my $destination_datastore_name = $self->_get_datastore_name($destination_file_path);
+	my $destination_relative_datastore_path = $self->_get_relative_datastore_path($destination_file_path);
 	
 	# Override the die handler
 	local $SIG{__DIE__} = sub{};
@@ -1332,11 +1360,11 @@ sub copy_file_from {
 	}
 	
 	# Get the source and destination arguments
-	my $source_file_path = $self->get_datastore_path(shift) || return;
-	my $destination_file_path = $self->get_normal_path(shift) || return;
+	my $source_file_path = $self->_get_datastore_path(shift) || return;
+	my $destination_file_path = normalize_file_path(shift) || return;
 	
 	# Get the destination directory path and make sure the directory exists
-	my $destination_directory_path = $self->get_parent_directory_normal_path($destination_file_path) || return;
+	my $destination_directory_path = $self->_get_parent_directory_normal_path($destination_file_path) || return;
 	if (!-d $destination_directory_path) {
 		# Attempt to create the directory
 		my $command = "mkdir -p -v \"$destination_directory_path\" 2>&1 && ls -1d \"$destination_directory_path\"";
@@ -1365,10 +1393,10 @@ sub copy_file_from {
 	my $computer_node_name = $self->data->get_computer_node_name() || return;
 	
 	# Get the source datastore name
-	my $source_datastore_name = $self->get_datastore_name($source_file_path) || return;
+	my $source_datastore_name = $self->_get_datastore_name($source_file_path) || return;
 	
 	# Get the source file relative datastore path
-	my $source_file_relative_datastore_path = $self->get_relative_datastore_path($source_file_path) || return;
+	my $source_file_relative_datastore_path = $self->_get_relative_datastore_path($source_file_path) || return;
 	
 	# Override the die handler
 	local $SIG{__DIE__} = sub{};
@@ -1420,7 +1448,7 @@ sub get_file_contents {
 	# Create a temp directory to store the file and construct the temp file path
 	# The temp directory is automatically deleted then this variable goes out of scope
 	my $temp_directory_path = tempdir( CLEANUP => 1 );
-	my $source_file_name = $self->get_file_name($path);
+	my $source_file_name = $self->_get_file_name($path);
 	my $temp_file_path = "$temp_directory_path/$source_file_name";
 	
 	$self->copy_file_from($path, $temp_file_path) || return;
@@ -1465,14 +1493,14 @@ sub move_file {
 	}
 	
 	# Get and check the file path arguments
-	my $source_file_path = $self->get_datastore_path(shift) || return;
-	my $destination_file_path = $self->get_datastore_path(shift) || return;
+	my $source_file_path = $self->_get_datastore_path(shift) || return;
+	my $destination_file_path = $self->_get_datastore_path(shift) || return;
 	
 	# Get the VM host name
 	my $computer_node_name = $self->data->get_computer_node_name() || return;
 	
 	# Get the destination directory path and create the directory if it doesn't exit
-	my $destination_directory_path = $self->get_parent_directory_datastore_path($destination_file_path) || return;
+	my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return;
 	$self->create_directory($destination_directory_path) || return;
 	
 	# Get a fileManager and Datacenter object
@@ -1529,12 +1557,12 @@ sub file_exists {
 	}
 	
 	# Get and check the file path argument
-	my $file_path = $self->get_datastore_path(shift) || return;
+	my $file_path = $self->_get_datastore_path(shift) || return;
 	
 	# Check if the path argument is the root of a datastore
 	if ($file_path =~ /^\[(.+)\]$/) {
 		my $datastore_name = $1;
-		(my @datastore_names = $self->get_datastore_names()) || return;
+		(my @datastore_names = $self->_get_datastore_names()) || return;
 		
 		if (grep(/^$datastore_name$/, @datastore_names)) {
 			notify($ERRORS{'DEBUG'}, 0, "file (datastore root) exists: $file_path");
@@ -1547,8 +1575,8 @@ sub file_exists {
 	}
 	
 	# Take the path apart, get the filename and parent directory path
-	my $base_directory_path = $self->get_parent_directory_datastore_path($file_path) || return;
-	my $file_name = $self->get_file_name($file_path) || return;
+	my $base_directory_path = $self->_get_parent_directory_datastore_path($file_path) || return;
+	my $file_name = $self->_get_file_name($file_path) || return;
 	
 	my $result = $self->find_files($base_directory_path, $file_name);
 	if ($result) {
@@ -1657,7 +1685,7 @@ sub find_files {
 	
 	$search_subdirectories = 1 if !defined($search_subdirectories);
 	
-	$base_directory_path = $self->get_normal_path($base_directory_path) || return;
+	$base_directory_path = $self->_get_normal_path($base_directory_path) || return;
 	
 	# Get the file info
 	my $file_info = $self->get_file_info("$base_directory_path/$search_pattern", $search_subdirectories);
@@ -1670,15 +1698,15 @@ sub find_files {
 	my @file_paths;
 	for my $file_path (keys(%{$file_info})) {
 		# Add the file path to the return array
-		push @file_paths, $self->get_normal_path($file_path);
+		push @file_paths, $self->_get_normal_path($file_path);
 		
 		# vmdk files will have a diskExtents key
 		# The extents must be added to the return array
 		if (defined($file_info->{$file_path}->{diskExtents})) {
 			for my $disk_extent (@{$file_info->{$file_path}->{diskExtents}}) {
 				# Convert the datastore file paths to normal file paths
-				$disk_extent = $self->get_normal_path($disk_extent);
-				push @file_paths, $self->get_normal_path($disk_extent);
+				$disk_extent = $self->_get_normal_path($disk_extent);
+				push @file_paths, $self->_get_normal_path($disk_extent);
 			}
 		}
 	}
@@ -1714,12 +1742,12 @@ sub get_available_space {
 	}
 	
 	# Get the datastore name
-	my $datastore_name = $self->get_datastore_name($path) || return;
+	my $datastore_name = $self->_get_datastore_name($path) || return;
 	
 	my $computer_node_name = $self->data->get_computer_node_name() || return;
 	
 	# Get the datastore summary hash
-	my $datastore_summaries = $self->get_datastore_summaries() || return;
+	my $datastore_summaries = $self->_get_datastore_summaries() || return;
 	
 	my $available_bytes = $datastore_summaries->{$datastore_name}{freeSpace};
 	if (!defined($available_bytes)) {
@@ -1853,20 +1881,20 @@ sub get_file_info {
 	}
 	
 	# Take the path argument apart
-	my $base_directory_path = $self->get_parent_directory_datastore_path($path_argument) || return;
-	my $search_pattern = $self->get_file_name($path_argument) || return;
+	my $base_directory_path = $self->_get_parent_directory_datastore_path($path_argument) || return;
+	my $search_pattern = $self->_get_file_name($path_argument) || return;
 	
 	# Set the default value for $search_subfolders if the argument wasn't passed
 	$search_subfolders = 0 if !$search_subfolders;
 	
 	# Make sure the base directory path is formatted as a datastore path
-	my $base_datastore_path = $self->get_datastore_path($base_directory_path) || return;
+	my $base_datastore_path = $self->_get_datastore_path($base_directory_path) || return;
 	
 	# Extract the datastore name from the base directory path
-	my $datastore_name = $self->get_datastore_name($base_directory_path) || return;
+	my $datastore_name = $self->_get_datastore_name($base_directory_path) || return;
 	
 	# Get a datastore object and host datastore browser object
-	my $datastore = $self->get_datastore_object($datastore_name) || return;
+	my $datastore = $self->_get_datastore_object($datastore_name) || return;
 	my $host_datastore_browser = Vim::get_view(mo_ref => $datastore->browser);
 	
 	# Create HostDatastoreBrowserSearchSpec spec
@@ -1953,11 +1981,11 @@ sub get_file_info {
 		if ($folder->file) {
 			# Retrieve the folder path, format: '[nfs-datastore] vmwarewinxp-base234-v12'
 			my $directory_datastore_path =  $folder->folderPath;
-			my $directory_normal_path = $self->get_normal_path($directory_datastore_path);
+			my $directory_normal_path = $self->_get_normal_path($directory_datastore_path);
 			
 			# Loop through all of the files under the folder
 			foreach my $file (@{$folder->file}) {
-				my $file_path = $self->get_datastore_path("$directory_normal_path/" . $file->path);
+				my $file_path = $self->_get_datastore_path("$directory_normal_path/" . $file->path);
 				
 				# Check the file type
 				if (ref($file) eq 'FolderFileInfo') {
@@ -1977,7 +2005,7 @@ sub get_file_info {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_names
+=head2 _get_datastore_names
 
  Parameters  : none
  Returns     : array
@@ -1986,33 +2014,27 @@ sub get_file_info {
 
 =cut
 
-sub get_datastore_names {
+sub _get_datastore_names {
 	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 host view
-	my $host_view = VIExt::get_host_view(1);
-	
-	# Get an array containing datastore managed object references
-	my @datastore_mo_refs = @{$host_view->datastore};
-	
-	# Loop through the datastore managed object references
-	# Get a datastore view, add the view's summary to the return hash
-	my @datastore_names;
-	for my $datastore_mo_ref (@datastore_mo_refs) {
-		my $datastore_view = Vim::get_view(mo_ref => $datastore_mo_ref);
-		push @datastore_names, $datastore_view->summary->name;
+	# Get the datastore summary information
+	my $datastore_summaries = $self->_get_datastore_summaries();
+	if (!$datastore_summaries) {
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve datastore names, unable to retrieve datastore summary information from the VM host");
+		return;
 	}
 	
-	return @datastore_names;
+	# The datastore names are the hash keys
+	return sort keys %$datastore_summaries;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_object
+=head2 _get_datastore_object
 
  Parameters  : $datastore_name
  Returns     : vSphere SDK datastore object
@@ -2021,7 +2043,7 @@ sub get_datastore_names {
 
 =cut
 
-sub get_datastore_object {
+sub _get_datastore_object {
 	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");
@@ -2056,7 +2078,7 @@ sub get_datastore_object {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_summaries
+=head2 _get_datastore_summaries
 
  Parameters  : none
  Returns     : hash reference
@@ -2064,7 +2086,7 @@ sub get_datastore_object {
                containing the datastore summary information. The keys of the
                hash are the datastore names. Example:
                
-               my $datastore_summaries = $self->get_datastore_summaries();
+               my $datastore_summaries = $self->_get_datastore_summaries();
                $datastore_summaries->{datastore1}{accessible} = '1'
                $datastore_summaries->{datastore1}{capacity} = '31138512896'
                $datastore_summaries->{datastore1}{datastore}{type} = 'Datastore'
@@ -2077,7 +2099,7 @@ sub get_datastore_object {
 
 =cut
 
-sub get_datastore_summaries {
+sub _get_datastore_summaries {
 	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");
@@ -2113,8 +2135,6 @@ sub get_datastore_summaries {
 		$datastore_summaries{$datastore_name} = $datastore_view->summary;
 	}
 	
-	my @datastore_names = sort keys(%datastore_summaries);
-	
 	# Store the data in this object so it doesn't need to be retrieved again
 	$self->{datastore_summaries} = \%datastore_summaries;
    return $self->{datastore_summaries};
@@ -2122,7 +2142,7 @@ sub get_datastore_summaries {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_path
+=head2 _get_datastore_path
 
  Parameters  : $path
  Returns     : string
@@ -2132,7 +2152,7 @@ sub get_datastore_summaries {
 
 =cut
 
-sub get_datastore_path {
+sub _get_datastore_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");
@@ -2140,11 +2160,23 @@ sub get_datastore_path {
 	}
 	
 	# Get the path argument
-	my $path = $self->get_normal_path(shift) || return;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	my $datastore_name = $self->get_datastore_name($path) || return;
-	my $datastore_root_normal_path = $self->get_datastore_root_normal_path($path) || return;
-	my $relative_datastore_path = $self->get_relative_datastore_path($path);
+	my $datastore_name = $self->_get_datastore_name($path_argument);
+	if (!$datastore_name) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore path, failed to determine datastore name: $path_argument");
+		return;
+	}
+	
+	my $relative_datastore_path = $self->_get_relative_datastore_path($path_argument);
+	if (!defined($relative_datastore_path)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore path, failed to determine relative datastore path: $path_argument");
+		return;
+	}
 	
 	if ($relative_datastore_path) {
 		return "[$datastore_name] $relative_datastore_path";
@@ -2156,7 +2188,7 @@ sub get_datastore_path {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_root_normal_path
+=head2 _get_datastore_root_normal_path
 
  Parameters  : $path
  Returns     : string
@@ -2167,52 +2199,50 @@ sub get_datastore_path {
 
 =cut
 
-sub get_datastore_root_normal_path {
+sub _get_datastore_root_normal_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, convert it to a normal path
-	my $path = $self->get_normal_path(shift) || return;
+	# Get the path argument
+	my $path = shift;
+	if (!$path) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	# Get the datastore summary information
-	my $datastore_summaries = $self->get_datastore_summaries();
-	my @datastore_normal_paths;
+	my $datastore_name = $self->_get_datastore_name($path);
+	if (!$datastore_name) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root normal path, unable to determine datastore name: $path");
+		return;
+	}
 	
-	# Loop through the datastores
-	for my $datastore_name (keys(%{$datastore_summaries})) {
-		my $datastore_normal_path = $datastore_summaries->{$datastore_name}{normal_path};
-		
-		# Check if the path begins with the datastore root path
-		if ($path =~ /^$datastore_normal_path/) {
-			return $datastore_normal_path;
-		}
-		else {
-			# Path does not begin with datastore path, add datastore path to array for warning message
-			push @datastore_normal_paths, $datastore_normal_path;
-		}
+	# Get the datastore summary information
+	my $datastore_summaries = $self->_get_datastore_summaries();
+	if (!$datastore_summaries) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root normal path, unable to retrieve datastore summaries");
+		return;
 	}
 	
-	notify($ERRORS{'WARNING'}, 0, "unable to determine datastore root path from path: $path, path does not begin with any of the datastore paths:\n" . join("\n", @datastore_normal_paths));
-	return;
+	return $datastore_summaries->{$datastore_name}{normal_path};
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_normal_path
+=head2 _get_datastore_root_url_path
 
  Parameters  : $path
  Returns     : string
- Description : Converts a datastore path to a normal path. The path returned
-               will never have any trailing slashes or spaces.
-               '[datastore1] folder/file.txt' --> '/vmfs/volumes/datastore1/folder/file.txt'
-               '[datastore1]' --> '/vmfs/volumes/datastore1'
+ Description : Parses the path argument and determines its datastore root path
+               in normal form.
+               '/vmfs/volumes/datastore1/folder/file.txt' --> '/vmfs/volumes/895cdc05-11c0ee8f'
+					'[datastore1] folder/file.txt' --> '/vmfs/volumes/895cdc05-11c0ee8f'
 
 =cut
 
-sub get_normal_path {
+sub _get_datastore_root_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");
@@ -2226,44 +2256,77 @@ sub get_normal_path {
 		return;
 	}
 	
-	# Remove any trailing slashes or spaces
-	$path =~ s/[\/\s]*$//g;
-	
-	# Check if path argument is a normal path - does not contain brackets
-	if ($path !~ /\[/) {
-		return normalize_file_path($path);
-	}
-	
-	# Extract the datastore name from the path
-	my ($datastore_name) = $path =~ /^\[(.+)\]/;
+	my $datastore_name = $self->_get_datastore_name($path);
 	if (!$datastore_name) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine datastore name from path: '$path'");
+		notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root URL path, unable to determine datastore name: $path");
 		return;
 	}
 	
 	# Get the datastore summary information
-	my $datastore_summaries = $self->get_datastore_summaries();
+	my $datastore_summaries = $self->_get_datastore_summaries();
+	if (!$datastore_summaries) {
+		notify($ERRORS{'WARNING'}, 0, "failed to determine datastore root URL path, unable to retrieve datastore summaries");
+		return;
+	}
+	
+	return $datastore_summaries->{$datastore_name}{url};
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _get_normal_path
+
+ Parameters  : $path
+ Returns     : string
+ Description : Converts a datastore path to a normal path. The path returned
+               will never have any trailing slashes or spaces.
+               '[datastore1] folder/file.txt' --> '/vmfs/volumes/datastore1/folder/file.txt'
+               '[datastore1]' --> '/vmfs/volumes/datastore1'
+
+=cut
+
+sub _get_normal_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;
+	}
 	
-	my $datastore_summary = $datastore_summaries->{$datastore_name};
-	if (!$datastore_summary) {
-		notify($ERRORS{'WARNING'}, 0, "datastore summary could not be retrieved for datastore: $datastore_name\n" . format_data($datastore_summaries));
+	# Get the path argument
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
 		return;
 	}
 	
-	my $datastore_root_normal_path = $datastore_summary->{normal_path};
+	if ($path_argument !~ /\[.+\]/ && $path_argument !~ /^\/vmfs\/volumes\//i) {
+		return normalize_file_path($path_argument);
+	}
 	
-	# Replace '[datastore] ' with the datastore root normal path
-	$path =~ s/^\[.+\]\s*/$datastore_root_normal_path\//g;
+	my $datastore_root_normal_path = $self->_get_datastore_root_normal_path($path_argument);
+	if (!$datastore_root_normal_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine normal path, failed to determine datastore root normal path: $path_argument");
+		return;
+	}
 	
-	# Remove any trailing slashes or spaces
-	$path =~ s/[\/\s]*$//g;
+	my $relative_datastore_path = $self->_get_relative_datastore_path($path_argument);
+	if (!defined($relative_datastore_path)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine normal path, failed to determine relative datastore path: $path_argument");
+		return;
+	}
+	
+	if ($relative_datastore_path) {
+		return "$datastore_root_normal_path/$relative_datastore_path";
+	}
+	else {
+		return $datastore_root_normal_path;
+	}
 	
-	return $path;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_datastore_name
+=head2 _get_datastore_name
 
  Parameters  : $path
  Returns     : string
@@ -2273,7 +2336,7 @@ sub get_normal_path {
 
 =cut
 
-sub get_datastore_name {
+sub _get_datastore_name {
 	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");
@@ -2287,36 +2350,32 @@ sub get_datastore_name {
 		return;
 	}
 	
-	# Check if path is a datastore path, datastore name will be in brackets
-	if ($path =~ /^\[(.+)\]/) {
-		return $1;
-	}
-	
-	$path = $self->get_normal_path($path);
+	$path = normalize_file_path($path);
 	
 	# Get the datastore summary information
-	my $datastore_summaries = $self->get_datastore_summaries();
+	my $datastore_summaries = $self->_get_datastore_summaries();
 	my @datastore_normal_paths;
 	
 	# Loop through the datastores, check if the path begins with the datastore path
 	for my $datastore_name (keys(%{$datastore_summaries})) {
 		my $datastore_normal_path = $datastore_summaries->{$datastore_name}{normal_path};
+		my $datastore_url = $datastore_summaries->{$datastore_name}{url};
 		
-		if ($path =~ /^$datastore_normal_path/) {
+		if ($path =~ /^(\[$datastore_name\]|$datastore_normal_path|$datastore_url)(\s|\/|$)/) {
 			return $datastore_name;
 		}
 		
 		# Path does not begin with datastore path, add datastore path to array for warning message
-		push @datastore_normal_paths, $datastore_normal_path;
+		push @datastore_normal_paths, ("'[$datastore_name]'", "'$datastore_normal_path'", "'$datastore_url'");
 	}
 	
-	notify($ERRORS{'WARNING'}, 0, "unable to determine datastore name from path: $path, path does not begin with any of the datastore paths:\n" . join("\n", @datastore_normal_paths));
+	notify($ERRORS{'WARNING'}, 0, "unable to determine datastore name from path: '$path', path does not begin with any of the datastore paths:\n" . join("\n", @datastore_normal_paths));
 	return;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_parent_directory_normal_path
+=head2 _get_parent_directory_normal_path
 
  Parameters  : $path
  Returns     : string
@@ -2326,7 +2385,7 @@ sub get_datastore_name {
 
 =cut
 
-sub get_parent_directory_normal_path {
+sub _get_parent_directory_normal_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");
@@ -2334,20 +2393,37 @@ sub get_parent_directory_normal_path {
 	}
 	
 	# Get the path argument
-	my $path = $self->get_normal_path(shift) || return;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	# Remove the last component of the path - after the last '/'
-	$path =~ s/[^\/\]]*$//g;
+	if ($path_argument !~ /\[.+\]/ && $path_argument !~ /^\/vmfs\/volumes\//i) {
+		# Remove the last component of the path - after the last '/'
+		$path_argument =~ s/[^\/\]]*$//g;
+	
+		return normalize_file_path($path_argument);
+	}
 	
-	# Remove any trailing spaces or slashes
-	$path =~ s/[\/\s]*$//g;
+	my $parent_directory_datastore_path = $self->_get_parent_directory_datastore_path($path_argument);
+	if (!$parent_directory_datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory normal path, parent directory datastore path could not be determined on which the normal path is based: '$path_argument'");
+		return;
+	}
 	
-	return $path;
+	my $parent_directory_normal_path = $self->_get_normal_path($parent_directory_datastore_path);
+	if (!$parent_directory_normal_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory normal path, parent directory datastore path could not be converted to a normal path: '$parent_directory_datastore_path'");
+		return;
+	}
+	
+	return $parent_directory_normal_path;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_parent_directory_datastore_path
+=head2 _get_parent_directory_datastore_path
 
  Parameters  : $path
  Returns     : string
@@ -2357,7 +2433,7 @@ sub get_parent_directory_normal_path {
 
 =cut
 
-sub get_parent_directory_datastore_path {
+sub _get_parent_directory_datastore_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");
@@ -2365,20 +2441,32 @@ sub get_parent_directory_datastore_path 
 	}
 	
 	# Get the path argument
-	my $path = $self->get_datastore_path(shift) || return;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	# Remove the last component of the path - after the last '/'
-	$path =~ s/[^\/\]]*$//g;
+	my $datastore_path = $self->_get_datastore_path($path_argument);
+	if (!$datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory datastore path, path argument could not be converted to a datastore path: '$path_argument'");
+		return;
+	}
+	
+	if ($datastore_path =~ /^\[.+\]$/) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory datastore path, path argument is the root path of a datastore: '$path_argument'");
+		return;
+	}
 	
-	# Remove any trailing spaces or slashes
-	$path =~ s/[\/\s]*$//g;
+	# Remove the last component of the path - after the last '/'
+	$datastore_path =~ s/[^\/\]]*$//g;
 	
-	return $path;
+	return normalize_file_path($datastore_path);
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_file_name
+=head2 _get_file_name
 
  Parameters  : $path
  Returns     : string
@@ -2387,7 +2475,7 @@ sub get_parent_directory_datastore_path 
 
 =cut
 
-sub get_file_name {
+sub _get_file_name {
 	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");
@@ -2395,25 +2483,32 @@ sub get_file_name {
 	}
 	
 	# Get the path argument
-	my $path = $self->get_normal_path(shift) || return;
-	
-	# Remove the last component of the path - after the last '/'
-	my ($file_name) = $path =~ /([^\/]*)$/;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	# Remove slashes or spaces from the beginning and end of file name
-	$file_name =~ s/(^[\/\s]*|[\/\s]*$)//g;
+	my $datastore_path = $self->_get_datastore_path($path_argument);
+	if (!$datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine file name, path argument could not be converted to a datastore path: '$path_argument'");
+		return;
+	}
 	
-	if (!$file_name) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine file name from path: '$path'");
+	if ($datastore_path =~ /^\[.+\]$/) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine file name, path argument is the root path of a datastore: '$path_argument'");
 		return;
 	}
 	
-	return $file_name;
+	# Extract the last component of the path - after the last '/'
+	my ($file_name) = $datastore_path =~ /([^\/\]]+)$/;
+	
+	return normalize_file_path($file_name);
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_file_base_name
+=head2 _get_file_base_name
 
  Parameters  : $path
  Returns     : string
@@ -2423,25 +2518,35 @@ sub get_file_name {
 
 =cut
 
-sub get_file_base_name {
+sub _get_file_base_name {
 	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 file name from the path argument
-	my $file_name = $self->get_file_name(shift) || return;
+	# Get the path argument
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
+	
+	my $file_name = $self->_get_file_name($path_argument);
+	if (!$file_name) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine file base name, file name could not be determined from path argument: '$path_argument'");
+		return;
+	}
 	
-	# Remove the file extension - everything after the last '.' in the file name
-	$file_name =~ s/\.[^\.]*$//;
+	# Remove the file extension - everything before the first '.' in the file name
+	my ($file_base_name) = $file_name =~ /^([^\.]*)/;
 	
-	return $file_name;
+	return $file_base_name;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_relative_datastore_path
+=head2 _get_relative_datastore_path
 
  Parameters  : $path
  Returns     : string
@@ -2451,7 +2556,7 @@ sub get_file_base_name {
 
 =cut
 
-sub get_relative_datastore_path {
+sub _get_relative_datastore_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");
@@ -2459,12 +2564,37 @@ sub get_relative_datastore_path {
 	}
 	
 	# Get the path argument
-	my $path = $self->get_normal_path(shift) || return;
+	my $path_argument = shift;
+	if (!$path_argument) {
+		notify($ERRORS{'WARNING'}, 0, "path argument was not specified");
+		return;
+	}
 	
-	my $datastore_root_normal_path = $self->get_datastore_root_normal_path($path) || return;
+	my $datastore_name = $self->_get_datastore_name($path_argument);
+	if (!$datastore_name) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine relative datastore path, failed to determine datastore name: $path_argument");
+		return;
+	}
+	
+	my $datastore_root_normal_path = $self->_get_datastore_root_normal_path($path_argument);
+	if (!$datastore_root_normal_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine relative datastore path, failed to determine the normal root path for the datastore: $path_argument");
+		return;
+	}
+	
+	my $datastore_root_url_path = $self->_get_datastore_root_url_path($path_argument);
+	if (!$datastore_root_normal_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine relative datastore path, failed to determine the normal root path for the datastore: $path_argument");
+		return;
+	}
+	
+	my ($datastore_path, $relative_datastore_path) = $path_argument =~ /^(\[$datastore_name\]|$datastore_root_normal_path|$datastore_root_url_path)(.*)/;
+	
+	if (!$datastore_path) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine relative datastore path: '$path_argument', path argument does not begin with any of the following:\n'[$datastore_name]'\n'$datastore_root_url_path'\n'$datastore_root_normal_path'");
+		return;
+	}
 	
-	# Extract the section of the path after the datastore path
-	my ($relative_datastore_path) = $path =~ /$datastore_root_normal_path(.*)/g;
 	$relative_datastore_path = '' if !$relative_datastore_path;
 	
 	# Remove slashes or spaces from the beginning and end of the relative datastore path
@@ -2475,6 +2605,124 @@ sub get_relative_datastore_path {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 _check_datastore_paths
+
+ Parameters  : @check_paths (optional)
+ Returns     : boolean
+ Description : Checks each of the vSphere.pm subroutines which parse a file path
+					argument. This subroutine returns false if any subroutine returns
+					undefined. The file paths passed to each subroutine that is
+					checked may be specified as arguments to _check_datastore_paths.
+					If no arguments are specified, several default paths will be
+					checked.
+
+=cut
+
+sub _check_datastore_paths {
+	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;
+	}
+	
+	my @check_paths = @_;
+	
+	# Check to make sure all of the vmdk file path components can be retrieved
+	my $undefined_string = "<undefined>";
+	
+	# Assemble a string of all of the components
+	my $check_paths_string = "====================\n";
+	
+	my @datastore_names = $self->_get_datastore_names();
+	if (!@datastore_names) {
+		notify($ERRORS{'WARNING'}, 0, "datastore names could not be retrieved");
+	}
+	$check_paths_string .= "datastore names:\n" . join("\n", @datastore_names) . "\n";
+	
+	my $datastore_summaries = $self->_get_datastore_summaries();
+	if (!$datastore_summaries) {
+		notify($ERRORS{'WARNING'}, 0, "datastore summary information could not be retrieved");
+		return;
+	}
+	notify($ERRORS{'DEBUG'}, 0, "datastore summary information:\n" . format_data($datastore_summaries));
+	
+	my @check_subroutines = (
+		'_get_datastore_name',
+		'_get_datastore_path',
+		'_get_normal_path',
+		'_get_datastore_root_normal_path',
+		'_get_datastore_root_url_path',
+		'_get_parent_directory_datastore_path',
+		'_get_parent_directory_normal_path',
+		'_get_relative_datastore_path',
+		'_get_file_name',
+		'_get_file_base_name',
+	);
+	
+	my $max_sub_name_length = max (map { length } @check_subroutines);
+	
+	if (!@check_paths) {
+		#for my $datastore_name (sort keys %$datastore_summaries) {
+		#	my $datastore_normal_path = $datastore_summaries->{$datastore_name}{normal_path};
+		#	my $datastore_url_path = $datastore_summaries->{$datastore_name}{url};
+		#	push @check_paths, (
+		#		"[$datastore_name] ",
+		#		"[$datastore_name] /",
+		#		"[$datastore_name] test/test file.txt ",
+		#		"$datastore_normal_path/test dir/test file.txt ",
+		#		"$datastore_normal_path/test dir/ ",
+		#		"$datastore_url_path/test dir/test file.txt ",
+		#		"$datastore_url_path/test dir/ ",
+		#		"$datastore_url_path/test.txt ",
+		#		"[invalid datastore] file.txt",
+		#	);
+		#}
+		
+		push @check_paths, (
+			$self->get_vmx_file_path(),
+			$self->get_vmx_directory_path(),
+			$self->get_vmx_base_directory_path(),
+			$self->get_vmdk_file_path(),
+			$self->get_vmdk_directory_path(),
+			$self->get_vmdk_base_directory_path(),
+			$self->get_vmdk_directory_path_persistent(),
+			$self->get_vmdk_directory_path_nonpersistent(),
+			$self->get_vmdk_file_path_persistent(),
+			$self->get_vmdk_file_path_nonpersistent(),
+		);
+	}
+	
+	for my $check_path (@check_paths) {
+		$check_paths_string .= "----------\n";
+		$check_paths_string .= "'$check_path'\n";
+		
+		for my $check_subroutine (@check_subroutines) {
+			my $result = eval "\$self->$check_subroutine(\$check_path)";
+			
+			$check_paths_string .= "$check_subroutine: ";
+			$check_paths_string .= " " x ($max_sub_name_length - length($check_subroutine));
+			
+			if (defined($result)) {
+				$check_paths_string .= "'$result'\n";
+			}
+			else {
+				$check_paths_string .= "$undefined_string\n";
+			}
+		}
+	}
+	
+	notify($ERRORS{'OK'}, 0, "retrieved datastore path components:\n$check_paths_string");
+	
+	if ($check_paths_string =~ /$undefined_string/) {
+		return;
+	}
+	else {
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 1;
 __END__