* Renamed striker-configure-host (back) to anvil-configure-host, and started updating it to work on any machine type.

* Created tools/anvil-check-memory to report how much RAM is used by a given program.
* Added documentation for some previously undocumented methods.
* Updated Database->archive_database() to take the 'tables' parameter.
* Updated Storage->scan_directory() to record a directory's mode and type, even when recursive isn't used.
* Finished System->check_memory().
* Updated ocf:alteeve:server to now NOT stop a DRBD resource unless 'stop_drbd_resources'.

Signed-off-by: Digimer <digimer@alteeve.ca>
This commit is contained in:
Digimer 2019-09-03 14:07:39 -04:00
parent c0dd34334e
commit 8a2c86d088
12 changed files with 331 additions and 57 deletions

View File

@ -1000,6 +1000,8 @@ sub _set_paths
},
exe => {
'anvil-change-password' => "/usr/sbin/anvil-change-password",
'anvil-check-memory' => "/usr/sbin/anvil-check-memory",
'anvil-configure-host' => "/usr/sbin/anvil-configure-host",
'anvil-daemon' => "/usr/sbin/anvil-daemon",
'anvil-download-file' => "/usr/sbin/anvil-download-file",
'anvil-file-details' => "/usr/sbin/anvil-file-details",
@ -1066,7 +1068,6 @@ sub _set_paths
'ssh-keyscan' => "/usr/bin/ssh-keyscan",
stonith_admin => "/usr/sbin/stonith_admin",
strings => "/usr/bin/strings",
'striker-configure-host' => "/usr/sbin/striker-configure-host",
'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target",
'striker-manage-peers' => "/usr/sbin/striker-manage-peers",
'striker-prep-database' => "/usr/sbin/striker-prep-database",

View File

@ -508,6 +508,7 @@ INSERT INTO
return(0);
}
### TODO: Write this, maybe? Or remove it and ->warning()?
=head2 error
=cut

View File

