# @(#)57 1.27 src/43haes/lib/perl/libcl_disp.pm, hacmp.wsm, 61haes_r721, 1632A_hacmp721 8/4/16 15:57:19 # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # 61haes_r721 src/43haes/lib/perl/libcl_disp.pm 1.27 # # Licensed Materials - Property of IBM # # Restricted Materials of IBM # # COPYRIGHT International Business Machines Corp. 1004,2016 # All Rights Reserved # # US Government Users Restricted Rights - Use, duplication or # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. # # IBM_PROLOG_END_TAG ########################################################### # Name: libcl_disp.pm # # A Perl library containing routines common to both cldisp # and wsm_cldisp.cgi. ########################################################### package libcl_disp; # Set secure PATH for RBAC compatibility $ENV{"PATH"} = "/usr/bin:/usr/sbin"; # "use strict" will help to keep this code cleaner use strict; use vars qw(@EXPORT @ISA); require Exporter; @ISA = qw(Exporter); @EXPORT = qw( snmpinfoRefresh nodes_in_cluster interfaces_on_node name_of_interface ip_of_interface network_of_ip_addr apps_on_cluster nodes_of_app_of_rg rg_of_app vgs_of_app_of_rg cc_vgs_of_app_of_rg filesystems_of_app_of_rg fast_connect_services_of_app_of_rg startscript_of_app stopscript_of_app service_labels_of_app_of_rg ip_of_service_label network_of_service_label monitors_of_app get_monitor_value state_of_local_clstrmgr state_of_cluster substate_of_cluster state_of_node state_of_interface state_of_network state_of_app_of_rg nodes_providing_app_of_rg calculateDNPPFallover calculateFalloverToNextPriorityNode calculateOfflineFallover initRgPrefs fallover_node_for_app_of_rg state_of_service_label translate translateCustomAttr probeStatic probeDynamic detaint $clusterName $HACMPBASE $ACTIVE $INACTIVE $UP $DOWN $UNKNOWN $UNSTABLE $STABLE $ERROR $RECONFIG $ONLINE $OFFLINE $ACQUIRING $RELEASING $MESSAGE_CL_HIGHEST_FREE_MEM $MESSAGE_CL_HIGHEST_IDLE_CPU $MESSAGE_CL_LOWEST_DISK_BUSY $MESSAGE_USER_SPECIFIED $MESSAGE_UNKNOWN_UNLESS_ONLINE $clusterName $overall_clstrmgr_state $local_clstrmgr_state $clusterState $clusterSubstate @apps $localNodeName $h $r @appStates $c @nodesByApp @providingNodesByApp @falloverNodesOfApps @startscriptsByApp @stopscriptsByApp @serviceIpLabelsOfApps @vgsOfApps @ccVgsOfApps @filesystemsOfApps @fastConnectServicesOfApps $s $t $rg @RgsByApp @serviceIpStates $c2 $rr2 @nodeNames %nodeID $cc2 @interfacesByNode @networksOfIpAddrs @networksOfServiceLabels @interfaceStatesByNode @ipsByNode @nodeStates @networkStates @monitorsOfApps @cllsif $SNMPINFO_SOURCE @serviceIpsOfApps $startupPref $falloverPref $fallbackPref ); # Subroutine declarations are in the same order in which they appear below sub separate($$$$); sub snmpinfoRefresh($); sub snmpinfoRefreshViaSnmpinfo($); sub snmpinfoRefreshViaWsm_cmd_exec($); sub nodes_in_cluster(); sub interfaces_on_node($); sub name_of_interface($$$); sub ip_of_interface($$$); sub network_of_ip_addr($$); sub apps_on_cluster(); sub nodes_of_app_of_rg($$); sub rg_of_app($); sub vgs_of_app_of_rg($$); sub cc_vgs_of_app_of_rg($$); sub filesystems_of_app_of_rg($$); sub fast_connect_services_of_app_of_rg($$); sub startscript_of_app($); sub stopscript_of_app($); sub service_labels_of_app_of_rg($$); sub ip_of_service_label($); sub network_of_service_label($); sub monitors_of_app($); sub get_monitor_value($$); sub state_of_local_clstrmgr(); sub state_of_cluster(); sub substate_of_cluster(); sub state_of_node($); sub state_of_interface($$); sub state_of_network($$); sub state_of_app_of_rg($$); sub nodes_providing_app_of_rg($$); sub calculateDNPPFallover($); sub calculateFalloverToNextPriorityNode(); sub calculateOfflineFallover(); sub initRgPrefs($); sub fallover_node_for_app_of_rg($$); sub state_of_service_label($); sub translate($); sub translateCustomAttr($); sub probeStatic(); sub probeDynamic(); sub detaint($); our $HACMPBASE="/usr/es/sbin/cluster"; # cluster services # IF THESE ARE CHANGED YOU MUST ALSO CHANGE THEM INSIDE THE 'translate' SUBROUTINE our $ACTIVE = 'active'; our $INACTIVE = 'inactive'; # cluster states # IF THESE ARE CHANGED YOU MUST ALSO CHANGE THEM INSIDE THE 'translate' SUBROUTINE our $UP = 'up'; our $DOWN = 'down'; our $UNKNOWN = 'unknown'; # cluster substates # IF THESE ARE CHANGED YOU MUST ALSO CHANGE THEM INSIDE THE 'translate' SUBROUTINE our $UNSTABLE = 'unstable'; our $STABLE = 'stable'; our $ERROR = 'error'; our $RECONFIG = 'reconfig'; # $UNKNOWN (defined above) # app states # IF THESE ARE CHANGED YOU MUST ALSO CHANGE THEM INSIDE THE 'translate' SUBROUTINE our $ONLINE = 'online'; our $OFFLINE = 'offline'; our $ACQUIRING = 'acquiring'; our $RELEASING = 'releasing'; # $ERROR (defined above) # $UNKNOWN (defined above) # IF THESE ARE CHANGED YOU MUST ALSO CHANGE THEM INSIDE THE 'translate' SUBROUTINE our $JOINING = 'joining'; our $LEAVING = 'leaving'; # Messages explaining to which node an application will fallover when the # resource group is set to use a particular dynamic node priority protocol. # Setting these doesn't actually help much because of having to use dspmsg. # these have been addded to cluster.cat, set 47, starting with msg 28 our $MESSAGE_CL_HIGHEST_FREE_MEM = "whichever node has the most free memory"; our $MESSAGE_CL_HIGHEST_IDLE_CPU = "whichever node has the least cpu usage"; our $MESSAGE_CL_LOWEST_DISK_BUSY = "whichever node has the least disk usage"; our $MESSAGE_USER_SPECIFIED = "placement is decided dynamically using a user-specified resource variable"; our $MESSAGE_UNKNOWN_UNLESS_ONLINE = "impossible to determine unless cluster services are active"; # The next three lines declare variables that are reused throughout the program # Care must be taken not to clobber some of these globals, particularly vars: # @snmpinfo, $app, $rg, $r, $c, $rr, $cc, $rr2, $cc2 our (@tmp, @tmp2, @word, $app, $rg, $h, $r, $c, $rr, $cc, $r2, $c2, $rr2, $cc2, $t, $s); our (@scrap, @odm, $ip, $netState); our @snmpinfo; our @cllsif; our @clshowsrv; our $SNMPINFO_SOURCE; # These declarations are arranged by how they relate to one another. Descriptions # of these variables can be found in the locations in which they're initialized. our $clusterName; our $overall_clstrmgr_state; our $local_clstrmgr_state; our $clusterState; our $clusterSubstate; our $localNodeName; our @apps; our @monitorsOfApps; our @nodesByApp; our @RgsByApp; our @nodesOfRg; our @startscriptsByApp; our @stopscriptsByApp; our @appStates; our @providingNodesByApp; our @falloverNodesOfApps; our @serviceIpLabelsOfApps; our @serviceIpStates; our @networksOfServiceLabels; our @serviceIpsOfApps; our @vgsOfApps; our @ccVgsOfApps; our @filesystemsOfApps; our @fastConnectServicesOfApps; our @nodeNames; our @nodeStates; our %nodeID; our @interfacesByNode; our @interfaceStatesByNode; our @networksOfIpAddrs; our @networkStates; our @ipsByNode; # These store resource group policies our $startupPref; our $falloverPref; our $fallbackPref; our @addrs_found = ""; our @names_found = ""; ##################################################### # Function: # # separate # # Description: # Given FS (field separator) and token delimitors (which identify the # beginning and end of the string that is NOT to be tokenized/split), # the function tokenize the given input sequence. Here FS is ':' and # delimitors are '[' and ']' signifying that string enclosed within # '[' and ']' will not be tokenized inspite of ':' separator in between. # # Arguments: # # $_[0] : input array # $_[1] : input separator (field separator) # $_[2] : character indicating input delimitor start # $_[3] : character indicating input delimitor end # # Returns: # Input tokenized as per the rules of parsing. ##################################################### sub separate ($$$$) { my (@interm_array, @op_array, @ip_array, $rec); my $ip_array = $_[0]; my $ip_sep = $_[1]; my $ip_delm_start= "\\".$_[2]; my $ip_delm_end = "\\".$_[3]; my $rec=""; my $i = 0; my $flag = 0; @interm_array = split(/$ip_sep/, $ip_array); foreach my $elem (@interm_array) { use Switch; switch ($elem) { case (/$ip_delm_start/) { $flag = 1 } case (/$ip_delm_end/) { $flag = 2 } } switch ($flag) { case 0 { $op_array[$i++] = $elem} case 1 { $rec = $rec.$elem.$ip_sep} case 2 { $op_array[$i++] = $rec.$elem; $rec=""; $flag=0;} } } return @op_array; } ##################################################### # Function: # snmpinfoRefresh # # Description: # Determines how to query the the MIB for the # needed info. If the this process is being run # by root it will use 'snmpinfo', but in the case # of wsm_cldisp the user will be 'nobody' so # wsm_cmd_exec will be used to query the info. # # Arguments: # Name of the output file to create ##################################################### sub snmpinfoRefresh($){ # No longer need to differentiate between RUIDs # as WebSMIT is now deprecated. return &snmpinfoRefreshViaSnmpinfo($_[0]); } ##################################################### # Function: # snmpinfoRefreshViaSnmpinfo # # Description: # # Arguments: # Name of the output file to create ##################################################### sub snmpinfoRefreshViaSnmpinfo($){ umask 177; my $SNMPINFO_OUT = $_[0]; # Try probing the MIB on each of the cluster's other nodes. # If nothing is returned by a node its cluster mgr must not # be active. We'll try successive nodes until we've found # or are forced to give up. # These are the keys for the info we're looking for in the MIB: my $CLUSTER_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.1"; my $NODE_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.2.1.1"; my $RG_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.11.1.1"; my $RES_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.11.2.1"; my $RG_STATE_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.11.3.1"; my $NETWORK_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.4.1.1"; #=========================================================================== # In 5.5, a new address format was added to the MIB in support of IPv6; # variables prefaced with "addr6". These new variables support both # IPv6 *and* IPv4. Unfortunately, testing revealed the need to continue # supporting the old format to accommodate mixed, migrated environments. # When a migrated node runs cldisp, and the node that is contacted below # has *not* been migrated yet, you can end up with an addr6-based cldisp # attempting to use non-addr6 MIB data!! Therefore, for as long as we have # current support for 5.4.1 or earlier, we need to still pull the non-addr6 # data. #=========================================================================== my $ADDRESS_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.3.1.1"; # Pre-5.5 format my $ADDRESS6_BRANCH = "1.3.6.1.4.1.2.3.1.2.1.5.13.1.1"; # New as of 5.5 my @nodes = &nodes_in_cluster(); my $found_active_node = 0; NODE: foreach my $remoteNode (@nodes){ # # Try to get the cluster branch first. If this fails, # then there is no need to try the remainder! # system("$HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $CLUSTER_BRANCH > $SNMPINFO_OUT"); # # We now need to check to see if the remote node actually had cluster # services enabled. If it didn't, the $SNMPINFO_OUT file will be empty # and we'll have to try another node # next NODE if (! -s "$SNMPINFO_OUT"); # # Get the rest of the data # system("$HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $NODE_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $ADDRESS_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $ADDRESS6_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $RG_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $RES_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $RG_STATE_BRANCH >> $SNMPINFO_OUT; $HACMPBASE/utilities/clrexec $remoteNode SNMPINFO -v $NETWORK_BRANCH >> $SNMPINFO_OUT"); $found_active_node = 1; last NODE; } if (!$found_active_node){ return $INACTIVE; } return $ACTIVE; } ##################################################### # Function: # snmpinfoRefreshViaWsm_cmd_exec # # Description: # # Arguments: # Name of the output file to create ##################################################### sub snmpinfoRefreshViaWsm_cmd_exec($){ umask 077; my $SNMPINFO_OUT = $_[0]; # Random filenames will be used for increased security my $random_num; $random_num = int(rand 999999); # Verification that the file doesn't already exist while( -e "snmpinfo.command.$random_num"){ $random_num = int(rand 999999); } # create a tmp file to run the command my $SNMPINFO_SCRIPT = "/tmp/snmpinfo.command.$random_num"; # Try probing the MIB on each of the cluster's other nodes. # If nothing is returned by a node its cluster mgr must not # be active. We'll try successive nodes until we've found # or are forced to give up. my @nodes = &nodes_in_cluster(); my $found_active_node = 0; foreach my $remoteNode (@nodes){ open (FPT, ">$SNMPINFO_SCRIPT"); print FPT "#!/bin/ksh\n"; print FPT "umask 111\n"; print FPT "BINDIR=$HACMPBASE/wsm/cgi-bin\n"; print FPT "CONFDIR=$HACMPBASE/wsm\n"; print FPT "\n"; print FPT ". \$CONFDIR/wsm_smit.conf > /dev/null\n"; print FPT "export AUTHORIZED_PORT REDIRECT_TO_HTTPS REQUIRE_AUTHENTICATION\n"; print FPT "export ACCEPTED_USERS SESSION_TIMEOUT REQUIRED_WEBSERVER_UID\n"; print FPT "\n"; print FPT "\$BINDIR/wsm_cmd_exec -M -n $remoteNode -e cluster > $SNMPINFO_OUT\n"; print FPT "\$BINDIR/wsm_cmd_exec -M -n $remoteNode -e node >> $SNMPINFO_OUT\n"; print FPT "\$BINDIR/wsm_cmd_exec -M -n $remoteNode -e address >> $SNMPINFO_OUT\n"; print FPT "\$BINDIR/wsm_cmd_exec -M -n $remoteNode -e resmanager >> $SNMPINFO_OUT\n"; print FPT "\$BINDIR/wsm_cmd_exec -M -n $remoteNode -e network >> $SNMPINFO_OUT\n"; close(FPT); system("chmod +x $SNMPINFO_SCRIPT"); system("$SNMPINFO_SCRIPT"); # We now need to check to see if the remote node actually had cluster services enabled. # If it didn't, the $SNMPINFO_OUT file will be empty and we'll have to try another node if( -s "$SNMPINFO_OUT" ){ $found_active_node = 1; last; } # cleanup system("rm -f $SNMPINFO_SCRIPT"); } if(!$found_active_node){ return $INACTIVE; } return $ACTIVE; } ##################################################### # Function: # nodes_in_cluster # # Description: # Returns a list of the names of nodes in the # cluster # # Arguments: # none ##################################################### sub nodes_in_cluster(){ my @nodes; my @tmp = `$HACMPBASE/utilities/clshownets`; $_ = $tmp[$#tmp]; @nodes = split; if(!$nodes[0]){ print STDERR `dspmsg -s 20 scripts.cat 25 "Error: No nodes in cluster or no access to cluster ODM.\n"`; exit 1; } foreach my $node (@nodes) { &detaint($node); } return @nodes; } ##################################################### # Function: # interfaces_on_nodes # # Description: # Returns a list of the interfaces on a # node # # Arguments: # $_[0]: a node name ##################################################### sub interfaces_on_node($){ my @interfaces; my $node = $_[0]; foreach my $cllsif_node (@cllsif){ my @cnode = split (/~/, $cllsif_node); if( ($cnode[3] eq "diskhbmulti") ){ if( ($cnode[5] eq $node) && ($cnode[6] ne "") ) { push @interfaces, $cnode[6]; } } else { if( ($cnode[5] eq $node) && ($cnode[8] ne "") ) { push @interfaces, $cnode[8]; } } } if(!$interfaces[0]){ print STDERR `dspmsg scripts.cat -s 23 100 "\nWARNING: Node %s does not appear to have any network interfaces.\n" "$node"`; } return @interfaces; } ##################################################### # Function: # name_of_interface # # Description: # Returns the name (as known to PowerHA SystemMirror) of an # interface # # Arguments: # $_[0]: a node name # $_[1]: an interface name (on that node. ex: en0) ##################################################### sub name_of_interface($$$){ my ( $node, $interface, $labelsFound ) = @_; my $found = 0; foreach my $intf (@cllsif){ my @inf = split(/~/, $intf); if( ($inf[3] eq "diskhbmulti") ){ if( ($inf[5] eq $node) && ($inf[6] eq $interface) ){ return $inf[0]; } } else { if( ($inf[5] eq $node) && ($inf[8] eq $interface) ){ $found = 0; foreach my $interface_name (@$labelsFound){ if( ($interface_name eq $inf[0]) ){ $found = 1; last; } } if ($found != 1){ return $inf[0]; } } } } print STDERR `dspmsg scripts.cat -s 23 101 "\nWARNING: No interface name found for %s on %s.\n" "$interface" "$node"`; } ##################################################### # Function: # ip_of_interface # # Description: # Returns the boot/standby IP address of an # interface. (In the case of a non-ip based # interface returns the path to the # corresponding device. ex: /dev/tty0) # # Arguments: # $_[0]: a node name # $_[1]: an interface name (on that node. ex: en0) ##################################################### sub ip_of_interface($$$){ my ( $node, $interface, $ipaddrsFound ) = @_; my $found = 0; # Finds and returns the ip that corresponds to # both the supplied node and interface foreach my $intf (@cllsif){ my @inf = split(/~/, $intf); if( ($inf[3] eq "diskhbmulti") ){ if( ($inf[5] eq $node) && ($inf[6] eq $interface) ){ return $inf[6]; } } else { if( ($inf[3] eq "diskhb") ){ if( ($inf[5] eq $node) && ($inf[8] eq $interface) ){ return $inf[6]; } } else { if( ($inf[5] eq $node) && ($inf[8] eq $interface) ){ $found = 0; foreach my $ip_addr (@$ipaddrsFound){ if( ($ip_addr eq $inf[6]) ){ $found = 1; last; } } if ($found != 1){ return $inf[6]; } } } } } print STDERR `dspmsg scripts.cat -s 23 102 "\nWARNING: No interface '%s' exists on node '%s'.\n" "$interface" "$node"`; } ##################################################### # Function: # network_of_ip_addr # # Description: # Returns the network (as it's known to PowerHA SystemMirror) to # which an interface belongs # # Arguments: # $_[0]: a node name # $_[1]: an ip address ##################################################### sub network_of_ip_addr($$){ my $node = $_[0]; my $ip = $_[1]; # We must check to make sure that both the node and interface names correspond # to the network which we're going to return foreach my $intf (@cllsif){ my @inf = split(/~/, $intf); if( ($inf[5] eq $node) && ($inf[6] eq $ip) ){ return $inf[2]; } } print STDERR `dspmsg scripts.cat -s 23 103 "\nWARNING: No interface '%s' exists on node '%s'.\n" "$ip" "$node"`; } ##################################################### # Function: # apps_on_cluster # # Description: # Returns a list of the names of all applications # known to PowerHA SystemMirror # # Arguments: # none ##################################################### sub apps_on_cluster(){ my @apps = `$HACMPBASE/utilities/cllsserv -c | cut -f1 -d:`; chomp @apps; foreach my $app (@apps) { &detaint($app); } return @apps; } ##################################################### # Function: # nodes_of_app_of_rg # # Description: # Returns an array of the all nodes that are members # of a application's resource group # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub nodes_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; local @_; local $_; my @ret_nodes; if($rg eq "NONE"){ return @ret_nodes; } my @nodes = `$HACMPBASE/utilities/clgetgrp -g $rg -f nodes`; chomp @nodes; foreach (@nodes) { my @tmp = split; for (my $i = 0; $i <= $#tmp; $i++){ push @ret_nodes, $tmp[$i]; } } foreach my $ret_node (@ret_nodes) { &detaint($ret_node); } return @ret_nodes; } ##################################################### # Function: # rg_of_app # # Description: # Returns an array holding resource groups' name # to which the application belongs # # Arguments: # $_[0]: an application name ##################################################### sub rg_of_app($){ my $app = $_[0]; $_ = `$HACMPBASE/utilities/cllsres -q "value = $app" -f group`; chomp $_; my @odm = split(/ +/,$_); chomp @odm; if ($odm[0] eq "") { $odm[0]="NONE"; } foreach my $o (@odm) { &detaint($o); } return @odm; } ##################################################### # Function: # vgs_of_app_of_rg # # Description: # Returns a list of the volume groups associated # with an application # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub vgs_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; local $_; local @_; # @odm = `odmget -q "group=$rg and name=VOLUME_GROUP" HACMPresource`; if($rg eq "NONE"){ return my @vgs; } $_ = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f2 -d:`; chomp $_; my @vgs = split (/ +/, $_); chomp @vgs; foreach my $vg (@vgs) { &detaint($vg); } return @vgs; } ##################################################### # Function: # cc_vgs_of_app_of_rg # # Description: # Returns a list of the concurrent volume groups # associated with an application # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub cc_vgs_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; local $_; local @_; # @odm = `odmget -q "group=$rg and name=CONCURRENT_VOLUME_GROUP" HACMPresource`; if($rg eq "NONE"){ return my @ccvgs; } $_ = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f3 -d:`; chomp $_; my @ccvgs = split (/ +/, $_); chomp @ccvgs; foreach my $ccvg (@ccvgs) { &detaint($ccvg); } return @ccvgs; } ##################################################### # Function: # filesystems_of_app_of_rg # # Description: # Returns a list of the filesystems associated # with an application # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub filesystems_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; local $_; local @_; # @odm = `odmget -q "group=$rg and name=FILESYSTEM" HACMPresource`; if($rg eq "NONE"){ return my @fs; } $_ = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f5 -d:`; chomp $_; my @fs = split (/ +/, $_); chomp @fs; foreach my $f (@fs) { &detaint($f); } return @fs; } ##################################################### # Function: # fast_connect_services_of_app_of_rg # # Description: # Returns a list of the AIX fast connect services # associated with a application # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub fast_connect_services_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; local $_; local @_; # @odm = `odmget -q "group=$rg and name=AIX_FAST_CONNECT_SERVICES" HACMPresource`; if($rg eq "NONE"){ return my @fc; } $_ = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f11 -d:`; chomp $_; my $fc = split (/ +/, $_); chomp $fc; &detaint($fc); return $fc; } ##################################################### # Function: # startscript_of_app # # Description: # Returns the path to the starting script of an # application # # Arguments: # $_[0]: an application name ##################################################### sub startscript_of_app($){ # my @odm = `odmget -q name=$_[0] HACMPserver`; my $start = `$HACMPBASE/utilities/cllsserv -cn $_[0] | cut -f2 -d:`; chomp $start; &detaint($start); return $start; } ##################################################### # Function: # stopscript_of_app # # Description: # Returns the path to the stop script of an # application # # Arguments: # $_[0]: an application name ##################################################### sub stopscript_of_app($){ # my @odm = `odmget -q name=$_[0] HACMPserver`; my $stop = `$HACMPBASE/utilities/cllsserv -cn $_[0] | cut -f3 -d:`; chomp $stop; &detaint($stop); return $stop; } ##################################################### # Function: # service_labels_of_app_of_rg # # Description: # Returns a list of the service labels of an # application # # Arguments: # $_[0]: an application name # $_[1]: rg name ##################################################### sub service_labels_of_app_of_rg($$){ my $rg = $_[1]; # my @odm = `odmget -q"group=$rg and name=SERVICE_LABEL" HACMPresource`; my @serviceLabels; my $tmpserviceLabels; if($rg eq "NONE"){ $serviceLabels[0] = "NONE"; return @serviceLabels; } $tmpserviceLabels = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f15 -d:`; if (length($tmpserviceLabels) == 0) { $serviceLabels[0] == "NONE"; } @serviceLabels = split( " ", $tmpserviceLabels); chomp @serviceLabels; foreach my $serviceLabel (@serviceLabels) { &detaint($serviceLabel); } return @serviceLabels; } ##################################################### # Function: # ip_of_service_label # # Description: # Returns the IP address of a service label # # Arguments: # $_[0]: a service label ##################################################### sub ip_of_service_label($){ foreach(@cllsif){ my @tmp = split(/~/, $_); if (($tmp[1] eq "service") && ($tmp[0] eq "$_[0]")){ return $tmp[6]; } } print STDERR `dspmsg scripts.cat -s 23 104 "\nWARNING: No service label '%s' was found on this cluster.\n" "$_[0]"`; } ##################################################### # Function: # network_of_service_label # # Description: # Returns the network to which a particular # service IP label belongs # # Arguments: # $_[0]: a service label ##################################################### sub network_of_service_label($){ foreach(@cllsif){ my @tmp = split(/~/, $_); if (($tmp[1] eq "service") && ($tmp[0] eq "$_[0]")){ return $tmp[2]; } } print STDERR `dspmsg scripts.cat -s 23 104 "\nWARNING: No service label '%s' was found on this cluster.\n" "$_[0]"`; } ##################################################### # Function: # monitors_of_app # # Description: # Returns a list of the application monitors # configured for an application # # Arguments: # $_[0]: an application name ##################################################### sub monitors_of_app($){ my $app = $_[0]; local $_; local @_; # @odm = `odmget -q "name=RESOURCE_TO_MONITOR and value=$app" HACMPmonitor`; $_ = `$HACMPBASE/utilities/cllsserv -csm -n $app | cut -f4 -d:`; chomp $_; my @monitors = split (/ +/, $_); chomp @monitors; foreach my $monitor (@monitors) { &detaint($monitor); } return @monitors; } ##################################################### # Function: # get_monitor_value # # Description: # Returns the value stored in the HACMPmonitor odm # for a particular monitor and attribute # # Arguments: # $_[0]: a monitor name # $_[1]: index ##################################################### sub get_monitor_value($$){ my $monitor = $_[0]; my $index = $_[1]; local $_; local @_; # @odm = `odmget -q "monitor=$monitor and name=$attribute" HACMPmonitor`; my $mval = `$HACMPBASE/utilities/cllsappmon -c $monitor | tail -1 | cut -f $index -d:`; chomp $mval; &detaint($mval); return $mval; } ##################################################### # Function: # state_of_local_clstrmgr # # Description: # Returns "active" if cluster services are active # and "inactive" if not. # # Arguments: # none ##################################################### # Possible return values: # "active" # "inactive" sub state_of_local_clstrmgr(){ @clshowsrv = `LC_ALL=C $HACMPBASE/utilities/clshowsrv clstrmgrES`; foreach my $line (@clshowsrv){ $line =~ s/^\s+//; # strip leading whitespace chomp $line; my @tmp = split (/ +/, $line); if(($tmp[0] eq "clstrmgrES") && ($tmp[3] eq "active")){ return $ACTIVE; } } return $INACTIVE; } ##################################################### # Function: # state_of_cluster # # Description: # Returns the state of the cluster # # Arguments: # none ##################################################### # Possible return values: # "up" # "down" # "unknown" sub state_of_cluster(){ # $SNMPINFO_SOURCE is generated by snmpinfoRefresh which should be called # shortly before this script is executed. foreach my $info (@snmpinfo){ my @entry = split (/ +/, $info); if ($entry[0] eq "clusterState.0"){ if ($entry[2] == 2){ return $UP; } elsif ($entry[2] == 4){ return $DOWN; } elsif ($entry[2] == 8){ return $UNKNOWN; } # This shouldn't happen print STDERR `dspmsg scripts.cat -s 23 105 "\nWARNING: Unable to determine cluster status.\n"`; return; } } } ##################################################### # Function: # substate_of_cluster # # Description: # Returns the substate of the cluster # # Arguments: # none ##################################################### # Possible return values: # "unknown" # "unstable" # "stable" # "error" # "reconfig" sub substate_of_cluster(){ my @tmp; foreach(@snmpinfo){ @tmp = split; if ($tmp[0] eq "clusterSubState.0"){ if ($tmp[2] == 8){ return $UNKNOWN; } elsif ($tmp[2] == 16){ return $UNSTABLE; } elsif ($tmp[2] == 32){ return $STABLE; } elsif ($tmp[2] == 64){ return $ERROR; } elsif ($tmp[2] == 128){ return $RECONFIG; } print STDERR `dspmsg scripts.cat -s 23 105 "\nWARNING: Unable to determine cluster substate.\n"`; return; } } } ##################################################### # Function: # state_of_node # # Description: # Returns the state of a node. ("up", "down", # "joining", "leaving") # # Arguments: # $_[0]: node name ##################################################### # Possible return values: # "up" # "down" # "joining" # "leaving" sub state_of_node($){ my @nodeNames; my @nodeStates; # Here we'll extract the node names and store them # in the array @nodeNames in the order in which we # found them. foreach(@snmpinfo){ my @tmp = split /\s|\./; if ($tmp[0] eq "nodeName"){ $tmp[3] =~ s/\"//g; push @nodeNames, $tmp[3]; $nodeID{$tmp[3]} = $tmp[1]; } # This will keep us from searching through the # entire @snmpinfo array after we've found # what we're looking for. elsif($tmp[0] =~ /addr/){ last; } } foreach(@snmpinfo){ my @tmp = split /\s|\./; if ($tmp[0] =~ /nodeState/){ push @nodeStates, $tmp[3]; } # This will keep us from searching through the # entire @snmpinfo array after we've found # what we're looking for. elsif($tmp[0] =~ /addr/){ last; } } for(my $r = 0; $nodeNames[$r]; $r++){ if ($_[0] eq $nodeNames[$r]){ if($nodeStates[$r] == 2){ return $UP; } elsif($nodeStates[$r] == 4){ return $DOWN; } elsif($nodeStates[$r] == 32){ return $JOINING; } elsif($nodeStates[$r] == 64){ return $LEAVING; } print STDERR `dspmsg scripts.cat -s 23 106 "\nWARNING:'%s' was found but a problem occurred while attempting to fetch state.\n" "$_[0]"`; return; } } } ##################################################### # Function: # state_of_interface # # Description: # Returns the state of an interface. (up/down) # # Arguments: # $_[0]: a node name # $_[1]: an interface name (on that node. ex: en0) ##################################################### # Possible return values: # "up" # "down" # "unknown" sub state_of_interface($$){ my $node = $_[0]; my $interface = $_[1]; my $inet_type = 1; if($ip = &ip_of_interface($_[0],$_[1],\@addrs_found) ){ # This is a work-around for non-ip interfaces. It probes # the MIB's ip-like value assigned to the interface so # that it can be used in-place of a real IP address. push @addrs_found, $ip; $_ = $ip; s/[0-9,a-f]//g; if($_ ne "..." && $_ !~ /\:*/){ #non-ip interface my $interfaceName = &name_of_interface($node, $interface, \@names_found); push @names_found, $interfaceName; foreach(@snmpinfo){ my @tmp = split /\s|\./; if($tmp[0] eq "addr6Label"){ if($tmp[10]){ $tmp[10] =~ s/\"//g; if($tmp[10] eq $interfaceName){ $ip = "$tmp[4].$tmp[5].$tmp[6].$tmp[7]"; last; } } } if($tmp[0] eq "addrLabel"){ if($tmp[7]){ $tmp[7] =~ s/\"//g; if($tmp[7] eq $interfaceName){ $ip = "$tmp[2].$tmp[3].$tmp[4].$tmp[5]"; last; } } } } } else { # ip interface. # In the absence of perl APIs for converting textual IP address # to in_addr/in6_addr, obtain the corresponding data structural # elements from the MIB. The below logic fetches these from the # instance qualifier of the 'addrLabel' MIB variable. # For reference on addr6Group table entries, refer # 'src/43haes/usr/sbin/cluster/clsmuxpd/hacmp.my'. my $interfaceName = &name_of_interface($node, $interface, \@names_found); push @names_found, $interfaceName; foreach (@snmpinfo) { my @interface_name = split /=/; my @tmp = split /\s|\./; my @tmp_interface_name = split(/\"/,$interface_name[1]); if(($tmp[0] eq "addr6Label") && ($tmp_interface_name[1] eq $interfaceName)){ $ip=""; $inet_type = $tmp[2]; # next is octet count...skip it. $ip = $tmp[4]; # this is the first octet of the ip address for(my $count = 1; $count < $tmp[3]; $count++) { $ip = "$ip.$tmp[$count + 4]"; } last; } } } my $using_addr6 = 1; my @addrStates = grep(/addr6State/, @snmpinfo); if ($inet_type == 1 && ! @addrStates) { $using_addr6 = 0; @addrStates = grep(/addrState/, @snmpinfo); } foreach (@addrStates){ my @tmp = split /\s|\./; # enum of inet_type is defined in # 'src/43haes/usr/sbin/cluster/clsmuxpd/hacmp.my'. use Switch; switch ($inet_type) { case (1) { #IPv4 if ($using_addr6) { # Using 5.5+ MIB data if ($ip eq "$tmp[4].$tmp[5].$tmp[6].$tmp[7]") { return $UP if ($tmp[$#tmp] == 2); return $DOWN if ($tmp[$#tmp] == 4); return $UNKNOWN; } } else { # Using pre-5.5 MIB data if ($ip eq "$tmp[2].$tmp[3].$tmp[4].$tmp[5]") { return $UP if ($tmp[$#tmp] == 2); return $DOWN if ($tmp[$#tmp] == 4); return $UNKNOWN; } } } case (2) { #IPv6 my $tmp_ip=""; $tmp_ip = $tmp[4]; #first octet of ip address for(my $count = 1; $count < $tmp[3]; $count++) { $tmp_ip = "$tmp_ip.$tmp[4+$count]"; } if ($ip eq $tmp_ip) { return $UP if ($tmp[$#tmp] == 2); return $DOWN if ($tmp[$#tmp] == 4); return $UNKNOWN; } } } } print STDERR `dspmsg scripts.cat -s 23 107 "\nWARNING: Unable to locate interface '%s' on node '%s'.\n" "$interface" "$node"`; } print STDERR `dspmsg scripts.cat -s 23 108 "\nWARNING: An unknown problem occurred while executing the ip_of_interface subroutine.\n"`; } ##################################################### # Function: # state_of_network # # Description: # Returns the state of a network. # (up/down/joining/leaving) # # Arguments: # $_[0]: the node name # $_[1]: the name of a network ##################################################### # Possible return values: # "up" # "down" # "joining" # "leaving" # "unknown" sub state_of_network($$){ local $_; my @snmpinfo = @snmpinfo; # Changes will only be for this dynamic scope my @tmp; my $netID = 9999; # First we need to find the netID of the network # While our @snmpinfo stack is still popping elements ... while($_ = shift @snmpinfo){ @tmp = split /\s|\./; if( ($tmp[0] eq "netName") && ($tmp[4] eq "\"$_[1]\"") ){ $netID = $tmp[2]; last; } } if($netID == 9999){ print STDERR `dspmsg scripts.cat -s 23 109 "\nERROR: Unable to find the network '%s' in the Management Information Base.\n" "$_[0]"`; return; } # Now, using the netID, we can find the state while($_ = shift @snmpinfo){ @tmp = split /\s|\./; if( ($tmp[0] eq "netState") && ($tmp[1] == $nodeID{$_[0]}) && ($tmp[2] == $netID) ){ $netState = $tmp[4]; last; } } # Interprets and reports the state if($netState == 2){ return $UP; } elsif($netState == 4){ return $DOWN; } elsif($netState == 32){ return $JOINING; } elsif($netState == 64){ return $LEAVING; } else{ return $UNKNOWN; } } ##################################################### # Function: # # state_of_app_of_rg # # Description: # # Returns the state of an application. # # Arguments: # # $_[0]: an application name # $_[1]: rg name ##################################################### # Possible return values: # "$INACTIVE" (the application server is configured but is not a member of a RG) # "$ONLINE" # "$OFFLINE" # "$ACQUIRING" # "$RELEASING" # "$ERROR" # "$UNKNOWN" sub state_of_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; my @resGroupNodeStates; my $resGroupID; if($rg eq "NONE"){ return $INACTIVE; } # Uses the resource group name to parse the resource group ID from snmpinfo my @tmpSnmpinfo = `cat $SNMPINFO_SOURCE | fgrep resGroupName`; foreach(@tmpSnmpinfo){ my @tmp = split /\s|\./; $tmp[3] =~ s/\"//g; if($tmp[3] eq $rg){ $resGroupID = $tmp[1]; last; } } # Builds a list of the states of all the nodes in the resource group we're # examining. @tmpSnmpinfo = `cat $SNMPINFO_SOURCE | grep resGroupNodeState`; foreach(@tmpSnmpinfo){ my @tmp = split /\s|\./; if(($tmp[0] eq "resGroupNodeState") && ($tmp[1] == $resGroupID)){ push @resGroupNodeStates, $tmp[4]; } } foreach my $resGroupNodeState (@resGroupNodeStates) { &detaint($resGroupNodeState); } # This shouldn't happen, but if it does something obviously went wrong. if(!@resGroupNodeStates){ print STDERR `dspmsg scripts.cat -s 23 110 "\nWARNING: Could not determine the state of application '%s'.\n" "$_[0]"`; return; } # Here we look at the state of the application on each of the nodes that are # capable of providing it and determine the app's state accordingly. my $nodes_acquiring = 0; my $nodes_releasing = 0; my $nodes_offline = 0; my $nodes_error = 0; my $nodes_unknown = 0; foreach(@resGroupNodeStates){ if($_ == 2){ # if its online on any node, the global state is online return $ONLINE; } elsif($_ == 4){ $nodes_offline++; } elsif($_ == 8){ $nodes_unknown++; } elsif($_ == 16){ $nodes_acquiring++; } elsif($_ == 32){ $nodes_releasing++; } elsif($_ == 64){ # if its in error state on any node, the global state is error return $ERROR; } } if($nodes_acquiring){ return $ACQUIRING; } # If they're all OFFLINE / RELEASING / ERROR elsif($nodes_offline == ($#resGroupNodeStates + 1)){ return $OFFLINE; } elsif($nodes_error == ($#resGroupNodeStates + 1)){ return $ERROR; } elsif($nodes_releasing == ($#resGroupNodeStates + 1)){ return $RELEASING; } # If they're any other combination of states return $UNKNOWN; } ##################################################### # Function: # # nodes_providing_app_of_rg # # Description: # # Returns a list of the nodes currently providing # an application # # Arguments: # # $_[0]: an application name # $_[1]: rg name ##################################################### sub nodes_providing_app_of_rg($$){ my $app = $_[0]; my $rg = $_[1]; my @nodes; my @nodesWithID; my @resGroupNodesID; my $resGroupID; my @resGroupNodeStates; my @results; my @tmpSnmpinfo = `cat $SNMPINFO_SOURCE | grep -e nodeName -e resGroupName -e resGroupNodeState`; my @tmp; if($rg eq "NONE"){ $results[0] = "NONE"; return @results; } # Parses snmpinfo's output for ... foreach my $line (@tmpSnmpinfo){ @tmp = split /\s|\./, $line; # ... the names of the nodes in the cluster ... # (Although we already have a subroutine that does this # we do it again here because we will also be gathering # the states of the nodes from snmpinfo and we need to # insure that our nodes and node-states match.) if("$tmp[0]" eq "nodeName"){ $tmp[3] =~ s/\"//g; push @nodesWithID, "$tmp[3].$tmp[1]"; } # ... the ID by which snmpinfo refers to app's RG ... elsif("$tmp[0]" eq "resGroupName"){ $tmp[3] =~ s/\"//g; if($tmp[3] eq $rg){ $resGroupID = $tmp[1]; } } # ... and the states of each node (This IF will never be # followed until after all the resGroupIDs have been found # since states are listed later in snmpinfo's output.) elsif( ("$tmp[0]" eq "resGroupNodeState") && ($tmp[1] == $resGroupID) ){ push @resGroupNodeStates, $tmp[4]; push @resGroupNodesID, $tmp[2]; } } # We should always find the nodes' states. If not, something's gone wrong. if(!@resGroupNodeStates){ print STDERR `dspmsg scripts.cat -s 23 111 "\nWARNING: Could not determine the state of application %s.\n" "$app"`; return; } # Keep only nodes that have this RG for(my $id = 0; $resGroupNodesID[$id]; $id++){ for(my $node_id = 0; $nodesWithID[$node_id]; $node_id++) { @tmp = split /\s|\./, $nodesWithID[$node_id]; if ($tmp[1] == $resGroupNodesID[$id]) { push @nodes, $tmp[0]; } } } # Returns a list of all nodes that are both in the app's RG and # have a state of "online". for(my $r = 0; $resGroupNodeStates[$r]; $r++){ if($resGroupNodeStates[$r] == 2){ push @results, $nodes[$r]; } } if(!$results[0]){ $results[0] = "NONE"; } return @results; } # ------------------------------------------------------------------------ # The next four subroutines are only called by fallover_node_for_app_of_rg # ------------------------------------------------------------------------ ##################################################### # Function: # # calculateDNPPFallover # # Description: # # Returns an explanation of which node will be # selected for fallover by the dynamic node # priority policy # # Arguments: # # resource group name ##################################################### sub calculateDNPPFallover($){ my $rg = $_[0]; local @_; # This keeps the global from being clobbered by local changes local $_; # This keeps the global from being clobbered by local changes # my @tmp = `odmget -q "group=$rg and name=NODE_PRIORITY_POLICY" HACMPresource`; my @currNode = &nodes_providing_app_of_rg($app,$rg); my $availableNodes = 0; # Counts up how many cluster nodes are $UP if(scalar(@currNode) > 0){ foreach(@nodesOfRg){ if( ( &state_of_node($_) eq $UP) && ($_ ne $currNode[0]) ){ $availableNodes++; } } } # We only need to report the dnpp message if there are two or # more other nodes available to fallover to. if($availableNodes > 1){ my $dnp = `$HACMPBASE/utilities/cllsres -cg $rg | tail -1 | cut -f23 -d:`; chomp $dnp; if($dnp eq "cl_highest_free_mem"){ return $MESSAGE_CL_HIGHEST_FREE_MEM; } elsif($dnp eq "cl_highest_idle_cpu"){ return $MESSAGE_CL_HIGHEST_IDLE_CPU; } elsif($dnp eq "cl_lowest_disk_busy"){ return $MESSAGE_CL_LOWEST_DISK_BUSY; } else{ return $MESSAGE_USER_SPECIFIED; } # This should never happen print STDERR `dspmsg scripts.cat -s 23 112 "\nERROR: Unable to determine the dynamic node priority policy for group '%s'.\n" "$rg"`; } # If there is only one other node available to fallover to, # we can just have calculateFalloverToNextPriorityNode() do # our work for us. elsif ($availableNodes == 1){ return calculateFalloverToNextPriorityNode(); } else{ return "NONE"; } } ##################################################### # Function: # # calculateFalloverToNextPriorityNode # # Description: # # Returns the highest-priority available node. # (The one to which a standard rotating or # cascading RG will fallover.) # # Arguments: # none ##################################################### sub calculateFalloverToNextPriorityNode(){ local $_; # This keeps the global from being clobbered by local changes # nodes_providing_app_of_rg will normally return a single node or a list of nodes (in # the case of a concurrent RG) but in this case we've already screened out the # concurrent RGs so we can just assume that the array @currNode will only have # a single element. my @currNode = &nodes_providing_app_of_rg($app,$rg); # Checks each of the nodes in the RG from highest to lowest precedence. # The RG will move to the first available node, so it's returned. if(scalar(@currNode) > 0){ foreach(@nodesOfRg){ if( ( &state_of_node($_) eq $UP) && ($_ ne $currNode[0]) ){ return "$_"; } } } return "NONE"; } ##################################################### # Function: # # calculateOfflineFallover # # Description: # # Returns the second node on the list of nodes # that are configured to provide the application # or "NONE" if there isn't one. This subroutine is # used only for clusters that are offline. # # Arguments: # # none ##################################################### sub calculateOfflineFallover(){ if($nodesOfRg[1]){ return "$nodesOfRg[1]"; } else{ return "NONE"; } } ##################################################### # Function: # # initRgPrefs # # Description: # # Initializes $startupPref, $falloverPref, # and $fallbackPref # # Arguments: # # $_[0]: Resource group name ##################################################### sub initRgPrefs($){ local $rg = $_[0]; # my $tmp = `odmget -q group=$rg HACMPgroup | grep startup_pref`; $startupPref = `$HACMPBASE/utilities/clgetgrp -cg $rg | tail -1 | cut -f5 -d:`; &detaint($startupPref); chomp $startupPref; # $tmp = `odmget -q group=$rg HACMPgroup | grep fallover_pref`; $falloverPref = `$HACMPBASE/utilities/clgetgrp -cg $rg | tail -1 | cut -f6 -d:`; &detaint($falloverPref); chomp $falloverPref; # $tmp = `odmget -q group=$rg HACMPgroup | grep fallback_pref`; $fallbackPref = `$HACMPBASE/utilities/clgetgrp -cg $rg | tail -1 | cut -f7 -d:`; &detaint($fallbackPref); chomp $fallbackPref; } ##################################################### # Function: # # fallover_node_for_app_of_rg # # Description: # # Returns the node to which an application will # fallover if the node on which it is currently # running fails. (Or if the resource group is # offline it returns the node that the app would # fallover to if it can be determined.) # # Arguments: # # $_[0]: an application name # $_[1]: rg name ##################################################### sub fallover_node_for_app_of_rg($$){ $app = $_[0]; $rg = $_[1]; local $_; # keeps the global from being clobbered by local changes local @_; # keeps the global from being clobbered by local changes if($rg eq "NONE"){ return; } # nodes_of_app_of_rg effectively gives us a list of the nodes in the same RG @nodesOfRg = &nodes_of_app_of_rg($app,$rg); # First we'll parse HACMPgroup for the custom config info # POSSIBLE VALUES: # startupPref = "OFAN" Online on first available node # startupPref = "OHN" Online on home node only # startupPref = "OAAN" Online on all available nodes # startupPref = "OUDP" Online Using Distribution Policy # falloverPref = "FNPN" Fallover to next priority node in the list # falloverPref = "FUDNP" Fallover Using Dynamic Node Priority # falloverPref = "BO" Bring offline (on error node only) # -- We don't actually care about fallback settings but here they are: # fallbackPref = "NFB" Never fall back # fallbackPref = "FBHPN" Fallback To Higher Priority Node In The List initRgPrefs($rg); # If the app is online if( $appStates[$h][$r] eq $ONLINE ){ if ($startupPref eq "OAAN"){ print STDERR `dspmsg scripts.cat -s 23 113 "\nApplication '%s' belongs to a resource group which is configured to run on all its nodes simultaneously. No fallover will occur." "$app"`; } elsif($falloverPref eq "BO"){ return "NONE"; } elsif($falloverPref eq "FNPN"){ return &calculateFalloverToNextPriorityNode; } elsif($falloverPref eq "FUDNP"){ return &calculateDNPPFallover($rg); } } # If cluster services are down elsif (&state_of_cluster() eq $DOWN){ #else if cluster is inactive if ($startupPref eq "OAAN"){ print STDERR `dspmsg scripts.cat -s 23 113 "\nApplication '%s' belongs to a resource group which is configured to run on all its nodes simultaneously. No fallover will occur." "$app"`; } elsif($falloverPref eq "BO"){ return "NONE"; } elsif($falloverPref eq "FUDNP"){ return &calculateDNPPFallover($rg); } elsif($startupPref eq "OHN" && $falloverPref eq "FNPN"){ return &calculateOfflineFallover; } else{ return $MESSAGE_UNKNOWN_UNLESS_ONLINE; } } else{ return "NONE"; } } ##################################################### # Function: # # state_of_service_label # # Description: # # Returns the state of a service label ("online" # or "offline/unknown"). # # Arguments: # # $_[0]: a service label ##################################################### # Possible return values: # "online" # "offline/unknown" sub state_of_service_label($){ my $serviceLabel = $_[0]; my $ip; my $tmpIP; my $tmp_label; my $state; local $_; local @_; my @snmpinfo = `cat $SNMPINFO_SOURCE | grep -e addr6Label -e addr6Role -e addr6State`; # Parses snmpinfo's output for ... foreach my $ent (@snmpinfo){ my @entry = split (/\s|\./, $ent); # ... the ip of the service label if($entry[0] eq "addr6Label"){ use Switch; # refer 'src/43haes/usr/sbin/cluster/clsmuxpd/hacmp.my' for # description on addr6Group table indexing. switch($entry[2]) { case (1) {$tmp_label = $entry[10]} #IPv4 case (2) {$tmp_label = $entry[22]} #IPv6 } $tmp_label =~ s/\"//g; if($tmp_label eq $serviceLabel){ # Cat stuff together to form the ip we're looking for $ip = $entry[4]; for(my $count = 1; $count < $entry[3]; $count++) { $ip = "$ip.$entry[$count + 4]"; } } } # ... the role of the service label in order to ensure that addr6Label is # really a service label elsif($entry[0] eq "addr6Role"){ my $addr_type; $tmpIP = $entry[4]; for(my $count = 1; $count < $entry[3]; $count++) { $tmpIP = "$tmpIP.$entry[$count + 4]"; } switch($entry[2]) { case (1) {$addr_type = $entry[10]} case (2) {$addr_type = $entry[22]} } # If we've found the line containing the role of our IP ... if($ip && ($tmpIP eq $ip) ){ # see /usr/es/sbin/cluster/hacmp.my for description of these values if( !( ($addr_type == 16)||($addr_type == 32)||($addr_type == 128)||($addr_type == 144)) ){ print STDERR `dspmsg scripts.cat -s 23 114 "\nERROR: %s is not a service label.\n" "$serviceLabel"`; return; } } } # ... elsif( $entry[0] eq "addr6State" ){ $tmpIP = $entry[4]; for(my $count = 1; $count < $entry[3]; $count++) { $tmpIP = "$tmpIP.$entry[$count + 4]"; } # If we've found the line containing the state of our IP (on a # particular node my $state; if($ip && ($tmpIP eq $ip) ){ switch($entry[2]) { case (1) {$state = $entry[10]} case (2) {$state = $entry[22]} } if($state == 2){ return $ONLINE; } } } } return "offline/unknown"; } ##################################################### # Function: # # translate # # Description: # # Calls a predefined 'dspmsg' command based on the # input it's passed. This is a workaround to make # values stored in the ODM or returned by other # functions in this program translate correctly # into other languages. # # Arguments: # # $_[0]: any word or phrase (should be in english) ##################################################### sub translate($){ if($_[0] eq "") { return; } my $translation; if($_[0] eq "custom"){ $translation = `dspmsg scripts.cat -s 23 115 "custom"`; } elsif($_[0] eq "up"){ $translation = `dspmsg scripts.cat -s 23 116 "up"`; } elsif($_[0] eq "down"){ $translation = `dspmsg scripts.cat -s 23 117 "down"`; } elsif($_[0] eq "unknown"){ $translation = `dspmsg scripts.cat -s 23 118 "unknown"`; } elsif($_[0] eq "joining"){ $translation = `dspmsg scripts.cat -s 23 119 "joining"`; } elsif($_[0] eq "leaving"){ $translation = `dspmsg scripts.cat -s 23 120 "leaving"`; } elsif($_[0] eq "unstable"){ $translation = `dspmsg scripts.cat -s 23 121 "unstable"`; } elsif($_[0] eq "stable"){ $translation = `dspmsg scripts.cat -s 23 122 "stable"`; } elsif($_[0] eq "error"){ $translation = `dspmsg scripts.cat -s 23 123 "error"`; } elsif($_[0] eq "reconfig"){ $translation = `dspmsg scripts.cat -s 23 124 "reconfig"`; } elsif($_[0] eq "active"){ $translation = `dspmsg scripts.cat -s 23 125 "active"`; } elsif($_[0] eq "inactive"){ $translation = `dspmsg scripts.cat -s 23 126 "inactive"`; } elsif($_[0] eq "online"){ $translation = `dspmsg scripts.cat -s 23 127 "online"`; } elsif($_[0] eq "offline"){ $translation = `dspmsg scripts.cat -s 23 128 "offline"`; } elsif($_[0] eq "offline/unknown"){ $translation = `dspmsg scripts.cat -s 23 129 "offline/unknown"`; } elsif($_[0] eq "acquiring"){ $translation = `dspmsg scripts.cat -s 23 130 "acquiring"`; } elsif($_[0] eq "releasing"){ $translation = `dspmsg scripts.cat -s 23 131 "releasing"`; } elsif($_[0] eq "process"){ $translation = `dspmsg scripts.cat -s 23 132 "process"`; } elsif($_[0] eq "notify"){ $translation = `dspmsg scripts.cat -s 23 133 "notify"`; } elsif($_[0] eq "fallover"){ $translation = `dspmsg scripts.cat -s 23 134 "fallover"`; } # The following elsifs must be identical to the decalartions of $UP/$DOWN/$UNKNOWN/etc # in are changed in wsm_cldisp.cgi.perl elsif($_[0] eq $UP){ $translation = ''.`dspmsg scripts.cat -s 23 116 "up"`.''; } elsif($_[0] eq $DOWN){ $translation = ''.`dspmsg scripts.cat -s 23 117 "down"`.''; } elsif($_[0] eq $UNKNOWN){ $translation = ''.`dspmsg scripts.cat -s 23 118 "unknown"`.''; } elsif($_[0] eq $UNSTABLE ){ $translation = ''.`dspmsg scripts.cat -s 23 121 "unstable"`.''; } elsif($_[0] eq $STABLE ){ $translation = ''.`dspmsg scripts.cat -s 23 122 "stable"`.''; } elsif($_[0] eq $ERROR ){ $translation = ''.`dspmsg scripts.cat -s 23 123 "error"`.''; } elsif($_[0] eq $RECONFIG ){ $translation = ''.`dspmsg scripts.cat -s 23 124 "reconfig"`.''; } elsif($_[0] eq $ACTIVE ){ $translation = ''.`dspmsg scripts.cat -s 23 125 "active"`.''; } elsif($_[0] eq $INACTIVE ){ $translation = ''.`dspmsg scripts.cat -s 23 126 "inactive"`.''; } elsif($_[0] eq $ONLINE ){ $translation = ''.`dspmsg scripts.cat -s 23 127 "online"`.''; } elsif($_[0] eq $OFFLINE ){ $translation = ''.`dspmsg scripts.cat -s 23 128 "offline"`.''; } elsif($_[0] eq $ACQUIRING ){ $translation = ''.`dspmsg scripts.cat -s 23 130 "acquiring"`.''; } elsif($_[0] eq $RELEASING ){ $translation = ''.`dspmsg scripts.cat -s 23 131 "releasing"`.''; } # If it's not in the table we'll just print the english word. else{ #NOTE: This appears on the screen! In the user's output!! Bad! #print STDERR `dspmsg scripts.cat -s 23 135 "\nWARNING: translate() was called on the word '%s' which is not in translation table.\n" "$_[0]"`; $translation = $_[0]; } &detaint($translation); return $translation; } ##################################################### # Function: # # translateCustomAttr # # Description: # # Returns the english/other language equivalent # of a resource group's startup/fallover/ # fallback settings. # # Arguments: # # $_[0]: a startup/fallover/fallback setting # acronym ##################################################### sub translateCustomAttr($){ my $translation; if($_[0] eq "OFAN"){ $translation = `dspmsg scripts.cat -s 23 140 "on first available node"`; } elsif($_[0] eq "OHN"){ $translation = `dspmsg scripts.cat -s 23 141 "on home node only"`; } elsif($_[0] eq "OUDP"){ $translation = `dspmsg scripts.cat -s 23 142 "using distribution policy"`; } elsif($_[0] eq "OAAN"){ $translation = `dspmsg scripts.cat -s 23 143 "on all available nodes"`; } elsif($_[0] eq "FNPN"){ $translation = `dspmsg scripts.cat -s 23 144 "to next priority node in the list"`; } elsif($_[0] eq "FUDNP"){ $translation = `dspmsg scripts.cat -s 23 145 "using dynamic node priority to determine target"`; } elsif($_[0] eq "BO"){ $translation = `dspmsg scripts.cat -s 23 146 "bring offline on error node"`; } elsif($_[0] eq "NFB"){ $translation = `dspmsg scripts.cat -s 23 147 "never"`; } elsif($_[0] eq "FBHPN"){ $translation = `dspmsg scripts.cat -s 23 148 "if higher priority node becomes available"`; } &detaint($translation); return $translation; } ##################################################### # Function: # # probeStatic # # Description: # # Most of the static info about the cluster config # is gathered here # # Arguments: # # none ##################################################### sub probeStatic(){ # Gets the name of our cluster. $clusterName = `$HACMPBASE/utilities/cllsclstr -c | tail -1 | cut -f2 -d:`; chomp $clusterName; &detaint($clusterName); $localNodeName = `$HACMPBASE/utilities/get_local_nodename`; chomp $localNodeName; &detaint($localNodeName); # @nodeNames stores the names of each of the nodes in the cluster @nodeNames = &nodes_in_cluster; chomp @nodeNames; # Creation of @interfacesByNode-- a 2D array of the interfaces on each # host. The rows of the arrays are in the same order as @nodeNames. @scrap = @nodeNames; my @addrsFound; $r = 0; while ($_ = shift @scrap){ my @tmp = &interfaces_on_node($_); for($c = 0; $c <= $#tmp; $c++){ $interfacesByNode[$r][$c] = $tmp[$c]; &detaint($interfacesByNode[$r][$c]); } $r++; } # /while # Creation of @ipsByNode--a 2D array of the ip addresses of each of the # interfaces on each node. This array is in the same layout as # @interfacesByNode. for($r = 0; $interfacesByNode[$r][0]; $r++){ for($c = 0; $interfacesByNode[$r][$c]; $c++){ $ipsByNode[$r][$c] = &ip_of_interface($nodeNames[$r], $interfacesByNode[$r][$c], \@addrsFound); &detaint($ipsByNode[$r][$c]); push @addrsFound, $ipsByNode[$r][$c]; } } # Creation of @networksOfIpAddrs--a 2D array of the networks to which # each IP address or device (non-IP network) belongs. This array is in the same layout as # @interfacesByNode. for($r = 0; $interfacesByNode[$r][0]; $r++){ for($c = 0; $interfacesByNode[$r][$c]; $c++){ $networksOfIpAddrs[$r][$c] = &network_of_ip_addr($nodeNames[$r], $ipsByNode[$r][$c]); &detaint($networksOfIpAddrs[$r][$c]); } } # Initializes @apps, a list of all the cluster's apps @apps = &apps_on_cluster(); # Initializes @RgsByApp---a 2D array of the applications and the resource groups to which application # belongs. It's order is the same as @apps. for($r = 0; $apps[$r]; $r++){ @scrap = &rg_of_app($apps[$r]); for($c = 0; $scrap[$c]; $c++) { $RgsByApp[$r][$c] = $scrap[$c]; &detaint($RgsByApp[$r][$c]); } } # Creation of @nodesByApp--a 3D array of the nodes configured to provide each # application. The application corresponds to height, rg to rows and nodes to # column of the array @apps. @scrap = @apps; $h = 0; while ($_ = shift @scrap){ for($r = 0; $RgsByApp[$h][$r]; $r++) { @tmp = &nodes_of_app_of_rg($_,$RgsByApp[$h][$r]); for($c = 0; $c <= $#tmp; $c++){ $nodesByApp[$h][$r][$c] = $tmp[$c]; &detaint($nodesByApp[$h][$r][$c]); } } $h++; } # /while # Initializes @startscriptsByApp @scrap = @apps; $r = 0; while ($_ = shift @scrap){ $startscriptsByApp[$r] = &startscript_of_app($_); &detaint($startscriptsByApp[$r]); $r++; } # Initializes @stopscriptsByApp @scrap = @apps; $r = 0; while ($_ = shift @scrap){ $stopscriptsByApp[$r] = &stopscript_of_app($_); &detaint($stopscriptsByApp[$r]); $r++; } # Initializes @serviceIpLabelsOfApps, a 3D array whose height is in same # order as @apps, rows in the order of rgs(to which the app belongs) # and columns contain the service ip labels. @scrap = @apps; $h = 0; while ($_ = shift @scrap){ for($r = 0; $RgsByApp[$h][$r]; $r++) { @tmp = &service_labels_of_app_of_rg($_, $RgsByApp[$h][$r]); for($c = 0; $tmp[$c]; $c++){ $serviceIpLabelsOfApps[$h][$r][$c] = $tmp[$c]; &detaint($serviceIpLabelsOfApps[$h][$r][$c]); } } $h++; } # /while # Initializes @serviceIpsOfApps, a 3D array whose entries correspond to the entries of # @serviceIpLabelsOfApps. for($h = 0; $h <= $#serviceIpLabelsOfApps; $h++) { for($r = 0; $serviceIpLabelsOfApps[$h][$r][0]; $r++){ for($c = 0; $serviceIpLabelsOfApps[$h][$r][$c]; $c++){ if($serviceIpLabelsOfApps[$h][$r][$c] ne "NONE"){ $serviceIpsOfApps[$h][$r][$c] = &ip_of_service_label($serviceIpLabelsOfApps[$h][$r][$c]); &detaint($serviceIpsOfApps[$h][$r][$c]); } } } } # /for # Initializes @networksOfServiceLabels for($h = 0; $h <= $#serviceIpLabelsOfApps; $h++) { for($r = 0; $serviceIpLabelsOfApps[$h][$r][0] ; $r++){ for($c = 0; $serviceIpLabelsOfApps[$h][$r][$c]; $c++){ if($serviceIpLabelsOfApps[$h][$r][$c] ne "NONE"){ $networksOfServiceLabels[$h][$r][$c] = &network_of_service_label($serviceIpLabelsOfApps[$h][$r][$c]); &detaint($networksOfServiceLabels[$h][$r][$c]); } } } } # /for # Initializes @vgsOfApps, a 3D array of the shared volume groups # associated with a particular app corresponding to each rg, it belongs to. for($h = 0; $apps[$h]; $h++){ for($r = 0; $RgsByApp[$h][$r]; $r++) { my @tmp = &vgs_of_app_of_rg($apps[$r],$RgsByApp[$h][$r]); for($c = 0; $tmp[$c]; $c++){ $vgsOfApps[$h][$r][$c] = $tmp[$c]; &detaint($vgsOfApps[$h][$r][$c]); } } } # Initializes @ccVgsOfApps, a 3D array of the concurrent shared volume # groups associated with a particular app corresponding to each rg, it belongs to. for($h = 0; $apps[$h]; $h++){ for($r = 0; $RgsByApp[$h][$r]; $r++) { my @tmp = &cc_vgs_of_app_of_rg($apps[$r],$RgsByApp[$h][$r]); for($c = 0; $tmp[$c]; $c++){ $ccVgsOfApps[$h][$r][$c] = $tmp[$c]; &detaint($ccVgsOfApps[$h][$r][$c]); } } } # Initializes @filesystemsOfApps, a 3D array of the filesystems # associated with a particular app corresponding to each rg, it belongs to. for($h = 0; $apps[$h]; $h++){ for($r = 0; $r < $RgsByApp[$h][$r]; $r++) { my @tmp = &filesystems_of_app_of_rg($apps[$r],$RgsByApp[$h][$r]); for($c = 0; $tmp[$c]; $c++){ $filesystemsOfApps[$h][$r][$c] = $tmp[$c]; &detaint($filesystemsOfApps[$h][$r][$c]); } } } # Initializes @fastConnectServicesOfApps, a 3D array of the fast # connect services associated with each rg to which the app belongs. for($h = 0; $apps[$h]; $h++){ for($r = 0; $RgsByApp[$h][$r]; $r++) { my @tmp = &fast_connect_services_of_app_of_rg($apps[$r],$RgsByApp[$h][$r]); for($c = 0; $tmp[$c]; $c++){ $fastConnectServicesOfApps[$h][$r][$c] = $tmp[$c]; &detaint($fastConnectServicesOfApps[$h][$r][$c]); } } } # Initializes @monitorsOfApps, a 2D array of the application monitors # configured to monitor a particular application for($r = 0; $apps[$r]; $r++){ my @tmp = &monitors_of_app($apps[$r]); for($c = 0; $tmp[$c]; $c++){ $monitorsOfApps[$r][$c] = $tmp[$c]; &detaint($monitorsOfApps[$r][$c]); } } } ##################################################### # Function: # # probeDynamic # # Description: # # Probes the cluster for information on it's # current state. # # Arguments: # # none ##################################################### sub probeDynamic(){ # Here we probe the MIB of potentially every node in # the cluster looking for a node where cluster # services are active. The MIB info is extracted # and stored in the file $SNMPINFO_SOURCE. If an active # cluster node was found, snmpinfoRefresh returns a 1 # indicating we should go ahead an extract the cluster's # current state from the $SNMPINFO_SOURCE file. $overall_clstrmgr_state = &snmpinfoRefresh("$SNMPINFO_SOURCE"); if($overall_clstrmgr_state eq $ACTIVE){ if( !(-e "$SNMPINFO_SOURCE") ){ print STDERR `dspmsg scripts.cat -s 23 149 "\nERROR: SNMP information file %s not found.\n" "$SNMPINFO_SOURCE"`; } @snmpinfo = `cat $SNMPINFO_SOURCE`; chomp @snmpinfo; $clusterState = &state_of_cluster; $clusterSubstate = &substate_of_cluster; # Initializes @nodeStates, an array of the states of the nodes in @nodeNames # Initializes @interfaceStatesByNode, a 2D array of the states of the interfaces in @interfacesByNode # Initializes @networkStates, a 2D array of the states of the networks in @networksOfIpAddrs for($r = 0; $nodeNames[$r]; $r++){ $nodeStates[$r] = &state_of_node($nodeNames[$r]); for($c = 0; $interfacesByNode[$r][$c]; $c++){ $interfaceStatesByNode[$r][$c] = &state_of_interface($nodeNames[$r], $interfacesByNode[$r][$c]); $networkStates[$r][$c] = &state_of_network($nodeNames[$r], $networksOfIpAddrs[$r][$c]); } } # /for for($h = 0; $apps[$h]; $h++){ for($r = 0; $RgsByApp[$h][$r]; $r++) { $appStates[$h][$r] = &state_of_app_of_rg($apps[$h],$RgsByApp[$h][$r]); @tmp = &nodes_of_app_of_rg($apps[$h], $RgsByApp[$h][$r]); for($c = 0; $c <= $#tmp; $c++){ $nodesByApp[$h][$r][$c] = $tmp[$c]; } } # /for for($r = 0; $RgsByApp[$h][$r]; $r++) { $appStates[$h][$r] = &state_of_app_of_rg($apps[$h],$RgsByApp[$h][$r]); @tmp = &nodes_providing_app_of_rg($apps[$h], $RgsByApp[$h][$r]); for($c = 0; $c <= $#tmp; $c++){ $providingNodesByApp[$h][$r][$c] = $tmp[$c]; } } # /for # Now we'll calculate the fallover nodes for the applications. We must make sure not # to call fallover_node_for_app_of_rg on an app that runs concurrently across the nodes # of it's RG because it will cause an error. We'll store the fallover nodes in # @falloverNodesOfApps, which is in the same order as @apps. # my $x = `odmget -q group=$RgsByApp[$r] HACMPgroup | grep startup_pref`; if($RgsByApp[$h][0] ne "NONE"){ for($r = 0; $RgsByApp[$h][$r]; $r++) { my $startupPref = `$HACMPBASE/utilities/clgetgrp -cg $RgsByApp[$h][$r] | tail -1 | cut -f5 -d:`; chomp $startupPref; # If it's not a concurrent rg ... if( $startupPref ne "OAAN" ){ $falloverNodesOfApps[$h][$r] = &fallover_node_for_app_of_rg($apps[$h],$RgsByApp[$h][$r]); } } } for(my $h2 = 0; $h2 <= $#serviceIpLabelsOfApps; $h2++) { for($r = 0; $serviceIpLabelsOfApps[$h][$r][0] ; $r++){ for($c = 0; $serviceIpLabelsOfApps[$h][$r][$c]; $c++){ $serviceIpStates[$h2][$r][$c] = &state_of_service_label($serviceIpLabelsOfApps[$h2][$r][$c]); } } } } # /for return 0; } return 1; # No probe done because cluster services are not active } ##################################################### # Function: # # detaint # # Description: # # Transparent regex check to appease RBAC # # Arguments: # # $_[0]: variable to be detained ##################################################### sub detaint($) { $_[0] =~ /(.*)/; $_[0] = $1; } #Since this is a Perl module, '1;' must be the last statement 1;