* Added a row-count check when deciding if a DB resync is needed.

* Updated the Database module to not sort or reorder the 'core_tables' array, and reordered them in the hash they're declared in.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 6 years ago
parent e67828b6c6
commit bfc2204352
  1. 23
      Anvil/Tools.pm
  2. 80
      Anvil/Tools/Database.pm
  3. 2
      Anvil/Tools/Remote.pm
  4. 93
      share/anvil.sql
  5. 18
      share/words.xml
  6. 2
      tools/anvil-daemon

@ -748,22 +748,25 @@ sub _set_defaults
trigger => 100000,
},
connections => 0,
# grep 'CREATE TABLE' tools/anvil.sql | grep -v history. | awk '{print $3}' | sort
### WARNING: The order the tables are resync'ed is important! Any table that has a
### foreign key needs to resync *AFTER* the tables with the primary keys.
# NOTE: Check that this list is complete with;
# grep 'CREATE TABLE' shared/anvil.sql | grep -v history. | awk '{print $3}'
core_tables => [
"hosts", # Always has to be first.
"users",
"host_variable",
"sessions", # Has to come after users and hosts
"alerts",
"alert_sent",
"variables",
"jobs",
"network_interfaces",
"bonds",
"bridges",
"hosts",
"host_variable",
"ip_addresses",
"jobs",
"network_interfaces",
"sessions",
"states",
"updated",
"users",
"variables",
"alert_sent",
"states",
],
local_lock_active => 0,
local_uuid => "",

