Author: | Damien Pollet |
---|---|
Date: | 2005-03-23 |
Version: | 1.12 |
Pour superviser l'état des machines d'un réseau, on déploie des agents sur chacune des machines à surveiller. Chaque agent va mesurer à intervalles réguliers un aspect de la machine sur laquelle il est installé. Les données locales ainsi collectées sont ensuite envoyées au serveur Zéphir qui centralise les informations provenant de tout un parc de machines.
Sur chaque machine, un service planifie et déclenche les mesures aux intervalles définis pour chaque agent. Ce service publie au fur et à mesure les résultats sur un site web dynamique local, et envoie régulièrement une archive des mesures vers le serveur Zéphir. Finalement, on peut contrôler l'état du service et des agents qu'il a chargés, ou déclencher des mesures à des moments quelconques via une interface XML-RPC.
Un agent est un objet Python dont la classe définit le type de mesure qu'il fait (e.g. le débit d'une interface réseau). Les modules définissant la classe d'un agent doivent donc être accessibles à l'interprète Python. Ces modules peuvent être installés comme tout module Python, soit en les déposant dans l'arborescence de la distribution Python, soit en modifiant la variable d'environnement $PYTHONPATH.
Chaque classe d'agent peut être instanciée autant de fois que nécessaire pour créer des agents avec différentes configurations (e.g. superviser telle ou telle interface).
Quand il démarre, le service consulte tous les fichiers *.agent qu'il trouve dans son répertoire de configurations (cf. option --config du service). Chacun de ces fichiers crée et configure un nombre quelconque d'agents que le service va charger. Les agents peuvent ainsi être regroupés thématiquement ou en fonction d'un paquet dont ils dépendent.
Ces fichiers d'instanciation *.agent contiennent les quelques déclarations en langage Python nécessaires pour instancier les agents et les ajouter à la liste dans la variable AGENTS. Un fichier d'instanciation typique a la structure suivante :
# -*- mode: Python; coding: iso-8859-1 -*- """ Paragraphe de documentation de ce groupe d'agents """ from agents.ifupdown import Ifupdown agent_eth0 = Ifupdown("eth0", device="/dev/eth0") agent_eth1 = Ifupdown("eth1", period=1, device="/dev/eth1", description = """Interface vers un autre réseau.""") AGENTS = [agent_eth0, agent_eth1]
Ce fichier instancie deux agents nommés eth0 et eth1 de type Ifupdown. Le nom est le seul paramètre obligatoire des agents. D'autres paramètres tels que la période de répétition des mesures ou la description sont facultatifs mais valables pour tout agent. Finalement chaque classe d'agents peut définir des paramètres spécifiques, comme ici device qui n'est à priori reconnu que par Ifupdown.
Le nom des variables intermédiaires n'a pas d'importance, tout ce qui importe au service est la valeur de la variable AGENTS, qui doit être une liste d'agents. Les agents dans cette liste seront chargés par le service.
Tous les agents reconnaissent les arguments génériques décrits ci-dessous. Cependant les valeurs par défaut peuvent varier d'une classe d'agent à une autre.
Le premier paramètre du constructeur est obligatoire, il s'agit du nom de l'agent.
Tous les autres paramètres sont nommés (syntaxe nom=valeur) ; ils peuvent donc être spécifiés dans un ordre quelconque, voire omis. Les classes d'agents reconnaissent certains de ces arguments et propagent les autres à leurs super-classes. La super-classe principale Agent reconnaît les arguments ci dessous.
Le répertoire de configurations contient un fichier site.cfg en plus des fichiers d'instanciation d'agents. Ce fichier déclare une variable SITE contenant une liste de couples de la forme ("Titre de section", [ 'agent1', 'agent2...' ]). La page web listant les agents de cette machine sera organisée en sections correspondant au contenu de SITE. Une section supplémentaire est automatiquement ajoutée pour les agents non affectés à une section.
Pour définir chaque nouveau type de mesure, il faut écrire une classe d'agents. Pour cela le framework fournit des classes de base : Agent et ses sous-classes RRDAgent et TableAgent (cf. Mémorisation des mesures). La classe du nouvel agent définira au moins les méthodes de mesure et de diagnostic décrites ci-dessous.
L'agent est paramétré une fois pour toutes lors de son instanciation. Le constructeur __init__() sera donc redéfini au besoin. Pour que les paramètres génériques soient traités, il faut appeler le constructeur de la classe mère en propageant les arguments nommés :
from agentmanager.agent import Agent from agentmanager import status class ExampleAgent(Agent): def __init__(self, name, some_parameter="default value", **params): # don't forget that or superclasses won't see their params Agent.__init__(self, name, **params) do_something_with(some_parameter)
La procédure pratique de mesure doit être implémentée en définissant la méthode measure(). Cette méthode sera appelée à chaque fois que le framework décide de déclencher une mesure. La méthode measure() doit renvoyer un dictionnaire {'champ': valeur}, avec la chaîne 'value' comme champ par défaut :
Certains agents ont besoin d'exécuter des commandes externes pour effectuer leur mesure. Comme le framework utilise TwistedMatrix il faut utiliser l'appel non bloquant twisted.internet.utils.getProcessOutput(), qui renvoie un objet de la classe Deferred. Dans ce cas, l'agent doit traiter le résultat de la commande dans un callback et renvoyer sans attendre l'objet deferred :
Le framework attend donc de measure() qu'elle renvoie un dictionnaire {'champ': valeur}, directement ou via un objet deferred.
En passant, une bonne source d'informations sous linux est l'arborescence /proc, présentée par exemple dans cet article de Linuxgazette.
L'agent doit indiquer s'il fonctionne de manière nominale ou dans un mode dégradé (e.g. impossible de prendre tout ou partie de la mesure, valeurs incohérentes). La méthode check_status() peut être redéfinie avec les heuristiques spécifiques à la classe d'agent pour renvoyer un niveau de fiabilité (voir la classe agentmanager.status.Status) :
Le framework mémorise automatiquement toutes les mesures programmées, pour les archiver vers le serveur Zéphir. La mémorisation dépend du format des résultats de mesures; elle est donc spécifique à chaque classe d'agents. La méthode save_measure() définit la mémorisation d'une mesure donnée. Son implémentation dans la classe Agent ne fait rien et devrait donc être redéfinie en même temps que measure(); cependant le framework fournit deux méthodes de mémorisation dans deux sous-classes d'Agent: RRDAgent et TableAgent.
RRDAgent mémorise les mesures en utilisant la suite d'outils RRDtool, adaptée aux mesures numériques et pouvant générer des graphes. TableAgent peut au contraire mémoriser des mesures d'un type quelconque, mais n'a pas les possibilités de synthèse de RRDAgent. En pratique, il suffit donc de définir la nouvelle classe d'agent en spécialisant soit RRDAgent soit TableAgent plutôt que directement Agent.
Pour mémoriser les mesures de manière spécifique, on peut redéfinir save_measure() ; par exemple pour un agent gardant seulement la dernière valeur :
Si l'agent génère des données autres que celles définies par sa super-classe, il doit d'une part archiver ces données au moment opportun, et d'autre part déclarer le format de ces données au framework.
Chaque agent dispose d'un répertoire dans lequel il peut archiver ses données et qui sera envoyé au serveur Zéphir. Ce répertoire contiendra au moins un fichier agent.xml généré automatiquement par la classe Agent. Ce fichier contient la description de l'agent: nom, statut, description, mais surtout la liste des données archivées par l'agent.
Cette liste de données est spécifiée par l'attribut data de la classe Agent, qu'il faut donc initialiser dans le constructeur. Cet attribut contient une liste d'objets des classes suivantes:
L'archivage proprement dit est déclenché à la demande du framework qui appellera pour cela la méthode write_data() de l'agent. Cette méthode peut être étendue par redéfinition pour écrire des fichiers supplémentaires spéciques à l'agent; par exemple les sous-classes de RRDAgent stockent l'archive de mesures au format .rrd ainsi qu'un graphe de ces mêmes mesures au format PNG. De même, les agents générant des données HTML implémenteront write_data() de la manière suivante :
def __init__(self, name, **params): # ... initializations ... self.html = HTMLData("") self.data = [self.html] def write_data(self): Agent.write_data(self) # only generate when the measured value is valid if self.last_measure is not None: html = "generated html code (template, string formatting...)" self.html.data = html