#!/usr/bin/perl # # TrippLite SNMP Fence Agent # # Core functions; # # Eric Knific (iggi) # eknific@etshost.com # www.etshost.com / www.ericknific.com / iggi.me # # FenceAgentAPI adaption; # # Digimer; digimer@digimer.ca # http://digimer.ca # # This software is released under the GPL v2. See the LICENSE file in the # configuration directory for a copy of the GPL v2. # # Bugs; # - None known, many expected # # Warnings; # - The TrippLite PDUs are notorious for having delays in state change actions # and state change detection. This makes them sub-optimal as fence devices. # If you use one, expect fence actions to take up to 30 seconds (with an # averaage of about 15 seconds in practice). # # Requirements; # - EL6 (Net::SNMP is not available in EL6 at this time and must be installed # via CPAN); # yum install perl && cpan -i Net::SNMP # - Fedora 15+ # yum install perl perl-Net-SNMP # # Notes; # - SNMP return codes; # 1 = Off # 2 = On # - Actions # 1 = Off # 2 = On # - FenceAgentAPI exit codes; # See; https://fedorahosted.org/cluster/wiki/FenceAgentAPI#agent_ops # # ToDo: # - Make the community an optional argument. # - Make calls to multiple ports asynchronous. # - Implement a timeout (-t, --timeout) before creating the lock. # # Play safe! use strict; use warnings; # IO::Handle is used for logging and Net::SNMP is used for communicating with # the TrippLite device. use IO::Handle; use Net::SNMP; # Catch signals for clean exits. $SIG{INT} = \&_catch_sig; $SIG{TERM} = \&_catch_sig; # These are the default values and will be over-written by the config file's # variables which in turn can, in some cases, be over-written by command line # arguments. my $conf={ 'system' => { agent_version => "1.0 alpha", version => 0, list => "", monitor => "", tl_id => 0, tl_num => 1, got_cla => 0, # This is set if command line arguments are read. quiet => 0, verbose => 0, debug => 0, lock_timeout => 120, lock_file => "/tmp/fence_tripplite_snmp.lock", 'log' => "/var/log/fence_tripplite_snmp.log", state_timeout => 30, # This is the maximum number of seconds to wait for a state change confirmation. }, path => { snmpset => "/usr/bin/snmpset", snmpget => "/usr/bin/snmpget", }, snmp => { ipaddr => "", tcp_port => 161, login => "", password => "", node => 0, password_script => "", action => "", agent => "", # This is only used by 'fenced' tl_name => "", # This is used for the 'list' function. handle => "", max_node => 0, oid_control => ".1.3.6.1.4.1.850.100.1.10.2.1.4.", oid_status => ".1.3.6.1.4.1.850.100.1.10.2.1.2.", community => "tripplite", } }; # Log file for output. my $log=IO::Handle->new(); print "Opening: [$conf->{'system'}{'log'}] for logging.\n" if $conf->{'system'}{debug}; open ($log, ">>$conf->{'system'}{'log'}") || die "Failed to open: [$conf->{'system'}{'log'}] for writing; Error: $!\n"; record ($conf, $log, "\nfence_tripplite_snmp called at: [".get_date_time($conf)."]\n"); # Set $log and STDOUT to hot (unbuffered) output. if (1) { select $log; $|=1; select STDOUT; $|=1; } # If this gets set in the next two function, the agent will exit. my $bad=0; # Read in arguments from the command line. ($bad)=read_cla($conf, $log, $bad); # Now read in arguments from STDIN, which is how 'fenced' passes arguments. ($bad)=read_stdin($conf, $log, $bad); # This loops until the lock file is removed. This is a terrible hack to deal # with two different sources calling this at once. This will be removed as soon # as I update NAOS with a proper semaphore. my $loop=0; # if the lock file is more than 300 seconds old, delete it as stale. while (-e $conf->{'system'}{lock_file}) { my $age=time-(stat($conf->{'system'}{lock_file}))[9]; record($conf, $log, __LINE__."; Waiting; lock: [$conf->{'system'}{lock_file}] is: [$age] second(s) old.\n"); if ($age > $conf->{'system'}{lock_timeout}) { record($conf, $log, __LINE__."; lock: [$conf->{'system'}{lock_file}] is now stale, removing it.\n"); unlink $conf->{'system'}{lock_file} or die "Failed to remove lock file: [$conf->{'system'}{lock_timeout}], error: $!\n"; record($conf, $log, __LINE__."; Removed.\n"); } sleep 1; } my $lock=IO::Handle->new(); open ($lock, ">$conf->{'system'}{lock_file}") || die "Failed to create lock file: [$conf->{'system'}{lock_file}]; Error: $!\n"; # This makes sure the node ID is 0 if not set. $conf->{snmp}{node}=0 if not $conf->{snmp}{node}; record($conf, $log, "Will work on node: [$conf->{snmp}{node}]\n") if $conf->{'system'}{debug}; if ($bad) { unlink $conf->{'system'}{lock_file}; die "Exiting on errors.\n"; } my @ny=("no", "yes"); record($conf, $log, "TrippLite: ..... [$conf->{snmp}{ipaddr}].\n"); record($conf, $log, "Community: ..... [$conf->{snmp}{community}].\n"); record($conf, $log, "Node: .......... [$conf->{snmp}{node}].\n"); #record($conf, $log, "Login: ......... [$conf->{snmp}{login}].\n"); # record($conf, $log, "Password: ...... [$conf->{snmp}{password}].\n"); #record($conf, $log, "Password: ...... [--].\n"); record($conf, $log, "Action: ........ [$conf->{snmp}{action}].\n"); record($conf, $log, "Version Request: [".$ny[$conf->{'system'}{version}]."].\n"); record($conf, $log, "Done reading args.\n"); # If I've been asked to show the version information, do so and then exit. if ($conf->{'system'}{version}) { version($conf, $log); do_exit($conf, $log, 0); } # If I've been asked to show the metadata XML, do so and then exit. if ($conf->{snmp}{action} eq "metadata") { metadata($conf, $log); do_exit($conf, $log, 0); } # Connect to the TrippLite. connect_to_tl($conf, $log); ############################################################################### # What do? # ############################################################################### # When asked to 'monitor' or 'list'. being multi-port, this will return a CSV # of outlets and their current state. record($conf, $log, "Action: ........ [$conf->{snmp}{action}].\n") if $conf->{'system'}{debug}; if (($conf->{snmp}{action} eq "monitor") or ($conf->{snmp}{action} eq "list")) { record($conf, $log, "Calling the 'show_list' function.\n") if $conf->{'system'}{debug}; show_list($conf, $log); do_exit($conf, $log, 0); } # If I made it this far, I am setting a state. Sort out what state from the # values in my conf->{snmp} hash. record($conf, $log, "Setting node: [$conf->{snmp}{node}] to action: [$conf->{snmp}{action}] using the TrippLite: [$conf->{snmp}{ipaddr}] using the community: [$conf->{snmp}{community}]\n") if $conf->{'system'}{debug}; # Convert the action into TrippLite protocol arguments. my $exit_code=process_action($conf, $log); record($conf, $log, "All calls complete, exiting.\n") if $conf->{'system'}{debug}; # Cleanup and exit. # print "Going into do_exit().\n"; do_exit($conf, $log, $exit_code); # print "Returned from do_exit().\n"; ############################################################################### # Here be functions. # ############################################################################### # This connects to a TrippLite and puts the handle in # $conf->{'system'}{handle}. sub connect_to_tl { my ($conf, $log)=@_; # Get the SNMP session. # print "Opening SNMP session at: [$conf->{snmp}{ipaddr}:$conf->{snmp}{tcp_port}], community: [$conf->{snmp}{community}]\n"; ($conf->{snmp}{handle}, my $error) = Net::SNMP->session( '-hostname' => "$conf->{snmp}{ipaddr}", '-community' => "$conf->{snmp}{community}", '-port' => "$conf->{snmp}{tcp_port}", ); if (not defined $conf->{snmp}{handle}) { record($conf, $log, "SNMP connection failed with error; $error\n", 1); do_exit($conf, $log, 1); } record($conf, $log, "tl::handle: .... [$conf->{snmp}{handle}]\n") if $conf->{'system'}{debug}; return ($conf->{snmp}{handle}); } # This cleanly exits the agent. sub do_exit { my ($conf, $log, $exit_status)=@_; $exit_status=9 if not defined $exit_status; # Remove the lock. unlink "$conf->{'system'}{lock_file}"; # Close the TrippLite and log file handle, if they exist. $conf->{snmp}{handle}->close() if $conf->{snmp}{handle}; $log->close() if $log; exit ($exit_status); } # This gets the state for the requested node and return the state as 'on' or # 'off'. sub get_state { my ($conf, $log, $node)=@_; # Get the state of the port. (1=off, 2=on) my $snmp_string=$conf->{snmp}{oid_status}.$node; my @oid_status=($snmp_string); my $result = $conf->{snmp}{handle}->get_request( '-varbindlist' => \@oid_status, ); my $state=$result->{$snmp_string}; if (($state eq "1") || ($state eq "2")) { $conf->{node}{$node}{state} = $state eq "1" ? "off" : "on"; } else { # Unknown state. record($conf, $log, "Node '$node' is in the unknown state: [$state]. Expected '1' (off) or '2' (on).\n"); do_exit($conf, $log, 1); } # Return the hash reference. return ($conf->{node}{$node}{state}); } # This disables and verifies the specified outlet. Failure returns 1, success # returns 0. It is up to the caller to react appropriately. # NOTE: I know I could merge this and the set_state_on() but I wanted to # separate them to simplify the 'reboot' action (where only 'off' needs # to succeed). sub set_state_off { my ($conf, $log, $node)=@_; # Set the state of the port to off. (1=off, 2=on) my $snmp_string=$conf->{snmp}{oid_control}.$node; $conf->{snmp}{handle}->set_request($snmp_string, INTEGER, 1); my $took_time; for (1..$conf->{'system'}{state_timeout}) { $took_time=$_; sleep 1; last if get_state($conf, $log, $node) eq "off"; } # If the node is on (state 2), return 1 (failure). Otherwise return 0 # (success). my $return=0; if ($conf->{node}{$node}{state} eq "off") { record($conf, $log, "SUCCESS: Disabled outlet: [$node]. Confirmed in: [$took_time seconds].\n"); } else { record($conf, $log, "ERROR: Failed to disable (or confirm) that outlet: [$node] was turned off.\n", 1); $return=1; } return ($return); } # This enables and verifies the specified outlet. Failure returns 1, success # returns 0. It is up to the caller to react appropriately. sub set_state_on { my ($conf, $log, $node)=@_; # Set the state of the port to on. (1=off, 2=on) my $snmp_string=$conf->{snmp}{oid_control}.$node; $conf->{snmp}{handle}->set_request($snmp_string, INTEGER, 2); my $took_time; for (1..$conf->{'system'}{state_timeout}) { $took_time=$_; sleep 1; last if get_state($conf, $log, $node) eq "on"; } # If the node is on (state 2), return 1 (failure). Otherwise return 0 # (success). my $return=0; if ($conf->{node}{$node}{state} eq "on") { record($conf, $log, "SUCCESS: Enabled outlet: [$node]. Confirmed in: [$took_time seconds].\n"); } else { record($conf, $log, "ERROR: Failed to enable (or confirm) that outlet: [$node] was turned on.\n", 1); $return=1; } return ($return); } # This returns the 'help' message. sub help { my ($conf, $log)=@_; # Point the user at the man page. print "See 'man fence_na' for instructions on using the TrippLite Fence Agent.\n"; do_exit($conf, $log, 0); } # This simply prints the 'metadata' XML data to STDOUT. sub metadata { my ($conf, $log)=@_; print q` fence_tripplite_snmp is a network-connected, multiport power fencing device. WARNING! TrippLite PDUs can take a long time to act on and confirm state changes. This makes them less than idea as a cluster fence device. Expect 'reboot' actions to take up to 40 seconds to complete. When possible, configure these devices as backup/secondary fence devices when possible. TrippLite switched PDUs are multiport, network-switchable power distribution units. This agent accesses the device using the device's hostname and SNMP community name only. The login credentials are not used, so please be sure to configure the SNMP's NMS community to be sufficiently restrictive if the PDU is on a non-secure network. Resolvable host name or IP of the TrippLite. Set the community name. Default 'tripplite'. Password. *Not implemented* Script to retrieve password. *Not implemented* The port (outlet number) to act on. User/login name. *Not implemented* Action (operation) to take; off, on, reboot, status, monitor, list, metadata Print detailed agent progress to STDOUT. (default) Supress all output to STDOUT, including critical messages. Check logfile if used. Print extensive debug information to STDOUT and to the log file. Prints the TrippLite fence agent version and exits. `; # Done, exit. do_exit($conf, $log, 0); } # This error message is printed when there was a connection problem with a # given TrippLite. sub no_connection_error { my ($conf, $log, $tl_id)=@_; record ($conf, $log, "\nERROR: Unable to query TrippLite: [$conf->{snmp}{$tl_id}{tl_name}]!\n", 1); record ($conf, $log, "ERROR: Please check that it is connected, that the information in\n", 1); record ($conf, $log, "ERROR: '/etc/cluster/fence_na.conf' is accurate and that the proper\n", 1); record ($conf, $log, "ERROR: configuration has be uploaded to the device.\n\n", 1); return (0); } # This handles the actual actions. sub process_action { my ($conf, $log)=@_; record($conf, $log, "In the 'process_action' function.\n") if $conf->{'system'}{debug}; my $exit_code=0; # Make this more readable. my $action=$conf->{snmp}{action}; my $node=$conf->{snmp}{node}; record($conf, $log, "action: [$action], port: [$node]\n") if $conf->{'system'}{debug}; # The following actions require a port. Error if I don't have one. if ($conf->{snmp}{node} == 0) { # These are the incompatible calls. if (($action eq "on") || ($action eq "off") || ($action eq "reboot") || ($action eq "status")) { record($conf, $log, "\nERROR! Action request: [$action] requires a node's port number!\n", 1) if $conf->{'system'}{debug}; record($conf, $log, "ERROR: I got: [$conf->{snmp}{node}] which does not seem to be valid.\n\n", 1); do_exit($conf, $log, 1); } } # Make sure my call order is clear. if ($action eq "on") { # Unfence the node. if (get_state($conf, $log, $conf->{snmp}{node}) eq "on") { # MADI: Turn on the port. record($conf, $log, "SUCCESS: Outlet: [$conf->{snmp}{node}] was already 'on'.\n"); do_exit($conf, $log, 0); } else { # Call the PDU. my $exit_code=set_state_on($conf, $log, $conf->{snmp}{node}); do_exit($conf, $log, $exit_code); } } elsif ($action eq "off") { # Fence the node. if (get_state($conf, $log, $conf->{snmp}{node}) eq "off") { record($conf, $log, "SUCCESS: Outlet: [$conf->{snmp}{node}] was already 'off'.\n"); do_exit($conf, $log, 0); } else { # Call the PDU. my $exit_code=set_state_off($conf, $log, $conf->{snmp}{node}); do_exit($conf, $log, $exit_code); } } elsif ($action eq "reboot") { # I don't do this gracefully because the API says this should # be an 'off' -> 'on' process, and 'off' is fence... my $success=1; # Fail until proved otherwise my $state=get_state($conf, $log, $conf->{snmp}{node}); # If the port is off already, succeed and proceed to boot. if ($state eq "on") { # Fence. $success=set_state_off($conf, $log, $conf->{snmp}{node}); } else { # Already off. Thus, we win the game. record($conf, $log, "SUCCESS: Outlet: [$conf->{snmp}{node}] was already 'off'. Will now attempt to enable.\n"); $success=0; } # If the 'off' was achieved, go for bonus points and re-enable # the outlet. if ($success == 0) { # I'm not concerned about the exit code here. set_state_on($conf, $log, $conf->{snmp}{node}); } do_exit($conf, $log, $success); } elsif ($action eq "status") { # This needs to return; # 0 = PDU is accessible and outlet is on. # 1 = PDU is not accessible. (this exit code happens if connect_to_tl() failes) # 2 = PDU is accessible and outlet is off. if (get_state($conf, $log, $conf->{snmp}{node}) eq "off") { do_exit($conf, $log, 2); } else { do_exit($conf, $log, 0); } } ### ACTIONS BELOW HERE ARE OUTSIDE OF THE FenceAgentAPI! elsif ($action eq "on_all") { # Enable all outlets that are off. my $success=0; foreach my $node (1..8) { if (get_state($conf, $log, $conf->{snmp}{node}) eq "off") { # Failure is not fatal, but a failure should be # result in a failure exit code. my $exit_code=set_state_on($conf, $log, $conf->{snmp}{node}); $success=1 if $exit_code==1; } } do_exit($conf, $log, $exit_code); } elsif ($action eq "off_all") { # Disable all outlets that are on. my $success=0; foreach my $node (1..8) { if (get_state($conf, $log, $conf->{snmp}{node}) eq "on") { # Failure is not fatal, but a failure should be # result in a failure exit code. my $exit_code=set_state_off($conf, $log, $conf->{snmp}{node}); $success=1 if $exit_code==1; } } do_exit($conf, $log, $exit_code); } else { record($conf, $log, "ERROR: Unknown action request: [$action]!\n", 1); do_exit($conf, $log, 1); } return ($exit_code); } # Read in command line arguments sub read_cla { my ($conf, $log, $bad)=@_; # Loop through the passed arguments, if any. record($conf, $log, "Got args:\n") if $conf->{'system'}{debug}; my $set_next=""; foreach my $arg (@ARGV) { record($conf, $log, "[$arg]\n") if $conf->{'system'}{debug}; $conf->{'system'}{got_cla}=1; # 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->{snmp}{$set_next}=$arg; record($conf, $log, "Setting: 'tl::$set_next': [$conf->{snmp}{$set_next}]\n") if $conf->{'system'}{debug}; # 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=~/--version/) { # Print the version information and then exit. $conf->{'system'}{version}=1; record($conf,$log,"Setting version\n") if $conf->{'system'}{debug}; } elsif ($arg=~/-v/) { # Print the version information and then exit. $conf->{'system'}{verbose}=1; record($conf,$log,"verbose output enabled\n") if $conf->{'system'}{debug}; } elsif ($arg=~/-q/) { # Suppress all messages, including critical messages, from STDOUT. $conf->{'system'}{quiet}=1; } elsif ($arg=~/-d/) { # Enable debug mode. $conf->{'system'}{debug}=1; } elsif ($arg=~/^-/) { $arg=~s/^-//; ### These are the switches set by Red Hat. if ($arg eq "a") { # This is the IP address or hostname of the # TrippLite to call. $set_next="ipaddr"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg eq "l") { # This is the login name. $set_next="login"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg eq "c") { # This is the SNMP community. $set_next="community"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg 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="password"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg eq "n") { # This is the node to work on. $set_next="node"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg eq "o") { # This is the action to take. $set_next="action"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } elsif ($arg 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="password_script"; record ($conf, $log, "Next argument will be stored in: [$set_next]\n") if $conf->{'system'}{debug}; } } else { ### MADI: I might want to pick up arguments via multiple lines. # Bad argument. record($conf, $log, "\nERROR: Argument: [$arg] is not valid!\n"); record($conf, $log, "ERROR: Please run: [man fence_na] to see a list of valid arguments.\n\n"); $bad=1; } } } # Read arguments from STDIN. This is adapted from the 'fence_brocade' agent. sub read_stdin { my ($conf, $log, $bad)=@_; return (0) if $conf->{'system'}{got_cla}; 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") if $conf->{'system'}{debug}; # 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. my ($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") if $conf->{'system'}{debug}; # 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->{snmp}{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->{snmp}{node}) { # Port isn't set yet, use this value which may # be replaced if 'port' is set later. (undef, $value) = split /\s+/,$value; $conf->{snmp}{node}=$value; record($conf, $log, "Warning! The argument 'fm' is deprecated, use 'port' instead.\n", 1); record($conf, $log, "Warning! Value: [$value] set for 'port'\n", 1); } else { # Port was already set, so simply ignore this. record($conf, $log, "Warning! The argument 'fm' is deprecated, use 'port' instead.\n", 1); record($conf, $log, "Warning! Value: [$value] ignored.\n", 1); } } elsif ($name eq "ipaddr") { # Record the IP Address or name of the TrippLite to # use. $conf->{snmp}{ipaddr}=$value; } elsif ($name eq "community") { # Record the SNMP community. $conf->{snmp}{'community'}=$value; } elsif ($name eq "login") { # Record the login name that was passed. $conf->{snmp}{login}=$value; } elsif ($name eq "name") { # Depricated argument used formerly for login name. if (not $conf->{snmp}{login}) { # Login isn't set yet, use this value which may # be replaced if 'login' is seen later. $conf->{snmp}{login}=$value; record($conf, $log, "Warning! The argument 'name' is deprecated, use 'login' instead.\n", 1); record($conf, $log, "Warning! Value: [$value] set for 'login'.\n", 1); } else { # I've already seen the 'login' value so I will # ignore this value. record($conf, $log, "Warning! The argument 'name' is deprecated, use 'login' instead.\n", 1); record($conf, $log, "Warning! Value: [$value] ignored.\n", 1); } } elsif (($name eq "action") or ($name eq "option")) { # 'option' is deprecated. record($conf, $log, "Please use 'action', not 'option', as the later is deprecated.\n", 1) if $name eq "option"; $conf->{snmp}{action}=$value; } elsif ($name eq "password") { # This is the login password. $conf->{snmp}{password}=$value; } elsif ($name eq "password_script") { # This is the path to the script that will return the # password to the agent. At this time, this is not # implemented. $conf->{snmp}{password_script}=$value; } elsif ($name eq "port") { # This sets the port number to act on. $conf->{snmp}{node}=$value; } elsif ($name eq "nodename") { # This is passed by 'fenced' via 'cluster.conf' as of # cluster version 3, but it's not yet documented. $conf->{'system'}{nodename}=$value; } elsif ($name eq "verbose") { # This is passed by 'fenced' via 'cluster.conf' as a # custom argument to supress output to STDOUT. $conf->{'system'}{verbose}=1; } else { record($conf, $log, "\nERROR: Illegal name in option: [$option] at line: [$line_count]\n\n", 1); # 'rohara' from #linux-cluster suggested it's better to # simply ignore unknown input, as that is the behaviour # the fenced authors expect. #$bad=1; } } return ($bad); } # This function simply prints messages to both the log and to stdout. sub record { my ($conf, $log, $msg, $critical)=@_; $critical=0 if not $critical; # The log file gets everything. print $log $msg; print $msg if $conf->{'system'}{verbose}; # Critical messages can only be surpressed by 'quiet'. print $msg if (($critical) && (not $conf->{'system'}{quiet})); return(0); } # When asked to 'monitor' or 'list', show a CSV of all nodes and their aliases, # when found in the config file. sub show_list { my ($conf, $log)=@_; record($conf, $log, "In 'show_list' function.\n") if $conf->{'system'}{debug}; # Get an up to date list of the ports. # MADI: Make this loop until unexpected result or error is found to # account for PDUs with >8 ports. foreach my $node (1..8) { my $state=get_state($conf, $log, $node); record ($conf, $log, "$node,$state\n", 1); } do_exit($conf, $log, 0); } # 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. record ($conf, $log, "Fence Agent ver. $conf->{'system'}{agent_version}\n", 1); # ToDo: Query the PDU for details. record ($conf, $log, "TrippLite PDU details not currently recorded.\n", 1); do_exit($conf, $log, 0); } # This returns the current date and time. sub get_date_time { my ($conf)=@_; # Get the current date and time, my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); # Format it to 'YYYY-MM-DD HH:MM:SS'. my $now=(1900+$year)."-".sprintf("%02d", ($mon+1))."-".sprintf("%02d", $mday)." ".sprintf("%02d", $hour).":".sprintf("%02d", $min).":".sprintf("%02d", $sec); return($now); } # Catch SIG, move zig! sub _catch_sig { my $signame = shift; record ($conf, $log, "fence_tripplite_snmp process with PID $$ Exiting on SIG${signame}.\n", 1); do_exit($conf, $log, 1); }