#!/bin/ksh
#
#pragma ident   "@(#)mstate_sync.m4 1.8     00/10/30 SMI"
#
# Copyright (c) 1997-1999 by Sun Microsystems, Inc.
# All rights reserved.
#
# mstate_sync - Maintenance mode synchronization command
#
# The purpose of this command is to execute on all nodes whenever the
# row corresponding to the LOGHOST_MSTATE format is updated in the
# CCD. Whenever a row of this format is changed, the fault monitors
# for the associated data service are restarted.
#
# The arguments obtained by this command are:
#
# <keyword> - can be one of CCDSYNC_PRE_ADDU, CCDSYNC_POST_ADDU,
#			    CCDSYNC_PRE_REMOVE, CCDSYNC_POST_REMOVE
#
# <row>     - the row being added/removed
#
# <ccdfile> - the full pathname of the dynamic CCD file. This is
#	      required because the dynamic CCD cannot be queried in
#	      the conventional manner from this command. It has to be
#	      queried as a static database.
#
# <numnodes> - integer identifying the number of nodes in the static
#	       cluster configuration.
#
# <curr_members> - list of node ids identifying the current cluster
#		   membership.
#
# <localnodeid> - node id of this node.

keyword=$1

case ${keyword} in
	CCDSYNC_PRE_RESTORE | CCDSYNC_PRE_RESTOREKEY | \
		CCDSYNC_POST_RESTORE | CCDSYNC_POST_RESTOREKEY)
			row=""
			CCDFILE=$2
			numnodes=$3
			curr_members=$4
			localnodeid=$5 ;;
	*)
			row=$2
			CCDFILE=$3
			numnodes=$4
			curr_members=$5
			localnodeid=$6 ;;
esac
		
# This should be removed later
export CCDFILE

pre="SUNWcluster.mstate"

###########################################################################
#
# function init
#
# The init function just initializes a lot of global variables. It also
# computes the dependency of the data services sitting on top of this
# logical host, and initializes arrays that corresponds to the data
# services's state, the logical hosts of the data service mastered on this
# node, not mastered on this node, etc.

