P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ I GUESS I'M REALLY FUCKED NOW Articles | Connexion
 
~Archivez vos données en Python

Précédent  
  Python  
  Suivant
 Présentation

Bien que nécessaire, l'archivage de données représente l'une des opérations informatiques les plus fastidieuses pour l'utilisateur. Nous allons donc rédiger un programme Python dont le rôle sera de réaliser cette tâche pour nous, automatiquement.
 Sommaire


 Introduction

L'outil informatique est aujourd'hui largement répandu et employé pour affranchir les utilisateurs du plus grand nombre de contraintes administratives possible. Cette centralisation extrême de l'information nous fait néanmoins prendre conscience de l'importance vitale de sauvegardes régulières. S'il est évident qu'une entreprise, quelle que soit sa taille, ne peut se permettre d'égarer des informations concernant ses clients ou ses transactions financières, il en va de même pour n'importe quel utilisateur. Le programme Python que nous allons étudier s'intitule UnderCover et met à profit la technologie XML-RPC pour vous permettre de sauvegarder automatiquement et régulièrement vos données sensibles.

L'application comprend 4 modules distincts. Le plus important se nomme UnderCoverServer.py et contient un serveur XML-RPC permettant de réaliser les sauvegardes. Le second, UnderCover.py, désigne un script de gestion du serveur qui pourra être utilisé pour le démarrer ou l'arrêter. UnderCoverCron.py servira pour sa part à exécuter le processus d'archivage à certains moments précis. Enfin, UnderCoverService.py encapsule UnderCoverCron au sein d'un service Windows et ne sera donc pas examiné.


 Le serveur XML-RPC

L'utilisation de XML-RPC pour la réalisation de cette application en surprendra plus d'un. Il est évident que cette technologie n'apporte aucun bénéfice pour un usage local. Toutefois, ses avantages deviennent évidents pour l'exploitation du logiciel sur un réseau. L'utilisation d'un véritable serveur permet par exemple d'autoriser n'importe quel machine d'un réseau local à demander une sauvegarde au serveur. Le choix d'XML-RPC en particulier vient de sa simplicité et de son universalité. Des bibliothèques disponibles pour de nombreux langages (Java, Perl, Ruby.) vous permettront d'en exploiter les services depuis n'importe quelle outil. En outre, cette technologie économise de nombreux efforts aux développeurs. Plutôt que de créer un protocole propriétaire et spécialisé, il nous suffira de rédiger trois ou quatre lignes de code pour mettre en place un serveur fonctionnel.

L'implémentation d'un serveur XML-RPC en Python ne demande que très peu de travail, ainsi qu'en témoigne la méthode _setupXMLRPCServer() du module UnderCoverServer. En trois lignes de code seulement, que vous présente le listing 1, vous pourrez exposer toutes les méthodes publiques d'un objet à travers le protocole XML-RPC. Dans notre cas, nous enregistrons l'objet courant. Pour vérifier que votre serveur fonctionne correctement, ajoutez simplement méthode testRPC(), décrite dans le listing 2, à la classe puis exécutez le code du listing 3 depuis l'interpréteur. Comme vous pouvez le constater, l'invocation d'une méthode distante s'effectue exactement comme l'appel d'une méthode locale. Cette caractéristique nous permet de rédiger un code clair et concis.



L'utilisation de XML-RPC nous permet de centraliser aisément le module d'archivage.

Malheureusement, l'invocation de la méthode serve_forever() du serveur XML-RPC reste insuffisante. Cette dernière ne constitue qu'une simple boucle infinie permettant de gérer les requêtes TCP. De fait, son utilisation au sein d'une méthode d'instance ne permettra pas à l'application de tourner indéfiniment. En exécutant le code du listing 1 vous constaterez que le script se termine immédiatement. La solution évidente consiste à faire appel aux threads, deux précisément. Le premier servira à contourner le blocage de l'exécution induit par serve_forever(). Le deuxième quant à lui constituera une simple boucle infinie. Leur code se trouve dans le listing 4. Le premier point important concerne la définition du premier thread que nous désignons ici comme un démon.

