#!/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; } } }