Guide du développeur de services de données Sun Cluster pour SE Solaris

Chapitre 12 Protocole de notification des reconfigurations de clusters

Ce chapitre présente le protocole CRNP (Cluster Reconfiguration Notification Protocol, protocole de notification des reconfigurations de clusters). Ce protocole permet aux applications évolutives et de basculement de “reconnaître les clusters.” Plus précisément, il propose un mécanisme qui permet aux applications de s'enregistrer et par la suite, d'être informées de manière asynchrone des événements de reconfiguration de Sun Cluster. Les services de données fonctionnant au sein du cluster et les applications exécutées à l'extérieur du cluster peuvent s'enregistrer pour être avertis des événements. Les événements sont générés lorsque l'appartenance à un cluster change et que l'état d'un groupe de ressources ou d'une ressource est modifié.


Remarque –

L'implémentation du type de ressources SUNW.Event fournit des services CRNP à haute disponibilité sur Sun Cluster. Cette implémentation est décrite plus en détail à la page de manuel SUNW.Event(5).


Ce chapitre contient les rubriques suivantes :

Concepts du protocole CRNP

Le protocole CRNP définit les couches application, présentation et session de la pile de protocoles OSI (Open System Interconnect/Interconnexion de systèmes ouverts) standard constituée de sept couches. La couche transport doit utiliser le protocole TCP, et la couche réseau le protocole IP. Le protocole CRNP est indépendant des couches liaison de données et physique. Tous les messages de la couche application échangés au moyen du protocole CRNP utilisent le langage XML 1.0.

Fonctionnement du protocole CRNP

Le protocole CRNP propose des mécanismes et des démons qui génèrent des événements de reconfiguration de cluster, les acheminent via le cluster et les transmettent aux clients intéressés.

Le démon cl_apid interagit avec les clients. Le gestionnaire de groupe de ressources (RGM, Resource Group Manager) de Sun Cluster génère des événements de reconfiguration de cluster. Ce démon utilise syseventd pour transmettre des événements à chaque noeud local. Le démon cl_apid communique avec les clients intéressés, via le protocole TCP/IP, en langage XML (Extensible Markup Language).

Le schéma suivant met en évidence le flux d'événements entre les composants CRNP. Un client est exécuté sur le noeud 2 du cluster tandis que l'autre fonctionne sur un ordinateur n'appartenant pas au cluster.

Figure 12–1 Flux d'événements entre les composants CRNP

Schéma illustrant le fonctionnement du protocole CRNP

Sémantique du protocole CRNP

Les clients débutent des communications en envoyant un message d'enregistrement (SC_CALLBACK_RG) au serveur. Ce message indique le type d'événements dont les clients souhaitent être avertis, ainsi que le port auquel ces événements peuvent être transmis. L'IP source de la connexion d'enregistrement et le port spécifié constituent l'adresse de rappel.

Chaque fois qu'un événement susceptible d'intéresser un client est généré au sein du cluster, le serveur contacte le client à son adresse de rappel (IP et port) et lui transmet l'événement (SC_EVENT). Le serveur est hautement disponible car il fonctionne sur le cluster lui-même. Il enregistre les connexions client en mémoire et en conserve la trace même après la réinitialisation du cluster.

Les clients annulent leur enregistrement en envoyant un message d'enregistrement (SC_CALLBACK_RG contenant un message REMOVE_CLIENT) au serveur. Lorsque ce dernier leur renvoie le message SC_REPLY, les clients ferment leur connexion.

Le schéma suivant met en évidence le flux de communications entre un client et un serveur.

Figure 12–2 Flux de communications entre un client et un serveur

Organigramme présentant le flux de communications entre un client et un serveur

Types de messages utilisés par le protocole CRNP

Le protocole CRNP utilise trois types de messages XML. Leur utilisation est décrite dans le tableau suivant. Ces trois types de messages sont également décrits plus en détail dans ce chapitre.

Type de message utilisé par le protocole CRNP 

Description 

SC_CALLBACK_REG

Ce message se présente sous quatre formes : ADD_CLIENT, REMOVE_CLIENT , ADD_EVENTS et REMOVE_EVENTS. Elles contiennent toutes les informations suivantes :

 

  • version du protocole ;

  • port de rappel au format ASCII (et non au format binaire).

ADD_CLIENT, ADD_EVENTS et REMOVE_EVENTS contiennent également une liste non bornée de types d'événements, comprenant chacun les informations suivantes :

 

  • classe de l'événement ;

  • sous-classe de l'événement (facultative) ;

  • liste des paires nom/valeurs (facultative).

La classe et la sous-classe d'événement définissent un “type d'événements” unique. La DTD (définition du type de document) qui permet de générer les classes de SC_CALLBACK_REG est SC_CALLBACK_REG. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP.

SC_REPLY

Ce message contient les informations suivantes : 

  • version du protocole ;

  • code d'erreur ;

  • message d'erreur.

La DTD qui permet de générer les classes de SC_REPLY est SC_REPLY. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP.

SC_EVENT

Ce message contient les informations suivantes : 

  • version du protocole ;

  • classe de l'événement ;

  • sous-classe de l'événement ;

  • fournisseur ;

  • éditeur ;

  • liste des paires nom/valeurs (0, 1 ou plusieurs structures de données de paires nom/valeurs) :

    • nom (chaîne de caractères) ;

    • valeur (chaîne de caractères ou tableau de chaînes de caractères).

Les valeurs d'un message SC_EVENT ne sont pas typées. La DTD qui permet de générer les classes de SC_EVENT est SC_EVENT. Elle est décrite plus en détail à l'Annexe F, Définitions de types de documents pour le protocole CRNP.

Processus d'enregistrement d'un client auprès du serveur

Cette section indique comment un administrateur de cluster configure le serveur, comment les clients sont identifiés, comment les informations sont envoyées via les couches application et session, ainsi que les conditions d'erreur.

Hypothèses sur la configuration du serveur par les administrateurs