@ -664,6 +664,13 @@ sub connect
db_uuid => $db_uuid,
}});
# If I wasn't passed an array reference of tables, use the core tables.
if (not $tables)
{
$tables = $anvil->data->{sys}{database}{core_tables};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { tables => $tables }});
}
my $start_time = [gettimeofday];
#print "Start time: [".$start_time->[0].".".$start_time->[1]."]\n";
@ -1132,6 +1139,7 @@ sub connect
# }
}
# Make sure my host UUID is valud
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::host_uuid" => $anvil->data->{sys}{host_uuid} }});
if ($anvil->data->{sys}{host_uuid} !~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/)
{
@ -5674,8 +5682,8 @@ sub _find_behind_databases
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->_find_behind_databases()" }});
my $source = $parameter->{source} ? $parameter->{source} : "";
my $tables = $parameter->{tables} ? $parameter->{tables} : "";
my $source = defined $parameter->{source} ? $parameter->{source} : "";
my $tables = defined $parameter->{tables} ? $parameter->{tables} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
source => $source,
tables => $tables,
@ -5688,32 +5696,29 @@ sub _find_behind_databases
return("!!error!!");
}
# Make sure I've got an array of tables.
if (ref($tables) ne "ARRAY")
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0218", variables => { name => "tables", value => $tables }});
return("!!error!!");
}
# Now, look through the core tables, plus any tables the user might have passed, for differing
# 'modified_date' entries, or no entries in one DB with entries in the other (as can happen with a
# newly setup db).
$anvil->data->{sys}{database}{check_tables} = [];
# The 'hosts' table always has to be the first table sync'ed as just about everything else references it.
push @{$anvil->data->{sys}{database}{check_tables}}, "hosts";
foreach my $table (@{$anvil->data->{sys}{database}{core_tables}})
{
next if $table eq "hosts";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { table => $table }});
push @{$anvil->data->{sys}{database}{check_tables}}, $table;
}
if (ref($tables) eq "ARRAY")
{
### NOTE: Don't sort this! Tables need to be resynced in a specific order!
# Loop through and resync the tables.
foreach my $table (@{$tables})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { table => $table }});
# Record the table in 'sys::database::check_tables' array for later use in archive and
# resync methods.
push @{$anvil->data->{sys}{database}{check_tables}}, $table;
}
}
# Preset all tables to have an initial 'modified_date' of 0.
foreach my $table (sort {$a cmp $b} @{$anvil->data->{sys}{database}{check_tables}})
{
# Preset all tables to have an initial 'modified_date' and 'row_count' of 0.
$anvil->data->{sys}{database}{table}{$table}{last_updated} = 0;
$anvil->data->{sys}{database}{table}{$table}{row_count} = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::table::${table}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{last_updated},
}});
@ -5770,12 +5775,12 @@ sub _find_behind_databases
SELECT
round(extract(epoch from modified_date))
FROM
$schema.$table ";
".$schema.".".$table." ";
if ($host_column)
{
$query .= "
WHERE
$host_column = ".$anvil->data->{sys}{database}{use_handle}->quote($anvil->data->{sys}{host_uuid}) ;
".$host_column." = ".$anvil->data->{sys}{database}{use_handle}->quote($anvil->data->{sys}{host_uuid}) ;
}
$query .= "
ORDER BY
@ -5786,22 +5791,40 @@ ORDER BY
query => $query,
}});
my $last_updated = $anvil->Database->query({uuid => $uuid, query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
# Get the count of columns as well as the most recent one.
my $results = $anvil->Database->query({uuid => $uuid, query => $query, source => $THIS_FILE, line => __LINE__});
my $row_count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
row_count => $row_count,
}});
my $last_updated = $results->[0]->[0];
$last_updated = 0 if not defined $last_updated;
# Record this table's last modified_date for later comparison. We'll also
# record the schema and host column, if found, to save looking the same thing
# up later if we do need a resync.
$anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated} = $last_updated;
$anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count} = $row_count;
$anvil->data->{sys}{database}{table}{$table}{schema} = $schema;
$anvil->data->{sys}{database}{table}{$table}{host_column} = $host_column;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::table::${table}::id::${uuid}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated},
"sys::database::table::${table}::id::${uuid}::row_count" => $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count},
"sys::database::table::${table}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{last_updated},
"sys::database::table::${table}::schema" => $anvil->data->{sys}{database}{table}{$table}{schema},
"sys::database::table::${table}::host_column" => $anvil->data->{sys}{database}{table}{$table}{host_column},
}});
if ($anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count} > $anvil->data->{sys}{database}{table}{$table}{row_count})
{
$anvil->data->{sys}{database}{table}{$table}{row_count} = $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::table::${table}::row_count" => $anvil->data->{sys}{database}{table}{$table}{row_count},
}});
}
if ($anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated} > $anvil->data->{sys}{database}{table}{$table}{last_updated})
{
$anvil->data->{sys}{database}{table}{$table}{last_updated} = $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated};
@ -5819,17 +5842,28 @@ ORDER BY
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::table::${table}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{last_updated},
"sys::database::table::${table}::row_count" => $anvil->data->{sys}{database}{table}{$table}{row_count},
}});
foreach my $uuid (sort {$a cmp $b} keys %{$anvil->data->{sys}{database}{table}{$table}{id}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::table::${table}::id::${uuid}::last_updated" => $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated},
"sys::database::table::${table}::id::${uuid}::row_count" => $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count},
}});
if ($anvil->data->{sys}{database}{table}{$table}{last_updated} > $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{last_updated})
{
# Resync needed.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0106", variables => { uuid => $uuid }});
# Mark it as behind.
$anvil->Database->_mark_database_as_behind({debug => $debug, uuid => $uuid});
last;
}
elsif ($anvil->data->{sys}{database}{table}{$table}{row_count} > $anvil->data->{sys}{database}{table}{$table}{id}{$uuid}{row_count})
{
# Resync needed.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "log_0219", variables => { uuid => $uuid }});
# Mark it as behind.
$anvil->Database->_mark_database_as_behind({debug => $debug, uuid => $uuid});
last;
@ -5838,6 +5872,10 @@ ORDER BY
last if $anvil->data->{sys}{database}{resync_needed};
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"sys::database::resync_needed" => $anvil->data->{sys}{database}{resync_needed},
}});
return(0);
}

@ -412,7 +412,7 @@ sub call
### 'sys::timeout::{all|host} = x'
# my $start_time = [gettimeofday];
$ssh_fh = Net::SSH2->new(timeout => 1000);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
ssh_fh => $ssh_fh,
target => $target,
port => $port,

