Avertisseur lumineux pour Nagios : NagiosLightHorn

De Wiki de Romain RUDIGER
Aller à : navigation, rechercher
Nagios RGB horn

Vous êtes fatigué de regarder vos mails, de payer un forfait pour recevoir des SMS ou encore de rafraichir une page HTML pour voir l'état de votre plateforme ? NagiosLightHorn est fait pour vous ! NagiosLightHorn est un avertisseur lumineux qui se connecte sur votre poste de travail ou encore directement sur votre serveur de supervision via un câble de moins de 9000 mètres (d'après Texas Instruments) et qui vous informe de l'état de vos services informatique en temps réel.

Cet avertisseur est le fruit d'un sous-projet réalisé lors de mon stage chez Silicomp-AQL, une filiale d'Orange Business Services.

Participant : Romain RÜDIGER.

Période : 27/04/09 - 05/05/09.

Le fonctionnement

NagiosLightHorn permet d'avoir un avertisseur lumineux qui donne en un coup d'œil l'état de la plateforme. Cet avertisseur à très bas coût (moins de 10€ en achetant tous les composants) est idéal pour une plateforme isolée de tout réseau de communication.

Diagramme de fonctionnement logique de NagiosLightHorn.

Le fonctionnement est simple, Nagios maintient à jour un fichier status.dat qui contient l'état en temps réel de tous les équipements et de leurs services. Un programme, écrit en Java par manque de temps, mais facilement réalisable en tout autre langage de programmation, permet de lire ce fichier toutes les 20 secondes environ. Le programme met à jour l'état de l'avertisseur en fonction de l'état des équipements et des services :

  • Aucun problème : la lumière bleue clignote lentement.
  • Peu de problèmes : la lumière rouge clignote lentement.
  • Beaucoup de problèmes : la lumière rouge clignote rapidement.

L'avertisseur en lui-même est composé d'un micro-contrôleur de la marque Microchip (PIC12F675) programmé avec le code du projet Simple serial RGB LED controller and driver du site Picprojects.

La maquette de test

Voici la maquette de test :

Maquette de test de NagiosLightHorn.

Cette maquette fonctionne parfaitement, elle a permis de tester et valider le programme avant d'aller plus loin dans ce projet.

La version définitive

Le circuit imprimé fait maison de NagiosLightHorn.
Le circuit imprimé une fois gravé de NagiosLightHorn (Avec deux pistes en moins...).
Le circuit terminé vue de dessous.
Le circuit terminé vue de dessus.
Le cordon de connexion de NagiosLightHorn, l'usb pour le 5V et un câble console Cisco pour le port série le tout sur un câble Ethernet pour se brancher sur la baie de brassage.

Le programme

Le programme du projet NagiosLightHorn est écrit en Java et utilise la bibliothèque RXTX.

Téléchargement

Voici le programme : Media:NagiosLightHorn.zip.

Utilisation

Pour utiliser ce programme, il vous faut :

  • un interpréteur Java ;
  • la bibliothèque RXTX, voir le site captain.at pour l'installation sous Linux et Windows ;
  • un accès au fichier status.dat de Nagios ;
  • un port série de libre.

Lancez le programme avec l'argument help pour visualiser les options d'exécution :

$java -jar nagiosLightHorn.jar help
Usage :
java NagiosLightHorn [full_path_to_nagios_status.dat_file serial_port_name] [verbose]
By default status.dat location is: /usr/local/nagios/var/status.dat
By default serial_port_name is: /dev/ttyS0

This program require rxtx library.

Si vous lancez ce programme sur le serveur de supervision et que vous avez utilisé les options d'installation de Nagios par défaut, vous pouvez lancer ce programme sans argument :

$ java -jar nagiosLightHorn.jar
INFO - Tue May 05 17:01:23 CEST 2009 - NagiosLightHorn start...
Experimental:  JNI_OnLoad called.
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version   = RXTX-2.1-7
RXTX Warning:  Removing stale lock file. /var/lock/LCK..ttyS0
Connected on the port: /dev/ttyS0
INFO - Tue May 05 17:01:23 CEST 2009 - NagiosLightHorn started.

