четверг, 24 августа 2017 г.

Oracle Clusterware active/passive failover скрипт: усовершенствования

Конфигурация Clusterware active/passive достаточно распространена, несмотря на то, что является костылем (в отличие от полного RAC).

По интернету гуляет перепечатка первоначального варианта скрипта (single_instance_action_script.sh).  Какой-то рукосуй написал как попало на своем Линуксе и теперь этот вариант выдается всеми без исключения индусами - "знатоками Оракла" - за универсальный:

 #!/bin/sh  
   
 export ORA_CRS_HOME=$_CRS_ORA_CRS_HOME  
 export ORACLE_HOME=$_CRS_ORACLE_HOME  
 export ORACLE_SID=$_CRS_ORACLE_SID  
 export SPFILE=$_CRS_SPFILE  
 export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH  
 NODE_NAME=$(${ORA_CRS_HOME}/bin/olsnodes -l)  
 VIP_IP=$(${ORA_CRS_HOME}/bin/srvctl config vip -n $NODE_NAME | grep IPv4| awk '{print $4}')  
 check_dbstatus() {  
 OUTPUT=`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v\\$session s,  
 v\\$process p,  
 v\\$parameter pa,  
 v\\$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v\\$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 EOF`  
   
 DBSTATUS=$(echo $OUTPUT | awk '{print $1}')  
 AUDITFILE=$(echo $OUTPUT | awk '{print $2}')  
 rm -f $AUDITFILE 2>/dev/null  
   
  if [ "$DBSTATUS" == "OPEN" ]  
  then  
   return 0  
  else  
   return 1  
  fi  
 }  
   
 case $1 in  
   
 'start')  
 echo "spfile='${SPFILE}'" > /tmp/init${ORACLE_SID}.ora  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 startup pfile=/tmp/init${ORACLE_SID}.ora  
 alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=${VIP_IP})(PORT=1521))))' scope=memory;  
 alter system set remote_listener='s2-scan:1521' scope=memory;  
 EOF  
 check_dbstatus  
 RET=$?  
 ;;  
   
 'stop')  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 shutdown immediate  
 EOF  
 NUM=`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`  
 if [ $NUM = 0 ]; then  
  RET=0  
 else  
  RET=1  
 fi  
 ;;  
   
 'clean')  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 shutdown abort  
 EOF  
 ##for i in `ps -ef | grep -i mon_${ORACLE_SID} | awk '{print $2}' ` ;do kill -9 $i; done  
 NUM=`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`  
 if [ $NUM = 0 ]; then  
  RET=0  
 else  
  RET=1  
 fi  
   
 ;;  
   
 'check')  
 check_dbstatus  
 RET=$?  
 ;;  
   
 '*')  
 RET=0  
 ;;  
 esac  
   
 if [ $RET -eq 0 ]; then  
  exit 0  
 else  
  exit 1  
 fi  
   

При этом скрипт является мало того, что предельно примитивным, он еще и полон башизмов (несмотря на то, что начинается с #!/bin/sh, что подразумевает Борн на большинстве *NIX систем). Ну да, вы можете написать его на перле/питоне/чем угодно сами. Но не будете, верно? Нынешние IT-специалисты не пишут скриптов, и тем не менее катят за "гуру".

Три вещи, которые представляются неправильными в данном скрипте:
  1. Он должен быть переписан под Борн. То, что работает в борне - работает везде.
  2. Скрипт должен поддерживать standby - базы. Кластерный failover - не полноценная защита.
  3. Скрипт должен поддерживать Active Data Guard. Когда standby-база открыта в read only (благодарность Vladislav Frants за наводку).
Не говорю, что предлагаемый вариант идеален. Однако он соответствует всем трем пунктам. Прошу взглянуть:

 #!/bin/sh  
 # Modified to support CW standby's by Yuri Voinov (c) 2017  
 # Version 2.0  
 #  
 # 22.08.2017 - Added ADG support  
 # 16.08.2017 - Initial version  
   
 # Variables  
 ORA_CRS_HOME=$_CRS_ORA_CRS_HOME  
 export ORA_CRS_HOME  
 ORACLE_HOME=$_CRS_ORACLE_HOME  
 export ORACLE_HOME  
 ORACLE_SID=$_CRS_ORACLE_SID  
 export ORACLE_SID  
 SPFILE=$_CRS_SPFILE  
 export SPFILE  
 LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH  
 export LD_LIBRARY_PATH  
 # Workaround for Solaris 10 branded zones on SuperCluster  
 if [ "`uname -s`" = "SunOS" -a "`uname -r`" = "5.10" ]; then  
  LD_LIBRARY_PATH_64=""  
  export LD_LIBRARY_PATH_64  
 fi  
 NODE_NAME="`${ORA_CRS_HOME}/bin/olsnodes -l`"  
 VIP_IP="`${ORA_CRS_HOME}/bin/srvctl config vip -n $NODE_NAME | grep IPv4| awk '{print $4}'`"  
   
 # Subroutines  
 check_dbstatus() {  
 OUTPUT="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v\\$session s,  
 v\\$process p,  
 v\\$parameter pa,  
 v\\$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v\\$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 EOF`"  
   
 # Check DB role  
 OUTPUT2="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v\\$database;  
 exit  
 EOF`"  
   
 DBSTATUS="`echo $OUTPUT | awk '{print $1}'`"  
 DBSTATUS2="`echo $OUTPUT2 | awk '{print $1 $2}'`"  
 AUDITFILE="`echo $OUTPUT | awk '{print $2}'`"  
 rm -f $AUDITFILE 2>/dev/null  
   
 # ADG support: PHYSICAL STANDBY can be opened as READ ONLY.  
 if [ "$DBSTATUS2" = "PHYSICALSTANDBY" -a \( "$DBSTATUS" = "MOUNTED" -o "$DBSTATUS" = "OPEN" \) ]; then  
  exit 0  
 elif [ "$DBSTATUS2" = "PRIMARY" -a "$DBSTATUS" = "OPEN" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 }  
   
 case "$1" in  
   
 "start")  
 echo "spfile=$SPFILE" > /tmp/init$ORACLE_SID.ora  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 startup mount pfile=/tmp/init${ORACLE_SID}.ora  
 alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=${VIP_IP})(PORT=1521))))' scope=memory;  
 alter system set remote_listener='s2-scan:1521' scope=memory;  
 exit  
 EOF  
 OUTPUT3="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v\\$database;  
 exit  
 EOF`"  
   
 DBSTATUS3="`echo $OUTPUT3 | awk '{print $1}'`"  
   
 if [ "$DBSTATUS3" = "PRIMARY" ]; then  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 alter database open;  
 exit  
 EOF  
 fi  
 check_dbstatus  
 ;;  
   
 "stop")  
 $ORACLE_HOME/bin/sqlplus -s /nolog <<EOF  
 connect / as sysdba  
 shutdown immediate  
 exit  
 EOF  
   
 if [ -n "`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 ;;  
   
 "clean")  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 shutdown abort  
 exit  
 EOF  
   
 if [ -n "`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 ;;  
   
 "check")  
 check_dbstatus  
 ;;  
   
 *)  
 exit 0  
   
 esac  
   