@ -38,6 +38,53 @@ BEGIN
END
$$;
-- This stores information about the host machine. This is the master table that everything will be linked
-- to.
CREATE TABLE hosts (
host_uuid uuid not null primary key, -- This is the single most important record in Anvil!. Everything links back to here.
host_name text not null,
host_type text not null, -- Either 'node' or 'dashboard'.
modified_date timestamp with time zone not null
);
ALTER TABLE hosts OWNER TO #!variable!user!#;
CREATE TABLE history.hosts (
history_id bigserial,
host_uuid uuid not null,
host_name text not null,
host_type text not null,
modified_date timestamp with time zone not null
);
ALTER TABLE history.hosts OWNER TO #!variable!user!#;
CREATE FUNCTION history_hosts() RETURNS trigger
AS $$
DECLARE
history_hosts RECORD;
BEGIN
SELECT INTO history_hosts * FROM hosts WHERE host_uuid = new.host_uuid;
INSERT INTO history.hosts
(host_uuid,
host_name,
host_type,
modified_date)
VALUES
(history_hosts.host_uuid,
history_hosts.host_name,
history_hosts.host_type,
history_hosts.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_hosts() OWNER TO #!variable!user!#;
CREATE TRIGGER trigger_hosts
AFTER INSERT OR UPDATE ON hosts
FOR EACH ROW EXECUTE PROCEDURE history_hosts();
-- This stores information about users.
-- Note that is all permissions are left false, the user can still interact with the Anvil! doing safe things, like changing optical media, perform migrations, start servers (but not stop them), etc.
CREATE TABLE users (
@ -112,52 +159,6 @@ CREATE TRIGGER trigger_users
FOR EACH ROW EXECUTE PROCEDURE history_users();
-- This stores information about the host machine. This is the master table that everything will be linked
-- to.
CREATE TABLE hosts (
host_uuid uuid not null primary key, -- This is the single most important record in Anvil!. Everything links back to here.
host_name text not null,
host_type text not null, -- Either 'node' or 'dashboard'.
modified_date timestamp with time zone not null
);
ALTER TABLE hosts OWNER TO #!variable!user!#;
CREATE TABLE history.hosts (
history_id bigserial,
host_uuid uuid not null,
host_name text not null,
host_type text not null,
modified_date timestamp with time zone not null
);
ALTER TABLE history.hosts OWNER TO #!variable!user!#;
CREATE FUNCTION history_hosts() RETURNS trigger
AS $$
DECLARE
history_hosts RECORD;
BEGIN
SELECT INTO history_hosts * FROM hosts WHERE host_uuid = new.host_uuid;
INSERT INTO history.hosts
(host_uuid,
host_name,
host_type,
modified_date)
VALUES
(history_hosts.host_uuid,
history_hosts.host_name,
history_hosts.host_type,
history_hosts.modified_date);
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
ALTER FUNCTION history_hosts() OWNER TO #!variable!user!#;
CREATE TRIGGER trigger_hosts
AFTER INSERT OR UPDATE ON hosts
FOR EACH ROW EXECUTE PROCEDURE history_hosts();
-- This stores special variables for a given host that programs may want to record.
CREATE TABLE host_variable (
host_variable_uuid uuid not null primary key, -- This is the single most important record in ScanCore. Everything links back to here.

@ -189,11 +189,11 @@ Connecting to Database with configuration ID: [#!variable!uuid!#]
<key name="log_0057">Updated: [#!variable!file!#] to require passwords for access.</key>
<key name="log_0058"><![CDATA[[ Error ] - The method Remote->call() was called but the port: [#!variable!port!#] is invalid. It must be a digit between '1' and '65535'.]]></key>
<key name="log_0059">Started the PostgreSQL database server.</key>
<key name="log_0060">Database user: [#!variable!user!#] already exists with ID: [#!variable!uuid!#].</key>
<key name="log_0060">Database user: [#!variable!user!#] already exists with UUID: [#!variable!uuid!#].</key>
<key name="log_0061"><![CDATA[[ Error ] - The method Get->users_home() was asked to find the home directory for the user: [#!variable!user!#], but was unable to do so.]]></key>
<key name="log_0062">SSH session opened without a password to: [#!variable!target!#].</key>
<key name="log_0063"><![CDATA[The database: [#!variable!host!# -> #!variable!name!#] with the UUID: [#!variable!uuid!#] did not respond to pings and 'database::#!variable!uuid!#::ping' is not set to '0' in '#!data!path::configs::anvil.conf!#', skipping it.]]></key>
<key name="log_0064">[ Warning ] - The database: [#!variable!name!#] on host: [#!variable!host!#] with ID: [#!variable!uuid!#] can not be used, skipping it.</key>
<key name="log_0064">[ Warning ] - The database: [#!variable!name!#] on host: [#!variable!host!#] with UUID: [#!variable!uuid!#] can not be used, skipping it.</key>
<key name="log_0065">
The database connection error was:
----------
@ -213,12 +213,12 @@ The database connection error was:
<key name="log_0070">The connection to the database: [#!variable!name!#] on host: [#!variable!host!#:#!variable!port!#] failed because the name could not be translated to an IP address. Is this database server's host name in '/etc/hosts'?</key>
<key name="log_0071">Successfully Connected to the database: [#!variable!name!#] (id: [#!variable!uuid!#]) on host: [#!variable!host!#:#!variable!port!#].</key>
<key name="log_0072"><![CDATA[[ Error ] - The method Database->query() was called without a database ID to query and 'sys::database::read_uuid' doesn't contain a database ID, either. Are any databases available?]]></key>
<key name="log_0073"><![CDATA[[ Error ] - The method Database->query() was asked to query the database with ID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0073"><![CDATA[[ Error ] - The method Database->query() was asked to query the database with UUID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0074">About to run: [#!variable!uuid!#]:[#!variable!query!#]</key>
<key name="log_0075"><![CDATA[[ Error ] - Failed to prepare the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$anvil->Log->secure' is not set.]]></key>
<key name="log_0076"><![CDATA[[ Error ] - Failed to execute the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$anvil->Log->secure' is not set.]]></key>
<key name="log_0077"><![CDATA[[ Error ] - The method Database->initialize() was called without a database ID to query and 'sys::database::read_uuid' doesn't contain a database ID, either. Are any databases available?]]></key>
<key name="log_0078"><![CDATA[[ Error ] - The method Database->initialize() was asked to query the database with ID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0078"><![CDATA[[ Error ] - The method Database->initialize() was asked to query the database with UUID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0079"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!uuid!#]) but a core SQL file to load wasn't passed, and the 'database::#!variable!uuid!#::core_sql' variable isn't set. Unable to initialize without the core SQL file.]]></key>
<key name="log_0080"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!uuid!#]) but the core SQL file: [#!variable!sql_file!#] doesn't exist.]]></key>
<key name="log_0081"><![CDATA[[ Error ] - The method Database->initialize() was asked to initialize the database: [#!variable!server!#] (id: [#!variable!uuid!#]) but the core SQL file: [#!variable!sql_file!#] exist, but can't be read.]]></key>
@ -229,13 +229,13 @@ The database connection error was:
<key name="log_0086"><![CDATA[[ Error ] - The method System->check_memory() was called without a program name to check.]]></key>
<key name="log_0087">Testing access to the the database: [#!variable!server!#] prior to query or write. Program will exit if it fails.</key>
<key name="log_0088">Access confirmed.</key>
<key name="log_0089"><![CDATA[[ Error ] - The method Database->write() was asked to write to the database with ID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0089"><![CDATA[[ Error ] - The method Database->write() was asked to write to the database with UUID: [#!variable!uuid!#] but there is no file handle open to the database. Was the connection lost?]]></key>
<key name="log_0090"><![CDATA[[ Error ] - Failed to 'do' the database query: [#!variable!query!#] on: [#!variable!server!#]. The error was: [#!variable!db_error!#]. Note that if the query reports '--', the query was listed as containing sensitive data and '$anvil->Log->secure' is not set.]]></key>
<key name="log_0091">Failed to connect to any database.</key>
<key name="log_0092"><![CDATA[[ Error ] - Unable to connect to the database: [#!variable!server!#] (id: [#!variable!uuid!#]).]]></key>
<key name="log_0093"><![CDATA[[ Error ] - The method Alert->check_alert_sent() was called but the 'modified_date' parameter was not passed and/or 'sys::database::timestamp' is not set. Did the program fail to connect to any databases?]]></key>
<key name="log_0094">[ Error ] - Failed to start the Postgres server. Please check the system logs for details.</key>
<key name="log_0095">The database user: [#!variable!user!#] was created with ID: [#!variable!id!#].</key>
<key name="log_0095">The database user: [#!variable!user!#] was created with UUID: [#!variable!id!#].</key>
<key name="log_0096">[ Error ] - Failed to add the database user: [#!variable!user!#]! Unable to proceed.</key>
<key name="log_0097"><![CDATA[[ Error ] - The method Alert->check_alert_sent() was called but the 'set' parameter was not passed or it is empty. It should be 'set' or 'clear'.]]></key>
<key name="log_0098">
@ -252,9 +252,9 @@ The database connection error was:
<key name="log_0101"><![CDATA[[ Error ] - The method Alert->register_alert() was called but the 'title_key' parameter was not passed or it is empty and 'header' is enable (default).]]></key>
<key name="log_0102">I am not recording the alert with message_key: [#!variable!message_key!#] to the database because its log level was lower than any recipients.</key>
<key name="log_0103">The local machine's UUID was not read properly. It should be stored in: [#!data!sys::host_uuid!#] and contain hexadecimal characters in the format: '012345-6789-abcd-ef01-23456789abcd' and usually matches the output of 'dmidecode --string system-uuid'. If this file exists and if there is a string in the file, please verify that it is structured correctly.</key>
<key name="log_0104">The database with ID: [#!variable!uuid!#] for: [#!variable!file!#] is behind.</key>
<key name="log_0104">The database with UUID: [#!variable!uuid!#] for: [#!variable!file!#] is behind.</key>
<key name="log_0105">Anvil! database: [#!variable!database!#] already exists.</key>
<key name="log_0106">The database with ID: [#!variable!uuid!#] is behind. A database resync will be requested.</key>
<key name="log_0106">The database with UUID: [#!variable!uuid!#] is behind. A database resync will be requested.</key>
<key name="log_0107">[ Warning ] - Failed to delete the temporary postgres password.</key>
<key name="log_0108"><![CDATA[[ Error ] - The method Database->insert_or_update_states() was called but the 'state_host_uuid' parameter was not passed or it is empty. Normally this is set to 'sys::data_uuid'.]]></key>
<key name="log_0109">[ Error ] - Failed to create the Anvil! database: [#!variable!database!#]</key>
@ -366,6 +366,8 @@ The database connection error was:
<key name="log_0215">Logging the user: [#!data!sys::users::user_name!#] out.</key>
<key name="log_0216">The #!variable!uuid_name!#: [#!variable!uuid!#] was passed in, but no record with that UUID was found in the database.</key>
<key name="log_0217">The variable with variable_uuid: [#!variable!variable_uuid!#], variable_source_table: [#!variable!variable_source_table!#] and variable_source_uuid: [#!variable!variable_source_uuid!#] was not found in the database, so unable to update.</key>
<key name="log_0218">The variable: [#!variable!name!#] was expected to be an array reference, but it wasn't. It contained (if anything): [#!variable!value!#].</key>
<key name="log_0219">The database with UUID: [#!variable!uuid!#] is missing rows. A database resync will be requested.</key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key>

@ -42,7 +42,7 @@ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, secure =
# Connect to the database(s). If we have no connections, we'll proceed anyway as one of the 'run_once' tasks
# is to setup the database server.
$anvil->Database->connect({debug => 2});
$anvil->Database->connect({debug => 3});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"});
# If I have no databases, sleep for a second and then exit (systemd will restart us).

Loading…
Cancel
Save