L'administrateur du cluster doit configurer le serveur à l'aide d'une adresse IP à haute disponibilité (une adresse IP qui n'est pas liée à une machine en particulier du cluster) et d'un numéro de port. L'administrateur du cluster doit transmettre cette adresse réseau aux clients potentiels. Le protocole CRNP ne définit pas comment ce nom de serveur est rendu accessible aux clients. L'administrateur du cluster utilise un service d'attribution de noms, ce qui permet aux clients de trouver l'adresse réseau du serveur de manière dynamique, ou ajoute le nom du réseau à un fichier de configuration que le client doit lire. Le serveur fonctionne au sein du cluster comme un type de ressource de basculement.

Processus d'identification d'un client par le serveur

Chaque client est identifié de manière unique par son adresse de rappel, c'est-à-dire son adresse IP et son numéro de port. Le port est spécifié dans les messages SC_CALLBACK_REG et l'adresse IP obtenue à partir de la connexion d'enregistrement TCP. Le protocole CRNP considère que les messages SC_CALLBACK_REG suivants ayant la même adresse de rappel proviennent du même client, même si le port source de transmission des messages est différent.

Processus de transmission des messages SC_CALLBACK_REG entre un client et le serveur

Pour procéder à l'enregistrement, le client commence par ouvrir une connexion TCP sur l'adresse IP et le numéro de port du serveur. Une fois la connexion TCP établie et prête pour l'écriture, le client doit envoyer son message d'enregistrement. Il doit s'agir d'un message SC_CALLBACK_REG correctement formaté, qui ne doit pas être suivi ni précédé d'octets supplémentaires.

Une fois tous les octets émis, le client doit rester connecté pour recevoir une réponse du serveur. S'il ne formate pas le message correctement, le serveur n'enregistre pas le client et lui transmet un message d'erreur. Toutefois, si le client ferme la connexion (au niveau du socket) avant que le serveur n'ait envoyé sa réponse, ce dernier enregistre le client en suivant la procédure habituelle.

Les clients peuvent contacter le serveur à tout moment. Chaque fois qu'un client contacte le serveur, il doit envoyer un message SC_CALLBACK_REG. Si le serveur reçoit un message malformé, non fonctionnel ou non valide, il renvoie un message d'erreur au client.

Pour transmettre un message ADD_EVENTS, REMOVE_EVENTS ou REMOVE_CLIENT, un client doit d'abord envoyer un message ADD_CLIENT. De même, avant de transmettre un message REMOVE_CLIENT, il doit envoyer un message ADD_CLIENT.

Si un client envoie un message ADD_CLIENT alors qu'il est déjà enregistré, le serveur peut tolérer ce message. Il remplace l'enregistrement client antérieur par le nouvel enregistrement spécifié dans le second message ADD_CLIENT.

Mais en règle générale, un client s'enregistre une seule fois auprès du serveur par l'envoi d'un message ADD_CLIENT. Un client annule une seule fois son enregistrement en envoyant un message REMOVE_CLIENT au serveur. Le protocole CRNP offre une plus grande souplesse aux clients qui doivent modifier de façon dynamique leur liste de types d'événements.

Contenu d'un message SC_CALLBACK_REG

Chaque message ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS et REMOVE_EVENTS contient une liste d'événements. Le tableau ci-après présente les types d'événements que le protocole CRNP accepte, avec les paires nom/valeurs requises.

Si un client effectue l'une des actions suivantes, le serveur ignore ces messages sans vous en avertir :

Classe et sous-classe 

Paire nom/valeurs 

Description 

EC_Cluster

ESC_cluster_membership

Requise : aucune 

En option : aucune 

