Transfert automatique de service après redémarrage d'un noeud

Si vous utilisez les services de bases de données d’un environnement RAC, vous avez sûrement déjà eu ce problème :
Lors de l’arrêt d’un noeud, le service est automatiquement transféré sur un des noeuds restant, mais lors du démarrage du premier noeud, le service n’est pas transféré automatiquement, le transfert est manuel, via la commande « relocate ».
Oracle a choisit de ne pas transférer automatiquement les services.

Voir doc Oracle :

When a service moves to an available instance, Oracle Database does not move the service back to the preferred instance when the preferred instance restarts because :

The service is running on the desired number of instances.

Maintaining the service on the current instance provides a higher level of service availability.

Not moving the service back to the initial preferred instance prevents a second outage.

Cependant, sur certains environnements, le transfert doit être transparent sans intervention de la part d’un administrateur.
Le but est donc d’ajouter un script qui permette de faire le relocate automatiquement lorsque le noeud redevient disponible.
La solution la plus adaptée est d’utiliser les FAN Callout (Fast Application Notification). Avant de présenter le script que l’on va utiliser, voici quelques informations à propos des FAN Callout
Depuis la version 10g, Oracle a introduit cette fonctionnalité, qui permet d’appeler un script sur un évènement au niveau du CRS (Arret Instance, Demarrage Service etc…).
Ce script doit se situer dans le répertoire CRS_HOME/racg/usrco (sur certaines versions, il faut créer le répertoire manuellement) et doit avoir les droits d’exécution. Pour chaque évènement  un ensemble de paramètres sont passés au script.
On va dans un premier temps, voir quels sont les paramètres envoyés au script.
Pour cela, on crée le fichier CRS_HOME/racg/usrco/callout_test.sh, on lui donne les droits d’exécution et on ajoute les lignes de commandes suivantes :

#!/bin/bash
> /tmp/callout.log
args=("$@")
for arg in ${args[@]}; do
 echo $arg >> /tmp/callout.log
done

On génère un évènement en arrêtant une instance du RAC, et on observe le log généré :

[oracle@atlas1 usrco]$ srvctl stop instance -d CRONOS -i CRONOS1
[oracle@atlas1 usrco]$ cat /tmp/callout.log
INSTANCE
VERSION=1.0
service=cronos
database=cronos
instance=CRONOS1
host=atlas1
status=down
reason=USER
timestamp=2012-11-28
09:16:13

On peut voir dans ce fichier de log, l’ensemble des éléments passés en paramètre du script lors de son appel. Pour chaque action, différents éléments sont passés. On peut l’observer en redémarrant l’instance :

[oracle@atlas1 usrco]$ srvctl start instance -d CRONOS -i CRONOS1
[oracle@atlas1 usrco]$ cat /tmp/callout.log
SERVICEMEMBER
VERSION=1.0
service=cronoservice
database=cronos
instance=CRONOS1
host=atlas1
status=up
reason=USER
card=1
timestamp=2012-11-28
09:18:23
SERVICE
VERSION=1.0
service=cronoservice
database=cronos
instance=
host=atlas1
status=up
reason=USER
timestamp=2012-11-28
09:18:23

On peut maintenant créer plusieurs scripts pour gérer les évènements, ou les monitorer, comme par exemple, un script qui envoie un mail à chaque évènement, etc…
Nous allons voir ici comment reloger un service, qui a été créé en mode actif / passif sur une instance (active/active) après le redémarrage d’un des noeuds.
Créons un service en mode actif / passif :

[oracle@atlas1 usrco]$ srvctl add service -d CRONOS -s cronoservice -r CRONOS1 -a CRONOS2
[oracle@atlas1 usrco]$ srvctl start service -d CRONOS -s cronoservice

On vérifie les informations :

