François' mini-projects

Collaborations inter-machines

1   Introduction

Dans un réseau de machines fortement connectées, il est naturel de parfois souhaiter que plusieurs machines participent à l'exécution d'un seul programme, en collaboration. Parfois, un programme sur une machine donnée peut désirer prendre avantage de la puissance de calcul d'une autre machine, accéder à la partie non-partagée du système de fichiers d'une autre machine, utiliser des programmes qui ne sont exploitables que sur certaines machines seulement, vérifier les idiosyncrasies d'une machine particulière.

Par exemple, on peut vouloir offrir divers services aux usagers de l'hypertoile ailleurs que sur le seul serveur Web d'un réseau, de manière à ne pas surcharger la machine qui roule ce serveur. Les scripts CGI peuvent alors faire en sorte d'exécuter l'essentiel de l'application d'un service particulier sur une machine sur lequel ce service est dédié.

Ce module Local.remote implante une architecture client-serveur par laquelle il est assez facile au programmeur de distribuer une application sur plusieurs machines. Une première machine peut demander de l'aide ou des services à d'autres machine en démarrant un serveur sur chacune d'entre elles, cette première machine est alors un client pour tous ces serveurs. Ces serveurs éloignés sont capables, sur demande, d'évaluer des expressions Python, d'appeler des fonctions ou d'exécuter des énoncés Python. Par le jeu d'installations préalables et des importations de Python, on peut sans grand effort déclencher des actions même assez lourdes sur des machines de service.

À chaque instance de la classe Serveur, correspond une connexion de réseau vers un serveur sur une autre machine. Après avoir été établie, la connexion est en général gardée active afin de servir plusieurs requêtes sur la machine éloignée. La connexion vers une machine particulière n'est abandonnée qu'une fois accompli l'ensemble du travail à effectuer par cette machine.

2   En tant que client

2.1   Initialisation d'une connexion

Pour fabriquer un objet Serveur, on utilise:

from Local import remote
serveur = remote.Serveur(CHEMIN, TRACE)

Par cette action, le serveur est automatiquement téléchargé au besoin sur toutes les machines mentionnées dans CHEMIN et sauvé dans le fichier ~USAGER/.python-remote-VERSION, où VERSION identifie la version du protocole utilisé. Ce fichier est réutilisé s'il existe déjà, pourvu qu'il démarre correctement et identifie de son côté la bonne version du protocole.

2.1.1   L'argument CHEMIN

Si CHEMIN n'est pas fourni ou vaut None, la machine courante est utilisée, dans installer ni utiliser un serveur éloigné. Sinon, CHEMIN doit être une chaîne qui contient une ou plusieurs spécifications USAGER@MACHINE séparées par des deux-points. Dans ce cas, tous les USAGER@MACHINE à l'exception du dernier définissent des connexions intermédiaires successives vers la destination finale. Si la partie USAGER@ est absente, on présume le même usager que celui de la machine précédente dans la chaîne des connexions.

Si la première machine apparaît dans le fichier ~/.ssh/config ou dans le fichier ~/.ssh/known_hosts, alors la première connexion est établie en utilisant SSH. On présume alors que l'échange de clés SSH a déjà été effectué et qu'aucun mot de passe explicite n'est requis. Dans les autres cas, le fichier ~/.netrc doit associer à cet machine une identification de compte et un mot de passe, et la connexion utilise alors un mélange de Telnet et de FTP, tout en inhibant les techniques traditionnelles de contrôle de flot de terminal.

Dans le cas le plus habituel, qui est aussi le plus simple, l'argument CHEMIN est le nom de la machine où doit rouler le serveur.

2.1.2   L'argument TRACE

Si TRACE n'est pas donné ou vaut zéro, il n'y aura aucune filature du protocole. Sinon, TRACE possède une valeur entre 1 et 3.

  • La valeur 1 provoque un rapport de filature sur l'erreur standard locale pour l'aspect client du protocole.
  • La valeur 2 a le même effet que la valeur 1, mais en plus, produit en plus un fichier sur MACHINE pour l'aspect serveur du protocole.
  • La valeur 3 a la même effet que les valeurs 1 et 2, mais en plus, produit en plus une copie de la communication Telnet sur l'erreur standard locale.