Enregistrement pour toutes les notifications d'événement de modification de l'appartenance au cluster (suppression ou ajout d'un noeud) 

EC_Cluster

ESC_cluster_rg_state

Une seule requise comme suit : 

rg_name

Type de valeurs : chaîne de caractères 

En option : aucune 

Enregistrement pour toutes les notifications d'événements de modification d'état du groupe de ressources name

EC_Cluster

ESC_cluster_r_state

Une seule requise comme suit : 

r_name

Type de valeurs : chaîne de caractères 

En option : aucune 

Enregistrement pour toutes les notifications d'événement de modification d'état de la ressource nom

EC_Cluster

Aucun 

Requise : aucune 

En option : aucune 

Enregistrement pour toutes les notifications d'événement de Sun Cluster 

Processus de réponse du client au serveur

Une fois l'enregistrement traité, le serveur qui a reçu la demande d'enregistrement envoie le message SC_REPLY sur la connexion TCP ouverte par le client. Le serveur ferme la connexion. Le client doit laisser la connexion TCP ouverte jusqu'à ce que le serveur lui transmette le message SC_REPLY.

Par exemple, le client :

  1. Ouvre une connexion TCP sur le serveur.

  2. Attend que la connexion devienne « inscriptible ».

  3. Envoie un message SC_CALLBACK_REG (contenant un message ADD_CLIENT).

  4. Attend un message SC_REPLY du serveur.

  5. Reçoit un message SC_REPLY du serveur.

  6. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  7. Ferme la connexion.

Un peu plus tard, le client :

  1. Ouvre une connexion TCP sur le serveur.

  2. Attend que la connexion devienne « inscriptible ».

  3. Envoie un message SC_CALLBACK_REG (contenant un message REMOVE_CLIENT).

  4. Attend un message SC_REPLY du serveur.

  5. Reçoit un message SC_REPLY du serveur.

  6. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  7. Ferme la connexion.

Chaque fois que le serveur reçoit un message SC_CALLBACK_REG d'un client, il envoie, sur la même connexion ouverte, un message SC_REPLY indiquant si l'opération a réussi ou échoué. DTD XML SC_REPLY contient la DTD XML d'un message SC_REPLY, ainsi que les éventuels messages d'erreur que ce message peut contenir.

Contenu d'un message SC_REPLY

Un message SC_REPLY indique si une opération a réussi ou a échoué. Il contient la version du message de protocole CRNP, un code d'état et un message d'état décrivant le code d'état plus en détail. Le tableau suivant présente les valeurs possibles du code d'état :

Code d'état 

Description 

OK

Le message a été traité avec succès. 

RETRY

La connexion du client a été rejetée par le serveur à cause d'une erreur temporaire. Le client doit faire une nouvelle tentative de connexion avec d'autres arguments. 

LOW_RESOURCE

Les ressources du cluster sont faibles, le client peut seulement réessayer ultérieurement. L'administrateur du cluster peut également en augmenter les ressources.  

SYSTEM_ERROR

Un incident grave s'est produit. Contactez l'administrateur du cluster.  

FAIL

Le refus d'une autorisation ou tout autre incident a provoqué l'échec de la connexion. 

MALFORMED

La requête XML est malformée et n'a pas pu être analysée. 

INVALID

La requête XML n'est pas valide, c'est-à-dire qu'elle n'est pas conforme aux spécifications XML.  

VERSION_TOO_HIGH

La version du message est trop élevée pour que le message soit traité avec succès.  

VERSION_TOO_LOW

La version du message est trop basse pour que le message soit traité avec succès.  

Processus de gestion des conditions d'erreur par un client

Dans des conditions normales, un client qui envoie un message SC_CALLBACK_REG reçoit une réponse précisant si l'enregistrement a réussi ou a échoué.

Toutefois, le serveur peut être confronté à une condition d'erreur pendant l'enregistrement, ce qui l'empêche de transmettre un message SC_REPLY au client. L'enregistrement a pu réussir avant l'erreur, échouer ou ne pas avoir été traité.

Étant donné que le serveur doit fonctionner en tant que serveur de basculement ou de haute disponibilité dans le cluster, cette condition d'erreur ne signifie pas que le service est interrompu. De fait, le serveur peut rapidement commencer à envoyer des événements au client nouvellement enregistré.

Pour pallier ces conditions, votre client doit effectuer les opérations suivantes :

Processus de notification des événements au client par le serveur

À mesure que les événements sont générés dans le cluster, le serveur CRNP les notifie à tous les clients qui ont demandé à être avertis de ce type d'événements. La notification se concrétise par l'envoi d'un message SC_EVENT à l'adresse de rappel des clients. Chaque notification d'événement utilise une nouvelle connexion TCP.

Aussitôt un client enregistré pour un type d'événement (à l'aide d'un message SC_CALLBACK_REG contenant un message ADD_CLIENT ou ADD_EVENT), le serveur lui transmet le dernier événement de ce type. Le client peut déterminer l'état actuel du système dont seront issus les événements ultérieurs.

Lorsque le serveur ouvre une connexion TCP sur le client, il envoie exactement un message SC_EVENT sur la connexion. Il procède ensuite à une fermeture bidirectionnelle.

Par exemple, le client :

  1. Attend que le serveur initie une connexion TCP.

  2. Accepte la connexion entrante du serveur.

  3. Attend un message SC_EVENT du serveur.

  4. Lit un message SC_EVENT du serveur.

  5. Reçoit un message stipulant que le serveur a fermé la connexion (lecture de 0 octet du socket).

  6. Ferme la connexion.

Une fois enregistrés, tous les clients doivent écouter leur adresse de rappel (adresse IP et numéro de port) en permanence afin de recevoir les éventuelles connexions entrantes de notification d'événement.

Si le serveur ne parvient pas à contacter le client pour lui notifier un événement, il réessaie suivant l'intervalle et le nombre de fois que vous avez spécifié. Si toutes les tentatives échouent, le client est supprimé de la liste des clients du serveur. Pour recevoir d'autres notifications d'événements, le client doit se ré-enregistrer en envoyant un autre message SC_CALLBACK_REG contenant un message ADD_CLIENT.

Mécanisme garantissant la notification des événements

Au sein du cluster, l'avertissement des clients suit l'ordre de génération des événements. En d'autres termes, si un événement A est généré au sein du cluster avant un événement B, le client X reçoit l'événement A avant l'événement B. Cependant, l'ordre d'envoi des notifications d'événements à tous les clients n'est pas conservé. Cela signifie que le client Y peut recevoir les événements A et B avant que le client X ne soit notifié de l'événement A. De cette façon, les clients dont la connexion est lente ne retardent pas la notification à tous les clients.

Tous les événements notifiés par le serveur (à l'exception du premier événement d'une sous-classe et des événements qui suivent des erreurs serveur) se produisent en réponse aux événements réels que génère le cluster, hormis lorsque le serveur est confronté à une erreur lui faisant ignorer les événements générés par le cluster. Le cas échéant, il génère un événement pour chaque type d'événements représentant l'état actuel du système. Chaque événement est transmis aux clients qui ont notifié leur souhait de recevoir ce type d'événements.

Chaque événement respecte la sémantique « au moins une fois », c'est-à-dire que le serveur peut envoyer la même notification d'événement à un client plus d'une fois. Cette autorisation est nécessaire lorsque le serveur s'arrête momentanément et qu'il est incapable de déterminer si le client a reçu les toutes dernières informations lorsqu'il se remet à fonctionner.

Contenu d'un message SC_EVENT

Le message SC_EVENT contient le message réel qui est généré au sein du cluster, retranscrit au format de message XML SC_EVENT. Le tableau ci-dessous présente les types d'événements que le protocole CRNP envoie, avec les paires nom/valeurs requises, l'éditeur et le fournisseur.


Remarque –

Les positions des éléments du tableau state_list sont synchronisées avec celles de node_list. Cela signifie que l'état du noeud répertorié en première position dans le tableau node_list se trouve en première position dans le tableau state_list.

Les autres noms commençant par ev_ et les valeurs connexes peuvent être présents, mais ne sont pas destinés à être utilisés par les clients.


Classe et sous-classe 

Éditeur et fournisseur 

Paire nom/valeurs 

EC_Cluster

ESC_cluster_membership

Éditeur : rgm 

Fournisseur : SUNW 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Le tableau state_list contient uniquement les numéros au format ASCII. Chaque numéro représente le numéro actuel du nœud dans le cluster. Si ce numéro correspond à celui reçu dans un message précédent, cela signifie que la relation qui lie le noeud au cluster n'a pas changé (disparu/connecté/déconnecté). Si ce numéro est -1, le noeud n'est pas un membre du cluster. Si ce numéro n'est pas un nombre négatif, le nœud est un membre du cluster.

Type de valeurs : tableau de chaînes de caractères 

EC_Cluster

ESC_cluster_rg_state

Éditeur : rgm 

Fournisseur : SUNW 

Nom : rg_name

Type de valeurs : chaîne de caractères 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Le tableau state_list contient les représentations (sous la forme de chaînes de caractères) de l'état du groupe de ressources. Les valeurs valides sont celles que vous pouvez extraire à l'aide des commandes scha_cmds(1HA).

Type de valeurs : tableau de chaînes de caractères 

EC_Cluster

ESC_cluster_r_state

Éditeur : rgm 

Fournisseur : SUNW 

Nom : r_name

Type de valeurs : chaîne de caractères 

Nom : node_list

Type de valeurs : tableau de chaînes de caractères 

Nom : state_list

Le tableau state_list contient les représentations (en chaînes de caractères) de l'état de la ressource. Les valeurs valides sont celles que vous pouvez extraire à l'aide des commandes scha_cmds(1HA).

Type de valeurs : tableau de chaînes de caractères 

Processus d'authentification des clients et du serveur par le protocole CRNP

Le serveur authentifie un client à l'aide d'une forme de wrappers TCP. L'adresse IP source du message d'enregistrement, qui sert également d'adresse IP de rappel à laquelle les notifications d'événements sont envoyées, doit figurer dans la liste des clients autorisés sur le serveur. En outre, cette adresse et ce message ne doivent pas figurer dans la liste des clients refusés. Si ces informations ne figurent pas dans la liste des clients autorisés, le serveur refuse la requête et renvoie un message d'erreur au client.

Lorsque le serveur reçoit un message SC_CALLBACK_REG ADD_CLIENT , les messages SC_CALLBACK_REG suivants envoyés par ce client doivent utiliser la même adresse IP source que le premier message. Si le serveur CRNP reçoit un message SC_CALLBACK_REG non conforme à cette spécification, il effectue l'une des opérations suivantes :

Ce mécanisme de sécurité permet de prévenir les attaques de refus de service, où une personne tente d'annuler l'enregistrement d'un client légitime.

Les clients doivent également authentifier le serveur de façon similaire. Les clients ne doivent accepter que les notifications d'événement d'un serveur dont l'adresse IP source et le numéro de port sont identiques à l'adresse IP d'enregistrement et au numéro de port qu'ils ont eux-mêmes utilisés.

Étant donné que les clients du service CRNP sont supposés se trouver à l'intérieur d'un pare-feu qui protège le cluster, le protocole CRNP ne comprend pas de mécanismes de sécurité supplémentaires

Exemple de création d'une application Java utilisant le protocole CRNP

L'exemple suivant montre comment développer une application Java simple nommée CrnpClient et qui utilise le protocole CRNP. Cette application enregistre le client, en vue de la réception de rappels d'événements, auprès du serveur CRNP sur le cluster ; elle écoute les rappels, puis traite les événements en imprimant leur contenu. Pour terminer, elle supprime les rappels d'événements.

Gardez ces informations à l'esprit lorsque vous étudiez cet exemple :

ProcedureConfiguration de l'environnement

Étapes
  1. Téléchargez puis installez JAXP ainsi que la version appropriée du compilateur Java et de la machine virtuelle.

    Vous trouverez des instructions à l'adresse http://java.sun.com/xml/jaxp/index.html.


    Remarque –

    Cet exemple requiert au minimum la version Java 1.3.1.


  2. Placez-vous dans le répertoire qui contient le fichier source et tapez les lignes suivantes :


    % javac -classpath jaxp-root/dom.jar:jaxp-rootjaxp-api. \
    jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \
    .jar:jaxp-root/xsltc.jar -sourcepath . source-filename.java
    

    jaxp-root est le chemin d'accès, absolu ou relatif, au répertoire contenant les fichiers jar de JAXP et source-filename est le nom de votre fichier source Java.

    Le fait de spécifier un classpath dans votre ligne de commande de compilation garantit que le programme de compilation trouvera les fichiers de classe JAXP.

  3. Lors de l'exécution de l'application, spécifiez le chemin classpath de manière à ce que l'application puisse charger les bons fichiers de classe JAXP (le premier chemin d'accès spécifié dans classpath correspond au répertoire actuel) :


    % java -cp .:jaxp-root/dom.jar:jaxp-rootjaxp-api. \
    jar:jaxp-rootsax.jar:jaxp-rootxalan.jar:jaxp-root/xercesImpl \
    .jar:jaxp-root/xsltc.jar source-filename arguments
    

    L'environnement est à présent configuré. Vous pouvez développer l'application.

