#!/usr/bin/perl
#
# Wrapper to aoemask for use in a cluster fence.
# Copyright (C) 2007 Brian Weck (bweck@weck.net)
#
# This script utilizes the 'aoemask' utility from:
# http://www.coraid.com/support/sr/
# which is written by Sam Hopkins.
#
# =======================================================================
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# =======================================================================
#
# ~~~~~ REVISION HISTORY ~~~~~
# 2007-08-17 - v1 - Brian Weck
# Initial release.
#
# ~~~~~ OVERVIEW ~~~~~
# Initial mask settings on the AoE device should contain the set of all
# MAC addresses using the AoE device from the cluster.
#
# When a fence operation occurs on a node, the fenced node's mac address is
# removed from the mask list on the AoE device. This method is conceptually
# the same as fencing via a fabric switch.
#
# Once a node is fenced, the MAC address is removed from the mask list on
# the AoE device. When the fenced node is ready to rejoin the cluster,
# the MAC address must be added to the device's mask list using this
# script or using aoemask.
#
# Script returns 0 on SUCCESS and non-zero otherwise.
#
# ~~~~~ INSTALLATION ~~~~~
# Add this file as /sbin/fence_aoemask directory and ensure the file has
# simliar permissions as the other fence_* agents.
#
# ~~~~~ CONFIGURATION ~~~~~
# This software operates on a single shelf / slot at a time. In order to
# fence multiple shelf and slots the user should create multiple fences.
#
# e.g. a cluster.conf snippet.
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# Command line options:
# see man aoemask.8
#
# stdin options (passed from fenced):
# shelf= |
# slot= |
# interface= |
# mac= |
# [ action=(disable|enable) ] | default is defined in $opt_action
# [ debug= ] |
# [ exclusive= ] |
# [ list= ] |
# [ spoof= ] | Spoof behavior is to assume success always.
# [ timeout= ] |
# [ verbose= ] | Option is used to increase logging of fence agent.
#
# Define where you aoemask binary lives if it is not in the path.
my $aoemask_prog="/usr/local/sbin/aoemask";
my $opt_action = 'disable'; # Default fence action
# Get the script name from $0 and strip directory names
$_=$0;
s/.*\///;
my $proggy = $_;
#
#
#
my $aoemask=$aoemask_prog;
my $opt_list = 1;
my $opt_debug = 1;
my $opt_verbose = 0;
#
sub _log
{
($msg)=@_;
print STDOUT $msg;
}
#
sub exit_success
{
my $rc = 0;
_log "$proggy returning $rc\n" if $opt_verbose;
exit $rc;
}
#
sub exit_fail
{
my $rc = 1;
_log "$proggy returning $rc\n" if $opt_verbose;
exit $rc;
}
#
sub fail_usage
{
($msg)=@_;
_log $msg."\n" if $msg;
_log "Please use see usage.\n";
exit_fail();
}
#
# If running command line, pass args as specified directly to aoemask
if (@ARGV > 0)
{
# Check for min number of args, 5
if( @ARGV < 5 )
{
$aoemask .= " -h";
}
else
{
# stub in the args
foreach $i (0 .. $#ARGV)
{
$aoemask .= " $ARGV[$i]";
}
}
}
else # Running via fenced, read the args in from stdin
{
read_stdin_as_options();
# validate required args are present
fail_usage "No shelf specified." unless defined $opt_shelf;
fail_usage "No slot specified." unless defined $opt_slot;
fail_usage "No interface specified." unless defined $opt_interface;
fail_usage "No mac specified." unless defined $opt_mac;
$aoemask .= " -d" if defined $opt_debug;
$aoemask .= " -e" if defined $opt_exclusive;
$aoemask .= " -l" if defined $opt_list;
$aoemask .= " -s $opt_spoof" if defined $opt_spoof;
$aoemask .= " -w $opt_timeout" if defined $opt_timeout;
$aoemask .= " $opt_shelf $opt_slot $opt_interface";
$_=$opt_action;
if (/enable/) { $aoemask .= " +$opt_mac"; }
elsif (/disable/) { $aoemask .= " -$opt_mac"; }
else
{
# This would only be reached if in the cluster.conf one specified action=
fail_usage "Unknown action: $_";
}
}
_log "$proggy executing '$aoemask'\n" if $opt_verbose;
#
# aoemask (release 1) always returns an exit code of 1
# if aoemask returned success or failure based on the response; could as follows:
#
# system($aoemask);
# $rc = ($? >> 8) & 0xff;
# exit $rc;
#
# therefore, we must ensure the listing function is performed and grep'd
open(FH, "$aoemask 2>&1 |");
@lines = ;
close FH;
#
if ($opt_verbose)
{
_log "-- begin read response --\n";
foreach $line (@lines) { chop $line; _log "$line\n"; }
_log "-- end read response --\n";
}
#
if ($opt_user_says_list)
{
@x = grep { /$opt_shelf\.$opt_slot/ } @lines;
_log foreach @x;
}
#
# If spoofing, nothing is returned, we assume success.
exit_success() if $opt_spoof;
# check output of aoemask for proper values depending on action.
if( ($opt_action =~ /enable/) && (grep { /$opt_mac/ } @lines) )
{
_log "action is to enable and found mac $opt_mac in list"."\n" if $opt_verbose;
exit_success();
}
elsif( ($opt_action =~ /disable/) && !(grep { /$opt_mac/ } @lines) )
{
# here's a caveat .. which requires the debug flag to be on.
# if one is performing a disable, and specify an invalid slot / shelf / interface
# a grep for the mac will not show and therfore a return success.
#
# Workaround: need to check for an additional string, of:
# read -1 bytes
#
if( ! grep { /read -1 bytes/} @lines )
{
# did not read that string; all is ok.
_log "action is to disable and did not find mac $opt_mac in list"."\n" if $opt_verbose;
exit_success();
}
else
{
_log "No bytes were read from '$aoemask'.\n";
_log "Check the slot|shelf|interface configs.\n"
}
}
# If none of the above matched, we failed.
exit_fail();
#
# Parse the stdin options
#
sub read_stdin_as_options()
{
my $opt;
my $line = 0;
while( defined($in = <>) )
{
$_ = $in;
chomp;
# strip leading and trailing whitespace
s/^\s*//;
s/\s*$//;
# skip any comments
next if /^#/;
$line+=1;
$opt=$_;
next unless $opt;
($name,$val)=split /\s*=\s*/, $opt;
if ( $name eq "" )
{
_log "parse error: illegal name in option $line\n";
exit_fail();
}
# shelf=
# slot=
# interface=
# mac=
# action=(disable|enable)
elsif ($name eq "shelf" )
{
$opt_shelf = $val;
}
elsif ($name eq "slot" )
{
$opt_slot = $val;
}
elsif ($name eq "interface" )
{
$opt_interface = $val;
}
elsif ($name eq "mac" )
{
$opt_mac = $val;
# pull out any ':' if configured as such.
# (even though aoemask can handle it)
$opt_mac =~ s/://g;
# uppercase the alphas
$opt_mac =~ tr/a-z/A-Z/;
}
elsif ($name eq "action")
{
$opt_action = $val;
}
# debug=
# exclusive=
# list=
# spoof=
# timeout=
elsif ($name eq "debug" )
{
$opt_debug = 1;
}
elsif ($name eq "exclusive" )
{
$opt_exclusive = 1;
}
elsif ($name eq "list" )
{
$opt_list = 1;
$opt_user_says_list = 1;
}
elsif ($name eq "spoof" )
{
$opt_spoof = $val;
}
elsif ($name eq "timeout" )
{
$opt_timeout = $val;
}
# verbose=
elsif ($name eq "verbose" )
{
$opt_verbose = 1;
}
}
}