#!/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()