| #!/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::inuse - Perl module for the VCL inuse state |
| |
| =head1 SYNOPSIS |
| |
| use VCL::inuse; |
| 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::inuse object based on the request information |
| my $inuse = VCL::inuse->new($request_info); |
| |
| $inuse->process(); |
| |
| =head1 DESCRIPTION |
| |
| This module supports the VCL "inuse" state. The inuse state is reached after a |
| user has made a reservation, acknowledged the reservation by clicking the |
| "Connect" button, and connected to the remote computer. |
| |
| If the "checkuser" flag is set for the image that the user requested, |
| this process will periodically check to make sure the user is still |
| connected. Users have 15 minutes to reconnect before the machine is |
| reclaimed. |
| |
| This module periodically checks the end time for the user's request versus |
| the current time. If the end time is near, notifications may be sent to the |
| user. Once the end time has been reached, the user is disconnected and the |
| request and computer are put into the "timeout" state. |
| |
| =cut |
| |
| ############################################################################### |
| package VCL::inuse; |
| |
| # 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.5'; |
| |
| # Specify the version of Perl to use |
| use 5.008000; |
| |
| use strict; |
| use warnings; |
| use diagnostics; |
| |
| use POSIX qw(ceil floor strftime); |
| use VCL::utils; |
| |
| ############################################################################### |
| |
| =head1 OBJECT METHODS |
| |
| =cut |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 process |
| |
| Parameters : none |
| Returns : exits |
| Description : Processes a reservation in the inuse state. |
| |
| =cut |
| |
| sub process { |
| my $self = shift; |
| |
| my $request_id = $self->data->get_request_id(); |
| my $request_state_name = $self->data->get_request_state_name(); |
| my $request_start = $self->data->get_request_start_time(); |
| my $request_end = $self->data->get_request_end_time(); |
| my $request_forimaging = $self->data->get_request_forimaging(); |
| 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 $server_request_id = $self->data->get_server_request_id(); |
| my $imagemeta_checkuser = $self->data->get_imagemeta_checkuser(); |
| my $is_parent_reservation = $self->data->is_parent_reservation(); |
| my $computer_id = $self->data->get_computer_id(); |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| my $connect_timeout_seconds = $self->os->get_timings('reconnecttimeout'); |
| |
| # Check if reboot operation was requested |
| if ($request_state_name =~ /reboot/) { |
| if ($self->os->can('reboot')) { |
| if (!$self->os->reboot()) { |
| notify($ERRORS{'CRITICAL'}, 0, "user requested reboot of $computer_short_name failed"); |
| } |
| } |
| else { |
| notify($ERRORS{'CRITICAL'}, 0, "'$request_state_name' operation requested, " . ref($self->os) . " does not implement a 'reboot' subroutine"); |
| } |
| |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # Check if server reservation has been modified |
| if ($request_state_name =~ /servermodified/) { |
| if (!$self->os->add_user_accounts()) { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to update server access"); |
| } |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # Make sure connect timeout is long enough |
| # It has to be a bit longer than the ~5 minute period between inuse checks due to cluster reservations |
| # If too short, a user may be connected to one computer in a cluster and another inuse process times out before the connected computer is checked |
| my $connect_timeout_minutes = ceil($connect_timeout_seconds / 60); |
| |
| # Connect timeout must be in whole minutes |
| $connect_timeout_seconds = ($connect_timeout_minutes * 60); |
| |
| my $now_epoch_seconds = time; |
| |
| my $request_start_epoch_seconds = convert_to_epoch_seconds($request_start); |
| my $request_end_epoch_seconds = convert_to_epoch_seconds($request_end); |
| |
| my $request_remaining_seconds = ($request_end_epoch_seconds - $now_epoch_seconds); |
| my $request_remaining_minutes = floor($request_remaining_seconds / 60); |
| |
| my $request_duration_seconds = ($request_end_epoch_seconds - $request_start_epoch_seconds); |
| my $request_duration_hours = floor($request_duration_seconds / 60 / 60); |
| |
| my $end_time_notify_seconds = $self->os->get_timings('general_end_notice_first'); |
| my $end_time_notify_minutes = floor($end_time_notify_seconds / 60); |
| my $second_end_time_notify_seconds = $self->os->get_timings('general_end_notice_second'); |
| my $second_end_time_notify_minutes = floor($second_end_time_notify_seconds / 60); |
| |
| my $now_string = strftime('%H:%M:%S', localtime($now_epoch_seconds)); |
| my $request_end_string = strftime('%H:%M:%S', localtime($request_end_epoch_seconds)); |
| my $request_remaining_string = strftime('%H:%M:%S', gmtime($request_remaining_seconds)); |
| my $end_time_notify_string = strftime('%H:%M:%S', gmtime($end_time_notify_seconds)); |
| my $connect_timeout_string = strftime('%H:%M:%S', gmtime($connect_timeout_seconds)); |
| |
| # Check if near the end time |
| # Compare remaining minutes to connect timeout minutes in case this is > 15 minutes |
| if ($request_remaining_minutes <= ($end_time_notify_minutes + 6)) { |
| # Only 1 reservation needs to handle the end time countdown |
| if (!$is_parent_reservation) { |
| notify($ERRORS{'OK'}, 0, "request end time countdown handled by parent reservation, exiting"); |
| $self->state_exit(); |
| } |
| |
| my $sleep_seconds = ($request_remaining_seconds - $end_time_notify_seconds); |
| if ($sleep_seconds > 0) { |
| my $sleep_string = strftime('%H:%M:%S', gmtime($sleep_seconds)); |
| notify($ERRORS{'OK'}, 0, "request end time is near, sleeping for $sleep_seconds seconds:\n" . |
| "current time : $now_string\n" . |
| "request end time : $request_end_string\n" . |
| "remaining time : $request_remaining_string\n" . |
| "notify time : $end_time_notify_string\n" . |
| "sleep time : $sleep_string" |
| ); |
| sleep $sleep_seconds; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "request notify end time has passed:\n" . |
| "current time : $now_string\n" . |
| "request end time : $request_end_string\n" . |
| "remaining time : $request_remaining_string\n" . |
| "notify time : $end_time_notify_string" |
| ); |
| } |
| |
| # Loop for $end_time_notify_minutes regardless of how much time is actually left |
| # The time until request.end may be short if vcld wasn't running |
| # This gives the user notice that the request is ending |
| for (my $iteration = 0; $iteration <= $end_time_notify_minutes; ++$iteration) { |
| $request_remaining_minutes = ($end_time_notify_minutes - $iteration); |
| notify($ERRORS{'OK'}, 0, "minutes until end of end of request: $request_remaining_minutes"); |
| |
| # Check if the request state changed for any reason |
| # This will occur if the user deletes the request, makeproduction is initiated, reboot is initiated, image capture is started |
| if ($self->request_state_changed()) { |
| $self->state_exit(); |
| } |
| |
| # Get the current request end time from the database |
| my $current_request_end = get_request_end($request_id); |
| my $current_request_end_epoch_seconds = convert_to_epoch_seconds($current_request_end); |
| |
| # Check if the user extended the request |
| if ($current_request_end_epoch_seconds > $request_end_epoch_seconds) { |
| notify($ERRORS{'OK'}, 0, "user extended request, end time: $request_end --> $current_request_end, returning request to inuse state"); |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # Notify user when 5 or 10 minutes remain |
| if ($request_remaining_minutes == $second_end_time_notify_minutes || $request_remaining_minutes == $end_time_notify_minutes) { |
| $self->notify_user_endtime_imminent("$request_remaining_minutes minutes"); |
| } |
| |
| if ($iteration < $end_time_notify_minutes) { |
| notify($ERRORS{'OK'}, 0, "sleeping for 60 seconds"); |
| sleep 60; |
| } |
| } |
| |
| # Notify user - endtime and image capture has started |
| $self->notify_user_endtime_reached(); |
| |
| # Initiate auto-capture process if this is an imaging request and not a cluster reservation |
| if ($request_forimaging && $reservation_count == 1) { |
| notify($ERRORS{'OK'}, 0, "initiating image auto-capture process"); |
| if (!$self->start_imaging_request()) { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to initiate image auto-capture process, changing request and computer state to maintenance"); |
| $self->state_exit('maintenance', 'maintenance'); |
| } |
| #Successful, cleanly exit with no state change |
| $self->state_exit() |
| } |
| |
| $self->state_exit('timeout', 'timeout', 'EOR'); |
| } |
| |
| # If duration is greater than 24 hours perform end time notice checks |
| if ($is_parent_reservation && $request_duration_hours >= 24) { |
| notify($ERRORS{'DEBUG'}, 0, "checking end time notice interval, request duration: $request_duration_hours hours, parent reservation: $is_parent_reservation"); |
| # Check end time for a notice interval - returns 0 if no notice is to be given |
| my $notice_interval = check_endtimenotice_interval($request_end); |
| if ($notice_interval) { |
| $self->notify_user_future_endtime($notice_interval); |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "skipping end time notice interval check, request duration: $request_duration_hours hours, parent reservation: $is_parent_reservation"); |
| } |
| |
| # Check if the computer is responding to SSH |
| # Skip connection checks if the computer is not responding to SSH |
| # This prevents a reservatino from timing out if the user is actually connected but SSH from the management node isn't working |
| if (!$self->os->is_ssh_responding()) { |
| notify($ERRORS{'OK'}, 0, "$computer_short_name is not responding to SSH, skipping user connection check"); |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # Update the firewall if necessary - this is what allows a user to click Connect from different locations |
| if ($self->os->can('firewall_compare_update')) { |
| $self->os->firewall_compare_update(); |
| } |
| |
| # Compare remaining minutes to connect timeout |
| # Connect timeout may be longer than 15 minutes |
| # Make sure connect timeout doesn't run into the end time notice |
| if ($request_remaining_minutes < ($connect_timeout_minutes + $end_time_notify_minutes)) { |
| notify($ERRORS{'DEBUG'}, 0, "skipping user connection check, connect timeout would run into the end time notice stage:\n" . |
| "current time : $now_string\n" . |
| "request end time : $request_end_string\n" . |
| "remaining time : $request_remaining_string\n" . |
| "notify time : $end_time_notify_string\n" . |
| "connect timeout : $connect_timeout_string" |
| ); |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # TODO: fix user connection checking for cluster requests |
| if ($reservation_count > 1) { |
| notify($ERRORS{'OK'}, 0, "skipping user connection check for cluster request"); |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| # Insert reconnecttimeout immediately before beginning to check for user connection |
| # 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, "reconnecttimeout", "begin reconnection timeout ($connect_timeout_seconds seconds)"); |
| |
| # 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 user to connect to $computer_short_name", $connect_timeout_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 ($request_forimaging) { |
| notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, imaging reservation"); |
| } |
| elsif ($reservation_count > 1) { |
| notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, cluster reservation"); |
| } |
| elsif ($request_duration_hours > 24) { |
| notify($ERRORS{'OK'}, 0, "never detected user connection, skipping timeout, request duration: $request_duration_hours hours"); |
| } |
| elsif (is_request_deleted($request_id) || $self->request_state_changed()) { |
| $self->state_exit(); |
| } |
| else { |
| # Update reservation lastcheck, otherwise request will be processed immediately again |
| update_reservation_lastcheck($reservation_id); |
| |
| $self->notify_user_timeout_inactivity(); |
| $self->state_exit('timeout', 'inuse', 'timeout'); |
| } |
| } |
| |
| $self->state_exit('inuse', 'inuse'); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 notify_user_future_endtime |
| |
| Parameters : $notice_interval |
| Returns : boolean |
| Description : Notifies the user how long they have until the end of the |
| request. A notice interval argument must be passed. Its value |
| gets inserted directly in the message sent to the user and should |
| contain something like "5 minutes". |
| |
| =cut |
| |
| sub notify_user_future_endtime { |
| my $self = shift; |
| |
| # Check to make sure notice interval is set |
| my $notice_interval = shift; |
| if (!defined($notice_interval)) { |
| notify($ERRORS{'WARNING'}, 0, "end time message not set, notice interval was not passed"); |
| return 0; |
| } |
| # Set the notice interval in the DataStructure object so that the text can contain [notice_interval] |
| $self->data->set_notice_interval($notice_interval); |
| |
| my $is_parent_reservation = $self->data->is_parent_reservation(); |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| my $image_os_type = $self->data->get_image_os_type(); |
| my $user_affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress(); |
| my $user_login_id = $self->data->get_user_login_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() || 'none';; |
| my $user_im_id = $self->data->get_user_im_id(); |
| |
| my $user_message_key = 'future_endtime'; |
| |
| # Send a message to the user notifying them the reservation end time is coming up |
| if ($is_parent_reservation && $user_emailnotices) { |
| my ($user_subject, $user_message) = $self->get_user_message($user_message_key); |
| if (defined($user_subject) && defined($user_message)) { |
| mail($user_email, $user_subject, $user_message, $user_affiliation_helpaddress); |
| } |
| } |
| |
| my $user_short_message = $self->get_user_short_message($user_message_key); |
| if ($user_short_message) { |
| # Display a message on the console or desktop if the OS module supports it |
| if ($self->os->can('notify_user_console')) { |
| $self->os->notify_user_console($user_short_message); |
| } |
| |
| # TODO: move this to OS module |
| if ($image_os_type =~ /osx/) { |
| # Mac images only, notify via oascript |
| notify_via_oascript($computer_short_name, $user_login_id, $user_short_message); |
| } |
| |
| # Notify via IM |
| if ($user_imtype_name ne "none" && defined($user_im_id)) { |
| notify_via_im($user_imtype_name, $user_im_id, $user_short_message); |
| } |
| } |
| |
| return 1; |
| } ## end sub notify_user_future_endtime |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 notify_user_endtime_imminent |
| |
| Parameters : $notice_interval |
| Returns : boolean |
| Description : Notifies the user that the request end time will be reached and |
| the session will be disconnected soon. A notice interval argument |
| must be passed. Its value gets inserted directly in the message |
| sent to the user and should contain something like "5 minutes". |
| |
| =cut |
| |
| sub notify_user_endtime_imminent { |
| my $self = shift; |
| |
| # Check to make sure notice interval is set |
| my $notice_interval = shift; |
| if (!defined($notice_interval)) { |
| notify($ERRORS{'WARNING'}, 0, "disconnect time message not set, notice interval was not passed"); |
| return 0; |
| } |
| # Set the notice interval in the DataStructure object so that the text can contain [notice_interval] |
| $self->data->set_notice_interval($notice_interval); |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| my $image_os_type = $self->data->get_image_os_type(); |
| my $user_affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress(); |
| my $user_login_id = $self->data->get_user_login_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() || 'none';; |
| my $user_im_id = $self->data->get_user_im_id(); |
| my $is_parent_reservation = $self->data->is_parent_reservation(); |
| my $request_forimaging = $self->data->get_request_forimaging(); |
| |
| my $user_message_key; |
| if ($request_forimaging) { |
| $user_message_key = 'endtime_imminent_imaging'; |
| } |
| else { |
| $user_message_key = 'endtime_imminent'; |
| } |
| |
| |
| # Send a message to the user notifying them the reservation end time is close |
| if ($is_parent_reservation && $user_emailnotices) { |
| my ($user_subject, $user_message) = $self->get_user_message($user_message_key); |
| if (defined($user_subject) && defined($user_message)) { |
| mail($user_email, $user_subject, $user_message, $user_affiliation_helpaddress); |
| } |
| } |
| |
| my $user_short_message = $self->get_user_short_message($user_message_key); |
| if ($user_short_message) { |
| # Display a message on the console or desktop if the OS module supports it |
| if ($self->os->can('notify_user_console')) { |
| $self->os->notify_user_console($user_short_message); |
| } |
| |
| # TODO: move this to OS module |
| if ($image_os_type =~ /osx/) { |
| # Mac images only, notify via oascript |
| notify_via_oascript($computer_short_name, $user_login_id, $user_short_message); |
| } |
| |
| # Notify via IM |
| if ($user_imtype_name ne "none" && defined($user_im_id)) { |
| notify_via_im($user_imtype_name, $user_im_id, $user_short_message); |
| } |
| } |
| |
| return 1; |
| } ## end sub notify_user_endtime_imminent |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 notify_user_timeout_inactivity |
| |
| Parameters : none |
| Returns : boolean |
| Description : Notifies the user that the session has timed out due to |
| inactivity. |
| |
| =cut |
| |
| sub notify_user_timeout_inactivity { |
| my $self = shift; |
| |
| my $user_affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress(); |
| 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() || 'none';; |
| my $user_im_id = $self->data->get_user_im_id(); |
| my $is_parent_reservation = $self->data->is_parent_reservation(); |
| |
| my $user_message_key = 'timeout_inactivity'; |
| my ($user_subject, $user_message) = $self->get_user_message($user_message_key); |
| |
| # Send a message to the user notifying them the reservation timed out |
| if ($is_parent_reservation && $user_emailnotices) { |
| if (defined($user_subject) && defined($user_message)) { |
| mail($user_email, $user_subject, $user_message, $user_affiliation_helpaddress); |
| } |
| } |
| |
| # Notify the user via IM |
| if ($user_imtype_name ne "none" && defined($user_im_id)) { |
| notify_via_im($user_imtype_name, $user_im_id, $user_message); |
| } |
| |
| return 1; |
| } ## end sub notify_user_timeout_inactivity |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 notify_user_endtime_reached |
| |
| Parameters : none |
| Returns : boolean |
| Description : Notifies the user that the request has ended because the end |
| time was reached. |
| |
| =cut |
| |
| sub notify_user_endtime_reached { |
| my $self = shift; |
| |
| my $request_forimaging = $self->data->get_request_forimaging(); |
| my $user_affiliation_helpaddress = $self->data->get_user_affiliation_helpaddress(); |
| 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() || 'none';; |
| my $user_im_id = $self->data->get_user_im_id(); |
| my $is_parent_reservation = $self->data->is_parent_reservation(); |
| |
| my $user_message_key; |
| if ($request_forimaging) { |
| $user_message_key = 'endtime_reached_imaging'; |
| } |
| else { |
| $user_message_key = 'endtime_reached'; |
| } |
| |
| my ($user_subject, $user_message) = $self->get_user_message($user_message_key); |
| if (!defined($user_subject) || !defined($user_message)) { |
| return; |
| } |
| |
| # Send a message to the user notifying them the reservation ended |
| if ($is_parent_reservation && $user_emailnotices) { |
| mail($user_email, $user_subject, $user_message, $user_affiliation_helpaddress); |
| } |
| |
| # Notify via IM |
| if ($user_imtype_name ne "none" && defined($user_im_id)) { |
| notify_via_im($user_imtype_name, $user_im_id, $user_message); |
| } |
| |
| return 1; |
| } ## end sub notify_user_endtime_reached |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 start_imaging_request |
| |
| Parameters : none |
| Returns : boolean |
| Description : Inserts an "autocapture" imaging request is imaging request times |
| out. |
| |
| =cut |
| |
| sub start_imaging_request { |
| my $self = shift; |
| |
| my $request_id = $self->data->get_request_id(); |
| |
| my $method = "XMLRPCautoCapture"; |
| my @argument_string = ($method, $request_id); |
| my $xml_ret = xmlrpc_call(@argument_string); |
| |
| # Check if the XML::RPC call failed |
| if (!defined($xml_ret)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to start imaging request, XML::RPC '$method' call failed"); |
| return; |
| } |
| elsif ($xml_ret->value->{status} !~ /success/) { |
| notify($ERRORS{'WARNING'}, 0, "failed to start imaging request, XML::RPC '$method' status: $xml_ret->value->{status}\n" . |
| "error code $xml_ret->value->{errorcode}\n" . |
| "error message: $xml_ret->value->{errormsg}" |
| ); |
| return; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| 1; |
| __END__ |
| |
| =head1 SEE ALSO |
| |
| L<http://cwiki.apache.org/VCL/> |
| |
| =cut |