[secours/secours.py] Réécriture du script de passage en connexion de secours

Ignore-this: e3ed5bde757a7629bc56cbf4e2755851

   - UTF-8
   - Fichiers dans /usr/scripts/var/secours
   - Factorisation des opérations courantes (ex: lecture/écriture de l'état)
   - Commentaires des chemins d'exécution
   - ...

darcs-hash:20090418054726-ffbb2-1104104531e42cbe0444c14ae1a3a69a28c6bb24.gz
This commit is contained in:
Nicolas Dandrimont 2009-04-18 07:47:26 +02:00
parent eac6b0be8e
commit 4fa805460a

View file

@ -1,164 +1,283 @@
#! /usr/bin/env python #! /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 $
""" Script de reconfigure des services en connexion de secours
Arguments : Arguments :
test : teste la connexion et passe en secours si besoin - test : en mode automatique, teste la connexion et passe en secours
normal : force la connexion normale si besoin -> configure l'état maître
secours : force la connexion de secours - normal : force la connexion normale
auto : permet la modification automatique de l'état - secours : force la connexion de secours
Sans argument configure les services comme indiqué dans etat_maitre - auto : libère la modification automatique de l'état -> teste la
connexion, et passe en secours si besoin
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 from socket import gethostname
hostname = gethostname().split(".")[0] import sys
path = os.path.dirname(sys.argv[0])
### Fichiers à modifier, chaine indiquant un commentaire dans ceux-ci HOSTNAME = gethostname().split(".")[0]
### et commandes à excécuter après édition SECOURS_PATH = "/usr/scripts/var/secours"
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)
# Machines à pinguer pour tester la connexion ETAT_HOTE = os.path.join(SECOURS_PATH, "etat_%s" % HOSTNAME)
hosts = ( '91.121.84.138', '216.239.57.104', '213.228.0.42', '217.12.3.11') 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 = {
'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 # # Fonctions utiles #
#################### #####################
def normal_ok() : def cron(message):
""" Teste le connexion normale à l'aide de pings """Envoie le `message' a cron.
Retourne False si il y a un problème sur la connexion normale Ceci est un raccourci pour sys.stderr.write("%s\n" % message)"""
Retourne True si la connexion normale est fonctionelle return sys.stderr.write("%s\n" % message)
"""
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) : def clobber(fichier, nouveau_contenu):
""" Edite le fichier fourni en commentant (mode normal) """Réécrit `nouveau_contenu' dans `fichier'."""
ou décommentant (mode secours) les lignes signalées : fichier = open(fichier, 'w')
* celles se terminant avec #POUR SECOURS fichier.write(nouveau_contenu)
* ou les n lignes (<10)suivant une qui commande par #POUR SECOURS-n fichier.flush()
(si le -n est omis une seule ligne est affectée)""" 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)"""
signal = '#POUR SECOURS' signal = '#POUR SECOURS'
l = len(signal)
fd = open(file) lines = open(fichier)
line = fd.readline() newlines = []
new = '' reste = 0 # Nombre de lignes restant à traiter
reste = 0 # Nombre de lignes restant à traiter for line in lines:
while line : sline = line.rstrip()
l = line.rstrip() marqueur = signal in sline
mo = re.match('^(.*)'+signal+'(|-.)$',l) avant = ""
if (mo and len(mo.group(1)) > 1) or reste: apres = ""
# Ligne pour secours if marqueur:
if not re.match('^' + comment,l) and etat == 'normal': # sline = "".join((avant, signal, apres))
# On est actuellement configuré en secours avant, apres = sline.split(signal, 2)
# Il faut passer en normal
new += comment + line # Ici, nous sommes dans les lignes a changer en cas
elif re.match('^' + comment,l) and etat == 'secours' : # de connexion de secours :
# On est actuellement configuré en normal # reste : lignes suivant une ligne #POUR SECOURS(-n)?
# Il faut passer en secours # marqueur and avant : ligne ayant des choses avant le marqueur
new += line.replace(comment,'',1) if reste or (marqueur and avant):
else : if not sline.startswith(comment) and etat == 'normal':
# Rien à faire, on est bien configuré # On est actuellement configuré en secours
new += line # Il faut passer en normal : commenter la ligne
if reste : 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 reste -= 1
elif mo and len(mo.group(1)) == 0: elif marqueur:
# On a une ligne avec secours uniquement, c'est les n # On a une ligne qui commence par le marqueur,
# prochaines lignes qui font foi # les n prochaines lignes sont à modifier.
try : reste = int(mo.group(2)[1:]) if apres:
except : reste = 1 reste = int(apres[1:]) # on enlève le tiret
new += line else:
else : reste = 1
newlines.append(line)
else:
# Ligne normale # Ligne normale
new += line newlines.append(line)
line = fd.readline() lines.close()
fd.close()
# Ecriture de la nouvelle version # Ecriture de la nouvelle version
open(file,'w').write(new) clobber(fichier, "".join(newlines))
if __name__ == '__main__' : #####################
# Etat dans lequel on devrai être # Boucle Principale #
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 : def connexion_ok():
if sys.argv[1] == 'secours' : """Vérifie si la connexion fonctionne, à l'aide de pings.
print "Connexion de secours forcée." Les hôtes testés sont ceux de `TEST_HOSTS'"""
nouvel_etat = 'secours'
mode = 'manuel'
open(path+'/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode))
elif sys.argv[1] == 'normal' : pings = commands.getoutput('/usr/sbin/fping %s' % ' '.join(TEST_HOSTS))
print "Connexion normale forcée." print pings
nouvel_etat = 'normal'
mode = 'manuel'
open(path+'/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode))
elif sys.argv[1] in ( 'auto', 'test' ) : # S'il y a autant de unreachable que de hosts,
if sys.argv[1] == 'auto' : # la connexion ne fonctionne pas
mode = 'auto' return pings.count('is unreachable') != len(TEST_HOSTS)
if mode != 'auto' : 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' print 'Mode manuel, passer en mode auto pour tester'
sys.exit(1) sys.exit(1)
# On choisi le mode de connexion if argument in ('auto', 'test'):
if normal_ok() : nouvel_etat = 'normal' # Test de la connexion nécessaire
else : nouvel_etat = 'secours' if argument == 'auto':
print "Passage en mode automatique."
mode_maitre_apres = 'auto'
if nouvel_etat != etat_maitre or sys.argv[1] == 'auto' : if connexion_ok():
open(path + '/etat_maitre','w').write('%s\n%s'% (nouvel_etat,mode)) etat_maitre_apres = 'normal'
else:
etat_maitre_apres = 'secours'
print "Mode %s" % mode return etat_maitre_apres, mode_maitre_apres
if nouvel_etat == etat_actuel : def main():
print etat_actuel """Routine principale"""
else : etat_m_avant, mode_m_avant = get_etat(maitre = True)
# Il faut changer
sys.stderr.write("Passage en mode %s\n" % nouvel_etat) # Ecriture sur stderr pour le cron # Valeurs par défaut pour le prochain état maître
for f, c in fichiers.items() : 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: try:
print 'Edition de %s' % f print 'Edition de %s' % fichier
edit(f,c,nouvel_etat) rewrite_config(fichier, commentaire, etat_h_apres)
except: except IOError:
import traceback import traceback
traceback.print_exc() traceback.print_exc(file = sys.stderr)
open('%s/etat_%s' % (path,hostname),'w').write(nouvel_etat)
for c in cmds :
os.system(c)
# É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()