From 324ef351feaa6563eb8c4e354339536fefddd3af Mon Sep 17 00:00:00 2001 From: Digimer Date: Tue, 6 Aug 2019 23:31:35 -0400 Subject: [PATCH] * Updated DRBD->get_devices() to properly identify the peer node, when run on an actual node in the cluster (not DR or Striker). * Created System->active_lv() that, surprise, activates an inactive logical volume. Also created ->check_storage() that parses out the LVM data. * Fixed a bug in tools/fence_pacemaker that was preventing it from compiling and running. * Updated ocf:alteeve:server to validate the target server's storage. Signed-off-by: Digimer --- Anvil/Tools.pm | 7 ++ Anvil/Tools/Account.pm | 2 +- Anvil/Tools/Alert.pm | 2 +- Anvil/Tools/Convert.pm | 2 +- Anvil/Tools/DRBD.pm | 66 ++++++++++-- Anvil/Tools/Database.pm | 6 +- Anvil/Tools/Get.pm | 2 +- Anvil/Tools/Job.pm | 2 +- Anvil/Tools/Log.pm | 4 +- Anvil/Tools/Remote.pm | 2 +- Anvil/Tools/Server.pm | 21 ++-- Anvil/Tools/Storage.pm | 2 +- Anvil/Tools/System.pm | 217 +++++++++++++++++++++++++++++++++++++- Anvil/Tools/Template.pm | 2 +- Anvil/Tools/Validate.pm | 2 +- Anvil/Tools/Words.pm | 2 +- cgi-bin/upload.pl | 4 +- ocf/alteeve/server | 122 +++++++++++++++++++-- share/words.xml | 8 +- tools/anvil-download-file | 2 +- tools/fence_pacemaker | 2 +- 21 files changed, 432 insertions(+), 47 deletions(-) diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index b8f17509..533dfb1b 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1041,6 +1041,9 @@ sub _set_paths 'iptables-save' => "/usr/sbin/iptables-save", journalctl => "/usr/bin/journalctl", logger => "/usr/bin/logger", + lvchange => "/usr/sbin/lvchange", + lvs => "/usr/sbin/lvs", + lvscan => "/usr/sbin/lvscan", md5sum => "/usr/bin/md5sum", 'mkdir' => "/usr/bin/mkdir", mv => "/usr/bin/mv", @@ -1053,6 +1056,8 @@ sub _set_paths psql => "/usr/bin/psql", 'postgresql-setup' => "/usr/bin/postgresql-setup", pwd => "/usr/bin/pwd", + pvs => "/usr/sbin/pvs", + pvscan => "/usr/sbin/pvscan", rpm => "/usr/bin/rpm", rsync => "/usr/bin/rsync", sed => "/usr/bin/sed", @@ -1076,6 +1081,8 @@ sub _set_paths usermod => "/usr/sbin/usermod", uuidgen => "/usr/bin/uuidgen", virsh => "/usr/bin/virsh", + vgs => "/usr/sbin/vgs", + vgscan => "/usr/sbin/vgscan", wget => "/usr/bin/wget", }, json => { diff --git a/Anvil/Tools/Account.pm b/Anvil/Tools/Account.pm index 3d8a1ed4..41519144 100644 --- a/Anvil/Tools/Account.pm +++ b/Anvil/Tools/Account.pm @@ -69,7 +69,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Alert.pm b/Anvil/Tools/Alert.pm index 158e8dd0..bb892809 100644 --- a/Anvil/Tools/Alert.pm +++ b/Anvil/Tools/Alert.pm @@ -63,7 +63,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Convert.pm b/Anvil/Tools/Convert.pm index f1bdf484..43c4688d 100644 --- a/Anvil/Tools/Convert.pm +++ b/Anvil/Tools/Convert.pm @@ -69,7 +69,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/DRBD.pm b/Anvil/Tools/DRBD.pm index 8b0da831..806dd2b8 100755 --- a/Anvil/Tools/DRBD.pm +++ b/Anvil/Tools/DRBD.pm @@ -64,7 +64,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); @@ -166,8 +166,10 @@ sub get_devices $anvil->nice_exit({exit_code => 1}); } - $anvil->data->{drbd}{'dump-xml'}{parsed} = $dump_xml; + #print Dumper $dump_xml; $anvil->data->{drbd}{config}{'auto-promote'} = 0; + $anvil->data->{drbd}{host}{'local'} = ""; + $anvil->data->{drbd}{host}{peer} = ""; foreach my $hash_ref (@{$dump_xml->{resource}}) { @@ -210,11 +212,11 @@ sub get_devices { ### TODO: Handle external metadata my $host = $host_href->{name}; - my $local = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host => $host }}); if (($host eq $anvil->_hostname) or ($host eq $anvil->_short_hostname)) { - $local = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'local' => $local }}); + $anvil->data->{drbd}{host}{'local'} = $host; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'drbd::host::local' => $anvil->data->{drbd}{host}{'local'} }}); } foreach my $volume_href (@{$host_href->{volume}}) { @@ -231,7 +233,7 @@ sub get_devices "drbd::config::${this_resource}::volume::${volume}::meta-disk" => $anvil->data->{drbd}{config}{$this_resource}{volume}{$volume}{'meta-disk'}, "drbd::config::${this_resource}::volume::${volume}::backing_lv" => $anvil->data->{drbd}{config}{$this_resource}{volume}{$volume}{backing_lv}, }}); - if ($local) + if (($anvil->data->{drbd}{host}{'local'}) && ($anvil->data->{drbd}{host}{'local'} eq $host)) { $anvil->data->{drbd}{'local'}{drbd_path}{$drbd_path}{on} = $lv_path; $anvil->data->{drbd}{'local'}{drbd_path}{$drbd_path}{resource} = $this_resource; @@ -244,6 +246,58 @@ sub get_devices } } } + + ### NOTE: Connections are listed as 'host A <-> Host B (options), 'host A <-> Host C + ### (options) and 'host B <-> Host C (options)'. So first we see which entry has + ### fencing, and ignore the others. The one with real fencing, we figure out which is + ### us (if any) and the other has to be the peer. + # Find my peer, if I am myself a node. + if (($anvil->data->{drbd}{host}{'local'}) && (not $anvil->data->{drbd}{host}{peer})) + { + #print Dumper $hash_ref->{connection}; + foreach my $hash_ref (@{$hash_ref->{connection}}) + { + # Look in 'section' for fencing data. + my $fencing = ""; + my $protocol = ""; + #print Dumper $hash_ref; + foreach my $section_ref (@{$hash_ref->{section}}) + { + next if $section_ref->{name} ne "net"; + foreach my $option_ref (@{$section_ref->{option}}) + { + if ($option_ref->{name} eq "fencing") + { + $fencing = $option_ref->{value}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { fencing => $fencing }}); + } + elsif ($option_ref->{name} eq "protocol") + { + $protocol = $option_ref->{value}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { protocol => $protocol }}); + } + } + } + + # If the protocol is 'resource-and-stonith', we care. Otherwise it's a + # connection involving DR and we don't. + next if $fencing ne "resource-and-stonith"; + + # If we're still alive, this should be our connection to our peer. + foreach my $host_ref (@{$hash_ref->{host}}) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'drbd::host::local' => $anvil->data->{drbd}{host}{'local'}, + 'host_ref->name' => $host_ref->{name}, + }}); + next if $host_ref->{name} eq $anvil->data->{drbd}{host}{'local'}; + + # Found the peer. + $anvil->data->{drbd}{host}{peer} = $host_ref->{name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'drbd::host::peer' => $anvil->data->{drbd}{host}{peer} }}); + } + } + } } return(0); diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 90bb6a71..e6d11a62 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -101,7 +101,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); @@ -6789,7 +6789,7 @@ ORDER BY "sys::database::table::${table}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{last_updated}, "sys::database::table::${table}::uuid::${uuid}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{last_updated}, }}); - my $difference = $anvil->Convert->add_commas({number => ($anvil->data->{sys}{database}{table}{$table}{last_updated} - $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{last_updated}) });; + my $difference = $anvil->Convert->add_commas({number => ($anvil->data->{sys}{database}{table}{$table}{last_updated} - $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{last_updated}) }); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0106", variables => { seconds => $difference, table => $table, @@ -6808,7 +6808,7 @@ ORDER BY "sys::database::table::${table}::row_count" => $anvil->data->{sys}{database}{table}{$table}{row_count}, "sys::database::table::${table}::uuid::${uuid}::row_count" => $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{row_count}, }}); - my $difference = $anvil->Convert->add_commas({number => ($anvil->data->{sys}{database}{table}{$table}{row_count} - $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{row_count}) });; + my $difference = $anvil->Convert->add_commas({number => ($anvil->data->{sys}{database}{table}{$table}{row_count} - $anvil->data->{sys}{database}{table}{$table}{uuid}{$uuid}{row_count}) }); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0219", variables => { missing => $difference, table => $table, diff --git a/Anvil/Tools/Get.pm b/Anvil/Tools/Get.pm index a23dd1e3..16485238 100644 --- a/Anvil/Tools/Get.pm +++ b/Anvil/Tools/Get.pm @@ -81,7 +81,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Job.pm b/Anvil/Tools/Job.pm index 4c96da28..021ba427 100644 --- a/Anvil/Tools/Job.pm +++ b/Anvil/Tools/Job.pm @@ -69,7 +69,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Log.pm b/Anvil/Tools/Log.pm index cb6a3705..39d67fd8 100644 --- a/Anvil/Tools/Log.pm +++ b/Anvil/Tools/Log.pm @@ -75,7 +75,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); @@ -691,7 +691,7 @@ sub variables } my $raw = ""; # NOTE: If you change this, be sure to update Tools.t - if ($entries <= 4) + if ($entries <= 3) { # Put all the entries on one line. foreach my $key (sort {$a cmp $b} keys %{$list}) diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm index 72e9d0ab..1e4ccfcc 100644 --- a/Anvil/Tools/Remote.pm +++ b/Anvil/Tools/Remote.pm @@ -70,7 +70,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Server.pm b/Anvil/Tools/Server.pm index 3ffa42b1..3e51ce41 100755 --- a/Anvil/Tools/Server.pm +++ b/Anvil/Tools/Server.pm @@ -63,7 +63,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); @@ -208,7 +208,12 @@ sub get_status $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "server::${server}::disk::xml" => $anvil->data->{server}{$server}{disk}{xml}, }}); - if ($anvil->data->{server}{$server}{from_disk}{xml}) + if (($anvil->data->{server}{$server}{from_disk}{xml} eq "!!errer!!") or (not $anvil->data->{server}{$server}{from_disk}{xml})) + { + # Failed to read it. + $anvil->data->{server}{$server}{from_disk}{xml} = ""; + } + else { $anvil->Server->_parse_definition({ debug => $debug, @@ -655,13 +660,13 @@ sub _parse_definition "server::${server}::${source}::device::${device}::target::${device_target}::driver::cache" => $anvil->data->{server}{$server}{$source}{device}{$device}{target}{$device_target}{driver}{cache}, }}); - $anvil->data->{server}{$server}{$source}{device}{$device_path}{on_lv} = defined $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{on} ? $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{on} : ""; - $anvil->data->{server}{$server}{$source}{device}{$device_path}{resource} = defined $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{resource} ? $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{resource} : ""; - $anvil->data->{server}{$server}{$source}{device}{$device_path}{target} = $device_target; + $anvil->data->{server}{$server}{device}{$device_path}{on_lv} = defined $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{on} ? $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{on} : ""; + $anvil->data->{server}{$server}{device}{$device_path}{resource} = defined $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{resource} ? $anvil->data->{drbd}{'local'}{drbd_path}{$device_path}{resource} : ""; + $anvil->data->{server}{$server}{device}{$device_path}{target} = $device_target; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "server::${server}::${source}::device::${device_path}::on_lv" => $anvil->data->{server}{$server}{$source}{device}{$device_path}{on_lv}, - "server::${server}::${source}::device::${device_path}::resource" => $anvil->data->{server}{$server}{$source}{device}{$device_path}{resource}, - "server::${server}::${source}::device::${device_path}::target" => $anvil->data->{server}{$server}{$source}{device}{$device_path}{target}, + "server::${server}::device::${device_path}::on_lv" => $anvil->data->{server}{$server}{device}{$device_path}{on_lv}, + "server::${server}::device::${device_path}::resource" => $anvil->data->{server}{$server}{device}{$device_path}{resource}, + "server::${server}::device::${device_path}::target" => $anvil->data->{server}{$server}{device}{$device_path}{target}, }}); ### TODO: Store the parts in some format that allows representing it better to the user and easier to find "open slots". diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm index 8fb1adc3..853cd901 100644 --- a/Anvil/Tools/Storage.pm +++ b/Anvil/Tools/Storage.pm @@ -86,7 +86,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm index 832064ab..cfbd9a68 100644 --- a/Anvil/Tools/System.pm +++ b/Anvil/Tools/System.pm @@ -15,11 +15,13 @@ our $VERSION = "3.0.0"; my $THIS_FILE = "System.pm"; ### Methods; +# activate_lv # call # change_shell_user_password # check_daemon # check_if_configured # check_memory +# check_storage # get_bridges # get_free_memory # get_host_type @@ -93,7 +95,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); @@ -104,6 +106,61 @@ sub parent # Public methods # ############################################################################################################# +=head2 activate_lv + +This takes a logical volume path and tries to activate it. If it is successfully activated, C<< 1 >> is returned. If the activation fails for any reason, C<< 0 >> is returned. + + my $activated = $anvil->System->activate_lv({path => "/dev/foo/bar"}); + +Parameters; + +=head3 path (required) + +This is the full path to the logical volume to activate. + +=cut +sub activate_lv +{ + 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->call()" }}); + + my $path = defined $parameter->{path} ? $parameter->{path} : ""; + my $activated = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { + path => $path, + }}); + + if (not $path) + { + # Woops! + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Systeme->activate_lv()", parameter => "path" }}); + return($activated); + } + if ((not -e $path) or (not -b $path)) + { + # Bad path + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0064", variables => { path => $path }}); + } + + my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{lvchange}." --activate y ".$path}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { + output => $output, + return_code => $return_code, + }}); + + # A non-zero return code indicates failure, but we'll check directly. + $anvil->System->check_storage({debug => $debug, scan => 2}); + + # Check if it worked. + $activated = $anvil->data->{lvm}{'local'}{lv}{$path}{active}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => 0, list => { activated => $activated }}); + + return($activated); +} + =head2 call This method makes a system call and returns the output (with the last new-line removed) and the return code. If there is a problem, 'C<< #!error!# >>' is returned and the error will be logged. @@ -543,7 +600,161 @@ sub check_memory return($used_ram); } -=hed2 get_bridges +=head2 check_storage + +Thic gathers LVM data from the local system. + +Parameters; + +=head4 scan (optional, default '1') + +Setting this to C<< 0 >> will disable scanning prior to data collection. When enabled, C<< pvscan; vgscan; lvscan >> are called before the C<< pvs >>, C<< vgs >> and C<< lvs >> calls used to collect the data this parses. + +=cut +sub check_storage +{ + 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->check_storage()" }}); + + my $scan = defined $parameter->{scan} ? $parameter->{scan} : 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { scan => $scan }}); + + # Do a scan, if requested. + if ($scan) + { + my ($output, $return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{pvscan}." && ".$anvil->data->{path}{exe}{vgscan}." && ".$anvil->data->{path}{exe}{lvscan}}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + return_code => $return_code, + }}); + } + + # Gather PV data. + my ($pvs_output, $pvs_return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{pvs}." --units b --noheadings --separator \\\#\\\!\\\# -o pv_name,vg_name,pv_fmt,pv_attr,pv_size,pv_free,pv_used,pv_uuid"}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + pvs_output => $pvs_output, + pvs_return_code => $pvs_return_code, + }}); + foreach my $line (split/\n/, $pvs_output) + { + $line = $anvil->Words->clean_spaces({string => $line}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + + my ($this_pv, $used_by_vg, $format, $attributes, $total_size, $free_size, $used_size, $uuid) = (split /#!#/, $line); + $total_size =~ s/B$//; + $free_size =~ s/B$//; + $used_size =~ s/B$//; + + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{used_by_vg} = $used_by_vg; + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{attributes} = $attributes; + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{total_size} = $total_size; + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{free_size} = $free_size; + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{used_size} = $used_size; + $anvil->data->{lvm}{'local'}{pv}{$this_pv}{uuid} = $uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "lvm::local::pv::${this_pv}::used_by_vg" => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{used_by_vg}, + "lvm::local::pv::${this_pv}::attributes" => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{attributes}, + "lvm::local::pv::${this_pv}::total_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{total_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{total_size}}).")", + "lvm::local::pv::${this_pv}::free_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{free_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{free_size}}).")", + "lvm::local::pv::${this_pv}::used_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{used_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{used_size}}).")", + "lvm::local::pv::${this_pv}::uuid" => $anvil->data->{lvm}{'local'}{pv}{$this_pv}{uuid}, + }}); + } + + # Gather VG data. + my ($vgs_output, $vgs_return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{vgs}." --units b --noheadings --separator \\\#\\\!\\\# -o vg_name,vg_attr,vg_extent_size,vg_extent_count,vg_uuid,vg_size,vg_free_count,vg_free,pv_name"}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + vgs_output => $vgs_output, + vgs_return_code => $vgs_return_code, + }}); + foreach my $line (split/\n/, $vgs_output) + { + $line = $anvil->Words->clean_spaces({string => $line}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + + my ($this_vg, $attributes, $pe_size, $total_pe, $uuid, $vg_size, $free_pe, $vg_free, $pv_name) = split /#!#/, $line; + $pe_size = "" if not defined $pe_size; + $vg_size = "" if not defined $vg_size; + $vg_free = "" if not defined $vg_free; + $attributes = "" if not defined $attributes; + + $pe_size =~ s/B$//; + $vg_size =~ s/B$//; + $vg_free =~ s/B$//; + + my $used_pe = 0; + if (($total_pe) && ($free_pe)) + { + $used_pe = $total_pe - $free_pe; + } + my $used_space = 0; + if (($vg_size) && ($vg_free)) + { + $used_space = $vg_size - $vg_free; + } + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{pe_size} = $pe_size; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{total_pe} = $total_pe; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{uuid} = $uuid; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{size} = $vg_size; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{used_pe} = $used_pe; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{used_space} = $used_space; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{free_pe} = $free_pe; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{free_space} = $vg_free; + $anvil->data->{lvm}{'local'}{vg}{$this_vg}{pv_name} = $pv_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "lvm::local::vg::${this_vg}::pe_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{pe_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{pe_size}}).")", + "lvm::local::vg::${this_vg}::total_pe" => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{total_pe}, + "lvm::local::vg::${this_vg}::uuid" => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{uuid}, + "lvm::local::vg::${this_vg}::size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{size}}).")", + "lvm::local::vg::${this_vg}::used_pe" => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{used_pe}, + "lvm::local::vg::${this_vg}::used_space" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{used_space}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{used_space}}).")", + "lvm::local::vg::${this_vg}::free_pe" => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{free_pe}, + "lvm::local::vg::${this_vg}::free_space" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{free_space}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{free_space}}).")", + "lvm::local::vg::${this_vg}::pv_name" => $anvil->data->{lvm}{'local'}{vg}{$this_vg}{pv_name}, + }}); + } + + # And finally, the LV data. + my ($lvs_output, $lvs_return_code) = $anvil->System->call({debug => $debug, shell_call => $anvil->data->{path}{exe}{lvs}." --units b --noheadings --separator \\\#\\\!\\\# -o lv_name,vg_name,lv_attr,lv_size,lv_uuid,lv_path,devices"}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + lvs_output => $lvs_output, + lvs_return_code => $lvs_return_code, + }}); + foreach my $line (split/\n/, $lvs_output) + { + $line = $anvil->Words->clean_spaces({string => $line}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); + + my ($lv_name, $on_vg, $attributes, $total_size, $uuid, $path, $devices) = (split /#!#/, $line); + + $total_size =~ s/B$//; + $devices =~ s/\(\d+\)//g; # Strip the starting PE number + + $anvil->data->{lvm}{'local'}{lv}{$path}{name} = $lv_name; + $anvil->data->{lvm}{'local'}{lv}{$path}{on_vg} = $on_vg; + $anvil->data->{lvm}{'local'}{lv}{$path}{active} = ($attributes =~ /.{4}(.{1})/)[0] eq "a" ? 1 : 0; + $anvil->data->{lvm}{'local'}{lv}{$path}{attributes} = $attributes; + $anvil->data->{lvm}{'local'}{lv}{$path}{total_size} = $total_size; + $anvil->data->{lvm}{'local'}{lv}{$path}{uuid} = $uuid; + $anvil->data->{lvm}{'local'}{lv}{$path}{on_devices} = $devices; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "lvm::local::lv::${path}::name" => $anvil->data->{lvm}{'local'}{lv}{$path}{name}, + "lvm::local::lv::${path}::on_vg" => $anvil->data->{lvm}{'local'}{lv}{$path}{on_vg}, + "lvm::local::lv::${path}::active" => $anvil->data->{lvm}{'local'}{lv}{$path}{active}, + "lvm::local::lv::${path}::attributes" => $anvil->data->{lvm}{'local'}{lv}{$path}{attributes}, + "lvm::local::lv::${path}::total_size" => $anvil->Convert->add_commas({number => $anvil->data->{lvm}{'local'}{lv}{$path}{total_size}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{lvm}{'local'}{lv}{$path}{total_size}}).")", + "lvm::local::lv::${path}::uuid" => $anvil->data->{lvm}{'local'}{lv}{$path}{uuid}, + "lvm::local::lv::${path}::on_devices" => $anvil->data->{lvm}{'local'}{lv}{$path}{on_devices}, + }}); + } + + return(0); +} + +=head2 get_bridges This finds a list of bridges on the host. Bridges that are found are stored is ' @@ -556,7 +767,7 @@ sub get_bridges my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "System->get_bridges()" }}); - my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{bridge}." -json -details link show"}); + my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{bridge}." -json -details link show"}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code, diff --git a/Anvil/Tools/Template.pm b/Anvil/Tools/Template.pm index 3e039d0d..19e709bb 100644 --- a/Anvil/Tools/Template.pm +++ b/Anvil/Tools/Template.pm @@ -69,7 +69,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Validate.pm b/Anvil/Tools/Validate.pm index 0330c10a..0dffc9b2 100644 --- a/Anvil/Tools/Validate.pm +++ b/Anvil/Tools/Validate.pm @@ -73,7 +73,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/Anvil/Tools/Words.pm b/Anvil/Tools/Words.pm index 724d4a15..e22e76f6 100644 --- a/Anvil/Tools/Words.pm +++ b/Anvil/Tools/Words.pm @@ -79,7 +79,7 @@ sub parent # Defend against memory leads. See Scalar::Util'. if (not isweak($self->{HANDLE}{TOOLS})) { - weaken($self->{HANDLE}{TOOLS});; + weaken($self->{HANDLE}{TOOLS}); } return ($self->{HANDLE}{TOOLS}); diff --git a/cgi-bin/upload.pl b/cgi-bin/upload.pl index 28c3eeec..65c7b0dd 100755 --- a/cgi-bin/upload.pl +++ b/cgi-bin/upload.pl @@ -63,8 +63,8 @@ if ($cgi->param()) ### NOTE: The timing is a guide only. The AJAX does a lot of work before this script is invoked. It ### might be better to just remove the timing stuff entirely... my $size = (stat($out_file))[7]; - my $say_size_human = $anvil->Convert->add_commas({number => $size}); - my $say_size_comma = $anvil->Convert->bytes_to_human_readable({'bytes' => $size}); + my $say_size_human = $anvil->Convert->bytes_to_human_readable({'bytes' => $size}); + my $say_size_comma = $anvil->Convert->add_commas({number => $size}); my $took = time - $start; $took = 1 if not $took; my $say_took = $anvil->Convert->add_commas({number => $took}); diff --git a/ocf/alteeve/server b/ocf/alteeve/server index d60cde39..c38e2a23 100755 --- a/ocf/alteeve/server +++ b/ocf/alteeve/server @@ -391,9 +391,10 @@ sub validate_all ### if the server is running elsewhere. # Read in an parse the server's XML. - $anvil->Server->get_status({debug => 2, server => $anvil->data->{environment}{OCF_RESKEY_name}}); - - # Is the name in the definition file what we expect? + $anvil->System->check_storage({debug => 3}); + $anvil->Server->get_status({debug => 3, server => $anvil->data->{environment}{OCF_RESKEY_name}}); + + # Is the name in the definition file what we expect (and did we read the XML data at all)? validate_name($anvil); # Make sure the emulator it wants is the one we have. @@ -409,8 +410,8 @@ sub validate_all # Validate bridges validate_bridges($anvil); - # Which DRBD resources do I need? - + # Validate storage (Disks and optical media) + validate_storage($anvil); return(0); } @@ -463,7 +464,54 @@ sub validate_storage { my ($anvil) = @_; + ### TODO: When checking on a running server, use 'from_memory'. + my $server = $anvil->data->{environment}{OCF_RESKEY_name}; + my $source = "from_disk"; + if (exists $anvil->data->{server}{$server}{from_memory}) + { + $source = "from_memory"; + } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + server => $server, + source => $source, + }}); + + ### TODO: If we're called with a status and find an ISO file missing and eject it instead of failing. + ### For now, we just fault out. + # Do the optical discs in the drive exist? If not, we'll eject it if we're about to boot and fail if + # we're about to migrate. We skip this check if we're migrating off or shutting down the server. + if ((exists $anvil->data->{server}{$server}{$source}{device}{cdrom}) && (not $anvil->data->{switches}{migrate_to}) && (not $anvil->data->{switches}{stop})) + { + foreach my $device_target (sort {$a cmp $b} keys %{$anvil->data->{server}{$server}{$source}{device}{cdrom}{target}}) + { + if ($anvil->data->{server}{$server}{$source}{device}{cdrom}{target}{$device_target}{path}) + { + my $file = $anvil->data->{server}{$server}{$source}{device}{cdrom}{target}{$device_target}{path}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file => $file }}); + if (not -e $file) + { + # It doesn't exist. Exit with OCF_ERR_INSTALLED (5). + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0398", variables => { file => $file }}); + $anvil->nice_exit({exit_code => 5}); + } + elsif (not -r $file) + { + # We can't read it. Exit with OCF_ERR_PERM (4). + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0399", variables => { file => $file }}); + $anvil->nice_exit({exit_code => 4}); + } + else + { + # We're OK. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 2, key => "log_0400", variables => { file => $file }}); + } + } + } + } + # Verify DRBD devices now + validate_storage_drbd($anvil); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0367"}); return(0); } @@ -474,7 +522,54 @@ sub validate_storage_drbd { my ($anvil) = @_; - + # Now check storage. + my $server = $anvil->data->{environment}{OCF_RESKEY_name}; + my $source = "from_disk"; + foreach my $device_target (sort {$a cmp $b} keys %{$anvil->data->{server}{$server}{$source}{device}{disk}{target}}) + { + my $drbd_device = $anvil->data->{server}{$server}{$source}{device}{disk}{target}{$device_target}{path}; + my $drbd_resource = $anvil->data->{drbd}{'local'}{drbd_path}{$drbd_device}{resource}; + my $on_lv = $anvil->data->{drbd}{'local'}{drbd_path}{$drbd_device}{on}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + drbd_device => $drbd_device, + drbd_resource => $drbd_resource, + on_lv => $on_lv, + }}); + + # If the logical volume here here and active? + if ((not $on_lv) or (not exists $anvil->data->{lvm}{'local'}{lv}{$on_lv})) + { + # LV not found + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 0, priority => "err", key => "log_0374", variables => { drbd_device => $drbd_device, lv_path => $on_lv }}); + $anvil->nice_exit({exit_code => 5}); + } + elsif (not $anvil->data->{lvm}{'local'}{lv}{$on_lv}{active}) + { + # LV not active. If we're starting the server or we're the migration target, try to + # activate it. + my $active = $anvil->System->activate_lv({debug => 2, path => $on_lv}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { active => $active }}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "log_0413", variables => { lv_path => $on_lv }}); + + if (not $active) + { + # Boo :( + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 0, priority => "err", key => "log_0375", variables => { drbd_device => $drbd_device, lv_path => $on_lv }}); + $anvil->nice_exit({exit_code => 5}); + } + } + + # LV is good if I am still alive. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "log_0376", variables => { + drbd_device => $drbd_device, + lv_path => $on_lv, + }}); + + } + + print "I am: [".$anvil->data->{drbd}{host}{'local'}."]. my peer is: [".$anvil->data->{drbd}{host}{peer}."]\n"; + + die; return(0); } @@ -536,13 +631,14 @@ sub validate_name { my ($anvil) = @_; - # Is the name in the definition file what we expect? my $server = $anvil->data->{environment}{OCF_RESKEY_name}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server => $server, "server::${server}::from_disk::info::name" => $anvil->data->{server}{$server}{from_disk}{info}{name}, }}); - if ($server ne $anvil->data->{server}{$server}{from_disk}{info}{name}) + + # If we failed to read the XML, the server probably doesn't exist. + if (not $anvil->data->{server}{$server}{from_disk}{xml}) { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0403", variables => { server => $server, @@ -550,6 +646,16 @@ sub validate_name }}); $anvil->nice_exit({exit_code => 1}); } + + # Is the name in the definition file what we expect? + if ($server ne $anvil->data->{server}{$server}{from_disk}{info}{name}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0403", variables => { + server => $server, + name => $anvil->data->{server}{$server}{from_disk}{info}{name}, + }}); + $anvil->nice_exit({exit_code => 1}); + } $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0363"}); return(0); diff --git a/share/words.xml b/share/words.xml index a1e9450c..f9df9452 100644 --- a/share/words.xml +++ b/share/words.xml @@ -686,9 +686,9 @@ Output of: [#!variable!command!#] was; Recording the local connection details for the resource: [#!variable!resource!#] -> [#!variable!address!#:#!variable!port!#]. Recording the peer's connection details for the resource: [#!variable!resource!#] -> [#!variable!address!#:#!variable!port!#]. Checking that the DRBD device: [#!variable!device_path!#] is ready. - The server wants to use: [#!variable!device_path!#] as a hard drive, but we couldn't find the backing logical volume on this node. - The server wants to use: [#!variable!device_path!#] as a hard drive, but the backing logical volume: [#!variable!lv!#] doesn't exist on this node. - The server wants to use: [#!variable!device_path!#] as a hard drive, which is backed by the logical volume: [#!variable!lv!#]. Will check that is is ready. + The server wants to use: [#!variable!drbd_device!#] as a hard drive, but we couldn't find the backing logical volume: [#!variable!lv_path!#] on this node. + The server wants to use: [#!variable!drbd_device!#] as a hard drive, but the backing logical volume: [#!variable!lv_path!#] is inactive and an attempt to activate it failed. + The server wants to use: [#!variable!drbd_device!#] as a hard drive, which is backed by the logical volume: [#!variable!lv_path!#]. It is ready to use. The attempt to read the DRBD status returned a non-zero code: [#!variable!return_code!#]. The returned output (if any) was: [#!variable!status_json!#]. The DRBD resource for this server is not running yet. Bringing up the resource: [#!variable!resource!#] for the server's: [#!variable!device_path!#] disk. @@ -730,6 +730,7 @@ Failed to promote the DRBD resource: [#!variable!resource!#] primary. Expected a ==== The server: [#!variable!server!#] is already on this node in the state: [#!variable!state!#], aborting the migration request. + The logical volume: [#!variable!lv_path!#] is inactive. Attempting to activate it now. Test @@ -1014,6 +1015,7 @@ Failed to generate an RSA public key for the user: [#!variable!user!#]. The outp The download job with UUID: [#!variable!job_uuid!#] is not valid. The download job with UUID: [#!variable!job_uuid!#] is already being handled by another process. Something went wrong trying to download: [#!variable!packages!#]. The return code should have been '0'. but: [#!variable!return_code!#] was received. Is a package missing upstream? + A request to active the logical volume: [#!variable!path!#] was made, but that path doesn't exist or isn't a block device. Yes diff --git a/tools/anvil-download-file b/tools/anvil-download-file index 187cb61b..a0c38b8f 100755 --- a/tools/anvil-download-file +++ b/tools/anvil-download-file @@ -282,7 +282,7 @@ sub download_file while(<$file_handle>) { chomp; - my $line = $anvil->Words->clean_spaces({string => $_});; + my $line = $anvil->Words->clean_spaces({string => $_}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0017", variables => { line => $line }}); # Check for problems diff --git a/tools/fence_pacemaker b/tools/fence_pacemaker index af70c447..8040fe5a 100755 --- a/tools/fence_pacemaker +++ b/tools/fence_pacemaker @@ -556,7 +556,7 @@ sub kill_target my $start_time = time; my $end_time = $start_time + 300; my $fenced = 0; - to_log($conf, {message => "start_time: [$start_time], end_time: [$return_code]", 'line' => __LINE__, level => 2}); + to_log($conf, {message => "start_time: [$start_time], end_time: [$end_time]", 'line' => __LINE__, level => 2}); until ($fenced) { # This will exit