SNMP4J

Test-Driven Development with SNMP4J

This article presents a simple example of test-driven development with SNMP4J. Unit testing goes hand in hand with TDD. Agile testing is designed to prevent bugs and relies primarily on unit tests. Comprehensive regression testing can be run several times in a day. A test suite prevents fixed bugs from coming back or finding side effects of changes. The source of bugs can be pinpointed.

The rhythm of coding in TDD is test a little… code a little… test a little… code a little… test a little… code a little…

We will follow the 5 steps of TDD in creating our project.

  1. Add a Test
  2. Watch the Test Fail
  3. Write the Code
  4. Run the Tests
  5. Refactor

1. Prerequisites

  1. SNMP4J and SNMP4J Agent libraries
  2. Mars Eclipse
  3. Have read SNMPv3 Set Example using SNMP4J
  4. Familiarity with Apache Maven.

2. Create the Project

Before anything else, we must create a Maven project in Eclipse. Click on File -> New -> Maven Project to create the project. Check the Create a simple project -> Next. Enter com.javacodegeeks.example for our groupId and snmp4j-unit-test for our artifactId. Lastly, add the following dependencies:

  • snmp4j
  • snmp4j-agent with a test scope
  • junit with a test scope

Our project object model should look like this:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.javacodegeeks.example</groupId>
  <artifactId>snmp4j-unit-test</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
  	<dependency>
  		<groupId>org.snmp4j</groupId>
  		<artifactId>snmp4j</artifactId>
  		<version>2.5.6</version>
  	</dependency>
  	<dependency>
  		<groupId>org.snmp4j</groupId>
  		<artifactId>snmp4j-agent</artifactId>
  		<version>2.5.3</version>
  		<scope>test</scope>
  	</dependency>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.12</version>
  		<scope>test</scope>
  	</dependency>
  </dependencies>
</project>

3. Add a Test

Create a package called com.javacodegeeks.example under the src/test/java folder. In this package we will create our test class named SnmpClientTest.java. We’ll just keep our requirements simple. Our client will just do a simple SNMP Get Request. Our final test class will look like the one below:

SnmpClientTest.java

package com.javacodegeeks.example;

import static org.junit.Assert.assertEquals;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class SnmpClientTest {

	static MyAgent agent;

	@BeforeClass
	public static void setup() throws Exception {
		agent = new MyAgent();
		agent.start();
	}

	@AfterClass
	public static void tearDown() {
		agent.stop();
	}

	@Test
	public void testGet() throws Exception {
		String actual = SnmpClient.get("127.0.0.1", 161, ".1.3.6.1.2.1.1.1.0");
		assertEquals("My Agent System Description", actual);
	}
}

Don’t worry about MyAgent just yet, we will be adding that later. In the simplest sense, our test class should only have the testGet method (highlighted). Go ahead and create a bare bones SnmpClient.java under the src/main/java folder with the same package name. The SnmpClient should have a static method named get and accepts three parameters then returns a string. For now, it should not contain any implementation. Let’s just make it return an empty string. This is our class under test. Our target now is to satisfy this test. The “actual” result returned from the SNMP Get Request must equal the one we are expecting which is “My Agent System Description”. On to the next step.

4. Watch the Test Fail

Let’s check and see if our test is working properly. This test should fail because we have no implementation for it yet. This proves that the test does not pass without requiring new code.

Our “mvn test” output should look something like this:

mvn test

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.javacodegeeks.example.SnmpClientTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.43 sec <<< FAILURE!
testGet(com.javacodegeeks.example.SnmpClientTest)  Time elapsed: 0.161 sec  <<< FAILURE!

Before implementing our code to satisfy the test, we need to create a test SNMP agent that will provide mock data to our SNMP client. Below is our test agent:

MyAgent.java

package com.javacodegeeks.example;

import java.io.File;
import java.io.IOException;

import org.snmp4j.TransportMapping;
import org.snmp4j.agent.BaseAgent;
import org.snmp4j.agent.CommandProcessor;
import org.snmp4j.agent.DuplicateRegistrationException;
import org.snmp4j.agent.mo.MOAccessImpl;
import org.snmp4j.agent.mo.MOScalar;
import org.snmp4j.agent.mo.snmp.RowStatus;
import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB;
import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB;
import org.snmp4j.agent.mo.snmp.SnmpTargetMIB;
import org.snmp4j.agent.mo.snmp.StorageType;
import org.snmp4j.agent.mo.snmp.VacmMIB;
import org.snmp4j.agent.security.MutableVACM;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModel;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.transport.TransportMappings;

public class MyAgent extends BaseAgent {

	public MyAgent() {
		super(new File("bootCounterFile.txt"), new File("configFile.txt"),
				new CommandProcessor(new OctetString(MPv3.createLocalEngineID())));
	}

	@Override
	protected void initTransportMappings() throws IOException {
		transportMappings = new TransportMapping<?>[1];
		Address addr = GenericAddress.parse("0.0.0.0/161");
		TransportMapping<? extends Address> tm = TransportMappings.getInstance().createTransportMapping(addr);
		transportMappings[0] = tm;
	}

	public void start() throws IOException {
		init();
		addShutdownHook();
		getServer().addContext(new OctetString("public"));
		finishInit();
		run();
		sendColdStartNotification();
	}

