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;
}