@ -114,7 +114,15 @@ sub parent
=head2 archive_database
NOTE: Not implemented yet.
This method takes an array reference of database tables and check each to see if their history schema version needs to be archived or not.
Parameters;
=head3 tables (required, hash reference)
This is an B<< array reference >> of tables to archive.
B<< NOTE >>: The array is processed in B<< reverse >> order! This is done to allow the same array used to create/sync tables to be used without modification (foreign keys will be archived/removed before primary keys)
=cut
sub archive_database
@ -125,6 +133,19 @@ sub archive_database
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->archive_database()" }});
my $tables = defined $parameter->{tables} ? $parameter->{tables} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
tables => $tables,
}});
# If the 'tables' parameter is an array reference, add it to 'sys::database::check_tables' (creating
# it, if needed).
if (ref($tables) ne "ARRAY")
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0432"});
return(1);
}
# Is archiving disabled?
if (not $anvil->data->{sys}{database}{archive}{trigger})
{
@ -140,18 +161,11 @@ sub archive_database
return(1);
}
# If we don't have an array of tables, we have nothing to do.
if ((not exists $anvil->data->{sys}{database}{check_tables}) or (ref(@{$anvil->data->{sys}{database}{check_tables}} ne "ARRAY")))
{
return(1);
}
# We'll use the list of tables created for _find_behind_databases()'s 'sys::database::check_tables'
# array, but in reverse so that tables with primary keys (first in the array) are archived last.
foreach my $table (reverse(@{$anvil->data->{sys}{database}{check_tables}}))
foreach my $table (reverse(@{$tables}))
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { table => $table }});
$anvil->Database->_archive_table({table => $table});
}
@ -1555,6 +1569,12 @@ sub get_local_uuid
This will initialize a database using a given file.
Parameters;
=head3 sql_file (required)
This is the full (or relative) path and file nane to use when initializing the database.
=cut
sub initialize
{
@ -5192,6 +5212,14 @@ WHERE
This reads, sets or updates the database lock file timestamp.
Parameters;
=head3 do (required, default 'get')
This controls whether we're setting (C<< set >>) or checking for (C<< get >>) a lock file on the local system.
If setting, or if checking and a lock file is found, the timestamp (in unixtime) in the lock fike is returned. If a lock file isn't found, C<< 0 >> is returned.
=cut
sub lock_file
{
@ -5660,7 +5688,7 @@ sub query
=head2 quote
This quotes a string for safe use in database queries/writes.
This quotes a string for safe use in database queries/writes. It operates exactly as C<< DBI >>'s C<< quote >> method. This method is simply a wrapper that uses the C<< DBI >> handle set as the currently active read database.
Example;

View File

@ -2013,7 +2013,41 @@ sub rsync
=head2 scan_directory
TODO: Wtite this...
This takes a directory and scan its contents. What is found is stored in the following hashes;
scan::directories::<parent_directory>::directory = <parent directory>
scan::directories::<parent_directory>::name = <file or directory name>
scan::directories::<parent_directory>::type = 'file', 'directory' or 'symlink' (other special types are ignored entirely)
If the fule is a directory, this is also set;
scan::directories::<parent_directory>::mode = <the mode of the directory, already masked>
If the file is a symlink, this is also set;
scan::directories::<parent_directory>::target = <target file>
If the file is an actual file, the following information is set;
scan::directories::<parent_directory>::mode = <the mode of the file, already masked>
scan::directories::<parent_directory>::user_id = <numeric user ID of the owner>
scan::directories::<parent_directory>::group_id = <numeric group ID of the owner>
scan::directories::<parent_directory>::size = <size in bytes>
scan::directories::<parent_directory>::mtime = <last modification time, in unixtime>
scan::directories::<parent_directory>::mimetype = <mimetype, as returned by File::MimeInfo->mimetype>
scan::directories::<parent_directory>::executable = '0' or '1'
Parameters;
=head3 directory (required)
This is the full path to the directory to scan.
=head3 recursive (optional, default '0')
If set to C<< 1 >>, any directories found will be scanned as well.
B<< NOTE >>: Symlinks that point to directories will B<< NOT >> be scanned.
=cut
### TODO: Make this work on remote systems
@ -2065,15 +2099,21 @@ sub scan_directory
"scan::directories::${full_path}::name" => $anvil->data->{scan}{directories}{$full_path}{name},
full_path => $full_path,
}});
if ((-d $full_path) && ($recursive))
if (-d $full_path)
{
# This is a directory, dive into it is asked.
my @details = stat($full_path);
$anvil->data->{scan}{directories}{$full_path}{type} = "directory";
$anvil->data->{scan}{directories}{$full_path}{mode} = sprintf("04%o", $details[2] & 07777);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"scan::directories::${full_path}::type" => $anvil->data->{scan}{directories}{$full_path}{type},
"scan::directories::${full_path}::mode" => $anvil->data->{scan}{directories}{$full_path}{mode},
}});
if ($recursive)
{
$anvil->Storage->scan_directory({debug => $debug, directory => $full_path, recursive => $recursive});
}
}
elsif (-l $full_path)
{
# Symlink

View File

@ -564,7 +564,13 @@ sub check_if_configured
=head2 check_memory
# Not yet written...
This calls 'anvil-check-memory' with the given program name, and looks at the output to see how much RAM that program uses (if it is even running).
Parameters;
=head3 program_name (required)
This is the name of the program (as seen in the output of C<< ps aux >>) to check the RAM of.
=cut
sub check_memory
@ -585,18 +591,19 @@ sub check_memory
my $used_ram = 0;
my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{''}." --program $program_name"});
my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{'anvil-check-memory'}." --program $program_name"});
foreach my $line (split/\n/, $output)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }});
if ($line =~ /= (\d+) /)
{
$used_ram = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { used_ram => $used_ram }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
used_ram => $anvil->Convert->add_commas({number => $used_ram})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $used_ram}).")",
}});
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { used_ram => $used_ram }});
return($used_ram);
}
@ -919,6 +926,8 @@ sub get_free_memory
This method tries to determine the host type and returns a value suitable for use is the C<< hosts >> table.
my $type = $anvil->System->get_host_type();
First, it looks to see if C<< sys::host_type >> is set and, if so, uses that string as it is.
If that isn't set, it then looks at the short host name. The following rules are used, in order;

View File

@ -1255,7 +1255,7 @@ sub configure_striker
debug => 3,
file => $THIS_FILE,
line => __LINE__,
job_command => $anvil->data->{path}{exe}{'striker-configure-host'},
job_command => $anvil->data->{path}{exe}{'anvil-configure-host'},
job_data => "form::config_step2",
job_name => "configure::network",
job_title => "job_0001",

2
notes
View File

@ -504,6 +504,8 @@ pcs property set stonith-enabled=true
pcs resource create hypervisor systemd:libvirtd op monitor interval=60
pcs resource clone hypervisor clone-max=2 notify="false"
pcs resource create drbd systemd:drbd op monitor interval=60
pcs resource clone drbd clone-max=2 notify="false"
pcs resource update test_server ocf:alteeve:server name="test_server" meta allow-migrate="true" op monitor interval="60"

View File

