Auffangen, Einreihen, Festschreiben – mit Camel und ActiveMQ


Quelle : http://upload.wikimedia.org/

Ab und zu meldet mal eine Netzkomponente (der Router, der Switch oder ein Service), dass etwas nicht stimmt. Methode der Wahl ist noch immer das Simple Network Management Protokoll SNMP. In diesem Fall nicht über den üblichen Request/Response-Weg sondern über einseitige Nachrichten (Notifications) sogenannte Traps oder Reports. So einen Trap-Dummy kann man leicht mit SNMP4j realisieren :

package eu.christophburmeister.testclient;

import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class SnmpV2TrapSender {

    public static final String community = "public";

    private static final String snmpTrapValue = ".1.3.6.1.4.1.42.1";

    private static final OID identifierOid = new OID(".1.3.6.1.4.1.42.1.1");
    private static final Variable identifierValue = new OctetString("42");

    private static final OID whatHappenedOid = new OID(".1.3.6.1.4.1.42.1.2");
    private static final Variable whatHappenedValue = new OctetString("Alarm");

    //IP of Receiving Host
    public static final String ipAddress = "127.0.0.1";

    // traps are sent on port 162
    public static final int port = 162;

    public static void main(String[] args) throws InterruptedException {
        SnmpV2TrapSender trapV2 = new SnmpV2TrapSender();
        trapV2.sendTrap_Version2();
    }

    public void sendTrap_Version2() throws InterruptedException {

        for (int i = 0; i < 5; i++) {

            try {
                TransportMapping transport = new DefaultUdpTransportMapping();
                transport.listen();

                // Create Target
                CommunityTarget cTarget = new CommunityTarget();
                cTarget.setCommunity(new OctetString(community));
                cTarget.setVersion(SnmpConstants.version2c);
                cTarget.setAddress(new UdpAddress(ipAddress + "/" + port));
                cTarget.setRetries(2);
                cTarget.setTimeout(5000);

                // Create PDU for V2
                PDU pdu = new PDU();

                // here come the variable-bindings
                pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, new OID(snmpTrapValue)));
                pdu.add(new VariableBinding(SnmpConstants.sysUpTime, new OctetString(String.valueOf(System.currentTimeMillis()))));
                pdu.add(new VariableBinding(identifierOid, identifierValue));
                pdu.add(new VariableBinding(whatHappenedOid, whatHappenedValue));

                // this is a trap!
                pdu.setType(PDU.NOTIFICATION);

                // Send the PDU
                Snmp snmp = new Snmp(transport);
                System.out.println("Sending V2 Trap");

                snmp.send(pdu, cTarget);
                snmp.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Thread.sleep(20);
        }
    }
}

Damit hätten wir zumindest einen Simulator. Jetzt geht es ans Eingemachte: Ziel ist es, eingehende Traps umgehend zu persistieren (in diesem Fall einfach in eine JMS-Queue) und anschließend so zu verarbeiten, dass sie aus der JMS-Queue auf Festplatte gespeichert werden. Für die JMS-Queue wird einfach die Standard-Installation 5.4.3 von ApacheMQ verwendet. Die Routing-Aufgabe wird an Apache Camel (Version 2.5) abgegeben. Da beide Produkte von der ASF stammen wird ein reibungsloses Zusammenarbeiten erwartet 😉

Die ActiveMQ-Instanz ist schnell heruntergeladen und gestartet. Praktischerweise steht nach dem Starten von activemq.bat eine hübsche Gui zur Verfügung : http://127.0.0.1:8161/admin/, mit der man die Queues (sowie Topics und Subscriber) überwachen kann.

Camel ist ein EIP-Routing-Framework und das eigentliche Herzstück der Enterprise Integration. In diesem ganz primitiven Beispiel arbeiten wir direkt aus Eclipse heraus und binden die Camel-Artifacts über Maven ein. Die Pom sieht so aus :


	4.0.0
	eu.christophburmeister.camel
	snmprouter
	0.0.1-SNAPSHOT
	jar

	
		2.5.0
	

	
		
			org.apache.camel
			camel-core
			${camel.version}
		
		
			org.apache.camel
			camel-snmp
			${camel.version}
		
		
			org.apache.camel
			camel-jms
			${camel.version}
		
		
			org.apache.activemq
			activemq-camel
			5.2.0
		
		
			commons-logging
			commons-logging
			1.1.1
		
		
			log4j
			log4j
			1.2.14
		
	

	
		snmpReceiver
		
			
				org.apache.maven.plugins
				maven-compiler-plugin
				
					1.6
					1.6
				
			
			
				org.apache.maven.plugins
				maven-assembly-plugin
				
					
						jar-with-dependencies
					
					
						
							
							eu.christophburmeister.camel.snmprouter.core.Start
						
					
				
				
					
						make-assembly
						
						package
						
							single
						
					
				
			
		
	


