diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 8a8d50f1..aa36b056 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1175,6 +1175,7 @@ sub _set_paths ifdown => "/sbin/ifdown", ifup => "/sbin/ifup", ip => "/usr/sbin/ip", + ipmitool => "/usr/bin/ipmitool", 'iptables-save' => "/usr/sbin/iptables-save", journalctl => "/usr/bin/journalctl", logger => "/usr/bin/logger", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index ef30901b..2f626d00 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -1862,7 +1862,7 @@ It also sets the variables; hosts::host_uuid::::host_name = ; hosts::host_uuid::::host_type = hosts::host_uuid::::host_key = - hosts::host_uuid::::host_ipmi = + hosts::host_uuid::::host_ipmi = hosts::host_uuid::::anvil_name = hosts::host_uuid::::anvil_uuid = @@ -1925,7 +1925,7 @@ FROM host_name => $host_name, host_type => $host_type, host_key => $host_key, - host_ipmi => $host_ipmi, + host_ipmi => $host_ipmi =~ /passw/ ? $anvil->Log->is_secure($host_ipmi) : $host_ipmi, modified_date => $modified_date, }}); @@ -1943,7 +1943,7 @@ FROM host_name => $host_name, host_type => $host_type, host_key => $host_key, - host_ipmi => $host_ipmi, + host_ipmi => $host_ipmi =~ /passw/ ? $anvil->Log->is_secure($host_ipmi) : $host_ipmi, modified_date => $modified_date, }; @@ -1951,14 +1951,14 @@ FROM $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name} = $host_name; $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type} = $host_type; $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_key} = $host_key; - $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi} = $host_ipmi; + $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi} = $anvil->Log->is_secure($host_ipmi); $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name} = $anvil_name; $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_uuid} = $anvil_uuid; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "hosts::host_uuid::${host_uuid}::host_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name}, "hosts::host_uuid::${host_uuid}::host_type" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type}, "hosts::host_uuid::${host_uuid}::host_key" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_key}, - "hosts::host_uuid::${host_uuid}::host_ipmi" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi}, + "hosts::host_uuid::${host_uuid}::host_ipmi" => $host_ipmi =~ /passw/ ? $anvil->Log->is_secure($anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi}) : $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_ipmi}, "hosts::host_uuid::${host_uuid}::anvil_name" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_name}, "hosts::host_uuid::${host_uuid}::anvil_uuid" => $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_uuid}, }}); @@ -2214,6 +2214,7 @@ AND # Finally, load IP addresses. $query = " SELECT + ip_address_uuid, ip_address_on_type, ip_address_on_uuid, ip_address_address, @@ -2235,10 +2236,11 @@ AND }}); foreach my $row (@{$results}) { - my $ip_address_on_type = $row->[0]; - my $ip_address_on_uuid = $row->[1]; - my $ip_address_address = $row->[2]; - my $ip_address_subnet_mask = $row->[3]; + my $ip_address_uuid = $row->[0]; + my $ip_address_on_type = $row->[1]; + my $ip_address_on_uuid = $row->[2]; + my $ip_address_address = $row->[3]; + my $ip_address_subnet_mask = $row->[4]; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip_address_on_type => $ip_address_on_type, ip_address_on_uuid => $ip_address_on_uuid, @@ -2264,6 +2266,15 @@ AND $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { on_interface => $on_interface }}); } my $on_network = ($on_interface =~ /^(.*?)_/)[0]; + if (not defined $on_network) + { + # This isn't a network we should know about (ie: it might be a stray 'virbrX' + # birdge), delete this IP. + my $query = "UPDATE ip_addresses SET ip_address_note = 'DELETED' WHERE ip_address_uuid = ".$anvil->Database->quote($ip_address_uuid).";"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + $anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__}); + next; + } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { on_network => $on_network }}); # Store it. @@ -5302,8 +5313,8 @@ WHERE $old_host_type = $row->[2]; $old_host_key = $row->[3]; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - old_host_ipmi => $old_host_ipmi, - old_host_name => $old_host_name =~ /passw/ ? $anvil->Log->is_secure($old_host_name) : $old_host_name, + old_host_ipmi => $old_host_ipmi =~ /passw/ ? $anvil->Log->is_secure($old_host_ipmi) : $old_host_ipmi, + old_host_name => $old_host_name, old_host_type => $old_host_type, old_host_key => $old_host_key, }}); diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm index 18712957..957716ef 100644 --- a/Anvil/Tools/Remote.pm +++ b/Anvil/Tools/Remote.pm @@ -626,7 +626,6 @@ sub call if ($timeout) { # Call with a timeout - #($output, $error) = $ssh_fh->capture2({timeout => $timeout}, $shell_call."; ".$anvil->data->{path}{exe}{echo}." return_code:\$?"); ($output, $error) = $ssh_fh->capture2({timeout => $timeout}, $shell_call); $output = "" if not defined $output; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => $secure, list => { 'ssh_fh->error' => $ssh_fh->error }}); diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm index df2852e7..c4734eef 100644 --- a/Anvil/Tools/System.pm +++ b/Anvil/Tools/System.pm @@ -1185,7 +1185,7 @@ sub check_storage This uses the host information along with the Anvil! the host is in to find and configure the local IPMI BMC. -If this host is not in an Anvil!, or if the host is in an Anvil!, but no IPMI BMC was found, or any other issue arises, C<< 0 >> is returned. +If this host is not in an Anvil!, or if the host is in an Anvil!, but no IPMI BMC was found, or any other issue arises, C<< 0 >> is returned. If there is any problem, C<< !!errer!! >> will be returned. If a BMC is found and configured, the C<< fence_ipmilan >> call used to check the status is stored in C<< hosts >> -> C<< host_ipmi >>, and the same string is returned. @@ -1280,7 +1280,7 @@ LIMIT 1 return(0); } - (my $machine, $manifest_uuid, $anvil_uuid) = ($anvil->data->{jobs}{job_data} =~ /as_machine=(.*?),manifest_uuid=(.*?),anvil_uuid=(.*?)$/); + (my $machine, $manifest_uuid, $anvil_uuid) = ($job_data =~ /as_machine=(.*?),manifest_uuid=(.*?),anvil_uuid=(.*?)$/); $machine = "" if not defined $machine; $manifest_uuid = "" if not defined $manifest_uuid; $anvil_uuid = "" if not defined $anvil_uuid; @@ -1303,26 +1303,15 @@ LIMIT 1 } # Make sure the IPMI IP, subnet mask and password are available. - my $ipmi_ip_address = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{machine}{$machine}{ipmi_ip}; - my $ipmi_password = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_password}; - my $escaped_ipmi_password = shell_quote($ipmi_password); - my $password_length = length(Encode::encode('UTF-8', $ipmi_password)); + my $ipmi_ip_address = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{machine}{$machine}{ipmi_ip}; + my $ipmi_password = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_password}; + my $password_length = length(Encode::encode('UTF-8', $ipmi_password)); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - ipmi_ip_address => $ipmi_ip_address, - ipmi_password => $anvil->Log->is_secure($ipmi_password), - escaped_ipmi_password => $anvil->Log->is_secure($escaped_ipmi_password), - password_length => $password_length, + ipmi_ip_address => $ipmi_ip_address, + ipmi_password => $anvil->Log->is_secure($ipmi_password), + password_length => $password_length, }}); - # Most (all?) IPMI BMCs don't support passwords over 20 bytes long. If the passed password is longer, reduce it. - my $short_ipmi_password = ""; - my $short_escaped_ipmi_password = ""; - if (length($ipmi_password) > 20) - { - - } - - my $subnet_mask = ""; my $gateway = ""; my $in_network = ""; @@ -1338,9 +1327,9 @@ LIMIT 1 foreach my $i (1..$count) { my $network_name = $network_type.$i; - my $network = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{$network_name}{network}; - my $this_subnet_mask = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{$network_name}{subnet}; - my $this_gateway = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{$network_name}{gateway}; + my $network = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{name}{$network_name}{network}; + my $this_subnet_mask = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{name}{$network_name}{subnet}; + my $this_gateway = $anvil->data->{manifests}{manifest_uuid}{$manifest_uuid}{parsed}{networks}{name}{$network_name}{gateway}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { network_name => $network_name, network => $network, @@ -1574,23 +1563,30 @@ LIMIT 1 } # HPs require a warm restart - if (($changes) && ($manufacturer eq "HP")) - { - # Do a warm reset. This should take about 30 seconds for pings to respond. We'll wait that - # long anyway in casr the IP itself didn't change, then wait for the pings to respond. - my $wait_until = time + 120; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0508", variables => { - manufacturer => $manufacturer, - ip_address => $ipmi_ip_address, + if (($changes) && (($manufacturer eq "HP") or ($manufacturer eq "Dell"))) + { + # HPs can get away with a warm reset. Dells need a cold reset. + my $reset_type = $manufacturer eq "HP" ? "warm" : "cold"; + my $reset_delay = $reset_type eq "cold" ? 60 : 30; + my $wait_until = time + 120; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + manufacturer => $manufacturer, + reset_type => $reset_type, + reset_delay => $reset_delay, + wait_until => $wait_until, }}); - my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{ipmitool}." mc restart warm"}); + # Do the reset. This should take about 30 ~ 60 seconds for pings to respond. We'll wait that + # long anyway in case the IP itself didn't change, then wait for the pings to respond. + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{ipmitool}." mc reset ".$reset_type}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code, }}); - sleep 30; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0516", variables => { reset_delay => $reset_delay}}); + sleep $reset_delay; + my $done = 0; until($done) { @@ -1678,16 +1674,181 @@ LIMIT 1 striker_host => $striker_host, striker_password => $anvil->Log->is_secure($striker_password), }}); - - my $shell_call = $anvil->data->{path}{directories}{fence_agents}."/fence_ipmilan --ip ".$ipmi_ip_address." --username ".$user_name." --password ".$escaped_ipmi_password." --action status"; + + # See if the current password works. + my $lanplus = "no-yes"; if (($manufacturer eq "HP") or ($manufacturer eq "Dell")) { # These need LAN Plus - $shell_call = $anvil->data->{path}{directories}{fence_agents}."/fence_ipmilan --lanplus --ip ".$ipmi_ip_address." --username ".$user_name." --password ".$escaped_ipmi_password." --action status"; + $lanplus = "yes-no" } - - # If the password doesn't work, we'll try again with a shorter password. + my $try_again = 1; + $host_ipmi = $anvil->System->test_ipmi({ + debug => 2, + ipmi_user => $user_name, + ipmi_password => $ipmi_password, + ipmi_target => $ipmi_ip_address, + lanplus => $lanplus, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { host_ipmi => $host_ipmi}}); + if (($host_ipmi) && ($host_ipmi ne "!!error!!")) + { + # We're good! + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0511"}); + $try_again = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { try_again => $try_again }}); + } + else + { + # Try it again from the dashboard, we may just not be able to talk to our own BMC (can happen + # on shared interfaces) + $host_ipmi = $anvil->System->test_ipmi({ + debug => 2, + ipmi_user => $user_name, + ipmi_password => $ipmi_password, + ipmi_target => $ipmi_ip_address, + lanplus => $lanplus, + target => $striker_host, + password => $striker_password, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { host_ipmi => $host_ipmi}}); + if (($host_ipmi) && ($host_ipmi ne "!!error!!")) + { + # We're good! + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0512"}); + + $try_again = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { try_again => $try_again }}); + } + else + { + # Change the password and then try again. + my $escaped_ipmi_password = shell_quote($ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { escaped_ipmi_password => $escaped_ipmi_password }}); + + my ($output, $return_code) = $anvil->System->call({debug => $debug, secure => 1, shell_call => $anvil->data->{path}{exe}{ipmitool}." user set password ".$user_number." ".$escaped_ipmi_password}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + if (($return_code) or ($output =~ /Password is too long/)) + { + # Try again with the 20-byte password. + my $twenty_byte_ipmi_password = $anvil->Words->shorten_string({ + debug => 3, + secure => 1, + string => $ipmi_password, + 'length' => 20, + }); + my $twenty_byte_escaped_ipmi_password = shell_quote($twenty_byte_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + twenty_byte_ipmi_password => $twenty_byte_ipmi_password, + twenty_byte_escaped_ipmi_password => $twenty_byte_escaped_ipmi_password, + }}); + + my ($output, $return_code) = $anvil->System->call({debug => $debug, secure => 1, shell_call => $anvil->data->{path}{exe}{ipmitool}." user set password ".$user_number." ".$twenty_byte_escaped_ipmi_password}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + if ($return_code) + { + # Try once more with the 16-byte password. + my $sixteen_byte_ipmi_password = $anvil->Words->shorten_string({ + debug => 3, + secure => 1, + string => $ipmi_password, + 'length' => 16, + }); + my $sixteen_byte_escaped_ipmi_password = shell_quote($sixteen_byte_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + sixteen_byte_ipmi_password => $sixteen_byte_ipmi_password, + sixteen_byte_escaped_ipmi_password => $sixteen_byte_escaped_ipmi_password, + }}); + + my ($output, $return_code) = $anvil->System->call({debug => $debug, secure => 1, shell_call => $anvil->data->{path}{exe}{ipmitool}." user set password ".$user_number." ".$sixteen_byte_escaped_ipmi_password}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + if ($return_code) + { + # Nothing more to do. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0137", variables => { + user_name => $user_name, + user_number => $user_number, + output => $output, + return_code => $return_code, + }}); + return('!!error!!'); + } + else + { + # Looks like the 16-byte version worked. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0515"}); + } + } + else + { + # Looks like the 20-byte version worked. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0514"}); + } + } + else + { + # Looks like the password took. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0513"}); + } + } + } + + if ($try_again) + { + $host_ipmi = $anvil->System->test_ipmi({ + debug => 2, + ipmi_user => $user_name, + ipmi_password => $ipmi_password, + ipmi_target => $ipmi_ip_address, + lanplus => $lanplus, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { host_ipmi => $host_ipmi}}); + if (($host_ipmi) && ($host_ipmi ne "!!error!!")) + { + # We're good, password was changed! + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0511"}); + } + else + { + # Try it again from the dashboard, we may just not be able to talk to our own BMC ( + # can happen on shared interfaces) + my $host_ipmi = $anvil->System->test_ipmi({ + debug => 2, + ipmi_user => $user_name, + ipmi_password => $ipmi_password, + ipmi_target => $ipmi_ip_address, + lanplus => $lanplus, + target => $striker_host, + password => $striker_password, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { host_ipmi => $host_ipmi}}); + if (($host_ipmi) && ($host_ipmi ne "!!error!!")) + { + # We're good! + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0511"}); + } + else + { + # Nothing worked. :( + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0138", variables => { + user_name => $user_name, + user_number => $user_number, + }}); + return('!!error!!'); + } + } + } + return($host_ipmi); } @@ -3480,6 +3641,315 @@ sub stty_echo return(0); } +=head2 test_ipmi + +This tests access to an IPMI interface, either locally or from a remote client. This method will automatically shorten the passed-in IPMI password to 20 bytes if the existing password is longer and fails. If the 20 byte password also fails, it will try a third time with a 16 byte password. + +If a working connection is found, the C<< fence_ipmilan >> command that worked will be returned (including the password that worked). + +B<< Note >>: This test uses the C<< fence_ipmilan >> fence agent. This must installed and available on the remote machine (if testing remotely). + +Parameters; + +=head3 ipmi_password (required) + +This is the IPMI user password to use for the IPMI user. It will be shortened to 20 bytes and 16 bytes if necessary. + +B<< Note >>: The password will be escaped for the shell inside this method. Do NOT escape it before sending it in! + +=head3 ipmi_target (required) + +This is the IP or (resolvable) host name of the IPMI BCM to be called. + +=head3 ipmi_user (required) + +This is the IPMI user to use when trying to log into the IPMI BMC. + +=head3 lanplus (optional, default "no-yes") + +This determines if LAN Plus is tried when connecting to the IPMI BMC. This is often vendor-specific (some vendors _usually_ use it, others _usually_ don't). If you know for sure which your BMC needs, you can set it. If you aren't sure, but think you know, you can have it try both, either using or not using LAN Plus initially. + +This can be set to; + +* C<< yes >> - use LAN Plus +* C<< no >> - don't use LAN Plus +* C<< yes-no >> - Try both, using LAN Plus, then trying without. +* C<< no-yes >> - Try both, trying without LAN Plus first, then trying with it. + +B<< Note >>: If there is an existing entry in C<< hosts >> -> C<< host_ipmi >>, that will possibly change this value. If C<< --lanplus >> is found, and this is set to C<< no >> or C<< no-yes >>, it will be changed to C<< yes-no >>. Reversed, if C<< --lanplus >> is NOT found, and this was set to C<< yes >> or C<< yes-no >>, it will be changed to C<< no-yes >>. As such, you may want to verify the returned shell command to see if LAN Plus is needed or not, regardless of how this is set. + +=head3 password (optional) + +If you are testing IPMI from a remote machine, this is the password used to connect to that machine. If not passed, an attempt to connect with passwordless SSH will be made (but this won't be the case in most instances). Ignored if C<< target >> is not given. + +=head3 port (optional, default 22) + +This is the TCP port number to use if connecting to a remote machine over SSH. Ignored if C<< target >> is not given. + +=head3 remote_user (optional, default root) + +If C<< target >> is set, this is the user we will use when logging in to the target machine. + +=head3 target (optional) + +This is the IP address or (resolvable) host name of the target machine to test the IPMI connection from. + +=cut +sub test_ipmi +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->test_ipmi()" }}); + + my $ipmi_password = defined $parameter->{ipmi_password} ? $parameter->{ipmi_password} : ""; + my $ipmi_target = defined $parameter->{ipmi_target} ? $parameter->{ipmi_target} : ""; + my $ipmi_user = defined $parameter->{ipmi_user} ? $parameter->{ipmi_user} : ""; + my $lanplus = defined $parameter->{lanplus} ? $parameter->{lanplus} : "no-yes"; + my $password = defined $parameter->{password} ? $parameter->{password} : ""; + my $port = defined $parameter->{port} ? $parameter->{port} : ""; + my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : ""; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + ipmi_password => $anvil->Log->is_secure($ipmi_password), + ipmi_user => $ipmi_user, + lanplus => $lanplus, + password => $anvil->Log->is_secure($password), + port => $port, + remote_user => $remote_user, + }}); + + if (not $ipmi_user) + { + # Nothing more we can do. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Systeme->test_ipmi()", parameter => "ipmi_user" }}); + return("!!error!!"); + } + if (not $ipmi_target) + { + # Nothing more we can do. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Systeme->test_ipmi()", parameter => "ipmi_target" }}); + return("!!error!!"); + } + if (not $ipmi_password) + { + # Nothing more we can do. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Systeme->test_ipmi()", parameter => "ipmi_password" }}); + return("!!error!!"); + } + if (($lanplus ne "yes") && ($lanplus ne "no") && ($lanplus ne "yes-no") && ($lanplus ne "no-yes")) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0136", variables => { lanplus => $lanplus }}); + return("!!error!!"); + } + + my $escaped_ipmi_password = shell_quote($ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { escaped_ipmi_password => $escaped_ipmi_password }}); + + my $twenty_byte_ipmi_password = ""; + my $twenty_byte_escaped_ipmi_password = ""; + if (length($ipmi_password) > 20) + { + $twenty_byte_ipmi_password = $anvil->Words->shorten_string({ + debug => 3, + string => $ipmi_password, + 'length' => 20, + secure => 1, + }); + $twenty_byte_escaped_ipmi_password = shell_quote($twenty_byte_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + twenty_byte_ipmi_password => $twenty_byte_ipmi_password, + twenty_byte_escaped_ipmi_password => $twenty_byte_escaped_ipmi_password, + }}); + } + my $sixteen_byte_ipmi_password = ""; + my $sixteen_byte_escaped_ipmi_password = ""; + if (length($ipmi_password) > 16) + { + $sixteen_byte_ipmi_password = $anvil->Words->shorten_string({ + debug => 3, + string => $ipmi_password, + 'length' => 16, + secure => 1, + }); + $sixteen_byte_escaped_ipmi_password = shell_quote($sixteen_byte_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + sixteen_byte_ipmi_password => $sixteen_byte_ipmi_password, + sixteen_byte_escaped_ipmi_password => $sixteen_byte_escaped_ipmi_password, + }}); + } + + # Read in the 'host_ipmi' (if it exists) to see if there's an old entry. If there is, and if the + # password matches one of the shorter ones, we'll try that first. + my $query = "SELECT host_ipmi FROM hosts WHERE host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid).";"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + results => $results, + count => $count, + }}); + my $host_ipmi = defined $results->[0]->[0] ? $results->[0]->[0] : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { host_ipmi => $host_ipmi }}); + + my @password_array = ($escaped_ipmi_password, $twenty_byte_escaped_ipmi_password, $sixteen_byte_escaped_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + 'password_array[0]' => $password_array[0], + 'password_array[1]' => $password_array[1], + 'password_array[2]' => $password_array[2], + }}); + + my $old_password = ""; + my $old_lanplus = ""; + if ($host_ipmi) + { + if ($host_ipmi =~ /--lanplus/) + { + # If we were given 'lanplus' of 'no' or 'no-yes', change it to 'yes-no'. + if (($lanplus eq "no") or ($lanplus eq "no-yes")) + { + $lanplus = "yes-no"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { lanplus => $lanplus }}); + } + } + else + { + # If we were given 'lanplus' of 'yes' or 'yes-no', change it to 'no-yes'. + if (($lanplus eq "yes") or ($lanplus eq "yes-no")) + { + $lanplus = "no-yes"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { lanplus => $lanplus }}); + } + } + + # If there was an old password, we ONLY use it if it matches the passed in password or one + # of the shorter variants. The caller may be checking if the password needs to be updated or + # has been updated successfully. + if ($host_ipmi =~ /--password (.*) --action/) + { + $old_password = $1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { old_password => $old_password }}); + + if (($twenty_byte_escaped_ipmi_password) && ($twenty_byte_escaped_ipmi_password eq $old_password)) + { + # It matches the 20-byte password, try it first. + @password_array = ($twenty_byte_escaped_ipmi_password, $escaped_ipmi_password, $sixteen_byte_escaped_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + 'password_array[0]' => $password_array[0], + 'password_array[1]' => $password_array[1], + 'password_array[2]' => $password_array[2], + }}); + } + elsif (($sixteen_byte_escaped_ipmi_password) && ($sixteen_byte_escaped_ipmi_password eq $old_password)) + { + # It matches the 16-byte password, try it first. + @password_array = ($sixteen_byte_escaped_ipmi_password, $escaped_ipmi_password, $twenty_byte_escaped_ipmi_password); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { + 'password_array[0]' => $password_array[0], + 'password_array[1]' => $password_array[1], + 'password_array[2]' => $password_array[2], + }}); + } + } + } + + my @lanplus_array; + if ($lanplus eq "no-yes") + { + @lanplus_array = ("", "--lanplus"); + } + elsif ($lanplus eq "yes-no") + { + @lanplus_array = ("--lanplus", ""); + } + elsif ($lanplus eq "yes") + { + @lanplus_array = ("--lanplus"); + } + elsif ($lanplus eq "no") + { + @lanplus_array = (""); + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'lanplus_array[0]' => $lanplus_array[0], + 'lanplus_array[1]' => defined $lanplus_array[1] ? defined $lanplus_array[1] : "--", + }}); + + my $shell_call = ""; + my $found_it = 0; + foreach my $lanplus_switch (@lanplus_array) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { lanplus_switch => $lanplus_switch }}); + foreach my $test_password (@password_array) + { + # If the password is blank, it's because the previous password wasn't too long, so + # no sense trying again. + next if $test_password eq ""; + + # Build the shell call. + $shell_call = $anvil->data->{path}{directories}{fence_agents}."/fence_ipmilan ".$lanplus_switch." --ip ".$ipmi_target." --username ".$ipmi_user." --password ".$test_password." --action status"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 1, list => { shell_call => $shell_call }}); + + my $output = ""; + my $return_code = ""; + if ($target) + { + ### Remote call + # HPs can take over 10 seconds to respond, so we set the timeout higher to account for this. + ($output, my $error, $return_code) = $anvil->Remote->call({ + debug => $debug, + secure => 1, + timeout => 30, + shell_call => $shell_call, + target => $target, + password => $password, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + error => $error, + output => $output, + return_code => $return_code, + }}); + } + else + { + ### Local call + ($output, $return_code) = $anvil->System->call({ + debug => $debug, + secure => 1, + shell_call => $shell_call, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + } + if (not $return_code) + { + # Got it! + $found_it = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { found_it => $found_it }}); + } + else + { + $shell_call = ""; + } + last if $found_it; + } + last if $found_it; + } + + # If we have a valid shell call, and it doesn't match the one we read in earlier, update it. + if (($shell_call) && ($host_ipmi ne $shell_call)) + { + # Update it. + my $query = "UPDATE hosts SET host_ipmi = ".$anvil->Database->quote($shell_call)." WHERE host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid).";"; + $anvil->Database->write({debug => $debug, query => $query, source => $THIS_FILE, line => __LINE__}); + } + + return($shell_call); +} + =head2 update_hosts This uses the host list from C<< Get->trusted_hosts >>, along with data from C<< ip_addresses >>, to create a list of host name to IP addresses that should be in C<< /etc/hosts >>. Existing hosts where the IP has changed will be updated. Missing entries will be added. All other existing entries are left unchanged. @@ -3497,7 +3967,7 @@ sub update_hosts # Get the list of hosts we trust. my $trusted_host_uuids = $anvil->Get->trusted_hosts({debug => $debug}); - $anvil->Database->get_ip_addresses({debug => $debug}); + $anvil->Database->get_ip_addresses({debug => 2}); foreach my $host_uuid (keys %{$anvil->data->{hosts}{host_uuid}}) { diff --git a/share/words.xml b/share/words.xml index b6485dbe..144b3509 100644 --- a/share/words.xml +++ b/share/words.xml @@ -195,6 +195,8 @@ The error was: Failed to read or parse the CIB! Is pacemaker running? Failed to start the daemon: [#!variable!daemon!#] on the local system, unable to boot the server. Failed to start the daemon: [#!variable!daemon!#] on [#!variable!host!#], unable to boot the server. + System->test_ipmi() was called with an invalid 'lanplus' parameter. It must be 'yes', 'no', 'yes-no' or 'no-yes'. Received: [#!variable!lanplus!#]. + All attempts to change the IPMI user: [#!variable!user_name!#] (number: [#!variable!user_number!#] failed. The last try's output (if any) was: [#!variable!output!#] (return code: [#!variable!return code!#]). Current Network Interfaces and States @@ -945,9 +947,15 @@ The IPMI BMC administrator (oem) user was not found. The output (if any) of the #!variable!output!# ==== - This host's manufacturer is: [#!variable!manufacturer!#], changing it to: [#!variable!ip_address!#]. + This host's manufacturer is: [#!variable!manufacturer!#], for the changes to take effect, the BMC will be reset now. Successfully pinged: [#!variable!ip_address!#]. Timed out waiting to ping: [#!variable!ip_address!#]! Configuration will process in case we can't ping our own BMC, but the fence test may fail. + The password for the IPMI BMC works, no need to update it. + The password for the IPMI BMC works, no need to update it. Note that we had to use another machine to confirm, it looks like we can't talk to our own BMC using the IP address. + The password for the IPMI BMC appears to have been successfully updated. Will test to confirm. + The password for the IPMI BMC appears to have been successfully updated, though we had to reduce it to 20-bytes long. Will test to confirm. + The password for the IPMI BMC appears to have been successfully updated, though we had to reduce it to 16-bytes long. Will test to confirm. + Waiting: [#!variable!reset_delay!#] seconds to give the BMC time to reset... The host name: [#!variable!target!#] does not resolve to an IP address. diff --git a/tools/anvil-daemon b/tools/anvil-daemon index 3547b5c2..268ae6a1 100755 --- a/tools/anvil-daemon +++ b/tools/anvil-daemon @@ -256,7 +256,8 @@ sub check_if_mapping if ($map_network_value) { # How long ago was it set? - if ($map_network_age >= $expire_age) + $anvil->data->{switches}{'clear-mapping'} = "" if not defined $anvil->data->{switches}{'clear-mapping'}; + if (($map_network_age >= $expire_age) or ($anvil->data->{switches}{'clear-mapping'})) { # Clear it. $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0470"}); diff --git a/tools/anvil-update-states b/tools/anvil-update-states index 709627f7..9ecc5a08 100755 --- a/tools/anvil-update-states +++ b/tools/anvil-update-states @@ -292,6 +292,7 @@ sub update_network $bridge_stp_enabled = $anvil->Storage->read_file({debug => 3, file => $full_path."/bridge/stp_state"}); $bridge_id =~ s/\n$//; $bridge_stp_enabled =~ s/\n$//; + $speed = 0; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { bridge_id => $bridge_id, bridge_stp_enabled => $bridge_stp_enabled, @@ -548,6 +549,7 @@ sub update_network ip_address_dns => $dns, ip_address_note => "", }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { ip_address_uuid => $ip_address_uuid }}); } } } @@ -604,6 +606,7 @@ sub update_network ip_address_dns => $dns, ip_address_note => "", }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { ip_address_uuid => $ip_address_uuid }}); } } } diff --git a/tools/test.pl b/tools/test.pl index 54059939..5d6a39ec 100755 --- a/tools/test.pl +++ b/tools/test.pl @@ -5,6 +5,7 @@ use strict; use warnings; use Anvil::Tools; use Data::Dumper; +use String::ShellQuote; use utf8; binmode(STDERR, ':encoding(utf-8)'); binmode(STDOUT, ':encoding(utf-8)'); @@ -23,16 +24,6 @@ my $anvil = Anvil::Tools->new(); $anvil->Log->level({set => 2}); $anvil->Log->secure({set => 1}); -my $string = "スパ シコレ パソーワアド"; -my $short = $anvil->Words->shorten_string({ - debug => 2, - string => $string, - 'length' => 16, -}); -print "String: [".$string."]\n"; -print "Short: [".$short."]\n"; -die; - print "Connecting to the database(s);\n"; $anvil->Database->connect(); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, secure => 0, key => "log_0132"});