Voila j’ai fait un petit script python pour me simplifier la vie pour la gestion des modules rsync.
Supposant que vous ayez défini les modules rsync à sauvegarder à partir d’un ou plusieurs serveurs, le script suivant vous permettra d’automatiser leur sauvegarde incémentale.
A présent ce script permet de définir le nombre de jours maximum d’archive, le délais minimum entre deux backups, l’adresse et le module distant à sauvegarder, le répertoire de sauvegarde sur la machine dédiée, et si oui ou non la sauvegarde est autorisée.
J’ai encore pas mal de choses en tête pour améliorer le script que ce soit au niveau layout ou options à ajouter …
Voici un exemple de fichier de configuration “config”:
[diamond.stones.lan]
comment = Sauvegarde de Produit
enable = yes
path = /mnt/backup
address = diamond.stones.lan
module = produit
days = 30
hours = 24
[emerald.stones.lan]
comment = Sauvegarde des base de données
enable = yes;
path = /mnt/backup
address = emerald.stones.lan
module = mysql
days = 5
hours = 6
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# =============================================================================
#
# File .....: bckmg.py - Gestionnaires de backup
# Date .....: 23/05/2008
# Author....: Franck Joncourt <franck
# !rtsp Dot Joncourt
# ..*q.. AT Wanadoo Dot Fr>
#
# Le script est largement inspiré de celui de Leland Elie (roller.py) et du
# travail fait sur http://www.mikerubel.org/computers/rsync_snapshots/
#
# -------------------------------
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to :
#
# the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301 USA
#
# -------------------------------
#
# =============================================================================
# Importation des librairies
import os
import time
import sys
from optparse import OptionParser
# Déclaration des constantes
NAME = "lbm4r"
DESC = "Mise à jour de la sauvegarde des modules via rsync."
VERSION = "0.4"
PIDFILE = "/var/run/" + NAME + ".pid"
# Déclaration des variables globales
Servers = []
OptionsList = ["name",
"comment",
"enable",
"path",
"address",
"module",
"days",
"hours"]
#*******************************************************************************
# Parser le fichier de configuration
#
# Cette fonction permet de lire le fichier de configuration afin
# d'obtenir les informations utile au backup
def ParseConfigFile():
print "Chargement de la configuration à partir du fichier /etc/lbm4r.conf\n"
CurrentServer = {}
# Ouvrir le fichier
try:
fh = open("/etc/lbm4r.conf", 'r')
data = fh.readlines()
fh.close();
except :
print ("ERROR: Unable to access config file")
sys.exit()
# Parcourir le fichier de configuration
for line in data:
line = line.strip()
# Obtenir le nom du serveur et sauvegarder la configuration précédente
if line.find('[') == 0:
# Sauvegarder la configuration précédente si elle existe
if len(CurrentServer) > 0:
Servers.append(CurrentServer)
CurrentServer = {}
# Obtenir le nom de la nouvelle configuration
if len(line) > 2:
CurrentServer['name'] = line.replace('[', '').replace(']', '')
# Obtenir les informations du serveur, pas les commentaires
elif line.find('#') != 0:
if len(line) > 2:
key = line.split('=')
if len(key) == 2:
CurrentServer[key[0].strip().lower()] = key[1].strip().lower()
# Sauvegarder la dernière configuration obtenue
if len(CurrentServer) != 0:
Servers.append(CurrentServer)
#*******************************************************************************
# Vérifier la configuration obtenue
#
# Cette fonction permet de vérifier la configuration obtenue en parsant le
# fichier de configuration de l'application
#
# \return 0 si la configuration serveur est valide
# -1 si une option est manquante
# -2 si la configuration n'est pas autorisée
def CheckConfig(server):
# Vérifier que toutes les options sont présentes
for option in OptionsList:
if server.has_key(option) == 0:
return -1
# Vérifier que le backup est autorisé
if server['enable'].find('yes') == 0:
print "* Configuration pour %s validée" % server['name']
return 0
else:
return -2
#*******************************************************************************
# Effectuer le traitement des anciens backups
#
# Cette fonction se déplace dans le répertoire de backup et le crée si
# nécessaire. Esnuite, il vérifie que le backup peut-être effectué en fonction
# du délais autorisé entre les backups. De la même manière, le plus ancien
# sera supprimé si nécessaire. Pour finir, une copie du plus récent backup
# sera faîte afin de recevoir la nouvelle sauvegarde incrémentale.
#
# - server.: nom du serveur
# - path ..: le chemin de sauvegarde pour le serveur
# - module : nom du module à sauvegarder
# - days...: nombre de jours maximum de sauvegarde
# - hours..: nombre d'heures minimum entre deux backups
#
# \return 0 si l'opération a été effectuée avec succès
# -1 si une erreur s'est produite
def ProcessLastBackups(server, path, module, days, hours):
# Se placer dans le répertoire racine associé au serveur
try:
os.chdir(path)
except OSError:
print " [ERROR] Impossible d'accéder au répertoire racine %s " % path,
print "associé au server %s." % server
return -1
# Se placer dans le répertoire racine du module
try:
os.chdir(module)
except OSError:
try:
os.mkdir(module)
except OSError:
print " [ERROR] Impossible de créer le répertoire racine",
print "%s/%s associé au module %s" % (path, module, module),
return -1
try:
os.chdir(module)
except OSError:
print " [ERROR] Impossible d'accéder au répertoire racine",
print "%s/%s associé au module %s" % (path, module, module),
return -1
# Obtenir la liste des précédentes sauvegardes
backupList = os.listdir('./')
for directory in backupList:
if os.path.isdir(directory) == False:
backupList.remove(directory)
backupList.sort(reverse=True)
# En déduire la plus ancienne et la plus récente sauvegarde
# et effectuer le traitement nécessaire
if len(backupList) > 0:
lastBackup = int(time.mktime(time.strptime("%s" % backupList[0],
"%Y%m%d%H")))
thisBackup = int(time.time())
# Vérifier que le backup respecte le délais minimum
calcul = (thisBackup - lastBackup) / 3600.0
if calcul <= 0:
print " [ERROR] Sauvegarde dans le futur détectée"
return -1
elif int(calcul) < int(hours):
print " [ERROR] Dernière sauvegarde trop récente"
return -1
# Supprimer les plus anciennes archives
for directory in backupList:
lastBackup = int(time.mktime(time.strptime("%s" % directory,
"%Y%m%d%H")))
calcul = (thisBackup - lastBackup) / (3600.0 * 24.0)
if calcul >= int(days):
print " Suppression de l'archive %s" % directory
pipe = os.popen("rm -Rf %s" % directory)
pipe.close()
sys.stdout.flush()
# Faire une copie du plus récent backup pour recevoir la nouvelle
# sauvegarde incrémentale si il existe après nettoyage
if os.path.isdir(backupList[0]) == True:
directory = time.strftime('%Y%m%d%H')
cde = "cp -al %s %s" % (backupList[0], directory)
pipe = os.popen(cde)
pipe.close()
sys.stdout.flush()
return 1
return 0
#*******************************************************************************
# Effectuer le backup d'un module
#
# Cette fonction permet de faire le backup d'un module pour un serveur donné
# via rsync. Pour ce faire il est nécessaire de fournir les différents
# arguments :
#
# - serverName : nom ou adresse du serveur auquel se connecter
# - module ....: nom du module à sauvegarder
# - path ......: chemin où sauvegarder le module sur le serveur local
#
# \return 0 si le backup c'est bien déroulé
# <0 si une erreur est survenue
def DoBackup(serverName, module, path, days, hours):
print "|-> " + time.strftime("%a, %d %b %Y %I:%M:%S %p"),
print " : Sauvegarde du module %s ..." % module
# Gérer les anciens backups
directory = time.strftime('%Y%m%d%H')
retval = ProcessLastBackups(serverName, path, module, days, hours)
if retval < 0:
return retval
elif retval == 0:
os.mkdir(directory)
# Faire la sauvegarde incrémentale
print " Création de l'archive %s" % directory
cde = "rsync -vrlptgoW --numeric-ids --delete "
cde = cde + "%s::%s " % (serverName, module)
cde = cde + "%s/%s/%s" % (path, module, directory)
sys.stdout.flush()
pipe = os.popen(cde)
data = pipe.readlines()
pipe.close()
sys.stdout.flush()
print " Done"
#*******************************************************************************
# Gérer le verrou de l'application
#
# Cette fonction permet de positionner/enlever le verrou de l'application afin
# d'éviter que plusieurs instances s'éxécutent en même temps.
#
# - mode ..: 1 pour créer le verrou
# 0 pour l'enlever
# - verrou : nom du verrou
#
# \return 0 si l'opération a été effectuée avec succès
# -1 si une erreur s'est produite
def SetVerrou(mode, verrou):
# Créer le verrou
if mode == 1:
# Afficher une erreur si un verrou existe
try:
fh = open(verrou, 'r')
fh.close()
print "[ERROR] Le verrou %s a été trouvé signifiant qu'une autre" % verrou,
print "instance est en cours ou que la précédente éxécution s'est mal",
print "terminée."
return -1
# Autrement créer le verrou avec la date courante
except IOError:
fh = open(verrou, 'w')
fh.write(time.strftime("%a, %d %b %Y %I:%M:%S %p\n"))
fh.close()
return 0
# Supprimer le verrou
else:
# Supprimer le verrou si il existe
try:
fh = open(verrou, 'r')
fh.close()
pipe = os.popen("rm -f %s" % verrou)
pipe.close()
return 0
# Afficher une erreur si le verrou n'existe pas
except IOError:
print "[ERROR] Impossible de supprimer le verrou %s !"
return -1
#*******************************************************************************
# Quitter l'application
#
# Cette fonction permet de quitter l'application
#
# - errorCode : valeur du code d'erreur à retourner
def Quit(errorCode):
date = "le " + time.strftime("%a, %d %b %Y %I:%M:%S %p") + " <--"
if errorCode < 0:
how = "--> Exécution annulée "
else:
how = "--> Fin d'exécution "
print how + date
sys.exit(errorCode)
# Afficher la bannière
#
# Cette fonction a pour seule utilité d'afficher la bannière de l'application
def ShowBanner():
print "%s version %s" % (NAME, VERSION)
print "%s\n" % DESC
#*******************************************************************************
# Décomposer les arguments de la ligne de commandes
#
# Cette fonction permet de décomposer la ligne de commandes afin d'obtenir les
# différents arguments passés au script. La liste des options est disponible
# grâce à l'option --help ou -h, et la version courante avec --version
def ParseCmdLine():
# Vérifier le paramètre verbose
def checkVerbose(option, opt, value, parser):
if (value < 0) or (value > 2):
print "[ERROR] L'option verbose ne peut être que 0, 1 ou 2."
else:
setattr(parser.values, option.dest, value)
# Parser la ligne de commande
parser = OptionParser(version="%prog " + VERSION)
parser.set_defaults(verbose=1)
parser.add_option("-d", "--debug",
action="callback", callback=checkVerbose,
type="int", dest="verbose",
help="Set the debug level")
(Options, args) = parser.parse_args()
#*******************************************************************************
# Boucle principale du programme
#
def main():
# Parser les arguments de la ligne de commandes
ParseCmdLine()
# Afficher la bannière
ShowBanner()
# Parser le fichier de configuration
ParseConfigFile()
# Mettre en place le verrou
retval = SetVerrou(1, PIDFILE)
if retval < 0:
Quit(retval)
# Parcourir toutes les configurations serveur
for server in Servers:
# Vérifier la configuration pour la backup
retval = CheckConfig(server)
if retval < 0:
continue
# Effectuer le backup pour chaque module du serveur
modules = server['module'].split(' ')
for mod in modules:
DoBackup(server['address'], mod, server['path'], server['days'],
server['hours'])
print ""
# Supprimer le verrou
retval = SetVerrou(0, PIDFILE)
# Terminer l'application
Quit(retval)
# Point d'entrée de l'application
if __name__ == "__main__":
main()