#!/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::Module::OS.pm - VCL base operating system module =head1 SYNOPSIS Needs to be written =head1 DESCRIPTION This module provides VCL support operating systems. =cut ############################################################################### package VCL::Module::OS; # Specify the lib path using FindBin use FindBin; use lib "$FindBin::Bin/../.."; # Configure inheritance use base qw(VCL::Module); # 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 English '-no_match_vars'; use File::Temp qw(tempdir); use POSIX qw(tmpnam); use Net::SSH::Expect; use List::Util qw(min max); use VCL::utils; ############################################################################### =head1 OBJECT METHODS =cut #////////////////////////////////////////////////////////////////////////////// =head2 pre_capture Parameters : $arguments->{end_state} Returns : boolean Description : Performs the tasks common to all OS's that must be done to the computer prior to capturing an image: * Check if the computer is responding to SSH * If not responding, check if computer is powered on * Power on computer if powered off and wait for SSH to respond * Create currentimage.txt file =cut sub pre_capture { my $self = shift; my $args = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name = $self->data->get_computer_node_name(); notify($ERRORS{'OK'}, 0, "beginning common image capture preparation tasks"); # Make sure the computer is responding to SSH # If it is not, check if it is powered on if (!$self->is_ssh_responding()) { notify($ERRORS{'OK'}, 0, "$computer_node_name is not responding to SSH, checking if it is powered on"); my $power_status = $self->provisioner->power_status(); if (!$power_status) { notify($ERRORS{'WARNING'}, 0, "unable to complete capture preparation tasks, $computer_node_name is not responding to SSH and the power status could not be determined"); return; } elsif ($power_status =~ /on/i) { notify($ERRORS{'WARNING'}, 0, "unable to complete capture preparation tasks, $computer_node_name is powered on but not responding to SSH"); return; } else { notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is powered off, attempting to power it on"); if (!$self->provisioner->power_on()) { notify($ERRORS{'WARNING'}, 0, "unable to complete capture preparation tasks, $computer_node_name could not be powered on"); return; } # Wait for computer to respond to SSH if (!$self->wait_for_response(30, 300, 10)) { notify($ERRORS{'WARNING'}, 0, "unable to complete capture preparation tasks, $computer_node_name never responded to SSH after it was powered on"); return; } } } # Delete an existing node configuration directory to clear out any scripts and log files from a previous image revision my $node_configuration_directory = $self->get_node_configuration_directory(); if ($node_configuration_directory) { $self->delete_file($node_configuration_directory); } # Create the currentimage.txt file if (!$self->create_currentimage_txt()) { notify($ERRORS{'WARNING'}, 0, "failed to create currentimage.txt on $computer_node_name"); return 0; } # Run custom pre_capture scripts $self->run_stage_scripts('pre_capture'); # Delete reservation_info.json my $reservation_info_json_file_path = $self->get_reservation_info_json_file_path(); if ($reservation_info_json_file_path) { $self->delete_file($reservation_info_json_file_path); } notify($ERRORS{'OK'}, 0, "completed common image capture preparation tasks"); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 post_capture Parameters : none Returns : boolean Description : Performs the tasks common to all OS's that must be done to the computer after capturing an image: * Runs post_capture stage scripts on the management node if any exist =cut sub post_capture { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Run custom post_capture scripts $self->run_stage_scripts('post_capture'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 post_load Parameters : none Returns : boolean Description : Performs common OS steps after an image is loaded. =cut sub post_load { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Run custom post_load scripts $self->run_stage_scripts('post_load'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 reserve Parameters : none Returns : boolean Description : Performs common OS steps to reserve the computer for a user: * The public IP address is updated if necessary * If the computer is mapped to a NAT host: ** General NAT host configuration is performed if not previously done. ** The NAT host is prepared for the reservation but specific port forwardings are not added. * User accounts are added Note: The 'reserve' subroutine should never open the firewall for a connection. This is done by the 'grant_access' subroutine. =cut sub reserve { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name = $self->data->get_computer_node_name(); my $nathost_hostname = $self->data->get_nathost_hostname(0); # Make sure the public IP address assigned to the computer matches the database if (!$self->update_public_ip_address()) { notify($ERRORS{'WARNING'}, 0, "unable to reserve computer, failed to update IP address"); return; } # Check if the computer is mapped to a NAT host if ($nathost_hostname) { # Perform general NAT host configuration - this only needs to be done once, nat_configure_host checks if it was already done if (!$self->nathost_os->firewall->nat_configure_host()) { notify($ERRORS{'WARNING'}, 0, "unable to reserve $computer_node_name, failed to configure NAT on $nathost_hostname"); return; } # Perform reservation-specific NAT configuration if (!$self->nathost_os->firewall->nat_configure_reservation()) { notify($ERRORS{'WARNING'}, 0, "unable to reserve $computer_node_name, failed to configure NAT on $nathost_hostname for this reservation"); return; } } # Add user accounts to the computer if (!$self->add_user_accounts()) { notify($ERRORS{'WARNING'}, 0, "unable to reserve computer, failed add user accounts"); return; } return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 post_reserve Parameters : none Returns : boolean Description : Performs common OS steps after an image is loaded. =cut sub post_reserve { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } # Run custom post_reserve scripts $self->run_stage_scripts('post_reserve'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 post_initial_connection Parameters : none Returns : boolean Description : Performs common OS steps after a user makes an initial connection. =cut sub post_initial_connection { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Run custom post_initial_connection scripts $self->run_stage_scripts('post_initial_connection'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 post_reservation Parameters : none Returns : boolean Description : Performs common OS steps after a user's reservation is over. =cut sub post_reservation { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } # Run custom post_reservation scripts $self->run_stage_scripts('post_reservation'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 pre_reload Parameters : none Returns : boolean Description : Performs common OS steps prior to a computer being reloaded. =cut sub pre_reload { my $self = shift; if (ref($self) !~ /VCL::Module/) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } # Run custom pre_reload scripts $self->run_stage_scripts('pre_reload'); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 add_user_accounts Parameters : none Returns : boolean Description : Adds all user accounts to the computer for a reservation. The reservationaccounts table is checked. If the user already exists in the table, it is assumed the user was previously created and nothing is done. If the user doesn't exist in the table it is added. If an entry for a user exists in the reservationaccounts table but the user is not assigned to the reservation, the user is deleted from the computer. =cut sub add_user_accounts { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $request_user_id = $self->data->get_user_id(); my $request_state_name = $self->data->get_request_state_name(); my $reservation_id = $self->data->get_reservation_id(); my $reservation_users = $self->data->get_reservation_users(); my $reservation_password = $self->data->get_reservation_password(0); my $computer_node_name = $self->data->get_computer_node_name(); # Collect users in reservationaccounts table my $reservation_accounts = get_reservation_accounts($reservation_id); # Add users RESERVATION_USER: foreach my $user_id (sort keys %$reservation_users) { my $username = $reservation_users->{$user_id}{unityid}; my $uid = $reservation_users->{$user_id}{uid}; my $root_access = $reservation_users->{$user_id}{ROOTACCESS}; my $use_public_keys = $reservation_users->{$user_id}{usepublickeys}; # If the $use_public_keys flag is set, retrieve the keys my $ssh_public_keys; if ($use_public_keys) { $ssh_public_keys = $reservation_users->{$user_id}{sshpublickeys}; } my $password; # Check if entry needs to be added to the useraccounts table if (defined($reservation_accounts->{$user_id}) && ($request_state_name =~ /servermodified/)) { # Entry already exists in useraccounts table and is servermodified, assume everything is correct skip to next user notify($ERRORS{'DEBUG'}, 0, "entry already exists in useraccounts table for $username (ID: $user_id) and request_state_name = $request_state_name"); # Make sure user's root access is correct - may have been moved from admin to access group, and vice versa if ($root_access) { $self->grant_administrative_access($username) if ($self->can('grant_administrative_access')); } else { $self->revoke_administrative_access($username) if ($self->can('revoke_administrative_access')); } next RESERVATION_USER; } else { notify($ERRORS{'DEBUG'}, 0, "entry does not already exist in useraccounts table for $username (ID: $user_id)"); # Determine whether or not the user account's password should be set my $should_set_user_password = 1; if ($self->can('should_set_user_password')) { $should_set_user_password = $self->should_set_user_password($user_id); if (!defined($should_set_user_password)) { notify($ERRORS{'CRITICAL'}, 0, "failed to determine if user account password should be set, user ID $user_id, assuming password should be set"); $should_set_user_password = 1; } } if ($should_set_user_password) { # Check if this is the request owner user ID and the reservation password has already been set if ($user_id eq $request_user_id) { if ($reservation_password) { $password = $reservation_password; notify($ERRORS{'DEBUG'}, 0, "user $username (ID: $user_id) is request owner, using existing reservation password: $password"); } else { # Generate a new random password $password = getpw(); $self->data->set_reservation_password($password); notify($ERRORS{'DEBUG'}, 0, "user $username (ID: $user_id) is request owner, generated new password: $password"); # Update the password in the reservation table if (!update_reservation_password($reservation_id, $password)) { $self->reservation_failed("failed to update password in the reservation table"); return; } } } else { # Generate a new random password $password = getpw(); notify($ERRORS{'DEBUG'}, 0, "user $username (ID: $user_id) is not the request owner, generated new password: $password"); } } # Add an entry to the useraccounts table if (!add_reservation_account($reservation_id, $user_id, $password)) { notify($ERRORS{'CRITICAL'}, 0, "failed to add entry to reservationaccounts table for $username (ID: $user_id)"); return; } # Create user on the OS if (!$self->create_user({ username => $username, password => $password, root_access => $root_access, uid => $uid, ssh_public_keys => $ssh_public_keys, })) { notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, removing entry added to reservationaccounts table"); # Delete entry to the useraccounts table if (!delete_reservation_account($reservation_id, $user_id)) { notify($ERRORS{'CRITICAL'}, 0, "failed to delete entry from reservationaccounts table for $username (ID: $user_id)"); } return; } } } # Remove anyone listed in reservationaccounts that is not a reservation user foreach my $user_id (sort keys %$reservation_accounts) { if (defined($reservation_users->{$user_id})) { next; } my $username = $reservation_accounts->{$user_id}{username}; notify($ERRORS{'OK'}, 0, "user $username (ID: $user_id) exists in reservationsaccounts table but is not assigned to this reservation, attempting to delete user"); # Delete the user from OS if (!$self->delete_user($username)) { notify($ERRORS{'WARNING'}, 0, "failed to delete user $username (ID: $user_id) from $computer_node_name"); next; } # Delete entry from reservationaccounts if (!delete_reservation_account($reservation_id, $user_id)) { notify($ERRORS{'CRITICAL'}, 0, "failed to delete entry from reservationaccounts table for user $username (ID: $user_id)"); } } return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 delete_user_accounts Parameters : none Returns : boolean Description : Deletes all user accounts from the computer which are assigned to the reservation or an entry exists in the reservationaccounts table. =cut sub delete_user_accounts { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $reservation_id = $self->data->get_reservation_id(); my $computer_node_name = $self->data->get_computer_node_name(); my %username_hash; my $reservation_users = $self->data->get_reservation_users(); foreach my $user_id (sort keys %$reservation_users) { my $username = $reservation_users->{$user_id}{unityid}; $username_hash{$username} = $user_id; } # Collect users in reservationaccounts table my $reservation_accounts = get_reservation_accounts($reservation_id); foreach my $user_id (sort keys %$reservation_accounts) { my $username = $reservation_accounts->{$user_id}{username}; $username_hash{$username} = $user_id; } my $error_encountered = 0; # Delete users foreach my $username (sort keys %username_hash) { my $user_id = $username_hash{$username}; # Delete user on the OS if (!$self->delete_user($username)) { $error_encountered = 1; notify($ERRORS{'WARNING'}, 0, "failed to delete user on $computer_node_name"); } # Delete entry to the useraccounts table delete_reservation_account($reservation_id, $user_id); } return !$error_encountered; } #////////////////////////////////////////////////////////////////////////////// =head2 get_source_configuration_directories Parameters : None Returns : Array containing filesystem path strings Description : Retrieves the $SOURCE_CONFIGURATION_DIRECTORY variable value for the classes which the OS object is a member of and returns an array containing these values. The first element of the array contains the value from the top-most class where the $SOURCE_CONFIGURATION_DIRECTORY variable was defined. The last element contains the value from the bottom-most class, which is probably the class which was instantiated. Example: An Windows XP OS object is instantiated from the XP class, which is a subclass of the Version_5 class, which is a subclass of the Windows class: VCL::Module::OS::Windows ^ VCL::Module::OS::Windows::Version_5 ^ VCL::Module::OS::Windows::Version_5::XP The XP and Windows classes each have a $SOURCE_CONFIGURATION_DIRECTORY variable defined but the Version_5 class does not. The array returned will be: [0] = '/usr/local/vcldev/current/bin/../tools/Windows' [1] = '/usr/local/vcldev/current/bin/../tools/Windows_XP' =cut sub get_source_configuration_directories { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL module object method"); return; } # Get an array containing the names of the Perl packages the OS object is a class of my @package_hierarchy = $self->get_package_hierarchy(); # Loop through each classes, retrieve any which have a $SOURCE_CONFIGURATION_DIRECTORY variable defined my @directories = (); for my $package_name (@package_hierarchy) { my $source_configuration_directory = eval '$' . $package_name . '::SOURCE_CONFIGURATION_DIRECTORY'; if ($EVAL_ERROR) { notify($ERRORS{'WARNING'}, 0, "unable to determine source configuration directory for $package_name, error:\n$EVAL_ERROR"); next; } elsif (!$source_configuration_directory) { notify($ERRORS{'DEBUG'}, 0, "source configuration directory is not defined for $package_name"); next; } notify($ERRORS{'DEBUG'}, 0, "package source configuration directory: $source_configuration_directory"); # Add the directory path to the return array # Use unshift to add to the beginning to the array unshift @directories, $source_configuration_directory; } return @directories; } #////////////////////////////////////////////////////////////////////////////// =head2 create_currentimage_txt Parameters : None Returns : boolean Description : Creates the currentimage.txt file on the computer. =cut sub create_currentimage_txt { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_id = $self->data->get_image_id(); my $image_name = $self->data->get_image_name(); my $image_prettyname = $self->data->get_image_prettyname(); my $imagerevision_id = $self->data->get_imagerevision_id(); my $imagerevision_date_created = $self->data->get_imagerevision_date_created(); my $computer_id = $self->data->get_computer_id(); my $computer_host_name = $self->data->get_computer_host_name(); my $file_contents = <create_text_file('~/currentimage.txt', $file_contents)) { return 1; } else { notify($ERRORS{'WARNING'}, 0, "failed to create currentimage.txt file on $computer_host_name"); return; } } #////////////////////////////////////////////////////////////////////////////// =head2 get_currentimage_txt_contents Parameters : None Returns : If successful: array If failed: false Description : Reads the currentimage.txt file on a computer and returns its contents as an array. Each array element represents a line in the file. =cut sub get_currentimage_txt_contents { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name = $self->data->get_computer_node_name(); # Attempt to retrieve the contents of currentimage.txt my $cat_command = "cat ~/currentimage.txt"; my ($cat_exit_status, $cat_output) = $self->execute($cat_command,1); if (!defined($cat_output)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to failed to retrieve currentimage.txt from $computer_node_name"); return; } elsif ($cat_exit_status ne '0') { notify($ERRORS{'WARNING'}, 0, "failed to retrieve currentimage.txt from $computer_node_name, exit status: $cat_exit_status, output:\n@{$cat_output}"); return; } else { notify($ERRORS{'DEBUG'}, 0, "retrieved currentimage.txt contents from $computer_node_name:\n" . join("\n", @$cat_output)); } my %output; my @current_image_txt_contents = @{$cat_output}; my $current_image_name; if (defined $current_image_txt_contents[0]) { $output{"current_image_name"} = $current_image_txt_contents[0]; } foreach my $l (@current_image_txt_contents) { #remove any line break characters $l =~ s/[\r\n]*//g; my ($a, $b) = split(/=/, $l); if (defined $b) { $output{$a} = $b; } } return %output; } ## end sub get_currentimage_txt_contents #////////////////////////////////////////////////////////////////////////////// =head2 get_current_imagerevision_id Parameters : none Returns : integer Description : Retrieves the imagerevision ID value from currentimage.txt. =cut sub get_current_imagerevision_id { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } return $self->get_current_image_tag('imagerevision_id'); } #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_reboot Parameters : $total_wait_seconds, $attempt_delay_seconds, $attempt_limit Returns : boolean Description : Waits for the computer to become unresponsive, respond to ping, then respond to SSH. =cut sub wait_for_reboot { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name = $self->data->get_computer_node_name(); # Attempt to get the total number of seconds to wait from the arguments my $total_wait_seconds_argument = shift; if (!defined($total_wait_seconds_argument) || $total_wait_seconds_argument !~ /^\d+$/) { $total_wait_seconds_argument = 300; } # Seconds to wait in between loop attempts my $attempt_delay_seconds_argument = shift; if (!defined($attempt_delay_seconds_argument) || $attempt_delay_seconds_argument !~ /^\d+$/) { $attempt_delay_seconds_argument = 15; } # Number of power reset attempts to make if reboot fails my $attempt_limit = shift; if (!defined($attempt_limit) || $attempt_limit !~ /^\d+$/) { $attempt_limit = 2; } elsif (!$attempt_limit) { $attempt_limit = 1; } ATTEMPT: for (my $attempt = 1; $attempt <= $attempt_limit; $attempt++) { my $total_wait_seconds = $total_wait_seconds_argument; my $attempt_delay_seconds = $attempt_delay_seconds_argument; if ($attempt > 1) { # Computer did not become responsive on previous attempt notify($ERRORS{'OK'}, 0, "$computer_node_name reboot failed to complete on previous attempt, attempting hard power reset"); # Call provisioning module's power_reset() subroutine if ($self->provisioner->power_reset()) { notify($ERRORS{'OK'}, 0, "reboot attempt $attempt/$attempt_limit: initiated power reset on $computer_node_name"); } else { notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name"); return 0; } # Add 2 minutes for each attempt to $total_wait_seconds in case argument supplied wasn't long enough $total_wait_seconds += (120 * $attempt); } my $start_time = time; notify($ERRORS{'DEBUG'}, 0, "waiting for $computer_node_name to reboot: attempt: $attempt/$attempt_limit maximum wait time: $total_wait_seconds seconds wait delay: $attempt_delay_seconds"); # Wait for the computer to become unresponsive to ping if (!$self->wait_for_no_ping($total_wait_seconds, 5)) { # Computer never stopped responding to ping notify($ERRORS{'WARNING'}, 0, "$computer_node_name never became unresponsive to ping"); next ATTEMPT; } # Decrease $total_wait_seconds by the amount of time elapsed so far my $no_ping_elapsed_seconds = (time - $start_time); $total_wait_seconds -= $no_ping_elapsed_seconds; # Computer is unresponsive, reboot has begun # Wait 5 seconds before beginning to check if computer is back online notify($ERRORS{'DEBUG'}, 0, "$computer_node_name reboot has begun, sleeping for 5 seconds"); sleep 5; # Wait for the computer to respond to ping if (!$self->wait_for_ping($total_wait_seconds, $attempt_delay_seconds)) { # Check if the computer was ever offline, it should have been or else reboot never happened notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to ping"); next ATTEMPT; } # Decrease $total_wait_seconds by the amount of time elapsed so far my $ping_elapsed_seconds = (time - $start_time); my $ping_actual_seconds = ($ping_elapsed_seconds - $no_ping_elapsed_seconds); $total_wait_seconds -= $ping_elapsed_seconds; notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is pingable, waiting for SSH to respond"); # Wait maximum of 3 minutes for ssh to respond if (!$self->wait_for_ssh($total_wait_seconds, $attempt_delay_seconds)) { notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to SSH"); next ATTEMPT; } # Decrease $total_wait_seconds by the amount of time elapsed so far my $ssh_elapsed_seconds = (time - $start_time); my $ssh_actual_seconds = ($ssh_elapsed_seconds - $ping_elapsed_seconds); notify($ERRORS{'OK'}, 0, "$computer_node_name responded to SSH: unresponsive: $no_ping_elapsed_seconds seconds respond to ping: $ping_elapsed_seconds seconds ($ping_actual_seconds seconds after unresponsive) respond to SSH $ssh_elapsed_seconds seconds ($ssh_actual_seconds seconds after ping)" ); return 1; } # If loop completed, maximum number of reboot attempts was reached notify($ERRORS{'WARNING'}, 0, "$computer_node_name reboot failed"); return 0; } #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_ping Parameters : Maximum number of seconds to wait (optional), delay between attempts (optional) Returns : If computer is pingable before the maximum amount of time has elapsed: 1 If computer never responds to ping before the maximum amount of time has elapsed: 0 Description : Attempts to ping the computer specified in the DataStructure for the current reservation. It will wait up to a maximum number of seconds. This can be specified by passing the subroutine an integer value or the default value of 300 seconds will be used. The delay between attempts can be specified as the 2nd argument in seconds. The default value is 15 seconds. =cut sub wait_for_ping { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Attempt to get the total number of seconds to wait from the arguments my $total_wait_seconds = shift; if (!defined($total_wait_seconds) || $total_wait_seconds !~ /^\d+$/) { $total_wait_seconds = 300; } # Seconds to wait in between loop attempts my $attempt_delay_seconds = shift; if (!defined($attempt_delay_seconds) || $attempt_delay_seconds !~ /^\d+$/) { $attempt_delay_seconds = 15; } my $computer_node_name = $self->data->get_computer_node_name(); my $message = "waiting for $computer_node_name to respond to ping"; # Call code_loop_timeout, specifify that it should call _pingnode with the computer name as the argument return $self->code_loop_timeout(\&_pingnode, [$computer_node_name], $message, $total_wait_seconds, $attempt_delay_seconds); } ## end sub wait_for_ping #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_no_ping Parameters : Maximum number of seconds to wait (optional), seconds to delay between attempts (optional) Returns : 1 if computer is not pingable, 0 otherwise Description : Attempts to ping the computer specified in the DataStructure for the current reservation. It will wait up to a maximum number of seconds for ping to fail. The delay between attempts can be specified as the 2nd argument in seconds. The default value is 15 seconds. =cut sub wait_for_no_ping { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Attempt to get the total number of seconds to wait from the arguments my $total_wait_seconds = shift; if (!defined($total_wait_seconds) || $total_wait_seconds !~ /^\d+$/) { $total_wait_seconds = 300; } # Seconds to wait in between loop attempts my $attempt_delay_seconds = shift; if (!defined($attempt_delay_seconds) || $attempt_delay_seconds !~ /^\d+$/) { $attempt_delay_seconds = 15; } my $computer_node_name = $self->data->get_computer_node_name(); my $message = "waiting for $computer_node_name to NOT respond to ping"; # Call code_loop_timeout and invert the result, specifify that it should call _pingnode with the computer name as the argument return $self->code_loop_timeout(sub{return !_pingnode(@_)}, [$computer_node_name], $message, $total_wait_seconds, $attempt_delay_seconds); } ## end sub wait_for_no_ping #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_ssh Parameters : Seconds to wait (optional), seconds to delay between attempts (optional) Returns : Description : Attempts to communicate to the reservation computer via SSH. SSH attempts are made until the maximum number of seconds has elapsed. The maximum number of seconds can be specified as the first argument. If an argument isn't supplied, a default value of 300 seconds will be used. A delay occurs between attempts. This can be specified by passing a 2nd argument. If a 2nd argument isn't supplied, a default value of 15 seconds will be used. =cut sub wait_for_ssh { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Attempt to get the total number of seconds to wait from the arguments my $total_wait_seconds = shift; if (!defined($total_wait_seconds) || $total_wait_seconds !~ /^\d+$/) { $total_wait_seconds = 300; } # Seconds to wait in between loop attempts my $attempt_delay_seconds = shift; if (!defined($attempt_delay_seconds) || $attempt_delay_seconds !~ /^\d+$/) { $attempt_delay_seconds = 15; } my $computer_node_name = $self->data->get_computer_node_name(); # Call the "can" function, it returns a code reference to the subroutine specified # This is passed to code_loop_timeout which will then execute the code until it returns true my $sub_ref = $self->can("is_ssh_responding"); my $message = "waiting for $computer_node_name to respond to SSH"; return $self->code_loop_timeout($sub_ref, [$self], $message, $total_wait_seconds, $attempt_delay_seconds); } #////////////////////////////////////////////////////////////////////////////// =head2 is_ssh_responding Parameters : $computer_name (optional), $max_attempts (optional) Returns : boolean Description : Checks if the computer is responding to SSH. The SSH port is first checked. If not open, 0 is returned. If the port is open a test SSH command which simply echo's a string is attempted. The default is to only attempt to run this command once. This can be changed by supplying the $max_attempts argument. If the $max_attempts is supplied but set to 0, only the port checks are done. =cut sub is_ssh_responding { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name; my $max_attempts = 1; my $argument_1 = shift; my $argument_2 = shift; if ($argument_1) { # Check if the argument is an integer if ($argument_1 =~ /^\d+$/) { $max_attempts = $argument_1; } else { $computer_node_name = $argument_1; if ($argument_2 && $argument_2 =~ /^\d+$/) { $max_attempts = $argument_2; } } } if (!$computer_node_name) { $computer_node_name = $self->data->get_computer_node_name(); } # If 'ssh_port' key is set in this object use it my $port = $self->{ssh_port} || 22; # Try nmap to see if any of the ssh ports are open before attempting to run a test command my $nmap_status = nmap_port($computer_node_name, $port) ? "open" : "closed"; if ($nmap_status ne 'open') { notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is NOT responding to SSH, port $port is closed"); return 0; } if ($max_attempts) { # Run a test SSH command #my ($exit_status, $output) = $self->execute({ # node => $computer_node_name, # command => "echo \"testing ssh on $computer_node_name\"", # max_attempts => $max_attempts, # display_output => 0, # timeout_seconds => 30, # ignore_error => 1, #}); my ($exit_status, $output) = $self->execute({ node => $computer_node_name, command => "echo \"testing ssh on $computer_node_name\"", max_attempts => $max_attempts, display_output => 0, timeout_seconds => 15, }); # The exit status will be 0 if the command succeeded if (defined($output) && grep(/testing/, @$output)) { notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is responding to SSH, port $port: $nmap_status"); return 1; } else { notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is NOT responding to SSH, SSH command failed, port $port: $nmap_status"); return 0; } } else { return 1; } } #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_response Parameters : Initial delay seconds (optional), SSH response timeout seconds (optional), SSH attempt delay seconds (optional) Returns : If successful: true If failed: false Description : Waits for the reservation computer to respond to SSH after it has been loaded. =cut sub wait_for_response { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $start_time = time(); my $reservation_id = $self->data->get_reservation_id(); my $computer_id = $self->data->get_computer_id(); my $computer_node_name = $self->data->get_computer_node_name(); my $initial_delay_seconds = shift; if (!defined $initial_delay_seconds) { $initial_delay_seconds = 120; } my $ssh_response_timeout_seconds = shift; if (!defined $ssh_response_timeout_seconds) { $ssh_response_timeout_seconds = 600; } my $ssh_attempt_delay_seconds = shift; if (!defined $ssh_attempt_delay_seconds) { $ssh_attempt_delay_seconds = 15; } # Sleep for the initial delay value if it has been set # Check SSH once to bypass the initial delay if SSH is already responding if ($initial_delay_seconds && !$self->is_ssh_responding()) { notify($ERRORS{'OK'}, 0, "waiting $initial_delay_seconds seconds for $computer_node_name to boot"); sleep $initial_delay_seconds; notify($ERRORS{'OK'}, 0, "waited $initial_delay_seconds seconds for $computer_node_name to boot"); } # Wait for SSH to respond, loop until timeout is reached if (!$self->wait_for_ssh($ssh_response_timeout_seconds, $ssh_attempt_delay_seconds)) { notify($ERRORS{'WARNING'}, 0, "failed to connect to $computer_node_name via SSH after $ssh_response_timeout_seconds seconds"); return; } my $end_time = time(); my $duration = ($end_time - $start_time); insertloadlog($reservation_id, $computer_id, "machinebooted", "$computer_node_name is responding to SSH after $duration seconds"); notify($ERRORS{'OK'}, 0, "$computer_node_name is responding to SSH after $duration seconds"); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_port_open Parameters : $port_number, $connection_target (optional), $total_wait_seconds (optional), $attempt_delay_seconds (optional) Returns : boolean Description : Uses nmap to check if the port specified is open. Loops until the port is open or $total_wait_seconds is reached. =cut sub wait_for_port_open { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $computer_node_name = $self->data->get_computer_node_name(); my $calling_subroutine = get_calling_subroutine(); my $port_number = shift; if (!defined($port_number)) { notify($ERRORS{'WARNING'}, 0, "port number argument was not supplied"); return; } my $connection_target = shift || $computer_node_name; my $total_wait_seconds = shift || 60; my $attempt_delay_seconds = shift || 5; my $mode = ($calling_subroutine =~ /wait_for_port_closed/ ? 'closed' : 'open'); my $message = "waiting for port $port_number to be $mode on $connection_target"; $message .= " ($computer_node_name)" if ($connection_target ne $computer_node_name); # Essentially perform xnor on nmap_port result and $mode eq open # Both either need to be true or false # $mode eq open:true, nmap_port:true, result:true # $mode eq open:false, nmap_port:true, result:false # $mode eq open:true, nmap_port:false, result:false # $mode eq open:false, nmap_port:false, result:true my $sub_ref = sub{ my $nmap_result = nmap_port(@_) || 0; return $mode =~ /open/ == $nmap_result; }; return $self->code_loop_timeout($sub_ref, [$connection_target, $port_number], $message, $total_wait_seconds, $attempt_delay_seconds); } #////////////////////////////////////////////////////////////////////////////// =head2 wait_for_port_closed Parameters : $port_number, $connection_target (optional), $total_wait_seconds (optional), $attempt_delay_seconds (optional) Returns : boolean Description : Uses nmap to check if the port specified is closed. Loops until the port is open or $total_wait_seconds is reached. =cut sub wait_for_port_closed { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } return wait_for_port_open($self, @_); } #////////////////////////////////////////////////////////////////////////////// =head2 update_ssh_known_hosts Parameters : $known_hosts_path (optional) Returns : boolean Description : Removes lines from the known_hosts file matching the computer name or private IP address, then runs ssh-keyscan to add the current keys to the known_hosts file. =cut sub update_ssh_known_hosts { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $known_hosts_path = shift || "/root/.ssh/known_hosts"; my $computer_short_name = $self->data->get_computer_short_name(); # Get the computer private IP address my $computer_private_ip_address; if ($self->can("get_private_ip_address") && ($computer_private_ip_address = $self->get_private_ip_address())) { notify($ERRORS{'DEBUG'}, 0, "retrieved private IP address for $computer_short_name using OS module: $computer_private_ip_address"); } elsif ($computer_private_ip_address = $self->data->get_computer_private_ip_address()) { notify($ERRORS{'DEBUG'}, 0, "retrieved private IP address for $computer_short_name from database: $computer_private_ip_address"); } else { notify($ERRORS{'WARNING'}, 0, "unable to retrieve private IP address for $computer_short_name using OS module or from database"); } # Open the file, read the contents into an array, then close it my @known_hosts_lines_original; if (open FILE, "<", $known_hosts_path) { @known_hosts_lines_original = ; close FILE; } else { notify($ERRORS{'WARNING'}, 0, "unable to open file for reading: $known_hosts_path"); return; } # Loop through the lines my @known_hosts_lines_modified; for my $line (@known_hosts_lines_original) { chomp $line; next if (!$line); # Check if line matches the computer name or private IP address if ($line =~ /(^|[\s,])$computer_short_name[\s,]/i) { # Don't add the line to the array which will be added back to the file notify($ERRORS{'DEBUG'}, 0, "removing line from $known_hosts_path matching computer name: $computer_short_name\n$line"); next; } elsif ($line =~ /(^|[\s,])$computer_private_ip_address[\s,]/i) { notify($ERRORS{'DEBUG'}, 0, "removing line from $known_hosts_path matching computer private IP address:$computer_private_ip_address\n$line"); next; } # Line doesn't match, add it to the array of lines for the new file push @known_hosts_lines_modified, "$line\n"; } # Write the modified contents to the file if (open FILE, ">", "$known_hosts_path") { print FILE @known_hosts_lines_modified; close FILE; notify($ERRORS{'DEBUG'}, 0, "removed lines from $known_hosts_path matching $computer_short_name or $computer_private_ip_address"); } else { notify($ERRORS{'WARNING'}, 0, "unable to open file for writing: $known_hosts_path"); return; } # Run ssh-keyscan run_command("ssh-keyscan -t rsa '$computer_short_name' '$computer_private_ip_address' 2>&1 | grep -v '^#' >> $known_hosts_path"); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 server_request_set_fixed_ip Parameters : none Returns : If successful: true If failed: false Description : =cut sub server_request_set_fixed_ip { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $reservation_id = $self->data->get_reservation_id() || return; my $computer_id = $self->data->get_computer_id() || return; my $computer_node_name = $self->data->get_computer_node_name() || return; my $image_os_name = $self->data->get_image_os_name() || return; my $image_os_type = $self->data->get_image_os_type() || return; my $computer_public_ip_address = $self->data->get_computer_public_ip_address(); my $public_ip_configuration = $self->data->get_management_node_public_ip_configuration() || return; my $server_request_id = $self->data->get_server_request_id(); my $server_request_fixed_ip = $self->data->get_server_request_fixed_ip(); if ($server_request_id) { if ($server_request_fixed_ip) { #Update the info related to fixedIP if (!$self->update_fixed_ip_info()) { notify($ERRORS{'WARNING'}, 0, "Unable to update information related fixedIP for server_request $server_request_id"); } #Confirm requested IP is not being used if (!$self->confirm_fixed_ip_is_available()) { #failed, insert into loadlog, fail reservation insertloadlog($reservation_id, $computer_id, "failed","$server_request_fixed_ip is NOT available"); return 0; } #if set for static IPs, save the old address to restore if ($public_ip_configuration =~ /static/i) { notify($ERRORS{'DEBUG'}, 0, "saving original IP for restore on post reseration"); my $original_IPvalue = "originalIPaddr_" . $server_request_id; set_variable($original_IPvalue, $computer_public_ip_address); } # Try to set the static public IP address using the OS module if ($self->can("set_static_public_address")) { if ($self->set_static_public_address()) { notify($ERRORS{'DEBUG'}, 0, "set static public IP address on $computer_node_name using OS module's set_static_public_address() method"); $self->data->set_computer_public_ip_address($server_request_fixed_ip); # Delete cached network configuration information so it is retrieved next time it is needed delete $self->{network_configuration}; if (update_computer_public_ip_address($computer_id, $server_request_fixed_ip)) { notify($ERRORS{'OK'}, 0, "updated public IP address in computer table for $computer_node_name, $server_request_fixed_ip"); } #Update Hostname to match Public assigned name if ($self->can("update_public_hostname")) { if ($self->update_public_hostname()) { notify($ERRORS{'OK'}, 0, "Updated hostname based on fixedIP $server_request_fixed_ip"); } } } else { notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_node_name"); insertloadlog($reservation_id, $computer_id, "failed"," Not able to assigne IPaddress $server_request_fixed_ip"); return 0; } } else { notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address on $computer_node_name, " . ref($self) . " module does not implement a set_static_public_address subroutine"); } } } return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 confirm_fixed_ip_is_available Parameters : none Returns : If successful: true If failed: 0 Description : Preforms checks to confirm the requested IP is not being used -- Check VCL database computer table for IP -- try to ping the IP -- future; good to check with upstream network switch or control =cut sub confirm_fixed_ip_is_available { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $reservation_id = $self->data->get_reservation_id() || return; my $computer_id = $self->data->get_computer_id() || return; my $computer_node_name = $self->data->get_computer_node_name() || return; my $server_request_id = $self->data->get_server_request_id(); my $server_request_fixed_ip = $self->data->get_server_request_fixed_ip(); #check VCL computer table if (is_ip_assigned_query($server_request_fixed_ip)) { notify($ERRORS{'WARNING'}, 0, "$server_request_fixed_ip is already assigned"); insertloadlog($reservation_id, $computer_id, "failed","$server_request_fixed_ip is already assigned"); return 0; } #Is IP pingable if (_pingnode($server_request_fixed_ip)) { notify($ERRORS{'WARNING'}, 0, "$server_request_fixed_ip is answering ping test"); insertloadlog($reservation_id, $computer_id, "failed","$server_request_fixed_ip is answering ping test, but is not assigned in VCL database"); return 0; } return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 update_public_ip_address Parameters : none Returns : If successful: true If failed: false Description : Checks the IP configuration mode for the management node - dynamic DHCP, manual DHCP, or static. If DHCP is used, the public IP address is retrieved from the computer and the IP address in the computer table is updated if necessary. If static public IP addresses are used, the computer is configured to use the public IP address stored in the computer table. =cut sub update_public_ip_address { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $reservation_id = $self->data->get_reservation_id() || return; my $computer_id = $self->data->get_computer_id() || return; my $computer_node_name = $self->data->get_computer_node_name() || return; my $image_os_name = $self->data->get_image_os_name() || return; my $image_os_type = $self->data->get_image_os_type() || return; my $computer_public_ip_address = $self->data->get_computer_public_ip_address(); my $public_ip_configuration = $self->data->get_management_node_public_ip_configuration() || return; my $nathost_hostname = $self->data->get_nathost_hostname(0); my $nathost_internal_ip_address = $self->data->get_nathost_internal_ip_address(0); if ($public_ip_configuration =~ /dhcp/i) { notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, attempting to retrieve dynamic public IP address from $computer_node_name"); # Wait for the computer to be assigned a public IP address # There is sometimes a delay if (!$self->wait_for_public_ip_address()) { notify($ERRORS{'WARNING'}, 0, "unable to update public IP address, $computer_node_name did not receive a public IP address via DHCP"); return; } # Retrieve the public IP address from the OS module my $retrieved_public_ip_address = $self->get_public_ip_address(); if ($retrieved_public_ip_address) { notify($ERRORS{'DEBUG'}, 0, "retrieved public IP address from $computer_node_name using the OS module: $retrieved_public_ip_address"); } else { notify($ERRORS{'WARNING'}, 0, "failed to retrieve dynamic public IP address from $computer_node_name"); insertloadlog($reservation_id, $computer_id, "dynamicDHCPaddress", "failed to retrieve dynamic public IP address from $computer_node_name"); return; } # Update the Datastructure and computer table if the retrieved IP address does not match what is in the database if ($computer_public_ip_address ne $retrieved_public_ip_address) { $self->data->set_computer_public_ip_address($retrieved_public_ip_address); if (update_computer_public_ip_address($computer_id, $retrieved_public_ip_address)) { notify($ERRORS{'OK'}, 0, "updated dynamic public IP address in computer table for $computer_node_name, $retrieved_public_ip_address"); insertloadlog($reservation_id, $computer_id, "dynamicDHCPaddress", "updated dynamic public IP address in computer table for $computer_node_name, $retrieved_public_ip_address"); } else { notify($ERRORS{'WARNING'}, 0, "failed to update dynamic public IP address in computer table for $computer_node_name, $retrieved_public_ip_address"); insertloadlog($reservation_id, $computer_id, "dynamicDHCPaddress", "failed to update dynamic public IP address in computer table for $computer_node_name, $retrieved_public_ip_address"); return; } } else { notify($ERRORS{'DEBUG'}, 0, "public IP address in computer table is already correct for $computer_node_name: $computer_public_ip_address"); } # If the computer is assigned to a NAT host, make sure default gateway is correct if ($nathost_hostname && $nathost_internal_ip_address) { my $current_default_gateway = $self->get_default_gateway(); if ($current_default_gateway) { if ($current_default_gateway eq $nathost_internal_ip_address) { notify($ERRORS{'DEBUG'}, 0, "static default gateway does NOT need to be set on $computer_node_name, default gateway assigned by DHCP matches NAT host internal IP address: $current_default_gateway"); } else { notify($ERRORS{'OK'}, 0, "static default gateway needs to be set on $computer_node_name, default gateway assigned by DHCP ($current_default_gateway) does NOT match NAT host internal IP address: $nathost_internal_ip_address"); $self->set_static_default_gateway(); } } } } elsif ($public_ip_configuration =~ /static/i) { notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, attempting to set public IP address"); # Try to set the static public IP address using the OS module if ($self->can("set_static_public_address")) { if ($self->set_static_public_address()) { notify($ERRORS{'DEBUG'}, 0, "set static public IP address on $computer_node_name using OS module's set_static_public_address() method"); insertloadlog($reservation_id, $computer_id, "staticIPaddress", "set static public IP address on $computer_node_name"); } else { notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_node_name"); insertloadlog($reservation_id, $computer_id, "staticIPaddress", "failed to set static public IP address on $computer_node_name"); return; } } else { notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address on $computer_node_name, " . ref($self) . " module does not implement a set_static_public_address subroutine"); } } else { notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, no public IP address updates necessary"); } return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 get_correct_default_gateway Parameters : none Returns : boolean Description : Determines which IP address should be used for the computer's default gateway: * If the computer is configured to use a NAT host, the computer's default gateway should be set to the NAT host's internal IP address * If NAT is not used and the management node profile is configured to use static public IP addresses, the computer's default gateway should be set to the management node profile's "Public Gateway" setting * If NAT is not used and the management node profile is configured to use DHCP-assigned public IP addresses, the computer's current default gateway should continue to be used =cut sub get_correct_default_gateway { my $self = shift; if (ref($self) !~ /VCL::Module::OS/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return 0; } my $computer_name = $self->data->get_computer_short_name(); my $nathost_hostname = $self->data->get_nathost_hostname(0); my $nathost_internal_ip_address = $self->data->get_nathost_internal_ip_address(0); my $management_node_ip_configuration = $self->data->get_management_node_public_ip_configuration(); if ($nathost_internal_ip_address) { notify($ERRORS{'DEBUG'}, 0, "$computer_name is configured to use NAT host $nathost_hostname, default gateway should be set to NAT host's internal IP address: $nathost_internal_ip_address"); return $nathost_internal_ip_address; } elsif ($management_node_ip_configuration =~ /static/i) { my $management_node_public_default_gateway = $self->data->get_management_node_public_default_gateway(); if ($management_node_public_default_gateway) { notify($ERRORS{'DEBUG'}, 0, "management node public IP mode is set to $management_node_ip_configuration, default gateway should be set to management node profile's default gateway setting: $management_node_public_default_gateway"); return $management_node_public_default_gateway; } else { notify($ERRORS{'WARNING'}, 0, "unable to determine correct default gateway to use on $computer_name, management node public IP mode is set to $management_node_ip_configuration but management node profile's default gateway setting could not be determined"); return; } } else { # Management node configured to use DHCP for public IP addresses # Get default gateway address assigned to computer my $current_default_gateway = $self->get_public_default_gateway(); if ($current_default_gateway) { notify($ERRORS{'DEBUG'}, 0, "management node public IP mode is set to $management_node_ip_configuration, default gateway currently configured on $computer_name should be used: $current_default_gateway"); return $current_default_gateway; } else { notify($ERRORS{'WARNING'}, 0, "unable to determine correct default gateway to use on $computer_name, management node public IP mode is set to $management_node_ip_configuration but default gateway currently configured on $computer_name could not be determined"); return; } } } #////////////////////////////////////////////////////////////////////////////// =head2 get_current_image_tag Parameters : $tag_name Returns : string Description : Reads currentimage.txt and attempts to locate a line beginning with the tag name specified by the argument. If found, the tag value following the = sign is returned. Null is returned if a line with the tag name doesn't exist. =cut sub get_current_image_tag { my $self = shift; if (ref($self) !~ /VCL::Module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my ($tag_name) = @_; if (!defined($tag_name)) { notify($ERRORS{'WARNING'}, 0, "property argument was not specified"); return; } my $computer_name = $self->data->get_computer_short_name(); my $current_image_file_path = 'currentimage.txt'; my @lines = $self->get_file_contents($current_image_file_path); for my $line (@lines) { my ($tag_value) = $line =~ /^$tag_name=(.*)$/gx; if (defined($tag_value)) { $tag_value = '' unless length($tag_value); notify($ERRORS{'DEBUG'}, 0, "found '$tag_name' tag line in $current_image_file_path: '$line', returning tag value: '$tag_value'"); return $tag_value; } } notify($ERRORS{'DEBUG'}, 0, "'$tag_name' tag is not set in $current_image_file_path, returning null"); return; } #////////////////////////////////////////////////////////////////////////////// =head2 set_current_image_tag Parameters : $tag_name, $tag_value Returns : boolean Description : Adds a line to currentimage.txt in the format: = (timestamp) The tag value must be a non-empty string and may be the integer 0. Example: set_current_image_tag('mytag'); Line added: mytag=0 (Wed Jun 29 17:47:36 2016) Any lines which already exist beginning with an identical tag name are removed. indicating a loaded computer is tainted and must be reloaded for any subsequent reservations. The format of the line added is: vcld_tainted=true (