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/12/15 21:53:50 UTC

svn commit: r1645748 - in /vcl/trunk/managementnode/lib/VCL: DataStructure.pm Module/OS.pm utils.pm

Author: arkurth
Date: Mon Dec 15 20:53:50 2014
New Revision: 1645748

URL: http://svn.apache.org/r1645748
Log:
VCL-767
Added utils.pm::determine_remote_connection_target. This get passed an IP address or hostname of a target to connect to. It attempts to determine which to use to connect. The preference is now to use the private IP address. If this is not populated in the database, it attempts to resolve the hostname and use the resolved IP address. If all else fails, the original argument is returned.

Added utils.pm::get_computer_current_private_ip_address. This retrieves the current privateIPaddress from the database.

Updated utils.pm::nmap_port, run_ssh_command, and run_scp_command to use the value returned from determine_remote_connection_target.

Updated DataStructure.pm::get_computer_private_ip_address to check the value in the database. The database value is authoritative. DataStructure.pm will get the correct value if updated outside of DataStructure.pm.

Updated OS.pm::execute_new to call determine_remote_connection_target and use the result to make the remote connection.

Modified:
    vcl/trunk/managementnode/lib/VCL/DataStructure.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/lib/VCL/DataStructure.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/DataStructure.pm?rev=1645748&r1=1645747&r2=1645748&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/DataStructure.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/DataStructure.pm Mon Dec 15 20:53:50 2014
@@ -1505,35 +1505,55 @@ sub get_computer_private_ip_address {
 	
 	my $suppress_warning = shift;
 	
+	my $computer_id = $self->get_computer_id();
 	my $computer_hostname = $self->get_computer_hostname();
-	if (!$computer_hostname) {
-		notify($ERRORS{'WARNING'}, 0, "computer hostname is not stored in this DataStructure object");
+	if (!defined($computer_id) || !defined($computer_hostname)) {
+		notify($ERRORS{'WARNING'}, 0, "computer ID and hostname are not stored in this DataStructure object");
 		return;
 	}
 	
-	# Check if this is being called by set_computer_private_ip_address
-	# Don't display log messages if this is the case to avoid confusion
-	my $display_output = 1;
-	if (get_calling_subroutine() =~ /set_computer_private_ip_address/) {
-		$display_output = 0;
-	}
-	
 	# Check if the IP address is already stored
-	my $private_ip_address = $self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress};
-	if ($private_ip_address) {
-		notify($ERRORS{'DEBUG'}, 0, "returning private IP address of $computer_hostname already stored in this DataStructure object: $private_ip_address") if $display_output;
-		return $private_ip_address;
-	}
+	my $data_structure_private_ip_address = $self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress};
+	my $env_private_ip_address = $ENV{computer_private_ip_address}{$computer_id};
 	
-	if ($display_output) {
-		if ($suppress_warning) {
-			notify($ERRORS{'DEBUG'}, 0, "private IP address of $computer_hostname is not set in DataStructure object")
+	# Check if private IP adddress is stored in %ENV and differs from this object's data
+	if ($data_structure_private_ip_address) {
+		if ($env_private_ip_address) {
+			if ($env_private_ip_address =~ /null/i) {
+				notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_hostname ($computer_id) is stored in this object $data_structure_private_ip_address, \%ENV is set to null, deleting private IP address from this object");
+				delete $self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress};
+				return;
+			}
+			elsif ($data_structure_private_ip_address eq $env_private_ip_address) {
+				notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_hostname ($computer_id) stored in this object matches IP address stored in \%ENV: $data_structure_private_ip_address");
+			}
+			else {
+				notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_hostname ($computer_id) stored in this object $data_structure_private_ip_address does not match IP address stored in \%ENV, updating private IP address stored in this object: $env_private_ip_address");
+				$self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress} = $env_private_ip_address;
+			}
+			return $env_private_ip_address;
 		}
 		else {
-			notify($ERRORS{'WARNING'}, 0, "private IP address of $computer_hostname is not set in DataStructure object")
+			notify($ERRORS{'DEBUG'}, 0, "returning private IP address of $computer_hostname ($computer_id) already stored in this DataStructure object: $data_structure_private_ip_address");
+			return $data_structure_private_ip_address;
+		}
+	}
+	else {
+		if ($env_private_ip_address && $env_private_ip_address !~ /null/i) {
+			notify($ERRORS{'DEBUG'}, 0, "private IP address for $computer_hostname ($computer_id) is not stored in this object but is stored in \%ENV, setting private IP address in this object: $env_private_ip_address");
+			$self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress} = $env_private_ip_address;
+			return $env_private_ip_address;
+		}
+		else {
+			if ($suppress_warning) {
+				notify($ERRORS{'DEBUG'}, 0, "private IP address of $computer_hostname ($computer_id) is not set in this object");
+			}
+			else {
+				notify($ERRORS{'WARNING'}, 0, "private IP address of $computer_hostname ($computer_id) is not set in this object")
+			}
+			return;
 		}
 	}
-	return;
 }
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -1579,30 +1599,18 @@ sub set_computer_private_ip_address {
 		return;
 	}
 	
