Wiki Linux SMH

   

Flux RSS

Sauvegardes

  script de sauvegarde avec ses explications et sauvegarde incrementale en cours

Et voila un petit script python pour faire de la sauvegarde incrémentale avec rsync. Il est pas fini pour le moment, mais comme je suis un peu débordé je le mets en attendant de le finir. Il n'est pas compliqué, donc si vous avez des remarques sur le script ou des idées.

Le but de ce script est de permettre l'automatisation d'un backup incrémental configurable s'adressant à différents serveurs par ssh, NFS ou directement sur le port d'écoute du démon rsync.

1 - Le script

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# =============================================================================
#
# File .....: bckmg.py - Gestionnaires de backup
# Date .....: 22/09/2007
# 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

# Déclaration des constantes
NAME = "bckmg"
DESC = "Mise à jour de la sauvegarde des modules via rsync."
VERSION = "0.1"
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 ./config\n"
  CurrentServer = {}

  # Ouvrir le fichier
  try:
    fh = open("./config", '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 du server pour 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
    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écute 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:
    print "--> Exécution annulée %s" % date

  else:
    print "--> Fin d'exécution %s" % date
   
  sys.exit(0)


# ----------------------------------------------------------------------------
# Afficher la bannière
#
# Cette fonction a pour seule utilité d'afficher la bannière de l'application

def ShowBanner():

  print "%s release %s" % (NAME, VERSION)
  print "%s\n" % DESC


# ----------------------------------------------------------------------------
# Boucle principale du programme
#

# 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
  module = server['module'].split(' ')
  for mod in module:
    DoBackup(server['name'], mod, server['path'], server['days'],
          server['hours'])

  print ""
 
# Supprimer le verrou
retval = SetVerrou(0, PIDFILE)
if retval < 0:
  Quit(retval)

# Terminer l'application
Quit(0)

2 - Exemple de configuration


[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 
Vous êtes : 38.107.191.87 Dernière modification : 07/10/07 19:22 Propriétaire : Fritz Modifié par : thialme

Commentaires

Afficher les commentaires
Il y a 1 commentaire(s)