2.2   Usages d'une connexion

Un contexte d'évaluation est établi pour la connexion courante sur le serveur éloigné. Le module Python pickle sérialise toute l'information transmise dans les deux directions, l'utilisateur doit donc se limiter aux structures de programmations que ce module peut manipuler.

Voici les méthodes d'un objet de classe Serveur:

serveur.appeler(FONCTION, [ARGUMENT]...)
serveur.appliquer(FONCTION, ARGUMENTS)
serveur.evaluer(EXPRESSION)
serveur.executer(ÉNONCÉ)
serveur.quitter()

La méthode appeler appelle FONCTION sur la machine éloignée en lui passant zéro, un ou plusieurs ARGUMENTs, puis retourne le résultat de cet appel. FONCTION est une chaîne contenant une expression Python qui, évaluée, donne une fonction appelable, et chaque ARGUMENT doit pouvoir être traité par le module pickle. La méthode appliquer fait la même chose, sauf que tous les ARGUMENTS sont fournis dans une seule séquence plutôt que séparément. La méthode evaluer évalue EXPRESSION sur la machine éloignée et retourne la valeur de cette expression. La méthode executer provoque l'exécution de ÉNONCÉ sur la machine éloignée et retourne None. EXPRESSION, aussi bien que ÉNONCÉ, sont des chaînes contenant un fragment de programme Python. Finalement, la méthode quitter coupe la connexion réseau au serveur éloigné.

2.3   Exemple d'usage

Voici un exemple simplet. Supposons que griffon est le nom d'une machine pour laquelle nous avons déjà un accès SSH garanti par l'échange approprié de clés. Pour faire calculer la valeur de l'expression Python 2 + 3 sur griffon, on peut faire:

from Local import remote
serveur = remote.Serveur('griffon')
print serveur.evaluer('2 + 3')
serveur.quitter()

3   En tant que serveur

L'information donnée ci-après peut faciliter la compréhension du fonctionnement interne de ce module, mais elle n'est pas nécessaire pour l'utiliser.

3.1   Protocole de communication

Voici une description du protocole de communication entre le client et le serveur.

  • Dès son démarrage, le serveur s'identifie par une ligne sur la sortie standard, suivie d'une ligne blanche. Cette identification contient le niveau de protocole utilisé.
  • Une fois démarré, le serveur s'engage dans une boucle dans laquelle il lit une requête en provenance du client sur son entrée standard suivie d'une ligne vide, et après avoir calculé une réponse à destination du client, la retourne sur sa sortie standard, suivie d'une ligne vide.
  • Les requêtes, aussi bien que les réponses, utilisent les chaînes dans le format produit par le module pickle, puis comprimés par le module zlib, puis transformées par le module base64, le résultat est un bloc de lignes de moins de 80 caractères chacune et constituées de caractères imprimables.
  • Toutes les requêtes sont traitées dans un même contexte de variables locales Python, du côté du serveur; l'effet des requêtes s'y accumule donc et une requête peut prendre avantage de l'effet des requêtes antérieures.
  • Une requête vide amène une réponse vide, et l'arrêt du serveur.

3.2   Démarrage du serveur

Le serveur est démarré automatiquement sur la machine éloignée au moment de la fabrication d'instance de Serveur. Pour y parvenir, un shell est établi via SSH ou Telnet, et la commande suivante est lancée:

~USAGER/.python-remote-VERSION [OPTION]... [CHEMIN]

Les variables USAGER et VERSION ont déjà été expliquées dans ce document. Quant aux options, elles peuvent être:

-i
Utiliser des réponses indirectes lorsque les réponses sont grosses. Dans le cas de Telnet en particulier, une grosse réponse sera plus efficacement transmise par la création d'un fichier pour le contenir, qui sera ensuite obtenu par FTP.
-t
Prendre des dispositions pour produire une filature du protocole dans un fichier, sur la machine où ce serveur s'exécute.

Si CHEMIN n'est pas fourni, alors le serveur est destiné à la machine courante. Sinon, ce serveur est simplement un mandataire vers le serveur final sur une autre machine et ne fait que transiter l'information dans les deux directions: CHEMIN indique alors comment lancer ce serveur final à partir de la machine du serveur courant.