ProcedurePremières étapes du développement de l'application

Dans cette partie de l'exemple, vous allez créer une classe de base appelée CrnpClient , avec une méthode principale qui analyse les arguments de ligne de commande et crée un objet CrnpClient. Cet objet transfère les arguments de la ligne de commande à la classe, attend que l'utilisateur arrête l'application, exécute la commande shutdown sur la classe CrnpClient, puis se déconnecte.

Le constructeur de la classe CrnpClient doit exécuter les tâches suivantes :

Étape

    Créez le code Java mettant en oeuvre la logique précédente.

    L'exemple suivant présente le code du squelette de la classe CrnpClient. Les mises en oeuvre des quatre méthodes d'assistant référencées dans les méthodes d'arrêt et du constructeur sont expliquées plus loin dans ce chapitre. Le code qui permet d'importer tous les packages requis est indiqué.

    import javax.xml.parsers.*;
    import javax.xml.transform.*;
    import javax.xml.transform.dom.*;
    import javax.xml.transform.stream.*;
    import org.xml.sax.*;
    import org.xml.sax.helpers.*;
    import org.w3c.dom.*;
    import java.net.*;
    import java.io.*;
    import java.util.*;
    
    class CrnpClient
    {
            public static void main(String []args)
            {
                    InetAddress regIp = null;
                    int regPort = 0, localPort = 0;
                    try {
                            regIp = InetAddress.getByName(args[0]);
                            regPort = (new Integer(args[1])).intValue();
                            localPort = (new Integer(args[2])).intValue();
                    } catch (UnknownHostException e) {
                            System.out.println(e);
                            System.exit(1);
                    }
                    CrnpClient client = new CrnpClient(regIp, regPort, 
                        localPort, args);
                    System.out.println("Hit return to terminate demo...");
                    try {
                            System.in.read();
                    } catch (IOException e) {
                            System.out.println(e.toString());
                    }
                    client.shutdown();
                    System.exit(0);
            }
    
            public CrnpClient(InetAddress regIpIn, int regPortIn, 
                int localPortIn, String []clArgs)
            {
                    try {
                            regIp = regIpIn;
                            regPort = regPortIn;
                            localPort = localPortIn;
                            regs = clArgs;
                            setupXmlProcessing();
                            createEvtRecepThr();
                            registerCallbacks();
                    } catch (Exception e) {
                            System.out.println(e.toString());
                            System.exit(1);
                    }
            }
    
            public void shutdown()
            {
                    try {
                            unregister();
                    } catch (Exception e) {
                            System.out.println(e);
                            System.exit(1);
                    }
            }
    
            private InetAddress regIp;
            private int regPort;
            private EventReceptionThread evtThr;
            private String regs[];
            public int localPort;
            public DocumentBuilderFactory dbf;
    }

    Les variables membres sont traitées plus en détail ultérieurement.

