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