blob: 9a2058a0fcca8cd7e62b9c704b068f3d0afa328c [file] [log] [blame]
#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
=head1 NAME
VCL::reserved - Perl module for the VCL reserved state
=head1 SYNOPSIS
use VCL::reserved;
use VCL::utils;
# Set variables containing the IDs of the request and reservation
my $request_id = 5;
my $reservation_id = 6;
# Call the VCL::utils::get_request_info subroutine to populate a hash
my $request_info = get_request_info($request_id);
# Set the reservation ID in the hash
$request_info->{RESERVATIONID} = $reservation_id;
# Create a new VCL::reserved object based on the request information
my $reserved = VCL::reserved->new($request_info);
=head1 DESCRIPTION
This module supports the VCL "reserved" state. The reserved state is reached
after a computer has been loaded. This module checks if the user has
acknowledged the reservation by clicking the Connect button and has connected
to the computer. Once connected, the reservation will be put into the "inuse"
state and the reserved process exits.
=cut
##############################################################################
package VCL::reserved;
# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/..";
# Configure inheritance
use base qw(VCL::Module::State);
# Specify the version of this module
our $VERSION = '2.4.1';
# Specify the version of Perl to use
use 5.008000;
use strict;
use warnings;
use diagnostics;
use VCL::utils;
use POSIX qw(strftime);
##############################################################################
=head1 OBJECT METHODS
=cut
#/////////////////////////////////////////////////////////////////////////////
=head2 process
Parameters : none
Returns : exits
Description : Processes a reservation in the reserved state. Waits for user
acknowledgement and connection.
=cut
sub process {
my $self = shift;
my $request_id = $self->data->get_request_id();
my $request_data = $self->data->get_request_data();
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 $server_request_id = $self->data->get_server_request_id();
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);
# 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");
# 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)");
# Wait for the user to acknowledge the request by clicking Connect button or from API
my $user_acknowledged = $self->code_loop_timeout(sub{$self->user_acknowledged()}, [], 'waiting for user acknowledgement', $acknowledge_timeout_seconds, 1, 10);
if (!$user_acknowledged) {
$self->_notify_user_timeout($request_data);
$self->state_exit('timeout', 'reserved', 'noack');
}
# Add noinitialconnection and then delete acknowledgetimeout
insertloadlog($reservation_id, $computer_id, "noinitialconnection", "user clicked Connect");
delete_computerloadlog_reservation($reservation_id, 'acknowledgetimeout');
# The frontend should have inserted an 'initialconnecttimeout' computerloadlog entry for the parent reservation, retrieve its timestamp
my $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 {
$connection_check_start_epoch_seconds = time;
notify($ERRORS{'WARNING'}, 0, "failed to retrieve timestamp of computerloadlog 'initialconnecttimeout' entry, web frontend should have inserted this, inserting new entry");
# initialconnecttimeout should be inserted immediately after user acknowledged
# 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
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");
}
# 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
my $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);
my $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 ($server_request_id) {
notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, server reservation");
}
elsif (is_request_deleted($request_id)) {
$self->state_exit();
}
else {
$self->_notify_user_no_login();
$self->state_exit('timeout', 'reserved', 'nologin');
}
}
# 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->process_connect_methods($remote_ip, 1)) {
notify($ERRORS{'CRITICAL'}, 0, "failed to process connect methods after user connected to computer");
}
# Run custom post_initial_connection scripts residing on the management node
$self->os->run_management_node_tools_scripts('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
#/////////////////////////////////////////////////////////////////////////////
=head2 wait_for_child_reservations
Parameters : none
Returns : boolean
Description : Checks if all child reservation 'reserved' processes have
completed.
=cut
sub wait_for_child_reservations {
my $self = shift;
my $request_id = $self->data->get_request_id();
exit if is_request_deleted($request_id);
# Check if 'reserved' computerloadlog entry exists for all reservations
my $request_loadstate_names = get_request_loadstate_names($request_id);
if (!$request_loadstate_names) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve request loadstate names");
return;
}
my @reserved_exists;
my @reserved_does_not_exist;
my @failed;
for my $reservation_id (keys %$request_loadstate_names) {
my @loadstate_names = @{$request_loadstate_names->{$reservation_id}};
if (grep { $_ eq 'postreserve' } @loadstate_names) {
push @reserved_exists, $reservation_id;
}
else {
push @reserved_does_not_exist, $reservation_id;
}
if (grep { $_ eq 'failed' } @loadstate_names) {
push @failed, $reservation_id;
}
}
# Check if any child reservations failed
if (@failed) {
$self->reservation_failed("child reservation reserve process failed: " . join(', ', @failed));
return;
}
if (@reserved_does_not_exist) {
notify($ERRORS{'DEBUG'}, 0, "computerloadlog 'postreserve' entry does NOT exist for all reservations:\n" .
"exists for reservation IDs: " . join(', ', @reserved_exists) . "\n" .
"does not exist for reservation IDs: " . join(', ', @reserved_does_not_exist)
);
return 0;
}
else {
notify($ERRORS{'DEBUG'}, 0, "computerloadlog 'postreserve' entry exists for all reservations");
}
notify($ERRORS{'DEBUG'}, 0, "all child reservation reserved processes have completed");
return 1;
}
#/////////////////////////////////////////////////////////////////////////////
=head2 user_acknowledged
Parameters : none
Returns : boolean
Description : Used as a helper function to the call to code_loop_timeout() in
process. First checks if the request has been deleted. If so, the
process exits. If not deleted, checks if the user has
acknowledged the request by checking if reservation.remoteip is
set.
=cut
sub user_acknowledged {
my $self = shift;
if (ref($self) !~ /VCL::reserved/) {
notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a class method of a VCL::reserved object");
return;
}
my $request_id = $self->data->get_request_id();
# Check if user deleted the request
if (is_request_deleted($request_id)) {
notify($ERRORS{'DEBUG'}, 0, "request deleted, exiting");
exit;
}
my $remote_ip = $self->data->get_reservation_remote_ip();
if ($remote_ip) {
notify($ERRORS{'DEBUG'}, 0, "user acknowledged from remote IP address: $remote_ip");
return 1;
}
else {
return 0;
}
}
#/////////////////////////////////////////////////////////////////////////////
=head2 _notify_user_ready
Parameters : none
Returns : boolean
Description : Notifies the user that the reservation is ready.
=cut
sub _notify_user_ready {
my $self = shift;
#my $request_id = $self->data->get_request_id();
my $request_state_name = $self->data->get_request_id();
#my $reservation_id = $self->data->get_reservation_id();
my $user_email = $self->data->get_user_email();
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();
my $affiliation_sitewwwaddress = $self->data->get_user_affiliation_sitewwwaddress();
my $affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress();
my $image_prettyname = $self->data->get_image_prettyname();
my $is_parent_reservation = $self->data->is_parent_reservation();
my $mailstring;
my $subject;
# Assemble the message body reservations
if ($request_state_name =~ /^(reinstall)$/) {
$subject = "VCL -- $image_prettyname reservation reinstalled";
$mailstring = <<"EOF";
Your reservation was successfully reinstalled and you can proceed to reconnect.
Please revisit the 'Current Reservations' page for any additional information.
EOF
}
else {
$subject = "VCL -- $image_prettyname reservation";
$mailstring = <<"EOF";
The resources for your VCL reservation have been successfully reserved.
Connection will not be allowed until you click the 'Connect' button on the 'Current Reservations' page.
You must acknowledge the reservation within the next 15 minutes or the resources will be reclaimed for other VCL users.
-Visit $affiliation_sitewwwaddress
-Select "Current Reservations"
-Click the "Connect" button
Upon acknowledgement, all of the remaining connection details will be displayed.
EOF
}
$mailstring .= <<"EOF";
Thank You,
VCL Team
******************************************************************
This is an automated notice. If you need assistance please respond
with detailed information on the issue and a help ticket will be
generated.
To disable email notices
-Visit $affiliation_sitewwwaddress
-Select User Preferences
-Select General Preferences
******************************************************************
EOF
if ($is_parent_reservation && $user_emailnotices) {
mail($user_email, $subject, $mailstring, $affiliation_helpaddress);
}
else {
# For email record keeping
notify($ERRORS{'MAILMASTERS'}, 0, " $user_email\n$mailstring");
}
if ($user_imtype_name ne "none") {
notify_via_im($user_imtype_name, $user_im_id, $mailstring, $affiliation_helpaddress);
}
return 1;
} ## end sub _notify_user_no_login
#/////////////////////////////////////////////////////////////////////////////
=head2 _notify_user_no_login
Parameters : none
Returns : boolean
Description : Notifies the user that the request has timed out becuase no
initial connection was made. An e-mail and/or IM message will
be sent to the user.
=cut
sub _notify_user_no_login {
my $self = shift;
my $request_id = $self->data->get_request_id();
my $reservation_id = $self->data->get_reservation_id();
my $user_email = $self->data->get_user_email();
my $user_emailnotices = $self->data->get_user_emailnotices();
my $user_im_name = $self->data->get_user_imtype_name();
my $user_im_id = $self->data->get_user_im_id();
my $affiliation_sitewwwaddress = $self->data->get_user_affiliation_sitewwwaddress();
my $affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress();
my $image_prettyname = $self->data->get_image_prettyname();
my $is_parent_reservation = $self->data->is_parent_reservation();
my $message = <<"EOF";
Your reservation has timed out for image $image_prettyname because no initial connection was made.
To make another reservation, please revisit $affiliation_sitewwwaddress.
Thank You,
VCL Team
******************************************************************
This is an automated notice. If you need assistance
please respond with detailed information on the issue
and a help ticket will be generated.
To disable email notices
-Visit $affiliation_sitewwwaddress
-Select User Preferences
-Select General Preferences
******************************************************************
EOF
my $subject = "VCL -- Reservation Timeout";
if ($is_parent_reservation && $user_emailnotices) {
#if "0" user does not care to get additional notices
mail($user_email, $subject, $message, $affiliation_helpaddress);
notify($ERRORS{'OK'}, 0, "sent reservation timeout e-mail to $user_email");
}
if ($user_im_name ne "none") {
notify_via_im($user_im_name, $user_im_id, $message);
notify($ERRORS{'OK'}, 0, "sent reservation timeout IM to $user_im_name");
}
return 1;
} ## end sub _notify_user_no_login
#/////////////////////////////////////////////////////////////////////////////
=head2 _notify_user_timeout
Parameters : none
Returns : boolean
Description : Notifies the user that the request has timed out becuase no
initial connection was made. An e-mail and/or IM message will
be sent to the user.
=cut
sub _notify_user_timeout {
my $self = shift;
my $request_id = $self->data->get_request_id();
my $reservation_id = $self->data->get_reservation_id();
my $user_email = $self->data->get_user_email();
my $user_emailnotices = $self->data->get_user_emailnotices();
my $user_im_name = $self->data->get_user_imtype_name();
my $user_im_id = $self->data->get_user_im_id();
my $affiliation_sitewwwaddress = $self->data->get_user_affiliation_sitewwwaddress();
my $affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress();
my $image_prettyname = $self->data->get_image_prettyname();
my $is_parent_reservation = $self->data->is_parent_reservation();
my $message = <<"EOF";
Your reservation has timed out for image $image_prettyname because no initial connection was made.
To make another reservation, please revisit $affiliation_sitewwwaddress.
Thank You,
VCL Team
******************************************************************
This is an automated notice. If you need assistance
please respond with detailed information on the issue
and a help ticket will be generated.
To disable email notices
-Visit $affiliation_sitewwwaddress
-Select User Preferences
-Select General Preferences
******************************************************************
EOF
my $subject = "VCL -- Reservation Timeout";
if ($is_parent_reservation && $user_emailnotices) {
#if "0" user does not care to get additional notices
mail($user_email, $subject, $message, $affiliation_helpaddress);
notify($ERRORS{'OK'}, 0, "sent reservation timeout e-mail to $user_email");
}
if ($user_im_name ne "none") {
notify_via_im($user_im_name, $user_im_id, $message);
notify($ERRORS{'OK'}, 0, "sent reservation timeout IM to $user_im_name");
}
return 1;
} ## end sub _notify_user_timeout
#/////////////////////////////////////////////////////////////////////////////
1;
__END__
=head1 SEE ALSO
L<http://cwiki.apache.org/VCL/>
=cut