sub process()

in managementnode/lib/VCL/reserved.pm [93:303]


sub process {
	my $self = shift;
	
	my $request_id                      = $self->data->get_request_id();
	my $request_logid                   = $self->data->get_request_log_id();
	my $request_checkuser               = $self->data->get_request_checkuser();
	my $reservation_id                  = $self->data->get_reservation_id();
	my $reservation_count               = $self->data->get_reservation_count();
	my $computer_id                     = $self->data->get_computer_id();
	my $computer_short_name             = $self->data->get_computer_short_name();
	my $is_parent_reservation           = $self->data->is_parent_reservation();
	my $parent_reservation_id           = $self->data->get_parent_reservation_id();
	my $is_server_request               = $self->data->is_server_request();
	my $imagemeta_checkuser             = $self->data->get_imagemeta_checkuser();
	
	my $acknowledge_timeout_seconds     = $self->os->get_timings('acknowledgetimeout');
	my $initial_connect_timeout_seconds = $self->os->get_timings('initialconnecttimeout');
	
	# Update the log loaded time to now for this request
	update_log_loaded_time($request_logid);
	
	# Make sure firewall object is initialized early to reduce time it takes to configure things after user clicks Connect
	$self->os->firewall() if ($self->os->can('firewall'));
	
	# Update the computer state to reserved
	# This causes pending to change to the Connect button on the Current Reservations page
	update_computer_state($computer_id, 'reserved');
	insertloadlog($reservation_id, $computer_id, "reserved", "$computer_short_name successfully reserved");
	
	
	if ($is_parent_reservation) {
		# Send an email and/or IM to the user
		# Do this after updating the computer state to reserved because this is when the Connect button appears
		$self->notify_user_ready();
		
		# Insert acknowledgetimeout immediately before beginning to check user clicked Connect
		# Web uses timestamp of this to determine when next to refresh the page
		# Important because page should refresh as soon as possible to reservation timing out
		insertloadlog($reservation_id, $computer_id, "acknowledgetimeout", "begin acknowledge timeout ($acknowledge_timeout_seconds seconds)");
	}
	
	my $acknowledge_check_start_epoch_seconds = $self->wait_for_reservation_loadstate($parent_reservation_id, "acknowledgetimeout", $acknowledge_timeout_seconds, 5);
	if (!$acknowledge_check_start_epoch_seconds) {
		notify($ERRORS{'WARNING'}, 0, "failed to retrieve timestamp of parent reservation $parent_reservation_id 'acknowledgetimeout' computerloadlog entry");
		return;
	}
	
	# Get the current time
	my $now_epoch_seconds = time;
	
	# Calculate the exact time when connection checking should end
	my $acknowledge_check_end_epoch_seconds = ($acknowledge_check_start_epoch_seconds + $acknowledge_timeout_seconds);
	my $acknowledge_timeout_remaining_seconds = ($acknowledge_check_end_epoch_seconds - $now_epoch_seconds);
	
	my $now_string                           = strftime('%H:%M:%S', localtime($now_epoch_seconds));
	my $acknowledge_check_start_string       = strftime('%H:%M:%S', localtime($acknowledge_check_start_epoch_seconds));
	my $acknowledge_check_end_string         = strftime('%H:%M:%S', localtime($acknowledge_check_end_epoch_seconds));
	my $acknowledge_timeout_string           = strftime('%H:%M:%S', gmtime($acknowledge_timeout_seconds));
	my $acknowledge_timeout_remaining_string = strftime('%H:%M:%S', gmtime($acknowledge_timeout_remaining_seconds));
	
	notify($ERRORS{'DEBUG'}, 0, "beginning to check for user acknowledgement:\n" .
		"acknowledge check start   :   $acknowledge_check_start_string\n" .
		"acknowledge timeout total : + $acknowledge_timeout_string\n" .
		"--------------------------------------\n" .
		"acknowledge check end     : = $acknowledge_check_end_string\n" .
		"current time              : - $now_string\n" .
		"--------------------------------------\n" .
		"acknowledge timeout remaining : = $acknowledge_timeout_remaining_string ($acknowledge_timeout_remaining_seconds seconds)\n"
	);
	
	# Wait for the user to acknowledge the request by clicking Connect button or from API
	# Note: for server requests, this will always return true because the frontend inserts reservation.remoteIP when the reservation is made
	my $user_acknowledged = $self->code_loop_timeout(sub{$self->user_acknowledged()}, [], 'waiting for user acknowledgement', $acknowledge_timeout_remaining_seconds, 1, 10);
	if (!$user_acknowledged) {
		$self->notify_user_timeout_no_acknowledgement();
		$self->state_exit('timeout', 'available', 'noack');
	}
	
	# Add noinitialconnection and then delete acknowledgetimeout
	insertloadlog($reservation_id, $computer_id, "noinitialconnection", "user clicked Connect");
	delete_computerloadlog_reservation($reservation_id, 'acknowledgetimeout');
	
	# For non-server requests, the frontend should have inserted an 'initialconnecttimeout' computerloadlog entry for the parent reservation when the user clicks Connect
	# Web uses timestamp of this to determine when next to refresh the page
	# The timestamp of this computerloadlog entry will be used to determine when to timeout the connection checking during the inuse state
	my $connection_check_start_epoch_seconds;
	if ($is_server_request) {
		$connection_check_start_epoch_seconds = time;
		insertloadlog($parent_reservation_id, $computer_id, "initialconnecttimeout", "begin initial connection timeout ($initial_connect_timeout_seconds seconds)");
	}
	else {
		$connection_check_start_epoch_seconds = get_reservation_computerloadlog_time($parent_reservation_id, 'initialconnecttimeout');
		if ($connection_check_start_epoch_seconds) {
			notify($ERRORS{'DEBUG'}, 0, "retrieved timestamp of computerloadlog 'initialconnecttimeout' entry inserted by web frontend: $connection_check_start_epoch_seconds");
		}
		else {
			notify($ERRORS{'DEBUG'}, 0, "could not retrieve timestamp of computerloadlog 'initialconnecttimeout' entry, web frontend should have inserted this, inserting new entry");
			$connection_check_start_epoch_seconds = time;
			insertloadlog($reservation_id, $computer_id, "initialconnecttimeout", "begin initial connection timeout ($initial_connect_timeout_seconds seconds)");
		}
	}
	
	# Call OS module's grant_access() subroutine which adds user accounts to computer
	if ($self->os->can("grant_access") && !$self->os->grant_access()) {
		$self->reservation_failed("OS module grant_access failed");
	}
	
	# User acknowledged request
	# Add the cluster information to the loaded computers if this is a cluster reservation
	if ($reservation_count > 1 && !$self->os->update_cluster()) {
		$self->reservation_failed("update_cluster failed");
	}
	
	# Create a JSON file containing the reservation info
	my $enable_experimental_features = get_variable('enable_experimental_features', 0);
	if ($enable_experimental_features) {
		$self->os->create_reservation_info_json_file();
	}
	
	# Check if OS module's post_reserve() subroutine exists
	if ($self->os->can("post_reserve") && !$self->os->post_reserve()) {
		$self->reservation_failed("OS module post_reserve failed");
	}
	
	# Add a 'postreserve' computerloadlog entry
	# Do this last - important for cluster reservation timing
	# Parent's reserved process will loop until this exists for all child reservations
	insertloadlog($reservation_id, $computer_id, "postreserve", "$computer_short_name post reserve successful");
	
	# Get the current time
	$now_epoch_seconds = time;
	
	# Calculate the exact time when connection checking should end
	my $connection_check_end_epoch_seconds = ($connection_check_start_epoch_seconds + $initial_connect_timeout_seconds);
	my $connect_timeout_remaining_seconds = ($connection_check_end_epoch_seconds - $now_epoch_seconds);
	
	$now_string                       = strftime('%H:%M:%S', localtime($now_epoch_seconds));
	my $connection_check_start_string    = strftime('%H:%M:%S', localtime($connection_check_start_epoch_seconds));
	my $connection_check_end_string      = strftime('%H:%M:%S', localtime($connection_check_end_epoch_seconds));
	my $connect_timeout_string           = strftime('%H:%M:%S', gmtime($initial_connect_timeout_seconds));
	my $connect_timeout_remaining_string = strftime('%H:%M:%S', gmtime($connect_timeout_remaining_seconds));
	
	notify($ERRORS{'DEBUG'}, 0, "beginning to check for initial user connection:\n" .
		"connection check start    :   $connection_check_start_string\n" .
		"connect timeout total     : + $connect_timeout_string\n" .
		"--------------------------------------\n" .
		"connection check end      : = $connection_check_end_string\n" .
		"current time              : - $now_string\n" .
		"--------------------------------------\n" .
		"connect timeout remaining : = $connect_timeout_remaining_string ($connect_timeout_remaining_seconds seconds)\n"
	);
	
	# Check to see if user is connected. user_connected will true(1) for servers and requests > 24 hours
	my $user_connected = $self->code_loop_timeout(sub{$self->user_connected()}, [], "waiting for initial user connection to $computer_short_name", $connect_timeout_remaining_seconds, 15);
	
	# Delete the connecttimeout immediately after acknowledgement loop ends
	delete_computerloadlog_reservation($reservation_id, 'connecttimeout');
	
	if (!$user_connected) {
		if (!$imagemeta_checkuser || !$request_checkuser) {
			notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, imagemeta checkuser: $imagemeta_checkuser, request checkuser: $request_checkuser");
		}
		elsif ($is_server_request) {
			notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, server reservation");
		}
		elsif (is_request_deleted($request_id) || $self->request_state_changed()) {
			$self->state_exit();
		}
		else {
			$self->notify_user_timeout_no_initial_connection();
			$self->state_exit('timeout', 'reserved', 'nologin');
		}
	}
	
	# Add a line to currentimage.txt indicating it's possible a user logged on to the computer
	$self->os->set_tainted_status('user may have logged in');
	
	# Update reservation lastcheck, otherwise inuse request will be processed immediately again
	update_reservation_lastcheck($reservation_id);
	
	# Tighten up the firewall
	# Process the connect methods again, lock the firewall down to the address the user connected from
	my $remote_ip = $self->data->get_reservation_remote_ip();
	if ($self->os->can('firewall') && $self->os->firewall->can('process_inuse')) {
		$self->os->firewall->process_inuse($remote_ip);
	}
	else {
		if (!$self->os->process_connect_methods($remote_ip, 1)) {
			notify($ERRORS{'CRITICAL'}, 0, "failed to process connect methods after user connected to computer");
		}
	}
	
	# Perform steps after a user makes an initial connection
	$self->os->post_initial_connection();
	
	# For cluster reservations, the parent must wait until all child reserved processes have exited
	# Otherwise, the state will change to inuse while the child processes are still finishing up the reserved state
	# vcld will then fail to fork inuse processes for the child reservations
	if ($reservation_count > 1 && $is_parent_reservation) {
		if (!$self->code_loop_timeout(sub{$self->wait_for_child_reservations()}, [], "waiting for child reservation reserved processes to complete", 360, 5)) {
			$self->reservation_failed('all child reservation reserved processes did not complete');
		}
		
		# Parent can't tell if reserved processes on other management nodes have terminated
		# Wait a short time in case processes on other management nodes are terminating
		sleep 3;
	}
	
	# Change the request and computer state to inuse then exit
	$self->state_exit('inuse', 'inuse');
} ## end sub process