#!/usr/bin/perl # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # # # Licensed Materials - Property of IBM # # (C) COPYRIGHT International Business Machines Corp. 2001,2019 # 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 # sccsid = "@(#)84 1.25 src/rsct/rm/ConfigRM/cli/bin/rmrpnode.perl, configrmcli, rsct_rady, rady2035a 11/12/15 16:40:32" ###################################################################### # # # Module: rmrpnode # # # # Purpose: # # rmrpnode - removes a node from an RSCT peer domain definition. # # # # Syntax: # # rmrpnode [-h] [-f] [-q] [-TV] Node_name [Node_name ...] # # # # rmrpnode [-h] [-f] [-q] -F File [-TV] # # # # Flags: # # -h Help. Writes the command's usage statement to standard # # output. # # -f Force. The force option overrides the majority rule for # # making configuration changes. # # -q Quiet. Do not return an error if the node cannot be # # removed from the peer domain. # # -F File The file containing the nodes names to be removed from # # the peer domain. The node names are defined as Node_name # # operands or in a file, not both. When in a file, each # # line of the file is scanned for one node name. Comments # # may be placed on each line, but after the comment # # character "#". Lines that start (col 1) with "#" or are # # entirely blank are ignored. Use -F "-" to specify STDIN # # as the input file. # # -T Trace. Writes the command's trace messages to standard # # error. For your software-service organization's use only.# # -V Verbose. Writes the command's verbose messages to # # standard output. # # # # Operands: # # Node_name The node to be removed from the peer domain # # definition. The node name is the peer domain node # # name of the node. The peer domain node names can be # # displayed using the lsrpnode command. # # # # Description: # # The rmrpnode command removes the specified nodes from the online # # peer domain of where the command is run. The command must be # # run on a node that is online to the peer domain in which the # # nodes are to be removed. The nodes to be removed must be # # offline to the peer domain and must be reachable from the node # # that runs the command. Use the stoprpnode command to bring # # nodes offline. # # # # Exit Values: # # 0 CRM_CLI_SUCCESS Command completed successfully. # # 1 CRM_CLI_RMC_ERROR Command terminated due to an underlying # # RMC error. # # 2 CRM_CLI_ERROR Command terminated due to an underlying # # error in the command script. # # 3 CRM_CLI_BAD_FLAG Command terminated due to user # # specifying an invalid flag. # # 4 CRM_CLI_BAD_OPERAND Command terminated due to user # # specifying a bad operand. # # 5 CRM_CLI_USER_ERROR Command terminated due to a user error, # # for example specifying a name that # # already exists. # # 6 CRM_CLI_NOT_FOUND Node not defined in peer domain. # # # # Examples: # # 1. To remove the peer domain definitions of nodes nodeB and # # nodeC from the peer domain ApplDomain, when nodeA is defined # # and online to ApplDomain, and nodeB and nodeC are reachable # # from nodeA, run the following command from nodeA: # # rmrpnode nodeB nodeC # # # # Man Page: # # For the most current detailed description of this command see # # the rmrpnode man page in /opt/rsct/man. # # # #--------------------------------------------------------------------# # Inputs: # # /opt/rsct/msgmaps/configrmcli.rmrpnode.map - # # message mapping # # # # Outputs: # # stdout - none. # # stderr - any error message. # # # # External Ref: # # Commands: ctdspmsg # # Modules: CRM_cli_utils.pm, CRM_cli_rc.pm, # # CRM_cli_include.pm, CT_cli_utils.pm # # Perl library routines: Getopt::Std # # # # Tab Settings: # # 4 and tabs should be expanded to spaces before saving this file. # # in vi: (:set ts=4 and :%!expand -4) # # # # Change Activity: # # 010806 JAC 75435: Initial design & write. # # 010827 JAC 75436: Change some initial checking. # # 011008 JAC 75442: Put in the -q flag. # # 011219 JAC 78807: Don't resolve node names or check if in cluster# # 020201 JAC 79963: switch to use rmrsrc-api instead of rmrsrc. # # 020207 JAC 80121: Make printing of c-api results a trace msg. # # 020422 JAC 82248: Rename rmclnode to rmrpnode. # # 020428 JAC 82316: Call process_exit_code to check $rc. # # 020502 JAC 82564: Set peer domain scope before calling rmrsrc-api# # 020724 JAC 84621: Use NodeNameList instead of Name in rmrsrc. # # 020822 JAC 86035: Add -f force option. # # 030324 JAC 93122: Don't specify default command arguments. # # 040319 JAC 97463: Add -F flag for file input for node names. # # 040406 JAC 106712: Update usage for file options. # # 050218 JAC 111917: Use runact-api instead of rmrsrc. # # 050406 JAC 119510: Add rc when calling process_api_error. # # 050506 JAC 119921: Fix verbose message when node list not known. # # 080107 JAC 148354: Remove reference to resolve_node_names. # ###################################################################### #--------------------------------------------------------------------# # General Program Flow/Logic: # # # # 1. Parse command line flags and operands. # # 2. Print usage if -h specified # # 3. find the name of the node you're on and make sure it is online # # to the cluster. It's an error if it isn't. # # 4. Resolve the target node names. # # 5. Make sure the target node is in the cluster and offline # # -For CAA, the opstate is ignored if the target node is online # # 6. Call RMC command rmrsrc. Also pass along -VT if necessary. # # 7. Return back any errors. # # # #--------------------------------------------------------------------# #--------------------------------------------------------------------# # Included Libraries and Extensions # #--------------------------------------------------------------------# use lib "/opt/rsct/pm"; use locale; use Getopt::Std; use CT_cli_utils qw(printIMsg printEMsg); use CRM_cli_rc qw(CRM_CLI_SUCCESS CRM_CLI_RMC_ERROR CRM_CLI_ERROR CRM_CLI_BAD_FLAG CRM_CLI_BAD_OPERAND CRM_CLI_USER_ERROR CRM_CLI_NOT_FOUND); use CRM_cli_utils qw(error_exit printCIMsg printCEMsg check_node_not_state check_for_nodes process_api_error process_exit_code get_nodes_nums_from_file get_opstate_by_name get_domain_type); use CRM_cli_include qw($TRUE $FALSE $RSNODE $RMC_CLI_USER_ERROR $RMC_OPSTATE_ONLINE $PEER_DOMAIN_SCOPE $CTBINDIR $CTDIR $DELIMITER); #--------------------------------------------------------------------# # Global Variables # #--------------------------------------------------------------------# $Trace = $FALSE; # default - trace off $Verbose = $FALSE; # default - verbose turned off $Opt_Quiet = $FALSE; # default - no Quiet option $Opt_Force = $FALSE; # default - no Force option $Opt_File_Input = $FALSE; # default - no file $ACTION_NOT_ALLOWED_RC = 65550; # retruned by configrm $PROGNAME = "rmrpnode"; # Program Name for messages $LSMSG = "$CTBINDIR/ctdspmsg"; # list / display message rtn $MSGCAT = "configrmcli.cat"; # msg catalogue for this cmd $ENV{'MSGMAPPATH'} = "$CTDIR/msgmaps"; # msg maps used by $LSMSG #--------------------------------------------------------------------# # Variables # #--------------------------------------------------------------------# my @node_names = (); # nodes in cluster my $resolved_node_names = ""; # resolved nodelist my $unresolved_node_names = ""; # unresolved nodelist my @not_online_node_names = (); # offline (not online) nodelist my $one_node = ""; # one from nodelist my %node_opstates = (); # shows node name and opstate my @node_info = (); # array version of %node_opstates my $node_offline = $FALSE; # boolean my $in_list = ""; # reference to array my $not_in_list = ""; # reference to array my @nnodes = (); # number of nodes my $file_name = ""; # file name for node names my $file_error = ""; # error with file my $node_names_file = ""; # reference to node names my $node_nums_file = ""; # reference to node numbers my @node_numbers = (); # nodes in cluster my $Use_rmrsrc = $FALSE; # is env var set to use old way my $force_element = ""; # force for runact my $passopts = ""; # TV options to pass to RMC CLI my $other_opts = ""; # parameters to pass to RMC CLI my $delimiter = "#:#"; $DELIMITER = $delimiter; # override so utils don't choke on IPv6 "::" #--------------------------------------------------------------------# # Main Code # #--------------------------------------------------------------------# my $rc = 0; my $config_rc = 0; my $tmp_rc = 0; # set peer domain scope $ENV{CT_MANAGEMENT_SCOPE} = $PEER_DOMAIN_SCOPE; # parse the command line, exit if there are errors ($rc, $file_name, @node_names) = &parse_cmd_line; ($rc == 0) || error_exit($rc); if ($Verbose) { printIMsg("IMsgrmrpnodeStarted"); } if ($Trace) { $passopts = $passopts." -T"; } if ($Verbose) { $passopts = $passopts." -V"; } # if CT_CONFIGRMCLI_METHOD is set, use startrsrc-api (old way) if (defined $ENV{CT_CONFIGRMCLI_METHOD}) { $Use_rmrsrc = $TRUE; } # set the force option, if specified if ($Opt_Force) { $other_opts = "${delimiter}Force${delimiter}1"; $force_element = "${delimiter}ForcedFlag${delimiter}1"; } #93122 - comment out specifying the default command argument #else { $other_opts = "${delimiter}Force${delimiter}0"; } # get the nodes opstate info %node_opstates = get_opstate_by_name($RSNODE); # make sure there are nodes in the cluster @nnodes = keys %node_opstates; if ($#nnodes <0) { printCEMsg("EMsgConfigRMcliNoCluster"); exit (CRM_CLI_USER_ERROR); } # get the node names from a file, if specified if ($Opt_File_Input) { # extract the node names from the file ($node_names_file, $node_nums_file) = get_nodes_nums_from_file($file_name); # copy to other array @node_names = @$node_names_file; @node_numbers = @$node_nums_file; } if ($Verbose) { printIMsg("IMsgrmrpnodeNodeList"); foreach $one_node (@node_names) { print(" ", $one_node); } print("\n"); } # make sure the target nodes are in the cluster ($in_list, $not_in_list) = check_for_nodes(\@node_names, \%node_opstates); # if not quiet, for nodes not in the list (not in the cluster), print error message if (!$Opt_Quiet) { foreach $one_node (@$not_in_list) { printEMsg("EMsgrmrpnodeNotInCluster",$one_node); $config_rc = CRM_CLI_NOT_FOUND; } } # for nodes in the list (in the cluster), # make sure they're offline and then add to list for deleting from cluster # Bypass the online check if this is CAA domain type foreach $one_node (@$in_list) { if(get_domain_type()){ push (@not_online_node_names, $one_node); } else { $node_offline = check_node_not_state($one_node,$RMC_OPSTATE_ONLINE, %node_opstates); if (!$node_offline) { # write error message if not quiet if (!$Opt_Quiet) { printEMsg("EMsgrmrpnodeNotOffline",$one_node); $config_rc = CRM_CLI_USER_ERROR; } } # it's offline so add to list to delete else { push (@not_online_node_names, $one_node); } } } # decide whether old way or new way # if CT_CONFIGRMCLI_METHOD is set, do old way if ($Use_rmrsrc) { $tmp_rc = rm_by_rmrsrc($other_opts, \@not_online_node_names); } # else use new way else { $tmp_rc = rm_by_action($force_element, $other_opts, \@not_online_node_names); } if ($tmp_rc != 0) { $config_rc = $tmp_rc}; if ($Verbose) { printIMsg("IMsgrmrpnodeEnded"); } if ($config_rc == 0) { exit($rc); } else { exit($config_rc); } #--------------------------------------------------------------------# # End Main Code # #--------------------------------------------------------------------# #--------------------------------------------------------------------# # parse_cmd_line - Parse the command line for options and operands. # # Set appropriate global variables as outlined below, make sure we # # have a valid combination of arguments / options. # # # # Return: # # $rc 0 Command line parsed fine, no problem. # # CRM_CLI_BAD_FLAG Command line contained a bad flag. # # @node_names Nodes to add to the cluster # # # # Global Variables Modified: # # $Verbose output True (-V) turn Verbose mode on. # # $Trace output True (-T) turn Trace mode on. # # $Opt_Force output True (-f) Force option specified. # # $Opt_Quiet output True (-q) Quiet option specified. # # $Opt_File_Input output True (-F) file name specified # #--------------------------------------------------------------------# sub parse_cmd_line { my(@original_argv) = @ARGV; my @node_names = (); # node names my $file_name = ""; # file name my %opts = (); # Process the command line... if (!&getopts('hfF:qVT', \%opts)) { # Gather options; # if errors &print_usage; # display proper usage return CRM_CLI_BAD_FLAG; # return bad rc - bad flag } # process h flag if (defined $opts{h}) { # -h, help request &print_usage; # print usage statement exit(0); # all done with good return! } if (defined $opts{T}) { # -T turn trace on $Trace = $TRUE; } if (defined $opts{V}) { # -V turn verbose mode on $Verbose = $TRUE; } if (defined $opts{f}) { # -f turn force on $Opt_Force = $TRUE; # -f flag specified } if (defined $opts{q}) { # -q turn quiet mode on $Opt_Quiet = $TRUE; # -q flag specified } # Get the arguments... # Operands: one or more node names if ($#ARGV >= 0) { # cluster name and maybe more @node_names = @ARGV; # get node names } # make sure -F for file used if there are no node names if (($#node_names < 0) && (!defined $opts{F})) { printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands"); &print_usage; return CRM_CLI_BAD_OPERAND; } if (defined $opts{F}) { # -F for file $Opt_File_Input = $TRUE; $file_name = $opts{F}; # make sure file and cmd line not both used for node names if ($#node_names >= 0) { printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands"); &print_usage; return CRM_CLI_BAD_OPERAND; } } return(0, $file_name, @node_names); # success } # end parse_cmd_line #--------------------------------------------------------------------# # print_usage : print the usage statement (syntax) to stdout. # #--------------------------------------------------------------------# sub print_usage { &printIMsg("IMsgrmrpnodeUsageF"); } # end print_usage #--------------------------------------------------------------------# # rm_by_rmrsrc - Remove nodes by using rmrsrc-api. # # # # Return: # # $rc Return code from rmrsrc-api. # # # # Input: # # $options Options to use on rmrsrc-api. # # \@nodes_to_remove Reference to a list of nodes to remove. # # # #--------------------------------------------------------------------# sub rm_by_rmrsrc { my $rc = 0; # return code my $config_rc = 0; # return code for all my $options = shift(@_); # options my $nodes_to_remove = shift(@_); # nodes to remove (array ref) my $one_node = ""; # single node my @cmd_out = (); # rmrsrc output # for each node to removed from the cluster, call rmrsrc foreach $one_node (@$nodes_to_remove) { if ($Trace) { print STDERR "$PROGNAME: calling rmrsrc-api\n";} # $rmrsrc_out = `$CTBINDIR/rmrsrc $passopts -s "Name == \\\"$one_node\\\"" $RSNODE 2>&1`; # @cmd_out=`$CTBINDIR/rmrsrc-api -s ${RSNODE}${delimiter}"Name==\\\"$one_node\\\"" 2>&1`; @cmd_out=`$CTBINDIR/rmrsrc-api -s -D"$delimiter" -I"$delimiter" "${RSNODE}${delimiter}NodeNameList|<{\\\"${one_node}\\\"}${options}" 2>&1`; # capture the return code from rmrsrc $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "rmrsrc-api results:\n"; print STDERR "@cmd_out"; print STDERR "$PROGNAME: rmrsrc-api returned $rc\n";} # show any errors if there was a bad rc if ($rc != 0) { process_api_error($delimiter,$rc,@cmd_out); } # save rc as ConfigRM CLI user error if it's an RMC CLI user error if ($rc == $RMC_CLI_USER_ERROR) { $config_rc = CRM_CLI_USER_ERROR; } # if rmrsrc failed, print RMC error message and save rc if (($rc != 0) && ($rc != $RMC_CLI_USER_ERROR)) { # printCEMsg("EMsgConfigRMcliUnExpectRMCrc",$rc); $config_rc = CRM_CLI_RMC_ERROR; } } return ($config_rc); } # end rm_by_rmrsrc #--------------------------------------------------------------------# # rm_by_action - Remove nodes by using runact-api. # # # # Return: # # $rc Return code from runact-api. # # # # Input: # # $options Options to use on runact-api. # # \@nodes_to_remove Reference to a list of nodes to remove. # # # #--------------------------------------------------------------------# sub rm_by_action { my $rc = 0; # return code my $config_rc = 0; # return code for all my $action_rc = 0; # return code for action my $options = shift(@_); # options my $rmrsrc_opts = shift(@_); # rmrsrc options just in case my $nodes_to_remove = shift(@_); # nodes to remove (array ref) my $one_node = ""; # single node my $remove_nodes = ""; # list of nodes to remove my $comma_needed = $FALSE; # add comma to list? my @cmd_out = (); # runact output # build a string containing the list of node names to remove $remove_nodes = ""; $comma_needed = $FALSE; foreach $one_node (@$nodes_to_remove) { if ( $comma_needed ) { $remove_nodes .= "," . $one_node; } else { $remove_nodes .= $one_node; $comma_needed = $TRUE; } } # call runact-api to remove the nodes if there are nodes to remove if ($#$nodes_to_remove >= 0) { # Defect 161752 #@cmd_out=`$CTBINDIR/runact-api -D"$delimiter" -I"$delimiter" -c "${RSNODE}${delimiter}${delimiter}DeleteMultipleNodes${delimiter}NodeNames${delimiter}{${remove_nodes}}${options}" 2>&1`; my $cmd = "$CTBINDIR/runact-api -D\"$delimiter\" -I\"$delimiter\" -c ${RSNODE}${delimiter}${delimiter}DeleteMultipleNodes${delimiter}NodeNames${delimiter}"."\"{${remove_nodes}}\"${options}"; if ($Trace) { print STDERR "$PROGNAME: calling runact-api\n";} if ($Trace) { print STDERR "$PROGNAME: $cmd\n";} @cmd_out=`$cmd 2>&1`; # capture the return code from runact-api $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "runact-api results:\n"; print STDERR "@cmd_out"; print STDERR "$PROGNAME: runact-api returned $rc\n";} # show any errors if there was a bad rc if ($rc != 0) { $action_rc = process_api_error_remove_action($delimiter,$rc,@cmd_out); } # save rc as ConfigRM CLI user error if it's an RMC CLI user error if ($rc == $RMC_CLI_USER_ERROR) { $config_rc = CRM_CLI_USER_ERROR; } # if runact-api failed, print RMC error message and save rc if (($rc != 0) && ($rc != $RMC_CLI_USER_ERROR) && ($action_rc != $ACTION_NOT_ALLOWED_RC) ) { # printCEMsg("EMsgConfigRMcliUnExpectRMCrc",$rc); $config_rc = CRM_CLI_RMC_ERROR; } # if the action failed because the version of the group leader is not at the correct level # do the remove the old way if ($action_rc == $ACTION_NOT_ALLOWED_RC) { $config_rc = rm_by_rmrsrc($rmrsrc_opts, $nodes_to_remove); } } return ($config_rc); } # end rm_by_action #--------------------------------------------------------------------# # process_api_error_remove_action -Scans the input varaible for error# # messages found by the c-api command. The c-api command errors are# # found by searching each output line for "ERROR" at the # # beginning of the line. The error message printed is the last # # double colon delimited string. # # # # This version checks for api rc=65550. This means the action can't # # run on this configuration and we need to go back and use # # rmrsrc-api. # # # # # # # # Parameters: # # $delimiter The delimiter used in the error string. # # $rc The return code from the c-api call. # # @command_output The output from the command. # # # # Returns: # # 0 If everything went ok. # # 65550 If action failed due to api rc=65550. # # # # Global Variables: # #--------------------------------------------------------------------# sub process_api_error_remove_action { my $rc = 0; # returned rc my $delimiter = shift(@_); # output delimiter my $api_rc = shift(@_); # rc from c-api call my @command_output = @_; # command output to scan my $error_count = 0; # number of error messages my $line = ""; # scan each line for ERROR foreach $line (@command_output) { # does it start with ERROR? if ($line =~ /^ERROR.*/) { # split it apart based on the delimiter # last one should be error message @error_parts = split /$delimiter/, $line; # check for scope error if ($error_parts[$#error_parts -1] == $RMC_SCOPE_ERROR) { printCEMsg("EMsgConfigRMcliNoCluster"); exit(CRM_CLI_USER_ERROR); } $error_count++; # check for action not allowed error if ($error_parts[$#error_parts -1] == $ACTION_NOT_ALLOWED_RC) { $rc = $ACTION_NOT_ALLOWED_RC; } # output the message (except for scope/action) else { # replace any escaped new lines with new lines $error_parts[$#error_parts] =~ s/\\n/\n/g; # last one should be error message. print it to STDERR. print STDERR $error_parts[$#error_parts]; } } } # print unexpected error if api rc was not 0 and no messages were displayed if (($error_count == 0) && ($api_rc != 0)) { printCEMsg("EMsgConfigRMcliUnexpectErrorRC",$api_rc); } return ($rc) } # end of process_api_error_remove_action