| #!/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 |