ProcedureAnalyse des arguments de ligne de commande

Étape

    Pour savoir comment analyser les arguments de ligne de commande, reportez-vous au code à l'Annexe G, Application CrnpClient.java .

ProcedureDéfinition d'un thread de réception d'événements

Vous devez vous assurer, au niveau du code, que les événements sont reçus sur un thread distinct, afin que votre application puisse continuer d'exécuter d'autres tâches tandis que le thread d'événements attend la notification de rappel d'événements.


Remarque –

La configuration XML est abordée plus loin dans ce chapitre.


Étapes
  1. Dans votre code, définissez une sous-classe Thread appelée EventReceptionThread qui crée une classe ServerSocket et attend que les événements arrivent sur le socket.

    Dans cette partie du code, les événements ne sont ni lus ni traités. La lecture et le traitement des événements sont abordés plus loin dans ce chapitre. La classe EventReceptionThread crée une classe ServerSocket sur une adresse générique de protocole inter-réseau. EventReceptionThread conserve également l'objet CrnpClient en référence afin de pouvoir lui transmettre des événements à traiter.

    class EventReceptionThread extends Thread
    {
            public EventReceptionThread(CrnpClient clientIn) throws IOException
            {
                    client = clientIn;
                    listeningSock = new ServerSocket(client.localPort, 50,
                        InetAddress.getLocalHost());
            }
    
            public void run()
            {
                    try {
                            DocumentBuilder db = client.dbf.newDocumentBuilder();
                            db.setErrorHandler(new DefaultHandler());
    
                            while(true) {
                                    Socket sock = listeningSock.accept();
                                    // Construct event from the sock stream and process it
                                    sock.close();
                            }
                            // UNREACHABLE
    
                    } catch (Exception e) {
                            System.out.println(e);
                            System.exit(1);
                    }
            }
    
            /* private member variables */
            private ServerSocket listeningSock;
            private CrnpClient client;
    }
  2. Créez un objet createEvtRecepThr.

    private void createEvtRecepThr() throws Exception
    {
            evtThr = new EventReceptionThread(this);
            evtThr.start();
    }

ProcedureEnregistrement et annulation de l'enregistrement aux rappels

L'enregistrement implique les opérations suivantes :