Puisque nous n'avons aucun contrôle sur la boucle interne à la méthode serve_forever(), nous ne pourrons pas quitter ce thread nous-mêmes. En le définissant comme démon, nous indiquons à l'interpréteur que l'exécution du programme sera terminée lorsque ce seul thread sera en activité (de manière générale, un programme se termine dès lors que tous les threads non démons sont terminés).

Pour ne pas pénaliser l'exécution des autres processus du système, la boucle de _serverLoop() contient une instruction réalisant une pause de 3 secondes dans le programme. Afin de pouvoir quitter notre programme proprement, nous utilisons l'attribut booléen _isServerAlive comme condition d'arrêt. Arrêter le serveur ne nécessitera que l'invocation de la méthode stop(), dont le seul rôle est de donner la valeur False à cet attribut.


 Archivage des fichiers

L'archivage des données ne sera effectué que sur demande explicite de la part d'une application cliente. De fait, ces dernières devront invoquer les méthodes distantes suivantes :

def backup(self, remotePath, localPaths)
def backupDirectory(self, remotePath, localPath, deleteRemote = False)
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
La première permet à l'utilisateur d'archiver une liste de répertoires, contenus dans localPaths, dans un répertoire de sauvegarde, remotePath. Pour chacun des répertoires devant être copiés, la méthode invoquera backupDirectory(). Cette dernière compare le contenu du répertoire d'origine et du répertoire de sauvegarde puis copie ou efface les fichiers pour les synchroniser. Le paramètre optionnel deleteRemote offre l'opportunité de supprimer les fichiers du répertoire d'archivage n'existant plus dans le répertoire d'origine.

Le procédé de synchronisation des répertoires nécessite l'utilisation de 6 listes de fichiers. Les deux premières contiennent la liste complète des fichiers du répertoire de sauvegarde et du répertoire d'origine. Sachez en outre que pour un répertoire donné, tous les sous répertoires seront traités. Les autres listes correspondent respectivement aux nouveaux fichier locaux, au fichiers n'existant que dans le répertoire d'archivage, aux fichiers communs aux deux répertoires et enfin aux fichiers communs et modifiés en local.

La construction des deux premières listes ne pose aucun problème particulier. Python propose en effet une fonction adaptée à notre problème, os.path.walk. Celle-ci permet de parcourir récursivement un répertoire et tous ses sous répertoires en récupérant l'ensemble des noms des fichiers. Le listing 5 présente ainsi le parcours du répertoire à archiver. Le traitement de chaque sous répertoire est réalisé grâce à une fonction dont nous précisons le nom à walk(). Cette dernière reçoit alors en paramètre le nom du dossier, une liste des fichiers qu'il contient et un tuple d'arguments.

A partir de ces nouvelles listes, nous pouvons très facilement en déduire le contenu de trois autres par de simples opérations de différence. Ainsi, pour déterminer la liste des fichiers locaux n'existant pas dans le répertoire archive, il nous suffit d'effectuer l'opération ensembliste CONTENU_LOCAL - CONTENU_ARCHIVE. L'opération inverse produit la liste des fichiers n'existant pas en local : CONTENU_ARCHIVE - CONTENU_LOCAL. Enfin, la liste des fichiers communs aux deux dossiers est obtenue grâce au calcul CONTENU_LOCAL - NOUVEAUX_FICHIERS. Le listing 6 présente la méthode utilisée pour réaliser ces opérations.

Cette dernière liste nous permet enfin d'atteindre l'un des éléments les plus importants du procédé de sauvegarde, soit l'ensemble des fichiers modifiés et devant être recopié dans le dossier de sauvegarde. Si cette étape ne se révèle pas indispensable, elle permettra à l'application de gagner un temps non négligeable lors de l'archivage de grandes quantités de fichiers. La fonction filter() du langage Python nous autorise la construction de cette liste en une seule ligne de code que vous retrouverez dans le listing 6. Le procédé consiste à filtrer les éléments de la liste existingFiles, autrement dit les fichiers communs aux deux dossiers, puis à n'en conserver que les éléments plus récents sur le disque local.