-	my $existing_private_ip_address = $self->get_computer_private_ip_address();
-	
-	if (!$existing_private_ip_address && $private_ip_address_argument =~ /null/i) {
-		notify($ERRORS{'DEBUG'}, 0, "setting private IP address of $computer_hostname to '$private_ip_address_argument' not necessary, it is not set in this DataStructure object");
-		return 1;
-	}
-	elsif ($existing_private_ip_address && $existing_private_ip_address eq $private_ip_address_argument) {
-		notify($ERRORS{'DEBUG'}, 0, "private IP address of $computer_hostname is already set to $private_ip_address_argument");
-		return 1;
+	# Update this DataStructure object
+	if ($private_ip_address_argument =~ /null/i) {
+		delete $self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress};
 	}
 	else {
-		# Update this DataStructure object
-		if ($private_ip_address_argument =~ /null/i) {
-			delete $self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress};
-		}
-		else {
-			$self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress} = $private_ip_address_argument;
-		}
-		
-		# Update the database
-		if (!update_computer_private_ip_address($computer_id, $private_ip_address_argument)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to update private IP address of $computer_hostname to $private_ip_address_argument, unable to update the database");
-			return;
-		}
+		$self->request_data->{reservation}{$self->reservation_id}{computer}{privateIPaddress} = $private_ip_address_argument;
+	}
+	
+	# Update the database
+	if (!update_computer_private_ip_address($computer_id, $private_ip_address_argument)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to update private IP address of $computer_hostname to $private_ip_address_argument, unable to update the database");
+		return;
 	}
 	
 	notify($ERRORS{'DEBUG'}, 0, "private IP address of $computer_hostname set to $private_ip_address_argument");

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=1645748&r1=1645747&r2=1645748&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS.pm Mon Dec 15 20:53:50 2014
@@ -2167,7 +2167,7 @@ sub execute {
 	# Check the argument type
 	if (ref($argument)) {
 		if (ref($argument) eq 'HASH') {
-			notify($ERRORS{'DEBUG'}, 0, "first argument is a hash reference:\n" . format_data($argument));
+			#notify($ERRORS{'DEBUG'}, 0, "first argument is a hash reference:\n" . format_data($argument));
 			
 			$computer_name = $argument->{node} if (!$computer_name);
 			$command = $argument->{command};
@@ -2315,13 +2315,18 @@ sub execute_new {
 		return;
 	}
 	
+	# Determine which string to use as the connection target
+	my $remote_connection_target = determine_remote_connection_target($computer_name);
+	my $computer_string = $computer_name;
+	$computer_string .= " ($remote_connection_target)" if ($remote_connection_target ne $computer_name);
+	
 	$display_output = 0 unless $display_output;
 	$timeout_seconds = 60 unless $timeout_seconds;
 	$max_attempts = 3 unless $max_attempts;
 	$port = 22 unless $port;
 	$user = 'root' unless $user;
 	
-	my $ssh_options = '-o StrictHostKeyChecking=no -o ConnectTimeout=30';
+	my $ssh_options = '-o StrictHostKeyChecking=no -o ConnectTimeout=30 -x';
 	
 	# Figure out which identity key to use
 	# If identity key argument was supplied, it may be a single path or a comma-separated list
@@ -2349,7 +2354,7 @@ sub execute_new {
 		if ($attempt > 0) {
 			$attempt_string = "attempt $attempt/$max_attempts: ";
 			$ssh->close() if $ssh;
-			delete $ENV{net_ssh_expect}{$computer_name};
+			delete $ENV{net_ssh_expect}{$remote_connection_target};
 			
 			notify($ERRORS{'DEBUG'}, 0, $attempt_string . "sleeping for $attempt_delay seconds before making next attempt");
 			sleep $attempt_delay;
@@ -2362,10 +2367,10 @@ sub execute_new {
 		# Use a flag to determine if null should be returned without making another attempt
 		my $return_null;
 		
-		if (!$ENV{net_ssh_expect}{$computer_name}) {
+		if (!$ENV{net_ssh_expect}{$remote_connection_target}) {
 			eval {
 				$ssh = Net::SSH::Expect->new(
-					host => $computer_name,
+					host => $remote_connection_target,
 					user => $user,
 					port => $port,
 					raw_pty => 1,
@@ -2376,18 +2381,20 @@ sub execute_new {
 				
 				if ($ssh) {
 					
-					notify($ERRORS{'DEBUG'}, 0, "created " . ref($ssh) . " object to control $computer_name, SSH options: $ssh_options");
+					notify($ERRORS{'DEBUG'}, 0, "created " . ref($ssh) . " object to control $computer_string, SSH options: $ssh_options");
 				}
 				else {
-					notify($ERRORS{'WARNING'}, 0, "failed to create Net::SSH::Expect object to control $computer_name, $!");
+					notify($ERRORS{'WARNING'}, 0, "failed to create Net::SSH::Expect object to control $computer_string, $!");
 					next ATTEMPT;
 				}
 				
 				if (!$ssh->run_ssh()) {
-					notify($ERRORS{'WARNING'}, 0, ref($ssh) . " object failed to fork SSH process to control $computer_name, $!");
+					notify($ERRORS{'WARNING'}, 0, ref($ssh) . " object failed to fork SSH process to control $computer_string, $!");
 					next ATTEMPT;
 				}
 				
+				sleep_uninterrupted(1);
+				
 				#$ssh->exec("stty -echo");
 				#$ssh->exec("stty raw -echo");
 				
@@ -2400,14 +2407,14 @@ sub execute_new {
 					notify($ERRORS{'DEBUG'}, 0, "SSH initialization output:\n$initialization_output") if ($display_output);
 					if ($initialization_output =~ /password:/i) {
 						if (defined($password)) {
-							notify($ERRORS{'WARNING'}, 0, "$attempt_string unable to connect to $computer_name, SSH is requesting a password but password authentication is not implemented, password is configured, output:\n$initialization_output");
+							notify($ERRORS{'WARNING'}, 0, "$attempt_string unable to connect to $computer_string, SSH is requesting a password but password authentication is not implemented, password is configured, output:\n$initialization_output");
 							
 							# In EVAL block here, 'return' won't return from entire subroutine, set flag
 							$return_null = 1;
 							return;
 						}
 						else {
-							notify($ERRORS{'WARNING'}, 0, "$attempt_string unable to connect to $computer_name, SSH is requesting a password but password authentication is not implemented, password is not configured, output:\n$initialization_output");
+							notify($ERRORS{'WARNING'}, 0, "$attempt_string unable to connect to $computer_string, SSH is requesting a password but password authentication is not implemented, password is not configured, output:\n$initialization_output");
 							$return_null = 1;
 							return;
 						}
@@ -2421,27 +2428,27 @@ sub execute_new {
 			return if ($return_null);
 			if ($EVAL_ERROR) {
 				if ($EVAL_ERROR =~ /^(\w+) at \//) {
-					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$1 error occurred initializing Net::SSH::Expect object for $computer_name") if ($display_output);
+					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$1 error occurred initializing Net::SSH::Expect object for $computer_string") if ($display_output);
 				}
 				else {
-					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$EVAL_ERROR error occurred initializing Net::SSH::Expect object for $computer_name") if ($display_output);
+					notify($ERRORS{'DEBUG'}, 0, $attempt_string . "$EVAL_ERROR error occurred initializing Net::SSH::Expect object for $computer_string") if ($display_output);
 				}
 				next ATTEMPT;
 			}
 		}
 		else {
-			$ssh = $ENV{net_ssh_expect}{$computer_name};
+			$ssh = $ENV{net_ssh_expect}{$remote_connection_target};
 			
 			# Delete the stored SSH object to make sure it isn't saved if the command fails
 			# The SSH object will be added back to %ENV if the command completes successfully
-			delete $ENV{net_ssh_expect}{$computer_name};
+			delete $ENV{net_ssh_expect}{$remote_connection_target};
 		}
 		
 		# Set the timeout
 		$ssh->timeout($timeout_seconds);
 		
 		(my $command_formatted = $command) =~ s/\s+(;|&|&&)\s+/\n$1 /g;
-		notify($ERRORS{'DEBUG'}, 0, $attempt_string . "executing command on $computer_name (timeout: $timeout_seconds seconds):\n$command_formatted") if ($display_output);
+		notify($ERRORS{'DEBUG'}, 0, $attempt_string . "executing command on $computer_string (timeout: $timeout_seconds seconds):\n$command_formatted") if ($display_output);
 		my $command_start_time = time;
 		$ssh->send($command . ' 2>&1 ; echo exitstatus:$?');
 		
@@ -2452,19 +2459,19 @@ sub execute_new {
 		
 		if ($EVAL_ERROR) {
 			if ($ignore_error) {
-				notify($ERRORS{'DEBUG'}, 0, "executed command on $computer_name: '$command', ignoring error, returning null") if ($display_output);
+				notify($ERRORS{'DEBUG'}, 0, "executed command on $computer_string: '$command', ignoring error, returning null") if ($display_output);
 				return;
 			}
 			elsif ($EVAL_ERROR =~ /^(\w+) at \//) {
-				notify($ERRORS{'WARNING'}, 0, $attempt_string . "$1 error occurred executing command on $computer_name: '$command'") if ($display_output);
+				notify($ERRORS{'WARNING'}, 0, $attempt_string . "$1 error occurred executing command on $computer_string: '$command'") if ($display_output);
 			}
 			else {
-				notify($ERRORS{'WARNING'}, 0, $attempt_string . "error occurred executing command on $computer_name: '$command'\nerror: $EVAL_ERROR") if ($display_output);
+				notify($ERRORS{'WARNING'}, 0, $attempt_string . "error occurred executing command on $computer_string: '$command'\nerror: $EVAL_ERROR") if ($display_output);
 			}
 			next ATTEMPT;
 		}
 		elsif (!$ssh_wait_status) {
-			notify($ERRORS{'WARNING'}, 0, $attempt_string . "command timed out after $timeout_seconds seconds on $computer_name: '$command'") if ($display_output);
+			notify($ERRORS{'WARNING'}, 0, $attempt_string . "command timed out after $timeout_seconds seconds on $computer_string: '$command'") if ($display_output);
 			next ATTEMPT;
 		}
 		
@@ -2487,15 +2494,15 @@ sub execute_new {
 		my @output_lines = split(/\n/, $output);
 		map { s/[\r]+//g; } (@output_lines);
 		
-		notify($ERRORS{'OK'}, 0, "executed command on $computer_name: '$command', exit status: $exit_status, output:\n$output") if ($display_output);
+		notify($ERRORS{'OK'}, 0, "executed command on $computer_string: '$command', exit status: $exit_status, output:\n$output") if ($display_output);
 		
 		# Save the SSH object for later use
-		$ENV{net_ssh_expect}{$computer_name} = $ssh;
+		$ENV{net_ssh_expect}{$remote_connection_target} = $ssh;
 		
 		return ($exit_status, \@output_lines);
 	}
 	
-	notify($ERRORS{'WARNING'}, 0, $attempt_string . "failed to execute command on $computer_name: '$command'") if ($display_output);
+	notify($ERRORS{'WARNING'}, 0, $attempt_string . "failed to execute command on $computer_string: '$command'") if ($display_output);
 	return;
 }
 

Modified: vcl/trunk/managementnode/lib/VCL/utils.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1645748&r1=1645747&r2=1645748&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/utils.pm Mon Dec 15 20:53:50 2014
@@ -107,6 +107,7 @@ our @EXPORT = qw(
 	delete_computerloadlog_reservation
 	delete_request
 	delete_variable
+	determine_remote_connection_target
 	escape_file_path
 	format_data
 	format_hash_keys
@@ -123,6 +124,7 @@ our @EXPORT = qw(
 	get_database_table_names
 	get_code_ref_package_name
 	get_code_ref_subroutine_name
+	get_computer_current_private_ip_address
 	get_computer_current_state_name
 	get_computer_grp_members
 	get_computer_ids
@@ -2134,8 +2136,8 @@ sub check_ssh {
 
 =head2 nmap_port
 
- Parameters  : $hostname,n $port
- Returns     : 1 open 0 closed
+ Parameters  : $hostname, $port
+ Returns     : boolean
  Description : use nmap port scanning tool to determine if port is open
 
 =cut
@@ -2152,16 +2154,19 @@ sub nmap_port {
 		return;
 	}
 	
-	my $command = "/usr/bin/nmap $hostname -P0 -p $port -T Aggressive";
-	my ($exit_status, $output) = run_command($command, 1);
+	# Determine which string to use as the connection target
+	my $remote_connection_target = determine_remote_connection_target($hostname);
+	my $hostname_string = $remote_connection_target;
+	$hostname_string .= " ($hostname)" if ($hostname ne $remote_connection_target);
 	
+	my $command = "/usr/bin/nmap $remote_connection_target -P0 -p $port -T Aggressive";
+	my ($exit_status, $output) = run_command($command, 1);
 	if (!defined($output)) {
 		notify($ERRORS{'WARNING'}, 0, "failed to run nmap command on management node: '$command'");
 		return;
 	}
-	
-	if (grep(/(open|filtered)/i, @$output)) {
-		#notify($ERRORS{'DEBUG'}, 0, "port $port is open on $hostname");
+	elsif (grep(/(open|filtered)/i, @$output)) {
+		notify($ERRORS{'DEBUG'}, 0, "port $port is open on $hostname_string");
 		return 1;
 	}
 	elsif (grep(/(nmap:|warning)/i, @$output)) {
@@ -2169,7 +2174,7 @@ sub nmap_port {
 		return;
 	}
 	else {
-		#notify($ERRORS{'DEBUG'}, 0, "port $port is closed on $hostname");
+		notify($ERRORS{'DEBUG'}, 0, "port $port is closed on $hostname_string");
 		return 0;
 	}
 } ## end sub nmap_port
@@ -3999,7 +4004,6 @@ sub run_ssh_command {
 		return VCL::Module::OS::execute_new($node, $command, $output_level, $timeout_seconds, $max_attempts, $port, $user, '', $identity_paths);
 	}
 	
-	# TODO: Add ssh path to config file and set global variable
 	# Locate the path to the ssh binary
 	my $ssh_path;
 	if (-f '/usr/bin/ssh') {
@@ -4018,7 +4022,7 @@ sub run_ssh_command {
 		notify($ERRORS{'WARNING'}, 0, "unable to locate the SSH executable in the usual places");
 		return 0;
 	}
-
+	
 	# Format the identity path string
 	if (defined $identity_paths && length($identity_paths) > 0) {
 		# Add -i to beginning of string
@@ -4044,6 +4048,11 @@ sub run_ssh_command {
 	#	$command = "echo -e \"$octal_string\" | \$SHELL";
 	#	notify($ERRORS{'DEBUG'}, 0, "octal command:\n$command");
 	#}
+	
+	# Determine which string to use as the connection target
+	my $remote_connection_target = determine_remote_connection_target($node);
+	my $node_string = $remote_connection_target;
+	$node_string .= " ($node)" if ($node ne $remote_connection_target);
 
 	# Assemble the SSH command
 	# -i <identity_file>, Selects the file from which the identity (private key) for RSA authentication is read.
@@ -4061,7 +4070,7 @@ sub run_ssh_command {
 	$ssh_command .= "-l $user ";
 	$ssh_command .= "-p $port ";
 	$ssh_command .= "-x ";
-	$ssh_command .= "$node '$command' 2>&1";
+	$ssh_command .= "$remote_connection_target '$command' 2>&1";
 	
 	# Execute the command
 	my $ssh_output = '';
@@ -4088,10 +4097,10 @@ sub run_ssh_command {
 		
 		# Print the SSH command, only display the attempt # if > 1
 		if ($attempts == 1) {
-			notify($ERRORS{'DEBUG'}, 0, "executing SSH command on $node: '$command'") if $output_level;
+			notify($ERRORS{'DEBUG'}, 0, "executing SSH command on $node_string: '$command'") if $output_level;
 		}
 		else {
-			notify($ERRORS{'DEBUG'}, 0, "attempt $attempts/$max_attempts: executing SSH command on $node: '$ssh_command'") if $output_level;
+			notify($ERRORS{'DEBUG'}, 0, "attempt $attempts/$max_attempts: executing SSH command on $node_string: '$ssh_command'") if $output_level;
 		}
 		
 		# Enclose SSH command in an eval block and use alarm to eventually timeout the SSH command if it hangs
@@ -4134,16 +4143,16 @@ sub run_ssh_command {
 			kill_child_processes($PID);
 			
 			if ($max_attempts == 1 || $attempts < $max_attempts) {
-				notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: SSH command timed out after $duration seconds, timeout threshold: $timeout_seconds seconds, command: $node:\n$ssh_command");
+				notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: SSH command timed out after $duration seconds, timeout threshold: $timeout_seconds seconds, command: $node_string:\n$ssh_command");
 			}
 			else {
-				notify($ERRORS{'CRITICAL'}, 0, "attempt $attempts/$max_attempts: SSH command timed out after $duration seconds, timeout threshold: $timeout_seconds seconds, command: $node:\n$ssh_command");
+				notify($ERRORS{'CRITICAL'}, 0, "attempt $attempts/$max_attempts: SSH command timed out after $duration seconds, timeout threshold: $timeout_seconds seconds, command: $node_string:\n$ssh_command");
 				return;
 			}
 			next;
 		}
 		elsif ($EVAL_ERROR) {
-			notify($ERRORS{'CRITICAL'}, 0, "attempt $attempts/$max_attempts: eval error was generated attempting to run SSH command: $node:\n$ssh_command, error: $EVAL_ERROR");
+			notify($ERRORS{'CRITICAL'}, 0, "attempt $attempts/$max_attempts: eval error was generated attempting to run SSH command: $node_string:\n$ssh_command, error: $EVAL_ERROR");
 			next;
 		}
 		
@@ -4193,26 +4202,26 @@ sub run_ssh_command {
 		# ssh exits with the exit status of the remote command or with 255 if an error occurred.
 		# Check for vmware-cmd usage message, it returns 255 if the vmware-cmd usage output is returned
 		if ($ssh_output_formatted =~ /ssh:.*(lost connection|reset by peer|no route to host|connection refused|connection timed out|resource temporarily unavailable|connection reset)/i) {
-			notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: failed to execute SSH command on $node: '$command', exit status: $exit_status, output:\n$ssh_output_formatted") if $output_level;
+			notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: failed to execute SSH command on $node_string: '$command', exit status: $exit_status, output:\n$ssh_output_formatted") if $output_level;
 			next;
 		}
 		elsif ($ssh_output_formatted =~ /(Connection timed out during banner exchange)/i) {
 			$banner_exchange_error_count++;
 			if ($banner_exchange_error_count >= $banner_exchange_error_limit) {
-				notify($ERRORS{'WARNING'}, 0, "failed to execute SSH command on $node, encountered $banner_exchange_error_count banner exchange errors");
+				notify($ERRORS{'WARNING'}, 0, "failed to execute SSH command on $node_string, encountered $banner_exchange_error_count banner exchange errors");
 				return ();
 			}
 			else {
 				# Don't count against attempt limit
 				$attempts--;
 				my $banner_exchange_delay_seconds = ($banner_exchange_error_count * 2);
-				notify($ERRORS{'DEBUG'}, 0, "encountered banner exchange error on $node, sleeping for $banner_exchange_delay_seconds seconds, command:\n$command\noutput:\n$ssh_output") if $output_level;
+				notify($ERRORS{'DEBUG'}, 0, "encountered banner exchange error on $node_string, sleeping for $banner_exchange_delay_seconds seconds, command:\n$command\noutput:\n$ssh_output") if $output_level;
 				sleep $banner_exchange_delay_seconds;
 				next;
 			}
 		}
 		elsif ($exit_status == 255 && $ssh_command !~ /(vmware-cmd|vim-cmd|vmkfstools|vmrun)/i) {
-			notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: failed to execute SSH command on $node: '$command', exit status: $exit_status, SSH exits with the exit status of the remote command or with 255 if an error occurred, output:\n$ssh_output_formatted") if $output_level;
+			notify($ERRORS{'WARNING'}, 0, "attempt $attempts/$max_attempts: failed to execute SSH command on $node_string: '$command', exit status: $exit_status, SSH exits with the exit status of the remote command or with 255 if an error occurred, output:\n$ssh_output_formatted") if $output_level;
 			next;
 		}
 		else {
@@ -4269,6 +4278,24 @@ sub run_scp_command {
 	$path1 =~ s/([^\\]) /$1\\ /g;
 	$path2 =~ s/([^\\]) /$1\\ /g;
 	
+	# Determine which IP address or hostname should be used to connect to the target
+	if ($path1 =~ /^([^:]+):/) {
+		my $node = $1;
+		my $connection_target = determine_remote_connection_target($node);
+		if ($connection_target ne $node) {
+			$path1 =~ s/^$node:/$connection_target:/;
+		}
+	}
+	
+	if ($path2 =~ /^([^:]+):/) {
+		my $node = $1;
+		my $connection_target = determine_remote_connection_target($node);
+		if ($connection_target ne $node) {
+			$path2 =~ s/^$node:/$connection_target:/;
+		}
+	}
+	
+	
 	# Format the identity path string
 	if ($identity_paths) {
 		$identity_paths =~ s/^\s*/-i /;
@@ -4810,10 +4837,10 @@ sub update_lastcheckin {
  Parameters  : $computer_identifier, $private_ip_address
  Returns     : boolean
  Description : Updates the computer.privateIPaddress value of the computer
-               specified by the argument. The value can be set to null by
-               passing 'null' as the argument. The $computer_identifier argument
-               may either be the numeric computer.id value or the
-               computer.hostname value.
+					specified by the argument. The value can be set to null by
+					passing 'null' as the argument. The $computer_identifier argument
+					may either be the numeric computer.id value or the
+					computer.hostname value.
 
 =cut
 
@@ -4829,6 +4856,25 @@ sub update_computer_private_ip_address {
 		return;
 	}
 	
+	# Figure out the computer ID based on the $computer_identifier argument
+	my $computer_id;
+	if ($computer_identifier =~ /^\d+$/) {
+		$computer_id = $computer_identifier;
+	}
+	else {
+		$computer_id = get_computer_ids($computer_identifier);
+		if (!$computer_id) {
+			notify($ERRORS{'WARNING'}, 0, "failed to update computer private IP address, computer ID could not be determed from argument: $computer_identifier");
+			return;
+		}
+	}
+	
+	my $computer_string = $computer_identifier;
+	$computer_string .= " ($computer_id)" if ($computer_identifier ne $computer_id);
+	
+	# Delete cached data if previously set
+	delete $ENV{computer_private_ip_address}{$computer_id};
+	
 	my $private_ip_address_text;
 	if ($private_ip_address =~ /null/i) {
 		$private_ip_address_text = 'NULL';
@@ -4845,25 +4891,19 @@ SET
 privateIPaddress = $private_ip_address_text
 WHERE
 computer.deleted != '1'
-AND 
+AND computer.id = '$computer_id'
 EOF
 
-	# If the computer identifier is all digits match it to computer.id
-	# Otherwise, match computer.hostname
-	if ($computer_identifier =~ /^\d+$/) {
-		$update_statement .= "computer.id = \'$computer_identifier\'";
-	}
-	else {
-		$update_statement .= "computer.hostname REGEXP '$computer_identifier(\\\\.|\$)'";
-	}
-
 	# Call the database execute subroutine
 	if (database_execute($update_statement)) {
-		notify($ERRORS{'OK'}, 0, "updated private IP address of computer $computer_identifier in database: $private_ip_address");
+		if ($private_ip_address !~ /null/i) {
+			$ENV{computer_private_ip_address}{$computer_id} = $private_ip_address;
+		}
+		notify($ERRORS{'OK'}, 0, "updated private IP address of computer $computer_string in database: $private_ip_address");
 		return 1;
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "failed to update private IP address of computer $computer_identifier in database: $private_ip_address");
+		notify($ERRORS{'WARNING'}, 0, "failed to update private IP address of computer $computer_string in database: $private_ip_address");
 		return;
 	}
 }
@@ -7555,17 +7595,27 @@ sub get_reservation_request_info {
 =head2 get_computer_ids
 
  Parameters  : $computer_identifier
- Returns     : array
+ Returns     : If called in scalar context: integer
+               If called in array context: array
  Description : Queries the computer table for computers matching the
                $computer_identifier argument. The argument may contain either
-               the computer's hostname or IP address. An array containing the
-               computer IDs is returned.
+               the computer's hostname or IP address. Deleted computers are not
+               considered.
+               
+               If called in scalar context:
+               - returns an integer if a single computer matched the argument
+               - returns null if no computers or multiple computers match the
+                 argument
+               
+               If called in array context:
+               - returns an array containing matching computer IDs
+               - returns an empty array if no computers match the argument
 
 =cut
 
 sub get_computer_ids {
 	my ($computer_identifier) = @_;
-
+	
 	if (!defined($computer_identifier)) {
 		notify($ERRORS{'WARNING'}, $LOGFILE, "computer identifier argument was not supplied");
 		return;
@@ -7573,7 +7623,8 @@ sub get_computer_ids {
 
 	my $select_statement = <<EOF;
 SELECT
-*
+id,
+hostname
 FROM
 computer
 WHERE
@@ -7586,28 +7637,43 @@ AND (
 EOF
 
 	my @selected_rows = database_select($select_statement);
-	if (!@selected_rows) {
-		notify($ERRORS{'DEBUG'}, 0, "no computers were found matching identifier: $computer_identifier");
-		return ();
-	}
-
 	my @computer_ids = map { $_->{id} } @selected_rows;
-	notify($ERRORS{'DEBUG'}, 0, "found computers matching identifier: $computer_identifier, IDs: @computer_ids");
-	return sort @computer_ids;
+	my $computer_id_count = scalar(@computer_ids);
+	
+	if (!$computer_id_count) {
+		notify($ERRORS{'WARNING'}, 0, "no computers were found matching argument: $computer_identifier");
+		return;
+	}
+	
+	if (wantarray) {
+		notify($ERRORS{'DEBUG'}, 0, $computer_id_count . " computer" . ($computer_id_count == 1 ? ' was' : 's were') . " found matching argument: $computer_identifier, returning computer ID" . ($computer_id_count == 1 ? '' : 's') . ": " . join(", ", @computer_ids));
+		return @computer_ids;
+	}
+	else {
+		if ($computer_id_count > 1) {
+			notify($ERRORS{'WARNING'}, 0, "multiple computers were found matching argument: $computer_identifier\n" . format_data(\@selected_rows));
+			return;
+		}
+		else {
+			my $computer_id = $computer_ids[0];
+			#notify($ERRORS{'DEBUG'}, 0, "computer was found matching argument $computer_identifier, returning computer ID: $computer_id");
+			return $computer_id;
+		}
+	}
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 insert_request
 
- Parameters  : $managementnode_id, $request_state_name, $request_laststate_name, $end_minutes_in_future, $user_unityid, $computer_id, $image_id, $imagerevision_id
+ Parameters  : $managementnode_id, $request_state_name, $request_laststate_name, $end_minutes_in_future, $user_unityid, $computer_identifier, $image_id, $imagerevision_id
  Returns     : 1 if successful, 0 if failed
  Description :
 
 =cut
 
 sub insert_request {
-	my ($managementnode_id, $request_state_name, $request_laststate_name, $request_logid, $user_unityid, $computer_id, $image_id, $imagerevision_id, $start_minutes_in_future, $end_minutes_in_future) = @_;
+	my ($managementnode_id, $request_state_name, $request_laststate_name, $request_logid, $user_unityid, $computer_identifier, $image_id, $imagerevision_id, $start_minutes_in_future, $end_minutes_in_future) = @_;
 
 
 	if (!$request_state_name) {
@@ -7623,8 +7689,8 @@ sub insert_request {
 		return 0;
 	}
 
-	if (!$computer_id) {
-		notify($ERRORS{'WARNING'}, 0, "missing mandatory reservation key: computer_id");
+	if (!$computer_identifier) {
+		notify($ERRORS{'WARNING'}, 0, "missing mandatory reservation key: computer_identifier");
 		return 0;
 	}
 	if (!$image_id) {
@@ -7640,15 +7706,18 @@ sub insert_request {
 		return 0;
 	}
 	
-	if ($computer_id !~ /^\d+$/) {
-		my @computer_ids = get_computer_ids($computer_id);
-		if (scalar(@computer_ids) != 1) {
-			notify($ERRORS{'WARNING'}, 0, "computer ID argument is not numeric and computer ID could not be determined");
+	my $computer_id;
+	if ($computer_identifier =~ /^\d+$/) {
+		$computer_id = $computer_identifier;
+	}
+	else {
+		$computer_id = get_computer_ids($computer_identifier);
+		if (!$computer_id) {
+			notify($ERRORS{'WARNING'}, 0, "failed to insert request, computer ID could not be determined from argument: $computer_identifier");
 			return;
 		}
-		$computer_id = $computer_ids[0];
 	}
-
+	
 	my $insert_request_statment = "
 	INSERT INTO
 	request
@@ -8762,7 +8831,7 @@ sub read_file_to_array {
 
 =head2 is_valid_ip_address
 
- Parameters  : IP address string
+ Parameters  : $ip_address, $display_output (optional)
  Returns     : If valid: true
                If not valid: false
  Description : Determines if the argument is a valid IP address.
@@ -8770,34 +8839,35 @@ sub read_file_to_array {
 =cut
 
 sub is_valid_ip_address {
-	my $ip_address = shift;
+	my ($ip_address, $display_output) = @_;
 	if (!$ip_address) {
 		notify($ERRORS{'WARNING'}, 0, "IP address argument was not specified");
 		return;
 	}
+	$display_output = 1 unless defined($display_output);
 	
 	# Split up the IP address being checked into its octets
 	my @octets = split(/\./, $ip_address);
 	
 	# Make sure 4 octets were found
 	if (scalar @octets != 4) {
-		notify($ERRORS{'DEBUG'}, 0, "IP address does not contain 4 octets: $ip_address, octets:\n" . join("\n", @octets));
+		notify($ERRORS{'DEBUG'}, 0, "IP address does not contain 4 octets: $ip_address, octets:\n" . join("\n", @octets)) if $display_output;
 		return 0;
 	}
 	
 	# Make sure address only contains digits
 	if (grep(/\D/, @octets)) {
-		notify($ERRORS{'DEBUG'}, 0, "IP address contains a non-digit: $ip_address");
+		notify($ERRORS{'DEBUG'}, 0, "IP address contains a non-digit: $ip_address") if $display_output;
 		return 0;
 	}
 	
 	# Make sure none of the octets is > 255
 	if ($octets[0] > 255 || $octets[0] > 255 || $octets[0] > 255 || $octets[0] > 255) {
-		notify($ERRORS{'DEBUG'}, 0, "IP address contains an octet > 255: $ip_address");
+		notify($ERRORS{'DEBUG'}, 0, "IP address contains an octet > 255: $ip_address") if $display_output;
 		return 0;
 	}
 	
-	#notify($ERRORS{'DEBUG'}, 0, "IP address is valid: $ip_address");
+	#notify($ERRORS{'DEBUG'}, 0, "IP address is valid: $ip_address") if $display_output;
 	return 1;
 }
 
@@ -12379,6 +12449,84 @@ EOF
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_computer_current_private_ip_address
+
+ Parameters  : $computer_identifier, $no_cache (optional)
+ Returns     : string
+ Description : Retrieves the privateIPaddress value from the computer table for
+               a single computer.
+               
+               Returns the private IP address if:
+               - a single non-deleted or deleted computer matches the argument
+                 (a warning is displayed if the computer is deleted)
+               
+               Returns null if:
+               - no computers or multiple computers match the argument
+
+=cut
+
+sub get_computer_current_private_ip_address {
+	my ($computer_identifier, $no_cache) = @_;
+	if (!$computer_identifier) {
+		notify($ERRORS{'WARNING'}, 0, "computer identifier argument was not supplied");
+		return;
+	}
+	$no_cache = 0 unless defined($no_cache);
+	
+	# Figure out the computer ID based on the $computer_identifier argument
+	my $computer_id;
+	if ($computer_identifier =~ /^\d+$/) {
+		$computer_id = $computer_identifier;
+	}
+	else {
+		$computer_id = get_computer_ids($computer_identifier);
+		if (!$computer_id) {
+			notify($ERRORS{'WARNING'}, 0, "failed to retrieve current private IP address from database, computer ID could not be determed from argument: $computer_identifier");
+			return;
+		}
+	}
+	
+	my $computer_string = $computer_identifier;
+	$computer_string .= " ($computer_id)" if ($computer_identifier ne $computer_id);
+	
+	if (!$no_cache && defined($ENV{computer_private_ip_address}{$computer_id})) {
+		notify($ERRORS{'DEBUG'}, 0, "returning cached private IP address for computer $computer_string: $ENV{computer_private_ip_address}{$computer_id}");
+		return $ENV{computer_private_ip_address}{$computer_id};
+	}
+	
+	my $select_statement = <<EOF;
+SELECT
+computer.hostname,
+computer.privateIPaddress
+FROM
+computer
+WHERE
+computer.id = '$computer_id'
+AND computer.deleted = 0
+EOF
+	
+	my @rows = database_select($select_statement);
+	if (!@rows) {
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve private IP address from database for computer $computer_string");
+		return;
+	}
+	
+	my $row = $rows[0];
+	my $private_ip_address = $row->{privateIPaddress};
+	my $hostname = $row->{hostname};
+	if ($private_ip_address) {
+		$ENV{computer_private_ip_address}{$computer_id} = $private_ip_address;
+		notify($ERRORS{'DEBUG'}, 0, "retrieved private IP address for computer $computer_string from database: $private_ip_address");
+		return $private_ip_address;
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "private IP address for computer $computer_string is set to null in database");
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 hostname_to_ip_address
 
  Parameters  : $hostname
@@ -12396,11 +12544,11 @@ sub hostname_to_ip_address {
 	my $packed_ip_address = gethostbyname($hostname);
 	if (defined $packed_ip_address) {
 		my $ip_address = inet_ntoa($packed_ip_address);
-		#notify($ERRORS{'DEBUG'}, 0, "determined IP address hostname $hostname resolves to: $ip_address");
+		notify($ERRORS{'DEBUG'}, 0, "resolved IP address from hostname $hostname --> $ip_address");
 		return $ip_address;
 	}
 	else {
-		notify($ERRORS{'DEBUG'}, 0, "unable to determined IP address hostname $hostname resolves to");
+		notify($ERRORS{'DEBUG'}, 0, "unable to resolve IP address from hostname: $hostname");
 		return;
 	}
 }
@@ -12423,11 +12571,11 @@ sub ip_address_to_hostname {
 	
 	my $hostname = gethostbyaddr(inet_aton($ip_address), AF_INET);
 	if (defined $hostname) {
-		notify($ERRORS{'DEBUG'}, 0, "determined hostname from IP address $ip_address: $hostname");
+		notify($ERRORS{'DEBUG'}, 0, "resolved hostname from IP address $ip_address --> $hostname");
 		return $hostname;
 	}
 	else {
-		notify($ERRORS{'DEBUG'}, 0, "unable to determine hostname from IP address: $ip_address");
+		notify($ERRORS{'DEBUG'}, 0, "unable to resolve hostname from IP address: $ip_address");
 		return;
 	}
 }
@@ -12611,6 +12759,80 @@ sub ip_address_to_network_address {
 	}
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 determine_remote_connection_target
+
+ Parameters  : $argument, $no_cache (optional)
+ Returns     : string
+ Description : Attempts to determine the string to use when connecting to a
+               remote node. If an IP address is passed, the same IP address is
+               always returned. If a hostname is passed, an attempt is made to
+               retrieve the private IP address of the host. If successful, the
+               private IP address is returned. Otherwise, the hostname argument
+               is returned.
+
+=cut
+
+sub determine_remote_connection_target {
+	my ($argument, $no_cache) = @_;
+	$no_cache = 0 unless defined($no_cache);
+	
+	if (!$no_cache && defined($ENV{remote_connection_target}{$argument})) {
+		return $ENV{remote_connection_target}{$argument};
+	}
+	
+	# If an IP address was passed, use it
+	if (is_valid_ip_address($argument, 0)) {
+		$ENV{remote_connection_target}{$argument} = $argument;
+		notify($ERRORS{'DEBUG'}, 0, "argument is a valid IP address, it will be used as the remote connection target: $ENV{remote_connection_target}{$argument}");
+		return $ENV{remote_connection_target}{$argument};
+	}
+	
+	# Attempt to retrieve the private IP address from the database
+	my $database_private_ip_address = get_computer_current_private_ip_address($argument);
+	if ($database_private_ip_address) {
+		$ENV{remote_connection_target}{$argument} = $database_private_ip_address;
+		notify($ERRORS{'DEBUG'}, 0, "private IP address is set in database for $argument, it will be used as the remote connection target: $ENV{remote_connection_target}{$argument}");
+		return $ENV{remote_connection_target}{$argument};
+	}
+	
+	# Private IP address could not be retrieved from the database or is set to NULL
+	# Try to resolve the argument to an IP address
+	# First make sure it is a valid hostname
+	if (!is_valid_dns_host_name($argument)) {
+		$ENV{remote_connection_target}{$argument} = $argument;
+		notify($ERRORS{'WARNING'}, 0, "failed to reliably determine the remote connection target to use for '$argument', it is not a valid IP address or DNS hostname");
+		return $ENV{remote_connection_target}{$argument};
+	}
+	
+	# Check if the hostname includes a DNS suffix
+	# If so, extract the short hostname and try to resolve that
+	my $resolved_ip_address;
+	if ($argument =~ /^([^\.]+)\./) {
+		my $short_hostname = $1;
+		notify($ERRORS{'DEBUG'}, 0, "hostname $argument contains a DNS suffix, attempting to resolve the hostname without its DNS suffix: $short_hostname");
+		$resolved_ip_address = hostname_to_ip_address($short_hostname);
+	}
+	if (!$resolved_ip_address) {
+		$resolved_ip_address = hostname_to_ip_address($argument);
+	}
+	
+	if ($resolved_ip_address) {
+		# Attempt to set the private IP address in the database
+		update_computer_private_ip_address($argument, $resolved_ip_address);
+		
+		$ENV{remote_connection_target}{$argument} = $resolved_ip_address;
+		notify($ERRORS{'DEBUG'}, 0, "$argument resolves to IP address $resolved_ip_address, it will be used as the remote connection target");
+		return $ENV{remote_connection_target}{$argument};
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to reliably determine the remote connection target to use for '$argument', it is not a valid IP address, a private IP address is not set in the database, and '$argument' does not resolve to an IP address on this management node");
+		$ENV{remote_connection_target}{$argument} = $argument;
+		return $ENV{remote_connection_target}{$argument};
+	}
+}
+
 #/////////////////////////////////////////////////////////////////////////////
 
 1;