[oracle@atlas1 usrco]$ srvctl config service -d CRONOS -s cronoservice
Service name: cronoservice
Service is enabled
Server pool: CRONOS_cronoservice
Cardinality: 1
Disconnect: false
Service role: PRIMARY
Management policy: AUTOMATIC
DTP transaction: false
AQ HA notifications: false
Failover type: NONE
Failover method: NONE
TAF failover retries: 0
TAF failover delay: 0
Connection Load Balancing Goal: LONG
Runtime Load Balancing Goal: NONE
TAF policy specification: NONE
Edition:
Preferred instances: CRONOS1
Available instances: CRONOS2

Le service aura l’instance CRONOS1 en « Prefered »  et pourra se déplacer sur CRONOS2 en cas d’arrêt de la première instance.
Vérifions son état :

[oracle@atlas1 usrco]$ crsctl stat res -t | grep cronoservice -A1
ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas1

On redémarre le premier noeud, et on vérifie l’état du service sur l’instance 2 :

[oracle@atlas2 ~]$ crsctl stat res -t | grep -A4 cronoservice
ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas2
ora.cronos.db
 1 OFFLINE OFFLINE Instance Shutdown
 2 ONLINE ONLINE atlas2 Open

Le RAC a correctement relogé le service sur l’instance encore disponible. Mais au redémarrage du noeud1, le service reste sur l’instance 1, il faut faire un relocate manuel :

[oracle@atlas2 ~]$ crsctl stat res -t | grep -A4 cronoservice
ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas2
ora.cronos.db
 1 ONLINE ONLINE atlas1 Open
 2 ONLINE ONLINE atlas2 Open
[oracle@atlas2 ~]$ srvctl relocate service -d CRONOS -s cronoservice -i CRONOS2 -t CRONOS1
[oracle@atlas2 ~]$ crsctl stat res -t | grep -A1 cronoservice
ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas1

Pour éviter toute action manuelle et reloger automatiquement le service lors du redémarrage du noeud 1, on va ajouter un script qui sera appelé par Oracle au démarrage de l’instance (ne pas oublier de faire un chmod +x sous linux, pour lui donner les droits d’exécution):