Parvenus au terme de cette première étape, nous pouvons nous intéresser à présent au problème de synchronisation des répertoires. Pour ce faire, chaque fichier des listes newFiles et modifiedFiles est copié dans le dossier de sauvegarde. La copie des données est permise grâce à la méthode _copy() qui accomplit un transfert de données par blocs. Le constructeur de la classe offre l'opportunité d'en modifier la taille, fixée par défaut à 32 kilo-octets. Vous pourrez alors aisément adapter UnderCoverServer à votre bande passante ou aux capacités de vos ordinateurs.

Lors de la description des deux méthodes accessibles par l'entremise de XML-RPC, nous avons évoqué un paramètre destiné à activer la suppression des fichiers devenus inutiles dans le répertoire d'archivage. Lorsque que celui-ci, deleteRemote, est vrai, la méthode backupDirectory() parcourt les fichiers et répertoires de la liste remoteOnlyFiles pour les supprimer un par un. Malheureusement, la fonction os.removedirs() proposée par l'API Python ne peut pas détruire un répertoire contenant des fichiers. Nous devons donc au préalable le parcourir, par l'intermédiaire de os.path.walk(), pour n'y laisser que des sous-répertoires.

L'implémentation du serveur de sauvegarde vous aura probablement agréablement surpris. Les lignes de code destinées à la gestion du réseau ne représentent en effet qu'une minuscule portion de l'ensemble, laissant le développeur se concentrer sur la partie sensible du programme.


 Journal d'activité

Le serveur que nous avons créé est prévu pour fonctionner sans aucune interface utilisateur, qu'elle soit graphique ou en console. Il est donc vital de proposer un journal d'activité du programme, que l'utilisateur pourra consulter à loisir. Ce journal rapportera les différentes erreurs rencontrées mais également toutes les actions effectuées par le programme : création de répertoire, suppression de fichier, et ainsi de suite.

Heureusement, la version 2.3 de Python contient un nouveau module particulièrement intéressant : logging. Celui-ci propose une API de journalisation complète et extrêmement efficace. Il est ainsi possible d'écrire des journaux dans un banal fichier texte, de les envoyer par mail, de les encapsuler dans des requêtes HTTP, de les conserver en mémoire. Les possibilités sont presque infinies.

Pour journaliser l'activité vous devrez créer trois objet : un "logger", qui représente le journal à proprement parler, un "handler", qui détermine le type du journal (fichier ou mail par exemple) et un "formatter" dont le rôle sera de mettre en forme vos messages automatiquement. Le listing 8 présente la création d'un journal dans un fichier, présentant les messages sous la forme "heure type_de_message message". De cette manière, l'exécution de l'instruction logger.info("Début archivage") provoquer l'écriture de la ligne suivante dans le fichier :

2003-11-24 23:03:00,222 INFO Début Archivage
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Nous vous conseillons vivement d'exploiter les services de ce fabuleux module dans toutes vos applications Python.


 Gestion du serveur

Bien que particulièrement efficace, notre serveur ne se révèle que difficilement exploitable tel quel. Pour pallier ce problème, il se trouve accompagné d'un script intitulé UnderCover.py qui aura une double utilité. Premièrement, l'utilisateur pourra l'invoquer pour démarrer, arrêter ou relancer le serveur. Les administrateurs système pourront même l'inscrire dans la liste des services de la machine pour autoriser son lancement et son arrêt automatiquement. Ensuite, ce script pourra être importé en tant que module dans d'autres programmes Python. Les programmeurs seront alors en mesure de manipuler le serveur et d'exécuter des opérations de sauvegarde depuis leurs programmes, ainsi que nous allons bientôt le voir. Le contenu de ce script se révèle particulièrement simple puisque ce dernier se contente d'invoquer les méthodes RPC en suivant l'exemple du listing 3. La véritable originalité concerne la lecture du fichier de configuration undercoverserver.conf, au format XML. Ce fichier sert à définir les valeurs des quatre paramètres du constructeur d'UnderCoverServer : le port, la taille des blocs de données, l'activation de la suppression des fichiers inutiles et la durée de vie du fichier journal.


 Planification des sauvegardes

