myorama
1/3/2019 - 4:49 PM

Repctl - Oracle Replication tool

Outil de mise en synchro et synchro régulière basé sur le logshipping Oracle Database.

La réplication impose que les bases sources et cibles aient le mode ARCHIVELOG actifs et que la FRA soit un système de fichier standard (EXT4, ACFS, etc.)

repctl

Outil de mise en synchro et synchro régulière basé sur le logshipping Oracle Database.

La réplication impose que les bases sources et cibles aient le mode ARCHIVELOG actifs et que la FRA soit un système de fichier standard (EXT4, ACFS, etc.)

Configuration

Le fichier /etc/repctl.conf doit être alimenté comme dans l'exemple suivant :

# Sample configuration
[general]
databases    = ORCL # MANDATORY: list of actives database configurations
orahome      = /app/oracle/product/x
orabase      = /app/oracle
fra_dest     = /fra
primary_node = runbdd001
standby_node = runbdd002
primary_cnx  = {{ primary_node }}:1521/{{ db_name }}
standby_cnx  = {{ standby_node }}:1521/{{ db_name }}

[ORCL] # configuration name: name of databases listed above
db_name          = ORCL # MANDATORY: database name
primary_node     = runbdd001
primary_orabase  = /app/oracle

standby_node     = runbdd002
standby_orabase  = /app/oracle

password        = Unsecured # MANDATORY: sysdba account

Exécution

  • Aide
Usage : repctl
Provide helpers for Oracle logshipping configuration and management

Commands :
  clone start stop restart open activate recover status list

Options
   -d    : database configuration (from /usr/local/bin/repctl.conf.sample)
   -v    : verbose mode (optional)
   -i    : inline nagios mode (optional)
   -w    : warning threshold (optional)
   -c    : critical threshold (optional)
  • Synchroniser la base standby
repctl recover -d TEST -v
  • Connaître l'état de la synchronisation
repctl status -d TEST
# ----    Get sequence lag for: TEST
# [OK]    Primary sequence is: 36
# [OK]    Standby sequence is: 36
# [OK]    Lag: 0
  • Synchroniser les répertoires

Dans le cas d'une configuration cluster, partager les clés ssh

for node in server-from server-to ; do ssh $node hostname --fqdn ; done
repctl rsync -d TEST
#!/bin/bash

## Variables
VERSION=20181025.1620
CURDIR=$(dirname $0)
SCRIPT=$(basename $0 .sh)
WARN=10
CRIT=30
ARCH_RETENTION=1

## Functions
upper() { echo "$1" | tr '[:lower:]' '[:upper:]' ; }
lower() { echo "$1" | tr '[:upper:]' '[:lower:]' ; }
  
help() {
  cat <<EOF
Usage : $SCRIPT
Provide helpers for Oracle logshipping configuration and management

Commands :
  $actions

Options
   -f    : configuration file (default $configfile)
   -d    : database configuration
   -o    : archive retention in days (default: 1)
   -v    : verbose mode (optional)
   -i    : inline nagios mode (optional)
   -w    : warning threshold (optional)
   -c    : critical threshold (optional)
EOF
  exit 0
}

# arg1: section name
# arg2: variable name
# arg3: general variable name 
ini() {
  var=$(sed -n -e "/^\[$1\]/I,/^\[/ p" $configfile | grep -i ^$2[^a-zA-Z] | cut -d= -f2 | cut -d# -f1)
  test -z "$var" && test -n "$3" && var=$(ini general $3) 
  test -z "$var" && test "$1" != "general" && var=$(ini general $2)
  test "$1" == "general" && echo $var | xargs && exit
  
  # recursive template variables like {{ db_name }}
  IFS=$'\n'
  for m in $(echo $var | grep -Poi '{{(\s?[a-z_]*\s?)}}') ; do
    s=$(echo $m | grep -Poi '[a-z_]*')
    var=$(echo $var | sed "s/$m/$(ini $1 $s)/g")
  done
  echo $var | xargs
}

# arg1: message
# arg2: return value (null, 0:ok 1:warn 2:crit 3:unknown)
# arg3: exit or null
trace() {
  case "$2" in
    0) printf "\033[1;32m[OK]\033[0m\t%s\n" "$1" ;;
    1) printf "\033[1;33m[WARN]\t%s\033[0m\n" "$1" ;;
    2) printf "\033[1;31m[KO]\t%s\033[0m\n" "$1" ;;
    3) printf "\033[1;31m[UNKN]\t%s\033[0m\n" "$1" ;;
    *) printf "\033[1;34m----\t%s\033[0m\n" "$1" ;;
  esac

  if [[ -n "$3" ]] && [[ "$3" = "exit" ]] ; then
    exit 1
  fi
}