@ -3,7 +3,7 @@
# This is the resource agent used to manage servers on the Anvil! Intelligent Availability platform.
#
# License: GNU General Public License (GPL) v2+
# (c) 1997-2018 - Alteeve's Niche! Inc.
# (c) 1997-2019 - Alteeve's Niche! Inc.
#
# WARNING: This is a pretty purpose-specific resource agent. No effort was made to test this on an rgmanager
# cluster or on any configuration outside how the Anvil! m3 uses it. If you plan to adapt it to
@ -132,9 +132,23 @@ if ($anvil->data->{environment}{PCMK_debug})
$anvil->Log->level({set => 3});
}
# Originally, this was designed to start and stop a server's DRBD resources on demand. Early testing appears
# to show this prone to higher risk of fencing if something goes wrong. As such, we're changing the default
# behaviour to leave DRBD resources up. Set this to '1' (here or by switch) to revert back to the old
# behaviour.
$anvil->data->{environment}{OCF_RESKEY_CRM_meta_stop_drbd_resources} = 0;
# Get any command line switches.
$anvil->Get->switches;
if ($anvil->data->{switches}{stop_drbd_resources})
{
$anvil->data->{environment}{OCF_RESKEY_CRM_meta_migrate_source} = 1;
}
#$anvil->DRBD->get_status({debug => 2});
#die;
# Something for the logs
if ((not $anvil->data->{switches}{metadaata}) and (not $anvil->data->{switches}{'meta-data'}))
{
@ -315,7 +329,7 @@ sub start_server
# Is the server already running somewhere?
find_server($anvil);
# Start the resource
# Start the resource, if needed.
start_drbd_resource($anvil);
# Still alive? Boot!
@ -383,7 +397,7 @@ sub stop_drbd_resource
return(0);
}
# This starts the drbd resource(s) for the requested server.
# This starts the drbd resource(s) for the requested server, if needed.
sub start_drbd_resource
{
my ($anvil) = @_;
@ -397,6 +411,37 @@ sub start_drbd_resource
peer => $peer,
}});
# Do we need startup?
my $startup_needed = 0;
$anvil->DRBD->get_status({debug => 3});
foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$server}{resource}})
{
# Is the current resource up locally already? If it is, we're done.
my $role = $anvil->data->{drbd}{status}{$host}{resource}{$resource}{role};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { role => $role }});
if ((lc($role) ne "secondary") && (lc($role) ne "primary"))
{
$startup_needed = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { startup_needed => $startup_needed }});
last;
}
else
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0419", variables => {
resource => $resource,
role => $role,
}});
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { startup_needed => $startup_needed }});
if (not $startup_needed)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0431"});
return(0);
}
# Start DRBD locally.
foreach my $resource (sort {$a cmp $b} keys %{$anvil->data->{server}{$server}{resource}})
{
@ -578,7 +623,13 @@ sub stop_server
}
# Now stop the DRBD resource(s).
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
'environment::OCF_RESKEY_CRM_meta_stop_drbd_resources' => $anvil->data->{environment}{OCF_RESKEY_CRM_meta_stop_drbd_resources},
}});
if ($anvil->data->{environment}{OCF_RESKEY_CRM_meta_stop_drbd_resources})
{
stop_drbd_resource($anvil);
}
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0324", variables => { server => $server }});
$anvil->nice_exit({exit_code => 0});

View File