function init
{
typeset loghost_row
typeset r rows
typeset ds_list dsname
typeset ordered_ds_list
typeset all_loghosts lm_loghosts nm_loghosts
typeset n
typeset pdbapps


# Cluster Application Bit Assignment. Refer to the cdb file for all
# assignments

CVM=3
VxVM=4
SDS=5
Netdisk=6

CLUSTERBIN=/opt/SUNWcluster/bin
CLUSTERVAR=/var/opt/SUNWcluster
CLUSTERETC=/etc/opt/SUNWcluster/conf
SSACLI=${CLUSTERBIN}/scssa
MDBIN=/usr/sbin


CLUSTERNAME=$(/bin/cat ${CLUSTERETC}/default_clustername)
cdbfile=${CLUSTERETC}/${CLUSTERNAME}.cdb
localhostname=$(${CLUSTERBIN}/cdbmatch cluster.node.${localnodeid}.hostname \
			${cdbfile})
clusterstate=$(${CLUSTERBIN}/clustm getstate ${CLUSTERNAME})

pdbapps=$(${CLUSTERBIN}/cdbmatch cluster.pdbapps ${cdbfile})
vm=$(${CLUSTERBIN}/appmatch ${pdbapps} ${CVM})
if [[ "${vm}" = "1" ]]; then
  vm=cvm
else
  vm=$(${CLUSTERBIN}/appmatch ${pdbapps} ${VxVM})
  if [[ "${vm}" = "1" ]]; then
    vm=vxvm
  else
      vm=$(${CLUSTERBIN}/appmatch ${pdbapps} ${SDS})
      if [[ "${vm}" = "1" ]]; then
        vm=sds
        if [[ ! -x ${MDBIN}/metaset || ! -x ${MDBIN}/metastat ]]; then
          log_info "${pre}.4342" "DiskSuite is not properly installed."
          exit 1
        fi
      else
        log_info "${pre}.4340" "Volume manager is not properly configured."
          exit 1
      fi
  fi
fi

let n=0
allnodes=""
while (( n < ${numnodes} )); do
  allnodes="${allnodes} ${n}"
  let n=n+1
done

# retrieve name of logical host and current master

lname=${row%:*}
lname=${lname#*:}
curr_master=${row#*:*:}

loghost_row=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} LOGHOST query \
			lname ${lname})
if [[ -z "${loghost_row}" ]]; then
  log_info "${pre}.4010" "$0 ${keyword} ${row}: The definition of logical host ${lname} does not exist in ${CCDFILE}"
  exit 1
fi

# split the information in loghost_row

dglist=${loghost_row%:*:*}
dglist=${dglist#*:*:*:}
dglist=$(print ${dglist} | tr ',' ' ')

nodelist=${loghost_row%:*:*:*}
nodelist=${nodelist#*:*:}
nodelist=$(print ${nodelist} | tr ',' ' ')

let in_nodelist=0
for nodename in ${nodelist}; do
  if [[ "${nodename}" = "${localhostname}" ]]; then
    let in_nodelist=1
    break
  fi
done

iplist=${loghost_row%:*}
iplist=${iplist#*:*:*:*:}
iplist=$(print ${iplist} | tr ',' ' ')

# get list of all data services on this logical host

rows=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} LOGHOST_DS query \
		lname ${lname})
ds_list=""
for r in ${rows}; do
  dsname=${r#*:*:}
  ds_list="${ds_list} ${dsname}"
done

ordered_ds_list=""
get_ordered_ds_list "${ds_list}" ordered_ds_list
ordered_ds_list=$(print ${ordered_ds_list} | tr ',' ' ')

# initialize arrays

set -A ordered_ds_array ${ordered_ds_list}
set -A all_loghosts_array
set -A lm_loghosts_array
set -A nm_loghosts_array
set -A ds_state_array

let n=0
while (( n < ${#ordered_ds_array[*]} )); do
  all_loghosts=""
  lm_loghosts=""
  nm_loghosts=""
  construct_method_args_list ${ordered_ds_array[n]} all_loghosts lm_loghosts \
		nm_loghosts
  all_loghosts_array[n]=${all_loghosts}
  lm_loghosts_array[n]=${lm_loghosts}
  nm_loghosts_array[n]=${nm_loghosts}

  # retrieve the state of the data service

  rows=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_STATE query \
			ds_name ${ordered_ds_array[n]})
  ds_state_array[n]=${rows#*:*:}
  let n=n+1
done
}

###########################################################################
#
# function construct_method_args_list i_dsname o_all_loghosts o_lm_loghosts
#				      o_nm_loghosts
#
# i_dsname - The data service name forms the input parameter
#
# o_all_loghosts - An output parameter that is a list of all the logical
#		   hosts in the cluster configuration offering this data
#		   service.
#
# o_lm_loghosts - An output parameter that is a list of all logical hosts
#		  that this physical node is supposed to master that is
#		  offering this data service.
#
# o_nm_loghosts - An output parameter that is a list of all logical hosts
#		  that this physical node is not supposed to master that is
#		  offering this data service.

function construct_method_args_list
{
typeset i_dsname
typeset o_all_loghosts
typeset o_lm_loghosts
typeset o_nm_loghosts
typeset cm_row cm
typeset r rows
typeset loghost_name
typeset tmp_list1 tmp_list2
i_dsname=$1

# get list of all logical hosts offering this data service

rows=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} LOGHOST_DS query \
			dsname ${i_dsname})

o_all_loghosts=""
o_lm_loghosts=""
o_nm_loghosts=""

for r in ${rows}; do
  loghost_name=${r%:*}
  loghost_name=${loghost_name#*:}
  if [[ -z "${o_all_loghosts}" ]]; then
    o_all_loghosts=${loghost_name}
  else
    o_all_loghosts="${o_all_loghosts},${loghost_name}"
  fi

  # special handling is required for the abort case, because the LOGHOST_CM
  # entries are not removed from the CCD and no decisions about mastered and
  # not-mastered lists should be made based on these entries. After the abort
  # transition, we know that this node will not own anything, so we stick
  # everything into the not-mastered list.

  if [[ "${clusterstate}" = "abort" ]]; then
    if [[ -z "${o_nm_loghosts}" ]]; then
      o_nm_loghosts=${loghost_name}
    else
      o_nm_loghosts="${o_nm_loghosts},${loghost_name}"
    fi
    continue
  fi

  # retrieve the current master of the logical host

  cm_row=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} LOGHOST_CM query \
			lname ${loghost_name})
  cm=${cm_row#*:*:}

  if [[ ( -n "${cm}" && "${cm}" = "${localhostname}" ) || \
        ( "${loghost_name}" = "${lname}"  && "${curr_master}" = "${localhostname}" ) \
     ]]; then
    if [[ -z "${o_lm_loghosts}" ]]; then
      o_lm_loghosts=${loghost_name}
    else
      o_lm_loghosts="${o_lm_loghosts},${loghost_name}"
    fi
  else
    if [[ -z "${o_nm_loghosts}" ]]; then
      o_nm_loghosts=${loghost_name}
    else
      o_nm_loghosts="${o_nm_loghosts},${loghost_name}"
    fi
  fi
done

# if we are removing the LOGHOST_CM row corresponding to lname, remove
# lname from the o_lm_loghosts list, and add it to the o_nm_loghosts list
 
if [[ "${keyword}" = "CCDSYNC_PRE_REMOVE" ]]; then
  tmp_list1=$(print ${o_lm_loghosts} | tr ',' ' ')
  tmp_list2=""
  for l in ${tmp_list1}; do
    if [[ "${l}" != "${lname}" ]]; then
      if [[ -z "${tmp_list2}" ]]; then
	tmp_list2=${l}
      else
	tmp_list2="${tmp_list2},${l}"
      fi
    else
      if [[ -z "${o_nm_loghosts}" ]]; then
	o_nm_loghosts=${l}
      else
	o_nm_loghosts="${o_nm_loghosts},${l}"
      fi
    fi
  done
  o_lm_loghosts=${tmp_list2}
fi


eval $2=${o_all_loghosts}
eval $3=${o_lm_loghosts}
eval $4=${o_nm_loghosts}
}

###########################################################################
#
# function get_ordered_ds_list i_ds_list o_ds_list
#
# i_ds_list - The input list of data services.
#
# o_ds_list - The output list of data services is the ordered list of input
#	      data services ordered according to the dependency between the
#	      serivces.
#
function get_ordered_ds_list
{
typeset i_ds_list
typeset o_ds_list tmp_ds_list
typeset rescan_list dependency_list
typeset found
typeset i j k

i_ds_list=$1
rescan_list=""

while (( 1 )); do
  for i in ${i_ds_list}; do

    dependency_list=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} \
		DS_DEPSVC_LIST query ds_name ${i})
    dependency_list=${dependency_list#*:*:}
    dependency_list=$(print ${dependency_list} | tr ',' ' ')

    # If the data service has no dependencies, put it in the output list.
    # Otherwise, evaluate each of the dependent data services for other
    # dependencies

    if [[ -z "${dependency_list}" ]]; then
      if [[ -z "${o_ds_list}" ]]; then
	o_ds_list=${i}
      else
	o_ds_list="${o_ds_list},${i}"
      fi
    else
      let found=0
      for j in ${dependency_list}; do
	tmp_ds_list=$(print ${o_ds_list} | tr ',' ' ')
	for k in ${tmp_ds_list}; do
	  if [[ "${j}" = "${k}" ]]; then
	    let found=1
	    break
	  fi
	done
	if (( found == 0 )); then
	  break
	fi
      done

      if (( found == 0 )); then
	rescan_list="${rescan_list} ${i}"
      else
	if [[ -z "${o_ds_list}" ]]; then
	  o_ds_list=${i}
	else
	  o_ds_list="${o_ds_list},${i}"
	fi
      fi
    fi
  done

  i_ds_list=${rescan_list}
  rescan_list=""
  if [[ -z "${i_ds_list}" ]]; then
    break
  fi
done

eval $2=${o_ds_list}
}

###########################################################################
#
# function exec_ds_method method_type
#
# method_type - input keyword which has one of the following values: "start",
#		"start_net", "stop", "stop_net", "abort", "abort_net",
#		"fm_init", "fm_start", "fm_stop", "fm_check".
#
# This function executes the method identified by the method_type parameter
# for all the data services in the ordered_ds_list array according to their
# dependency order.

function exec_ds_method
{
typeset method_type
typeset dsname dsrow
typeset method_path method_to
typeset n
method_type=$1
if [[ "${method_type}" = "start" || "${method_type}" = "start_net" || \
      "${method_type}" = "fm_init" || "${method_type}" = "fm_start" ]]; then
  
  let n=0
  while (( n < ${#ordered_ds_array[*]} )); do
    dsname=${ordered_ds_array[n]}
    if [[ "${method_type}" = "start" ]]; then

      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_METHOD_PATH \
		query ds_name ${dsname})
      method_path=${dsrow%:*:*:*:*:*}
      method_path=${method_path#*:*:}

      method_to=${dsrow%:*:*:*:*}
      method_to=${method_to#*:*:*:}

    elif [[ "${method_type}" = "start_net" ]]; then

      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_NM_PATH \
		query ds_name ${dsname})
      method_path=${dsrow%:*:*:*:*:*}
      method_path=${method_path#*:*:}

      method_to=${dsrow%:*:*:*:*}
      method_to=${method_to#*:*:*:}

    elif [[ "${method_type}" = "fm_init" || "${method_type}" = "fm_start" ]]; then

      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_FM_PATH \
		query ds_name ${dsname})
      
      if [[ "${method_type}" = "fm_init" ]]; then
	method_path=${dsrow%:*:*:*:*:*:*:*}
	method_path=${method_path#*:*:}

	method_to=${dsrow%:*:*:*:*:*:*}
	method_to=${method_to#*:*:*:}
	
      elif [[ "${method_type}" = "fm_start" ]]; then
	method_path=${dsrow%:*:*:*:*:*}
	method_path=${method_path#*:*:*:*:}

	method_to=${dsrow%:*:*:*:*}
	method_to=${method_to#*:*:*:*:*:}
      fi
    fi

    if [[ -n "${method_path}" ]]; then
      call_method ${method_type} ${n} ${method_path} ${method_to}
    fi

    let n=n+1
  done

elif [[ "${method_type}" = "stop" || "${method_type}" = "stop_net" || \
	"${method_type}" = "abort" || "${method_type}" = "abort_net" || \
	"${method_type}" = "fm_stop" ]]; then

  let n=${#ordered_ds_array[*]}-1
  while (( n >= 0 )); do
    dsname=${ordered_ds_array[n]}

    if [[ "${method_type}" = "stop" || "${method_type}" = "abort" ]]; then
      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_METHOD_PATH \
		query ds_name ${dsname})

      if [[ "${method_type}" = "stop" ]]; then
	method_path=${dsrow%:*:*:*}
	method_path=${method_path#*:*:*:*:}

	method_to=${dsrow%:*:*}
	method_to=${method_to#*:*:*:*:*:}
	
      elif [[ "${method_type}" = "abort" ]]; then
	method_path=${dsrow%:*}
	method_path=${method_path#*:*:*:*:*:*:}

	method_to=${dsrow#*:*:*:*:*:*:*:}
      fi

    elif [[ "${method_type}" = "stop_net" || "${method_type}" = "abort_net" ]]; then
      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_NM_PATH \
		query ds_name ${dsname})

      if [[ "${method_type}" = "stop_net" ]]; then
	method_path=${dsrow%:*:*:*}
	method_path=${method_path#*:*:*:*:}

	method_to=${dsrow%:*:*}
	method_to=${method_to#*:*:*:*:*:}

      elif [[ "${method_type}" = "abort_net" ]]; then
	method_path=${dsrow%:*}
	method_path=${method_path#*:*:*:*:*:*:}

	method_to=${dsrow#*:*:*:*:*:*:*:}
      fi

    elif [[ "${method_type}" = "fm_stop" ]]; then
      dsrow=$(${CLUSTERBIN}/scccd -f ${CCDFILE} ${CLUSTERNAME} DS_FM_PATH \
		query ds_name ${dsname})

      method_path=${dsrow%:*:*:*}
      method_path=${method_path#*:*:*:*:*:*:}

      method_to=${dsrow%:*:*}
      method_to=${method_to#*:*:*:*:*:*:*:}
    fi

    if [[ -n "${method_path}" ]]; then
      call_method ${method_type} ${n} ${method_path} ${method_to}
    fi
    let n=n-1
  done
fi
}

###########################################################################
#
# function call_method method_type ds_index method_path method_to
#
# method_type - the type of the method to be called. Same as the first
#		argument to exec_ds_method
#
# ds_index - the index of the data service in the ordered_ds_array.
#
# method_path - the full path of the method to be called.
#
# method_to - the timeout registered with the method

function call_method
{
typeset method_type
typeset ds_index
typeset method_path
typeset method_to
typeset retval method_called
method_type=$1
ds_index=$2
method_path=$3
method_to=$4

CONF_FILE=/etc/opt/SUNWcluster/conf/dflt_sched_class

DFLT_CLASS=TS
if [ -f ${CONF_FILE} ] ; then
        DFLT_CLASS=`cat ${CONF_FILE}`
fi


cpu_count=$(/usr/sbin/psrinfo 2> /dev/null | awk 'END {print NR}' )

retval=0
let method_called=0

if [[ "${ds_state_array[ds_index]}" = "1" && -f "${method_path}" ]]; then
  
  if [[ "${method_type}" = "stop" || "${method_type}" = "stop_net" || \
        "${method_type}" = "abort" || "${method_type}" = "abort_net" || \
        "${method_type}" = "fm_stop" ]]; then

	  # for stop/stopnet let us run as REAL time
	  # changing the real time priority to the minimum (100) instead of
	  # the priority of the spawning thread.
          # bug 4321549: only execute method in RT when there are more than 1 CPUs
          if (( cpu_count > 1)); then
		/usr/bin/priocntl -c RT -p 0 -e ${CLUSTERBIN}/timed_run ${method_to} ${method_path} \
		"${lm_loghosts_array[ds_index]}" "${nm_loghosts_array[ds_index]}" \
			${method_to} || retval=$?
          else
                /usr/bin/priocntl -c ${DFLT_CLASS} -e ${CLUSTERBIN}/timed_run ${method_to} ${method_path} \
                "${lm_loghosts_array[ds_index]}" "${nm_loghosts_array[ds_index]}" \
                        ${method_to} || retval=$?
          fi

  else
	 # run start/start_net as TIME SHARED.

	  /usr/bin/priocntl -c ${DFLT_CLASS} -e ${CLUSTERBIN}/timed_run \
		${method_to} ${method_path} "${lm_loghosts_array[ds_index]}" \
			 "${nm_loghosts_array[ds_index]}"  ${method_to} 
	  retval=$?
  fi
  let method_called=1

elif [[ "${ds_state_array[ds_index]}" = "0" && -f "${method_path}" ]]; then
  if [[ "${method_type}" = "stop" || "${method_type}" = "stop_net" || \
        "${method_type}" = "abort" || "${method_type}" = "abort_net" || \
        "${method_type}" = "fm_stop" ]]; then

      # bug 4321549: only execute method in RT when there are more than 1 CPUs
      if (( cpu_count > 1 )) ; then
    	/usr/bin/priocntl -c RT -p 0 -e ${CLUSTERBIN}/timed_run ${method_to} ${method_path} "" \
		"${all_loghosts_array[ds_index]}" ${method_to} || retval=$?
      else
        /usr/bin/priocntl -c ${DFLT_CLASS} -e ${CLUSTERBIN}/timed_run ${method_to} ${method_path} ""\
                "${all_loghosts_array[ds_index]}" ${method_to} || retval=$?
      fi

    let method_called=1
  fi
fi

if (( method_called == 1 )); then
  if [[ "${retval}" -eq 0 ]]; then
    log_info "${pre}.1050" "${method_type} method of data service ${ordered_ds_array[ds_index]} completed successfully."
  else
    log_info "${pre}.3010" "${method_type} method of data service ${ordered_ds_array[ds_index]} failed"
  fi
fi

}

case ${keyword} in
  
  CCDSYNC_POST_ADDU)
	CLUSTERBIN=/opt/SUNWcluster/bin
        CLUSTERVAR=/var/opt/SUNWcluster
        CLUSTERETC=/etc/opt/SUNWcluster/conf
        SSACLI=${CLUSTERBIN}/scssa

	lname=${row%:*}
	lname=${lname#*:}
	mode=${row#*:*:}
	if [ -f ${CLUSTERVAR}/loghost.${lname} ]; then
		rm -rf ${CLUSTERVAR}/loghost.${lname}
		exit 1
	fi
	#
	# if we are not in maintence mode don't do anything.
	if (( mode == 1 )); then
		exit 0
	fi
	init
	export CLUSTERNAME
	if [[ "${curr_master}" = "${localhostname}" ]]; then
	  log_info "${pre}.1040" "Take over of logical host ${lname} succeeded"
	fi
	exec_ds_method fm_start
	;;

  *)
	;;
esac

exit 0

