#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""Reconfiguration des services pour la connexion de secours

 Arguments :
  - test : en mode automatique, teste la connexion et passe en secours
  si besoin -> configure l'état maître
  - normal : force la connexion normale
  - secours : force la connexion de secours
  - auto : libère la modification automatique de l'état -> teste la
  connexion, et passe en secours si besoin

 Sans argument, configure les services selon l'état maître

 Auteurs :
  - Frédéric Pauget, Août 2005 (Implémentation initiale)
  - Nicolas Dandrimont, Avril 2009 (Réécriture pour nettoyages)
"""

import commands
import os
from socket import gethostname
import sys

HOSTNAME = gethostname().split(".")[0]
SECOURS_PATH = "/usr/scripts/var/secours"

ETAT_HOTE = os.path.join(SECOURS_PATH, "etat_%s" % HOSTNAME)
ETAT_MAITRE = os.path.join(SECOURS_PATH, "etat_maitre")

### Fichiers à modifier, chaine indiquant un commentaire dans ceux-ci
### et commandes à excécuter après édition
FICHIERS = {
    'redisdead': {
        '/etc/postfix/main.cf': '#',
        },
    'sable': {
        '/etc/bind/named.conf.options': '//' ,
        '/etc/squid3/squid.conf': '#',
        },
    'charybde': {
        '/etc/bind/named.conf.options': '//' ,
        },
    'nem': {
        '/etc/bind/named.conf.options': '//' ,
        },
    'zamok': {
        '/etc/postfix/main.cf': '#',
        },
    'odlyd': {
        '/proc/net/nf_condition/secours': '0',
        }
    }.get(HOSTNAME, {})

COMMANDES = {
    'redisdead': [
        '/etc/init.d/postfix restart',
        ],
    'sable': [
        '/etc/init.d/squid3 reload',
        '/etc/init.d/bind9 reload',
        ],
    'charybde': [
        '/etc/init.d/bind9 reload',
        ],
    'nem': [
        '/etc/init.d/bind9 reload',
        ],
    'zamok': [
        '/etc/init.d/postfix restart',
        ],
    'odlyd': [
	   '/etc/init.d/aiccu restart',
	],
    }.get(HOSTNAME, [])

# Adresses à pinguer pour tester la connexion
TEST_HOSTS = (
    '91.121.179.40', # soyouz.crans.org
    'free.fr',
    'google.com',
    'yahoo.com',
    )

#####################
# Fonctions  utiles #
#####################

def cron(message):
    """Envoie le `message' a cron.
    Ceci est un raccourci pour sys.stderr.write("%s\n" % message)"""
    return sys.stderr.write("%s\n" % message)

def clobber(fichier, nouveau_contenu):
    """Réécrit `nouveau_contenu' dans `fichier'."""
    fichier = open(fichier, 'w')
    fichier.write(nouveau_contenu)
    fichier.flush()
    fichier.close()

def set_etat(etat, mode = None):
    """Écrit l'état de la connexion de secours sur l'hôte.
    Si `mode' n'est pas None, alors c'est l'état maître qui est écrit."""

    if not mode:
        clobber(ETAT_HOTE, "%s\n" % etat)
    else:
        clobber(ETAT_MAITRE, "%s\n%s\n" % (etat, mode))

def get_etat(maitre = False):
    """Récupère l'état de la connexion de secours.
    Renvoie une paire `etat', `mode'.  Si `maitre' est True, l'état
    maître et le mode sont récupérés. Sinon, `mode' est None"""

    etat = None
    mode = None
    if not maitre:
        try:
            fichier = open(ETAT_HOTE)
        except IOError:
            pass
        else:
            etat = fichier.read().strip()
            fichier.close()
    else:
        try:
            fichier = open(ETAT_MAITRE)
        except IOError:
            pass
        else:
            lines = fichier.readlines()
            fichier.close()
            etat = lines[0].strip()
            mode = lines[1].strip()

    return etat, mode