Étapes
  1. Créez le code Java mettant en oeuvre la logique précédente.

    L'exemple de code suivant présente la mise en oeuvre de la méthode registerCallbacks de la classe CrnpClient (qui est appelée par le constructeur CrnpClient). Les appels des fonctions createRegistrationString() et readRegistrationReply () sont décrits plus en détail ultérieurement.

    regIp et regPort sont des objets membres définis par le constructeur.

    private void registerCallbacks() throws Exception
    { 
            Socket sock = new Socket(regIp, regPort);
            String xmlStr = createRegistrationString();
            PrintStream ps = new 
                    PrintStream(sock.getOutputStream());
            ps.print(xmlStr);
            readRegistrationReply(sock.getInputStream();
            sock.close();
    }
  2. Mettez en œuvre la méthode unregister.

    Cette méthode est appelée par la méthode shutdown de la classe CrnpClient. La mise en oeuvre de createUnregistrationString est présentée plus en détail plus loin dans ce chapitre.

    private void unregister() throws Exception
    {
            Socket sock = new Socket(regIp, regPort);
            String xmlStr = createUnregistrationString();
            PrintStream ps = new PrintStream(sock.getOutputStream());
            ps.print(xmlStr);
            readRegistrationReply(sock.getInputStream());
            sock.close();
    }

ProcedureGénération du langage XML

Maintenant que vous avez défini la structure de l'application et que vous avez rédigé l'intégralité du code de mise en réseau, vous allez rédiger le code qui permet de générer et d'analyser le langage XML. Commencez par rédiger le code qui permet de générer le message de connexion XML SC_CALLBACK_REG.

Un message SC_CALLBACK_REG est constitué d'un type de connexion (ADD_CLIENT, REMOVE_CLIENT, ADD_EVENTS ou REMOVE_EVENTS), d'un port de rappel et d'une liste d'événements intéressants. Chaque événement comprend une classe et une sous-classe, suivies d'une liste de paires nom/valeurs.

Dans cette partie de l'exemple, vous rédigez une classe CallbackReg qui enregistre le type de connexion, le port de rappel et la liste des événements de connexion. Cette classe peut également se sérialiser en message XML SC_CALLBACK_REG.

Cette classe comprend la méthode convertToXml. Cette méthode intéressante crée une chaîne de message XML SC_CALLBACK_REG à partir des membres de la classe. La documentation JAXP disponible à l'adresse http://java.sun.com/xml/jaxp/index.html décrit le code de cette méthode plus en détail.

La mise en oeuvre de la classe Event est présentée dans l'exemple de code suivant. La classe CallbackReg utilise une classe Event qui mémorise un événement et peut le convertir en Element XML.

Étapes
  1. Créez le code Java mettant en oeuvre la logique précédente.

    class CallbackReg
    {
            public static final int ADD_CLIENT = 0;
            public static final int ADD_EVENTS = 1;
            public static final int REMOVE_EVENTS = 2;
            public static final int REMOVE_CLIENT = 3;
    
            public CallbackReg()
            {
                    port = null;
                    regType = null;
                    regEvents = new Vector();
            }
    
            public void setPort(String portIn)
            {
                    port = portIn;
            }
    
            public void setRegType(int regTypeIn)
            {
                    switch (regTypeIn) {
                    case ADD_CLIENT:
                            regType = "ADD_CLIENT";
                            break;
                    case ADD_EVENTS:
                            regType = "ADD_EVENTS";
                            break;
                    case REMOVE_CLIENT:
                            regType = "REMOVE_CLIENT";
                            break;
                    case REMOVE_EVENTS:
                            regType = "REMOVE_EVENTS";
                            break;
                    default:
                            System.out.println("Error, invalid regType " +
                                regTypeIn);
                            regType = "ADD_CLIENT";
                            break;
                    }
            }
    
            public void addRegEvent(Event regEvent)
            {
                    regEvents.add(regEvent);
            } 
    
            public String convertToXml()
            {
                    Document document = null;
                    DocumentBuilderFactory factory =
                        DocumentBuilderFactory.newInstance();
                    try {
                            DocumentBuilder builder = factory.newDocumentBuilder();
                            document = builder.newDocument(); 
                    } catch (ParserConfigurationException pce) {
                            // Parser with specified options can't be built
                            pce.printStackTrace();
                            System.exit(1);
                    }
    
                    // Create the root element
                    Element root = (Element) document.createElement("SC_CALLBACK_REG");
    
                    // Add the attributes
                    root.setAttribute("VERSION", "1.0");
                    root.setAttribute("PORT", port);
                    root.setAttribute("regType", regType);
    
                    // Add the events
                    for (int i = 0; i < regEvents.size(); i++) {
                            Event tempEvent = (Event)
                                (regEvents.elementAt(i));
                            root.appendChild(tempEvent.createXmlElement(document));
                    }
                    document.appendChild(root);
    
                    // Convert the whole thing to a string
                    DOMSource domSource = new DOMSource(document);
                    StringWriter strWrite = new StringWriter();
                    StreamResult streamResult = new StreamResult(strWrite);
                    TransformerFactory tf = TransformerFactory.newInstance();
                    try {
                            Transformer transformer = tf.newTransformer();
                            transformer.transform(domSource, streamResult);
                    } catch (TransformerException e) {
                            System.out.println(e.toString());
                            return ("");
                    }
                    return (strWrite.toString());
            }
    
            private String port;
            private String regType;
            private Vector regEvents;
    }
  2. Mettez en oeuvre les classes Event et NVPair.

    La classe CallbackReg utilise une classe Event, qui elle-même utilise une classe NVPair .

    class Event
    {
    
            public Event()
            {
                    regClass = regSubclass = null;
                    nvpairs = new Vector();
            }
    
            public void setClass(String classIn)
            {
                    regClass = classIn;
            }
    
            public void setSubclass(String subclassIn)
            {
                    regSubclass = subclassIn;
            }
    
            public void addNvpair(NVPair nvpair)
            {
                    nvpairs.add(nvpair);
            }
    
            public Element createXmlElement(Document doc)
            {
                    Element event = (Element)
                        doc.createElement("SC_EVENT_REG");
                    event.setAttribute("CLASS", regClass);
                    if (regSubclass != null) {
                            event.setAttribute("SUBCLASS", regSubclass);
                    }
                    for (int i = 0; i < nvpairs.size(); i++) {
                         NVPair tempNv = (NVPair)
                             (nvpairs.elementAt(i));
                         event.appendChild(tempNv.createXmlElement(doc));
                    }
                    return (event);
            }
    
            private String regClass, regSubclass;
            private Vector nvpairs;
    }
    
    class NVPair
    {
            public NVPair()
            {
                    name = value = null;
            }
    
            public void setName(String nameIn)
            {
                    name = nameIn;
            }
    
            public void setValue(String valueIn)
            {
                    value = valueIn;
            }
    
            public Element createXmlElement(Document doc)
            {
                    Element nvpair = (Element)
                        doc.createElement("NVPAIR");
                    Element eName = doc.createElement("NAME");
                    Node nameData = doc.createCDATASection(name);
                    eName.appendChild(nameData);
                    nvpair.appendChild(eName);
                    Element eValue = doc.createElement("VALUE");
                    Node valueData = doc.createCDATASection(value);
                    eValue.appendChild(valueData);
                    nvpair.appendChild(eValue);
    
                    return (nvpair);
            }
    
            private String name, value;
    }

ProcedureCréation des messages d'enregistrement et d'annulation de l'enregistrement

Maintenant que vous avez créé les classes d'assistant qui permettent de générer les messages XML, vous pouvez écrire la mise en oeuvre de la méthode createRegistrationString. Cette méthode est appelée par la méthode registerCallbacks, décrite à la section Enregistrement et annulation de l'enregistrement aux rappels.

createRegistrationString crée un objet CallbackReg et définit son type et son port de connexion. createRegistrationString crée ensuite plusieurs événements, à l'aide des méthodes d'assistant createAllEvent, createMembershipEvent, createRgEvent et createREvent . Chaque événement est ajouté à l'objet CallbackReg une fois cet objet créé. Enfin, createRegistrationString appelle la méthode convertToXml sur l'objet CallbackReg pour rechercher et extraire les messages XML au format String.

La variable membre regs enregistre les arguments de ligne de commande qu'un utilisateur fournit à l'application. Le cinquième argument et les arguments ultérieurs spécifient les événements pour lesquels l'application doit s'enregistrer. Le quatrième argument spécifie le type d'enregistrement, mais il est ignoré dans cet exemple. L'intégralité du code, que vous trouverez à l'Annexe G, Application CrnpClient.java explique comment utiliser ce quatrième argument.

Étapes
  1. Créez le code Java mettant en oeuvre la logique précédente.

    private String createRegistrationString() throws Exception
    {
            CallbackReg cbReg = new CallbackReg();
            cbReg.setPort("" + localPort);
    
            cbReg.setRegType(CallbackReg.ADD_CLIENT);
    
            // add the events
            for (int i = 4; i < regs.length; i++) {
                    if (regs[i].equals("M")) {
                            cbReg.addRegEvent(createMembershipEvent());
                    } else if (regs[i].equals("A")) {
                            cbReg.addRegEvent(createAllEvent());
                    } else if (regs[i].substring(0,2).equals("RG")) {
                            cbReg.addRegEvent(createRgEvent(regs[i].substring(3)));
                    } else if (regs[i].substring(0,1).equals("R")) {
                            cbReg.addRegEvent(createREvent(regs[i].substring(2))); 
                    }
            }
    
            String xmlStr = cbReg.convertToXml();
            return (xmlStr);
    }
    
    private Event createAllEvent()
    {
            Event allEvent = new Event();
            allEvent.setClass("EC_Cluster");
            return (allEvent);
    }
    
    private Event createMembershipEvent()
    {
            Event membershipEvent = new Event();
            membershipEvent.setClass("EC_Cluster");
            membershipEvent.setSubclass("ESC_cluster_membership");
            return (membershipEvent);
    }
    
    private Event createRgEvent(String rgname)
    {
            Event rgStateEvent = new Event();
            rgStateEvent.setClass("EC_Cluster");
            rgStateEvent.setSubclass("ESC_cluster_rg_state");
    
            NVPair rgNvpair = new NVPair();
            rgNvpair.setName("rg_name");
            rgNvpair.setValue(rgname);
            rgStateEvent.addNvpair(rgNvpair);
    
            return (rgStateEvent);
    }
    
    private Event createREvent(String rname)
    {
            Event rStateEvent = new Event();
            rStateEvent.setClass("EC_Cluster");
            rStateEvent.setSubclass("ESC_cluster_r_state");
    
            NVPair rNvpair = new NVPair();
            rNvpair.setName("r_name");
            rNvpair.setValue(rname);
            rStateEvent.addNvpair(rNvpair);
    
            return (rStateEvent);
    }
  2. Créez la chaîne de caractères d'annulation de l'enregistrement.

    Il est plus facile de créer une chaîne de caractères d'annulation d'enregistrement qu'une chaîne de caractères d'enregistrement, car vous n'avez pas à prendre en compte les événements.

    private String createUnregistrationString() throws Exception
    {
            CallbackReg cbReg = new CallbackReg();
            cbReg.setPort("" + localPort);
            cbReg.setRegType(CallbackReg.REMOVE_CLIENT);
            String xmlStr = cbReg.convertToXml();
            return (xmlStr);
    }

ProcedureConfiguration de l'analyseur XML

Vous avez créé le code de génération XML et de gestion de réseaux de l'application. Le constructeur CrnpClient appelle une méthode setupXmlProcessing . Celle-ci crée un objet DocumentBuilderFactory et définit plusieurs propriétés d'analyse sur cet objet. Cette méthode est décrite plus en détail dans la documentation JAXP. Voir http://java.sun.com/xml/jaxp/index.html.

Étape

    Créez le code Java mettant en oeuvre la logique précédente.

    private void setupXmlProcessing() throws Exception
    {
            dbf = DocumentBuilderFactory.newInstance();
    
            // We don't need to bother validating
            dbf.setValidating(false);
            dbf.setExpandEntityReferences(false);
    
            // We want to ignore comments and whitespace
            dbf.setIgnoringComments(true);
            dbf.setIgnoringElementContentWhitespace(true);
    
            // Coalesce CDATA sections into TEXT nodes.
            dbf.setCoalescing(true);
    }

ProcedureAnalyse de la réponse d'enregistrement

Pour pouvoir analyser le message XML SC_REPLY que le serveur CRNP envoie en réponse à un message d'enregistrement ou d'annulation d'un enregistrement, vous avez besoin d'une classe d'assistant RegReply . Vous pouvez créer cette classe à partir d'un document XML. Elle propose les mécanismes d'accès du code et du message d'état. Pour analyser le flux XML du serveur, vous devez créer un nouveau document XML et utiliser la méthode d'analyse de ce document. Cette méthode est décrite plus en détail dans la documentation JAXP disponible à l'adresse http://java.sun.com/xml/jaxp/index.html.

Étapes
  1. Créez le code Java mettant en oeuvre la logique précédente.

    La méthode readRegistrationReply utilise la nouvelle classe RegReply.

    private void readRegistrationReply(InputStream stream) throws Exception
    {
            // Create the document builder
            DocumentBuilder db = dbf.newDocumentBuilder();
            db.setErrorHandler(new DefaultHandler());
    
            //parse the input file
            Document doc = db.parse(stream);
    
            RegReply reply = new RegReply(doc);
            reply.print(System.out);
    }
  2. Mettez en oeuvre la classe RegReply.

    La méthode retrieveValues parcourt l'arborescence DOM dans le document XML et extrait le code et le message d'état. Pour en savoir plus, consultez la documentation JAXP à l'adresse http://java.sun.com/xml/jaxp/index.html.

    class RegReply
    {
            public RegReply(Document doc)
            {
                    retrieveValues(doc);
            }
    
            public String getStatusCode()
            {
                    return (statusCode);
            }
    
            public String getStatusMsg()
            {
                    return (statusMsg);
            }
            public void print(PrintStream out)
            {
                    out.println(statusCode + ": " +
                        (statusMsg != null ? statusMsg : ""));
            }
    
            private void retrieveValues(Document doc)
            {
                    Node n;
                    NodeList nl;
                    String nodeName;
    
                    // Find the SC_REPLY element.
                    nl = doc.getElementsByTagName("SC_REPLY");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "SC_REPLY node.");
                            return;
                    }
    
                    n = nl.item(0);
    
                    // Retrieve the value of the statusCode attribute
                    statusCode = ((Element)n).getAttribute("STATUS_CODE");
    
                    // Find the SC_STATUS_MSG element
                    nl = ((Element)n).getElementsByTagName("SC_STATUS_MSG");
                    if (nl.getLength() != 1) {
                            System.out.println("Error in parsing: can't find "
                                + "SC_STATUS_MSG node.");
                            return;
                    }
                    // Get the TEXT section, if there is one.
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                    // Not an error if there isn't one, so we just silently return.
                            return;
                    }
    
                    // Retrieve the value
                    statusMsg = n.getNodeValue();
            }
    
            private String statusCode;
            private String statusMsg;
    }

