From f9ca6fb170c7495f46d17969918005e7d00e6c2e Mon Sep 17 00:00:00 2001 From: Digimer Date: Tue, 13 Dec 2022 16:28:59 -0500 Subject: [PATCH] * This adds the new anvil-version-change tool which anvil-daemon will call on startup to handle checks for changes made over releases/updates. * Added the new 'dr_link_note" column to the dr_links tables so that links can be marked as DELETED. Signed-off-by: Digimer --- Anvil/Tools.pm | 1 + Anvil/Tools/Database.pm | 343 ++++++++++++++++++++++++++++++ share/anvil.sql | 4 + share/words.xml | 4 + tools/Makefile.am | 1 + tools/anvil-daemon | 168 ++------------- tools/anvil-version-changes | 407 ++++++++++++++++++++++++++++++++++++ 7 files changed, 773 insertions(+), 155 deletions(-) create mode 100755 tools/anvil-version-changes diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 12d51b7a..8ecd3ea5 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1151,6 +1151,7 @@ sub _set_paths 'anvil-update-files' => "/usr/sbin/anvil-update-files", 'anvil-update-states' => "/usr/sbin/anvil-update-states", 'anvil-update-system' => "/usr/sbin/anvil-update-system", + 'anvil-version-changes' => "/usr/sbin/anvil-version-changes", augtool => "/usr/bin/augtool", base64 => "/usr/bin/base64", blockdev => "/usr/sbin/blockdev", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 7ccee9c1..eacccd7b 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -29,6 +29,7 @@ my $THIS_FILE = "Database.pm"; # get_alerts # get_anvils # get_bridges +# get_dr_links # get_fences # get_file_locations # get_files @@ -53,6 +54,7 @@ my $THIS_FILE = "Database.pm"; # insert_or_update_anvils # insert_or_update_bridges # insert_or_update_bonds +# insert_or_update_dr_links # insert_or_update_fences # insert_or_update_file_locations # insert_or_update_files @@ -2925,6 +2927,112 @@ WHERE } +=head2 get_dr_links + +This loads the known dr_link devices into the C<< anvil::data >> hash at: + +* dr_links::dr_link_uuid::::dr_link_host_uuid +* dr_links::dr_link_uuid::::dr_link_anvil_uuid +* dr_links::dr_link_uuid::::dr_link_note +* dr_links::dr_link_uuid::::modified_date + +To simplify finding links by host or Anvil! UUID, links to C<< dr_link_uuid >> are stored in these hashes; + +* dr_links::by_anvil_uuid::::dr_link_host_uuid::::dr_link_uuid +* dr_links::by_host_uuid::::dr_link_anvil_uuid::::dr_link_uuid + +If the hash was already populated, it is cleared before repopulating to ensure no stale data remains. + +B<>: Deleted links (ones where C<< dr_link_note >> is set to C<< DELETED >>) are ignored. See the C<< include_deleted >> parameter to include them. + +Parameters; + +=head3 include_deleted (Optional, default 0) + +If set to C<< 1 >>, deleted links are included when loading the data. When C<< 0 >> is set, the default, any dr_link agent with C<< dr_link_note >> set to C<< DELETED >> is ignored. + +=cut +sub get_dr_links +{ + 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 => "Database->get_dr_links()" }}); + + my $include_deleted = defined $parameter->{include_deleted} ? $parameter->{include_deleted} : 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + include_deleted => $include_deleted, + }}); + + if (exists $anvil->data->{dr_links}) + { + delete $anvil->data->{dr_links}; + } + + my $query = " +SELECT + dr_link_uuid, + dr_link_host_uuid, + dr_link_anvil_uuid, + dr_link_note, + modified_date +FROM + dr_links "; + if (not $include_deleted) + { + $query .= " +WHERE + dr_link_note != 'DELETED'"; + } + $query .= " +;"; + $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, + }}); + foreach my $row (@{$results}) + { + my $dr_link_uuid = $row->[0]; + my $dr_link_host_uuid = $row->[1]; + my $dr_link_anvil_uuid = $row->[2]; + my $dr_link_note = defined $row->[3] ? $row->[3] : ""; + my $modified_date = $row->[4]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + dr_link_uuid => $dr_link_uuid, + dr_link_host_uuid => $dr_link_host_uuid, + dr_link_anvil_uuid => $dr_link_anvil_uuid, + dr_link_note => $dr_link_note, + modified_date => $modified_date, + }}); + + # Record the data in the hash, too. + $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_host_uuid} = $dr_link_host_uuid; + $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_anvil_uuid} = $dr_link_anvil_uuid; + $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_note} = $dr_link_note; + $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{modified_date} = $modified_date; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "dr_links::dr_link_uuid::${dr_link_uuid}::dr_link_host_uuid" => $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_host_uuid}, + "dr_links::dr_link_uuid::${dr_link_uuid}::dr_link_anvil_uuid" => $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_anvil_uuid}, + "dr_links::dr_link_uuid::${dr_link_uuid}::dr_link_note" => $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_note}, + "dr_links::dr_link_uuid::${dr_link_uuid}::modified_date" => $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{modified_date}, + }}); + + $anvil->data->{dr_links}{by_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_host_uuid}{$dr_link_host_uuid}{dr_link_uuid} = $dr_link_uuid; + $anvil->data->{dr_links}{by_host_uuid}{$dr_link_host_uuid}{dr_link_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_uuid} = $dr_link_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "dr_links::by_anvil_uuid::${dr_link_anvil_uuid}::dr_link_host_uuid::${dr_link_host_uuid}::dr_link_uuid" => $anvil->data->{dr_links}{by_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_host_uuid}{$dr_link_host_uuid}{dr_link_uuid}, + "dr_links::by_host_uuid::${dr_link_host_uuid}::dr_link_anvil_uuid::${dr_link_anvil_uuid}::dr_link_uuid" => $anvil->data->{dr_links}{by_host_uuid}{$dr_link_host_uuid}{dr_link_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_uuid}, + }}); + } + + return(0); +} + + =head2 get_fences This loads the known fence devices into the C<< anvil::data >> hash at: @@ -7168,6 +7276,241 @@ WHERE } +=head2 insert_or_update_dr_links + +This updates (or inserts) a record in the 'dr_links' table. The C<< dr_link_uuid >> UUID will be returned. + +If there is an error, an empty string is returned. + +Parameters; + +=head3 uuid (optional) + +If set, only the corresponding database will be written to. + +=head3 file (optional) + +If set, this is the file name logged as the source of any INSERTs or UPDATEs. + +=head3 line (optional) + +If set, this is the file line number logged as the source of any INSERTs or UPDATEs. + +=head3 delete (optional) + +If this is set to C<< 1 >>, the record will be deleted. Specifiically, C<< dr_link_note >> is set to C<< DELETED >>. + +=head3 dr_link_uuid (optional, usually) + +This is the specific record to update. If C<< delete >> is set, then either this OR both C<< dr_link_host_uuid >> and C<< dr_link_anvil_uuid >> are required. + +=head3 dr_link_host_uuid (required, must by a host_type -> dr) + +This is the DR host's c<< hosts >> -> C<< host_uuid >>. The host_type is checked and only hosts with C<< host_type >> = C<< dr >> are allowed. + +=head3 dr_link_anvil_uuid (required) + +This is the C<< anvils >> -> C<< anvil_uuid >> that will be allowed to use this DR host. + +=head3 dr_link_note (optional) + +This is an optional note that can be used to store anything. If this is set to C<< DELETED >>, the DR to Anvil! link is severed. + +=cut +sub insert_or_update_dr_links +{ + 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 => "Database->insert_or_update_dr_links()" }}); + + my $uuid = defined $parameter->{uuid} ? $parameter->{uuid} : ""; + my $file = defined $parameter->{file} ? $parameter->{file} : ""; + my $line = defined $parameter->{line} ? $parameter->{line} : ""; + my $delete = defined $parameter->{'delete'} ? $parameter->{'delete'} : ""; + my $dr_link_uuid = defined $parameter->{dr_link_uuid} ? $parameter->{dr_link_uuid} : ""; + my $dr_link_host_uuid = defined $parameter->{dr_link_host_uuid} ? $parameter->{dr_link_host_uuid} : ""; + my $dr_link_anvil_uuid = defined $parameter->{dr_link_anvil_uuid} ? $parameter->{dr_link_anvil_uuid} : ""; + my $dr_link_note = defined $parameter->{dr_link_note} ? $parameter->{dr_link_note} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + uuid => $uuid, + file => $file, + line => $line, + dr_link_host_uuid => $dr_link_host_uuid, + dr_link_anvil_uuid => $dr_link_anvil_uuid, + dr_link_note => $dr_link_note, + }}); + + # Make sure that the UUIDs are valid. + $anvil->Database->get_hosts({deubg => $debug}); + $anvil->Database->get_dr_links({debug => $debug}); + + # If deleting, and if we have a valid 'dr_link_uuid' UUID, delete now and be done, + if ($delete) + { + # Do we have a valid dr_link_uuid? + if ($dr_link_uuid) + { + # + if (not exists $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}) + { + # Invalid, can't delete. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0397", variables => { uuid => $dr_link_uuid }}); + return(""); + } + + # If we're here, delete it if it isn't already. + if ($anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_note} ne "DELETED") + { + my $query = " +UPDATE + dr_links +SET + dr_link_node = 'DELETED', + modified_date = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)." +WHERE + dr_link_uuid = ".$anvil->Database->quote($dr_link_uuid)." +;"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Database->write({uuid => $uuid, query => $query, source => $file ? $file." -> ".$THIS_FILE : $THIS_FILE, line => $line ? $line." -> ".__LINE__ : __LINE__}); + } + return($dr_link_uuid) + } + } + + # Still here? Make sure we've got sane parameters + if (not $dr_link_host_uuid) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Database->insert_or_update_dr_links()", parameter => "dr_link_host_uuid" }}); + return(""); + } + if (not $dr_link_anvil_uuid) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Database->insert_or_update_dr_links()", parameter => "dr_link_anvil_uuid" }}); + return(""); + } + + # We've got UUIDs, but are they valid? + if (not exists $anvil->data->{hosts}{host_uuid}{$dr_link_host_uuid}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0394", variables => { uuid => $dr_link_host_uuid }}); + return(""); + } + elsif ($anvil->data->{hosts}{host_uuid}{$dr_link_host_uuid}{host_type} ne "dr") + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0395", variables => { + uuid => $dr_link_host_uuid, + name => $anvil->data->{hosts}{host_uuid}{$dr_link_host_uuid}{host_name}, + type => $anvil->data->{hosts}{host_uuid}{$dr_link_host_uuid}{host_type}, + }}); + return(""); + } + if (not exists $anvil->data->{anvils}{anvil_uuid}{$dr_link_anvil_uuid}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0396", variables => { uuid => $dr_link_anvil_uuid }}); + return(""); + } + + my $dr_host_name = $anvil->data->{hosts}{host_uuid}{$dr_link_host_uuid}{host_name}; + my $anvil_name = $anvil->data->{anvils}{anvil_uuid}{$dr_link_anvil_uuid}{anvil_name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + dr_host_name => $dr_host_name, + anvil_name => $anvil_name, + }}); + + # Get the dr_link_uuid, if one exists. + if (not $dr_link_uuid) + { + if ((exists $anvil->data->{dr_links}{by_anvil_uuid}{$dr_link_anvil_uuid}) && + (exists $anvil->data->{dr_links}{by_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_host_uuid}{$dr_link_host_uuid})) + { + $dr_link_uuid = $anvil->data->{dr_links}{by_anvil_uuid}{$dr_link_anvil_uuid}{dr_link_host_uuid}{$dr_link_host_uuid}{dr_link_uuid}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { dr_link_uuid => $dr_link_uuid }}); + } + } + + # If we're deleting and we found a dr_link_uuid, DELETE now and return. + if ($delete) + { + if (($dr_link_uuid) && ($anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_note} ne "DELETED")) + { + my $query = " +UPDATE + dr_links +SET + dr_link_node = 'DELETED', + modified_date = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)." +WHERE + dr_link_uuid = ".$anvil->Database->quote($dr_link_uuid)." +;"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Database->write({uuid => $uuid, query => $query, source => $file ? $file." -> ".$THIS_FILE : $THIS_FILE, line => $line ? $line." -> ".__LINE__ : __LINE__}); + } + return($dr_link_uuid) + } + + # Do we have a UUID? + if ($dr_link_uuid) + { + # Yup. Has something changed? + my $old_dr_link_anvil_uuid = $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_anvil_uuid}; + my $old_dr_link_host_uuid = $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_host_uuid}; + my $old_dr_link_note = $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_note}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + old_dr_link_anvil_uuid => $old_dr_link_anvil_uuid, + old_dr_link_host_uuid => $old_dr_link_host_uuid, + old_dr_link_note => $old_dr_link_note, + }}); + if (($old_dr_link_anvil_uuid ne $dr_link_anvil_uuid) or + ($old_dr_link_host_uuid ne $dr_link_host_uuid) or + ($old_dr_link_note ne $dr_link_note)) + { + # Clear the stop data. + my $query = " +UPDATE + dr_links +SET + dr_link_host_uuid = ".$anvil->Database->quote($dr_link_host_uuid).", + dr_link_anvil_uuid = ".$anvil->Database->quote($dr_link_anvil_uuid).", + dr_link_note = ".$anvil->Database->quote($dr_link_note).", + modified_date = ".$anvil->Database->quote($anvil->Database->refresh_timestamp)." +WHERE + dr_link_uuid = ".$anvil->Database->quote($dr_link_uuid)." +;"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Database->write({uuid => $uuid, query => $query, source => $file ? $file." -> ".$THIS_FILE : $THIS_FILE, line => $line ? $line." -> ".__LINE__ : __LINE__}); + } + } + else + { + # No, INSERT. + $dr_link_uuid = $anvil->Get->uuid(); + my $query = " +INSERT INTO + dr_links +( + dr_link_uuid, + dr_link_host_uuid, + dr_link_anvil_uuid, + dr_link_note, + modified_date +) VALUES ( + ".$anvil->Database->quote($dr_link_uuid).", + ".$anvil->Database->quote($dr_link_host_uuid).", + ".$anvil->Database->quote($dr_link_anvil_uuid).", + ".$anvil->Database->quote($dr_link_note).", + ".$anvil->Database->quote($anvil->Database->refresh_timestamp)." +); +"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Database->write({uuid => $uuid, query => $query, source => $file ? $file." -> ".$THIS_FILE : $THIS_FILE, line => $line ? $line." -> ".__LINE__ : __LINE__}); + } + + return($dr_link_uuid); +} + + =head2 insert_or_update_fences This updates (or inserts) a record in the 'fences' table. The C<< fence_uuid >> UUID will be returned. diff --git a/share/anvil.sql b/share/anvil.sql index 22fa6904..cdcbb03a 100644 --- a/share/anvil.sql +++ b/share/anvil.sql @@ -414,6 +414,7 @@ CREATE TABLE dr_links ( dr_link_uuid uuid not null primary key, dr_link_host_uuid uuid not null, dr_link_anvil_uuid uuid not null, + dr_link_note text, -- Set to 'DELETE' when no longer used. modified_date timestamp with time zone not null, FOREIGN KEY(dr_link_host_uuid) REFERENCES hosts(host_uuid), @@ -426,6 +427,7 @@ CREATE TABLE history.dr_links ( dr_link_uuid uuid, dr_link_host_uuid uuid, dr_link_anvil_uuid uuid, + dr_link_note text, modified_date timestamp with time zone not null ); ALTER TABLE history.dr_links OWNER TO admin; @@ -440,11 +442,13 @@ BEGIN (dr_link_uuid, dr_link_host_uuid, dr_link_anvil_uuid, + dr_link_note, modified_date) VALUES (history_dr_links.dr_link_uuid, history_dr_links.dr_link_host_uuid, history_dr_links.dr_link_anvil_uuid, + history_dr_links.dr_link_note, history_dr_links.modified_date); RETURN NULL; END; diff --git a/share/words.xml b/share/words.xml index 91555093..df97e118 100644 --- a/share/words.xml +++ b/share/words.xml @@ -566,6 +566,10 @@ The definition data passed in was: * 2 or "warning" * 3 or "notice" * 4 or "info" + [ Error ] - The host UUID: [#!variable!uuid!#] was not found. + [ Error ] - The host UUID: [#!variable!uuid!#], with the host name: [#!variable!name!#] is of host type: [#!variable!type!#]. This must be a type 'dr'. + [ Error ] - The Anvil! UUID: [#!variable!uuid!#] was not found. + [ Error ] - The DR link UUID: [#!variable!uuid!#] was not found. diff --git a/tools/Makefile.am b/tools/Makefile.am index edb8ea28..d2733ca1 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -43,6 +43,7 @@ dist_sbin_SCRIPTS = \ anvil-update-issue \ anvil-update-states \ anvil-update-system \ + anvil-version-changes \ anvil-watch-bonds \ scancore \ striker-auto-initialize-all \ diff --git a/tools/anvil-daemon b/tools/anvil-daemon index c4e5487c..c24a4410 100755 --- a/tools/anvil-daemon +++ b/tools/anvil-daemon @@ -1294,6 +1294,19 @@ sub handle_special_cases { my ($anvil) = @_; + # Thsi is now handled by 'anvil-version-changes' + my $shell_call = $anvil->data->{path}{exe}{'anvil-version-changes'}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); + + my ($states_output, $return_code) = $anvil->System->call({debug => 3, shell_call => $shell_call, source => $THIS_FILE, line => __LINE__}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + states_output => $states_output, + return_code => $return_code, + }}); + + return(0); + + my $host_type = $anvil->Get->host_type(); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host_type => $host_type }}); if ($host_type ne "striker") @@ -1323,162 +1336,7 @@ sub handle_special_cases if ($host_type eq "striker") { - # This converts the old/broken 'notifications' tables with the more appropriately named 'alert-override' - if (1) - { - foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{cache}{database_handle}}) - { - my $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = 'anvil' AND table_name = 'notifications';"; - $anvil->Log->variables({source => $THIS_FILE, uuid => $uuid, line => __LINE__, level => 2, list => { query => $query }}); - - my $count = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0]; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }}); - - if ($count) - { - my $queries = []; - push @{$queries}, "DROP FUNCTION history_notifications() CASCADE;"; - push @{$queries}, "DROP TABLE history.notifications;"; - push @{$queries}, "DROP TABLE public.notifications;"; - push @{$queries}, q|CREATE TABLE alert_overrides ( - alert_override_uuid uuid not null primary key, - alert_override_recipient_uuid uuid not null, -- The recipient we're linking. - alert_override_host_uuid uuid not null, -- This host_uuid of the referenced machine - alert_override_alert_level integer not null, -- This is the alert level (at or above) that this user wants alerts from. If set to '-1', the record is deleted. - modified_date timestamp with time zone not null, - - FOREIGN KEY(alert_override_host_uuid) REFERENCES hosts(host_uuid), - FOREIGN KEY(alert_override_recipient_uuid) REFERENCES recipients(recipient_uuid) -); -ALTER TABLE alert_overrides OWNER TO admin; - -CREATE TABLE history.alert_overrides ( - history_id bigserial, - alert_override_uuid uuid, - alert_override_recipient_uuid uuid, - alert_override_host_uuid uuid, - alert_override_alert_level integer, - modified_date timestamp with time zone not null -); -ALTER TABLE history.alert_overrides OWNER TO admin; - -CREATE FUNCTION history_alert_overrides() RETURNS trigger -AS $$ -DECLARE - history_alert_overrides RECORD; -BEGIN - SELECT INTO history_alert_overrides * FROM alert_overrides WHERE alert_override_uuid = new.alert_override_uuid; - INSERT INTO history.alert_overrides - (alert_override_uuid, - alert_override_recipient_uuid, - alert_override_host_uuid, - alert_override_alert_level, - modified_date) - VALUES - (history_alert_overrides.alert_override_uuid, - history_alert_overrides.alert_override_recipient_uuid, - history_alert_overrides.alert_override_host_uuid, - history_alert_overrides.alert_override_alert_level, - history_alert_overrides.modified_date); - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -ALTER FUNCTION history_alert_overrides() OWNER TO admin; - -CREATE TRIGGER trigger_alert_overrides - AFTER INSERT OR UPDATE ON alert_overrides - FOR EACH ROW EXECUTE PROCEDURE history_alert_overrides(); -|; - foreach my $query (@{$queries}) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); - } - $anvil->Database->write({debug => 2, uuid => $uuid, query => $queries, source => $THIS_FILE, line => __LINE__}); - } - } - } - # This checks to make sure that the 'audits' table exists (added late into M3.0 pre-release) - if (0) - { - foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{cache}{database_handle}}) - { - my $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = 'anvil' AND table_name = 'audits';"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); - - my $count = $anvil->Database->query({query => $query, uuid => $uuid, source => $THIS_FILE, line => __LINE__})->[0]->[0]; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }}); - - if (not $count) - { - # Add the table. - my $query = q| -CREATE TABLE audits ( - audit_uuid uuid primary key, - audit_user_uuid uuid not null, -- This is the users -> user_uuid the audit is tracking - audit_details text not null, -- This is the information explaining the action being audited. - modified_date timestamp with time zone not null, - - FOREIGN KEY(audit_user_uuid) REFERENCES users(user_uuid) -); -ALTER TABLE audits OWNER TO admin; - -CREATE TABLE history.audits ( - history_id bigserial, - audit_uuid uuid, - audit_user_uuid uuid, - audit_details text, - modified_date timestamp with time zone not null -); -ALTER TABLE history.audits OWNER TO admin; - -CREATE FUNCTION history_audits() RETURNS trigger -AS $$ -DECLARE - history_audits RECORD; -BEGIN - SELECT INTO history_audits * FROM audits WHERE audit_uuid = new.audit_uuid; - INSERT INTO history.audits - (audit_uuid, - audit_user_uuid, - audit_details, - modified_date) - VALUES - (history_audit.audit_uuid, - history_audit.audit_user_uuid, - history_audit.audit_details, - history_audit.modified_date); - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -ALTER FUNCTION history_audits() OWNER TO admin; - -CREATE TRIGGER trigger_audits - AFTER INSERT OR UPDATE ON audits - FOR EACH ROW EXECUTE PROCEDURE history_audits(); -|; - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); - $anvil->Database->write({debug => 2, uuid => $uuid, query => $query, source => $THIS_FILE, line => __LINE__}); - } - } - } - } - - ### TODO: Remove these later. This is here to clean up how we used to handle db_in_use and lock_request flags. - if (1) - { - # Broadly clear all states that are '0' now. - my $queries = []; - push @{$queries}, "DELETE FROM states WHERE state_name LIKE 'db_in_use::%' AND state_note != '1';"; - push @{$queries}, "DELETE FROM history.variables WHERE variable_name = 'lock_request';"; - push @{$queries}, "DELETE FROM variables WHERE variable_name = 'lock_request';"; - foreach my $query (@{$queries}) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); - } - $anvil->Database->write({debug => 2, query => $queries, source => $THIS_FILE, line => __LINE__}); } return(0); diff --git a/tools/anvil-version-changes b/tools/anvil-version-changes new file mode 100755 index 00000000..71a4def6 --- /dev/null +++ b/tools/anvil-version-changes @@ -0,0 +1,407 @@ +#!/usr/bin/perl +# +# This does checks for changes that are needed because of version changes. Over time, checks here can be +# removed. + +use strict; +use warnings; +use Anvil::Tools; +use Data::Dumper; +use Text::Diff; + +$| = 1; + +my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0]; +my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0]; +if (($running_directory =~ /^\./) && ($ENV{PWD})) +{ + $running_directory =~ s/^\./$ENV{PWD}/; +} + +my $anvil = Anvil::Tools->new(); + +# Get a list of all interfaces with IP addresses. +$anvil->Get->switches({list => [ +]}); +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}}); + +$anvil->Database->connect(); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132" }); +if (not $anvil->data->{sys}{database}{connections}) +{ + # No databases, exit. + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003" }); + $anvil->nice_exit({ exit_code => 1 }); +} + +my $host_type = $anvil->Get->host_type(); +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { host_type => $host_type }}); + +if ($host_type eq "striker") +{ + striker_checks($anvil); +} +elsif ($host_type eq "node") +{ + node_checks($anvil); +} +elsif ($host_type eq "dr") +{ + dr_checks($anvil); +} + + +$anvil->nice_exit({exit_code => 0}); + + +############################################################################################################# +# Functions # +############################################################################################################# + +# Check for things that need to happen on Striker dashboards. +sub striker_checks +{ + my ($anvil) = @_; + + # This converts the old/broken 'notifications' tables with the more appropriately named 'alert-override' + update_notifications($anvil); + + ### NOTE: Disabled until review complete + # This checks to make sure that the 'audits' table exists (added late into M3.0 pre-release) + #update_audits($anvil); + + ### NOTE: Disabled until review complete + # This checks to make sure that the new dr_links table exists, and that existing anvil_dr1_host_uuid + # entries are copied. + update_dr_links($anvil); + + ### TODO: Remove these later. This is here to clean up how we used to handle db_in_use and lock_request flags. + if (1) + { + # Broadly clear all states that are '0' now. + my $queries = []; + push @{$queries}, "DELETE FROM states WHERE state_name LIKE 'db_in_use::%' AND state_note != '1';"; + push @{$queries}, "DELETE FROM history.variables WHERE variable_name = 'lock_request';"; + push @{$queries}, "DELETE FROM variables WHERE variable_name = 'lock_request';"; + foreach my $query (@{$queries}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); + } + $anvil->Database->write({debug => 2, query => $queries, source => $THIS_FILE, line => __LINE__}); + } + + return(0); +} + +# Check for things that need to happen on Anvil! Subnodes. +sub node_checks +{ + my ($anvil) = @_; + + # RHBZ #1961562 - https://bugzilla.redhat.com/show_bug.cgi?id=1961562#c16 + handle_bz1961562($anvil); + + # Make sure DRBD compiled after a kernel upgrade. + $anvil->DRBD->_initialize_kmod({debug => 2}); + + return(0); +} + +# Check for things that need to happen on DR hosts. +sub dr_checks +{ + my ($anvil) = @_; + + # RHBZ #1961562 - https://bugzilla.redhat.com/show_bug.cgi?id=1961562#c16 + handle_bz1961562($anvil); + + # Make sure DRBD compiled after a kernel upgrade. + $anvil->DRBD->_initialize_kmod({debug => 2}); + + return(0); +} + +# +sub update_dr_links +{ + my ($anvil) = @_; + + foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{cache}{database_handle}}) + { + my $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = 'anvil' AND table_name = 'dr_links';"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + + my $count = $anvil->Database->query({query => $query, uuid => $uuid, source => $THIS_FILE, line => __LINE__})->[0]->[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }}); + + if (not $count) + { + # Add the table. + my $query = q| +CREATE TABLE dr_links ( + dr_link_uuid uuid not null primary key, + dr_link_host_uuid uuid not null, + dr_link_anvil_uuid uuid not null, + dr_link_note text, -- Set to 'DELETE' when no longer used. + modified_date timestamp with time zone not null, + + FOREIGN KEY(dr_link_host_uuid) REFERENCES hosts(host_uuid), + FOREIGN KEY(dr_link_anvil_uuid) REFERENCES anvils(anvil_uuid) +); +ALTER TABLE dr_links OWNER TO admin; + +CREATE TABLE history.dr_links ( + history_id bigserial, + dr_link_uuid uuid, + dr_link_host_uuid uuid, + dr_link_anvil_uuid uuid, + dr_link_note text, + modified_date timestamp with time zone not null +); +ALTER TABLE history.dr_links OWNER TO admin; + +CREATE FUNCTION history_dr_links() RETURNS trigger +AS $$ +DECLARE + history_dr_links RECORD; +BEGIN + SELECT INTO history_dr_links * FROM dr_links WHERE dr_link_uuid = new.dr_link_uuid; + INSERT INTO history.dr_links + (dr_link_uuid, + dr_link_host_uuid, + dr_link_anvil_uuid, + dr_link_note, + modified_date) + VALUES + (history_dr_links.dr_link_uuid, + history_dr_links.dr_link_host_uuid, + history_dr_links.dr_link_anvil_uuid, + history_dr_links.dr_link_note, + history_dr_links.modified_date); + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; +ALTER FUNCTION history_dr_links() OWNER TO admin; + +CREATE TRIGGER trigger_dr_links + AFTER INSERT OR UPDATE ON dr_links + FOR EACH ROW EXECUTE PROCEDURE history_dr_links(); +|; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); + $anvil->Database->write({debug => 2, uuid => $uuid, query => $query, source => $THIS_FILE, line => __LINE__}); + } + } + + # Now make sure that existing DR entries are copied here. + $anvil->Database->get_hosts({deubg => 2}); + $anvil->Database->get_dr_links({debug => 2}); + + foreach my $anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}}) + { + my $anvil_uuid = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_uuid}; + my $anvil_dr1_host_uuid = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_dr1_host_uuid}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "s1:anvil_name" => $anvil_name, + "s2:anvil_uuid" => $anvil_uuid, + "s3:anvil_dr1_host_uuid" => $anvil_dr1_host_uuid, + }}); + if ($anvil_dr1_host_uuid) + { + my $dr1_host_name = $anvil->data->{hosts}{host_uuid}{$anvil_dr1_host_uuid}{short_host_name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { dr1_host_name => $dr1_host_name }}); + + if ((not exists $anvil->data->{dr_links}{by_anvil_uuid}{$anvil_uuid}) or + (not exists $anvil->data->{dr_links}{by_anvil_uuid}{$anvil_uuid}{dr_link_host_uuid}{$anvil_dr1_host_uuid}) or + (not exists $anvil->data->{dr_links}{by_anvil_uuid}{$anvil_uuid}{dr_link_host_uuid}{$anvil_dr1_host_uuid}{dr_link_uuid})) + { + # Add it. + my $dr_link_uuid = $anvil->Database->insert_or_update_dr_links({ + debug => 2, + dr_link_anvil_uuid => $anvil_uuid, + dr_link_host_uuid => $anvil_dr1_host_uuid, + dr_link_note => "auto_generated", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { dr1_host_name => $dr1_host_name }}); + } + } + } + + return(0); +} + +# This checks to make sure that the 'audits' table exists (added late into M3.0 pre-release) +sub update_audits +{ + my ($anvil) = @_; + + foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{cache}{database_handle}}) + { + my $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = 'anvil' AND table_name = 'audits';"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + + my $count = $anvil->Database->query({query => $query, uuid => $uuid, source => $THIS_FILE, line => __LINE__})->[0]->[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }}); + + if (not $count) + { + # Add the table. + my $query = q| +CREATE TABLE audits ( + audit_uuid uuid primary key, + audit_user_uuid uuid not null, -- This is the users -> user_uuid the audit is tracking + audit_details text not null, -- This is the information explaining the action being audited. + modified_date timestamp with time zone not null, + + FOREIGN KEY(audit_user_uuid) REFERENCES users(user_uuid) +); +ALTER TABLE audits OWNER TO admin; + +CREATE TABLE history.audits ( + history_id bigserial, + audit_uuid uuid, + audit_user_uuid uuid, + audit_details text, + modified_date timestamp with time zone not null +); +ALTER TABLE history.audits OWNER TO admin; + +CREATE FUNCTION history_audits() RETURNS trigger +AS $$ +DECLARE + history_audits RECORD; +BEGIN + SELECT INTO history_audits * FROM audits WHERE audit_uuid = new.audit_uuid; + INSERT INTO history.audits + (audit_uuid, + audit_user_uuid, + audit_details, + modified_date) + VALUES + (history_audit.audit_uuid, + history_audit.audit_user_uuid, + history_audit.audit_details, + history_audit.modified_date); + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; +ALTER FUNCTION history_audits() OWNER TO admin; + +CREATE TRIGGER trigger_audits + AFTER INSERT OR UPDATE ON audits + FOR EACH ROW EXECUTE PROCEDURE history_audits(); +|; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); + $anvil->Database->write({debug => 2, uuid => $uuid, query => $query, source => $THIS_FILE, line => __LINE__}); + } + } + + return(0); +} + +# This converts the old/broken 'notifications' tables with the more appropriately named 'alert-override' +sub update_notifications +{ + my ($anvil) = @_; + + foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{cache}{database_handle}}) + { + my $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_catalog = 'anvil' AND table_name = 'notifications';"; + $anvil->Log->variables({source => $THIS_FILE, uuid => $uuid, line => __LINE__, level => 2, list => { query => $query }}); + + my $count = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }}); + + if ($count) + { + my $queries = []; + push @{$queries}, "DROP FUNCTION history_notifications() CASCADE;"; + push @{$queries}, "DROP TABLE history.notifications;"; + push @{$queries}, "DROP TABLE public.notifications;"; + push @{$queries}, q|CREATE TABLE alert_overrides ( + alert_override_uuid uuid not null primary key, + alert_override_recipient_uuid uuid not null, -- The recipient we're linking. + alert_override_host_uuid uuid not null, -- This host_uuid of the referenced machine + alert_override_alert_level integer not null, -- This is the alert level (at or above) that this user wants alerts from. If set to '-1', the record is deleted. + modified_date timestamp with time zone not null, + + FOREIGN KEY(alert_override_host_uuid) REFERENCES hosts(host_uuid), + FOREIGN KEY(alert_override_recipient_uuid) REFERENCES recipients(recipient_uuid) +); +ALTER TABLE alert_overrides OWNER TO admin; + +CREATE TABLE history.alert_overrides ( + history_id bigserial, + alert_override_uuid uuid, + alert_override_recipient_uuid uuid, + alert_override_host_uuid uuid, + alert_override_alert_level integer, + modified_date timestamp with time zone not null +); +ALTER TABLE history.alert_overrides OWNER TO admin; + +CREATE FUNCTION history_alert_overrides() RETURNS trigger +AS $$ +DECLARE + history_alert_overrides RECORD; +BEGIN + SELECT INTO history_alert_overrides * FROM alert_overrides WHERE alert_override_uuid = new.alert_override_uuid; + INSERT INTO history.alert_overrides + (alert_override_uuid, + alert_override_recipient_uuid, + alert_override_host_uuid, + alert_override_alert_level, + modified_date) + VALUES + (history_alert_overrides.alert_override_uuid, + history_alert_overrides.alert_override_recipient_uuid, + history_alert_overrides.alert_override_host_uuid, + history_alert_overrides.alert_override_alert_level, + history_alert_overrides.modified_date); + RETURN NULL; +END; +$$ +LANGUAGE plpgsql; +ALTER FUNCTION history_alert_overrides() OWNER TO admin; + +CREATE TRIGGER trigger_alert_overrides + AFTER INSERT OR UPDATE ON alert_overrides + FOR EACH ROW EXECUTE PROCEDURE history_alert_overrides(); +|; + foreach my $query (@{$queries}) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0124", variables => { query => $query }}); + } + $anvil->Database->write({debug => 2, uuid => $uuid, query => $queries, source => $THIS_FILE, line => __LINE__}); + } + } + + return(0); +} + +sub handle_bz1961562 +{ + my ($anvil) = @_; + + ### TODO: Test that this is fixed. The bug is now ERRATA + # RHBZ #1961562 - https://bugzilla.redhat.com/show_bug.cgi?id=1961562#c16 + # We're a node or DR host. We need to touch this file. + my $work_around_file = "/etc/qemu/firmware/50-edk2-ovmf-cc.json"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { work_around_file => $work_around_file }}); + if (not -e $work_around_file) + { + $anvil->Storage->write_file({ + debug => 2, + file => $work_around_file, + body => "", + overwrite => 0, + backup => 0, + mode => "0644", + user => "root", + group => "root", + }); + } + + return(0); +}