#!/bin/bash
# Desactive la langue sur le serveur pour travailler avec les status des services et des bases en anglais
unset LANG
LOGFILE=/home/oracle/log/auto_relocate_service.log
SCRIPTDIR=`dirname $0`
# Determine le grid home
if [[ "${SCRIPTDIR:(-11)}" == "/racg/usrco" ]]; then
     GRID_HOME=""${SCRIPTDIR:0:$(( ${#SCRIPTDIR} - 11 ))}""
     export GRID_HOME
fi
# Executer ce script seulement pour les evenement de type instance
if [ "$1" != "INSTANCE" ]; then
  exit 0
fi
STATUS=""
DATABASE=""
INSTANCE=""
# On parse tous les parametres pour leur affecter une variable
args=("$@")
for arg in ${args[@]}; do
  if [[ "$arg" == *=* ]]; then
    KEY=${arg%=*}
    VALUE=${arg#*=}
    case "$KEY" in
      status)
        STATUS="$VALUE"
        ;;
      database)
        DATABASE="$VALUE"
        ;;
      instance)
        INSTANCE="$VALUE"
        ;;
    esac
  fi
done
# On veririe que le status de la base est up, sinon on sort
if [[ -z "$DATABASE" || -z "$INSTANCE" || "$STATUS" != "up" ]]; then
  exit 0
fi
echo "`date`" >> "$LOGFILE"
echo "[$DATABASE][`hostname`] Instance $INSTANCE up" >> "$LOGFILE"
# On recupere l oracle home a partir de la commande crsctl
DBCONFIG=`$GRID_HOME/bin/crsctl status res ora.$DATABASE.db -f | grep "ORACLE_HOME="`
if [ -z "$DBCONFIG" ]; then
  exit 0
fi
declare -r "$DBCONFIG"
echo "ORACLE_HOME=$ORACLE_HOME" >> "$LOGFILE"
# Fonction sur les tableau
in_array() {
    local hay needle=$1
    shift
    for hay; do
        [[ $hay == $needle ]] && return 0
    done
    return 1
}
# Lecture des informations a propos des services
for service in `$GRID_HOME/bin/crsctl status res | grep -E "ora.$DATABASE.(.+).svc" | sed -rne "s/NAME=ora.$DATABASE.(.+).svc/1/gip"`; do
  SERVICECONFIG=`$ORACLE_HOME/bin/srvctl config service -d $DATABASE -s $service`
  echo "Service $service" >> "$LOGFILE"
  if [[ "$SERVICECONFIG" == *"Service is enabled"* ]]; then
    echo " enabled" >> "$LOGFILE"
    PREFERRED=( `echo "$SERVICECONFIG" | grep "Preferred instances:" | sed -rne "s/.*: ([a-zA-Z0-9]+)/1/p" | tr "," "n"` )
    # On verifie si l instance courante est l instance preferee du service
    if in_array "$INSTANCE" "${PREFERRED[@]}" ; then
      echo " preferred" >> "$LOGFILE"
      # On verifie si le service est deja demarre sur l instance courante
      SRVSTATUS=`$ORACLE_HOME/bin/srvctl status service -d $DATABASE -s $service`
      if [[ "$SRVSTATUS" == *"is not running"* ]]; then
          # Si le service n est pas up, on le demarre
        echo " service stopped, starting" >> "$LOGFILE"
        $ORACLE_HOME/bin/srvctl start service -d "$DATABASE" -s "$service" >> "$LOGFILE"
      else
        # Le service tourne, mais on verifie si il tourne sur l instance preferee
        RUNNING=( `echo "$SRVSTATUS" | sed -rne "s/.* ([a-zA-Z0-9]+)/1/p" | tr "," "n"` )
        if ! in_array "$INSTANCE" "${RUNNING[@]}" ; then
          echo " not running on preferred $INSTANCE" >> "$LOGFILE"
          # On recupere la premiere instance demarree non preferee sur laquelle le service tourne
          CURRENT=""
          for inst in "${RUNNING[@]}"; do
            if ! in_array "$inst" "${PREFERRED[@]}" ; then
              CURRENT="$inst"
              break
            fi
          done
          # On deplace le service
          if [[ -n "$CURRENT" ]]; then
            echo " relocate $CURRENT -> $INSTANCE" >> "$LOGFILE"
            $ORACLE_HOME/bin/srvctl relocate service -d "$DATABASE" -s "$service" -i "$CURRENT" -t "$INSTANCE" >> "$LOGFILE"
          fi
        else
            # Il tourne deja sur l instance preferee
          echo " running on preferred $INSTANCE" >> "$LOGFILE"
        fi
      fi
    fi
  fi
done

Après la mise en place du script, on refait notre test de redémarrage du noeud 1 :

[oracle@atlas1 ~]$ crsctl stat res -t |grep -A4 cronoservice
 ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas1
 ora.cronos.db
 1 ONLINE ONLINE atlas1 Open
 2 ONLINE ONLINE atlas2 Open
[oracle@atlas1 ~]$ exit
 logout
[root@atlas1 ~]# reboot
[oracle@atlas2 ~]$ crsctl stat res -t | grep -A4 cronoservice
 ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas2
 ora.cronos.db
 1 ONLINE OFFLINE Instance Shutdown
 2 ONLINE ONLINE atlas2 Open
--Quelques minutes plus tard
[oracle@atlas2 ~]$ crsctl stat res -t | grep -A4 cronoservice
 ora.cronos.cronoservice.svc
 1 ONLINE ONLINE atlas1
 ora.cronos.db
 1 ONLINE ONLINE atlas1 Open
 2 ONLINE ONLINE atlas2 Open

Le service est correctement relogé sur son instance « Prefered ».

source : http://ilmarkerm.blogspot.fr/2012/05/scipt-to-automatically-move-rac-11gr2.html