ProcedureAnalyse des événements de rappel

L'étape finale consiste à analyser et traiter les événements de rappels réels. Pour faciliter cette tâche, modifiez la classe Event que vous avez créée à la section Génération du langage XML, afin qu'elle puisse créer un Event à partir d'un document XML et un Element XML. Pour effectuer cette modification, vous aurez besoin d'un constructeur supplémentaire (exécutant un document XML), une méthode retrieveValues, de l'ajout de deux variables membres (vendor et publisher), de méthodes de mécanisme d'accès à tous les champs et d'une méthode d'impression.

Étapes
  1. Créez le code Java mettant en oeuvre la logique précédente.

    Ce code est identique à celui de la classe RegReply décrit à la section Analyse de la réponse d'enregistrement.

    public Event(Document doc)
            {
                    nvpairs = new Vector();
                    retrieveValues(doc);
            }
            public void print(PrintStream out)
            {
                    out.println("\tCLASS=" + regClass);
                    out.println("\tSUBCLASS=" + regSubclass);
                    out.println("\tVENDOR=" + vendor);
                    out.println("\tPUBLISHER=" + publisher);
                    for (int i = 0; i < nvpairs.size(); i++) {
                            NVPair tempNv = (NVPair)
                                (nvpairs.elementAt(i));
                            out.print("\t\t");
                            tempNv.print(out);
                    }
            }
    
            private void retrieveValues(Document doc)
            {
                    Node n;
                    NodeList nl;
                    String nodeName;
    
                    // Find the SC_EVENT element.
                    nl = doc.getElementsByTagName("SC_EVENT");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "SC_EVENT node.");
                       return;
                    }
    
                    n = nl.item(0);
    
                    //
                    // Retrieve the values of the CLASS, SUBCLASS,
                    // VENDOR and PUBLISHER attributes.
                    //
                    regClass = ((Element)n).getAttribute("CLASS");
                    regSubclass = ((Element)n).getAttribute("SUBCLASS");
                    publisher = ((Element)n).getAttribute("PUBLISHER");
                    vendor = ((Element)n).getAttribute("VENDOR");
    
                    // Retrieve all the nv pairs
                    for (Node child = n.getFirstChild(); child != null;
                        child = child.getNextSibling())
                    {
                           nvpairs.add(new NVPair((Element)child));
                    }
            }
    
            public String getRegClass()
            {
                    return (regClass);
            }
    
            public String getSubclass()
            {
                    return (regSubclass);
            }
    
            public String getVendor()
            {
                    return (vendor);
            }
    
            public String getPublisher()
            {
                    return (publisher);
            }
    
            public Vector getNvpairs()
            {
                    return (nvpairs);
            }
    
            private String vendor, publisher;
  2. Mettez en oeuvre les autres constructeurs et méthodes de la classe NVPair qui prennent en charge l'analyse XML.

    Les modifications apportées à la classe Event (cf Étape 1) doivent être identiques à celles apportées à la classe NVPair.

    public NVPair(Element elem)
            {
                    retrieveValues(elem);
            }
            public void print(PrintStream out)
            {
                    out.println("NAME=" + name + " VALUE=" + value);
            }
            private void retrieveValues(Element elem)
            {
                    Node n;
                    NodeList nl;
                    String nodeName;
    
                    // Find the NAME element
                    nl = elem.getElementsByTagName("NAME");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "NAME node.");
                       return;
                    }
                    // Get the TEXT section
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                       System.out.println("Error in parsing: can't find "
                           + "TEXT section.");
                       return;
                    }
    
                    // Retrieve the value
                    name = n.getNodeValue();
    
                    // Now get the value element
                    nl = elem.getElementsByTagName("VALUE");
                    if (nl.getLength() != 1) {
                       System.out.println("Error in parsing: can't find "
                           + "VALUE node.");
                       return;
                    }
                    // Get the TEXT section
                    n = nl.item(0).getFirstChild();
                    if (n == null || n.getNodeType() != Node.TEXT_NODE) {
                    System.out.println("Error in parsing: can't find "
                                + "TEXT section.");
                            return;
                    }
    
                    // Retrieve the value
                    value = n.getNodeValue();
                    }
    
            public String getName()
            {
                    return (name);
            }
    
            public String getValue()
            {
                    return (value);
            }
    }
  3. Mettez en oeuvre la boucle while dans la classe EventReceptionThread qui attend les rappels d'événements.

    La classe EventReceptionThread est présentée à la section Définition d'un thread de réception d'événements.

    while(true) {
                    Socket sock = listeningSock.accept();
                    Document doc = db.parse(sock.getInputStream());
                    Event event = new Event(doc);
                    client.processEvent(event);
                    sock.close();
            }

ProcedureExécution de l'application

Étapes
  1. Connectez-vous en tant que superutilisateur ou prenez un rôle équivalent.

  2. Exécutez votre application.


    # java CrnpClient crnpHost crnpPort localPort ...
    

    L'intégralité du code de l'application CrnpClient est disponible à l'Annexe G, Application CrnpClient.java .