Это Борн, насколько мне вообще удалось его сделать Борн-совместимым, поддерживается Standby, поддерживается ADG. Кроме того, включен один workaround (для issue. у которой нет удовлетворительного объяснения и, тем более, документированного решения на MOS).

По данному workaround я просто покажу итоговые данные и логи:

 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl start res namedb.db  
 CRS-2672: Attempting to start 'namedb.db' on 's1-host1'  
 CRS-2674: Start of 'namedb.db' on 's1-host1' failed  
 CRS-2679: Attempting to clean 'namedb.db' on 's1-host1'  
 CRS-2681: Clean of 'namedb.db' on 's1-host1' succeeded  
 CRS-2563: Attempt to start resource 'namedb.db' on 's1-host1' has failed. Will re-retry on 's1-host12' now.  
 CRS-2672: Attempting to start 'namedb.db' on 's1-host12'  
 CRS-2674: Start of 'namedb.db' on 's1-host12' failed  
 CRS-2679: Attempting to clean 'namedb.db' on 's1-host12'  
 CRS-2681: Clean of 'namedb.db' on 's1-host12' succeeded  
 CRS-2632: There are no more servers to try to place resource 'namedb.db' on that would satisfy its placement policy  
 CRS-4000: Command Start failed, or completed with errors.  
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl status resource namedb.db  
 NAME=namedb.db  
 TYPE=single_instance_db  
 TARGET=ONLINE  
 STATE=OFFLINE  
   
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl stop res namedb.db  
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl status resource namedb.db  
 NAME=namedb.db  
 TYPE=single_instance_db  
 TARGET=OFFLINE  
 STATE=OFFLINE  
   
   
 root @ s1-host1 / # cat /tmp/script.log  
 ORA_CRS_HOME=/ora/grid.12.1.0.2  
 + export ORA_CRS_HOME   
 ORACLE_HOME=/ora/oracle/10.2.0.4.11  
 + export ORACLE_HOME   
 ORACLE_SID=namedb  
 + export ORACLE_SID   
 SPFILE=/s1_namedb_data/namedb/spfilenamedb.ora  
 + export SPFILE   
 LD_LIBRARY_PATH=/ora/oracle/10.2.0.4.11/lib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:  
 + export LD_LIBRARY_PATH   
 + /ora/grid.12.1.0.2/bin/olsnodes -l   
 NODE_NAME=s1-host1  
 + /ora/grid.12.1.0.2/bin/srvctl config vip -n s1-host1   
 + awk {print $4}   
 + grep IPv4   
 VIP_IP=192.168.180.91  
 + check_dbstatus   
 + /ora/oracle/10.2.0.4.11/bin/sqlplus -s /nolog   
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v$session s,  
 v$process p,  
 v$parameter pa,  
 v$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 ld.so.1: sqlplus: fatal: relocation error: file /ora/oracle/10.2.0.4.11/lib/libnnz10.so: symbol nzdacvalue: referenced symbol not found  
 OUTPUT=  
 + /ora/oracle/10.2.0.4.11/bin/sqlplus -s /nolog   
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v$database;  
 exit  
 ld.so.1: sqlplus: fatal: relocation error: file /ora/oracle/10.2.0.4.11/lib/libnnz10.so: symbol nzdacvalue: referenced symbol not found  
 OUTPUT2=  
 + awk {print $1}   
 + echo   
 DBSTATUS=  
 + awk {print $1 $2}   
 + echo   
 DBSTATUS2=  
 + awk {print $2}   
 + echo   
 AUDITFILE=  
 + rm -f   
 + [ = PHYSICALSTANDBY -a = MOUNTED ]   
 + [ = PRIMARY -a = OPEN ]   
 + exit 1   
   
 #!/bin/sh  
 # Put at the beginning on script. Born/bash compatible  
 set -x  
 exec 2>/tmp/script.log  
   

Обратите внимание на последние строчки. Именно это решение позволило идентифицировать, локализовать и решить проблему. Это просто редирект всего выполнения скрипта, вместе с stdout/stderr в режиме отладки в лог. В данной ситуации увидеть, почему не выполняется, по-другому просто невозможно.