|
#!/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 |
Bonjour Florent,
Très bon script.
Je me suis permis de le récupérer et d'y travailler un peu pour effectuer les changements suivant:
J'ai viré les couleurs dans la fonction trace, car je l'utilisais en crontab, avec une redirection dans un fichier de log. Pas très lisible les couleurs dans ce cas ;-)
J'ai viré la variable db_name pour plutôt utiliser les alias réseaux de la primaire et de la standby qui ne sont pas les mêmes:
-primary_base et standby_base
J'ai supprimé le rsync dans la FRA de la standby, car je suis tombé sur un problème. je transfère dans un répertoire temporaire, et je fais un
catalog start dans RMAN:
$'CATALOG START WITH '\'$(ini $1 standby_dest)\'' NOPROMPT;' \
:Avec une très très grosse activité (25G d'archivelogues produit en 1h), rsync créé des fichiers temporaires .mon_fichier.xxx pendant le
transfère qui sont référencés par la standby et donc Oracle cherché a l'appliquer sur la standby, sauf que le fichier .mon_fichier.xxx a été
renommé en mon fichier après le transfert rsync. Et donc, la standby est restée bloquée sur l'archivelogues .mon_fichier.xxxx qui n'existait plus.
J'ai ajouté l'utilisation d'un flag pour éviter d'avoir le script qui tourne plusieurs fois en // si jamais la première exécution est trop longue.
dans ce que j’aimerai faire:
Au niveau des logs, je cherche un moyen d'inclure des traces dans un fichier, avec par exemple l'utilisation d'un paramètre -l pour log (écriture des traces ds un fichier) via une redirection ( | tee mon_fichier.log ou 1>mon_fichier 2>&1), mais je n'ai pas encore d'idée sans faire une usine à gaz. Si tu as des idées, je suis preneur.
Si tu veux une copie de ce que j'ai fait, pas de souci, dis-le-moi.
Cordialement,
Thierry