def rewrite_config(fichier, comment, etat):
    """Modifie le fichier selon le nouvel état fourni.
    Si `etat' est "normal" ("secours"), commente (décommente) les
    lignes terminant par "#POUR SECOURS", ou les n lignes suivant une
    ligne commençant par "#POUR SECOURS-n" (si le -n est omis, une
    seule ligne est affectée)"""
    if HOSTNAME=='odlyd':
       if etat=='secours':
         clobber(fichier,'1')
       elif etat=='normal':
         clobber(fichier,'0')
       return      
    signal = '#POUR SECOURS'

    lines = open(fichier)
    newlines = []
    reste = 0                     # Nombre de lignes restant à traiter
    for line in lines:
        sline = line.rstrip()
        marqueur = signal in sline
        avant = ""
        apres = ""
        if marqueur:
            # sline = "".join((avant, signal, apres))
            avant, apres = sline.split(signal, 2)

        # Ici, nous sommes dans les lignes a changer en cas
        # de connexion de secours :
        # reste : lignes suivant une ligne #POUR SECOURS(-n)?
        # marqueur and avant : ligne ayant des choses avant le marqueur
        if reste or (marqueur and avant):
            if not sline.startswith(comment) and etat == 'normal':
                # On est actuellement configuré en secours
                # Il faut passer en normal : commenter la ligne
                newlines.append("%s%s" % (comment, line))
            elif sline.startswith(comment) and etat == 'secours':
                # On est actuellement configuré en normal
                # Il faut passer en secours : décommenter la ligne
                newlines.append(line.replace(comment, '', 1))
            else:
                # Rien à faire, on est bien configuré
                newlines.append(line)
            if reste:
                reste -= 1
        elif marqueur:
            # On a une ligne qui commence par le marqueur,
            # les n prochaines lignes sont à modifier.
            if apres:
                reste = int(apres[1:]) # on enlève le tiret
            else:
                reste = 1
            newlines.append(line)
        else:
            # Ligne normale
            newlines.append(line)

    lines.close()

    # Ecriture de la nouvelle version
    clobber(fichier, "".join(newlines))

#####################
# Boucle Principale #
#####################

def connexion_ok():
    """Vérifie si la connexion fonctionne, à l'aide de pings.
    Les hôtes testés sont ceux de `TEST_HOSTS'"""

    pings = commands.getoutput('/usr/bin/fping %s' % ' '.join(TEST_HOSTS))
    print pings

    # S'il y a autant de unreachable que de hosts,
    # la connexion ne fonctionne pas
    ok = (pings.count('is unreachable') + pings.count('address not found')) != len(TEST_HOSTS)
    return ok, pings

def new_etat_maitre(argument, mode_maitre_avant):
    """Renvoie l'état et le mode maitre selon l'argument passé au
    script, le mode maître actuel, et le test de ping."""

    mode_maitre_apres = mode_maitre_avant

    if argument == 'secours':
        print "Connexion de secours forcée."
        etat_maitre_apres, mode_maitre_apres = 'secours', 'manuel'

    elif argument == 'normal':
        print "Connexion normale forcée."
        etat_maitre_apres, mode_maitre_apres = 'normal', 'manuel'

    elif argument == 'test':
        if mode_maitre_avant != 'auto':
            print 'Mode manuel, passer en mode auto pour tester'
            sys.exit(1)

    if argument in ('auto', 'test'):
        # Test de la connexion nécessaire
        if argument == 'auto':
            print "Passage en mode automatique."
            mode_maitre_apres = 'auto'

        ok, pings = connexion_ok()
        if ok:
            etat_maitre_apres = 'normal'
        else:
            etat_maitre_apres = 'secours'

    return etat_maitre_apres, mode_maitre_apres, pings

def main():
    """Routine principale"""
    etat_m_avant, mode_m_avant = get_etat(maitre = True)

    # Valeurs par défaut pour le prochain état maître
    etat_m_apres, mode_m_apres = etat_m_avant, mode_m_avant

    #####
    # Un argument a été passé, le script pourra changer le mode et l'état maitre
    pings = "" # Pour que la variable existe même si la fonction new_etat_maitre n'est pas appelée
    if len(sys.argv) == 2:
        arg = sys.argv[1]

        # Nouvel état et nouveau mode
        etat_m_apres, mode_m_apres, pings = new_etat_maitre(arg, mode_m_avant)

        # On a récupéré toutes les informations, on peut changer le mode maître.
        if etat_m_avant != etat_m_apres or mode_m_avant != mode_m_apres:
            set_etat(etat_m_apres, mode_m_apres)

    #####
    # Changement de l'état local
    etat_h_avant, _ = get_etat()
    etat_h_apres = etat_m_apres

    print "Mode %s" % mode_m_apres


    if HOSTNAME == 'odlyd':
      fichier, commentaire = FICHIERS.items()[0]
      try:
        rewrite_config(fichier, commentaire, etat_h_apres)
      except IOError:
        import traceback
        traceback.print_exc(file = sys.stderr)

    if etat_h_apres == etat_h_avant:
        print "L'hôte est déjà en état `%s'" % etat_h_avant
    else:
        cron("Passage de `%s' en etat `%s'" % (HOSTNAME, etat_h_apres))
        cron(pings)
        
        # Réécriture des fichiers
        for fichier, commentaire in FICHIERS.items():
            try:
                print 'Edition de %s' % fichier
                rewrite_config(fichier, commentaire, etat_h_apres)
            except IOError:
                import traceback
                traceback.print_exc(file = sys.stderr)

        # Écriture de l'état local
        set_etat(etat_h_apres)

        # Exécution des commandes
        print "Execution des commandes %s" % ('; '.join(COMMANDES))
        for commande in COMMANDES:
            os.system(commande)

if __name__ == "__main__":
    if not (FICHIERS or COMMANDES):
        print "Script sans effet sur cette machine."
        sys.exit(1)
    else:
        main()
