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/09 22:11:38 UTC

svn commit: r1644185 [2/2] - in /vcl/trunk/managementnode/lib/VCL: ./ Module/ Module/OS/ Module/OS/Linux/ Module/OS/Linux/firewall/ Module/OS/Linux/init/

Modified: vcl/trunk/managementnode/lib/VCL/new.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/new.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/new.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/new.pm Tue Dec  9 21:11:37 2014
@@ -698,14 +698,14 @@ sub computer_not_being_used {
 			notify($ERRORS{'WARNING'}, 0, "$computer_short_name is NOT available, its state is $computer_state_name");
 			return 0;
 		}
-
+		
 		# Return 0 if computer state is maintenance and request state name is not vmhostinuse
 		# Allow computers to go from maintenance directly to a vmhost
 		if ($computer_state_name =~ /^(maintenance)$/ && $request_state_name !~ /tovmhostinuse/) {
 			notify($ERRORS{'WARNING'}, 0, "$computer_short_name is NOT available, its state is $computer_state_name");
 			return 0;
 		}
-
+		
 		# Warn if computer state isn't available or reload - except for reinstall requests
 		if ($request_state_name !~ /^(reinstall)$/ && $computer_state_name !~ /^(available|reload)$/) {
 			notify($ERRORS{'WARNING'}, 0, "$computer_short_name state is $computer_state_name, checking if any conflicting reservations are active");
@@ -916,10 +916,10 @@ sub reserve_computer {
 	my $user_emailnotices               = $self->data->get_user_emailnotices();
 	my $user_imtype_name                = $self->data->get_user_imtype_name();
 	my $user_im_id                      = $self->data->get_user_im_id();
-
+	
 	# Needed for computerloadflow	
 	insertloadlog($reservation_id, $computer_id, "addinguser", "Adding user to $computer_short_name");
-
+	
 	# Call OS module's reserve subroutine
 	if (!$self->os->reserve()) {
 		$self->reservation_failed("OS module failed to reserve resources for this reservation");

Modified: vcl/trunk/managementnode/lib/VCL/reclaim.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/reclaim.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/reclaim.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/reclaim.pm Tue Dec  9 21:11:37 2014
@@ -106,30 +106,42 @@ sub process {
 
 	# Remove related fixedIPsr variable, if it exists
 	if ($server_request_id) {
- 		my $variable_name = "fixedIPsr" . $server_request_id;
- 		if(is_variable_set($variable_name)){
-               #Delete from variable table.
-               my $delete_sql_statement = "DELETE variable FROM variable WHERE name = '$variable_name' ";
-               if (database_execute($delete_sql_statement)) {
-         				notify($ERRORS{'DEBUG'}, 0, "Deleted server reservation entry for $variable_name from variable table");
-               }   
-         }   
+		my $variable_name = "fixedIPsr" . $server_request_id;
+		if (is_variable_set($variable_name)){
+			#Delete from variable table.
+			my $delete_sql_statement = "DELETE variable FROM variable WHERE name = '$variable_name' ";
+			if (database_execute($delete_sql_statement)) {
+			notify($ERRORS{'DEBUG'}, 0, "Deleted server reservation entry for $variable_name from variable table");
+			}   
+		}   
 		
-		if($public_ip_configuration =~ /static/i) {
+		if ($public_ip_configuration =~ /static/i) {
 			my $original_IPvalue = "originalIPaddr_" . $server_request_id;
-			if(is_variable_set($original_IPvalue)) {
+			if (is_variable_set($original_IPvalue)) {
 				my $original_Public_IP = get_variable($original_IPvalue);
-				if(update_computer_public_ip_address($computer_id, $original_Public_IP)) {
+				if (update_computer_public_ip_address($computer_id, $original_Public_IP)) {
 					notify($ERRORS{'DEBUG'}, 0, "restored original IP address: $original_Public_IP for $computer_state_name ");
 				}
-            my $delete_sql_statement = "DELETE variable FROM variable WHERE name = '$original_IPvalue' ";
-            if (database_execute($delete_sql_statement)) {
-         		notify($ERRORS{'DEBUG'}, 0, "Deleted server reservation entry for $original_IPvalue from variable table");
-            }   
+				my $delete_sql_statement = "DELETE variable FROM variable WHERE name = '$original_IPvalue' ";
+				if (database_execute($delete_sql_statement)) {
+					notify($ERRORS{'DEBUG'}, 0, "Deleted server reservation entry for $original_IPvalue from variable table");
+				}   
 			}
 		}
 	}
-
+	
+	# Clean up rules on the NAT host if NAT is used
+	if ($self->nathost_os(0)) {
+		my $nathost_hostname = $self->data->get_nathost_hostname();
+		if ($self->nathost_os->firewall()) {
+			if (!$self->nathost_os->firewall->delete_chain('nat', $reservation_id)) {
+				notify($ERRORS{'CRITICAL'}, 0, "failed to delete '$reservation_id' chain from the nat table on NAT host $nathost_hostname");
+			}
+		}
+		else {
+			notify($ERRORS{'CRITICAL'}, 0, "failed to delete '$reservation_id' chain from the nat table on NAT host $nathost_hostname, NAT host OS object is not available");
+		}
+	}
 	
 	# Insert into computerloadlog if request state = timeout
 	if ($request_state_name =~ /timeout|deleted/) {

Modified: vcl/trunk/managementnode/lib/VCL/utils.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1644185&r1=1644184&r2=1644185&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/utils.pm Tue Dec  9 21:11:37 2014
@@ -126,6 +126,7 @@ our @EXPORT = qw(
 	get_computer_grp_members
 	get_computer_ids
 	get_computer_info
+	get_computer_nathost_info
 	get_computer_private_ip_address_info
 	get_computers_controlled_by_mn
 	get_connect_method_info
@@ -157,6 +158,7 @@ our @EXPORT = qw(
 	get_management_node_vmhost_ids
 	get_management_node_vmhost_info
 	get_module_info
+	get_nathost_assigned_public_ports
 	get_next_image_default
 	get_os_info
 	get_production_imagerevision_info
@@ -203,6 +205,7 @@ our @EXPORT = qw(
 	notify_via_IM
 	notify_via_oascript
 	parent_directory_path
+	populate_reservation_natport
 	preplogfile
 	read_file_to_array
 	remove_array_duplicates
@@ -2974,7 +2977,7 @@ sub database_execute {
 
 =head2  get_request_info
 
- Parameters  : $request_id
+ Parameters  : $request_id, $no_cache (optional)
  Returns     : hash
  Description : Retrieves all request/reservation information.
 
@@ -2982,12 +2985,17 @@ sub database_execute {
 
 
 sub get_request_info {
-	my ($request_id) = @_;
+	my ($request_id, $no_cache) = @_;
 	if (!(defined($request_id))) {
 		notify($ERRORS{'WARNING'}, 0, "request ID argument was not specified");
 		return;
 	}
 	
+	# Don't use cached info by default
+	if (!defined($no_cache)) {
+		$no_cache = 1;
+	}
+	
 	# Get a hash ref containing the database column names
 	my $database_table_columns = get_database_table_columns();
 	
@@ -3083,31 +3091,43 @@ EOF
 		
 		# Add the image info to the hash
 		my $image_id = $request_info->{reservation}{$reservation_id}{imageid};
-		my $image_info = get_image_info($image_id, 1);
+		my $image_info = get_image_info($image_id, $no_cache);
 		$request_info->{reservation}{$reservation_id}{image} = $image_info;
 		
 		# Add the imagerevision info to the hash
 		my $imagerevision_id = $request_info->{reservation}{$reservation_id}{imagerevisionid};
-		my $imagerevision_info = get_imagerevision_info($imagerevision_id, 1);
+		my $imagerevision_info = get_imagerevision_info($imagerevision_id, $no_cache);
 		$request_info->{reservation}{$reservation_id}{imagerevision} = $imagerevision_info;
 		
 		# Add the computer info to the hash
 		my $computer_id = $request_info->{reservation}{$reservation_id}{computerid};
-		my $computer_info = get_computer_info($computer_id, 1);
+		my $computer_info = get_computer_info($computer_id, $no_cache);
 		$request_info->{reservation}{$reservation_id}{computer} = $computer_info;
 		
+		# Populate natport table for reservation
+		# Make sure this wasn't called from populate_reservation_natport or else recursive loop will occur
+		my $caller_trace = get_caller_trace(5);
+		if ($caller_trace !~ /populate_reservation_natport/) {
+			my $request_state_name = $request_info->{state}{name};
+			if ($request_state_name =~ /(new|reserved|modified|test)/) {
+				if (!populate_reservation_natport($reservation_id)) {
+					notify($ERRORS{'CRITICAL'}, 0, "failed to populate natport table for reservation");
+				}
+			}
+		}
+		
 		# Add the connect method info to the hash
-		my $connect_method_info = get_connect_method_info($imagerevision_id);
+		my $connect_method_info = get_connect_method_info($imagerevision_id, $no_cache);
 		$request_info->{reservation}{$reservation_id}{connect_methods} = $connect_method_info;
-	
+		
 		# Add the managementnode info to the hash
 		my $management_node_id = $request_info->{reservation}{$reservation_id}{managementnodeid};
-		my $management_node_info = get_management_node_info($management_node_id);
+		my $management_node_info = get_management_node_info($management_node_id, $no_cache);
 		$request_info->{reservation}{$reservation_id}{managementnode} = $management_node_info;
 		
 		# Retrieve the user info and add to the hash
 		my $user_id = $request_info->{userid};
-		my $user_info = get_user_info($user_id, 0, 1);
+		my $user_info = get_user_info($user_id, 0, $no_cache);
 		$request_info->{user} = $user_info;
 		
 		my $imagemeta_root_access = $request_info->{reservation}{$reservation_id}{image}{imagemeta}{rootaccess};
@@ -3899,7 +3919,7 @@ EOF
 	
 	$vmhost_info->{vmprofile}{vmpath} = $vmhost_info->{vmprofile}{datastorepath} if !$vmhost_info->{vmprofile}{vmpath};
 	$vmhost_info->{vmprofile}{virtualdiskpath} = $vmhost_info->{vmprofile}{vmpath} if !$vmhost_info->{vmprofile}{virtualdiskpath};
-	
+
 	notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_id info, computer: $vmhost_info->{computer}{hostname}");
 	$ENV{vmhost_info}{$vmhost_id} = $vmhost_info;
 	return $ENV{vmhost_info}{$vmhost_id};
@@ -6882,8 +6902,11 @@ sub get_computer_info {
 		notify($ERRORS{'WARNING'}, 0, "computer identifier argument was not supplied");
 		return;
 	}
-	
-	return $ENV{computer_info}{$computer_identifier} if (!$no_cache && $ENV{computer_info}{$computer_identifier});
+
+	if (!$no_cache && defined($ENV{computer_info}{$computer_identifier})) {
+		return $ENV{computer_info}{$computer_identifier};
+	}
+	notify($ERRORS{'DEBUG'}, 0, "retrieving info for computer $computer_identifier");
 	
 	# Get a hash ref containing the database column names
 	my $database_table_columns = get_database_table_columns();
@@ -6895,7 +6918,6 @@ sub get_computer_info {
 		'module',
 		'schedule',
 		'platform',
-		'nathost',
 	);
 	
 	# Construct the select statement
@@ -6935,10 +6957,6 @@ ON (
 )
 LEFT JOIN (schedule) ON (schedule.id = computer.scheduleid)
 LEFT JOIN (module AS predictivemodule) ON (predictivemodule.id = computer.predictivemoduleid)
-LEFT JOIN (nathost, nathostcomputermap) ON (
-	nathostcomputermap.computerid = computer.id
-	AND nathostcomputermap.nathostid = nathost.id
-)
 
 WHERE
 computer.deleted != '1'
@@ -6996,6 +7014,8 @@ EOF
 		}
 	}
 	
+	my $computer_id = $computer_info->{id};
+	
 	# Set the short name of the computer based on the hostname
 	my $computer_hostname = $computer_info->{hostname};
 	my ($computer_shortname) = $computer_hostname =~ /^([^\.]+)/;
@@ -7054,7 +7074,12 @@ EOF
 		}
 	}
 	
-	#notify($ERRORS{'DEBUG'}, 0, "retrieved info for computer '$computer_identifier':\n" . format_data($computer_info));
+	my $nathost_info = get_computer_nathost_info($computer_id, $no_cache);
+	if ($nathost_info) {
+		$computer_info->{nathost} = $nathost_info;
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "retrieved info for computer: $computer_identifier");
 	$ENV{computer_info}{$computer_identifier} = $computer_info;
 	$ENV{computer_info}{$computer_identifier}{RETRIEVAL_TIME} = time;
 	return $ENV{computer_info}{$computer_identifier};
@@ -7062,6 +7087,456 @@ EOF
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_computer_nathost_info
+
+ Parameters  : $computer_identifier
+ Returns     : hash reference
+ Description : Retrieves nathost info if a nathost is mapped to the computer via
+               the nathostcomputermap table. Example:
+					{
+					  "HOSTNAME" => "nat1.vcl.org",
+					  "datedeleted" => undef,
+					  "deleted" => 0,
+					  "id" => 2,
+					  "natIP" => "x.x.x.x",
+					  "nathostcomputermap" => {
+						 "computerid" => 3591,
+						 "nathostid" => 2
+					  },
+					  "resource" => {
+						 "id" => 6185,
+						 "resourcetype" => {
+							"id" => 16,
+							"name" => "managementnode"
+						 },
+						 "resourcetypeid" => 16,
+						 "subid" => 8
+					  },
+					  "resourceid" => 6185
+					}
+
+=cut
+
+sub get_computer_nathost_info {
+	my ($computer_identifier, $no_cache) = @_;
+	if (!defined($computer_identifier)){
+		notify($ERRORS{'WARNING'}, 0, "computer identifier argument was not supplied");
+		return;
+	}
+	
+	return $ENV{nathost_info}{$computer_identifier} if (!$no_cache && $ENV{nathost_info}{$computer_identifier});
+	
+	# Get a hash ref containing the database column names
+	my $database_table_columns = get_database_table_columns();
+	
+	my @tables = (
+		'nathost',
+		'nathostcomputermap',
+		'resource',
+		'resourcetype',
+	);
+	
+	# Construct the select statement
+	my $select_statement = "SELECT DISTINCT\n";
+	
+	# Get the column names for each table and add them to the select statement
+	for my $table (@tables) {
+		my @columns = @{$database_table_columns->{$table}};
+		for my $column (@columns) {
+			$select_statement .= "$table.$column AS '$table-$column',\n";
+		}
+	}
+	
+	# Remove the comma after the last column line
+	$select_statement =~ s/,$//;
+	
+	# Complete the select statement
+	$select_statement .= <<EOF;
+FROM
+computer,
+nathostcomputermap,
+nathost,
+resource,
+resourcetype
+
+WHERE
+nathostcomputermap.computerid = computer.id
+AND nathostcomputermap.nathostid = nathost.id
+AND nathost.resourceid = resource.id
+AND resource.resourcetypeid = resourcetype.id
+AND
+EOF
+
+	# If the computer identifier is all digits match it to computer.id
+	# Otherwise, match computer.hostname
+	if ($computer_identifier =~ /^\d+$/) {
+		$select_statement .= "computer.id = \'$computer_identifier\'";
+	}
+	else {
+		$select_statement .= "computer.hostname REGEXP '$computer_identifier(\\\\.|\$)'";
+	}
+	
+	# Call the database select subroutine
+	my @selected_rows = database_select($select_statement);
+
+	if (!@selected_rows) {
+		notify($ERRORS{'DEBUG'}, 0, "NAT host is not mapped to computer $computer_identifier");
+		return {};
+	}
+	elsif (scalar @selected_rows > 1) {
+		notify($ERRORS{'WARNING'}, 0, "unable to determine NAT host mapped to computer $computer_identifier, " . scalar @selected_rows . " rows were returned from database select statement:\n$select_statement");
+		return;
+	}
+	
+	# Get the single row returned from the select statement
+	my $row = $selected_rows[0];
+	
+	# Construct a hash with all of the computer info
+	my $nathost_info;
+	
+	# Loop through all the columns returned
+	for my $key (keys %$row) {
+		my $value = $row->{$key};
+		
+		# Split the table-column names
+		my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
+		
+		# Add the values for the primary table to the hash
+		# Add values for other tables under separate keys
+		if ($table eq $tables[0]) {
+			$nathost_info->{$column} = $value;
+		}
+		elsif ($table eq 'resourcetype') {
+			$nathost_info->{resource}{resourcetype}{$column} = $value;
+		}
+		else {
+			$nathost_info->{$table}{$column} = $value;
+		}
+	}
+	
+	$nathost_info->{HOSTNAME} = '<unknown>';
+	
+	my $resource_id = $nathost_info->{resource}{id};
+	my $resource_type = $nathost_info->{resource}{resourcetype}{name};
+	my $resource_subid = $nathost_info->{resource}{subid};
+	if ($resource_type eq 'managementnode') {
+		my $management_node_info = get_management_node_info($resource_subid) || {};
+		$nathost_info->{HOSTNAME} = $management_node_info->{hostname};
+	}
+	elsif ($resource_type eq 'computer') {
+		my $computer_info = get_computer_info($resource_subid) || {};
+		$nathost_info->{HOSTNAME} = $computer_info->{hostname};
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "NAT host resource type is not supported: $resource_type, resource ID: $resource_id");
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "retrieved info for NAT host mapped to computer computer $computer_identifier:\n" . format_data($nathost_info));
+	$ENV{nathost_info}{$computer_identifier} = $nathost_info;
+	return $ENV{nathost_info}{$computer_identifier};
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_nathost_assigned_public_ports
+
+ Parameters  : $nathost_id
+ Returns     : array
+ Description : Retrieves a list of natport.publicport values for the nathost
+               specified by the argument.
+
+=cut
+
+sub get_nathost_assigned_public_ports {
+	my ($nathost_id) = @_;
+	if (!defined($nathost_id)) {
+		notify($ERRORS{'WARNING'}, 0, "nathost ID argument was not supplied");
+		return;
+	}
+	
+	my $select_statement = "SELECT publicport FROM natport WHERE nathostid = $nathost_id ORDER BY publicport ASC";
+	my @selected_rows = database_select($select_statement);
+	my @ports;
+	for my $row (@selected_rows) {
+		push @ports, $row->{publicport};
+	}
+	
+	if (@ports) {
+		notify($ERRORS{'DEBUG'}, 0, "retrieved assigned public ports for nathost $nathost_id: " . join(", ", @ports));
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "no public ports are assigned for nathost $nathost_id");
+	}
+	return @ports;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 populate_reservation_natport
+
+ Parameters  : $reservation_id
+ Returns     : boolean
+ Description : Populates the natport table for a reservation if the computer is
+               mapped to a nathost. The natport table is checked for each
+               connect method port assigned to each connect method for the
+               reservation.
+
+=cut
+
+sub populate_reservation_natport {
+	my ($reservation_id) = @_;
+	if (!defined($reservation_id)) {
+		notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
+		return;
+	}
+	
+	my $request_info = {get_reservation_request_info($reservation_id, 0)};
+	my $computer_id = $request_info->{reservation}{$reservation_id}{computerid};
+	my $computer_name = $request_info->{reservation}{$reservation_id}{computer}{SHORTNAME};
+	my $imagerevision_id = $request_info->{reservation}{$reservation_id}{imagerevision}{id};
+	
+	my $nathost_info = $request_info->{reservation}{$reservation_id}{computer}{nathost};
+	my $nathost_id = $nathost_info->{id};
+	my $nathost_hostname = $nathost_info->{HOSTNAME};
+	my $nathost_public_ip_address = $nathost_info->{natIP};
+	
+	# Make sure the nathost info is defined
+	if (!defined($nathost_id)) {
+		notify($ERRORS{'DEBUG'}, 0, "natport table does not need to be populated, computer $computer_id is not mapped to a NAT host");
+		return 1;
+	}
+	notify($ERRORS{'DEBUG'}, 0, "computer $computer_id is mapped to NAT host $nathost_hostname ($nathost_hostname)");
+	
+	# Retrieve the connect method info - do not use cached info
+	my $connect_method_info = get_connect_method_info($imagerevision_id, 1);
+	
+	# Retrieve the ports already assigned to the nathost
+	my @assigned_ports = get_nathost_assigned_public_ports($nathost_id);
+	
+	# Put the assigned ports into a hash for easy checking
+	my %assigned_port_hash = map { $_ => 1 } @assigned_ports;
+	
+	my %available_port_hash;
+	
+	# Retrieve and parse the natport_ranges variable
+	my $natport_ranges_variable = get_variable('natport_ranges') || '49152-65535';
+	my @natport_ranges = split(/[,;]+/, $natport_ranges_variable);
+	for my $natport_range (@natport_ranges) {
+		my ($start_port, $end_port) = $natport_range =~ /(\d+)-(\d+)/g;
+		if (!defined($start_port)) {
+			notify($ERRORS{'WARNING'}, 0, "unable to parse NAT port range: '$natport_range'");
+			next;
+		}
+		
+		# Make sure port range isn't backwards
+		if ($end_port < $start_port) {
+			my $start_port_temp = $start_port;
+			$start_port = $end_port;
+			$end_port = $start_port_temp;
+		}
+		
+		# Loop through all of the ports in the range, check if already assigned
+		for (my $port = $start_port; $port<=$end_port; $port++) {
+			if (!defined($assigned_port_hash{$port})) {
+				$available_port_hash{$port} = 1;
+			}
+		}
+	}
+	
+	# Loop through the connect methods
+	# Check if a public port has been assigned for each
+	for my $connect_method_id (sort keys %$connect_method_info) {
+		my $connect_method_port_info = $connect_method_info->{$connect_method_id}{connectmethodport};
+		
+		CONNECT_METHOD_PORT_ID: for my $connect_method_port_id (sort keys %$connect_method_port_info) {
+			my $connect_method_port = $connect_method_port_info->{$connect_method_port_id};
+			
+			if (defined($connect_method_port->{natport})) {
+				notify($ERRORS{'DEBUG'}, 0, "NAT public port is already assigned for connect method ID: $connect_method_id, port ID: $connect_method_port_id\n" . format_data($connect_method_port->{natport}));
+				next CONNECT_METHOD_PORT_ID;
+			}
+			
+			# Select a random port from the list of available ports
+			my @available_ports = sort keys %available_port_hash;
+			my $available_port_count = scalar(@available_ports);
+			if (!$available_port_count) {
+				notify($ERRORS{'CRITICAL'}, 0, "no NAT public ports are available for NAT host $nathost_hostname ($nathost_id)");
+				return;
+			}
+			my $random_index =  int(rand($available_port_count));
+			my $public_port = $available_ports[$random_index];
+			
+			# Remove the selected port from the available port hash
+			delete $available_port_hash{$public_port};
+			
+			# Make multiple attempts, it's possible 2 reservations will attempt to assign the same public port at the same time
+			# Using random ports helps avoid collisions
+			# TODO: add semaphore
+			my $attempt_limit = 5;
+			for (my $attempt = 1; $attempt <= $attempt_limit; $attempt++) {
+				sleep_uninterrupted(1);
+				if (insert_natport($reservation_id, $nathost_id, $connect_method_port_id, $public_port)) {
+					next CONNECT_METHOD_PORT_ID;
+				}
+			}
+			notify($ERRORS{'CRITICAL'}, 0, "failed to insert natport entry after making $attempt_limit attempts:\n" .
+				"connectmethod ID: $connect_method_id\n" .
+				"connectmethodport ID: $connect_method_port_id\n" .
+				"reservation ID: $reservation_id\n" .
+				"nathost ID: $nathost_id\n" .
+				"public port: $public_port"
+			);
+			return;
+		}
+	}
+	
+	# Get the connect method info again to verify all ports are assigned a NAT public port
+	my $info_string = "";
+	$connect_method_info = get_connect_method_info($imagerevision_id, 1);
+	for my $connect_method_id (sort keys %$connect_method_info) {
+		my $connect_method = $connect_method_info->{$connect_method_id};
+		my $connect_method_name = $connect_method->{name};
+		
+		for my $connect_method_port_id (sort keys %{$connect_method->{connectmethodport}}) {
+			my $connect_method_port = $connect_method->{connectmethodport}{$connect_method_port_id};
+			my $protocol = $connect_method_port->{protocol};
+			my $port = $connect_method_port->{port};
+			
+			my $nat_port = $connect_method_port->{natport};
+			my $public_port = $nat_port->{publicport};
+			if (!defined($nat_port)) {
+				notify($ERRORS{'WARNING'}, 0, "NAT public port is not assigned for connect method ID: $connect_method_id, port ID: $connect_method_port_id");
+				return;
+			}
+			$info_string .= "$connect_method_name: $nathost_public_ip_address:$public_port --> $computer_name:$port ($protocol)\n";
+		}
+	}
+	notify($ERRORS{'DEBUG'}, 0, "NAT port forwarding information:\n$info_string");
+	return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 insert_natport
+
+ Parameters  : $reservation_id, $nathost_id, $connect_method_port_id, $public_port
+ Returns     : boolean
+ Description : Inserts an entry into the natport table.
+
+=cut
+
+sub insert_natport {
+	my ($reservation_id, $nathost_id, $connect_method_port_id, $public_port) = @_;
+	if (!defined($reservation_id)) {
+		notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
+		return;
+	}
+	elsif (!defined($nathost_id)) {
+		notify($ERRORS{'WARNING'}, 0, "nathost ID argument was not supplied");
+		return;
+	}
+	elsif (!defined($connect_method_port_id)) {
+		notify($ERRORS{'WARNING'}, 0, "connect method port ID argument was not supplied");
+		return;
+	}
+	elsif (!defined($public_port)) {
+		notify($ERRORS{'WARNING'}, 0, "public port argument was not supplied");
+		return;
+	}
+	
+	my $insert_statement = <<EOF;
+INSERT INTO
+natport
+(
+   reservationid,
+   nathostid,
+   connectmethodportid,
+   publicport
+)
+VALUES
+(
+   $reservation_id,
+   $nathost_id,
+   $connect_method_port_id,
+   $public_port
+)
+EOF
+
+	my $result = database_execute($insert_statement);
+	if ($result) {
+		notify($ERRORS{'DEBUG'}, 0, "inserted entry into natport table:\nreservation: $reservation_id\nnathost ID: $nathost_id\nconnectmethodport ID: $connect_method_port_id\npublic port: $public_port");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to insert entry into natport table\nreservation: $reservation_id\nnathost ID: $nathost_id\nconnectmethodport ID: $connect_method_port_id\npublic port: $public_port");
+		return 0;
+	}
+}
+	
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_reservation_request_id
+
+ Parameters  : $reservation_id, $no_cache (optional)
+ Returns     : integer
+ Description : Retrieves the request ID assigned of a reservation.
+
+=cut
+
+sub get_reservation_request_id {
+	my ($reservation_id, $no_cache) = @_;
+	if (!defined($reservation_id)) {
+		notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
+		return;
+	}
+	
+	if (!$no_cache && defined($ENV{reservation_request_id}{$reservation_id})) {
+		return $ENV{reservation_request_id}{$reservation_id};
+	}
+	
+	my $select_statement = "SELECT requestid FROM reservation WHERE id = '$reservation_id'";
+	my @selected_rows = database_select($select_statement);
+	if (!@selected_rows) {
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve request ID for reservation $reservation_id");
+		return;
+	}
+	
+	my $row = $selected_rows[0];
+	my $request_id = $row->{requestid};
+	notify($ERRORS{'DEBUG'}, 0, "retrieved reservation $reservation_id request ID: $request_id");
+	$ENV{reservation_request_id}{$reservation_id} = $request_id;
+	return $request_id;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_reservation_request_info
+
+ Parameters  : $reservation_id, $no_cache (optional)
+ Returns     : hash reference
+ Description : Retrieves the request info for a reservation.
+
+=cut
+
+sub get_reservation_request_info {
+	my ($reservation_id, $no_cache) = @_;
+	if (!defined($reservation_id)) {
+		notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
+		return;
+	}
+	
+	my $request_id = get_reservation_request_id($reservation_id, $no_cache);
+	if (!$request_id) {
+		notify($ERRORS{'WARNING'}, 0, "unable to retrieve request info for reservation $reservation_id, failed to determine request ID for reservation");
+		return;
+	}
+	
+	return get_request_info($request_id, $no_cache);
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 get_computer_ids
 
  Parameters  : $computer_identifier
@@ -7897,11 +8372,15 @@ sub reservation_being_processed {
 sub run_command {
 	my ($command, $no_output) = @_;
 	
-	my $output_string = `$command 2>&1`;
+	my $output_string;
+	$output_string = `$command 2>&1`;
+	$output_string = '' unless $output_string;
+	
 	my $exit_status = $?;
 	if ($exit_status >= 0) {
 		$exit_status = $exit_status >> 8;
 	}
+
 	
 	# Remove any trailing newlines from the output
 	chomp $output_string;
@@ -9611,7 +10090,52 @@ sub kill_child_processes {
  Parameters  : $imagerevision_id, $no_cache (optional)
  Returns     : hash reference
  Description : Returns the connect methods for the image revision specified as
-               the argument.
+               the argument. Example:
+					{
+					 4 => {
+						"RETRIEVAL_TIME" => "1417709281",
+						"connectmethodmap" => {
+						  "OSid" => 36,
+						  "OStypeid" => undef,
+						  "autoprovisioned" => undef,
+						  "connectmethodid" => 4,
+						  "disabled" => 0,
+						  "imagerevisionid" => undef
+						},
+						"connectmethodport" => {
+						  35 => {
+							 "connectmethodid" => 4,
+							 "id" => 35,
+							 "natport" => {
+								"connectmethodportid" => 35,
+								"nathostid" => 2,
+								"publicport" => 56305,
+								"reservationid" => 3115
+							 },
+							 "port" => 3389,
+							 "protocol" => "TCP"
+						  },
+						  37 => {
+							 "connectmethodid" => 4,
+							 "id" => 37,
+							 "natport" => {
+								"connectmethodportid" => 37,
+								"nathostid" => 2,
+								"publicport" => 63058,
+								"reservationid" => 3115
+							 },
+							 "port" => 3389,
+							 "protocol" => "UDP"
+						  }
+						},
+						"description" => "Linux xRDP (Remote Desktop Protocol)",
+						"id" => 4,
+						"name" => "xRDP",
+						"servicename" => "xrdp",
+						"startupscript" => undef
+					 }
+				  }
+
 
 =cut
 
@@ -9646,6 +10170,7 @@ sub get_connect_method_info {
 		'connectmethod',
 		'connectmethodport',
 		'connectmethodmap',
+		'natport',
 	);
 	
 	# Construct the select statement
@@ -9666,10 +10191,13 @@ sub get_connect_method_info {
 	$select_statement .= <<EOF;
 FROM
 connectmethod,
-connectmethodport,
+
+connectmethodport
+LEFT JOIN natport ON (natport.connectmethodportid = connectmethodport.id),
+
 connectmethodmap,
-imagerevision
 
+imagerevision
 LEFT JOIN image ON (image.id = imagerevision.imageid)
 LEFT JOIN OS ON (OS.id = image.OSid)
 LEFT JOIN OStype ON (OStype.name = OS.type)
@@ -9712,15 +10240,18 @@ EOF
 			# Split the table-column names
 			my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
 			
-			# Add the values for the primary table to the hash
-			# Add values for other tables under separate keys
 			if ($table eq 'connectmethod') {
 				$connect_method_info->{$connectmethod_id}{$column} = $value;
 			}
 			elsif ($table eq 'connectmethodport') {
-				my $protocol = $row->{"connectmethodport-protocol"};
-				my $port = $row->{"connectmethodport-port"};
-				$connect_method_info->{$connectmethod_id}{$table}{$protocol}{$port} = 1;
+				my $connectmethodport_id = $row->{"connectmethodport-id"};
+				$connect_method_info->{$connectmethod_id}{'connectmethodport'}{$connectmethodport_id}{$column} = $value;
+			}
+			elsif ($table eq 'natport') {
+				if (defined($value)) {
+					my $connectmethodport_id = $row->{"connectmethodport-id"};
+					$connect_method_info->{$connectmethod_id}{'connectmethodport'}{$connectmethodport_id}{'natport'}{$column} = $value;
+				}
 			}
 			else {
 				$connect_method_info->{$connectmethod_id}{$table}{$column} = $value;
@@ -11932,7 +12463,6 @@ EOF
 
 =cut
 
-
 sub get_provisioning_osinstalltype_info {
 	my ($provisioning_id) = @_;
 	if (!defined($provisioning_id)) {