# arg1: connexion string
# arg2: password
# arg3: statement
sql() {
  cnx="$1" ; pwd="$2" ; shift 2
  result=$($SQLPLUS -L -S /nolog <<OEF
    SET HEAD OFF;
    CONNECT sys/$pwd@//$cnx as sysdba;
    $(echo "$@" | sed 's/;/;\n/g' | sed 's/--BLANK/\n/g')
OEF
)
  if [[ ! $result =~ 'TNS-' ]] && \
     [[ ! $result =~ 'ORA-' ]] && \
     [[ ! $result =~ 'SP2-' ]] ;
  then
    echo "$result"
  else
    (>&2 echo "---- SQL: $@")
    (>&2 echo "$result")
    return 1
  fi
}

# arg1: target connection string
# arg2: auxiliary connection string or 0
# arg2: password
# arg3: statement
rman() {
  target="$1" ; auxiliary="$2" ; pwd="$3" ; shift 3
  result=$($RMAN <<OEF
    CONNECT TARGET sys/$pwd@//$target;
    $(test ! "$auxiliary"  = "0" && echo CONNECT AUXILIARY sys/$pwd@//$auxiliary;)
    $(echo "$@" | sed 's/;/;\n/g')
OEF
)
  if [[ ! $result =~ 'TNS-' ]] && \
     [[ ! $result =~ 'ORA-' ]] && \
     [[ ! $result =~ 'SP2-' ]] ;
  then
    echo "$result"
  else
    (>&2 echo "---- SQL: $@")
    (>&2 echo "$result")
    return 1
  fi
}

# arg1: database config name
lag() {
  trace "Get sequence lag for: $1"
  prim_seq=$(sql $(ini $1 primary_cnx) $(ini $1 password) \
    $'select max(sequence#) from v$archived_log;')
  trace "Primary sequence is: $(echo $prim_seq)" "$(test -n "$prim_seq" && echo 0 || echo 1)"

  stdb_seq=$(sql $(ini $1 standby_cnx) $(ini $1 password) \
    $'select max(sequence#) from v$archived_log where applied=\'YES\';')
  trace "Standby sequence is: $(echo $stdb_seq)" "$(test -n "$stdb_seq" && echo 0 || echo 1)"

  if [[ $prim_seq -ge $stdb_seq ]] ; then
    diff_seq=$(expr ${prim_seq:-0} - ${stdb_seq:-0})
    trace "Lag: $diff_seq" "$(test $diff_seq -ge $CRIT && echo 2 || { test $diff_seq -ge $WARN && echo 1 || echo 0; })"
  else
    trace "Standby database has been activated" 1 "exit"
  fi
}

# arg1: database config name
inline_lag() {
  prim_seq=$(sql $(ini $1 primary_cnx) $(ini $1 password) \
    $'select min(seq) from (select max(sequence#) seq from gv$archived_log group by inst_id) a;')
  stdb_seq=$(sql $(ini $1 standby_cnx) $(ini $1 password) \
    $'select max(sequence#) from v$archived_log where applied=\'YES\';')

  if [[ $prim_seq -ge $stdb_seq ]] ; then
    diff_seq=$(expr ${prim_seq:-0} - ${stdb_seq:-0})
    echo "$1: $diff_seq"
  else
    echo "Standby database has been activated"
  fi
}

# arg1: database config name
load() {
  ORACLE_HOME=$(ini ${1:-general} orahome)
  ORACLE_BASE=$(ini ${1:-general} orabase)

  test -d $ORACLE_HOME && export ORACLE_HOME
  test -d $ORACLE_BASE && export ORACLE_BASE

  export RMAN=${ORACLE_HOME}/bin/rman
  export SQLPLUS=${ORACLE_HOME}/bin/sqlplus
  export NLS_DATE_FORMAT="YYYY/MM/DD HH24:MI:SS"  
}

# arg1: database config name
configured?() {
  if [[ ! "$(ini general databases)" =~ "$1" ]] || [[ -z "$1" ]] ; then
    echo "Database '$1' is not configured"
    exit 1
  fi
  load "$1"
}

# arg1: database config name
ssh?() {  
  for node in $(ini $1 primary_node) $(ini $1 standby_node); do
    ssh -q $node exit
    if [[ $? -gt 0 ]] ; then
      echo "SSH connectivity failed on $node with $(whoami) user"
      exit 1
    fi
  done
}

do_list() {
  echo "Available databases configuration :"
  for cfg in $(ini general databases) ; do
    cat <<EOF

  $cfg:
    - primary = $(ini $cfg primary_cnx)
    - standby = $(ini $cfg standby_cnx)
EOF
  done
}

# arg1: database config name (optional)
do_status() {
  if [[ -z "$1" ]] ; then
    for cfg in $(ini general databases) ; do
      load "$cfg"
      ${INLINE}lag $cfg
    done
  else
    configured? "$1"
    ${INLINE}lag "$1"
  fi
}

# arg1: database config name (optional)
do_recover() {
  if [[ -z "$1" ]] ; then
    for cfg in $(ini general databases) ; do
      recover "$cfg"
    done
  else
    recover "$1"
  fi
}

# arg1: database config name
recover() {
  configured? "$1"
  rman $(ini $1 standby_cnx) 0 $(ini $1 password) \
    $'CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON STANDBY;' \
    $'RECOVER DATABASE;' \
    $'CROSSCHECK ARCHIVELOG ALL;' \
    $'DELETE NOPROMPT ARCHIVELOG ALL COMPLETED BEFORE \'SYSDATE-'${ARCH_RETENTION}$'\';'
}

# arg1: database config name (optional)
do_rsync() {
  if [[ -z "$1" ]] ; then
    for cfg in $(ini general databases) ; do
      rsync_arch "$cfg"
    done
  else
    rsync_arch "$1"
  fi
}

# arg1: database config name
rsync_arch() {
  from=$(ini "$1" primary_nodes)
  if [[ -n "$from" ]] ; then
    # first available
    for node in $from ; do
      from=$(ssh $node hostname --fqdn 2>&1)
      test $? -eq 0 && break
    done
  else
    # no cluster configuration
    from=$(ini "$1" primary_node)
  fi

  remote_fra="$(ini "$1" primary_fra_dest fra_dest)/$1/archivelog"
  local_fra="$(ini "$1" standby_fra_dest fra_dest)/$1/archivelog"

  result=$(rsync -az --stats "$from:$remote_fra/" "$local_fra")
  n=$(echo "$result" | grep "files transferred" | cut -d: -f2)
  trace "Transfer $(echo $n) file(s) from $from into $local_fra" "$(echo $?)"
}

# arg1: database config name
do_open() {
  configured? "$1"
  sql $(ini $1 standby_cnx) $(ini $1 password) \
    $'RECOVER AUTOMATIC STANDBY DATABASE;--BLANK' \
    $'ALTER DATABASE OPEN READ ONLY;'
}

# arg1: database config name
do_activate() {
  configured? "$1"
  sql $(ini $1 standby_cnx) $(ini $1 password) \
    $'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE FINISH;' \
    $'ALTER DATABASE ACTIVATE STANDBY DATABASE;' \
    $'ALTER DATABASE OPEN;'

  rman $(ini $1 standby_cnx) 0 $(ini $1 password) \
    $'CONFIGURE ARCHIVELOG DELETION POLICY CLEAR;'
}

configfile=$CURDIR/repctl.conf.sample
test -f $CURDIR/repctl.conf && configfile=$CURDIR/repctl.conf

actions="activate list open recover rsync status"
if [[ ! $actions =~ "$1" ]] || [[ -z "$1" ]] ; then
  help
fi
ACTION="$1"
shift

while getopts ":d:w:c:f:iv" opt; do
  case $opt in
    d) DBCONF=$OPTARG ;;
    c) CRIT=$OPTARG ;;
    i) INLINE=inline_ ;;
    f) configfile=$OPTARG ;;
    v) VERBOSE=0 ;;
    w) WARN=$OPTARG ;;
    :) echo "Option -$OPTARG requires an argument." ; exit 1;;
    *) echo "Invalid option -$OPTARG" ; exit 1;;
  esac
done

test ! $VERBOSE && exec 2> /dev/null
supported="11.2 12.2"

# call action logic
do_$ACTION $DBCONF