Le programme UnderCoverCron permet non seulement de démontrer l'utilisation du module UnderCover dans vos applications, mais également d'apporter une solution de planification des sauvegardes pour les systèmes ne disposant pas d'un équivalent de cron. Cela permettra également aux utilisateurs de s'abstenir de toute modification dans crontab. La définition des opérations d'archivage se trouve dans le fichier undercovercron.conf, au format XML ainsi que l'atteste le listing 8. Les éléments définissent, avec une syntaxe équivalent au démon cron d'Unix, les moments auxquels procéder à l'archivage de vos dossiers définis dans . Dans notre exemple, UnderCoverCron invoquera la méthode UnderCover.backup() à 2h, 3h et 12h chaque jour. L'implémentation de cron s'avère simple : un thread effectue une pause d'une minute avant de parcourir le fichier de configuration puis d'effectuer les sauvegardes nécessaires. En lisant le fichier toutes les minutes nous offrons à l'utilisateur la possibilité de modifier la planification des tâches sans interrompre le serveur.

import SimpleXMLRPCServer
# .
self._xmlrpcServer = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 4620))
self._xmlrpcServer.register_instance(self)
self._xmlrpcServer.serve_forever()
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
def testRPC(self):
  return "XML-RPC [OK]"
       
      
JextCopier dans Jext
import xmlrpclib
rpc = xmlrpclib.ServerProxy("http://localhost:" + str(4620))
print rpc.testRPC()
       
      
JextCopier dans Jext
def __init__(self, .):
  self._stopEvent = threading.Event()
  self._sleepPeriod = 3.0
  self._isServerAlive = True

  self._xmlrpcServerThread = threading.Thread(target = self._setupXMLRPCServer)
  self._xmlrpcServerThread.setDaemon(True)
  self._xmlrpcServerThread.start()

  self._serverThread = threading.Thread(target = self._serverLoop)
  self._serverThread.start()

  def _serverLoop(self):
    while self._isServerAlive:
      self._stopEvent.wait(self._sleepPeriod)
       
      
JextCopier dans Jext
def _addFile(self, args, dirname, names):
  for name in names: args[1].append(os.path.join(dirname[len(args[0]) + 1:], name))

# ...
  os.path.walk(localPath,  self._addFile, (localPath,  _localContent ))
       
      
JextCopier dans Jext
def _difference(self, list1, list2):
  return [element for element in list1 if not element in list2]

# .
  newFiles = self._difference(_localContent, _remoteContent)
  remoteOnlyFiles = self._difference(_remoteContent, _localContent)
  existingFiles = self._difference(_localContent, newFiles)
  modifiedFiles = filter(lambda file: os.path.getmtime(os.path.join(localPath,  file)) > \
    os.path.getmtime(os.path.join(remotePath, file)), existingFiles)
       
      
JextCopier dans Jext
logger    = logging.getLogger('UnderCover')
handler   = logging.FileHandler(logFile)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')

handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
       
      
JextCopier dans Jext
<undercovercron>
  <action>
    <at>0 12 * * *</at>
    <at>0 3 * * *</at>
    <at>0 2 * * *</at>
  </action>
  <remotePath>/mnt/ntfs/backup</remotePath>
  <localPaths>
    <path>/home/gfx</path>
  </localPaths>
</undercovercron>
       
      
JextCopier dans Jext

 Téléchargements

Le programme UnderCover peut être téléchargé depuis la page des téléchargements de ce site.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006


Précédent  
  Python  
  Suivant

 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE