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); +}