Pour spécifier l'emplacement du fichier status.dat ainsi que le port série à utiliser, il faut utiliser sous Windows :

$java -jar nagiosLightHorn.jar p:\status.dat COM1
5 mai 2009 16:47:33 NagiosLightHorn main
INFO: NagiosLightHorn start...
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version   = RXTX-2.1-7
Connected on the port: COM1
5 mai 2009 16:47:34 NagiosLightHorn <init>
INFO - Tue May 05 16:47:34 CEST 2009 - NagiosLightHorn started.

Pour obtenir le détail de l'activité du programme, ajoutez l'option verbose, ici sous Linux :

$ java -jar nagiosLightHorn.jar verbose
INFO - Tue May 05 17:02:32 CEST 2009 - NagiosLightHorn start in verbose mode...
Experimental:  JNI_OnLoad called.
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version   = RXTX-2.1-7
RXTX Warning:  Removing stale lock file. /var/lock/LCK..ttyS0
Connected on the port: /dev/ttyS0
INFO - Tue May 05 17:02:32 CEST 2009 - NagiosLightHorn started.
FINEST - Tue May 05 17:02:32 CEST 2009 - Services state: ok=118 warn=0 crit=0 Hosts state: ok=19 warn=0 crit=0.
FINEST - Tue May 05 17:02:32 CEST 2009 - redRate = -1(Lum=255) - blueRate = 4(Lum=255)

Code source

Pour le programme du PIC, merci de voir le projet dont je me suis servi et ses instructions : Simple serial RGB LED controller and driver.

Voici le code source de l'application qui pilote le circuit électronique  :

/*
 * You are free to Share, to copy, to adapt, distribute and transmit this work under the same or similar license to this one.
 * You may not use this work for commercial purposes.
 * You must attribute this work to Romain RÜDIGER <romain.rudiger at novalan.fr> (http://romain.novalan.fr/).
 * This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 2.0 France License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.0/fr/.
 */

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * This program is use to show the status of nagios on lights.
 * Slow blue flashing if all is right else red from slow to very fast flashing if one or more problems.
 *
 * Copyright  : novalan.fr
 *
 * @author Romain RÜDIGER <romain.rudiger at novalan.fr>
 */
public class NagiosLightHorn {

    private final static Logger logger = Logger.getLogger(NagiosLightHorn.class.getName());
    private OutputStream outputStream;
    private int hostCritCount;
    private int hostWarnCount;
    private int hostOkCount;
    private int serviceCritCount;
    private int serviceWarnCount;
    private int serviceOkCount;
    private int red = 0;
    private int green = 0;
    private int blue = 0;

    /**
     * Contructor with serial port and status.dat file
     * @param serialPortName the serial port name
     * @param statusDatFile the status file (full path)
     */
    public NagiosLightHorn(String serialPortName, String statusDatFile) {
        //initialize the port and create the output stream
        outputStream = this.initializeSerialPort(serialPortName);
        if (outputStream == null) {
            logger.severe("Can't open the outputStream on the port " + serialPortName + ".");
        } else {
            logger.info("NagiosLightHorn started.");
            while (true) {
                parseStatusData(statusDatFile);
                logger.finest("Services state: ok=" + serviceOkCount + " warn=" + serviceWarnCount + " crit=" + serviceCritCount + " Hosts state: ok=" + hostOkCount + " warn=" + hostWarnCount + " crit=" + hostCritCount + ".");
                //compute lights pulse rate
                int total = serviceOkCount + serviceWarnCount + serviceCritCount + hostOkCount + hostWarnCount + hostCritCount;
                int redRate = -1;
                int redMaxLum = 255;
                int blueRate = -1;
                int blueMaxLum = 0;
                if ((serviceCritCount + hostCritCount + serviceWarnCount + hostWarnCount) > 0) {
                    redRate = ((serviceCritCount + hostCritCount + serviceWarnCount + hostWarnCount) * 250) / total; //the redRate if the percentage of crit or warn object * 2.5
                } else {
                    blueRate = 4;
                    blueMaxLum = (int) ((((((float) hostOkCount + (float) serviceOkCount) * (float) 100) / (float) total) / (float) 100) * (float) 255);
                }
                logger.finest("redRate = " + redRate + "(Lum=" + redMaxLum + ") - blueRate = " + blueRate + "(Lum=" + blueMaxLum + ")");
                setLightsStatus(redRate, -1, blueRate, redMaxLum, -1, blueMaxLum);
            }
        }
    }

