diff --git a/secours/secours.py b/secours/secours.py index 456fea9d..e6498df9 100644 --- a/secours/secours.py +++ b/secours/secours.py @@ -1,164 +1,283 @@ #! /usr/bin/env python -# -*- coding: iso-8859-15 -*- +# -*- encoding: utf-8 -*- +"""Reconfiguration des services pour la connexion de secours -# $Id: secours.py,v 1.5 2007-08-27 12:22:07 salles Exp $ + 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 -""" Script de reconfigure des services en connexion de secours - Arguments : - test : teste la connexion et passe en secours si besoin - normal : force la connexion normale - secours : force la connexion de secours - auto : permet la modification automatique de l'état - Sans argument configure les services comme indiqué dans etat_maitre - -Frédéric Pauget août 2005 + 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 sys, re, os, commands +import commands +import os from socket import gethostname -hostname = gethostname().split(".")[0] -path = os.path.dirname(sys.argv[0]) +import sys -### Fichiers à modifier, chaine indiquant un commentaire dans ceux-ci -### et commandes à excécuter après édition -if hostname == 'rouge' : - fichiers = { '/etc/bind/named.conf.options' : '//' , - '/etc/postfix/main.cf' : '#' } - cmds = [ '/etc/init.d/postfix restart' , '/etc/init.d/bind9 reload' ] -elif hostname == 'sila' : - fichiers = { '/etc/bind/named.conf.options' : '//' , - '/etc/squid/squid.conf' : '#' } - cmds = [ '/etc/init.d/squid reload' , '/etc/init.d/bind9 reload' ] -elif hostname == 'sable' : - fichiers = { '/etc/squid/squid.conf' : '#' } - cmds = [ '/etc/init.d/squid reload' ] -elif hostname == 'zamok' : - fichiers = { '/etc/postfix/main.cf' : '#' } - cmds = [ '/etc/init.d/postfix restart' ] -else : - print "Script sans effet sur cette machine." - sys.exit(1) +HOSTNAME = gethostname().split(".")[0] +SECOURS_PATH = "/usr/scripts/var/secours" -# Machines à pinguer pour tester la connexion -hosts = ( '91.121.84.138', '216.239.57.104', '213.228.0.42', '217.12.3.11') +ETAT_HOTE = os.path.join(SECOURS_PATH, "etat_%s" % HOSTNAME) +ETAT_MAITRE = os.path.join(SECOURS_PATH, "etat_maitre") -#################### -# Fonctions utiles # -#################### +### Fichiers à modifier, chaine indiquant un commentaire dans ceux-ci +### et commandes à excécuter après édition +FICHIERS = { + 'rouge': { + '/etc/bind/named.conf.options': '//', + '/etc/postfix/main.cf': '#', + }, + 'sila': { + '/etc/bind/named.conf.options': '//' , + '/etc/squid/squid.conf': '#', + }, + 'sable': { + '/etc/squid/squid.conf': '#', + }, + 'zamok': { + '/etc/postfix/main.cf': '#', + }, + }.get(HOSTNAME, {}) + +COMMANDES = { + 'rouge': [ + '/etc/init.d/postfix restart', + '/etc/init.d/bind9 reload', + ], + 'sila': [ + '/etc/init.d/squid reload', + '/etc/init.d/bind9 reload', + ], + 'sable': [ + '/etc/init.d/squid reload', + ], + 'zamok': [ + '/etc/init.d/postfix restart', + ], + }.get(HOSTNAME, []) + +# Adresses à pinguer pour tester la connexion +TEST_HOSTS = ( + '91.121.84.138', # ovh.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)""" -def normal_ok() : - """ Teste le connexion normale à l'aide de pings - Retourne False si il y a un problème sur la connexion normale - Retourne True si la connexion normale est fonctionelle - """ - pings = commands.getoutput('/usr/sbin/fping %s' % ' '.join(hosts)) - print pings - if pings.count('is unreachable') == len(hosts) : - return False - else : - return True - -def edit(file,comment,etat) : - """ Edite le fichier fourni en commentant (mode normal) - ou décommentant (mode secours) les lignes signalées : - * celles se terminant avec #POUR SECOURS - * ou les n lignes (<10)suivant une qui commande par #POUR SECOURS-n - (si le -n est omis une seule ligne est affectée)""" - signal = '#POUR SECOURS' - l = len(signal) - - fd = open(file) - line = fd.readline() - new = '' - reste = 0 # Nombre de lignes restant à traiter - while line : - l = line.rstrip() - mo = re.match('^(.*)'+signal+'(|-.)$',l) - if (mo and len(mo.group(1)) > 1) or reste: - # Ligne pour secours - if not re.match('^' + comment,l) and etat == 'normal': - # On est actuellement configuré en secours - # Il faut passer en normal - new += comment + line - elif re.match('^' + comment,l) and etat == 'secours' : - # On est actuellement configuré en normal - # Il faut passer en secours - new += line.replace(comment,'',1) - else : - # Rien à faire, on est bien configuré - new += line - if reste : + + 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 mo and len(mo.group(1)) == 0: - # On a une ligne avec secours uniquement, c'est les n - # prochaines lignes qui font foi - try : reste = int(mo.group(2)[1:]) - except : reste = 1 - new += line - else : + 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 - new += line + newlines.append(line) - line = fd.readline() + lines.close() - fd.close() - # Ecriture de la nouvelle version - open(file,'w').write(new) + clobber(fichier, "".join(newlines)) -if __name__ == '__main__' : - # Etat dans lequel on devrai être - etat_maitre, mode = map(str.strip,open(path+'/etat_maitre').readlines()) - nouvel_etat = etat_maitre - try: - etat_actuel = open('%s/etat_%s' % (path,hostname)).readline().strip() - except : - etat_actuel = None - - if len(sys.argv) == 2 : - if sys.argv[1] == 'secours' : - print "Connexion de secours forcée." - nouvel_etat = 'secours' - mode = 'manuel' - open(path+'/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode)) +##################### +# Boucle Principale # +##################### - elif sys.argv[1] == 'normal' : - print "Connexion normale forcée." - nouvel_etat = 'normal' - mode = 'manuel' - open(path+'/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode)) - - elif sys.argv[1] in ( 'auto', 'test' ) : - if sys.argv[1] == 'auto' : - mode = 'auto' - - if mode != 'auto' : - print 'Mode manuel, passer en mode auto pour tester' - sys.exit(1) - - # On choisi le mode de connexion - if normal_ok() : nouvel_etat = 'normal' - else : nouvel_etat = 'secours' - - if nouvel_etat != etat_maitre or sys.argv[1] == 'auto' : - open(path + '/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode)) - - print "Mode %s" % mode - - if nouvel_etat == etat_actuel : - print etat_actuel - else : - # Il faut changer - sys.stderr.write("Passage en mode %s\n" % nouvel_etat) # Ecriture sur stderr pour le cron - for f, c in fichiers.items() : +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/sbin/fping %s' % ' '.join(TEST_HOSTS)) + print pings + + # S'il y a autant de unreachable que de hosts, + # la connexion ne fonctionne pas + return pings.count('is unreachable') != len(TEST_HOSTS) + +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' + + if connexion_ok(): + etat_maitre_apres = 'normal' + else: + etat_maitre_apres = 'secours' + + return etat_maitre_apres, mode_maitre_apres + +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 + if len(sys.argv) == 2: + arg = sys.argv[1] + + # Nouvel état et nouveau mode + etat_m_apres, mode_m_apres = 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 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)) + + # Réécriture des fichiers + for fichier, commentaire in FICHIERS.items(): try: - print 'Edition de %s' % f - edit(f,c,nouvel_etat) - except: + print 'Edition de %s' % fichier + rewrite_config(fichier, commentaire, etat_h_apres) + except IOError: import traceback - traceback.print_exc() - open('%s/etat_%s' % (path,hostname),'w').write(nouvel_etat) - for c in cmds : - os.system(c) - + traceback.print_exc(file = sys.stderr) + + # Écriture de l'état local + set_etat(etat_h_apres) + + # Exécution des 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()