In der Start-Klasse wird der Camel-Kontext initiiert und die JMS-Anbindung konfiguriert. Sie sieht folgendermaßen aus:

package eu.christophburmeister.camel.snmprouter.core;

import javax.jms.ConnectionFactory;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.CamelContext;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.impl.DefaultCamelContext;

public class Start {
    
    // where is the queue?
    private final static String JMS_BROKER_URL = "tcp://127.0.0.1:61616";    

    public static void main(String[] args) throws Exception {
        // a connection-factory for the activemq-connection
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(JMS_BROKER_URL);
        
        // creating a new camel-context
        CamelContext camelContext = new DefaultCamelContext();
        // instanciation of the required routes
        SnmpViaJmsToFileRoute snmpTrapRoute = new SnmpViaJmsToFileRoute();
        
        // adding all the components and routes to the context
        camelContext.addComponent("jms", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
        camelContext.addRoutes(snmpTrapRoute);
        
        // start and wait for actions at the endpoints of camel
        camelContext.start();     
        
        while(true){
            // just a server-wait!
        }
    }
}

Und jetzt zum Herzstück des Routers : den Routen. Die können entweder per Spring angelegt werden oder per Camel-DSL. In diesem Fall nehme ich einfach die angenehmere DSL, die dank Auto-Complete auch problemlos geschrieben werden kann. Für die zu erstellenden Routen wird dier RouteBuilder abgeleitet:

package eu.christophburmeister.camel.snmprouter.core;

import org.apache.camel.builder.RouteBuilder;

public class SnmpViaJmsToFileRoute extends RouteBuilder {

    public SnmpViaJmsToFileRoute() {
    }

    public void configure() throws Exception {

        // route#1 : SNMP-Trap -> JMS-Queue
        //    #1 waits for snmp-traps, 
        //    #2 converts their bodies to String-Class, 
        //    #3 sets the "JMSCorrelationID"-header-attribute to a value, 
        //         that is found by xpath (in this case the sysUpTime-oid)
        //    #4 logs some debugging-stuff into the logger
        //    #5 puts all into the jms-queue 
        String origin = "snmp:127.0.0.1:162?protocol=udp&type=TRAP";
        String jmsQueue = "jms:queue:incomingSnmpTraps";

        from(origin)
                .convertBodyTo(String.class)
                .setHeader("JMSCorrelationID", xpath("//entry[oid='1.3.6.1.2.1.1.3.0']/value/text()"))
                .to("log:com.mycompany.order?level=DEBUG")
                .to(jmsQueue);

        
        
        // route#2 : JMS-Queue -> File-System
        String destinationFile = "file://d:/tmp/?fileName=${in.header.JMSCorrelationID}.xml";

        from(jmsQueue)
                 .to("log:com.mycompany.order?level=DEBUG")
                 .to(destinationFile);
    }
}

Die log4j.properties sieht dann so aus :

log4j.rootLogger=DEBUG, file, console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=d:/camel.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

Wenn man die zweite Route (das Schreiben der JMS-Messages als File) auskommentiert und den SNMP-Client anstartet, kann man in der WebConsole von ApacheMQ sehen, dass alle eingehenden SNMP-Traps vom Camel in die ActiveMQ-Queue "incomingSnmpTraps" geroutet werden. Und wenn dann nix mit ihnen passiert, bleiben sie da auch 🙂

Über die Gui von ActiveMQ bekommt man außerdem den vom SNMP-Adapter konvertierten XML-String des SNMP-Traps:


    
      1.3.6.1.6.3.1.1.4.1.0
      1.3.6.1.4.1.42.1
   
   
      1.3.6.1.2.1.1.3.0
      1301683105434
   
   
      1.3.6.1.4.1.42.1.1
      42
   
   
      1.3.6.1.4.1.42.1.2
      Alarm
   

Außerdem kann man hier auch erkennen, dass die JMS-Correlation-Id gemäß der SysUp-Time (OID "1.3.6.1.2.1.1.3.0") richtig von xpath ausgelesen und gesetzt wurde.

und wenn man die auskommentierte 2. Route wieder aktiviert, werden die JMS-Bodies auch richtig mit der CorrelationId + "xml" auf Platte geschrieben:

das ganze Projekt sieht dann so aus :