    /**
     * This method control the lights with the specified value for a time
     * @param red light pulse rate (0(slow) to infinite(very fast) or -1 = off)
     * @param green light pulse rate (0(slow) to infinite(very fast) or -1 = off)
     * @param blue light pulse rate (0(slow) to infinite(very fast) or -1 = off)
     */
    public void setLightsStatus(int redRate, int greenRate, int blueRate, int redMaxLum, int greenMaxLum, int blueMaxLum) {
        try {
            //internal parameters
            int timeDuration = 10; //the duration of the status in seconds
            int refreshRate = 10; //the loop wait in millis
            //!internal parameters
            int stop = (timeDuration * 1000) / refreshRate; //number of loop
            int nbrOfLoop = 0;
            int start = 129; //the start code of a trame
            int checksum;
            if (redRate == -1) {
                red = 0;
            }
            if (greenRate == -1) {
                green = 0;
            }
            if (blueRate == -1) {
                blue = 0;
            }
            boolean redGrow = true;
            boolean greenGrow = true;
            boolean blueGrow = true;
            while (nbrOfLoop < stop) {
                if (redGrow) {
                    red = red + (1 + redRate);
                } else {
                    red = red - (1 + redRate);
                }
                if (greenGrow) {
                    green = green + (1 + greenRate);
                } else {
                    green = green - (1 + greenRate);
                }
                if (blueGrow) {
                    blue = blue + (1 + blueRate);
                } else {
                    blue = blue - (1 + blueRate);
                }
                if (red >= redMaxLum) {
                    red = redMaxLum;
                    redGrow = false;
                }
                if (green >= greenMaxLum) {
                    green = greenMaxLum;
                    greenGrow = false;
                }
                if (blue >= blueMaxLum) {
                    blue = blueMaxLum;
                    blueGrow = false;
                }
                if (red <= 0) {
                    red = 0;
                    redGrow = true;
                }
                if (green <= 0) {
                    green = 0;
                    greenGrow = true;
                }
                if (blue <= 0) {
                    blue = 0;
                    blueGrow = true;
                }
                //System.out.println("RGB = " + red + " " + " " + green + " " + blue);
                //compute the checksum
                checksum = (((start + red + green + blue) ^ 255) + 1) & 255;
                outputStream.write(start);
                outputStream.write(red);
                outputStream.write(green);
                outputStream.write(blue);
                outputStream.write(checksum);
                outputStream.flush();
                Thread.sleep(refreshRate);
                nbrOfLoop++;
            }
        } catch (InterruptedException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    /**
     * This method parse the status.dat nagios file to get the status of the hosts and services
     * It will update these vars:
     * <ul>
     * <li>hostCritCount</li>
     * <li>hostWarnCount</li>
     * <li>hostOkCount</li>
     * <li>serviceCritCount</li>
     * <li>serviceWarnCount</li>
     * <li>serviceOkCount</li>
     * </ul>
     * 
     * @param fileName
     */
    public void parseStatusData(String fileName) {
        try {
            FileInputStream fstream = new FileInputStream(fileName);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine;

            //fields to check in nagios status.dat
            String serviceStatus = "^.*servicestatus.*";
            String currstate = "^.*current_state=.*";
            String ackcheck = "^.*been_acknowledged=.*";
            String discheck = "^.*active_checks_enabled=.*";

            //reinit counters
            hostCritCount = 0;
            hostWarnCount = 0;
            hostOkCount = 0;
            serviceCritCount = 0;
            serviceWarnCount = 0;
            serviceOkCount = 0;

            String current_state = "";
            String active_checks_enabled = "";
            String acknowledged = "";
            boolean isAServiceStatus = false;
            boolean serviceStatusReadComplete = false;
            while ((strLine = br.readLine()) != null) {
                //servicestatus
                if (Pattern.matches(serviceStatus, strLine)) {
                    isAServiceStatus = true;
                }
                //current_state
                if (Pattern.matches(currstate, strLine)) {
                    current_state = strLine.substring(strLine.indexOf("=") + 1, strLine.length());
                }
                if (isAServiceStatus) {
                    //active_checks_enabled
                    if (Pattern.matches(discheck, strLine)) {
                        active_checks_enabled = strLine.substring(strLine.indexOf("=") + 1, strLine.length());
                    }
                    //acknowledged
                    if (Pattern.matches(ackcheck, strLine)) {
                        acknowledged = strLine.substring(strLine.indexOf("=") + 1, strLine.length());
                        serviceStatusReadComplete = true;
                    }
                } else {
                    //acknowledged
                    if (Pattern.matches(ackcheck, strLine)) {
                        acknowledged = strLine.substring(strLine.indexOf("=") + 1, strLine.length());
                    }
                    //active_checks_enabled
                    if (Pattern.matches(discheck, strLine)) { //1=Enable 0=Disable
                        active_checks_enabled = strLine.substring(strLine.indexOf("=") + 1, strLine.length());
                        serviceStatusReadComplete = true;
                    }
                }

                if (serviceStatusReadComplete) {
                    //if its a service status
                    if (isAServiceStatus) {
                        //if this service is active
                        if (active_checks_enabled.equals("1")) {
                            //test current_state (0=OK 1=WARNING 2=CRITICAL)
                            if (current_state.equals("0")) {
                                serviceOkCount++;
                            } else if (current_state.equals("1")) {
                                //test acknowledged (1=Enable 0=Disable)
                                if (acknowledged.equals("0")) {
                                    serviceWarnCount++;
                                }
                            } else if (current_state.equals("2")) {
                                //test acknowledged (1=Enable 0=Disable)
                                if (acknowledged.equals("0")) {
                                    serviceCritCount++;
                                }
                            }
                        }
                    } else {//if its a host status
                        //if this service is active
                        if (active_checks_enabled.equals("1")) {
                            //test current_state (0=OK 1=WARNING 2=CRITICAL)
                            if (current_state.equals("0")) {
                                hostOkCount++;
                            } else if (current_state.equals("1")) {
                                //test acknowledged (1=Enable 0=Disable)
                                if (acknowledged.equals("0")) {
                                    hostWarnCount++;
                                }
                            } else if (current_state.equals("2")) {
                                //test acknowledged (1=Enable 0=Disable)
                                if (acknowledged.equals("0")) {
                                    hostCritCount++;
                                }
                            }
                        }
                    }
                    isAServiceStatus = false;
                    serviceStatusReadComplete = false;
                }
            }
            //Close the input stream
            in.close();
        } catch (Exception e) {//Catch exception if any
            logger.severe("Error while parsing status.dat file: " + e.getMessage());
        }
    }

    /**
     * This is the main method to initialize and launch the program.
     * 
     * @param args the command line arguments are [full_path_to_nagios_status.dat_file serial_port_name] [verbose]
     */
    public static void main(String[] args) {
        if (args.length > 3 || (args.length == 1 && args[0].equalsIgnoreCase("help"))) {
            System.out.println("Usage : ");
            System.out.println("java NagiosLightHorn [full_path_to_nagios_status.dat_file serial_port_name] [verbose]");
            System.out.println("By default status.dat location is: /usr/local/nagios/var/status.dat");
            System.out.println("By default serial_port_name is: /dev/ttyS0");
            System.out.println("");
            System.out.println("This program require rxtx library.");
        } else {
            Logger.getLogger("").getHandlers()[0].setFormatter(new CustomFormatter());
            if ((args.length == 1 && args[0].equalsIgnoreCase("verbose")) || (args.length == 3 && args[2].equalsIgnoreCase("verbose"))) {
                logger.setLevel(Level.FINEST);
                Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL);
                logger.info("NagiosLightHorn start in verbose mode...");
            } else {
                logger.info("NagiosLightHorn start...");
            }
            String statusDatFile;
            String serialPortName;
            if (args.length >= 2) {
                statusDatFile = args[0];
                serialPortName = args[1];
                logger.fine("Status.dat file = " + statusDatFile);
                logger.fine("Serial Port Name = " + serialPortName);
            } else {
                statusDatFile = "/usr/local/nagios/var/status.dat";
                serialPortName = "/dev/ttyS0";
            }
            //Run the program
            NagiosLightHorn nagiosRGB = new NagiosLightHorn(serialPortName, statusDatFile);
        }
    }

    /**
     * Initialize the com port
     * @param serialPortName
     * @return
     */
    public OutputStream initializeSerialPort(String serialPortName) {
        try {
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName);
            System.out.println("Connected on the port: " + serialPortName);
            SerialPort serialPort = (SerialPort) portIdentifier.open("Main", 2000);
            serialPort.setSerialPortParams(2400, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
            return serialPort.getOutputStream();
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (UnsupportedCommOperationException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (PortInUseException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (NoSuchPortException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (UnsatisfiedLinkError ule) {
            logger.severe("This program require rxtx library. (" + ule.getMessage() + ").");
        }
        return null;
    }

    /**
     * CustomFormatter is a Logger formatter
     * Code by "Anghel Leonard" from http://www.devx.com/tips/Tip/41156.
     */
    static class CustomFormatter extends java.util.logging.Formatter {

        public String format(LogRecord log) {
            Date date = new Date(log.getMillis());
            String level = log.getLevel().getName();
            String logmessage = level + " - " + date.toString() + " - " + log.getMessage() + "\r\n";
            Throwable thrown = log.getThrown();
            if (thrown != null) {
                logmessage = logmessage + thrown.toString();
            }
            return logmessage;
        }
    }
}

Le script d'initialisation

Fonctionnement

Ce script permet de lancer automatiquement nagiosLightHorn avec le serveur. Ce script a été testé sur RHEL 5.

L'archive Java (jar) doit être dans le dossier ci-dessous, si ce n'est pas le cas, changez la variable DAEMON.

/usr/local/nagios/bin/nagiosLightHorn.jar

Il suffit de mettre le code ci-dessous dans un fichier nommé :

/etc/init.d/nagiosLightHorn

Ensuite il faut le déclarer pour qu'il soit exécuté au démarrage du serveur :

chkconfig --add nagiosLightHorn

Le script offre quatre fonctions :

  • start : pour lancer le programme
  • stop : pour arrêter le programme
  • restart : pour arrêter puis lancer le programme
  • status : pour voir si le programme est lancé ou non

Code du script

#! /bin/sh 
#
#
# AUTHOR: Romain Rüdiger <romain.rudiger@novalan.fr>
#
# chkconfig: 345 99 01
# description: Nagios serial light horn
# processname: nagiosLightHorn

DAEMON=/usr/local/nagios/bin/nagiosLightHorn.jar
NAME=nagiosLightHorn

getpid()
{
	ps -eo "pid comm args" | awk '$5 ~ /nagiosLightHorn.jar/ {print $1}'
}

status()
{
	PID=`getpid`
	if [ "x$PID" != "x" ]; then
		echo "NagiosLightHorn is running as process $PID"
		return 0
	else
		echo "NagiosLightHorn is not running"
		return 1
	fi
}

killproc()
{
	PID=`getpid`
	kill $PID 2>/dev/null
}

set -e

case "$1" in
  status)
	status
	;;

  start)
	if status > /dev/null ; then
		echo "NagiosLightHorn is already running"
		exit 0
	fi
	
	java -jar $DAEMON &

	if status > /dev/null ; then
		echo "NagiosLightHorn started"
	fi
	;;

  stop)
	if status > /dev/null ; then
		killproc
		status > /dev/null
		if [ ! $? -ne 0 ]; then
			echo "NagiosLightHorn stopped"
		fi
	else
		echo "NagiosLightHorn is not running"
	fi
	;;

  restart)
	$0 stop
	sleep 1
	$0 start
	;;

  *)
	echo "Usage: $N {start|stop|restart|status}" 
	exit 1
	;;
esac

exit 0