Skip to content

Anaisdeb/SR05-Algorithme_reparti

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SR05 - Projet de Programmation

[[TOC]]

Présentation

Ce projet a été réalisé dans le cadre de l'enseignement SR05 en Printemps 2022. Celui-ci concerne l'implémentation d'un algorithme réparti qui:

  • Dans un premier temps, combine l'algorithme de sauvegarde et de file d'attente. Cela permet de capturer le réseau à certains moments, et de régir les demandes d'entrées en section critique entre les différents sites.
  • Dans un second temps, permet l'accès à un fichier en lecture/écriture lors d'une entrée en section critique.

Installation et Lancement du projet

Installation

Dans un premier temps, il est nécessaire de cloner le projet sur Gitlab :`

Si le compte possède une clé SSH :

git clone [email protected]:rdelaage/sr05-projet.git

Sinon :

git clone https://gitlab.utc.fr/rdelaage/sr05-projet.git

Lancement

Pour lancer le projet, il est nécessaire de créer dans un premier temps un pipe nommé dans /tmp (par exemple) avec la commande suivante :

mkfifo /tmp/f

Ensuite, dans le dossier du projet, il faut lancer le programme net.py n fois sur la même ligne de commande pour avoir accès à l'interface d'utilisation du projet avec n sites. Il faudra préalablement mettre à n la variable "nbSite" ligne 19 de net.py qui par défaut est à 3. Il est à noter qu'il faut nommer chaque site avec un numéro en paramètre, à partir de 0 et consécutifs. On aurait avec 3 sites par exemple :

./net.py 0 < /tmp/f | ./net.py 1 | ./net.py 2 > /tmp/f

ou 

python3 ./net.py 0 < /tmp/f |python3 ./net.py 1 |python3 ./net.py 2 > /tmp/f

Une fois les fenêtres ouvertes, on peut alors modifier le contenu textuel (qui contient au départ les explications des commandes). Pour cela on peut entrer des commandes au format suivant : x;y;z

  • x est ici le numéro de ligne concernée,
  • y est la commande à exécuter, on a ici :
    • s pour substituer le contenu de la ligne x par le contenu définit dans z
    • d pour supprimer la ligne x. Le contenu de z n'a pas d'importance dans ce cas.
    • a pour ajouter une ligne après la ligne x avec le contenu défini dans z
    • i pour insérer une ligne avant la ligne x avec le contenu défini dans z
  • z est le contenu du texte faisant l'objet de la commande.

Principe de fonctionnement

Le projet combine l'algorithme de l'activité 4, avec celui de la file d'attente et de sauvegarde. La topologie du réseau est ici un anneau, permettant ainsi de ne pas avoir à implémenter d'algorithme de création d'anneau de contrôle.

Activité 4

On retrouve ici la solution 6 de l'activité 4 qui consiste en l'implémentation d'une file de messages gérée par un thread "centurion", et remplie d'une part par un thread de lecture dans le flux stdin, et de l'autre par le site voisin écrivant sur son flux stdout connecté à l'entrée du site.

Étant donné que les messages sont stockés dans une file, on a donc un traitement séquentiel et FIFO de ceux-ci. Concernant la structure de celle-ci et des messages, on a dans un premier temps choisi que chaque objet de la file est un tableau de 2 cases :

  • La première case est un mot qui définit quelle action faire avec le message. Si c'est "send", le message sera envoyé, si c'est "process", le message sera traité si le site est destinataire de ce message.
  • La seconde case est le contenu du message sous forme d'une chaîne de caractères. Il est nécessaire alors avant de le traiter de créer une instance de message à partir de cette chaîne de caractères. Le format des messages est presque identique à celui de Airplug, c'est-à-dire "who~fromWho~messageType~color~isPrepost~vectClock~what". Nous avons ajouté les champs isPrepost et vectClock dans le but de ne pas avoir à le faire dans le champs what.

Concernant les différents acteurs de cet algorithme, on retrouve deux threads et une fonction permettant d'assurer la réception, l'envoi et le traitement des messages :

  • Le centurion, dans un thread, va ici dépiler un objet de la file, lire la première case et décider alors ce que va être fait du contenu du message dans la seconde case. Lorsqu'il y a traitement du message, celui-ci est passé en paramètre de la fonction receiveExternalMessage() qui va réagir en fonction de son type.
  • Le thread de readMessage() lit chaque message écrit sur stdin et les enregistre sur la file(voir documentation).
  • La fonction writeMessage() permet d'envoyer le message sur le flux stdout. Le message arrive alors sur le flux stdin du site voisin de par la topologie du réseau, et donc est lu par la fonction readMessage()(voir documentation).

Algorithme de file d'attente

Concernant cet algorithme, on a implémenté l'algorithme disponible sur moodle (Ci-contre). Pour cela, il a fallu adapter le code de l'activité 4 en ajoutant des attributs, méthodes et classes.

  • En effet, il a fallu dans un premier temps ajouter des attributs dans la classe Net qui sont
    • stamp : Horloge logique du site,
    • networkState : État des requêtes de section critique dans le réseau,
  • Dans un second temps ont été implémentées des méthodes permettant de gérer les requêtes et entrées en section critique :
    • basCsRequest(self) : Envoie de requête d'entrée en section critique,
    • basCsRelease(self) : Envoie de déclaration de libération de section critique,
    • checkState(self) : Vérification de l'état du Site, pour éventuellement entrer en section critique,
    • enterCs(self) : Entrée en section critique,
    • receiveExternalLockRequestMessage(self, msgReceived) : Méthode de réaction face à un message de requête d'entrée en section critique,
    • receiveExternalReleaseMessage(self, msgReceived) : Méthode de réaction face à un message de déclaration de libération de section critique,
    • receiveExternalAckMessage(self, msgReceived) : Méthode de réaction face à un message d'accusé de réception
  • Dans une dernière mesure, nous avons ajouté des classes de message permettant de reconnaître des nouveaux types de messages (voir documentation) :
    • BroadcastMessage : Classe pour les messages à envoyer à tous les sites,
    • LockRequestMessage : Classe pour les messages de requêtes d'entrées en section critique,
    • AckMessage : Classe pour les messages d'accusés de réception,
    • ReleaseMessage : Classe pour les messages de libération de section critique.

Dès lors que ces différents outils ont été définis, il a suffit de dérouler l'algorithme défini ci-contre.

Algorithme de sauvegarde

Concernant l'algorithme de sauvegarde, il y a eu des ajouts comme dans l'algorithme de file d'attente.

  • En effet des attributs ont été ajoutés sur les classes Net, State et Message:
    • initiatorSave (Net): booléen indiquant si le site est à l'initiative de la sauvegarde.
    • messageAssess(State) Bilan des messages en transit dans le réseau.
    • globalState (Net): Dernier enregistrement de l'état global par le site,
    • nbWaitingMessage et NbWaitingState (Net): Nombre de messages et d'états attendus par le site,
    • vectClock(Net et Message): Valeur de l'horloge vectorielle pour le message ou l'état.
  • Ensuite, des méthodes ont été ajoutées :
    • initSnapshot(self): Méthode d'initialisation de la sauvegarde par un site,
  • Enfin, des classes dans utils.py et message.py on été ajoutées:
    • VectClock : classe permettant d'instancier des horloges vectorielles (voir documentation),
    • SnapshotRequestMessage Messages ayant comme but de transmettre les demandes de sauvegardes.
    • StateMessage : Messages permettant de transmettre les états des sites dans leur contenu.

Dès lors que ces outils ont été définis, l'algorithme 11 vu dans ce cours a été suivi, notamment grâce au fait que la topologie du réseau est un anneau permettant de faire office d'anneau de contrôle.

Algorithme de l'application de base

Pour l'application de base, nous avons choisi d'implémenter un système de partage de fichier avec un fichier partagé, accessible en lecture/écriture. Pour cela :

  • On a crée dans un premier temps une classe Bas dans le fichier bas.py,
  • On a lié une instance de Bas à chaque site Net dès leur construction à l'aide d'un attribut bas,
  • A l'aide d'une interface graphique et un système de commande, l'utilisateur à la possibilité d'écrire des commandes pour commander une modification, enclenchant alors une demande d'entrée en section critique.
  • Dès que la demande est émise, le site attend son entrée en section critique. Dès son entrée, c'est-à-dire dès que checkStatus() déclenche enterCs(), qui envoie grâce à bas.send() un message à Bas, celui-ci va exécuter la commande entrée dans doAction(). Le fichier est modifié avec la méthode edit() enclenchée par doAction()
  • Après modification du document, Bas envoie un message au réseau avec sendMessageFromBas() pour leur indiquer ce qui a été modifié. Les différents sites détectent alors un nouveau type de message EditMessage indiquant une modification du fichier partagé, et en réaction lancent la méthode doAction() avec la commande en paramètre. La modification est alors propagée.

Pour plus de précision sur les méthodes implémentées, voir la documentation sur la classe Bas.

Concernant les méthodes ajoutées dans Net, on a:

  • sendToBas(self, message): fonction qui fait appel à bas.send(self, message). Permet la transmission de messages de Net à Bas,
  • sendMessageFromBas(self, message): fonction qui transmet les Edit Messages aux autres sites en faisant appel à writeMessage().

Concernant les classes ajoutées, on a:

  • BasState (utils.py): Classe permettant d'instancier des états de Bas, et de les envoyer dans les instances State lors des sauvegardes.
  • EditMessage (message.py): Messages permettant la transmission de commandes dans le réseau.
  • Command (bas.py): Classe permettant d'instancier les commandes réalisées par les applications de base.

Scénarios de fonctionnement

Premier scénario : Ajout de contenu

  • On lance la commande de démarrage du logiciel (donné ci-contre),
  • On entre dans la section de commande le texte :
3;a;Ligne ajoutée
  • On peut alors observer l'ajout d'une ligne après la troisième ligne avec le contenu de la dernière section "Ligne ajoutée"

Second scénario : Modification de contenu

  • On lance la commande de démarrage du logiciel (donné ci-contre),
  • On entre dans la section de commande le texte:
3;s;Ligne modifiée
  • On observe alors que la troisième ligne a été changée en "Ligne modifiée"

Troisième scénario : Suppression de contenu

  • On lance la commande de démarrage du logiciel (donné ci-contre),
  • On entre dans la section de commande le texte:
3;d;Ligne supprimée
  • On peut alors observer la suppression de la troisième ligne dans le contenu textuel.

Quatrième scénario : Lancement d'une sauvegarde

  • On lance la commande de démarrage du logiciel (donné ci-contre),
  • On lance alors les commandes du premier et second scénario, et on clique ensuite sur le bouton Request a snapshot. On observe alors que le fichier save a été créé et rempli avec différentes lignes. Celles-ci correspondent aux états des 3 sites qui ont été démarrés. L'ordre des états est ici le même que celui dans lequel les sites ont été initialisés.
  • Le format de chaque état est :
netId°nbSite°messageAssess°vectClock°basState
  • netId est l'identifiant du site,
  • nbSite est le nombre de site dans le réseau,
  • messageAssess est le bilan des messages du site en transit dans le réseau,
  • vectClock est l'horloge vectorielle du site au format netId#nbSite#h#h#h, où les deux premières informations sont identiques à celles précédemment citées, et h les horloges logiques de chaque site.
  • basState est l'état de l'application de base, qui est au format isRequestingCs°command°encodedText, où :
    • isRequestingCS est le booléen indiquant si le site souhaite l'entrée en section critique,
    • command est le contenu de la commande envoyé,
    • encodedText est le contenu du fichier partagé, encodé en base 64.

Complément: Ajout de la possibilité de multiples prises de snapshot

Comme vous pouvez le remarquer dans le fichier net.py et messages.py, nous avions commencé à réfléchir à la possibilité de prendre plusieurs snapshot dans la même instance du projet. Pour cela, nous avions donc commencé à créer un mécanisme de libération de snapshot, avec l'envoi de message de type SnapshotReleaseMessage à la fin d'un snapshot, qui réinitialiserait l'ensemble des variables affiliées au snapshot.

Cependant, nous n'avions pas conscience que cela cause également des problèmes de synchronisations entre les sites, notamment sur leur gestion des messages dans l'anneau de contrôle. Nous avons donc choisi de ne pas implémenter totalement cette fonctionnalité.

Documentation

On retrouve dans ce projet 4 fichiers :

net.py - Classe Net

  • Attributs :
    • netID : Identifiant du site,
    • nbSite: Nombre de sites sur l'ensemble du réseau,
    • bas : Application BAS affiliée au site,
    • stamp : Horloge logique du site,
    • networkState : Tableau avec une case par site représentant leur état pour l'algorithme de la file d'attente ("R": Requête, "A" : Accusé de réception, "L" : Libéré),
    • color : Couleur du site (blanc ou rouge),
    • initiatorSave : Booléen répondant à la question : Est-ce que ce site est l'initiateur de l'algorithme de sauvegarde ?
    • messageAssess : Bilan des messages en transit dans le réseau,
    • globalState : État global du réseau selon ce site,
    • nbWaitingMessage : Nombre de messages attendus par le site,
    • nbWaitingState : Nombre d'états attendus par le site,
    • messages : File contenant l'ensemble des messages en attente de traitement. Chaque message est un tableau contenant en première case l'action à effectuer sur le message, et le contenu du message dans la seconde case.
    • readMessageThread : thread maintenant la fontion readMessage(),
    • centurionThread : thread maintenant la fonction centurion(),
    • state : État local du site,
  • Méthodes
    • logger(self, logContent) : Méthode d'affichage, permettant ainsi l'affichage du fonctionnement de l'algorithme sur le terminal,
    • readMessage(self) : Méthode permettant, en étant lancée dans un thread, de lire l'ensemble des lignes inscrites sur stdin et de les mettre dans la file de messages,
    • writeMessage(self, message): Méthode permettant de mettre les messages à envoyer dans la file de messages,
    • centurion(self) : Méthode permettant d'officier le rôle de centurion (référence à l'armée de César), en dépilant séquentiellement chaque message de la file. En fonction de la première case du message, le centurion effectue une action différente :
      • "send" : Envoie le contenu du message au site voisin,
      • "process" : Traite le message, dans le cas où celui-ci le concerne (s'il est le destinataire du message), et le renvoie si le message concerne tous les sites, ou s'il n'est pas le destinataire du message.
    • initSnapshot(self) : Méthode permettant d'initialiser la demande de sauvegarde et de la transmettre au réseau.
    • sendMessageFromBas(self, message) : Méthode permettant de diffuser un message reçu de BAS.
    • sendToBas(self, message) : Méthode permettant d'envoyer un message à BAS.
    • basCsRequest(self) : Envoie une requête de Section critique au réseau, et inscrit cette demande dans le tableau networkState.
    • basCsRelease(self) : Envoie un message de libération de la section critique au réseau et inscrit la libération dans le tableau networkState.
    • checkState(self) : Vérifie si le site peut entrer en section critique, en fonction de l'état du réseau dans l'algorithme de file d'attente.
    • enterCs(self) : Permet d'entrer en section critique, et envoie un message à Bas pour signaler l'entrée en section critique,
    • receiveExternalMessage(self, msgReceived) : méthode permettant la réception de message. En fonction du corps du message.

bas.py - Classe Bas et classe Command

  • Bas
    • Attributs :
      • net: Site NET lié à l'instance BAS,
      • state: Contenu du fichier partagé,
      • root: Instance de l'interface TKinter,
      • requestSnapshotButton: Instance de bouton pour lancer des demandes de sauvegarde,
      • printTextWidget: Instance de la section de texte explicatif de l'application.
      • commandFrame: Frame contenant la zone de texte ainsi que le bouton "Modifier".
      • commandEntry: Instance de la zone d'entrée de texte.
      • commandButton: Instance contenant le bouton de modification du fichier.
    • Méthodes :
      • send(self, msg): Méthode permettant de lancer une instruction, contenue dans le paramètre msg, qui est soit un retour de net suite à une demande de section critique, soit une demande d'exécution reçue par NET (suite à la modification par un autre site par exemple).
      • action(self): Méthode permettant d'émettre, si l'on appuie sur le bouton "Modifier", de lancer une demande de section critique sur le réseau, et de désactiver les boutons en attendant la réponse.
      • doAction(self): Méthode permettant de lancer la commande contenue dans le champs de texte. Après exécution, les boutons sont à nouveau cliquables.
      • edit(self, command): Méthode permettant la modification du champ texte "printTextWidget" suite à la réception d'une commande ici en paramètre.
      • snapshot: Méthode lancée après appui sur le bouton "Request a snapshot", lance la fonction associée de la classe Net.
      • run(self): Méthode permettant de lancer l'affichage de l'interface TKinter.
  • Command
    • Attributs :
      • lineNumber: Ligne du texte concernée par la commande.
      • action: Action à réaliser sur le texte (modifier/ajouter/supprimer).
      • text: Texte contenu de la commande (à ajouter, rempaçant le texte présent).
    • Méthodes :
      • parse(cls, s): Méthode statique permettant de créer une instance de commande à partir d'une chaîne de caractères.

utils.py - Classes utilitaires

  • VectClock
    • Attributs :
      • netID: Identifiant du site visé par l'instance d'horloge vectorielle .
      • nbSite: Nombre de sites sur le réseau.
      • clockArray: Valeur de l'horloge vectorielle
    • Méthodes :
      • incr(self, otherClock): Méthode permettant d'incrémenter l'horloge vectorielle en prenant en compte celle d'un autre site.
      • str(self): Méthode "override" permettant d'afficher une horloge vectorielle au format "netId#nbSite#strClockArray".
      • fromString(cls, stringToConvert): Méthode statique retournant une instance d'Horloge vectorielle à partir d'une chaîne de caractères.
  • BasState : Classe permettant d'exprimer l'état d'une application BAS
    • Attributs :
      • text: Contenu du fichier partagé par les sites au moment de la capture.
      • command: Contenu de la zone de texte permettant le lancement de commandes au moment de la capture.
      • isRequestingCs: Est-ce que l'application était en demande de section critique au moment de la capture ?
    • Méthodes :
      • fromString(cls, stringToConvert): Méthode permettant de créer une instance de BasState à partir d'une chaîne de caractères.
  • State : Classe permettant d'exprimer les états locaux des sites
    • Attributs :
      • messageAssess: Bilan des messages en transit au moment de la capture.
      • netID: Identifiant du site concerné par la capture.
      • nbSite: Nombre de sites dans le réseau.
      • vectClock: Horloge vectorielle du site au moment de la capture.
      • basState: État de l'application Bas liée au site.
    • Méthodes :
      • fromString(cls, stringToConvert): Créer une instance d'État à partir d'une chaîne de caractères.

messages.py - Classes de messages

  • Attributs:
    • who: destinataire du message,
    • fromWho: auteur du message,
    • messageType: Type du message (StateMessage, EditMessage, etc.)
    • color: Couleur du site émetteur du message (Blanc ou Rouge),
    • isPrepost: Booléen exprimant le fait que le message soit émis d'un site blanc et arrive sur un site rouge.
    • vectClock: Horloge vectorielle du site émetteur du message.
    • what: Contenu du message.
  • Méthodes
    • toPrepost(self): Méthode qui passe l'attribut "isPrepost" à True et retourne l'instance de Message.
    • setColor(self, color): Méthode setter de l'attribut "color".
    • fromString(cls, s): Méthode créant une instance de Message à partir d'une chaîne de caractères. Il existe dans ce projet différents types de messages héritant de la classe Message ou de BroadcastMessage:
    • BroadcastMessage(Message): Messages ayant pour but d'être envoyés à tout le monde.
    • EditMessage(BroadcastMessage): Messages permettant de transmettre des messages permettant de modifier le contenu du fichier partagé.
    • LockRequestMessage(BroadcastMessage): Messages ayant comme but d'envoyer des requêtes d'entrées en section critique.
    • AckMessage(Message): Messages ayant pour but d'envoyer des accusés de réception.
    • ReleaseMessage(BroadcastMessage): Messages ayant pour but d'envoyer des déclaration de libération de section critique.
    • StateMessage(BroadcastMessage): Messages ayant pour but de transmettre des États.
    • SnapshotRequestMessage(BroadcastMessage): Messages ayant pour but d'émettre des demandes de sauvegardes.