sub state_exit()

in managementnode/lib/VCL/Module/State.pm [871:1073]


sub state_exit {
	my $self = shift;
	if (ref($self) !~ /VCL/) {
		notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a class method of a VCL object");
		return;
	}
	
	# Set flag to avoid this subroutine from being called more than once
	$ENV->{state_exit} = 1;
	
	my ($request_state_name_new, $computer_state_name_new, $request_log_ending) = @_;
	notify($ERRORS{'DEBUG'}, 0, "beginning state module exit tasks, " .
		"request state argument: " . ($request_state_name_new ? $request_state_name_new : '<not specified>') . ', ' .
		"computer state argument: " . ($computer_state_name_new ? $computer_state_name_new : '<not specified>') . ', ' .
		"log ending argument: " . ($request_log_ending ? $request_log_ending : '<not specified>')
	);
	
	
	my $calling_sub = get_calling_subroutine();
	
	my $request_id                 = $self->data->get_request_id();
	my $request_logid              = $self->data->get_request_log_id(0);
	my $reservation_id             = $self->data->get_reservation_id();
	my @reservation_ids            = $self->data->get_reservation_ids();
	my $reservation_count          = $self->data->get_reservation_count();
	my $is_parent_reservation      = $self->data->is_parent_reservation();
	my $request_state_name_old     = $self->data->get_request_state_name();
	my $request_laststate_name_old = $self->data->get_request_laststate_name();
	my $computer_id                = $self->data->get_computer_id();
	my $computer_shortname         = $self->data->get_computer_short_name();
	my $nathost_hostname           = $self->data->get_nathost_hostname(0);

	if ($is_parent_reservation) {
		# If parent of a cluster request, wait for child processes to exit before switching the state
		if ($reservation_count > 1) {
			# Check frequently if reservation timed out to cause Reservations page to remove the Connect button ASAP
			if ($request_state_name_new && $request_state_name_new =~ /(timeout)/) {
				$self->wait_for_child_reservations_to_exit(300, 3);
			}
			else {
				$self->wait_for_child_reservations_to_exit();
			}
			
			# Check if any reservations failed
			my @failed_reservation_ids = $self->does_loadstate_exist_any_reservation('failed');
			if (@failed_reservation_ids && (!$request_state_name_new || $request_state_name_new ne 'failed')) {
				notify($ERRORS{'OK'}, 0, "another reservation failed, request state will be updated to 'failed'");
				$request_state_name_new = 'failed';
			}
			
			if ($request_state_name_new && $request_state_name_new eq 'failed') {
				# Child reservations will leave the state of the computer to 'reloading' if they didn't fail
				# Need to change state back to available for child reservations which didn't fail
				for my $cluster_reservation_id (@reservation_ids) {
					next if $cluster_reservation_id eq $reservation_id;
					
					my $reservation_data = $self->data->get_reservation_data($cluster_reservation_id) || next;
					my $reservation_computer_id = $reservation_data->get_computer_id() || next;
					my $reservation_computer_hostname = $reservation_data->get_computer_hostname() || next;
					if (!(grep { $_ eq $cluster_reservation_id } @failed_reservation_ids)) {
						notify($ERRORS{'DEBUG'}, 0, "child reservation $cluster_reservation_id did not fail, checking state of computer assigned to reservation: $reservation_computer_id");
						
						my $computer_current_state_name = get_computer_current_state_name($reservation_computer_id) || next;
						if ($computer_current_state_name =~ /(reloading)/) {
							notify($ERRORS{'DEBUG'}, 0, "state of computer $reservation_computer_id assigned to child reservation $cluster_reservation_id is $computer_current_state_name, reservation did not fail, changing state to available");
							update_computer_state($reservation_computer_id, 'available');
						}
						else {
							notify($ERRORS{'DEBUG'}, 0, "state of computer $reservation_computer_id assigned to child reservation $cluster_reservation_id is $computer_current_state_name, reservation did not fail, state of computer will not be changed");
						}
					}
				}
			}
		}
		
		if ($request_state_name_new) {
			# Never set request state to failed if previous state is image
			# Allow pending/checkpoint --> reserved/checkpoint
			if ($request_state_name_old =~ /(image|checkpoint)/ && $request_state_name_new !~ /(reserved|complete|maintenance)/) {
				notify($ERRORS{'CRITICAL'}, 0, "previous request state is $request_state_name_old, not setting request state to $request_state_name_new, setting request and computer state to maintenance");
				$request_state_name_new = 'maintenance';
				$computer_state_name_new = 'maintenance';
			}
			elsif ($request_state_name_old =~ /(inuse|reboot|server)/ && $request_state_name_new !~ /(inuse|timeout|maintenance)/) {
				notify($ERRORS{'CRITICAL'}, 0, "previous request state is $request_state_name_old, not setting request state to $request_state_name_new, setting request and computer state to inuse");
				$request_state_name_new = 'inuse';
				$computer_state_name_new = 'inuse';
			}
		}
	}
	
	
	# If $request_log_ending was passed this should be the end of the reservation
	# If NAT is used, rules added to the NAT host should be removed
	if ($nathost_hostname) {
		my $nat_sanitize_needed = 0;
		if ($request_log_ending) {
			notify($ERRORS{'DEBUG'}, 0, "attempting to sanitize firewall rules created for reservation $reservation_id on NAT host $nathost_hostname, \$request_log_ending argument was specified");
			$nat_sanitize_needed = 1;
		}
		elsif ($request_state_name_new && $request_state_name_new =~ /(timeout|deleted|complete|image|checkpoint|failed)/) {
			notify($ERRORS{'DEBUG'}, 0, "attempting to sanitize firewall rules created for reservation $reservation_id on NAT host $nathost_hostname, next request state is '$request_state_name_new'");
			$nat_sanitize_needed = 1;
		}
		
		if ($nat_sanitize_needed) {
			$self->nathost_os->firewall->nat_sanitize_reservation();
		}
	}
	
	
	# Update the computer state if argument was supplied
	if ($computer_state_name_new) {
		my $computer_state_name_old = $self->data->get_computer_state_name();
		
		if ($computer_state_name_new eq $computer_state_name_old) {
			notify($ERRORS{'DEBUG'}, 0, "state of computer $computer_shortname not updated, already set to $computer_state_name_old");
		}
		elsif (!update_computer_state($computer_id, $computer_state_name_new)) {
			notify($ERRORS{'CRITICAL'}, 0, "failed update state of computer $computer_shortname: $computer_state_name_old->$computer_state_name_new");
		}
	}
	
	
	if ($is_parent_reservation) {
		# Clean computerloadlog as late as possible
		if ($request_state_name_old =~ /(new|reserved)/) {
			# Only delete computerloadlog entries with loadstatename = 'begin' for all reservations in this request
			delete_computerloadlog_reservation(\@reservation_ids, '(begin)');
		}
		else {
			# Delete all computerloadlog entries for all reservations in this request
			delete_computerloadlog_reservation(\@reservation_ids);
		}
		
		# Update log.ending if this is the parent reservation and argument was supplied
		if ($request_logid && $request_log_ending) {
			if (!update_log_ending($request_logid, $request_log_ending)) {
				notify($ERRORS{'CRITICAL'}, 0, "failed to set log ending to $request_log_ending, log ID: $request_logid");
			}
		}
		
		# Update the reservation.lastcheck time to now if the next request state is inuse
		# Do this to ensure that reservations are not processed again quickly after this process exits
		# For cluster requests, the parent may have had to wait a while for child processes to exit
		# Resetting reservation.lastcheck causes reservations to wait the full interval between inuse checks
		if ($request_state_name_new && $request_state_name_new =~ /(reserved|inuse|reboot|server)/) {
			update_reservation_lastcheck(@reservation_ids);
		}
	}
	
	# Insert a computerloadlog 'exited' entry
	# This is used by the parent cluster reservation
	# Do this as late as possible, if request.state is changed to 'complete', vcld may begin processing it before this process exits
	# Warning will be generated if request is deleted before insertloadlog is executed
	insertloadlog($reservation_id, $computer_id, "exited", "vcld process exiting");
	
	
	if ($is_parent_reservation && $request_state_name_new) {	
		# Update the request state
		if ($request_state_name_old ne 'deleted') {
			if (is_request_deleted($request_id)) {
				notify($ERRORS{'OK'}, 0, "request has been deleted, request state not updated: $request_state_name_old --> $request_state_name_new");
			}
			else {
				# Check if the request state has already been updated
				# This can occur if another reservation in a cluster failed
				my ($request_state_name_current, $request_laststate_name_current) = get_request_current_state_name($request_id);
				if ($request_state_name_current eq $request_state_name_new && $request_laststate_name_current eq $request_state_name_old) {
					notify($ERRORS{'OK'}, 0, "request has NOT been deleted, current state already set to: $request_state_name_current/$request_laststate_name_current");
				}
				else {
					notify($ERRORS{'OK'}, 0, "request has NOT been deleted, updating request state: $request_state_name_old/$request_laststate_name_old --> $request_state_name_new/$request_state_name_old");
					if (!update_request_state($request_id, $request_state_name_new, $request_state_name_old)) {
						notify($ERRORS{'WARNING'}, 0, "failed to change request state: $request_state_name_old/$request_laststate_name_old --> $request_state_name_new/$request_state_name_old");
					}
				}
			}
		}
		else {
			# Current request state = 'deleted', always set the request state to 'complete'
			if (!update_request_state($request_id, 'complete', $request_state_name_old)) {
				notify($ERRORS{'WARNING'}, 0, "failed to change request state: $request_state_name_old/$request_laststate_name_old --> $request_state_name_new/$request_state_name_old");
			}
		}
	}
	
	
	# Don't call exit if this was called from DESTROY or else DESTROY gets called again
	if ($calling_sub) {
		if ($calling_sub =~ /DESTROY/) {
			notify($ERRORS{'DEBUG'}, 0, "calling subroutine: $calling_sub, skipping call to exit");
			return;
		}
		else {
			notify($ERRORS{'DEBUG'}, 0, "calling subroutine: $calling_sub, calling exit");
		}
	}
	else {
		notify($ERRORS{'DEBUG'}, 0, "calling subroutine not defined, calling exit");
	}
	exit;
}