@ -748,6 +748,9 @@ Failed to promote the DRBD resource: [#!variable!resource!#] primary. Expected a
<key name="log_0428">[ Error ] - The server: [#!variable!server!#] can't by migrated to: [#!variable!target!#] because the resource: [#!variable!resource!#] isn't connected. The current connection state is: [#!variable!connection_state!#].</key>
<key name="log_0429">[ Error ] - The server: [#!variable!server!#] can't by migrated to: [#!variable!target!#] because we can't reach it at all right now.</key>
<key name="log_0430">The migration of the server: [#!variable!server!#] over to: [#!variable!target!#] isn't needed, it's already running on the target. Exiting successfully.</key>
<key name="log_0431">All DRBD resources appear to be up, skipping individual DRBD resource startup.</key>
<key name="log_0432"><![CDATA[Database->archive_database() was not passed an array reference of tables to archive. Please pass an array reference using the 'tables' parameter.]]></key>
<key name="log_0433">The 'smaps' proc file for the process ID: [#!variable!pid!#] was not found. Did the program just close?</key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key>
@ -1033,6 +1036,8 @@ Failed to generate an RSA public key for the user: [#!variable!user!#]. The outp
<key name="error_0062">The download job with UUID: [#!variable!job_uuid!#] is already being handled by another process.</key>
<key name="error_0063">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?</key>
<key name="error_0064">A request to active the logical volume: [#!variable!path!#] was made, but that path doesn't exist or isn't a block device.</key>
<key name="error_0065"><![CDATA[No program name given (via --program <name>), unable to proceed.]]></key>
<key name="error_0066"><![CDATA[The program: [#!variable!program!#] was not found to be running on this system.]]></key>
<!-- These are units, words and so on used when displaying information. -->
<key name="unit_0001">Yes</key>

119
tools/anvil-check-memory Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/perl
#
# This reports the total memory used by all processes with to passed-in program name.
#
# The size in bytes is returned. If '0' is reported, check the exit code to see why.
#
# This software was created by Alteeve's Niche! Inc. and has been released under the terms of the GNU GPL
# version 2.
#
# https://alteeve.com
#
# Exit Codes:
# 0 - Success
# 1 - No program name passed-in.
# 2 - No PIDs found for the passed-in program name.
# 3 - No PID returned by pgrep
#
use strict;
use warnings;
use Anvil::Tools;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1});
$anvil->data->{switches}{program} = "";
$anvil->Get->switches;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
'switches::program' => $anvil->data->{switches}{program},
}});
$anvil->data->{memory}{total} = 0;
# If we weren't given a program, exit
if (not $anvil->data->{switches}{program})
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0065"});
print "#!error!#\n";
$anvil->nice_exit({exit_code => 1});
}
# Find the PID(s) of the program.
$anvil->data->{sys}{pids} = $anvil->System->pids({ignore_me => 1, program_name => $anvil->data->{switches}{program}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 'sys::pids' => $anvil->data->{sys}{pids} }});
my $pids_found = @{$anvil->data->{sys}{pids}};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { pids_found => $pids_found }});
if (not $pids_found)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0066", variables => { prorgram => $anvil->data->{switches}{program} }});
print $anvil->data->{switches}{program}." = ".$anvil->data->{memory}{total}."\n";
$anvil->nice_exit({exit_code => 2});
}
# Read in the smaps for each pid
foreach my $pid (sort {$a cmp $b} @{$anvil->data->{sys}{pids}})
{
my $smaps_path = "/proc/".$pid."/smaps";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { smaps_path => $smaps_path }});
# This will store the amount of RAM used by this specific PID.
$an->data->{memory}{pid}{$pid} = 0;
if (not -e $smaps_path)
{
# It is possible that the program just closed.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 0, level => 1, key => "log_0433", variables => { pid => $pid }});
next;
}
# Read in the file.
my $body = $anvil->Storage->read_file({file => $smaps_path});
foreach my $line (split/\n/, $body)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { line => $line }});
if ($line =~ /^Private_Dirty:\s+(\d+) (.*B)$/)
{
my $size = $1;
my $type = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
type => $type,
size => $size,
}});
next if not $size;
next if $size =~ /\D/;
# This uses 'kB' for 'KiB' >_>
$type = lc($type);
$type =~ s/b$/ib/ if $type !~ /ib$/;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { type => $type }});
my $size_in_bytes = $anvil->Convert->human_readable_to_bytes({size => $size, type => $type, base2 => 1});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
size_in_bytes => $anvil->Convert->add_commas({number => $size_in_bytes})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $size_in_bytes}).")",
}});
$anvil->data->{memory}{pid}{$pid} += $size_in_bytes;
$anvil->data->{memory}{total} += $size_in_bytes;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"memory::pid::${pid}" => $anvil->Convert->add_commas({number => $anvil->data->{memory}{pid}{$pid}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{memory}{pid}{$pid}}).")",
'memory::total' => $anvil->Convert->add_commas({number => $anvil->data->{memory}{total}})." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{memory}{total}}).")",
}});
}
}
}
# Report and exit.
print $anvil->data->{switches}{program}." = ".$an->data->{memory}{total}." # ".$anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{memory}{total}})."\n";
$anvil->nice_exit({exit_code => 0});

View File

@ -1,6 +1,6 @@
#!/usr/bin/perl
#
# This is called when striker needs to configure the local network and user accounts.
# This is called when striker, an node or a DR host needs to configure the local network and user accounts.
#
# Exit codes;
# 0 = Normal exit.
@ -114,6 +114,12 @@ sub update_passwords
{
my ($anvil) = @_;
# Is this a striker dashboard, node or DR host?
my $type = $anvil->System->get_host_type();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, list => { type => $type }});
if ($type eq "dashboard")
{
# Set the passwords
my $password = $anvil->data->{variables}{form}{config_step2}{striker_password}{value};
my $temp_file = "/tmp/anvil-".$anvil->Get->uuid;
@ -156,6 +162,18 @@ sub update_passwords
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, key => "error_0011", variables => { return_code => $return_code }});
}
}
}
else
{
if ($type eq "node")
{
# We'll need to update pcsd's password as well.
}
# Set passwords that are common to nodes and DR hosts.
}
$anvil->Job->update_progress({
progress => 95,
@ -644,6 +662,7 @@ sub reconfigure_network
}
}
# We're half-way there.
$anvil->Job->update_progress({
progress => 50,
job_uuid => $anvil->data->{job}{uuid},

View File

@ -75,7 +75,6 @@ if ((not $anvil->data->{switches}{abort}) && (not $anvil->data->{switches}{url})
{
get_job_details($anvil);
}
die;
# Do what now?
if ($anvil->data->{switches}{abort})