diff --git a/Anvil/Tools/Get.pm b/Anvil/Tools/Get.pm
index a9c50a77..906af03a 100755
--- a/Anvil/Tools/Get.pm
+++ b/Anvil/Tools/Get.pm
@@ -663,8 +663,8 @@ sub switches
}
else
{
- # Got a value without an argument.
- $anvil->data->{switches}{error} = 1;
+ # Got a value without an argument, so just record it as '#!SET!#'.
+ $anvil->data->{switches}{$argument} = "#!SET!#";
}
}
}
diff --git a/ocf/alteeve/server b/ocf/alteeve/server
index 2fca30b9..842197e4 100755
--- a/ocf/alteeve/server
+++ b/ocf/alteeve/server
@@ -1,12 +1,360 @@
#!/usr/bin/perl
#
-# This is the resource agent used to manage servers on the Anvil! Intelligent Availability platform.
+# 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.
+#
+# 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
+# another purpose, let us know and we'll try to help.
#
# Based on: https://github.com/ClusterLabs/resource-agents/blob/master/doc/dev-guides/ra-dev-guide.asc
#
+# Error types from pacemaker's perspective;
+#
+# - Soft Error - Unless specifically configured otherwise, pacemaker will attempt to recover a resource
+# in-place - usually by restarting the resource on the same node.
+# - Hard Error - Unless specifically configured otherwise, pacemaker will attempt to recover a resource
+# which failed with this error by restarting the resource on a different node.
+# - Fatal Error - This is a cluster-wide error, it would make no sense to recover such a resource on a
+# different node, let alone in-place. When a resource fails with this error, Pacemaker will
+# attempt to shut down the resource, and wait for administrator intervention.
+#
+# Exit codes;
+# 0 - OCF_SUCCESS
+# - The action completed successfully. This is the expected return code for any successful start, stop,
+# migrate_to, meta_data, help, and usage action.
+# - For monitor, however, a modified convention applies:
+# - If the server is running we return, OCF_SUCCESS. If not running and gracefully stopped or migrated
+# off, return OCF_NOT_RUNNING.
+#
+# 1 - OCF_ERR_GENERIC
+# - The action returned a generic error. This is used only when none of the more specific error codes,
+# defined below, accurately describes the problem.
+# - Pacemaker interprets this exit code as a soft error.
+#
+# 2 - OCF_ERR_ARGS
+# - The resource’s configuration is not valid on this machine. This can happen if the serve fails to boot
+# because of a missing bridge, for example.
+#
+# 3 - OCF_ERR_UNIMPLEMENTED
+# - The resource agent was instructed to execute an action that we do not implement.
+# - Not all resource agent actions are mandatory. We don't implement 'promote' or 'demote'. We do implement
+# 'migrate_to', 'migrate_from', and 'notify'. If we're misconfigured as a master/slave resource, for
+# example, then will alert the user about this misconfiguration by returning OCF_ERR_UNIMPLEMENTED.
+#
+# 4 - OCF_ERR_PERM
+# - The action failed due to insufficient permissions. This may be due to a node not being able to open a
+# definition file or resource config.
+# - Pacemaker interprets this exit code as a hard error.
+#
+# 5 - OCF_ERR_INSTALLED
+# - The action failed because a required component is missing on the node where the action was executed.
+# This may be due to a required binary not being executable, or a the DRBD resource config file not
+# existing.
+# - Pacemaker interprets this exit code as a hard error.
+#
+# 6 - OCF_ERR_CONFIGURED
+# - The action failed because the user misconfigured the resource in pacemaker. For example, the user may
+# have configured an alphanumeric string for a parameter that really should be an integer.
+# - Pacemaker interprets this exit code as a fatal error.
+#
+# 7 - OCF_NOT_RUNNING
+# - The resource was found not to be running. This is an exit code that may be returned by the monitor
+# action exclusively. Note that this implies that the resource has either gracefully shut down, or has
+# never been started.
+#
+# 8 - OCF_RUNNING_MASTER
+# 9 - OCF_FAILED_MASTER
+# - These OCF exit codes are not used here.
+#
+# NOTE: We don't use Anvil::Tools to keep overhead low and to keep this agent independent as possible.
use strict;
use warnings;
+use XML::Simple;
+use Data::Dumper;
+
+# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
+$| = 1;
+
+my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
+my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
+if (($running_directory =~ /^\./) && ($ENV{PWD}))
+{
+ $running_directory =~ s/^\./$ENV{PWD}/;
+}
+
+my $conf = {
+ 'log' => {
+ facility => "local0",
+ level => 2,
+ line_numbers => 1,
+ tag => $THIS_FILE,
+ },
+ # If a program isn't at the defined path, $ENV{PATH} will be searched.
+ path => {
+ exe => {
+ cibadmin => "/usr/sbin/cibadmin",
+ crm_error => "/usr/sbin/crm_error",
+ drbdadm => "/usr/sbin/drbdadm",
+ echo => "/usr/bin/echo",
+ getent => "/usr/bin/getent",
+ logger => "/usr/bin/logger",
+ stonith_admin => "/usr/sbin/stonith_admin",
+ },
+ },
+ environment => {
+ # The name of the server we care about.
+ OCF_RESKEY_name => "",
+ },
+};
+
+# Find executables.
+find_executables($conf);
+
+# Get any command line switches.
+get_switches($conf);
+
+if (($conf->{switches}{metadaata}) or ($conf->{switches}{'meta-data'}))
+{
+ show_metadata($conf);
+}
+
+# Something for the logs
+to_log($conf, {message => "ocf:alteeve:server invoked.", 'line' => __LINE__});
+
+
+# If we hit here, something very wrong happened.
+exit(255);
+
+
+#############################################################################################################
+# Functions #
+#############################################################################################################
+
+# This prints out the metadata and exits.
+sub show_metadata
+{
+ my ($conf) = @_;
+
+ # This is a pretty simple agent, by design. We only take a server name for now.
+ print '
+
+
+ 0.1
+
+This resource agent manages KVM+qemu virtual servers on an Anvil! m3 Intelligent Availability™ system.
+It manages underlying components like DRBD 9 storage resources, brodge connections and so forth.
+
+ Anvil! m3 server resource agent
+
+
+
+ This is the name of the server as reported by virsh.
+
+ Server name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+';
+
+ exit(0);
+}
+
+# This gathers command line switches and stores them in 'swithes::'.
+sub get_switches
+{
+ my ($conf) = @_;
+
+ my $last_argument = "";
+ foreach my $argument (@ARGV)
+ {
+ if ($last_argument eq "raw")
+ {
+ # Don't process anything.
+ $conf->{switches}{raw} .= " $argument";
+ }
+ elsif ($argument =~ /^-/)
+ {
+ # If the argument is just '--', appeand everything after it to 'raw'.
+ if ($argument eq "--")
+ {
+ $last_argument = "raw";
+ $conf->{switches}{raw} = "";
+ }
+ else
+ {
+ ($last_argument) = ($argument =~ /^-{1,2}(.*)/)[0];
+ if ($last_argument =~ /=/)
+ {
+ # Break up the variable/value.
+ ($last_argument, my $value) = (split /=/, $last_argument, 2);
+ $conf->{switches}{$last_argument} = $value;
+ }
+ else
+ {
+ $conf->{switches}{$last_argument} = "#!SET!#";
+ }
+ }
+ }
+ else
+ {
+ if ($last_argument)
+ {
+ $conf->{switches}{$last_argument} = $argument;
+ $last_argument = "";
+ }
+ else
+ {
+ # Got a value without an argument. That's OK.
+ $conf->{switches}{$argument} = "#!SET!#";
+ }
+ }
+ }
+ # Clean up the initial space added to 'raw'.
+ if ($conf->{switches}{raw})
+ {
+ $conf->{switches}{raw} =~ s/^ //;
+ }
+
+ return(0);
+}
+
+# Log file entries
+sub to_log
+{
+ my ($conf, $parameters) = @_;
+
+ my $facility = defined $parameters->{facility} ? $parameters->{facility} : $conf->{'log'}{facility};
+ my $level = defined $parameters->{level} ? $parameters->{level} : 1;
+ my $line = defined $parameters->{'line'} ? $parameters->{'line'} : 0;
+ my $message = defined $parameters->{message} ? $parameters->{message} : "";
+ my $priority = defined $parameters->{priority} ? $parameters->{priority} : "";
+
+ # Leave if we don't care about this message
+ return if $level > $conf->{'log'}{level};
+ return if not $message;
+
+ # Build the message. We log the line
+ if (($conf->{'log'}{line_numbers}) && ($line))
+ {
+ $message = $line."; ".$message;
+ }
+
+ my $priority_string = $facility;
+ if ($priority)
+ {
+ $priority_string .= ".".$priority;
+ }
+ elsif ($level eq "0")
+ {
+ $priority_string .= ".notice";
+ }
+ elsif (($level eq "1") or ($level eq "2"))
+ {
+ $priority_string .= ".info";
+ }
+ else
+ {
+ $priority_string .= ".debug";
+ }
+
+ # Clean up the string for bash
+ $message =~ s/"/\\\"/gs;
+ #$message =~ s/\(/\\\(/gs;
+
+ my $shell_call = $conf->{path}{exe}{logger}." --priority ".$priority_string." --tag ".$conf->{'log'}{tag}." -- \"".$message."\"";
+ open (my $file_handle, $shell_call." 2>&1 |") or die "Failed to call: [".$shell_call."]. The error was: $!\n";
+ while(<$file_handle>)
+ {
+ # This should not generate output.
+ chomp;
+ my $line = $_;
+ print "Unexpected logging output: [".$line."]\n";
+ }
+ close $file_handle;
+
+ return(0);
+}
+
+# This checks the given paths and, if something isn't found, it searches PATH trying to find it.
+sub find_executables
+{
+ my ($conf) = @_;
+
+ # Variables.
+ my $check = "";
+ my $bad = 0;
+
+ # Log entries can only happen if I've found 'logger', so an extra check will be made on 'to_log'
+ # calls.
+ my @dirs = split/:/, $ENV{PATH};
+ foreach my $exe (sort {$b cmp $a} keys %{$conf->{path}{exe}})
+ {
+ if ( not -e $conf->{path}{exe}{$exe} )
+ {
+ to_log($conf, {message => "The program: [$exe] is not at: [".$conf->{path}{exe}{$exe}."]. Looking for it now...", 'line' => __LINE__, level => 1});
+ foreach my $path (@dirs)
+ {
+ $check = "$path/$exe";
+ $check =~ s/\/\//\//g;
+ to_log($conf, {message => "Checking: [$check]", 'line' => __LINE__, level => 2});
+ if ( -e $check )
+ {
+ if (-e $conf->{path}{exe}{logger})
+ {
+ to_log($conf, {message => "Found it! Changed path for: [$exe] from: [".$conf->{path}{exe}{$exe}."] to: [$check]", 'line' => __LINE__, level => 1});
+ }
+ else
+ {
+ warn "DEBUG: Found it! Changed path for: [$exe] from: [".$conf->{path}{exe}{$exe}."] to: [$check]\n";
+ }
+ $conf->{path}{exe}{$exe} = $check;
+ }
+ else
+ {
+ to_log($conf, {message => "Not found.", 'line' => __LINE__, level => 2});
+ }
+ }
+ }
+ else
+ {
+ to_log($conf, {message => "Found!", 'line' => __LINE__, level => 3});
+ next;
+ }
+ # Make sure it exists now.
+ to_log($conf, {message => "Checking again if: [$exe] is at: [".$conf->{path}{exe}{$exe}."].", 'line' => __LINE__, level => 3});
+ if (not -e $conf->{path}{exe}{$exe})
+ {
+ $bad = 1;
+ if (-e $conf->{path}{exe}{logger})
+ {
+ to_log($conf, {message => "Failed to find executable: [$exe]. Unable to proceed.", 'line' => __LINE__, level => 0});
+ }
+ else
+ {
+ warn "Failed to find executable: [$exe]. Unable to proceed.\n";
+ }
+ }
+ }
+ if ($bad)
+ {
+ exit(1);
+ }
-exit(0);
+ return(0);
+}