Fence na.lib
Node Assassin :: Fence na.lib |
NOTE: The comments in this file need to be update, please don't trust them.
This is the fence agent's function library that exists in /etc/na/.
#!/usr/bin/perl
#
# This is the function library for the Node Assassin fence agent.
#
# Node Assassin - Fence Agent
# Digimer; digimer@alteeve.com
# Mar. 05, 2010.
# Version: 0.1.003
#
# This cleanly exits the agent.
sub do_exit
{
($conf, $log, $exit_status)=@_;
$exit_status=9 if not defined $exit_status;
$conf->{node}{handle}->close;
$log->close();
exit ($exit_status);
}
# This returns the 'help' message.
sub help
{
my ($conf, $log)=@_;
my $msg=q`
NOTE: This is now out of date!
Node Assassin Fencing Agent
This program interfaces with the Node Assassin to set one or more nodes
to one or more states.
Usage:
./fence_na <options>
Overview:
This takes one or more arguments relating to the desired state to set a
node to follow by one or more Node IDs to act on. Multiple states can
be set at the same time.
When specifying a single node, pass a single ID (not zero-padded).
When specifying two or more nodes, seperate them with a comma with no
spaces.
States:
0
This state will fence the nodes specified by the list.
1
This will release the fence and allow the specified node(s) to
boot.
2
This will fence the node(s) for one second. This is meant to be
used on ports connected to a node's power button. If the node
is alive and supports ACPI, this should start a graceful power
down of the node. Conversly, if the node was off, this will
boot the node. When connected to a node's reset switch, this
will cause a quick reboot without a graceful power off.
3
This state will fence the node(s) for five seconds. This is
specifically designed for ports connected to a node's power
button. It will allow a frozen node to be forced off by holding
the power button closed long enough to force a power off. This
state serves no real difference over state 2 when connected to
a reset switch.
Options:
In all cases, '--set_state_X=<list>' and '-X <list>' are equal and
interchangable. The examples below will use the long-form arguments for
the sake of clarity.
--set_state_0=<list>
This sets the specified port(s) state 0.
--set_state_1=<list>
This sets the specified port(s) state 0.
--set_state_2=<list>
This sets the specified port(s) state 2.
--set_state_2=<list>
This sets the specified port(s) state 2.
Examples:
Fence node 1.
./fence_na --set_state_0=1
Release the fence on node 1.
./fence_na --set_state_1=1
Boot nodes 1 and 2
./fence_na --set_state_2=1,2
Force node 2 to power off.
./fence_na --set_state_3=2
Fence nodes 4 and 5 then boot node 6
./fence_na --set_state_0=4,5 --set_state_2=6
Note:
An internal pager is not implemented. You may wish to run this via
'less':
./fence_na | less
NOTE: This is now out of date!
`;
print $msg;
do_exit($conf, $log, 0);
}
# This handles the actual actions.
sub process_action
{
my ($conf, $log)=@_;
# Make this more readable.
my $na_id=$conf->{'system'}{node_assassin_id};
my $action=$conf->{node}{action};
my $port=$conf->{node}{port};
# Translate the port passed in by the fence agent into the actual ports
# in the Node Assassin. Mapping is:
# Node 01 -> Power = Port 01
# Node 01 -> Reset = Port 02
# Node 02 -> Power = Port 03
# Node 02 -> Reset = Port 04
# Node 03 -> Power = Port 05
# Node 03 -> Reset = Port 06
# Node 04 -> Power = Port 07
# Node 04 -> Reset = Port 08
# ...
my $power_port=sprintf("%02d", (($port*2)-1));
my $reset_port=sprintf("%02d", ($port*2));
record($conf, $log, "Translated node port: [$port] to power port: [$power_port] and reset port: [$reset_port]\n");
if ($action eq "on")
{
# Release the fence and boot the node.
$conf->{'system'}{call_order}="$reset_port:1,$power_port:1,sleep,$power_port:2";
}
elsif ($action eq "off")
{
# Fence the node by pressing and holding the reset to make sure
# the node immediately dies. Then I release the fence long
# enough to force a power off, then I re-apply then fence to
# make sure the node doesn't come back up. This is needed
# because some machines won't power off if the reset is held
# high when the power is pressed, even for > 4 seconds.
$conf->{'system'}{call_order}="$reset_port:0,sleep,$reset_port:1,sleep,$power_port:0,sleep 5,$reset_port:0";
}
elsif ($action eq "reboot")
{
# Currently, I don't do this gracefully because, well, if it's
# being fenced, it's not meant to be graceful.
# This is a combination of the 'off' -> 'on' actions.
$conf->{'system'}{call_order}="$reset_port:0,sleep,$reset_port:1,sleep,$power_port:0,sleep 5,$reset_port:0";
$conf->{'system'}{call_order}.=",$reset_port:1,$power_port:1,sleep,$power_port:2";
}
elsif ($action eq "status")
{
# This should check the probe, but for now, it checks the
# port's state.
}
elsif (($action eq "monitor") or ($action eq "list"))
{
# Not sure what to do here.
}
else
{
record($conf, $log, "Unknown action request: [$action]!\n");
do_exit($conf, $log, 9);
}
}
# Read in the config file.
sub read_conf
{
my ($conf)=@_;
$conf={} if not $conf;
# I can't call the 'record' method here because I've not read in the
# log file and thus don't know where to write the log to yet. Comment
# out or delete 'print' statements before release.
my $read=IO::Handle->new();
my $shell_call="$conf->{'system'}{conf_file}";
# print "Shell call: [$shell_call]\n";
open ($read, "<$shell_call") or die "Failed to read: [$shell_call], error was: $!\n";
while (<$read>)
{
chomp;
my $line=$_;
next if not $line;
next if $line !~ /=/;
$line=~s/^\s+//;
$line=~s/\s+$//;
next if $line =~ /^#/;
next if not $line;
my ($var, $val)=(split/=/, $line, 2);
$var=~s/^\s+//;
$var=~s/\s+$//;
$val=~s/^\s+//;
$val=~s/\s+$//;
next if (not $var);
# print "Storing: [$var] = [$val]\n";
_make_hash_reference($conf, $var, $val);
}
$read->close();
return (0);
}
# Read in command line arguments
sub read_cla
{
my ($conf, $log, $bad)=@_;
# MADI: Remove this before release.
record($conf, $log, "Got args:\n");
# Loop through the passed arguments, if any.
my $set_next="";
foreach my $arg (@ARGV)
{
# MADI: Remove this before release.
record($conf, $log, "[$arg]\n");
# If 'set_next' has a value, push this argument into the 'conf'
# hash.
if ($set_next)
{
# It's set, use it's contents as the hash key.
$conf->{node}{$set_next}=$arg;
# MADI: Remove this before release.
record($conf, $log, "Setting: 'node::$set_next': [$conf->{node}{$set_next}]\n");
# Clear it now for the next go-round.
$set_next="";
next;
}
if ($arg=~/-h/)
{
# Print the help message and then exit.
help($conf, $log);
}
elsif ($arg=~/-[vV]/)
{
# Print the version information and then exit.
$conf->{'system'}{version}=1;
}
elsif ($arg=~/-q/)
{
# Suppress all non-critical messages from STDOUT.
$conf->{'system'}{quiet}=1;
}
elsif ($arg=~/^-/)
{
$arg=~s/^-//;
### These are the switches set by Red Hat.
if ($set_next eq "a")
{
# This is the IP address or hostname of the
# Node Assassin to call.
$set_next="ipaddr";
}
elsif ($set_next eq "l")
{
# This is the login name.
$set_next="login";
}
elsif ($set_next eq "p")
{
# This is the password. If it starts with '/'
# it is interpreted to be a file containing the
# password which will be read in and it's
# contents will replace# this value.
$set_next="passwd";
}
elsif ($set_next eq "n")
{
# This is the node to work on.
$set_next="port";
}
elsif ($set_next eq "o")
{
# This is the action to take. Valid actions
# are:
# on = ##:1 # Release fence
# off = ##:0 # Fence
# reboot = ##:3 -> ##:2 # Force off then boot.
# status = Returns the node's current status.
# monitor = Returns the status of all nodes.
$set_next="action";
}
elsif ($set_next eq "S")
{
# This is the script to run to retrieve the
# password when it is not stored in
# 'cluster.conf'. This script should echo/print
# the password to STDOUT.
$set_next="passwd_script";
}
}
else
{
### MADI: I might want to pick up arguments via multiple lines.
# Bad argument.
record($conf, $log, "Argument: [$arg] is not valid!\n");
record($conf, $log, "Please run './fence_na --help' to see a list of valid arguments.\n");
$bad=1;
}
}
}
# Read arguments from STDIN. This is adapted from the 'fence_brocade' agent.
sub read_stdin
{
my ($conf, $log, $bad)=@_;
my $option;
my $line_count=0;
while(defined (my $option=<>))
{
# Get rid of newlines.
chomp $option;
# Record the line for now, but comment this out before release.
record ($conf, $log, "Processing option line: [$option]\n");
# strip leading and trailing whitespace
$option=~s/^\s*//;
$option=~s/\s*$//;
# skip comments
next if ($option=~ /^#/);
# Increment my option line count.
$line_count++;
# Go to the next line if the option line is empty.
next if not $option;
# Split the option up into the name and the value.
($name,$value)=split /\s*=\s*/, $option;
# Record the line for now, but comment this out before release.
record ($conf, $log, "Name: [$name], value: [$value].\n");
# Set my variables depending on the veriable name.
if ($name eq "agent")
{
# This is only used by 'fenced', but I record it for
# potential debugging.
$conf->{node}{agent}=$value;
}
elsif ($name eq "fm")
{
# This is a deprecated argument that should no longer
# be used. Now 'port' should be used.
if (not $conf->{node}{port})
{
# Port isn't set yet, use this value which may
# be replaced if 'port' is set later.
(undef, $value) = split /\s+/,$value;
$conf->{node}{port}=$value;
warn "Warning! The argument 'fm' is deprecated, use 'port' instead. Value: [$value] set for 'port'\n";
}
else
{
# Port was already set, so simply ignore this.
warn "Warning! The argument 'fm' is deprecated, use 'port' instead. Value: [$value] ignored.\n";
}
}
elsif ($name eq "ipaddr")
{
# Record the IP Address or name of the Node Assassin to
# use.
$conf->{node}{ipaddr}=$value;
}
elsif ($name eq "login")
{
# Record the login name that was passed.
$conf->{node}{login}=$value;
}
elsif ($name eq "name")
{
# Depricated argument used formerly for login name.
if (not $conf->{node}{login})
{
# Login isn't set yet, use this value which may
# be replaced if 'login' is seen later.
$conf->{node}{login}=$value;
warn "Warning! The argument 'name' is deprecated, use 'login' instead. Value: [$value] set for 'login'.\n";
}
else
{
# I've already seen the 'login' value so I will
# ignore this value.
warn "Warning! The argument 'name' is deprecated, use 'login' instead. Value: [$value] ignored.\n";
}
}
elsif (($name eq "action") or ($name eq "option"))
{
# It looks like 'option' is going to be deprecated in
# favour of 'action'. If/when that happens, add a warn.
$conf->{node}{action}=$value;
}
elsif ($name eq "passwd")
{
# This is the login password.
$conf->{node}{passwd}=$value;
}
elsif ($name eq "passwd_script")
{
# This is the path to the script that will return the
# password to the agent. At this time, this is not
# implemented.
$conf->{node}{passwd_script}=$value;
}
elsif ($name eq "port")
{
# This sets the port number to act on.
$conf->{node}{port}=$value;
}
else
{
warn "Illegal name in option: [$option] at line: [$line_count]\n";
$bad=1;
}
}
return ($bad);
}
# This function simply prints messages to both the log and to stdout.
sub record
{
my ($conf, $log, $msg)=@_;
print $log $msg;
print $msg if not $conf->{'system'}{quiet};
return(0);
}
# When asked to 'monitor' or 'list', do this... whatever 'this' is. All I know
# is that it should not generate output.
sub show_list
{
my ($conf, $log)=@_;
### MADI: No idea what will be needed here, so here are both queries.
### Make them available elsewhere if not used here.
record($conf, $log, "Checking states:\n");
my @state_out=$conf->{node}{handle}->cmd("00:0");
foreach my $line (@state_out)
{
# record($conf, $log, $line);
}
record($conf, $log, "Done.\n");
# Query states and Node Assassin info.
record($conf, $log, "Checking Node Assassin info:\n");
my @info_out=$conf->{node}{handle}->cmd("00:1");
my $node_name="";
foreach my $line (@info_out)
{
record($conf, $log, $line);
$node_name=$1 if $line=~/- Node Name: ..... (.*)/;
}
record($conf, $log, "Node name: [$node_name]\n");
record($conf, $log, "Done.\n");
do_exit($conf, $log, 0);
}
# This queries the Node Assassin and returns the state of the requested node.
sub show_state
{
my ($conf, $log)=@_;
my @state_out=$conf->{node}{handle}->cmd("00:0");
my $state="";
my $node=$conf->{node}{port};
foreach my $line (@state_out)
{
chomp;
my $line=$_;
my ($state)=($line=~/- Node $node: (.*?)/);
if ($state)
{
$state=lc($state)=~/fenced/ ? 2 : 0;
last;
}
}
# No state means something went wrong while talking to the Node
# Assassin.
$state=1 if (($state != 0) && ($state != 2));
# As per: http://sources.redhat.com/cluster/wiki/FenceAgentAPI
# The exit state must be:
# 0 = Node is running
# 1 = Failed to contact fence, unknown state.
# 2 = Node is fenced.
do_exit($conf, $log, $state);
}
# This prints the version information of this fence agent and of any configured
# fence devices.
sub version
{
my ($conf, $log)=@_;
# Print the Fence Agent version first.
print "Fence Agent: ......... Node Assassin ver. $conf->{'system'}{agent_version}\n";
print "Configured Nodes: .... $conf->{'system'}{nodes}\n";
for my $node (1..$conf->{'system'}{nodes})
{
print " - Node $node Name: .. $conf->{node}{$node}{name}\n";
print " - Node $node IP: .... $conf->{node}{$node}{ip}\n";
print " - Node $node Port: .. $conf->{node}{$node}{port}\n";
print " - Node $node MAC: ... $conf->{node}{$node}{mac}\n";
print " - Node $node Netmask: $conf->{node}{$node}{ip}\n";
print " - Node $node Gateway: $conf->{node}{$node}{ip}\n";
}
do_exit($conf, $log, 0);
}
###############################################################################
# Private functions below here. #
###############################################################################
### Contributed by Shaun Fryer and Viktor Pavlenko by way of TPM.
# This is a helper to the above '_add_href' method. It is called each time a
# new string is to be created as a new hash key in the passed hash reference.
sub _add_hash_reference
{
my $href1=shift;
my $href2=shift;
for my $key (keys %$href2)
{
if (ref $href1->{$key} eq 'HASH')
{
_add_hash_reference($href1->{$key}, $href2->{$key});
}
else
{
$href1->{$key}=$href2->{$key};
}
}
}
### Contributed by Shaun Fryer and Viktor Pavlenko by way of TPM.
# This takes a string with double-colon seperators and divides on those
# double-colons to create a hash reference where each element is a hash key.
sub _make_hash_reference
{
my $href=shift;
my $key_string=shift;
my $value=shift;
# print "variable: [$key_string], value: [$value]\n";
my $chomp_root=0;
if ($chomp_root) { $key_string=~s/\w+:://; }
my @keys = split /::/, $key_string;
my $last_key = pop @keys;
my $_href = {};
$_href->{$last_key}=$value;
while (my $key = pop @keys)
{
my $elem = {};
$elem->{$key} = $_href;
$_href = $elem;
}
_add_hash_reference($href, $_href);
}
1;
Input, advice, complaints and meanderings all welcome! | ||||
Digimer | digimer@alteeve.ca | https://alteeve.ca/w | legal stuff: | |
All info is provided "As-Is". Do not use anything here unless you are willing and able to take resposibility for your own actions. © 1997-2013 | ||||
Naming credits go to Christopher Olah! | ||||
In memory of Kettle, Tonia, Josh, Leah and Harvey. In special memory of Hannah, Jack and Riley. |