	@Override
	protected void registerManagedObjects() {
		getSnmpv2MIB().unregisterMOs(server, getContext(getSnmpv2MIB()));

		MOScalar mo = new MOScalar(new OID(".1.3.6.1.2.1.1.1.0"),
				MOAccessImpl.ACCESS_READ_ONLY, new OctetString("My Agent System Description"));
		try {
			server.register(mo, null);
		} catch (DuplicateRegistrationException e) {
			e.printStackTrace();
		}
	}

	@Override
	protected void unregisterManagedObjects() {
		// do nothing
	}

	@Override
	protected void addUsmUser(USM usm) {
		// do nothing
	}

	@Override
	protected void addNotificationTargets(SnmpTargetMIB targetMIB, SnmpNotificationMIB notificationMIB) {
		// do nothing
	}

	@Override
	protected void addViews(VacmMIB vacmMIB) {
		vacmMIB.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString("cpublic"), new OctetString("v1v2group"),
				StorageType.nonVolatile);

		vacmMIB.addAccess(new OctetString("v1v2group"), new OctetString("public"), SecurityModel.SECURITY_MODEL_ANY,
				SecurityLevel.NOAUTH_NOPRIV, MutableVACM.VACM_MATCH_EXACT, new OctetString("fullReadView"),
				new OctetString("fullWriteView"), new OctetString("fullNotifyView"), StorageType.nonVolatile);

		vacmMIB.addViewTreeFamily(new OctetString("fullReadView"), new OID(".1.3"), new OctetString(),
				VacmMIB.vacmViewIncluded, StorageType.nonVolatile);
	}

	@Override
	protected void addCommunities(SnmpCommunityMIB communityMIB) {
		Variable[] com2sec = new Variable[] {
		        new OctetString("public"),              // community name
		        new OctetString("cpublic"),             // security name
		        getAgent().getContextEngineID(),        // local engine ID
		        new OctetString("public"),              // default context name
		        new OctetString(),                      // transport tag
		        new Integer32(StorageType.nonVolatile), // storage type
		        new Integer32(RowStatus.active)         // row status
		    };
	    SnmpCommunityMIB.SnmpCommunityEntryRow row = communityMIB.getSnmpCommunityEntry().createRow(
	          new OctetString("public2public").toSubIndex(true), com2sec);
	    communityMIB.getSnmpCommunityEntry().addRow(row);
	}
}

Place MyAgent under the src/test/java com.javacodegeeks.example package. Our agent extends the BaseAgent of SNMP4J. The files in the constructor do not exist yet but will be created by the test agent. The things to note are highlighted. We are using 0.0.0.0 instead of the localhost IP address. Our read community is public. In the registerManagedObjects method, we removed the SNMPv2-MIB (installed by default) and replaced it with our own scalar managed object. The value of this object is what our client will be requesting.
The addCommunities method is the minimal implementation for the table of community strings configured in the SNMP Local Configuration Datastore. The addViews method provides the minimal view based access control. We’re not implementing addUsmUser because will be using SNMPv2c and not SNMPv3.

5. Write the Code

Finally, we will write the code that will satisfy the test.

SnmpClient.java

package com.javacodegeeks.example;

import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class SnmpClient {

	public static String get(String address, int port, String oid) throws Exception {
		TransportMapping transport = new DefaultUdpTransportMapping();
		Snmp snmp = new Snmp(transport);
		snmp.listen();

		PDU pdu = new PDU();
		pdu.add(new VariableBinding(new OID(oid)));
		pdu.setType(PDU.GET);

		CommunityTarget target = new CommunityTarget();
		target.setCommunity(new OctetString("public"));
		target.setAddress(GenericAddress.parse(String.format("udp:%s/%s", address, port)));
		target.setRetries(2);
		target.setTimeout(1500);
		target.setVersion(SnmpConstants.version2c);

		ResponseEvent event = snmp.send(pdu, target);
		if (event != null) {
			return event.getResponse().get(0).getVariable().toString();
		}
		
		return null;
	}
}

Our method performs an SNMPv2c Get Request (highlighted) using a "public" read community. The object identifier is placed in a variable binding. The response is then evaluated and returned. The above code should be easy to read and understand.

6. Run the Tests

Our test should pass now. The test output should look like the one below:

mvn test

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.javacodegeeks.example.SnmpClientTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.665 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.716 s
[INFO] Finished at: 2017-07-03T20:56:04+08:00
[INFO] Final Memory: 10M/164M
[INFO] ------------------------------------------------------------------------

7. Test-Driven Development with SNMP4J Summary

In the TDD approach, we wrote the unit test first. Ran the unit test to watch it fail. Then wrote the code to make it pass. We extended the BaseAgent class of SNMP4J to create a test agent. We added a scalar object as our test data which will be requested by the client. Lastly, our production code communicated with the test agent.

8. Download the Source Code

This is an example of test-driven development with SNMP4J.

Download
You can download the source code of this example here: snmp4j-unit-test.zip.

Joel Patrick Llosa

I graduated from Silliman University in Dumaguete City with a degree in Bachelor of Science in Business Computer Application. I have contributed to many Java related projects at Neural Technologies Ltd., University of Southampton (iSolutions), Predictive Technologies, LLC., Confluence Service, North Concepts, Inc., NEC Telecom Software Philippines, Inc., and NEC Technologies Philippines, Inc. You can also find me in Upwork freelancing as a Java Developer.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button