blob: de3e8d76abf3ee457739580c22facc883373be06 [file] [log] [blame]
#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
=head1 NAME
VCL::utils
=head1 SYNOPSIS
use VCL::utils;
=head1 DESCRIPTION
This module contains general VCL utility subroutines.
=cut
###############################################################################
package VCL::utils;
# Specify the lib path using FindBin
use FindBin;
use lib "$FindBin::Bin/..";
# Configure inheritance
use base qw();
# Specify the version of this module
our $VERSION = '2.5.1';
# Specify the version of Perl to use
use 5.008000;
use strict;
use warnings;
use diagnostics;
use Mail::Mailer;
use File::Find;
use Time::Local;
use DBI;
use DBI::Const::GetInfoType;
use diagnostics;
use Net::Ping::External qw(ping);
use Fcntl qw(:DEFAULT :flock);
use FindBin;
use Getopt::Long;
use Carp;
use Term::ANSIColor 2.00 qw(:constants :pushpop color colored);
use Text::Wrap;
use English;
use List::Util qw(min max);
use HTTP::Headers;
use RPC::XML::Client;
use Scalar::Util 'blessed';
use Storable qw(dclone);
use Data::Dumper;
use Cwd;
use Sys::Hostname;
use XML::Simple;
use Time::HiRes qw(gettimeofday tv_interval);
use Crypt::OpenSSL::RSA;
use B qw(svref_2object);
use Socket;
use Net::Netmask;
BEGIN {
$ENV{PERL_RL} = 'Perl';
};
use Term::ReadLine;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
_pingnode
add_imageid_to_newimages
add_reservation_account
call_check_crypt_secrets
character_to_ascii_value
check_blockrequest_time
check_endtimenotice_interval
check_messages_need_validating
check_time
clear_next_image_id
clearfromblockrequest
convert_to_datetime
convert_to_epoch_seconds
create_management_node_directory
database_execute
database_select
delete_computerloadlog_reservation
delete_management_node_cryptsecret
delete_request
delete_reservation_account
delete_variable
delete_vcld_semaphore
determine_remote_connection_target
escape_file_path
format_array_reference
format_data
format_hash_keys
format_number
get_all_reservation_ids
get_affiliation_info
get_array_intersection
get_block_request_image_info
get_caller_trace
get_calling_subroutine
get_changelog_info
get_changelog_remote_ip_address_info
get_database_names
get_database_table_columns
get_database_table_names
get_code_ref_package_name
get_code_ref_subroutine_name
get_collapsed_hash_reference
get_computer_current_private_ip_address
get_computer_current_state_name
get_computer_grp_members
get_computer_ids
get_computer_info
get_computer_nathost_info
get_computer_private_ip_address_info
get_computers_controlled_by_mn
get_connectlog_info
get_connectlog_remote_ip_address_info
get_copy_speed_info_string
get_current_file_name
get_current_image_contents_no_data_structure
get_current_package_name
get_current_reservation_lastcheck
get_current_subroutine_name
get_file_size_info_string
get_group_name
get_image_active_directory_domain_info
get_image_info
get_imagemeta_info
get_imagerevision_cleanup_info
get_imagerevision_info
get_imagerevision_loaded_info
get_imagerevision_names
get_imagerevision_names_recently_reserved
get_imagerevision_reservation_info
get_local_user_info
get_managable_resource_groups
get_management_node_ad_domain_credentials
get_management_node_blockrequests
get_management_node_computer_ids
get_management_node_id
get_management_node_info
get_management_node_cryptkey_pubkey
get_management_node_cryptsecret_info
get_management_node_cryptsecret_value
get_management_node_requests
get_management_node_vmhost_ids
get_management_node_vmhost_info
get_message_variable_info
get_module_info
get_nathost_assigned_public_ports
get_natport_ranges
get_next_image_default
get_os_info
get_production_imagerevision_info
get_provisioning_osinstalltype_info
get_provisioning_table_info
get_random_mac_address
get_request_by_computerid
get_request_current_state_name
get_request_end
get_request_info
get_request_log_info
get_reservation_accounts
get_reservation_computerloadlog_entries
get_reservation_computerloadlog_time
get_reservation_connect_method_info
get_reservation_management_node_hostname
get_reservation_vcld_process_name_regex
get_request_loadstate_names
get_resource_groups
get_user_group_member_info
get_user_info
get_variable
get_vcld_semaphore_info
get_vmhost_assigned_vm_info
get_vmhost_assigned_vm_provisioning_info
get_vmhost_info
getnewdbh
getpw
hash_to_xml_string
help
hostname_to_ip_address
insert_nathost
insert_natport
insert_reload_request
insert_request
insert_vcld_semaphore
insertloadlog
ip_address_to_hostname
ip_address_to_network_address
is_inblockrequest
is_ip_assigned_query
is_management_node_process_running
is_public_ip_address
is_request_deleted
is_valid_dns_host_name
is_valid_ip_address
is_variable_set
kill_child_processes
mail
makedatestring
nmap_port
normalize_file_path
notify
notify_via_im
notify_via_oascript
parent_directory_path
pfd
populate_reservation_natport
preplogfile
prune_array_reference
prune_hash_child_references
prune_hash_reference
read_file_to_array
remove_array_duplicates
rename_vcld_process
reservation_being_processed
round
run_command
run_scp_command
run_ssh_command
set_logfile_path
set_management_node_cryptkey_pubkey
set_managementnode_state
set_reservation_lastcheck
set_variable
setnextimage
setup_confirm
setup_get_array_choice
setup_get_hash_choice
setup_get_hash_multiple_choice
setup_get_input_file_path
setup_get_input_string
setup_get_menu_choice
setup_print_break
setup_print_error
setup_print_ok
setup_print_warning
setup_print_wrap
set_production_imagerevision
sleep_uninterrupted
sort_by_file_name
stopwatch
string_to_ascii
switch_vmhost_id
update_blockrequest_processing
update_changelog_request_user_remote_ip
update_changelog_reservation_remote_ip
update_changelog_reservation_user_remote_ip
update_computer_imagename
update_computer_lastcheck
update_computer_private_ip_address
update_computer_procnumber
update_computer_procspeed
update_computer_public_ip_address
update_computer_ram
update_computer_state
update_computer_vmhost_id
update_connectlog
update_currentimage
update_image_name
update_image_type
update_lastcheckin
update_log_ending
update_log_loaded_time
update_preload_flag
update_request_checkuser
update_request_state
update_reservation_lastcheck
update_reservation_natlog
update_reservation_password
update_sublog_ipaddress
wrap_string
xml_string_to_hash
xmlrpc_call
yaml_deserialize
yaml_serialize
$CONF_FILE_PATH
$DAEMON_MODE
$DATABASE
$DEFAULTHELPEMAIL
$EXECUTE_NEW
$FQDN
$LOGFILE
$MYSQL_SSL
$MYSQL_SSL_CERT
$PIDFILE
$PROCESSNAME
$WINDOWS_ROOT_PASSWORD
$SERVER
$SETUP_MODE
$TOOLS
$VERBOSE
$WRTPASS
$WRTUSER
$XMLRPC_USER
$XMLRPC_PASS
$XMLRPC_URL
%ERRORS
%OPTIONS
);
our %ERRORS = (
'OK' => 0,
'WARNING' => 1,
'CRITICAL' => 2,
'UNKNOWN' => 3,
'DEPENDENT' => 4,
'MAILMASTERS' => 5,
'DEBUG' => 6
);
our $PROCESSNAME;
our $LOGFILE;
our $PIDFILE;
our $FQDN;
our $SERVER;
our $DATABASE;
our $WRTUSER;
our $WRTPASS;
our $MYSQL_SSL;
our $MYSQL_SSL_CERT;
our $JABBER;
our $JABBER_SERVER;
our $JABBER_USER;
our $JABBER_PASSWORD;
our $JABBER_RESOURCE;
our $JABBER_PORT;
our $DEFAULTHELPEMAIL;
our $RETURNPATH;
our $WINDOWS_ROOT_PASSWORD;
our $XMLRPC_USER;
our $XMLRPC_PASS;
our $XMLRPC_URL;
our $BIN_PATH = $FindBin::Bin;
our $TOOLS = "$FindBin::Bin/../tools";
our $VERBOSE;
our $CONF_FILE_PATH;
our $DAEMON_MODE;
our $SETUP_MODE;
our $EXECUTE_NEW;
INIT {
# Parse config file and set globals
# Set Getopt pass_through so this module doesn't erase parameters that other modules may use
Getopt::Long::Configure('pass_through');
# Set a default config file path
my $hostname = hostname();
$hostname =~ s/\..*//g;
my $cwd = getcwd();
$CONF_FILE_PATH = "$cwd/$hostname.conf";
if (!-f $CONF_FILE_PATH) {
if ($BIN_PATH && $BIN_PATH =~ /dev/) {
$CONF_FILE_PATH = "/etc/vcl/vcldev.conf";
}
else {
$CONF_FILE_PATH = "/etc/vcl/vcld.conf";
}
}
# Store the command line options in hash
our %OPTIONS;
GetOptions(\%OPTIONS,
'config=s' => \$CONF_FILE_PATH,
'daemon!' => \$DAEMON_MODE,
'logfile=s' => \$LOGFILE,
'help' => \&help,
'setup!' => \$SETUP_MODE,
'verbose!' => \$VERBOSE,
'exn!' => \$EXECUTE_NEW,
);
my %parameters = (
'log' => \$LOGFILE,
'pidfile' => \$PIDFILE,
'fqdn' => \$FQDN,
'database' => \$DATABASE,
'server' => \$SERVER,
'lockerwrtuser' => \$WRTUSER,
'wrtpass' => \$WRTPASS,
'xmlrpc_username' => \$XMLRPC_USER,
'xmlrpc_pass' => \$XMLRPC_PASS,
'xmlrpc_url' => \$XMLRPC_URL,
'enable_mysql_ssl' => \$MYSQL_SSL,
'mysql_ssl_cert' => \$MYSQL_SSL_CERT,
'returnpath' => \$RETURNPATH,
'jabber' => \$JABBER,
'jabserver' => \$JABBER_SERVER,
'jabuser' => \$JABBER_USER,
'jabpass' => \$JABBER_PASSWORD,
'jabport' => \$JABBER_PORT,
'jabresource' => \$JABBER_RESOURCE,
'processname' => \$PROCESSNAME,
'windows_root_password' => \$WINDOWS_ROOT_PASSWORD,
'verbose' => \$VERBOSE,
'defaulthelpemail' => \$DEFAULTHELPEMAIL,
);
# Make sure the config file exists
if (!-f $CONF_FILE_PATH) {
if (!$SETUP_MODE) {
print STDERR "FATAL: vcld configuration file does not exist: $CONF_FILE_PATH\n";
help();
}
}
elsif (!open(CONF, $CONF_FILE_PATH)) {
print STDERR "FATAL: failed to open vcld configuration file: $CONF_FILE_PATH, $!\n";
exit;
}
my @conf_file_lines = <CONF>;
close(CONF);
my $line_number = 0;
foreach my $line (@conf_file_lines) {
$line_number++;
$line =~ s/[\s]*$//g;
# Skip commented and blank lines
if ($line =~ /^\s*#/ || $line !~ /\w/) {
next;
}
my ($parameter, $value) = $line =~ /\s*([^=]+)=(.+)/;
if (!defined($parameter) || !defined($value)) {
#print STDERR "WARNING: ignoring line $line_number in $CONF_FILE_PATH: $line\n";
next;
}
# Remove any leading and trailing spaces
for ($parameter, $value) {
s/^\s+//;
s/\s+$//;
}
$parameter = lc($parameter);
if (my $variable_ref = $parameters{$parameter}) {
if (defined($$variable_ref)) {
#print STDOUT "INFO: ignoring previously set parameter: $parameter\n";
}
else {
$$variable_ref = $value;
#print STDOUT "set parameter: '$parameter' = '$value'\n";
}
}
else {
#print STDERR "WARNING: unsupported parameter found on line $line_number in $CONF_FILE_PATH: " . string_to_ascii($parameter) . "\n";
}
}
if (!$FQDN) {
print STDERR "FATAL: FQDN parameter must be configured in $CONF_FILE_PATH\n";
exit;
}
$PROCESSNAME = 'vcld' if !$PROCESSNAME;
$PIDFILE = "/var/run/$PROCESSNAME.pid" if !$PIDFILE;
$LOGFILE = "/var/log/$PROCESSNAME.log" if !defined($LOGFILE);
$WINDOWS_ROOT_PASSWORD = "clOudy" if !defined($WINDOWS_ROOT_PASSWORD);
$DEFAULTHELPEMAIL = "vcl_help\@example.org" if !$DEFAULTHELPEMAIL;
# Can't be both daemon mode and setup mode, use setup if both are set
if ($SETUP_MODE) {
$DAEMON_MODE = 0;
}
elsif (!defined($DAEMON_MODE)) {
$DAEMON_MODE = 1;
}
elsif (!defined($EXECUTE_NEW)) {
$EXECUTE_NEW = 0;
}
$ENV{execute_new} = $EXECUTE_NEW if $EXECUTE_NEW;
# Set boolean variables to 0 or 1, they may be set to 'no' or 'yes' in the conf file
for ($MYSQL_SSL, $JABBER, $VERBOSE, $DAEMON_MODE, $SETUP_MODE) {
if (!$_ || $_ =~ /no/i) {
$_ = 0;
}
else {
$_ = 1;
}
}
if ($JABBER) {
# Jabber is enabled - import required module
eval {
require "Net/Jabber.pm";
import Net::Jabber qw(client);
};
if ($EVAL_ERROR) {
print STDERR "FATAL: failed to load Jabber module, error:\n$EVAL_ERROR\n";
exit;
}
}
} ## end INIT
#//////////////////////////////////////////////////////////////////////////////
=head2 help
Parameters : None
Returns : Nothing, terminates program
Description : Displays a help message and exits.
=cut
sub help {
my $message = <<"END";
============================================================================
Please read the README and INSTALLATION files in the source directory.
Documentation is available at http://cwiki.apache.org/VCL.
Command line options:
-conf=<path> | Specify vcld configuration file (default: /etc/vcl/vcld.conf)
-log=<path> | Specify vcld log file (default: /var/log/vcld.log)
-setup | Run management node setup
-verbose | Run vcld in verbose mode
-nodaemon | Run vcld in non-daemon mode
-exn | Use persistent SSH connections (experimental)
-help | Display this help information
============================================================================
END
print $message;
exit 1;
} ## end sub help
#//////////////////////////////////////////////////////////////////////////////
=head2 preplogfile
Parameters : nothing
Returns : nothing
Description : writes header to global log file
=cut
sub preplogfile {
my $currenttime = makedatestring();
#Print the vcld process info
my $process_info = <<EOF;
============================================================================
VCL Management Node Daemon (vcld) | $currenttime
============================================================================
bin path: $BIN_PATH
config file: $CONF_FILE_PATH
log file: $LOGFILE
pid file: $PIDFILE
daemon mode: $DAEMON_MODE
setup mode: $SETUP_MODE
verbose mode: $VERBOSE
============================================================================
EOF
if ($LOGFILE) {
if (!open(LOGFILE, ">>$LOGFILE")) {
die "Failed to open log file: $LOGFILE";
}
print LOGFILE $process_info;
close(LOGFILE);
} else {
print STDOUT $process_info;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 notify
Parameters : $error, $LOG, $string, $data
Returns : true
Description : based on error value write string and/or data to
provide or default log file
=cut
sub notify {
my $error = shift;
my $log = shift;
my $string = shift;
my @data = @_;
# Just return if DEBUG and verbose isn't enabled
return if ($error == 6 && !$VERBOSE);
# Get the current time
my $currenttime = makedatestring();
# Open the log file for writing if passed as an argument or set globally
# If not, print to STDOUT
$log = $LOGFILE if (!$log);
# Get info about the subroutine which called this subroutine
my ($package, $filename, $line, $sub) = caller(0);
# Assemble the caller information
my $caller_info;
if (caller(1)) {
$sub = (caller(1))[3];
}
# Remove leading path from filename
$filename =~ s/.*\///;
# Remove the leading package path from the sub name (VC::...)
$sub =~ s/.*:://;
$caller_info = "$filename:$sub($line)";
# Format the message string
# Remove Windows carriage returns from the message string for consistency
$string =~ s/\r//gs;
## Remove newlines from the beginning and end of the message string
#$string =~ s/^\n+//;
#$string =~ s/\n+$//;
# Remove any spaces from the beginning or end of the string
$string =~ s/(^\s+)|(\s+$)//gs;
# Remove any spaces from the beginning or end of the each line
#$string =~ s/\s*\n\s*/\n/gs;
# Remove blank lines
$string =~ s/\n{2,}/\n/gs;
# Replace consecutive spaces with a single space to keep log file concise as long as string doesn't contain a quote
if ($string !~ /[\'\"]/gs && $string !~ /\s:\s/gs) {
$string =~ s/[ \t]+/ /gs;
}
# Assemble the process identifier string
my $process_identifier;
$process_identifier .= "|$PID|";
$process_identifier .= $ENV{request_id} if defined $ENV{request_id};
$process_identifier .= "|";
$process_identifier .= $ENV{reservation_id} if defined $ENV{reservation_id};
$process_identifier .= "|";
$process_identifier .= $ENV{state} || 'vcld';
$process_identifier .= "|$filename:$sub|$line";
# Assemble the log message
my $log_message = "$currenttime$process_identifier|$string";
# Format the data if WARNING or CRITICAL, and @data was passed
my $formatted_data;
if (@data && ($error == 1 || $error == 2)) {
# Add the data to the message body if it was passed
$formatted_data = "DATA:\n" . format_data(\@data, 'DATA');
chomp $formatted_data;
}
# Assemble an email message body if CRITICAL
my $body;
my $body_separator = '-' x 72;
my $sysadmin = '';
my $shared_mail_box = '';
if ($error == 2 || $error == 5) {
my $caller_trace = get_caller_trace(999);
if ($caller_trace !~ /get_management_node_info/) {
my $management_node_info = get_management_node_info();
if ($management_node_info) {
$sysadmin = $management_node_info->{SYSADMIN_EMAIL} if $management_node_info->{SYSADMIN_EMAIL};
$shared_mail_box = $management_node_info->{SHARED_EMAIL_BOX} if $management_node_info->{SHARED_EMAIL_BOX};
}
}
}
# WARNING
if ($error == 1) {
my $caller_trace = get_caller_trace();
$log_message = "\n---- WARNING ---- \n$log_message\n$caller_trace\n\n";
}
# CRITICAL
elsif ($error == 2) {
my $caller_trace = get_caller_trace(15);
$log_message = "\n---- CRITICAL ---- \n$log_message\n$caller_trace\n\n";
if ($sysadmin) {
# Assemble the e-mail message body
$body = <<"END";
$string
$body_separator
time: $currenttime
caller: $caller_info
$caller_trace
$body_separator
END
# Add the reservation info to the message if the DataStructure object is defined in %ENV
if ($ENV{data}) {
my $reservation_info_string = $ENV{data}->get_reservation_info_string();
if ($reservation_info_string) {
$reservation_info_string =~ s/\s+$//;
$body .= "$reservation_info_string\n";
$body .= "$body_separator\n";
}
}
# Get the previous several log file entries for this process
my $log_history_count = 100;
my $log_history = "RECENT LOG ENTRIES FOR THIS PROCESS:\n";
$log_history .= `grep "|$PID|" $log | tail -n $log_history_count` if $log;
chomp $log_history;
$body .= $log_history;
# Add the formatted data to the message body if data was passed
$body .= "\n\nDATA:\n$formatted_data\n" if $formatted_data;
my ($management_node_short_name) = $FQDN =~ /^([^.]+)/;
my $subject = "PROBLEM -- $management_node_short_name|";
# Assemble the process identifier string
if (defined $ENV{request_id} && defined $ENV{reservation_id} && defined $ENV{state}) {
$subject .= "$ENV{request_id}:$ENV{reservation_id}|$ENV{state}|$filename";
}
else {
$subject .= "$caller_info";
}
if (defined($ENV{data})) {
my $blockrequest_name = $ENV{data}->get_blockrequest_name(0);
$subject .= "|$blockrequest_name" if (defined $blockrequest_name);
my $computer_name = $ENV{data}->get_computer_short_name(0);
$subject .= "|$computer_name" if (defined $computer_name);
my $vmhost_hostname = $ENV{data}->get_vmhost_short_name(0);
$subject .= ">$vmhost_hostname" if (defined $vmhost_hostname);
my $image_name = $ENV{data}->get_image_name(0);
$subject .= "|$image_name" if (defined $image_name);
my $user_name = $ENV{data}->get_user_login_id(0);
$subject .= "|$user_name" if (defined $user_name);
}
my $from = "root\@$FQDN";
my $to = $sysadmin;
mail($to, $subject, $body, $from);
}
} ## end elsif ($error == 2) [ if ($error == 1)
# MAILMASTERS - only for email notifications
elsif ($error == 5 && $shared_mail_box) {
my $to = $shared_mail_box;
my $from = "root\@$FQDN";
my $subject = "Informational -- $filename";
# Assemble the e-mail message body
$body = <<"END";
$string
Time: $currenttime
PID: $PID
Caller: $caller_info
END
mail($to, $subject, $body, $from);
}
# Add the process identifier to every line of the log message
chomp $log_message;
$log_message =~ s/\n([^\n])/\n$process_identifier| $1/g;
# Check if the logfile path has been set and not running in daemon mode and redirect output to log file
# No need to redirect in daemon mode because STDOUT is redirected by vcld
if (!$DAEMON_MODE && $log) {
open(OUTPUT, ">>$log");
print OUTPUT "$log_message\n";
close OUTPUT;
}
else {
open(STDOUT, ">>$log");
# ARK - for testing competing reservations
#if ($ENV{reservation_id}) {
# if ($ENV{reservation_id} == 3115) {
# print colored($log_message, "YELLOW");
# }
# elsif ($ENV{reservation_id} == 3116) {
# print colored($log_message, "CYAN");
# }
# else {
# print colored($log_message, "MAGENTA");
# }
# print "\n";
# return 1;
#}
print STDOUT "$log_message\n";
}
} ## end sub notify
#//////////////////////////////////////////////////////////////////////////////
=head2 makedatestring
Parameters : empty
Returns : current time in date_time format
Description :
=cut
sub makedatestring {
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime();
$year += 1900;
$mon++;
my $datestring = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec);
return $datestring;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 convert_to_datetime
Parameters : time in epoch format
Returns : date in datetime format
Description : accepts time in epoch format (10 digit) and returns time in
datetime format
=cut
sub convert_to_datetime {
my ($epochtime) = shift;
if (!defined($epochtime) || $epochtime == 0) {
$epochtime = time();
}
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($epochtime);
$year += 1900;
$mon++;
my $datestring = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec);
return $datestring;
} ## end sub convert_to_datetime
#//////////////////////////////////////////////////////////////////////////////
=head2 convert_to_epoch_seconds
Parameters : datetime
Returns : time in epoch format
Description : takes input(optional) and returns epoch 10 digit string of
the supplied date_time or the current time
=cut
sub convert_to_epoch_seconds {
my ($date_time) = shift;
if (!defined($date_time)) {
return time();
}
#somehow we got a null timestamp, set it to current time
if ($date_time =~ /0000-00-00 00:00:00/) {
$date_time = makedatestring;
}
#format received: year-mon-mday hr:min:sec
my ($vardate, $vartime) = split(/ /, $date_time);
my ($yr, $mon, $mday) = split(/-/, $vardate);
my ($hr, $min, $sec) = split(/:/, $vartime);
$mon = $mon - 1; #time uses 0-11 for months :(
my $epoch_time = timelocal($sec, $min, $hr, $mday, $mon, $yr);
return $epoch_time;
} ## end sub convert_to_epoch_seconds
#//////////////////////////////////////////////////////////////////////////////
=head2 check_endtimenotice_interval
Parameters : endtime
Returns : scalar: 2week, 1week, 2day, 1day, 30min, or 0
Description : used to send a notice to owner regarding how far out the end of
their reservation is
=cut
sub check_endtimenotice_interval {
my $end = $_[0];
notify($ERRORS{'WARNING'}, 0, "endtime not set") if (!defined($end));
my $now = convert_to_epoch_seconds();
my $epochend = convert_to_epoch_seconds($end);
my $epoch_until_end = $epochend - $now;
notify($ERRORS{'OK'}, 0, "endtime= $end epoch_until_end= $epoch_until_end");
my $diff_seconds = $epoch_until_end;
my $diff_weeks = int($epoch_until_end/604800);
$diff_seconds -= $diff_weeks * 604800;
my $diff_days = int($diff_seconds/86400);
my $total_days = int($epoch_until_end/86400);
$diff_seconds -= $diff_days * 86400;
my $diff_hours = int($diff_seconds/3600);
$diff_seconds -= $diff_hours * 3600;
my $diff_minutes = int($diff_seconds/60);
$diff_seconds -= $diff_minutes * 60;
notify($ERRORS{'OK'}, 0, "End Time is in: $diff_weeks week\(s\) $diff_days day\(s\) $diff_hours hour\(s\) $diff_minutes min\(s\) and $diff_seconds sec\(s\)");
#flag on: 2 & 1 week; 2,1 day, 1 hour, 30,15,10,5 minutes
#ignore over 2weeks away
if ($diff_weeks >= 2) {
return 0;
}
#2 week: between 14 days and a 14 day -6 minutes window
elsif ($total_days >= 13 && $diff_hours >= 23 && $diff_minutes >= 55) {
return "2 weeks";
}
#Ignore: between 7 days and 14 day - 6 minute window
elsif ($total_days >=7) {
return 0;
}
# 1 week notice: between 7 days and a 7 day -6 minute window
elsif ($total_days >= 6 && $diff_hours >= 23 && $diff_minutes >= 55) {
return "1 week";
}
# Ignore: between 2 days and 7 day - 15 minute window
elsif ($total_days >= 2) {
return 0;
}
# 2 day notice: between 2 days and a 2 day -6 minute window
elsif ($total_days >= 1 && $diff_hours >= 23 && $diff_minutes >= 55) {
return "2 days";
}
# 1 day notice: between 1 days and a 1 day -6 minute window
elsif ($total_days >= 0 && $diff_hours >= 23 && $diff_minutes >= 55) {
return "24 hours";
}
return 0;
} ## end sub check_endtimenotice_interval
#//////////////////////////////////////////////////////////////////////////////
=head2 check_blockrequest_time
Parameters : start, end, and expire times
Returns : 0 or 1 and task
Description : check current time against all three tasks
expire time overides end, end overrides start
=cut
sub check_blockrequest_time {
my ($start_datetime, $end_datetime, $expire_datetime) = @_;
# Check the arguments
if (!$start_datetime) {
notify($ERRORS{'WARNING'}, 0, "start time argument was not passed correctly");
return;
}
if (!$end_datetime) {
notify($ERRORS{'WARNING'}, 0, "end time argument was not passed correctly");
return;
}
if (!$expire_datetime) {
notify($ERRORS{'WARNING'}, 0, "expire time argument was not passed correctly");
return;
}
# Get the current time in epoch seconds
my $current_time_epoch_seconds = time();
my $expire_time_epoch_seconds = convert_to_epoch_seconds($expire_datetime);
my $expire_delta_minutes = int(($expire_time_epoch_seconds - $current_time_epoch_seconds) / 60);
#notify($ERRORS{'DEBUG'}, 0, "expire: $expire_datetime, epoch: $expire_time_epoch_seconds, delta: $expire_delta_minutes minutes");
# If expire time is in the past, remove it
if ($expire_delta_minutes < 0) {
# Block request has expired
notify($ERRORS{'OK'}, 0, "block request expired " . abs($expire_delta_minutes) . " minutes ago, returning 'expire'");
return "expire";
}
if ($start_datetime =~ /^-?\d*$/ || $end_datetime =~ /^-?\d*$/) {
notify($ERRORS{'DEBUG'}, 0, "block request is not expired but has no block times assigned to it, returning 0");
return 0;
}
# Convert the argument datetimes to epoch seconds for easy calculation
my $start_time_epoch_seconds = convert_to_epoch_seconds($start_datetime);
my $end_time_epoch_seconds = convert_to_epoch_seconds($end_datetime);
# Calculate # of seconds away start, end, and expire times are from now
# Positive value means time is in the future
my $start_delta_minutes = int(($start_time_epoch_seconds - $current_time_epoch_seconds) / 60);
my $end_delta_minutes = int(($end_time_epoch_seconds - $current_time_epoch_seconds) / 60);
#notify($ERRORS{'DEBUG'}, 0, "start: $start_datetime, epoch: $start_time_epoch_seconds, delta: $start_delta_minutes minutes");
#notify($ERRORS{'DEBUG'}, 0, "end: $end_datetime, epoch: $end_time_epoch_seconds, delta: $end_delta_minutes minutes");
# End time it is less than 1 minute
if ($end_delta_minutes < 0) {
# Block request end time is reached
notify($ERRORS{'OK'}, 0, "block request end time has been reached ($end_delta_minutes minutes from now), returning 'end'");
return "end";
}
# if 1min to 6 hrs in advance: start assigning resources
if ($start_delta_minutes <= (6 * 60)) {
# Block request within start window
notify($ERRORS{'OK'}, 0, "block request start time is within start window ($start_delta_minutes minutes from now), returning 'start'");
return "start";
}
#notify($ERRORS{'DEBUG'}, 0, "block request does not need to be processed now, returning 0");
return 0;
} ## end sub check_blockrequest_time
#//////////////////////////////////////////////////////////////////////////////
=head2 check_time
Parameters : $request_start, $request_end, $reservation_lastcheck, $request_state_name, $request_laststate_name
Returns : start, preload, end, poll, old, remove, or 0
Description : based on the input return a value used by vcld
=cut
sub check_time {
my ($request_start, $request_end, $reservation_lastcheck, $request_state_name, $request_laststate_name, $serverrequest, $reservation_cnt, $changelog_timestamp) = @_;
# Check the arguments
if (!defined($request_state_name)) {
notify($ERRORS{'WARNING'}, 0, "\$request_state_name argument is not defined");
return 0;
}
if (!defined($request_laststate_name)) {
notify($ERRORS{'WARNING'}, 0, "\$request_laststate_name argument is not defined");
return 0;
}
# Get the current time epoch seconds
my $current_time_epoch_seconds = time();
# If lastcheck isn't set, set it to now
if (!defined($reservation_lastcheck) || !$reservation_lastcheck) {
$reservation_lastcheck = makedatestring($current_time_epoch_seconds);
}
if (!defined($changelog_timestamp) || !$changelog_timestamp) {
$changelog_timestamp = makedatestring($current_time_epoch_seconds);
}
# First convert to datetime in case epoch seconds was passed
if ($reservation_lastcheck !~ /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/) {
$reservation_lastcheck = convert_to_datetime($reservation_lastcheck);
}
if ($request_end !~ /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/) {
$request_end = convert_to_datetime($request_end);
}
if ($request_start !~ /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/) {
$request_start = convert_to_datetime($request_start);
}
# Convert times to epoch seconds
my $lastcheck_epoch_seconds = convert_to_epoch_seconds($reservation_lastcheck);
my $start_time_epoch_seconds = convert_to_epoch_seconds($request_start);
my $end_time_epoch_seconds = convert_to_epoch_seconds($request_end);
my $changelog_epoch_seconds = convert_to_epoch_seconds($changelog_timestamp);
# Calculate time differences from now in seconds
# These will be positive if in the future, negative if in the past
my $lastcheck_diff_seconds = $lastcheck_epoch_seconds - $current_time_epoch_seconds;
my $start_diff_seconds = $start_time_epoch_seconds - $current_time_epoch_seconds;
my $end_diff_seconds = $end_time_epoch_seconds - $current_time_epoch_seconds;
my $changelog_diff_seconds = $changelog_epoch_seconds - $current_time_epoch_seconds;
# Calculate the time differences from now in minutes
# These will be positive if in the future, negative if in the past
my $lastcheck_diff_minutes = round($lastcheck_diff_seconds / 60);
my $start_diff_minutes = round($start_diff_seconds / 60);
my $end_diff_minutes = round($end_diff_seconds / 60);
my $preload_window_minutes_prior_high = 35;
my $preload_window_minutes_prior_low = 25;
# Print the time differences
#notify($ERRORS{'OK'}, 0, "reservation lastcheck difference: $lastcheck_diff_minutes minutes ($lastcheck_diff_seconds seconds)");
#notify($ERRORS{'OK'}, 0, "request start time difference: $start_diff_minutes minutes");
#notify($ERRORS{'OK'}, 0, "request end time difference: $end_diff_minutes minutes");
#notify($ERRORS{'OK'}, 0, "changelog timestamp difference: $changelog_diff_seconds seconds");
# Check the state, and then figure out the return code
if ($request_state_name =~ /(new|imageprep|reload|tovmhostinuse|test)/) {
if ($start_diff_minutes > 0) {
# Start time is either now or in future, $start_diff_minutes is positive
my $preload_start_minutes = ($start_diff_minutes - $preload_window_minutes_prior_high);
if ($start_diff_minutes > $preload_window_minutes_prior_high) {
if (($preload_start_minutes <= 5 || $start_diff_minutes % 5 == 0) && ($start_diff_seconds % 60) >= 50) {
notify($ERRORS{'DEBUG'}, 0, "$request_state_name request start time is $start_diff_minutes minutes from now, preload process will begin in $preload_start_minutes minutes ($preload_window_minutes_prior_high minutes prior to request start time), returning 0");
}
return "0";
}
elsif ($start_diff_minutes >= $preload_window_minutes_prior_low && $start_diff_minutes <= $preload_window_minutes_prior_high) {
notify($ERRORS{'DEBUG'}, 0, "$request_state_name request start time is $start_diff_minutes minutes from now, returning 'preload'");
return "preload";
}
else {
if (($start_diff_minutes <= 5 || $start_diff_minutes % 5 == 0) && ($start_diff_seconds % 60) >= 50) {
notify($ERRORS{'DEBUG'}, 0, "$request_state_name request will be processed in $start_diff_minutes minutes, returning 0");
}
return "0";
}
} ## end if ($start_diff_minutes > 0)
else {
# Start time is in past, $start_diff_minutes is negative
#Start time is fairly old - something is off
#send warning to log for tracking purposes
if ($start_diff_minutes < -17) {
notify($ERRORS{'WARNING'}, 0, "reservation start time was in the past 17 minutes ($start_diff_minutes), returning 'start'");
}
else {
notify($ERRORS{'DEBUG'}, 0, "reservation start time was $start_diff_minutes minutes ago, returning 'start'");
}
return "start";
} ## end else [ if ($start_diff_minutes > 0)
}
elsif ($request_state_name =~ /(tomaintenance)/) {
# Only display this once per hour at most, tomaintenance can be far into the future
if ($start_diff_minutes > 0) {
if ($start_diff_minutes % 60 == 0 && ($start_diff_seconds % 60) >= 50) {
notify($ERRORS{'DEBUG'}, 0, "$request_state_name request will be processed in $start_diff_minutes minutes, returning 0");
}
return "0";
}
else {
# Start time is in past, $start_diff_minutes is negative
notify($ERRORS{'DEBUG'}, 0, "$request_state_name request will be processed now, returning 'start'");
return "start";
}
}
elsif ($request_state_name =~ /(inuse)/) {
if ($end_diff_minutes <= 10) {
notify($ERRORS{'DEBUG'}, 0, "reservation will end in 10 minutes or less ($end_diff_minutes), returning 'end'");
return "end";
}
elsif ($lastcheck_epoch_seconds < $changelog_epoch_seconds && $changelog_diff_seconds < 0) {
notify($ERRORS{'DEBUG'}, 0, "reservation lastcheck $reservation_lastcheck is older than most recent changelog timestamp $changelog_timestamp, returning 'poll'");
return "poll";
}
else {
# End time is more than 10 minutes in the future
if ($serverrequest) {
my $server_inuse_check_time = ($ENV{management_node_info}->{SERVER_INUSE_CHECK} * -1);
if ($lastcheck_diff_minutes <= $server_inuse_check_time) {
return "poll";
}
else {
return 0;
}
}
elsif ($reservation_cnt > 1) {
my $cluster_inuse_check_time = ($ENV{management_node_info}->{CLUSTER_INUSE_CHECK} * -1);;
if ($lastcheck_diff_minutes <= $cluster_inuse_check_time) {
return "poll";
}
else {
return 0;
}
}
else {
#notify($ERRORS{'DEBUG'}, 0, "reservation will end in more than 10 minutes ($end_diff_minutes)");
my $general_inuse_check_time = ($ENV{management_node_info}->{GENERAL_INUSE_CHECK} * -1);
if ($lastcheck_diff_minutes <= $general_inuse_check_time) {
notify($ERRORS{'DEBUG'}, 0, "reservation was last checked more than $general_inuse_check_time minutes ago ($lastcheck_diff_minutes), returning 'poll'");
return "poll";
}
else {
#notify($ERRORS{'DEBUG'}, 0, "reservation has been checked within the past $general_inuse_check_time minutes ($lastcheck_diff_minutes), returning 0");
return 0;
}
}
} ## end else [ if ($end_diff_minutes <= 10)
}
elsif ($request_state_name =~ /(complete|failed)/) {
# Don't need to keep requests in database if laststate was...
if ($request_laststate_name =~ /image|deleted|makeproduction|reload|tomaintenance|tovmhostinuse/) {
notify($ERRORS{'DEBUG'}, 0, "request laststate is '$request_laststate_name', returning 'remove'");
return "remove";
}
elsif ($end_diff_minutes < 0) {
notify($ERRORS{'DEBUG'}, 0, "reservation end time was in the past ($end_diff_minutes), returning 'remove'");
return "remove";
}
else {
# End time is now or in the future
#notify($ERRORS{'DEBUG'}, 0, "reservation end time is either right now or in the future ($end_diff_minutes), returning 0");
return "0";
}
} # Close if state is complete or failed
# Just return start for all other states
else {
notify($ERRORS{'DEBUG'}, 0, "request state is '$request_state_name', returning 'start'");
return "start";
}
} ## end sub check_time
#//////////////////////////////////////////////////////////////////////////////
=head2 mail
Parameters : $to, $subject, $mailstring, $from
Returns : 1(success) or 0(failure)
Description : send an email
=cut
sub mail {
my ($to, $subject, $mailstring, $from) = @_;
my ($package, $filename, $line, $sub) = caller(0);
# Mail::Mailer relies on sendmail as written, this causes a "die" on Windows
# TODO: Rework this subroutine to not rely on sendmail
my $osname = lc($^O);
if ($osname =~ /win/i) {
notify($ERRORS{'OK'}, 0, "sending mail from Windows not yet supported\n-----\nTo: $to\nSubject: $subject\nFrom: $from\n$mailstring\n-----");
return;
}
# Wrap text for lines longer than 72 characters
#$Text::Wrap::columns = 72;
#$mailstring = wrap('', '', $mailstring);
# compare requestor and owner, if same only mail one
if (!(defined($from))) {
$from = $DEFAULTHELPEMAIL;
}
my $mailer;
my $mailer_options = '';
if (defined($RETURNPATH)) {
$mailer_options = "-f $RETURNPATH";
}
eval {
$mailer = Mail::Mailer->new("sendmail", $mailer_options);
};
if ($EVAL_ERROR) {
notify($ERRORS{'WARNING'}, 0, "failed to send mail, error:\n$EVAL_ERROR");
return;
}
my $shared_mail_box = '';
my $management_node_info = get_management_node_info();
if ($management_node_info) {
$shared_mail_box = $management_node_info->{SHARED_EMAIL_BOX} if $management_node_info->{SHARED_EMAIL_BOX};
}
my $mail_args = {From => $from, To => $to, Subject => $subject};
if ($shared_mail_box) {
$mail_args->{Bcc} = $shared_mail_box;
}
if($mailstring =~ /<html>/ || $mailstring =~ /<html .*>/) {
$mail_args->{'Content-Type'} = "text/html";
notify($ERRORS{'DEBUG'}, 0, "Encountered message containing <html> tag, adding Content-Type header");
}
if ($mailer->open($mail_args)) {
print $mailer $mailstring;
$mailer->close();
notify($ERRORS{'OK'}, 0, "SUCCESS -- Sending mail To: $to, $subject");
}
else {
notify($ERRORS{'WARNING'}, 0, "NOTICE -- Problem sending mail to: $to");
}
} ## end sub mail
#//////////////////////////////////////////////////////////////////////////////
=head2 update_preload_fla
Parameters : request id, flag 1,0
Returns : 1 success 0 failure
Description : update preload flag
=cut
sub update_preload_flag {
my ($request_id, $flag) = @_;
my ($package, $filename, $line, $sub) = caller(0);
notify($ERRORS{'WARNING'}, 0, "request id is not defined") unless (defined($request_id));
notify($ERRORS{'WARNING'}, 0, "preload flag is not defined") unless (defined($flag));
my $update_statement = "
UPDATE
request
SET
preload = $flag
WHERE
id = $request_id
";
# Call the database execute subroutine
if (database_execute($update_statement)) {
# Update successful
notify($ERRORS{'OK'}, $LOGFILE, "preload flag updated for request_id $request_id ");
return 1;
}
else {
notify($ERRORS{'CRITICAL'}, 0, "unable to update preload flag updated for request_id $request_id");
return 0;
}
} ## end sub update_preload_flag
#//////////////////////////////////////////////////////////////////////////////
=head2 update_request_state
Parameters : $request_id, $state_name, $laststate_name, $force (optional)
Returns : boolean
Description : Updates the request state and laststate. If the state is being
updated to pending, the laststate argument must match the current
state. This prevents problems if the state was updated via the
website after the running vcld process was launched:
OK - Current: inuse/reserved --> Argument: pending/inuse
Not OK - Current: image/inuse --> Argument: pending/inuse
This can be overridden by passing the $force argument and should
only be done for testing.
=cut
sub update_request_state {
my ($request_id, $state_name, $laststate_name, $force) = @_;
# Check the passed parameters
if (!defined($request_id)) {
notify($ERRORS{'WARNING'}, 0, "unable to update request state, request id argument was not supplied");
return;
}
if (!defined($state_name)) {
notify($ERRORS{'WARNING'}, 0, "unable to update request $request_id state, state name argument not supplied");
return;
}
if (!defined($laststate_name)) {
notify($ERRORS{'WARNING'}, 0, "unable to update request $request_id state to $state_name, last state name argument not supplied");
return;
}
my $update_statement = <<EOF;
UPDATE
request,
state state,
state laststate,
state currentstate,
state currentlaststate
SET
request.stateid = state.id,
request.laststateid = laststate.id
WHERE
request.id = $request_id
AND request.stateid = currentstate.id
AND request.laststateid = currentlaststate.id
AND state.name = '$state_name'
AND laststate.name = '$laststate_name'
AND currentstate.name != 'maintenance'
EOF
if (!$force) {
if ($state_name eq 'pending') {
# Avoid: deleted/inuse --> pending/inuse
$update_statement .= "AND laststate.name = currentstate.name\n";
}
elsif ($state_name !~ /(complete|failed|maintenance)/) {
# New state is not pending
# Avoid: pending/image --> inuse/inuse
$update_statement .= "AND currentstate.name = 'pending'\n";
$update_statement .= "AND currentlaststate.name = '$laststate_name'\n";
}
}
# Call the database execute subroutine
my $result = database_execute($update_statement);
if (defined($result)) {
my $rows_updated = (sprintf '%d', $result);
if ($rows_updated) {
notify($ERRORS{'OK'}, $LOGFILE, "request $request_id state updated to $state_name/$laststate_name");
return 1;
}
else {
my ($current_state_name, $current_laststate_name) = get_request_current_state_name($request_id);
if ($state_name eq $current_state_name && $laststate_name eq $current_laststate_name) {
notify($ERRORS{'OK'}, $LOGFILE, "request $request_id state already set to $current_state_name/$current_laststate_name");
return 1;
}
elsif ($current_state_name eq 'deleted' || $current_laststate_name eq 'deleted') {
notify($ERRORS{'OK'}, $LOGFILE, "request $request_id has been deleted, state not set to $state_name/$laststate_name, current state: $current_state_name/$current_laststate_name");
return 1;
}
else {
notify($ERRORS{'WARNING'}, $LOGFILE, "unable to update request $request_id state to $state_name/$laststate_name, current state: $current_state_name/$current_laststate_name, SQL statement:\n$update_statement");
return;
}
}
return $rows_updated;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to update request $request_id state to $state_name/$laststate_name");
return;
}
} ## end sub update_request_state
#//////////////////////////////////////////////////////////////////////////////
=head2 update_computer_state
Parameters : $computer_id, $state_name, $log
Returns : 1 success 0 failure
Description : update computer state
=cut
sub update_computer_state {
my ($computer_id, $state_name, $log) = @_;
my ($package, $filename, $line, $sub) = caller(0);
notify($ERRORS{'WARNING'}, $log, "computer id is not defined") unless (defined($computer_id));
notify($ERRORS{'WARNING'}, $log, "statename is not defined") unless (defined($state_name));
return 0 unless (defined $computer_id && defined $state_name);
my $update_statement = "
UPDATE
computer,
state
SET
computer.stateid = state.id
WHERE
state.name = \'$state_name\'
AND computer.id = $computer_id
";
# Call the database execute subroutine
if (database_execute($update_statement)) {
# Update successful
notify($ERRORS{'OK'}, $LOGFILE, "computer $computer_id state updated to: $state_name");
return 1;
}
else {
notify($ERRORS{'CRITICAL'}, 0, "unable to update states for computer $computer_id");
return 0;
}
} ## end sub update_computer_state
#//////////////////////////////////////////////////////////////////////////////
=head2 update_computer_lastcheck
Parameters : $computer_id, $datestring, $log
Returns : 1 success 0 failure
Description : update computer state
=cut
sub update_computer_lastcheck {
my ($computer_id, $datestring, $log) = @_;
my ($package, $filename, $line, $sub) = caller(0);
$log = 0 unless (defined $log);
notify($ERRORS{'WARNING'}, $log, "computer id is not defined") unless (defined($computer_id));
return 0 unless (defined $computer_id);
unless (defined($datestring)) {
$datestring = makedatestring;
}
my $update_statement = "
UPDATE
computer
SET
computer.lastcheck = '$datestring'
WHERE
computer.id = $computer_id
";
# Call the database execute subroutine
if (database_execute($update_statement)) {
# Update successful
notify($ERRORS{'DEBUG'}, $log, "computer $computer_id lastcheck updated to: $datestring");
return 1;
}
else {
notify($ERRORS{'CRITICAL'}, $log, "unable to update datestring for computer $computer_id");
return 0;
}
} ## end
#//////////////////////////////////////////////////////////////////////////////
=head2 update_computer_procnumber
Parameters : $computer_id, $cpu_count
Returns : boolean
Description : Updates the computer.procnumber value for the specified computer.
=cut
sub update_computer_procnumber {
my ($computer_id, $cpu_count) = @_;
if (!$computer_id || !$cpu_count) {
notify($ERRORS{'WARNING'}, 0, "computer ID and CPU count arguments were not supplied correctly");
return;
}
my $update_statement = <<EOF;
UPDATE
computer
SET
computer.procnumber = '$cpu_count'
WHERE
computer.id = $computer_id
EOF
# Call the database execute subroutine
if (database_execute($update_statement)) {
notify($ERRORS{'DEBUG'}, 0, "updated the procnumber value to $cpu_count for computer ID $computer_id");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to update the procnumber value to $cpu_count for computer ID $computer_id");
return 0;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 update_computer_procspeed
Parameters : $computer_id, $cpu_speed
Returns : boolean
Description : Updates the computer.procspeed value for the specified computer.
The $cpu_speed argument should contain an integer value of the
CPU speed in MHz.
=cut
sub update_computer_procspeed {
my ($computer_id, $cpu_speed_mhz) = @_;
if (!$computer_id || !$cpu_speed_mhz) {
notify($ERRORS{'WARNING'}, 0, "computer ID and CPU speed arguments were not supplied correctly");
return;
}
my $update_statement = <<EOF;
UPDATE
computer
SET
computer.procspeed = '$cpu_speed_mhz'
WHERE
computer.id = $computer_id
EOF
# Call the database execute subroutine
if (database_execute($update_statement)) {
notify($ERRORS{'DEBUG'}, 0, "updated the procspeed value to $cpu_speed_mhz for computer ID $computer_id");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to update the procspeed value to $cpu_speed_mhz for computer ID $computer_id");
return 0;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 update_computer_ram
Parameters : $computer_id, $ram_mb
Returns : boolean
Description : Updates the computer.ram value for the specified computer.
The $ram_mb argument should contain an integer value of the
RAM in MB.
=cut
sub update_computer_ram {
my ($computer_id, $ram_mb) = @_;
if (!$computer_id || !$ram_mb) {
notify($ERRORS{'WARNING'}, 0, "computer ID and RAM arguments were not supplied correctly");
return;
}
my $update_statement = <<EOF;
UPDATE
computer
SET
computer.ram = '$ram_mb'
WHERE
computer.id = $computer_id
EOF
# Call the database execute subroutine
if (database_execute($update_statement)) {
notify($ERRORS{'DEBUG'}, 0, "updated the RAM value to $ram_mb for computer ID $computer_id");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to update the RAM value to $ram_mb for computer ID $computer_id");
return 0;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 update_reservation_password
Parameters : $reservation_id, $password
Returns : 1 success 0 failure
Description : updates password field for reservation id
=cut
sub update_reservation_password {
my ($reservation_id, $password) = @_;
if (!$reservation_id) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
return;
}
if (!$password) {
notify($ERRORS{'WARNING'}, 0, "password argument was not supplied");
return;
}
my $update_statement = "UPDATE reservation SET pw = \'$password\' WHERE id = $reservation_id";
# Call the database execute subroutine
if (database_execute($update_statement)) {
# Update successful
notify($ERRORS{'OK'}, $LOGFILE, "password updated for reservation $reservation_id: $password");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to update password for reservation $reservation_id");
return 0;
}
} ## end sub update_reservation_password
#//////////////////////////////////////////////////////////////////////////////
=head2 is_request_deleted
Parameters : $request_id
Returns : boolean
Description : Checks if the request state or laststate is deleted.
=cut
sub is_request_deleted {
my ($request_id) = @_;
if (!defined($request_id)) {
notify($ERRORS{'WARNING'}, 0, "request ID argument was not specified");
return;
}
my ($state_name, $laststate_name) = get_request_current_state_name($request_id, 1);
if (!$state_name || !$laststate_name) {
notify($ERRORS{'OK'}, 0, "request $request_id state data could not be retrieved, assuming request is deleted and was removed from the database, returning true");
return 1;
}
if ($state_name =~ /(deleted)/ || $laststate_name =~ /(deleted)/) {
notify($ERRORS{'DEBUG'}, 0, "request $request_id state: $state_name/$laststate_name, returning true");
return 1;
}
else {
#notify($ERRORS{'DEBUG'}, 0, "request $request_id state: $state_name/$laststate_name, returning false");
return 0;
}
} ## end sub is_request_deleted
#//////////////////////////////////////////////////////////////////////////////
=head2 get_reservation_accounts
Parameters : $reservation_id
Returns : hash reference
Description : Used for server loads, provides list of users for group access
=cut
sub get_reservation_accounts {
my ($reservation_id) = @_;
if (!defined($reservation_id)) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
return 0;
}
my $select_statement = <<EOF;
SELECT DISTINCT
reservationaccounts.userid AS reservationaccounts_userid,
reservationaccounts.password AS reservationaccounts_password,
affiliation.name AS affiliation_name,
user.unityid AS user_name
FROM
reservationaccounts,
affiliation,
user
WHERE
user.id = reservationaccounts.userid
AND affiliation.id = user.affiliationid
AND reservationaccounts.reservationid = $reservation_id
EOF
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @selected_rows = database_select($select_statement);
my $reservation_accounts = {};
for my $row (@selected_rows) {
my $user_id = $row->{reservationaccounts_userid};
$reservation_accounts->{$user_id}{"userid"} = $user_id;
$reservation_accounts->{$user_id}{"password"} = $row->{reservationaccounts_password};
$reservation_accounts->{$user_id}{"affiliation"} = $row->{affiliation_name};
$reservation_accounts->{$user_id}{"username"} = $row->{user_name};
}
return $reservation_accounts;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_current_reservation_lastcheck
Parameters : @reservation_ids
Returns : string
Description : Retrieves the current value of reservation.lastcheck from the
database. Either a single reservation ID or multiple reservation
IDs may be passed as the argument. If a single reservation ID is
passed, a string is returned containing the reservation.lastcheck
value. If multiple reservation IDs are passed, a hash reference
is returned with the keys set to the reservation IDs.
=cut
sub get_current_reservation_lastcheck {
my @reservation_ids = @_;
# Check the passed parameter
if (!@reservation_ids) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not specified");
return;
}
my $reservation_id_string = join(', ', @reservation_ids);
# Create the select statement
my $select_statement = <<EOF;
SELECT
reservation.id,
reservation.lastcheck
FROM
reservation
WHERE
reservation.id IN ($reservation_id_string)
EOF
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (!@selected_rows) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve current reservation lastcheck for reservations: $reservation_id_string");
return;
}
elsif (scalar(@selected_rows) == 1) {
my $row = $selected_rows[0];
return $row->{lastcheck};
}
else {
my $reservation_lastcheck_info = {};
for my $row (@selected_rows) {
$reservation_lastcheck_info->{$row->{id}} = $row->{lastcheck};
}
return $reservation_lastcheck_info;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 add_reservation_account
Parameters : $reservation_id, $userid, $password
Returns : boolean
Description : Adds an entry to the reservationaccounts table.
=cut
sub add_reservation_account {
my ($reservation_id, $user_id, $password) = @_;
if (!$reservation_id) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not specified");
return;
}
elsif (!$user_id) {
notify($ERRORS{'WARNING'}, 0, "user ID argument was not specified");
return;
}
my $password_string;
if ($password) {
$password_string = "'$password'";
}
else {
$password_string = 'NULL';
}
my $statement = <<EOF;
INSERT IGNORE INTO
reservationaccounts
(
reservationid,
userid,
password
)
VALUES
(
'$reservation_id',
'$user_id',
$password_string
)
ON DUPLICATE KEY UPDATE password = $password_string
EOF
if (!database_execute($statement)) {
notify($ERRORS{'OK'}, 0, "failed to add user $user_id to reservationaccounts table");
return;
}
return 1;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 delete_reservation_account
Parameters : $reservation_id, $userid
Returns : boolean
Description : Deletes an entry from the reservationaccounts table.
=cut
sub delete_reservation_account {
my ($reservation_id, $user_id) = @_;
if (!$reservation_id) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not specified");
return;
}
elsif (!$user_id) {
notify($ERRORS{'WARNING'}, 0, "user ID argument was not specified");
return;
}
my $statement = <<EOF;
DELETE FROM
reservationaccounts
WHERE
reservationid = '$reservation_id'
AND userid = '$user_id'
EOF
if (!database_execute($statement)) {
notify($ERRORS{'OK'}, 0, "failed to delete user $user_id from reservationaccounts table");
return;
}
return 1;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_next_image_default
Parameters : $computerid
Returns : imageid,imagerevisionid,imagename
Description : Looks for any upcoming reservations for supplied computerid, if
starttime is within 50 minutes return that imageid. Else fetch
and return next image
=cut
sub get_next_image_default {
my ($computerid) = @_;
my ($calling_package, $calling_filename, $calling_line, $calling_sub) = caller(0);
if (!defined($computerid)) {
notify($ERRORS{'WARNING'}, 0, "$calling_sub $calling_package missing mandatory variable: computerid ");
return 0;
}
my $select_statement = "
SELECT DISTINCT
req.start AS starttime,
ir.imagename AS imagename,
res.imagerevisionid AS imagerevisionid,
res.imageid AS imageid
FROM
reservation res,
request req,
image i,
state s,
imagerevision ir
WHERE
res.requestid = req.id
AND req.stateid = s.id
AND i.id = res.imageid
AND ir.id = res.imagerevisionid
AND res.computerid = $computerid
AND (s.name = \'new\' OR s.name = \'reload\' OR s.name = \'imageprep\')
";
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @selected_rows = database_select($select_statement);
my @ret_array;
# Check to make sure 1 or more rows were returned
if (scalar @selected_rows > 0) {
# Loop through list of upcoming reservations
# Based on the start time load the next one
my $now = time();
# It contains a hash
for (@selected_rows) {
my %reservation_row = %{$_};
# $reservation_row{starttime}
# $reservation_row{imagename}
# $reservation_row{imagerevisionid}
# $reservation_row{imageid}
my $epoch_start = convert_to_epoch_seconds($reservation_row{starttime});
my $diff = $epoch_start - $now;
# If start time is less than 50 minutes from now return this image
notify($ERRORS{'OK'}, 0, "get_next_image_default : diff= $diff image= $reservation_row{imagename} imageid=$reservation_row{imageid}");
if ($diff < (50 * 60)) {
notify($ERRORS{'OK'}, 0, "get_next_image_default : future reservation detected diff= $diff image= $reservation_row{imagename} imageid=$reservation_row{imageid}");
push(@ret_array, $reservation_row{imagename}, $reservation_row{imageid}, $reservation_row{imagerevisionid});
return @ret_array;
}
} ## end for (@selected_rows)
} ## end if (scalar @selected_rows > 0)
# No upcoming reservations - fetch next image information
my $select_nextimage = "
SELECT DISTINCT
imagerevision.imagename AS imagename,
imagerevision.id AS imagerevisionid,
image.id AS imageid
FROM
image,
computer,
imagerevision
WHERE
imagerevision.imageid = computer.nextimageid
AND imagerevision.production = 1
AND computer.nextimageid = image.id
AND computer.id = $computerid
";
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @next_selected_rows = database_select($select_nextimage);
# Check to make sure at least 1 row were returned
if (scalar @next_selected_rows == 0) {
notify($ERRORS{'WARNING'}, 0, "get_next_image_default failed to fetch next image for computerid $computerid");
return 0;
}
elsif (scalar @next_selected_rows > 1) {
notify($ERRORS{'WARNING'}, 0, "" . scalar @next_selected_rows . " rows were returned from database select");
return 0;
}
notify($ERRORS{'OK'}, 0, "get_next_image_default : returning next image=$next_selected_rows[0]{imagename} imageid=$next_selected_rows[0]{imageid}");
push(@ret_array, $next_selected_rows[0]{imagename}, $next_selected_rows[0]{imageid}, $next_selected_rows[0]{imagerevisionid});
return @ret_array;
} ## end sub get_next_image
#//////////////////////////////////////////////////////////////////////////////
=head2 setnextimage
Parameters : $computerid, $image
Returns : 1 success, 0 failed
Description : updates nextimageid on provided computerid
=cut
sub setnextimage {
my ($computerid, $imageid) = @_;
my ($package, $filename, $line, $sub) = caller(0);
notify($ERRORS{'WARNING'}, 0, "computerid: node is not defined") if (!(defined($computerid)));
notify($ERRORS{'WARNING'}, 0, "imageid: node is not defined") if (!(defined($imageid)));
my $update_statement = " UPDATE computer SET nextimageid = $imageid WHERE id = $computerid ";
# Call the database execute subroutine
if (database_execute($update_statement)) {
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "unable to update nextimageid");
return 0;
}
} ## end sub setnextimage
#//////////////////////////////////////////////////////////////////////////////
=head2 nmap_port
Parameters : $hostname, $port
Returns : boolean
Description : use nmap port scanning tool to determine if port is open
=cut
sub nmap_port {
my ($hostname, $port) = @_;
if (!$hostname) {
notify($ERRORS{'WARNING'}, 0, "hostname argument was not specified");
return;
}
if (!defined($port)) {
notify($ERRORS{'WARNING'}, 0, "port argument was not specified");
return;
}
# Determine which string to use as the connection target
my $remote_connection_target = determine_remote_connection_target($hostname);
my $hostname_string = $remote_connection_target;
$hostname_string .= " ($hostname)" if ($hostname ne $remote_connection_target);
my $command = "/usr/bin/nmap $remote_connection_target -P0 -p $port -T Aggressive -n";
my ($exit_status, $output) = run_command($command, 1);
if (!defined($output)) {
notify($ERRORS{'WARNING'}, 0, "failed to run nmap command on management node: '$command'");
return;
}
elsif (grep(/(open|filtered)/i, @$output)) {
notify($ERRORS{'DEBUG'}, 0, "port $port is open on $hostname_string");
return 1;
}
elsif (grep(/(nmap:|warning)/i, @$output)) {
notify($ERRORS{'WARNING'}, 0, "error occurred running nmap command: '$command', output:\n" . join("\n", @$output));
return;
}
else {
notify($ERRORS{'DEBUG'}, 0, "port $port is closed on $hostname_string");
return 0;
}
} ## end sub nmap_port
#//////////////////////////////////////////////////////////////////////////////
=head2 _pingnode
Parameters : $node
Returns : boolean
Description : Uses Net::Ping::External to check if a node is responding to ICMP
echo ping.
=cut
sub _pingnode {
my ($node) = @_;
if (!$node) {
notify($ERRORS{'WARNING'}, 0, "node argument was not supplied");
return;
}
my $node_string = $node;
my $remote_connection_target;
if (is_valid_ip_address($node, 0)) {
$remote_connection_target = $node;
}
else {
$remote_connection_target = determine_remote_connection_target($node);
if ($remote_connection_target) {
$node_string .= " ($remote_connection_target)";
$node = $remote_connection_target;
}
}
my $result = ping(host => $remote_connection_target, timeout => 1);
if ($result) {
#notify($ERRORS{'DEBUG'}, 0, "$node_string is responding to ping");
return 1;
}
else {
#notify($ERRORS{'DEBUG'}, 0, "$node_string is NOT responding to ping");
return 0;
}
} ## end sub _pingnode
#//////////////////////////////////////////////////////////////////////////////
=head2 getnewdbh
Parameters : none
Returns : 0 failed or database handle
Description : gets a databasehandle
=cut
sub getnewdbh {
#my $caller_trace = get_caller_trace(7, 1);
#notify($ERRORS{'DEBUG'}, 0, "called from: $caller_trace");
my ($database) = @_;
$database = $DATABASE if !defined($database);
my $dbh;
# Try to use the existing database handle
if ($ENV{dbh} && $ENV{dbh}->ping && $ENV{dbh}->{Name} =~ /^$database:/) {
#notify($ERRORS{'DEBUG'}, 0, "using database handle stored in \$ENV{dbh}");
return $ENV{dbh};
}
elsif ($ENV{dbh} && $ENV{dbh}->ping) {
my ($stored_database_name) = $ENV{dbh}->{Name} =~ /^([^:]*)/;
notify($ERRORS{'DEBUG'}, 0, "database requested ($database) does not match handle stored in \$ENV{dbh} (" . $ENV{dbh}->{Name} . ")");
}
elsif (defined $ENV{dbh}) {
notify($ERRORS{'DEBUG'}, 0, "unable to use database handle stored in \$ENV{dbh}");
}
else {
#notify($ERRORS{'DEBUG'}, 0, "\$ENV{dbh} is not defined, creating new database handle");
}
my $attempt = 0;
my $max_attempts = 5;
my $retry_delay = 2;
# Assemble the data source string
my $data_source;
if ($MYSQL_SSL) {
$data_source = "$database:$SERVER;mysql_ssl=1;mysql_ssl_ca_file=$MYSQL_SSL_CERT";
}
else {
$data_source = "$database:$SERVER";
}
# Attempt to connect to the data source and get a database handle object
my $dbi_result;
while (!$dbh && $attempt < $max_attempts) {
$attempt++;
# Attempt to connect
#notify($ERRORS{'DEBUG'}, 0, "attempting to connect to data source: $data_source, user: " . string_to_ascii($WRTUSER) . ", pass: " . string_to_ascii($WRTPASS));
$dbh = DBI->connect(qq{dbi:mysql:$data_source}, $WRTUSER, $WRTPASS, {PrintError => 0});
# Check if connect was successful
if ($dbh && $dbh->ping) {
# Set InactiveDestroy = 1 for all dbh's belonging to child processes
# Set InactiveDestroy = 0 for all dbh's belonging to vcld
if (!defined $ENV{vcld} || !$ENV{vcld}) {
$dbh->{InactiveDestroy} = 1;
}
else {
$dbh->{InactiveDestroy} = 0;
}
# Increment the dbh count environment variable if it is defined
# This is only for development and testing to see how many handles a process creates
$ENV{dbh_count}++ if defined($ENV{dbh_count});
# Store the newly created database handle in an environment variable
# Only store it if $ENV{dbh} is already defined
# It's up to other modules to determine if $ENV{dbh} is defined, they must initialize it
if (defined $ENV{dbh}) {
$ENV{dbh} = $dbh;
notify($ERRORS{'DEBUG'}, 0, "database handle stored in \$ENV{dbh}");
}
return $dbh;
} ## end if ($dbh && $dbh->ping)
# Something went wrong, construct a DBI result string
$dbi_result = "DBI result: ";
if (defined(DBI::err())) {
$dbi_result = "(" . DBI::err() . ")";
}
if (defined(DBI::errstr())) {
$dbi_result .= " " . DBI::errstr();
}
# Check for access denied
if (DBI::err() == 1045 || DBI::errstr() =~ /access denied/i) {
notify($ERRORS{'WARNING'}, 0, "unable to connect to database, $dbi_result");
return 0;
}
# Either connect or ping failed
if ($dbh && !$dbh->ping) {
notify($ERRORS{'DEBUG'}, 0, "database connect succeeded but ping failed, attempt $attempt/$max_attempts, $dbi_result");
$dbh->disconnect;
}
else {
notify($ERRORS{'DEBUG'}, 0, "database connect failed, attempt $attempt/$max_attempts, $dbi_result");
}
notify($ERRORS{'DEBUG'}, 0, "sleeping for $retry_delay seconds");
sleep $retry_delay;
next;
} ## end while (!$dbh && $attempt < $max_attempts)
# Maximum number of attempts was reached
notify($ERRORS{'WARNING'}, 0, "failed to connect to database, attempts made: $attempt/$max_attempts, $dbi_result");
return 0;
} ## end sub getnewdbh
#//////////////////////////////////////////////////////////////////////////////
=head2 notify_via_oascript
Parameters : $node, $user, $message
Returns : 0 or 1
Description : using apple oascript write supplied $message to finder
=cut
sub notify_via_oascript {
my ($node, $user, $message) = @_;
my ($package, $filename, $line, $sub) = caller(0);
notify($ERRORS{'WARNING'}, 0, "node is not defined") if (!(defined($node)));
notify($ERRORS{'WARNING'}, 0, "message is not defined") if (!(defined($message)));
notify($ERRORS{'WARNING'}, 0, "user is not defined") if (!(defined($user)));
# Escape new lines
$message =~ s/\n/ /gs;
$message =~ s/\'/\\\\\\\'/gs;
notify($ERRORS{'DEBUG'}, 0, "message:\n$message");
my $command = "/var/root/VCL/oamessage \"$message\"";
if (run_ssh_command($node, $ENV{management_node_info}{keys}, $command)) {
notify($ERRORS{'OK'}, 0, "successfully sent message to OSX user $user on $node");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to send message to OSX user $user on $node");
return 0;
}
} ## end sub notify_via_oascript
#//////////////////////////////////////////////////////////////////////////////
=head2 getpw
Parameters : $password_length (optional), $include_special_characters (optional)
Returns : string
Description : Generates a random password.
=cut
sub getpw {
my ($password_length, $include_special_characters) = @_;
if (!$password_length) {
$password_length = $ENV{management_node_info}{USER_PASSWORD_LENGTH} || 8;
}
if (!defined($include_special_characters)) {
$include_special_characters = $ENV{management_node_info}{INCLUDE_SPECIAL_CHARS};
}
#Skip certain confusing chars like: iI1lL,0Oo Zz2
my @character_set = (
'A' .. 'H',
'J' .. 'K',
'M' .. 'N',
'P' .. 'Y',
'a' .. 'h',
'j' .. 'k',
'm' .. 'n',
'p' .. 'y',
'3' .. '9',
);
if ($include_special_characters) {
my @special_characters = (
'-',
'_',
'!',
'%',
'#',
'$',
'@',
'+',
'=',
'{',
'}',
'?',
);
push @character_set, @special_characters;
}
my $character_set_size = (scalar(@character_set));
my $password;
srand;
for (1 .. $password_length) {
my $random_index = int(rand($character_set_size));
$password .= $character_set[$random_index];
}
return $password;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_user_group_member_info
Parameters : $usergroupid
Returns : array of user group memebers
Description : queries database and collects user members of supplied usergroupid
=cut
sub get_user_group_member_info {
my ($user_group_id) = @_;
if (!defined($user_group_id)) {
notify($ERRORS{'WARNING'}, 0, "user group ID argument was not specified");
return;
}
my $select_statement = <<EOF;
SELECT
user.*
FROM
user,
usergroupmembers
WHERE
user.id = usergroupmembers.userid
AND usergroupmembers.usergroupid = '$user_group_id'
EOF
# Call the database select subroutine
my @selected_rows = database_select($select_statement);
if (!@selected_rows) {
notify($ERRORS{'DEBUG'}, 0, "no data was returned for user group ID $user_group_id, returning an empty list");
return {};
}
my $user_group_member_info;
for my $row (@selected_rows) {
my $user_id = $row->{id};
for my $column (keys %$row) {
next if $column eq 'id';
$user_group_member_info->{$user_id}{$column} = $row->{$column};
}
}
#notify($ERRORS{'DEBUG'}, 0, "retrieved member info for user group ID $user_group_id:\n" . format_data($user_group_member_info));
return $user_group_member_info;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 notifyviaIM
Parameters : IM type, IM user ID, message string
Returns : 0 or 1
Description : if Jabber enabled - send IM to user
currently only supports jabber
=cut
sub notify_via_im {
my ($im_type, $im_id, $im_message) = @_;
notify($ERRORS{'WARNING'}, 0, "IM type is not defined") if (!(defined($im_type)));
notify($ERRORS{'WARNING'}, 0, "IM id is not defined") if (!(defined($im_id)));
notify($ERRORS{'WARNING'}, 0, "IM message is not defined") if (!(defined($im_message)));
if ($im_type eq "jabber") {
# Check if jabber functions are disabled on this management node
if ($JABBER) {
notify($ERRORS{'DEBUG'}, 0, "jabber functions are enabled on this management node");
}
else {
notify($ERRORS{'DEBUG'}, 0, "jabber functions are disabled on this management node");
return 1;
}
# Create a jabber client object
my $jabber_client = new Net::Jabber::Client();
if ($jabber_client) {
notify($ERRORS{'DEBUG'}, 0, "jabber client object created");
}
else {
notify($ERRORS{'DEBUG'}, 0, "failed to created jabber client object");
return;
}
# Attempt to connect to the jabber server
my $jabber_connect_result = $jabber_client->Connect(hostname => $JABBER_SERVER, port => $JABBER_PORT);
if (!$jabber_connect_result) {
notify($ERRORS{'DEBUG'}, 0, "connected to jabber server: $JABBER_SERVER, port: $JABBER_PORT, result: $jabber_connect_result");
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to connect to jabber server: $JABBER_SERVER, port: $JABBER_PORT, result: $jabber_connect_result");
return;
}
# Attempt to authenticate to jabber
my @jabber_auth_result = $jabber_client->AuthSend(
username => $JABBER_USER,
password => $JABBER_PASSWORD,
resource => $JABBER_RESOURCE
);
# Check the jabber authentication result
if ($jabber_auth_result[0] && $jabber_auth_result[0] eq "ok") {
notify($ERRORS{'DEBUG'}, 0, "authenticated to jabber server: $JABBER_SERVER, user: $JABBER_USER, resource: $JABBER_RESOURCE");
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to authenticate to jabber server: $JABBER_SERVER, user: $JABBER_USER, resource: $JABBER_RESOURCE");
return;
}
# Create jabber message
my $jabber_message = Net::Jabber::Message->new();
$jabber_message->SetMessage(
to => $im_id,
subject => "Notification",
type => "chat",
body => $im_message
);
# Attempt to send the jabber message
my $jabber_send_result = $jabber_client->Send($jabber_message);
if ($jabber_send_result) {
notify($ERRORS{'OK'}, 0, "jabber message sent to $im_id");
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to send jabber message to $JABBER_USER");
return;
}
} ## end if ($im_type eq "jabber" && defined $jabberready)
else {
notify($ERRORS{'WARNING'}, 0, "IM type is not supported: $im_type");
return 0;
}
return 1;
} ## end sub notify_via_im
#//////////////////////////////////////////////////////////////////////////////
=head2 insertloadlog
Parameters : $reservation_id, $computer_id, $loadstatename, $additional_info
Returns : boolean
Description : Inserts an entry into the computerloadlog table.
=cut
sub insertloadlog {
my ($reservation_id, $computer_id, $loadstatename, $additional_info) = @_;
if (!defined($reservation_id)) {
notify($ERRORS{'WARNING'}, 0, "reservation ID argument was not supplied");
return;
}
elsif (!defined($computer_id)) {
notify($ERRORS{'WARNING'}, 0, "computer ID argument was not supplied");
return;
}
elsif (!defined($loadstatename)) {
notify($ERRORS{'WARNING'}, 0, "computerloadstate name argument was not supplied");
return;
}
$additional_info = 'no additional info' unless defined($additional_info);
# Escape any special characters in additional info
$additional_info = quotemeta $additional_info;
# Assemble the SQL statement
my $insert_loadlog_statement = <<EOF;
INSERT INTO
computerloadlog
(
reservationid,
computerid,
loadstateid,
timestamp,
additionalinfo
)
VALUES
(
'$reservation_id',
'$computer_id',
(SELECT id FROM computerloadstate WHERE loadstatename = '$loadstatename'),
NOW(),
'$additional_info'
)
EOF
# Execute the insert statement, the return value should be the id of the computerloadlog row that was inserted
if (database_execute($insert_loadlog_statement)) {
notify($ERRORS{'OK'}, 0, "inserted '$loadstatename' computerloadlog entry");
return 1;
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to insert '$loadstatename' computerloadlog entry");
return;
}
} ## end sub insertloadlog
#//////////////////////////////////////////////////////////////////////////////
=head2 database_select
Parameters : SQL select statement
Returns : array containing hash references to rows returned
Description : gets information from the database
=cut
sub database_select {
my ($select_statement, $database) = @_;
my $calling_sub = (caller(1))[3] || 'undefined';
# Initialize the database_select_calls element if not already initialized
if (!ref($ENV{database_select_calls})) {
$ENV{database_select_calls} = {};
}
# For performance tuning - count the number of calls
$ENV{database_select_count}++;
if (!defined($ENV{database_select_calls}{$calling_sub})) {
$ENV{database_select_calls}{$calling_sub} = 1;
}
else {
$ENV{database_select_calls}{$calling_sub}++;
}
$database = $DATABASE unless $database;
my $dbh;
if (!($dbh = getnewdbh($database))) {
# Try again if first attempt failed
if (!($dbh = getnewdbh($database))) {
notify($ERRORS{'WARNING'}, 0, "unable to obtain database handle, " . DBI::errstr());
return ();
}
}
# Prepare the select statement handle
my $select_handle;
$select_handle = $dbh->prepare($select_statement);
# Check the select statement handle
if (!$select_handle) {
notify($ERRORS{'WARNING'}, 0, "could not prepare select statement, $select_statement, " . $dbh->errstr());
$dbh->disconnect if !defined $ENV{dbh};
return ();
}
# Execute the statement handle
if (!($select_handle->execute())) {
(my $select_statement_condensed = $select_statement) =~ s/[\n\s]+/ /g;
notify($ERRORS{'WARNING'}, 0, "failed to execute statement on $database database:\n$select_statement\n" .
"---\n" .
"copy/paste select statement:\n$select_statement_condensed\n" .
"---\n" .
"error: " . $dbh->errstr()
);
$select_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
return ();
}
# Fetch all the rows returned by the select query
# An array reference is created containing hash refs because {} is passed to fetchall_arrayref
my @return_rows = @{$select_handle->fetchall_arrayref({})};
$select_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
return @return_rows;
} ## end sub database_select
#//////////////////////////////////////////////////////////////////////////////
=head2 database_execute
Parameters : $sql_statement, $database (optional)
Returns : boolean
Description : Executes an SQL statement. If $sql_statement is an INSERT
statement, the ID of the row inserted is returned.
=cut
sub database_execute {
my ($sql_statement, $database) = @_;
$ENV{database_execute_count}++;
my $dbh;
if (!($dbh = getnewdbh($database))) {
# Try again if first attempt failed
if (!($dbh = getnewdbh($database))) {
notify($ERRORS{'WARNING'}, 0, "unable to obtain database handle, " . DBI::errstr());
return;
}
}
# Prepare the statement handle
my $statement_handle = $dbh->prepare($sql_statement);
# Check the statement handle
if (!$statement_handle) {
my $error_string = $dbh->errstr() || '<unknown error>';
notify($ERRORS{'WARNING'}, 0, "could not prepare SQL statement, $sql_statement, $error_string");
$dbh->disconnect if !defined $ENV{dbh};
return;
}
# Execute the statement handle
my $result = $statement_handle->execute();
if (!defined($result)) {
my $error_string = $dbh->errstr() || '<unknown error>';
$statement_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
if (wantarray) {
return (0, $error_string);
}
else {
notify($ERRORS{'WARNING'}, 0, "could not execute SQL statement: $sql_statement\n$error_string");
return;
}
}
#my $sql_warning_count = $statement_handle->{'mysql_warning_count'};
#if ($sql_warning_count) {
# my $warnings = $dbh->selectall_arrayref('SHOW WARNINGS');
# notify($ERRORS{'WARNING'}, 0, "warning generated from SQL statement:\n$sql_statement\nwarnings:\n" . format_data($warnings));
#}
# Get the id of the last inserted record if this is an INSERT statement
if ($sql_statement =~ /^\s*insert/i) {
my $sql_insertid = $statement_handle->{'mysql_insertid'};
my $sql_warning_count = $statement_handle->{'mysql_warning_count'};
$statement_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
if ($sql_insertid) {
return $sql_insertid;
}
else {
return $result;
}
}
else {
$statement_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
return $result;
}
} ## end sub database_execute
#//////////////////////////////////////////////////////////////////////////////
=head2 database_update
Parameters : $update_statement, $database (optional)
Returns : boolean
Description : Executes an SQL UPDATE statement. Returns the number of rows
updated. 0 is returned if the statement was successfully executed
but no rows were updated. Undefined is returned if the statement
failed to execute.
=cut
sub database_update {
my ($update_statement, $database) = @_;
if (!$update_statement) {
notify($ERRORS{'WARNING'}, 0, "SQL UPDATE statement argument not supplied");
return;
}
elsif ($update_statement !~ /^\s*UPDATE\s/i) {
notify($ERRORS{'WARNING'}, 0, "invalid SQL UPDATE statement argument, it does not begin with 'UPDATE':\n$update_statement");
return;
}
my $dbh = getnewdbh($database);
if (!$dbh) {
sleep_uninterrupted(3);
$dbh = getnewdbh($database);
if (!$dbh) {
notify($ERRORS{'WARNING'}, 0, "unable to obtain database handle, " . DBI::errstr());
return;
}
}
# Prepare the statement handle
my $statement_handle = $dbh->prepare($update_statement);
# Check the statement handle
if (!$statement_handle) {
my $error_string = $dbh->errstr() || '<unknown error>';
notify($ERRORS{'WARNING'}, 0, "failed to prepare SQL UPDATE statement, $update_statement, $error_string");
$dbh->disconnect if !defined $ENV{dbh};
return;
}
# Execute the statement handle
my $result = $statement_handle->execute();
if (!defined($result)) {
my $error_string = $dbh->errstr() || '<unknown error>';
$statement_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
notify($ERRORS{'WARNING'}, 0, "failed to execute SQL UPDATE statement: $update_statement\nerror:\n$error_string");
return;
}
my $updated_row_count = $statement_handle->rows;
$statement_handle->finish;
$dbh->disconnect if !defined $ENV{dbh};
$update_statement =~ s/[\n\s]+/ /g;
notify($ERRORS{'DEBUG'}, 0, "returning number of rows affected by UPDATE statement: $updated_row_count\n$update_statement");
return $updated_row_count;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_request_info
Parameters : $request_id, $no_cache (optional)
Returns : hash reference
Description : Retrieves all request/reservation information.
=cut
sub get_request_info {
my ($request_id, $no_cache) = @_;
if (!(defined($request_id))) {
notify($ERRORS{'WARNING'}, 0, "request ID argument was not specified");
return;
}
# Don't use cached info by default
if (!$no_cache) {
$no_cache = 1;
}
# Get a hash ref containing the database column names
my $database_table_columns = get_database_table_columns();
my %tables = (
'request' => 'request',
'serverrequest' => 'serverrequest',
'reservation' => 'reservation',
'state' => 'state',
'laststate' => 'state',
);
# Construct the select statement
my $select_statement = "SELECT DISTINCT\n";
# Get the column names for each table and add them to the select statement
for my $table_alias (keys %tables) {
my $table_name = $tables{$table_alias};
my @columns = @{$database_table_columns->{$table_name}};
for my $column (@columns) {
$select_statement .= "$table_alias.$column AS '$table_alias-$column',\n";
}
}
# Remove the comma after the last column line
$select_statement =~ s/,$//;
# Complete the select statement
$select_statement .= <<EOF;
FROM
request
LEFT JOIN (serverrequest) ON (serverrequest.requestid = request.id),
reservation,
state,
state laststate
WHERE
request.id = $request_id
AND reservation.requestid = request.id
AND state.id = request.stateid
AND laststate.id = request.laststateid
GROUP BY
reservation.id
EOF
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @selected_rows = database_select($select_statement);
# Check to make sure 1 or more rows were returned
if (!@selected_rows) {
notify($ERRORS{'WARNING'}, 0, "info for request $request_id could not be retrieved from the database, select statement:\n$select_statement");
return;
}
# Build the hash
my $request_info;
my $request_state_name;
for my $reservation_row (@selected_rows) {
my $reservation_id = $reservation_row->{'reservation-id'};
if (!$reservation_id) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve request info, row does not contain a reservation-id value:\n" . format_data($reservation_row));
return;
}
$request_info->{RESERVATIONID} = $reservation_id if (scalar @selected_rows == 1);
# Loop through all the columns returned
for my $key (keys %$reservation_row) {
my $value = $reservation_row->{$key};
# Split the table-column names
my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
if ($table eq 'request') {
$request_info->{$column} = $value;
}
elsif ($table eq 'reservation') {
$request_info->{reservation}{$reservation_id}{$column} = $value;
}
elsif ($table eq 'serverrequest') {
$request_info->{reservation}{$reservation_id}{serverrequest}{$column} = $value;
}
else {
$request_info->{$table}{$column} = $value;
}
}
$request_state_name = $request_info->{state}{name};
# Store duration in epoch seconds format
my $request_start_epoch = convert_to_epoch_seconds($request_info->{start});
my $request_end_epoch = convert_to_epoch_seconds($request_info->{end});
$request_info->{DURATION} = ($request_end_epoch - $request_start_epoch);
# Add the image info to the hash
my $image_id = $request_info->{reservation}{$reservation_id}{imageid};
my $image_info = get_image_info($image_id, $no_cache);
$request_info->{reservation}{$reservation_id}{image} = $image_info;
# Add the imagerevision info to the hash
my $imagerevision_id = $request_info->{reservation}{$reservation_id}{imagerevisionid};
my $imagerevision_info = get_imagerevision_info($imagerevision_id, $no_cache);
$request_info->{reservation}{$reservation_id}{imagerevision} = $imagerevision_info;
# Add the computer info to the hash
my $computer_id = $request_info->{reservation}{$reservation_id}{computerid};
my $computer_info = get_computer_info($computer_id, $no_cache);
$request_info->{reservation}{$reservation_id}{computer} = $computer_info;
# Populate natport table for reservation
# Make sure this wasn't called from populate_reservation_natport or else recursive loop will occur
if (defined $ENV{reservation_id} && $ENV{reservation_id} eq $reservation_id) {
my $caller_trace = get_caller_trace(5);
if ($caller_trace !~ /populate_reservation_natport/) {
if ($request_state_name =~ /(new|reserved|modified|test)/) {
if (!populate_reservation_natport($reservation_id)) {
notify($ERRORS{'CRITICAL'}, 0, "failed to populate natport table for reservation");
}
if (!update_reservation_natlog($reservation_id)) {
notify($ERRORS{'CRITICAL'}, 0, "failed to populate natlog table for reservation");
}
}
}
}
# Add the connect method info to the hash
my $connect_method_info = get_reservation_connect_method_info($reservation_id, 0);
$request_info->{reservation}{$reservation_id}{connect_methods} = $connect_method_info;
# Add the managementnode info to the hash
my $management_node_id = $request_info->{reservation}{$reservation_id}{managementnodeid};
my $management_node_info = get_management_node_info($management_node_id, $no_cache);
$request_info->{reservation}{$reservation_id}{managementnode} = $management_node_info;
# Retrieve the user info and add to the hash
my $user_id = $request_info->{userid};
my $user_info = get_user_info($user_id, 0, $no_cache);
$request_info->{user} = $user_info;
my $imagemeta_root_access = $request_info->{reservation}{$reservation_id}{image}{imagemeta}{rootaccess};
# If server request and logingroupid is set, add user group members to hash, set ROOTACCESS to 0
if (my $login_group_id = $request_info->{reservation}{$reservation_id}{serverrequest}{logingroupid}) {
my $login_group_member_info = get_user_group_member_info($login_group_id);
for my $login_user_id (keys %$login_group_member_info) {
$request_info->{reservation}{$reservation_id}{users}{$login_user_id} = get_user_info($login_user_id, 0, 1);
$request_info->{reservation}{$reservation_id}{users}{$login_user_id}{ROOTACCESS} = 0;
}
}
# If server request and admingroupid is set, add user group members to hash, set ROOTACCESS to the value configured in imagemeta
if (my $admin_group_id = $request_info->{reservation}{$reservation_id}{serverrequest}{admingroupid}) {
my $admin_group_member_info = get_user_group_member_info($admin_group_id);
for my $admin_user_id (keys %$admin_group_member_info, $user_id) {
$request_info->{reservation}{$reservation_id}{users}{$admin_user_id} = get_user_info($admin_user_id, 0, 1);
$request_info->{reservation}{$reservation_id}{users}{$admin_user_id}{ROOTACCESS} = $imagemeta_root_access;
}
}
# Add the request user to the hash, set ROOTACCESS to the value configured in imagemeta
$request_info->{reservation}{$reservation_id}{users}{$user_id} = $user_info;
$request_info->{reservation}{$reservation_id}{users}{$user_id}{ROOTACCESS} = $imagemeta_root_access;
# For imaging reservations if imagemeta.rootaccess = 0, make sure the reservation user has root access ONLY IF the user is the image owner
# The web frontend should not allow an image capture by anyone but the image owner if imagemeta.rootaccess = 0
my $image_owner_id = $image_info->{ownerid};
my $request_forimaging = $request_info->{forimaging};
if ($request_forimaging && !$imagemeta_root_access) {
my $image_name = $imagerevision_info->{imagename};
my $image_owner_login_name = $image_info->{unityid};
if ($user_id eq $image_owner_id) {
notify($ERRORS{'OK'}, 0, "user $user_id is the owner of image $image_name, overriding imagemeta.rootaccess: $imagemeta_root_access --> 1");
$request_info->{reservation}{$reservation_id}{users}{$user_id}{ROOTACCESS} = 1;
}
else {
notify($ERRORS{'CRITICAL'}, 0, "capture attempted for image with root access disabled by a user other than the image owner\n" .
"image: $image_name\n" .
"imagemeta.rootaccess: $imagemeta_root_access\n" .
"image owner ID: $image_owner_id\n" .
"request user ID: $user_id"
);
}
}
# If server request or duration is greater >= 24 hrs disable user checks
if ($request_info->{reservation}{$reservation_id}{serverrequest}{id}) {
notify($ERRORS{'DEBUG'}, 0, "server request - disabling user checks");
$request_info->{checkuser} = 0;
$request_info->{reservation}{$reservation_id}{serverrequest}{ALLOW_USERS} = $request_info->{user}{unityid};
}
elsif ($request_info->{DURATION} >= (60 * 60 * 24)) {
#notify($ERRORS{'DEBUG'}, 0, "request length > 24 hours, disabling user checks");
$request_info->{checkuser} = 0;
}
$request_info->{reservation}{$reservation_id}{READY} = '0';
}
# Set some default non-database values for the entire request
# All data ever added to the hash should be initialized here
$request_info->{PID} = $PID;
$request_info->{PPID} = getppid();
$request_info->{PRELOADONLY} = '0';
$request_info->{CHECKTIME} = '';
$request_info->{NOTICEINTERVAL} = '';
$request_info->{RESERVATIONCOUNT} = scalar keys %{$request_info->{reservation}};
$request_info->{UPDATED} = '0';
$request_info->{NOTICE_INTERVAL} = '';
if ($request_state_name eq 'image') {
$request_info->{IMAGE_CAPTURE_TYPE} = 'Capture';
}
elsif ($request_state_name eq 'checkpoint') {
$request_info->{IMAGE_CAPTURE_TYPE} = 'Checkpoint';
}
else {
$request_info->{IMAGE_CAPTURE_TYPE} = '';
}
#notify($ERRORS{'DEBUG'}, 0, "retrieved request info:\n" . format_data($request_info));
return $request_info;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_request_log_info
Parameters : $request_id, $no_cache (optional)
Returns : hash reference
Description : Retrieves data from the log and sublog tables for the request.
A hash is constructed. Example:
{
"computerid" => 3588,
"ending" => "none",
"finalend" => "0000-00-00 00:00:00",
"id" => 5354,
...
"sublog" => {
74 => {
"IPaddress" => undef,
"blockEnd" => undef,
"blockRequestid" => undef,
"blockStart" => undef,
"computerid" => 3588,
"hostcomputerid" => undef,
"id" => 74,
"imageid" => 3081,
"imagerevisionid" => 9147,
"logid" => 5354,
"managementnodeid" => 8,
"predictivemoduleid" => 8
},
75 => {
"IPaddress" => undef,
...
},
},
"userid" => 2870,
"wasavailable" => 0
}
=cut
sub get_request_log_info {
my ($request_id, $no_cache) = @_;
if (!defined($request_id)) {
notify($ERRORS{'WARNING'}, 0, "request ID argument was not specified");
return;
}
if (!$no_cache && defined($ENV{log_info}{$request_id})) {
return $ENV{log_info}{$request_id};
}
# Get a hash ref containing the database column names
my $database_table_columns = get_database_table_columns();
my %tables = (
'log' => 'log',
'sublog' => 'sublog',
);
# Construct the select statement
my $select_statement = "SELECT DISTINCT\n";
# Get the column names for each table and add them to the select statement
for my $table_alias (keys %tables) {
my $table_name = $tables{$table_alias};
my @columns = @{$database_table_columns->{$table_name}};
for my $column (@columns) {
$select_statement .= "$table_alias.$column AS '$table_alias-$column',\n";
}
}
# Remove the comma after the last column line
$select_statement =~ s/,$//;
# Complete the select statement
$select_statement .= <<EOF;
FROM
request,
log,
sublog
WHERE
request.id = $request_id
AND log.id = request.logid
AND sublog.logid = log.id
EOF
# Call the database select subroutine
my @rows = database_select($select_statement);
if (!@rows) {
notify($ERRORS{'WARNING'}, 0, "log info for request $request_id could not be retrieved from the database, select statement:\n$select_statement");
return;
}
# Build the hash
my $log_info;
for my $row (@rows) {
my $sublog_id = $row->{'sublog-id'};
if (!$sublog_id) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve log info for request $request_id, row does not contain a 'sublog-id' value:\n" . format_data($row));
return;
}
# Loop through all the columns returned
for my $key (keys %$row) {
my $value = $row->{$key};
# Split the table-column names
my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
if ($table eq 'log') {
$log_info->{$column} = $value;
}
elsif ($table eq 'sublog') {
$log_info->{$table}{$sublog_id}{$column} = $value;
}
else {
$log_info->{$table}{$column} = $value;
}
}
}
$ENV{log_info}{$request_id} = $log_info;
#notify($ERRORS{'DEBUG'}, 0, "retrieved log info for request $request_id:\n" . format_data($log_info));
return $log_info;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 set_managementnode_state
Parameters : management node info, state
Returns : 1 or 0
Description : sets a given management node to maintenance
=cut
sub set_managementnode_state {
my ($mninfo, $state) = @_;
if (!(defined($state))) {
notify($ERRORS{'WARNING'}, 0, "state was not specified");
return ();
}
if (!(defined($mninfo->{hostname}))) {
notify($ERRORS{'WARNING'}, 0, "management node hostname was not specified");
return ();
}
if (!(defined($mninfo->{id}))) {
notify($ERRORS{'WARNING'}, 0, "management node ID was not specified");
return ();
}
my $mn_ID = $mninfo->{id};
my $mn_hostname = $mninfo->{hostname};
# Construct the update statement
my $update_statement = "
UPDATE
managementnode,
state
SET
managementnode.stateid = state.id
WHERE
state.name = '$state' AND
managementnode.id = '$mn_ID'
";
# Call the database execute subroutine
if (database_execute($update_statement)) {
# Update successful, return timestamp
notify($ERRORS{'OK'}, 0, "Successfully updated management node $mn_hostname state to $state");
return 1;
}
else {
notify($ERRORS{'CRITICAL'}, 0, "unable to update database, management node $mn_hostname state to $state");
return 0;
}
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_management_node_requests
Parameters : $management_node_id
Returns : hash
Description : gets request information for a particular management node
=cut
sub get_management_node_requests {
my ($management_node_id) = @_;
if (!(defined($management_node_id))) {
notify($ERRORS{'WARNING'}, 0, "management node ID was not specified");
return ();
}
my $select_statement = "
SELECT DISTINCT
request.id AS request_id,
request.stateid AS request_stateid,
request.laststateid AS request_laststateid,
request.logid AS request_logid,
request.start AS request_start,
request.end AS request_end,
request.daterequested AS request_daterequested,
request.datemodified AS request_datemodified,
request.preload AS request_preload,
requeststate.name AS requeststate_name,
requestlaststate.name AS requestlaststate_name,
reservation.id AS reservation_id,
reservation.requestid AS reservation_requestid,
reservation.managementnodeid AS reservation_managementnodeid,
reservation.lastcheck AS reservation_lastcheck,
serverrequest.id AS serverrequest_serverrequestid,
MAX(changelog.timestamp) AS changelog_timestamp
FROM
request
LEFT JOIN (serverrequest) ON (serverrequest.requestid = request.id)
LEFT JOIN (log, changelog) ON (request.logid = log.id AND changelog.logid = log.id AND changelog.remoteIP IS NOT NULL),
reservation,
state requeststate,
state requestlaststate
WHERE
reservation.managementnodeid = $management_node_id
AND reservation.requestid = request.id
AND requeststate.id = request.stateid
AND requestlaststate.id = request.laststateid
GROUP BY
reservation.id
";
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @selected_rows = database_select($select_statement);
# Build the hash
my $requests = {};
for (@selected_rows) {
my %reservation_row = %{$_};
# Grab the request and reservation IDs to make the code a little cleaner
my $request_id = $reservation_row{request_id};
my $reservation_id = $reservation_row{reservation_id};
# Loop through all the columns returned for the reservation
foreach my $key (keys %reservation_row) {
my $value = $reservation_row{$key};
# Create another variable by stripping off the column_ part of each key
# This variable stores the original (correct) column name
(my $original_key = $key) =~ s/^.+_//;
if ($key =~ /^request_/) {
# Set the top-level key if not already set
$requests->{$request_id}{$original_key} = $value if (!$requests->{$request_id}{$original_key});
}
elsif ($key =~ /requeststate_/) {
$requests->{$request_id}{state}{$original_key} = $value if (!$requests->{$request_id}{state}{$original_key});
}
elsif ($key =~ /requestlaststate_/) {
$requests->{$request_id}{laststate}{$original_key} = $value if (!$requests->{$request_id}{laststate}{$original_key});
}
elsif ($key =~ /reservation_/) {
$requests->{$request_id}{reservation}{$reservation_id}{$original_key} = $value;
}
elsif ($key =~ /serverrequest_/) {
$requests->{$request_id}{reservation}{$reservation_id}{$original_key} = $value;
}
elsif ($key =~ /changelog_/) {
$requests->{$request_id}{log}{changelog}{$original_key} = $value;
}
else {
notify($ERRORS{'WARNING'}, 0, "unknown key found in SQL data: $key");
}
} # Close foreach key in reservation row
} # Close loop through selected rows
#notify($ERRORS{'DEBUG'}, 0, "retrieved management node requests:\n" . format_data($requests));
return $requests;
} ## end sub get_management_node_requests
#//////////////////////////////////////////////////////////////////////////////
=head2 get_image_info
Parameters : $image_identifier, $no_cache (optional), $ignore_error (optional)
Returns : hash reference
Description : Retrieves info for the image specified by the argument. The
argument can either be the image ID or image name.
=cut
sub get_image_info {
my ($image_identifier, $no_cache, $ignore_error) = @_;
if (!defined($image_identifier)) {
notify($ERRORS{'WARNING'}, 0, "image identifier argument was not specified");
return;
}
# Check if cached image info exists
if (!$no_cache && defined($ENV{image_info}{$image_identifier})) {
# Check the time the info was last retrieved
my $data_age_seconds = (time - $ENV{image_info}{$image_identifier}{RETRIEVAL_TIME});
if ($data_age_seconds < 600) {
return $ENV{image_info}{$image_identifier};
}
else {
notify($ERRORS{'DEBUG'}, 0, "retrieving current image info for '$image_identifier' from database, cached data is stale: $data_age_seconds seconds old");
}
}
# Get a hash ref containing the database column names
my $database_table_columns = get_database_table_columns();
my @tables = (
'image',
'platform',
'OS',
'OStype',
'imagetype',
'module',
);
# Construct the select statement
my $select_statement = "SELECT DISTINCT\n";
# Get the column names for each table and add them to the select statement
for my $table (@tables) {
my @columns = @{$database_table_columns->{$table}};
for my $column (@columns) {
$select_statement .= "$table.$column AS '$table-$column',\n";
}
}
# Remove the comma after the last column line
$select_statement =~ s/,$//;
# Complete the select statement
$select_statement .= <<EOF;
FROM
image,
platform,
OS,
OStype,
imagetype,
module
WHERE
platform.id = image.platformid
AND OS.id = image.OSid
AND OS.type = OStype.name
AND image.imagetypeid = imagetype.id
AND module.id = OS.moduleid
AND
EOF
if ($image_identifier =~ /^\d+$/) {
$select_statement .= "image.id = $image_identifier";
}
else {
$image_identifier =~ s/(-v)\d+$/$1/g;
$select_statement .= "image.name LIKE '$image_identifier\%'";
}
# Call the database select subroutine
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (scalar @selected_rows == 0) {
if ($ignore_error) {
notify($ERRORS{'DEBUG'}, 0, "image does NOT exist in the database: $image_identifier");
}
else {
notify($ERRORS{'WARNING'}, 0, "zero rows were returned from database select statement:\n$select_statement");
}
return;
}
elsif (scalar @selected_rows > 1) {
notify($ERRORS{'WARNING'}, 0, scalar @selected_rows . " rows were returned from database select statement:\n$select_statement");
return;
}
# Get the single row returned from the select statement
my $row = $selected_rows[0];
# Construct a hash with all of the image info
my $image_info;
# Loop through all the columns returned
for my $key (keys %$row) {
my $value = $row->{$key};
# Split the table-column names
my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
# Add the values for the primary table to the hash
# Add values for other tables under separate keys
if ($table eq $tables[0]) {
$image_info->{$column} = $value;
}
elsif ($table =~ /^(module|OStype)$/) {
$image_info->{OS}{$table}{$column} = $value;
}
else {
$image_info->{$table}{$column} = $value;
}
}
# Retrieve the imagemeta info and add it to the hash
my $imagemeta_id = $image_info->{imagemetaid};
my $imagemeta_info = get_imagemeta_info($imagemeta_id);
$image_info->{imagemeta} = $imagemeta_info;
my $image_owner_id = $image_info->{ownerid};
my $image_owner_user_info = get_user_info($image_owner_id);
$image_info->{owner} = $image_owner_user_info;
# TODO: Get rid of the IDENTITY key, it shouldn't be used anymore
my $management_node_info = get_management_node_info();
if ($management_node_info) {
$image_info->{IDENTITY} = $management_node_info->{keys};
}
# Retrieve AD info if configured for the image
my $image_id = $image_info->{id};
my $domain_info = get_image_active_directory_domain_info($image_id, $no_cache);
$image_info->{imagedomain} = $domain_info;
#notify($ERRORS{'DEBUG'}, 0, "retrieved info for image '$image_identifier':\n" . format_data($image_info));
$ENV{image_info}{$image_identifier} = $image_info;
$ENV{image_info}{$image_identifier}{RETRIEVAL_TIME} = time;
return $ENV{image_info}{$image_identifier};
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_imagerevision_info
Parameters : $imagerevision_identifier, $no_cache (optional)
Returns : Hash reference
Description : collects data from database on supplied $imagerevision_id
=cut
sub get_imagerevision_info {
my ($imagerevision_identifier, $no_cache) = @_;
if (!defined($imagerevision_identifier)) {
notify($ERRORS{'WARNING'}, 0, "imagerevision identifier argument was not specified");
return;
}
# Check if cached imagerevision info exists
if (!$no_cache && defined($ENV{imagerevision_info}{$imagerevision_identifier})) {
# Check the time the info was last retrieved
my $data_age_seconds = (time - $ENV{imagerevision_info}{$imagerevision_identifier}{RETRIEVAL_TIME});
if ($data_age_seconds < 600) {
return $ENV{imagerevision_info}{$imagerevision_identifier};
}
else {
notify($ERRORS{'DEBUG'}, 0, "retrieving current imagerevision info for '$imagerevision_identifier' from database, cached data is stale: $data_age_seconds seconds old");
}
}
my $select_statement = <<EOF;
SELECT
imagerevision.*
FROM
imagerevision
WHERE
EOF
# Check input value - complete select_statement
if ($imagerevision_identifier =~ /^\d/) {
$select_statement .= "imagerevision.id = '$imagerevision_identifier'";
}
else {
$select_statement .= "imagerevision.imagename = \'$imagerevision_identifier\'";
}
# Call the database select subroutine
# This will return an array of one or more rows based on the select statement
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (!@selected_rows) {
notify($ERRORS{'WARNING'}, 0, "imagerevision '$imagerevision_identifier' was not found in the database, 0 rows were returned from database select statement:\n$select_statement");
return;
}
elsif (scalar @selected_rows > 1) {
notify($ERRORS{'WARNING'}, 0, scalar @selected_rows . " rows were returned from database select statement:\n$select_statement");
return;
}
my $imagerevision_info = $selected_rows[0];
# Retrieve the image info
my $imagerevision_image_id = $imagerevision_info->{imageid};
my $imagerevision_image_info = get_image_info($imagerevision_image_id);
if (!$imagerevision_image_info) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve imagerevision info, image info could not be retrieved for image ID: $imagerevision_image_id");
return;
}
$imagerevision_info->{image} = $imagerevision_image_info;
# Retrieve the imagerevision user info
$imagerevision_info->{user} = get_user_info($imagerevision_info->{userid});
# Add the info to %ENV so it doesn't need to be retrieved from the database again
$ENV{imagerevision_info}{$imagerevision_identifier} = $imagerevision_info;
$ENV{imagerevision_info}{$imagerevision_identifier}{RETRIEVAL_TIME} = time;
#notify($ERRORS{'DEBUG'}, 0, "retrieved info from database for imagerevision '$imagerevision_identifier':\n" . format_data($ENV{imagerevision_info}{$imagerevision_identifier}));
return $ENV{imagerevision_info}{$imagerevision_identifier};
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_production_imagerevision_info
Parameters : $image_id, $no_cache (optional)
Returns : Hash containing imagerevision data for the production revision of an image
Description :
=cut
sub get_production_imagerevision_info {
my ($image_identifier, $no_cache) = @_;
# Check the passed parameter
if (!defined($image_identifier)) {
notify($ERRORS{'WARNING'}, 0, "imagerevision identifier argument was not specified");
return;
}
return $ENV{production_imagerevision_info}{$image_identifier} if (!$no_cache && $ENV{production_imagerevision_info}{$image_identifier});
my $select_statement = <<EOF;
SELECT
id
FROM
imagerevision
WHERE
imagerevision.production = '1'
AND
EOF
# Check input value - complete select_statement
if ($image_identifier =~ /^\d/) {
$select_statement .= "imagerevision.imageid = '$image_identifier'";
}
else {
# Assume $image_identifier is the image name, strip off '-v*' from the end
# Otherwise query may fail if production version is not the exact revision passed as the argument
$image_identifier =~ s/-v\d+$/-v%/;
$select_statement .= "imagerevision.imagename LIKE \'$image_identifier\'";
}
# Call the database select subroutine
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (!@selected_rows) {
notify($ERRORS{'WARNING'}, 0, "production imagerevision for image '$image_identifier' was not found in the database, 0 rows were returned, select statement:\n$select_statement");
return;
}
elsif (scalar @selected_rows > 1) {
notify($ERRORS{'WARNING'}, 0, "" . scalar @selected_rows . " rows were returned from database select statement:\n$select_statement");
return;
}
my $imagerevision_id = $selected_rows[0]{id};
my $imagerevision_info = get_imagerevision_info($imagerevision_id);
my $image_name = $imagerevision_info->{imagename};
$ENV{production_imagerevision_info}{$image_identifier} = $imagerevision_info;
notify($ERRORS{'DEBUG'}, 0, "retrieved info from database for production revision for image identifier '$image_identifier', production image: '$image_name'");
return $ENV{production_imagerevision_info}{$image_identifier};
} ## end sub get_production_imagerevision_info
#//////////////////////////////////////////////////////////////////////////////
=head2 set_production_imagerevision
Parameters : $imagerevision_id
Returns : boolean
Description : Sets the production flag to 1 for the image revision specified by
the argument. Sets production to 0 for all other revisions of the
image. Sets image.name to imagerevision.imagename of the
production revision. Sets the image.test flag to 0.
=cut
sub set_production_imagerevision {
my ($imagerevision_id) = @_;
if (!defined($imagerevision_id)) {
notify($ERRORS{'WARNING'}, 0, "imagerevision ID argument was not supplied");
return;
}
# Delete cached data
delete $ENV{production_imagerevision_info};
my $sql_statement = <<EOF;
UPDATE
image,
imagerevision imagerevision_production,
imagerevision imagerevision_others
SET
image.name = imagerevision_production.imagename,
image.test = 0,
image.lastupdate = NOW(),
imagerevision_production.production = 1,
imagerevision_others.production = 0
WHERE
imagerevision_production.id = $imagerevision_id
AND imagerevision_production.imageid = image.id
AND imagerevision_others.imageid = imagerevision_production.imageid
AND imagerevision_others.id != imagerevision_production.id
EOF
# Call the database execute subroutine
if (!database_execute($sql_statement)) {
notify($ERRORS{'WARNING'}, 0, "failed to set imagerevision $imagerevision_id to production");
return 0;
}
notify($ERRORS{'OK'}, 0, "imagerevision $imagerevision_id set to production");
# create reload reservations for any available computers loaded with different
# revisions of the image
$sql_statement = "SELECT imageid FROM imagerevision WHERE id = $imagerevision_id";
my @rows = database_select($sql_statement);
my $row = $rows[0];
my $image_id = $row->{imageid};
if (!$image_id) {
notify($ERRORS{'DEBUG'}, 0, "failed to get image_id for $imagerevision_id");
return 1;
}
$sql_statement = <<EOF;
SELECT
id
FROM
computer
WHERE
currentimageid = $image_id
AND imagerevisionid != $imagerevision_id
AND deleted = 0
AND stateid = (SELECT id FROM state WHERE name = 'available')
AND RAM >= (SELECT minram FROM image WHERE id = $image_id)
AND procnumber >= (SELECT minprocnumber FROM image WHERE id = $image_id)
AND network >= (SELECT minnetwork FROM image WHERE id = $image_id)
AND id NOT IN
(
SELECT reservation.computerid
FROM reservation
JOIN request ON (reservation.requestid = request.id)
WHERE
request.start > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
AND request.start < DATE_ADD(NOW(), INTERVAL 30 MINUTE)
AND request.stateid NOT IN (SELECT id FROM state WHERE name IN ('deleted', 'complete', 'timeout'))
)
EOF
my @selected_rows = database_select($sql_statement);
if (scalar @selected_rows == 0) {
notify($ERRORS{'DEBUG'}, 0, "no computers found loaded with a different revision of $image_id than $imagerevision_id");
return 1;
}
my $sql_statement_template = <<EOF;
SELECT
mn.id
FROM computer c
JOIN resource rc ON (rc.resourcetypeid = 12 AND rc.subid = c.id)
JOIN resourcegroupmembers rgm ON (rgm.resourceid = rc.id)
JOIN resourcemap rm ON (rgm.resourcegroupid = rm.resourcegroupid2 AND rm.resourcetypeid2 = 12 AND rm.resourcetypeid1 = 16)
JOIN resourcegroupmembers rgmmn ON (rgmmn.resourcegroupid = rm.resourcegroupid1)
JOIN resource rmn ON (rgmmn.resourceid = rmn.id)
JOIN managementnode mn ON (rmn.subid = mn.id AND rmn.resourcetypeid = 16 AND mn.stateid != 1)
WHERE c.id = %d
LIMIT 1
EOF
my ($comp_id, $mn_id, @mn_selected_rows);
for my $selected_row (@selected_rows) {
$comp_id = $selected_row->{id};
$sql_statement = sprintf($sql_statement_template, $comp_id);
@mn_selected_rows = database_select($sql_statement);
next if (scalar @mn_selected_rows == 0);
$mn_id = $mn_selected_rows[0]{id};
notify($ERRORS{'DEBUG'}, 0, "creating reload reservation: compid: $comp_id, imageid: $image_id, revision: $imagerevision_id, mnid: $mn_id");
insert_request($mn_id, 'reload', 'reload', 'vclreload', $comp_id, $image_id, $imagerevision_id, 0, 15);
}
return 1;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_imagemeta_info
Parameters : $imagemeta_id, $no_cache (optional)
Returns : Hash reference
Description :
=cut
sub get_imagemeta_info {
my ($imagemeta_id, $no_cache) = @_;
my $default_imagemeta_info = get_default_imagemeta_info();
# Return defaults if nothing was passed as the imagemeta id
if (!$imagemeta_id) {
return $default_imagemeta_info;
}
if (!$no_cache && $ENV{imagemeta_info}{$imagemeta_id}) {
return $ENV{imagemeta_info}{$imagemeta_id};
}
# If imagemetaid isnt' NULL, perform another query to get the meta info
my $select_statement = <<EOF;
SELECT
imagemeta.*
FROM
imagemeta
WHERE
imagemeta.id = '$imagemeta_id'
EOF
# Call the database select subroutine
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (!@selected_rows || scalar @selected_rows > 1) {
$ENV{imagemeta_info}{$imagemeta_id} = $default_imagemeta_info;
notify($ERRORS{'WARNING'}, 0, "failed to retrieve imagemeta ID=$imagemeta_id, returning default imagemeta values");
return $ENV{imagemeta_info}{$imagemeta_id};
}
# Get the single row returned from the select statement
my $imagemeta_info = $selected_rows[0];
for my $column (keys %$imagemeta_info) {
if (!defined($imagemeta_info->{$column})) {
$imagemeta_info->{$column} = $default_imagemeta_info->{$column};
}
}
notify($ERRORS{'DEBUG'}, 0, "retrieved imagemeta info:\n" . format_data($imagemeta_info));
$ENV{imagemeta_info}{$imagemeta_id} = $imagemeta_info;
return $ENV{imagemeta_info}{$imagemeta_id};
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_default_imagemeta_info
Parameters :
Returns : Hash reference
Description :
=cut
sub get_default_imagemeta_info {
if ($ENV{imagemeta_info}{default}) {
# Create a copy to ensure that the correct default data is returned
# Other processes may use the same cached copy
# If the same reference is returned for multiple processes, one process may alter the data
my %default_imagemeta_info = %{$ENV{imagemeta_info}{default}};
return \%default_imagemeta_info;
}
# Call the database select subroutine to retrieve the imagemeta table structure
my $describe_imagemeta_statement = "DESCRIBE imagemeta";
my @describe_imagemeta_rows = database_select($describe_imagemeta_statement);
if (!@describe_imagemeta_rows) {
notify($ERRORS{'WARNING'}, 0, "failed to retrieve imagemeta table structure, SQL statement:\n$describe_imagemeta_statement");
return;
}
my $default_imagemeta_info;
for my $describe_imagemeta_row (@describe_imagemeta_rows) {
my $field = $describe_imagemeta_row->{Field};
my $default_value = $describe_imagemeta_row->{Default};
if (defined($default_value)) {
$default_imagemeta_info->{$field} = $default_value;
}
else {
$default_imagemeta_info->{$field} = '';
}
}
$ENV{imagemeta_info}{default} = $default_imagemeta_info;
my %default_imagemeta_info_copy = %{$ENV{imagemeta_info}{default}};
#notify($ERRORS{'DEBUG'}, 0, "retrieved default imagemeta info:\n" . format_data(\%default_imagemeta_info_copy));
return \%default_imagemeta_info_copy;
}
#//////////////////////////////////////////////////////////////////////////////
=head2 get_vmhost_info
Parameters : $vmhost_identifier, $no_cache (optional)
Returns : hash reference
Description : Retrieves info from the database for the vmhost, vmprofile, and
repository and datastore imagetypes. The $vmhost_identifier
argument may be used to match vmhost.id, vmprofile.profilename,
or computer.hostname.
=cut
sub get_vmhost_info {
my ($vmhost_identifier, $no_cache) = @_;
# Check the passed parameter
if (!defined($vmhost_identifier)) {
notify($ERRORS{'WARNING'}, 0, "VM host identifier argument was not specified");
return;
}
return $ENV{vmhost_info}{$vmhost_identifier} if (!$no_cache && $ENV{vmhost_info}{$vmhost_identifier});
my $management_node_id = get_management_node_id();
# Get a hash ref containing the database column names
my $database_table_columns = get_database_table_columns();
my %tables = (
'vmhost' => 'vmhost',
'vmprofile' => 'vmprofile',
'repositoryimagetype' => 'imagetype',
'datastoreimagetype' => 'imagetype',
'cryptsecret' => 'cryptsecret',
);
# Construct the select statement
my $select_statement = "SELECT\n";
# Get the column names for each table and add them to the select statement
for my $table_alias (keys %tables) {
my $table_name = $tables{$table_alias};
my @columns = @{$database_table_columns->{$table_name}};
for my $column (@columns) {
$select_statement .= "$table_alias.$column AS '$table_alias-$column',\n";
}
}
# Remove the comma after the last column line
$select_statement =~ s/,$//;
# Complete the select statement
$select_statement .= <<EOF;
FROM
vmhost,
vmprofile
LEFT JOIN (cryptsecret, cryptkey) ON (
vmprofile.secretid = cryptsecret.secretid AND
cryptsecret.cryptkeyid = cryptkey.id AND
cryptkey.hosttype = 'managementnode' AND
cryptkey.hostid = $management_node_id
),
imagetype repositoryimagetype,
imagetype datastoreimagetype,
computer
WHERE
vmprofile.id = vmhost.vmprofileid
AND vmprofile.repositoryimagetypeid = repositoryimagetype.id
AND vmprofile.datastoreimagetypeid = datastoreimagetype.id
AND vmhost.computerid = computer.id
AND
EOF
if ($vmhost_identifier =~ /^\d+$/) {
$select_statement .= "vmhost.id = '$vmhost_identifier'\n";
}
else {
$select_statement .= "(\n";
$select_statement .= " computer.hostname REGEXP '$vmhost_identifier(\\\\.|\$)'\n";
$select_statement .= " OR vmprofile.profilename = '$vmhost_identifier'\n";
$select_statement .= ")";
}
# Call the database select subroutine
my @selected_rows = database_select($select_statement);
# Check to make sure 1 row was returned
if (scalar @selected_rows == 0) {
notify($ERRORS{'WARNING'}, 0, "zero rows were returned from database select statement:\n$select_statement");
return;
}
my $row;
if (scalar @selected_rows > 1) {
my $vmhost_string;
for my $selected_row (@selected_rows) {
# Check if the vmprofile.profilename exactly matches the VM host identifier argument
if ($selected_row->{'vmprofile-profilename'} eq $vmhost_identifier) {
$row = $selected_row;
last;
}
$vmhost_string .= "VM host ID: " . $selected_row->{'vmhost-id'};
$vmhost_string .= ", computer ID: " . $selected_row->{'vmhost-computerid'};
$vmhost_string .= ", VM profile ID: " . $selected_row->{'vmprofile-id'};
$vmhost_string .= ", VM profile name: " . $selected_row->{'vmprofile-profilename'};
$vmhost_string .= "\n";
}
if (!$row) {
notify($ERRORS{'WARNING'}, 0, "unable to determine VM host from ambiguous argument: $vmhost_identifier, " . scalar @selected_rows . " rows were returned from database select statement:\n$select_statement\nrows:\n$vmhost_string");
return;
}
}
else {
# Get the single row returned from the select statement
$row = $selected_rows[0];
}
# Construct a hash with all of the vmhost info
my $vmhost_info;
# Loop through all the columns returned
for my $key (keys %$row) {
my $value = $row->{$key};
# Split the table-column names
my ($table, $column) = $key =~ /^([^-]+)-(.+)/;
# Add the values for the vmhost table to the hash
# Add values for other tables under separate keys
if ($table eq 'vmhost') {
$vmhost_info->{$column} = $value;
}
elsif ($table eq 'cryptsecret') {
$vmhost_info->{vmprofile}{$table}{$column} = $value;
}
else {
$vmhost_info->{$table}{$column} = $value;
}
}
# Get the vmhost computer info and add it to the hash
my $computer_id = $vmhost_info->{computerid};
my $computer_info = get_computer_info($computer_id, $no_cache);
if ($computer_info) {
$vmhost_info->{computer} = $computer_info;
}
else {
notify($ERRORS{'WARNING'}, 0, "unable to retrieve vmhost computer info, computer ID: $computer_id");
}
# Get the vmprofile image info and add it to the hash
my $vmprofile_image_identifier = $vmhost_info->{vmprofile}{imageid};
$vmprofile_image_identifier = 'noimage' if !$vmprofile_image_identifier;
my $vmprofile_image_info = get_image_info($vmprofile_image_identifier);
if ($vmprofile_image_info) {
$vmhost_info->{vmprofile}{image} = $vmprofile_image_info;
}
else {
notify($ERRORS{'WARNING'}, 0, "unable to retrieve vmprofile image info, image identifier: $vmprofile_image_identifier");
}
$vmhost_info->{vmprofile}{username} = '' if !$vmhost_info->{vmprofile}{username};
$vmhost_info->{vmprofile}{password} = '' if !$vmhost_info->{vmprofile}{password};
# Decrypt the vmhost password
if ($vmhost_info->{vmprofile}{rsakey} && -f $vmhost_info->{vmprofile}{rsakey} && $vmhost_info->{vmprofile}{encryptedpasswd}) {
# Read the private keyfile into a string
local $/ = undef;
open FH, $vmhost_info->{vmprofile}{rsakey} or
notify($ERRORS{'WARNING'}, 0, "Could not read private keyfile (" . $vmhost_info->{vmprofile}{rsakey} . "): $!");
my $key = <FH>;
close FH;
if ($key) {
my $encrypted = $vmhost_info->{vmprofile}{encryptedpasswd};
my $rsa = Crypt::OpenSSL::RSA->new_private_key($key);
# Croak on an invalid key
$rsa->check_key;
# Use the same padding algorithm as the PHP code
$rsa->use_pkcs1_oaep_padding;
# Convert password from hex to binary, decrypt
# and store in the vmprofile.password field
$vmhost_info->{vmprofile}{password} = $rsa->decrypt(pack("H*", $encrypted));
notify($ERRORS{'DEBUG'}, 0, "decrypted vmprofile password with key: " . $vmhost_info->{vmprofile}{rsakey});
}
else {
notify($ERRORS{'WARNING'}, 0, "unable to decrypt vmprofile password");
}
}
# Clean up the extraneous data
delete $vmhost_info->{vmprofile}{rsakey};
delete $vmhost_info->{vmprofile}{encryptedpassword};
$vmhost_info->{vmprofile}{vmpath} = $vmhost_info->{vmprofile}{datastorepath} if !$vmhost_info->{vmprofile}{vmpath};
$vmhost_info->{vmprofile}{virtualdiskpath} = $vmhost_info->{vmprofile}{vmpath} if !$vmhost_info->{vmprofile}{virtualdiskpath};
my $vmhost_id = $vmhost_info->{id};
notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_identifier info, VM host ID: $vmhost_id, computer: $vmhost_info->{computer}{hostname}, computer ID: $vmhost_info->{computer}{id}");
$ENV{vmhost_info}{$vmhost_identifier} = $vmhost_info;
if ($vmhost_identifier ne $vmhost_id) {
$ENV{vmhost_info}{$vmhost_id} = $vmhost_info;
}
return $ENV{vmhost_info}{$vmhost_identifier};
}
#//////////////////////////////////////////////////////////////////////////////
=head2 run_ssh_command
Parameters : $node, $identity_path, $command, $user, $port, $output_level, $timeout_seconds
-or-
Hash reference with the following keys:
node - node name (required)
command - command to be executed remotely (required)
identity_paths - string containing paths to identity key files separated by commas (optional)
user - user to run remote command as (optional, default is 'root')
port - SSH port number (optional, default is 22)
output_level - allows the amount of output to be controlled: 0, 1, or 2 (optional)
max_attempts - maximum number of SSH attempts to make
timeout_seconds - maximum number seconds SSH process can run before being terminated
Returns : If successful: array:
$array[0] = the exit status of the command
$array[1] = reference to array containing lines of output
If failed: false
Description : Runs an SSH command on the specified node.
=cut
sub run_ssh_command {
my ($node, $identity_paths, $command, $user, $port, $output_level, $timeout_seconds) = @_;
my $max_attempts = 3;
if (ref($_[0]) eq 'HASH') {
my $arguments = shift;
$node = $arguments->{node};
$command = $arguments->{command};
$identity_paths = $arguments->{identity_paths} || '';
$user = $arguments->{user} || 'root';
$port = $arguments->{port} || '22';
$output_level = $arguments->{output_level};
$max_attempts = $arguments->{max_attempts} || 3;
$timeout_seconds = $arguments->{timeout_seconds};
}
# Determine the output level if it was specified
# Set $output_level to 0, 1, or 2
if (!defined($output_level)) {
$output_level = 2;
}
elsif ($output_level =~ /0|none/i) {
$output_level = 0;
}
elsif ($output_level =~ /1|min/i) {
$output_level = 1;
}
else {
$output_level = 2;
}
# Check the arguments
if (!defined($node) || !$node) {
notify($ERRORS{'WARNING'}, 0, "computer node was not specified");
return 0;
}
if (!defined($command) || !$command) {
notify($ERRORS{'WARNING'}, 0, "command was not specified");
return 0;
}
# Set default values if not passed as an argument
$user = "root" if (!$user);
$port = 22 if (!$port);
$timeout_seconds = 0 if (!$timeout_seconds);
if (!defined $identity_paths || length($identity_paths) == 0) {
my $management_node_info = get_management_node_info();
$identity_paths = $management_node_info->{keys}
}
# TESTING: use the new subroutine if $ENV{execute_new} is set and the command isn't one that's known to fail with the new subroutine
my $calling_subroutine = get_calling_subroutine();
if ($calling_subroutine && $calling_subroutine !~ /execute/) {
if ($ENV{execute_new} && $command !~ /(vmkfstools|qemu-img|Convert-VHD|scp|reboot|shutdown)/) {
return VCL::Module::OS::execute_new($node, $command, $output_level, $timeout_seconds, $max_attempts, $port, $user, '', $identity_paths);
}
}
# Locate the path to the ssh binary
my $ssh_path;
if (-f '/usr/bin/ssh') {
$ssh_path = '/usr/bin/ssh';
}
elsif (-f 'C:/cygwin/bin/ssh.exe') {
$ssh_path = 'C:/cygwin/bin/ssh.exe';
}
elsif (-f 'D:/cygwin/bin/ssh.exe') {
$ssh_path = 'D:/cygwin/bin/ssh.exe';
}
elsif (-f 'C:/cygwin64/bin/ssh.exe') {
$ssh_path = 'C:/cygwin64/bin/ssh.exe';
}
else {
notify($ERRORS{'WARNING'}, 0, "unable to locate the SSH executable in the usual places");
return 0;
}
# Format the identity path string
if (defined $identity_paths && length($identity_paths) > 0) {
# Add -i to beginning of string
$identity_paths = "-i $identity_paths";
# Split string on commas, add -i to each value after a comma
$identity_paths =~ s/\s*,\s*/ -i /g;
# Add a space to the end of the string
$identity_paths .= ' ';
}
else {
$identity_paths = '';
}
#notify($ERRORS{'DEBUG'}, 0, "ssh path: $ssh_path");
#notify($ERRORS{'DEBUG'}, 0, "node: $node, identity file paths: $identity_paths, user: $user, port: $port");
#notify($ERRORS{'DEBUG'}, 0, "command: $command");
#if ($command =~ /['\\]/) {
# my @octals = map { "0" . sprintf("%o", $_) } unpack("C*", $command);
# my $octal_string = '\\' . join("\\", @octals);
# $command = "echo -e \"$octal_string\" | \$SHELL";
# notify($ERRORS{'DEBUG'}, 0, "octal command:\n$command");
#}
# Determine which string to use as the connection target
my $remote_connection_target = determine_remote_connection_target($node);
my $node_string = $remote_connection_target;
$node_string .= " ($node)" if ($node ne $remote_connection_target);
# Command argument is enclosed in single quotes in ssh command
# Single quotes contained within the command argument won't work without splitting it up
# Argument:
# echo 'foo bar' > file
# Enclosed in single quotes
# 'echo 'foo bar' > file' <-- won't work
# For each single quote in the argument, split the command by adding single quotes before and after
# Enclose the original single quote in double quotes
# 'echo '"'"'foo bar'"'"' > file'
if ($command =~ /'/) {
my $original_command = $command;
$command =~ s/'/'"'"'/g;
notify($ERRORS{'DEBUG'}, 0, "command argument contains single quotes, enclosed all single quotes in double quotes:\n" .
"original command: '$original_command'\n" .
"modified command: '$command'"
);
}
# Assemble the SSH command
# -i <identity_file>, Selects the file from which the identity (private key) for RSA authentication is read.
# -l <login_name>, Specifies the user to log in as on the remote machine.
# -p <port>, Port to connect to on the remote host.
# -x, Disables X11 forwarding.
# Dont use: -q, Quiet mode. Causes all warning and diagnostic messages to be suppressed.
my $ssh_command = "$ssh_path $identity_paths ";
$ssh_command .= "-o StrictHostKeyChecking=no ";
$ssh_command .= "-o UserKnownHostsFile=/dev/null ";
$ssh_command .= "-o ConnectionAttempts=1 ";
$ssh_command .= "-o ConnectTimeout=30 ";
$ssh_command .= "-o BatchMode=no ";
$ssh_command .= "-o PasswordAuthentication=no ";
$ssh_command .= "-l $user ";
$ssh_command .= "-p $port ";
$ssh_command .= "-x ";
$ssh_command .= "$remote_connection_target '$command' 2>&1";
# Truncate command shown in vcld.log if it is very long
my $max_command_output_length = 3000;
my $beginning_characters_shown = 500;
my $ending_characters_shown = 200;
my $command_length = length($command);
my $command_summary;
my $command_chars_suppressed = ($command_length - $beginning_characters_shown - $ending_characters_shown - $max_command_output_length);
if ($command_chars_suppressed > 0) {
$command_summary = substr($command, 0, $beginning_characters_shown) . "<$command_chars_suppressed characters omitted>" . substr($command, -$ending_characters_shown);
}
else {
$command_summary = $command;
}
my $ssh_command_length = length($ssh_command);
my $ssh_command_summary;
my $ssh_command_chars_suppressed = ($ssh_command_length - $beginning_characters_shown - $ending_characters_shown - $max_command_output_length);
if ($ssh_command_chars_suppressed > 0) {
$ssh_command_summary = substr($ssh_command, 0, $beginning_characters_shown) . "<$ssh_command_chars_suppressed characters omitted>" . substr($ssh_command, -$ending_characters_shown);
}
else {
$ssh_command_summary = $ssh_command;
}
# Execute the command
my $ssh_output = '';
my $ssh_output_formatted = '';
my $attempts = 0;
my $exit_status = 255;
my $banner_exchange_error_count = 0;
my $banner_exchange_error_limit = 3;
# Make multiple attempts if failure occurs
while ($attempts < $max_attempts) {
$attempts++;
# Delay performing next attempt if this isn't the first attempt
if ($attempts > 1) {
my $delay_seconds = (2 * ($attempts - 1));
notify($ERRORS{'DEBUG'}, 0, "sleeping for $delay_seconds seconds before making next SSH attempt") if $output_level;
sleep $delay_seconds;
}
## Add -v (verbose) argument to command if this is the 2nd attempt
#$ssh_command =~ s/$ssh_path/$ssh_path -v/ if $attempts == 2;