#!/bin/ksh93
#  ALTRAN_PROLOG_BEGIN_TAG
#  This is an automatically generated prolog.
#
#  Copyright (C) Altran ACT S.A.S. 2017,2021.  All rights reserved.
#
#  ALTRAN_PROLOG_END_TAG
#
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# 61haes_r721 src/43haes/lib/ksh93/ezupdate/Cluster_t.sh 1.4 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 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 

# @(#)  7d4c34b 43haes/lib/ksh93/ezupdate/Cluster_t.sh, 726, 2147A_aha726, Feb 05 2021 09:50 PM

###############################
# Function list:
#  create
#  check_rte
#  display
#  file_propagate
#  refresh
###############################

typeset scriptname=${0##*/}

. ${EZU_LIB_DIR}/common
. ${EZU_LIB_DIR}/log

if [[ -z $NODE_T_DEFINED ]]; then
    . ${EZU_LIB_DIR}/Node_t
fi

###################################################
# This type to encapsulate cluster management
###################################################
typeset -T Cluster_t=(
    typeset -h 'name of the cluster'    name=""
    typeset -h 'cluster state'          state=""
    typeset -h 'cluster version'        version=""
    typeset -h 'cluster version number' version_number=""
    typeset -h 'resource groups list'   rg_list=""
    typeset -h 'unsync changes'         unsynced_changes=""
    typeset -h 'mixed cluster'          mixed=""
    typeset -h 'local node name'        local_node_name=""

    Node_t -A nodes

    #####################################################################
    #
    # NAME: display
    #
    # FUNCTION:
    #     Display the current Cluster_t object
    #
    # EXECUTION ENVIRONMENT:
    #
    # NOTES:
    #
    # RECOVERY OPERATION:
    #
    # DATA STRUCTURES:
    #     parameters:
    #     global:
    #           Cluster_t
    #
    # RETURNS: none
    #
    # OUTPUT:
    #####################################################################
    function display {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x

        typeset current_name=""
        print -- " cluster"
        print -- "    name="${_.name}
        print -- "    state="${_.state}
        print -- "    version="${_.version}
        print -- "    version number="${_.version_number}
        print -- "    resources groups="${_.rg_list}
        print -- "    unsync changes="${_.unsynced_changes}
        print -- "    mixed cluster="${_.mixed}

        for current_name in ${managed_nodes}
        do
            # This eval is necessary to set a compound object
            # to another compound object and this is necessary if ever
            # we want to call a method on this compound object
            eval "Node_t currentNode=${_.nodes[$current_name]}"
            currentNode.display
        done
    } # End of "display()"

    #####################################################################
    #
    # NAME: refresh
    #
    # FUNCTION:
    #     Refresh Cluster_t data
    #
    # EXECUTION ENVIRONMENT:
    #
    # NOTES:
    #     It runs the refresh function for each node of the cluster.
    #
    # RECOVERY OPERATION:
    #
    # DATA STRUCTURES:
    #     parameters:
    #     global:
    #           Cluster_t
    #
    # RETURNS: (int)
    #     RC.FAILURE - error with the clmgr command or while refreshing the nodes
    #     RC.OK      - Success
    #
    # OUTPUT:
    #####################################################################
    function refresh {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x

        typeset cmd=""
        typeset -i rc=${RC.OK}
        typeset cmd_output=""
        typeset tmp_version=""

        cmd="$CLMGR_CMD -cSa STATE,VERSION,VERSION_NUMBER,UNSYNCED_CHANGES query cluster"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd 2>&1)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        (( $rc != 0 )) && return ${RC.FAILURE}
        echo $cmd_output | IFS=: read _.state _.version _.version_number _.unsynced_changes

        # Refresh all nodes defined in the cluster
        for current_name in "${!_.nodes[@]}"
        do
            # This eval is necessary to set a compound object to
            # another compound object and this is necessary if ever
            # we want to call a method on this compound object

            eval "Node_t currentNode=${_.nodes[$current_name]}"
            currentNode.refresh
            (( $? != ${RC.OK} && rc == ${RC.OK} )) && rc=${RC.FAILURE}

            eval "_.nodes[$current_name]=$currentNode"
            [[ -z ${tmp_version} ]] && tmp_version=${currentNode.version}

            if [[ ${_.version} != ${currentNode.version} ]]
            then
                _.mixed="YES"
                echo ${_.version} | IFS="." read cl_mr cl_r cl_tl cl_sp
                echo ${currentNode.version} | IFS="." read node_mr node_r node_tl node_sp
                if (( ${node_mr} < ${cl_mr} ))
                then
                    _.version=${currentNode.version}
                elif (( ${node_r} < ${cl_r} ))
                then
                    _.version=${currentNode.version}
                elif (( ${node_tl} < ${cl_tl} ))
                then
                    _.version=${currentNode.version}
                elif (( ${node_sp} < ${cl_sp} ))
                then
                    _.version=${currentNode.version}
                fi
            fi
        done

        return $rc
    } # End of "refresh()"


    #####################################################################
    #
    # NAME: create
    #
    # FUNCTION:
    #     Create the Cluster_t object when it is declared.
    #a
    # EXECUTION ENVIRONMENT:
    #
    # NOTES:
    #     The discipline function create is special, it is invoked like a
    #     constructor when an instance is created.
    #
    # RECOVERY OPERATION:
    #
    # DATA STRUCTURES:
    #     parameters:
    #     global:
    #           Cluster_t
    #
    # RETURNS: Upon error this function exit the script with return code set.
    #     RC.FAILURE - a clmgr or other command failed
    #     RC.OK      - Success
    #
    # OUTPUT:
    #####################################################################
    function create {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x
        trap 'print -- "$CANNOT_INT_MSG"' INT

        typeset local_hostname=""
        typeset node_list=""
        typeset cmd=""
        typeset -i rc=${RC.OK}
        typeset cmd_output=""

        _.mixed="NO"

        # Get local hostname
        cmd="/usr/bin/hostname"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        (( $rc != 0 )) && exit ${RC.FAILURE}
        local_hostname=$cmd_output

        # Get local node name
        _.local_node_name=$(VERBOSE_LOGGING="" $GET_LOCAL_NODENAME)

        # Check run time environment
        _.check_rte
        rc=$?
        (( $rc != ${RC.OK} )) && exit $rc

        # Get cluster information using clmgr
        cmd="$CLMGR_CMD -cSa CLUSTER_NAME,STATE,VERSION,VERSION_NUMBER,UNSYNCED_CHANGES query cluster"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd 2>&1)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        if (( $rc != 0 ))
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"
            exit ${RC.FAILURE}
        fi
        echo $cmd_output | IFS=: read _.name _.state _.version _.version_number _.unsynced_changes

	#Check if the cluster is in unsync state then exit
	if [[ ${_.unsynced_changes} == "true" ]]
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 23 'ERROR: The cluster is in unsynced state. Please verify and sync the cluster and retry\n'
            exit ${RC.FAILURE}
        fi	
	
	DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 24 'INFO: The cluster: %1$s is in state: %2$s' "${_.name}" "${_.state}"

        # Get node list
        cmd="$CLMGR_CMD query nodes"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd 2>&1)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        if (( $rc != 0 ))
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"
            exit ${RC.FAILURE}
        fi
        node_list=$cmd_output

        # Get RG list
        cmd="$CLMGR_CMD query resource_group"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd 2>&1)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        if (( $rc != 0 ))
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"
            exit ${RC.FAILURE}
        fi
        _.rg_list=$cmd_output

        # Build the array of Node_t for each node of the cluster
        for node_name in $node_list
        do
            Node_t node
            node.rg_list=${_.rg_list}
            node.name=$node_name

            node.build
            if (( $? != ${RC.OK} ))
            then
                    DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"
                    exit ${RC.FAILURE}
            fi
            [[ -z ${_.version} ]] && _.version=${node.version}

            if [[ ${_.version} != ${node.version} ]]
            then
                _.mixed="YES"
                echo ${_.version} | IFS="." read cl_mr cl_r cl_tl cl_sp
                echo ${node.version} | IFS="." read node_mr node_r node_tl node_sp
                if (( ${node_mr} < ${cl_mr} ))
                then
                    _.version=${node.version}
                elif (( ${node_r} < ${cl_r} ))
                then
                    _.version=${node.version}
                elif (( ${node_tl} < ${cl_tl} ))
                then
                    _.version=${node.version}
                elif (( ${node_sp} < ${cl_sp} ))
                then
                    _.version=${node.version}
                fi
            fi

            eval "_.nodes[${node_name}]=${node}"
        done

        return $rc
    } # End of "create()"

    #####################################################################
    #
    # NAME: clrsh_cmd
    #
    # FUNCTION:
    #   Execute a command on a remote node.
    #
    # EXECUTION ENVIRONMENT:
    #
    # NOTES:
    #   It uses /usr/es/sbin/cluster/utilities/cl_rsh for remote node.
    #
    # RECOVERY OPERATION:
    #
    # DATA STRUCTURES:
    #     parameters:
    #           1: OUTPUT to put the output of the command execution
    #           2: node_name is the name of the node to run the command
    #           3: as many parameter as wanted to specify the command
    #     global:
    #           Cluster_t
    #
    # RETURNS: (int)
    #     the command return code
    #
    # OUTPUT:
    #     OUTPUT contains the output of the command
    #####################################################################
    function clrsh_cmd {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x

        typeset -n OUTPUT=$1
        shift;
        typeset node_name=$1
        shift;
        typeset cmd="$*"
        typeset -i cmd_rc
        typeset cmd_output=""
        cmd="$cmd 2>&1"

        cmd="$CLRSH -n ${node_name} '$cmd'"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$(eval $cmd)
        cmd_rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code:$cmd_rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        OUTPUT=$cmd_output
        return $cmd_rc
    } # End of "clrsh_cmd()"

    #####################################################################
    #
    # NAME: file_propagate
    #
    # FUNCTION:
    #     Copy all files in a given directory to all nodes in the cluster.
    #a
    # EXECUTION ENVIRONMENT:
    #
    # NOTES:
    #     The discipline function create is special, it is invoked like a
    #     constructor when an instance is created.
    #
    # RECOVERY OPERATION:
    #
    # DATA STRUCTURES:
    #     parameters:
    #        1  -  The directory of files to propagate
    #        2  _  the list of nodes to propagate
    #     global:
    #           Cluster_t
    #
    # RETURNS: (int)
    #     RC.EMPTY_DIR - Specified repository directory is empty
    #     RC.FAILURE   - an error occured
    #     RC.OK        - Success
    #
    # OUTPUT:
    #####################################################################
    function file_propagate {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x

        typeset SDIR="$1"
        typeset node_list=$2
        typeset cmd=""
        typeset cmd_output=""           # to get command output
        typeset node_name=""
        typeset FCHR=""
        typeset TDIR=""
        typeset FCHR=""
        typeset FCHR=""

        # remove ending slash if there is one
        SDIR=${SDIR%\/}

        if [[ -z $SDIR ]]
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 1 'Error, arg1 (directory name) is required.\n'
            return ${RC.FAILURE}
        fi

        # turn relative path into full path name
        FCHR=$(echo $SDIR | head -c1)
        if [[ "$FCHR" == "/" ]]
        then
            TDIR=$SDIR
        else
            TDIR=$(pwd)
            TDIR=$TDIR/$SDIR
        fi

        # Check for empty directory
        if [[ -z "$(ls -A $TDIR)" ]]
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 2 'ERROR: Directory %1$s is empty.\n' "$SDIR"
            return ${RC.EMPTY_DIR}
        fi

        # Get local node
        if [[ -z ${_.local_node_name} ]]
        then
           DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 3 'ERROR: getting local node.\n'
           return ${RC.FAILURE}
        fi

        [[ -z $node_list ]] && node_list="${!_.nodes[@]}"
        log_trace 0 "$0()[$LINENO]($SECONDS): node_list=$node_list"

        # Make the target directory on each node if needed
        for node_name in  $node_list
        do
            if [[ "${_.local_node_name}" != "${node}" ]]
            then
                cmd="/usr/bin/mkdir -p $TDIR"
                _.clrsh_cmd cmd_output $node_name "$cmd"
                if (( $? != 0 ))
                then
                    DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"
                    return ${RC.FAILURE}
                fi
            fi
        done

        # for each file in the list, copy it to all specified nodes
        for file in $(ls $TDIR)
        do
            if [[ -f $TDIR/$file ]]
            then
                for node_name in $node_list
                do
                    if [[ "${_.local_node_name}" != "${node_name}" ]]
                    then
                        $CLRCP $TDIR/$file $node_name:$TDIR/$file
                        if (( $? != 0 ))
                        then
                            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "cl_rcp $TDIR/$file $node_name:$TDIR/$file"
                            return ${RC.FAILURE}
                        else
                            log_trace 0 "$0()[$LINENO]($SECONDS): copied $TDIR/$file to node $node_name\n"
                        fi
                    fi
                done
            else
                log_trace 0 "$0()[$LINENO]($SECONDS): $file is not a regular file so is not copied\n"
            fi
        done

        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 5 'Source directory %1$s successfully copied to nodes: %2$s.\n' "$SDIR" "$node_list"

        return ${RC.OK}
    } # End of "file_propagate()"


    #####################################################################
    #
    # NAME: check_rte
    #
    # FUNCTION:
    #     checks the run time environment required for EZUpdate
    #         - root user
    #         - PowerHA version 7.1.3 or later
    #         - AIX version 7 or greater
    #         - each node can send and receive clcomd messages
    # NOTES:
    #
    # DATA STRUCTURES:
    #     parameters:
    #     global:
    #           Node_t
    #
    # RETURNS: (int)
    #     RC.FAILURE       - RTE check error
    #     RC.PHA_CMD_ERROR - PowerHA command error
    #     RC.OK            - Success
    #
    # OUTPUT
    ###################################################################
    function check_rte {
        [[ "$VERBOSE_LOGGING" == "high" ]] && set -x
        [[ "$DEBUG_MODE" == "yes" ]] && set -x

        typeset cmd=""
        typeset cmd_output=""           # to get command output
        typeset -i rc=${RC.OK}
        typeset VV="" RR="" MM="" FF=""
        typeset -i MAX_TRIES=5
        typeset node=""
        typeset node_list=""
        typeset -i try=0 noOutbound=1 noInbound=1

        #
        : Check for root authority
        #
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 6 'Checking for root authority...\n'
        log_trace 0 "$0: whoami: [[ \"$(whoami)\" != \"root\" ]]"
        if [[ "$(whoami)" != "root" ]]
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 7 'ERROR: you must be root to use this program.\n'
            return ${RC.FAILURE}
        else
            DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 8 '    Running as root.\n\n'
        fi

        #
        : Check for AIX level greater than 6
        #
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 9 'Checking for AIX level...\n'
        /usr/bin/oslevel | IFS=. read VV RR MM FF
        log_trace 0 "$0()[$LINENO]($SECONDS): VV=$VV RR=$RR MM=$MM FF=$FF"

        if (( $VV < 7 ))
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 10 'ERROR: EZUpdate requires AIX version 7 or later.\n'
            return ${RC.FAILURE}
        fi
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 11 '    The installed AIX version is supported.\n\n'

        #
        : Check PowerHA version - must be 7.1.3 or later
        #
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 12 'Checking for PowerHA SystemMirror version.\n'
        if ! /usr/bin/lslpp -lcqOr cluster.es.server.rte 2>/dev/null | /usr/bin/cut -f3 -d":" | IFS=. read version release mod fix
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 13 'ERROR: PowerHA SystemMirror is not installed.\n'
            return ${RC.FAILURE}
        else
            if (( $version == 7 ))
            then
                if (( $release == 1 ))
                then
                    if (( $mod < 3 ))
                    then
                        DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 14 'ERROR: EZUpdate requires PowerHA SystemMirror version 7.1.3 or later.\n'
                        return ${RC.FAILURE}
                    fi
                elif (( $version < 1 ))
                then
                    DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 14 'ERROR: EZUpdate requires PowerHA SystemMirror version 7.1.3 or later.\n'
                    return ${RC.FAILURE}
                fi
            elif (( $version < 7 ))
            then
                DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 14 'ERROR: EZUpdate requires PowerHA SystemMirror version 7.1.3 or later.\n'
                return ${RC.FAILURE}
            fi
        fi
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 19 '    The installed PowerHA SystemMirror version is supported.\n\n'

        #
        : Get node list
        : Need a query here as the node list is not built so far
        #
        cmd="$CLMGR_CMD query nodes"
        log_trace 0 "$0()[$LINENO]($SECONDS): execute cmd: $cmd"
        cmd_output=$($cmd 2>&1)
        rc=$?
        log_trace 0 "$0()[$LINENO]($SECONDS): command returns code: $rc"
        log_trace 0 "$0()[$LINENO]($SECONDS): command output='$cmd_output'"
        if (( $rc != 0 ))
        then
            DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 4 'ERROR: command "%1$s" failed.\n' "$cmd"

            #Print the error output to specify the cause of the failure
	    #Printing only the command output which need not be translated so just printing in English
	    print -- "$cmd_output"
	    return ${RC.PHA_CMD_ERROR}
        fi
        node_list=$cmd_output

        #
        : Check clcomd communication
        #
        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 15 'Checking for clcomd communication on all nodes...\n'
        for node in ${node_list}
        do
            #
            : Check outbound communication for $node
            #
            noOutbound=1
            for (( try=0; try<$MAX_TRIES; try++ ))
            do
                cmd="/usr/bin/hostname"
                _.clrsh_cmd output $node "$cmd"
                if (( $? == 0 ))
                then
                    noOutbound=0
                    break
                elif (( try < 4 ))
                then
                    sleep 1
                fi
            done
            if (( noOutbound ))
            then
                DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 16 'ERROR: Unable to verify outbound clcomd communication to node: %1$s\n' "${node}"
                return ${RC.FAILURE}
            fi

            #
            : Check inbound communication for $node
            #
            noInbound=1
            for (( try=0; try<$MAX_TRIES; try++ ))
            do
                $CLRSH -n $node "$CLRSH -n ${_.local_node_name} /usr/bin/hostname" >/dev/null 2>&1
                if (( $? == 0 ))
                then
                    # Inbound communication is working
                    noInbound=0
                    break
                elif (( try < 4 ))
                then
                    sleep 1
                fi
            done
            if (( noInbound ))
            then
                DSP_MSG ${MSG_TYPE.ERR} $CLUSTER_T_SET 17 'ERROR: Unable to verify inbound clcomd communication to node: %1$s\n' "${node}"
                return ${RC.FAILURE}
            fi
        done

        DSP_MSG ${MSG_TYPE.INF} $CLUSTER_T_SET 18 '    clcomd on each node can both send and receive messages.\n\n'
        return ${RC.OK}
    } # End of "check_rte()"
)


export CLUSTER_T_DEFINED=1
