Merge remote-tracking branch 'origin/master'
diff --git a/apps/sdnip/pom.xml b/apps/sdnip/pom.xml
index 390b429..0ad0c11 100644
--- a/apps/sdnip/pom.xml
+++ b/apps/sdnip/pom.xml
@@ -42,6 +42,16 @@
<artifactId>onlab-thirdparty</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java
new file mode 100644
index 0000000..c14c20b
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/AsPathTest.java
@@ -0,0 +1,169 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for the BgpRouteEntry.AsPath class.
+ */
+public class AsPathTest {
+ /**
+ * Generates an AS Path.
+ *
+ * @return a generated AS Path
+ */
+ private BgpRouteEntry.AsPath generateAsPath() {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+
+ return asPath;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ String expectedString =
+ "AsPath{pathSegments=" +
+ "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}";
+ assertThat(asPath.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null Path Segments.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullPathSegments() {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = null;
+ new BgpRouteEntry.AsPath(pathSegments);
+ }
+
+ /**
+ * Tests getting the fields of an AS Path.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+
+ // Generate the entry to test
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ assertThat(asPath.getPathSegments(), is(pathSegments));
+ }
+
+ /**
+ * Tests getting the AS Path Length.
+ */
+ @Test
+ public void testGetAsPathLength() {
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+ assertThat(asPath.getAsPathLength(), is(4));
+
+ // Create an empty AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ asPath = new BgpRouteEntry.AsPath(pathSegments);
+ assertThat(asPath.getAsPathLength(), is(0));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry.AsPath}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry.AsPath asPath1 = generateAsPath();
+ BgpRouteEntry.AsPath asPath2 = generateAsPath();
+
+ assertThat(asPath1, is(asPath2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry.AsPath}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry.AsPath asPath1 = generateAsPath();
+
+ // Setup AS Path 2
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 55); // Different
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments);
+
+ assertThat(asPath1, is(not(asPath2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ String expectedString =
+ "AsPath{pathSegments=" +
+ "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}";
+ assertThat(asPath.toString(), is(expectedString));
+ }
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java
new file mode 100644
index 0000000..52a0214
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpRouteEntryTest.java
@@ -0,0 +1,498 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Unit tests for the BgpRouteEntry class.
+ */
+public class BgpRouteEntryTest {
+ private BgpSession bgpSession;
+ private static final IpAddress BGP_SESSION_BGP_ID =
+ IpAddress.valueOf("10.0.0.1");
+ private static final IpAddress BGP_SESSION_IP_ADDRESS =
+ IpAddress.valueOf("20.0.0.1");
+
+ private BgpSession bgpSession2;
+ private static final IpAddress BGP_SESSION_BGP_ID2 =
+ IpAddress.valueOf("10.0.0.2");
+ private static final IpAddress BGP_SESSION_IP_ADDRESS2 =
+ IpAddress.valueOf("20.0.0.1");
+
+ private BgpSession bgpSession3;
+ private static final IpAddress BGP_SESSION_BGP_ID3 =
+ IpAddress.valueOf("10.0.0.1");
+ private static final IpAddress BGP_SESSION_IP_ADDRESS3 =
+ IpAddress.valueOf("20.0.0.2");
+
+ @Before
+ public void setUp() throws Exception {
+ // Mock objects for testing
+ bgpSession = createMock(BgpSession.class);
+ bgpSession2 = createMock(BgpSession.class);
+ bgpSession3 = createMock(BgpSession.class);
+
+ // Setup the BGP Sessions
+ expect(bgpSession.getRemoteBgpId())
+ .andReturn(BGP_SESSION_BGP_ID).anyTimes();
+ expect(bgpSession.getRemoteIp4Address())
+ .andReturn(BGP_SESSION_IP_ADDRESS).anyTimes();
+ //
+ expect(bgpSession2.getRemoteBgpId())
+ .andReturn(BGP_SESSION_BGP_ID2).anyTimes();
+ expect(bgpSession2.getRemoteIp4Address())
+ .andReturn(BGP_SESSION_IP_ADDRESS2).anyTimes();
+ //
+ expect(bgpSession3.getRemoteBgpId())
+ .andReturn(BGP_SESSION_BGP_ID3).anyTimes();
+ expect(bgpSession3.getRemoteIp4Address())
+ .andReturn(BGP_SESSION_IP_ADDRESS3).anyTimes();
+
+ replay(bgpSession);
+ replay(bgpSession2);
+ replay(bgpSession3);
+ }
+
+ /**
+ * Generates a BGP Route Entry.
+ *
+ * @return a generated BGP Route Entry
+ */
+ private BgpRouteEntry generateBgpRouteEntry() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+
+ BgpRouteEntry bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+
+ return bgpRouteEntry;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ String expectedString =
+ "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+ "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" +
+ "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " +
+ "localPref=100, multiExitDisc=20}";
+ assertThat(bgpRouteEntry.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null BGP Session.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullBgpSession() {
+ BgpSession bgpSessionNull = null;
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+
+ new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath,
+ localPref);
+ }
+
+ /**
+ * Tests invalid class constructor for null AS Path.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullAsPath() {
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ BgpRouteEntry.AsPath asPath = null;
+ long localPref = 100;
+
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ }
+
+ /**
+ * Tests getting the fields of a BGP route entry.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+
+ // Generate the entry to test
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ assertThat(bgpRouteEntry.prefix(), is(prefix));
+ assertThat(bgpRouteEntry.nextHop(), is(nextHop));
+ assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession));
+ assertThat(bgpRouteEntry.getOrigin(), is(origin));
+ assertThat(bgpRouteEntry.getAsPath(), is(asPath));
+ assertThat(bgpRouteEntry.getLocalPref(), is(localPref));
+ assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc));
+ }
+
+ /**
+ * Tests whether a BGP route entry is a local route.
+ */
+ @Test
+ public void testIsLocalRoute() {
+ //
+ // Test non-local route
+ //
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+ assertThat(bgpRouteEntry.isLocalRoute(), is(false));
+
+ //
+ // Test local route with AS Path that begins with AS_SET
+ //
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+
+ //
+ // Test local route with empty AS Path
+ //
+ pathSegments = new ArrayList<>();
+ asPath = new BgpRouteEntry.AsPath(pathSegments);
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+ }
+
+ /**
+ * Tests getting the BGP Neighbor AS number for a route.
+ */
+ @Test
+ public void testGetNeighborAs() {
+ //
+ // Get neighbor AS for non-local route
+ //
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+ assertThat(bgpRouteEntry.getNeighborAs(), is((long) 1));
+
+ //
+ // Get neighbor AS for a local route
+ //
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0));
+ }
+
+ /**
+ * Tests whether a BGP route entry has AS Path loop.
+ */
+ @Test
+ public void testHasAsPathLoop() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ // Test for loops: test each AS number in the interval [1, 6]
+ for (int i = 1; i <= 6; i++) {
+ assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true));
+ }
+
+ // Test for non-loops
+ assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false));
+ }
+
+ /**
+ * Tests the BGP Decision Process comparison of BGP routes.
+ */
+ @Test
+ public void testBgpDecisionProcessComparison() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+ BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+ //
+ // Compare two routes that are same
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+ //
+ // Compare two routes with different LOCAL_PREF
+ //
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 50; // Different
+ long multiExitDisc = 20;
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ localPref = bgpRouteEntry1.getLocalPref(); // Restore
+
+ //
+ // Compare two routes with different AS_PATH length
+ //
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>();
+ pathSegments2.add(pathSegment1);
+ // Different AS Path
+ BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2);
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+ //
+ // Compare two routes with different ORIGIN
+ //
+ origin = BgpConstants.Update.Origin.EGP; // Different
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ origin = bgpRouteEntry1.getOrigin(); // Restore
+
+ //
+ // Compare two routes with different MULTI_EXIT_DISC
+ //
+ multiExitDisc = 10; // Different
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ multiExitDisc = bgpRouteEntry1.getMultiExitDisc(); // Restore
+
+ //
+ // Compare two routes with different BGP ID
+ //
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+
+ //
+ // Compare two routes with different BGP address
+ //
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+ BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+ assertThat(bgpRouteEntry1, is(bgpRouteEntry2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+
+ // Setup BGP Route 2
+ IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
+ IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 1);
+ segmentAsNumbers1.add((long) 2);
+ segmentAsNumbers1.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 4);
+ segmentAsNumbers2.add((long) 5);
+ segmentAsNumbers2.add((long) 6);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 500; // Different
+ long multiExitDisc = 20;
+ BgpRouteEntry bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+
+ assertThat(bgpRouteEntry1, is(not(bgpRouteEntry2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ String expectedString =
+ "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+ "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" +
+ "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " +
+ "localPref=100, multiExitDisc=20}";
+ assertThat(bgpRouteEntry.toString(), is(expectedString));
+ }
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java
new file mode 100644
index 0000000..99e3be1
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/BgpSessionManagerTest.java
@@ -0,0 +1,403 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.sdnip.RouteListener;
+import org.onlab.onos.sdnip.RouteUpdate;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.TestUtils;
+import org.onlab.util.TestUtils.TestUtilsException;
+
+import com.google.common.net.InetAddresses;
+
+/**
+ * Unit tests for the BgpSessionManager class.
+ */
+public class BgpSessionManagerTest {
+ private static final IpAddress IP_LOOPBACK_ID =
+ IpAddress.valueOf("127.0.0.1");
+ private static final IpAddress BGP_PEER1_ID = IpAddress.valueOf("10.0.0.1");
+ private static final long DEFAULT_LOCAL_PREF = 10;
+ private static final long DEFAULT_MULTI_EXIT_DISC = 20;
+
+ // The BGP Session Manager to test
+ private BgpSessionManager bgpSessionManager;
+
+ // Remote Peer state
+ private ClientBootstrap peerBootstrap;
+ private TestBgpPeerChannelHandler peerChannelHandler =
+ new TestBgpPeerChannelHandler(BGP_PEER1_ID, DEFAULT_LOCAL_PREF);
+ private TestBgpPeerFrameDecoder peerFrameDecoder =
+ new TestBgpPeerFrameDecoder();
+
+ // The socket that the Remote Peer should connect to
+ private InetSocketAddress connectToSocket;
+
+ private final DummyRouteListener dummyRouteListener =
+ new DummyRouteListener();
+
+ /**
+ * Dummy implementation for the RouteListener interface.
+ */
+ private class DummyRouteListener implements RouteListener {
+ @Override
+ public void update(RouteUpdate routeUpdate) {
+ // Nothing to do
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ //
+ // Setup the BGP Session Manager to test, and start listening for BGP
+ // connections.
+ //
+ bgpSessionManager = new BgpSessionManager(dummyRouteListener);
+ // NOTE: We use port 0 to bind on any available port
+ bgpSessionManager.startUp(0);
+
+ // Get the port number the BGP Session Manager is listening on
+ Channel serverChannel = TestUtils.getField(bgpSessionManager,
+ "serverChannel");
+ SocketAddress socketAddress = serverChannel.getLocalAddress();
+ InetSocketAddress inetSocketAddress =
+ (InetSocketAddress) socketAddress;
+
+ //
+ // Setup the BGP Peer, i.e., the "remote" BGP router that will
+ // initiate the BGP connection, send BGP UPDATE messages, etc.
+ //
+ ChannelFactory channelFactory =
+ new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
+ Executors.newCachedThreadPool());
+ ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ // Setup the transmitting pipeline
+ ChannelPipeline pipeline = Channels.pipeline();
+ pipeline.addLast("TestBgpPeerFrameDecoder",
+ peerFrameDecoder);
+ pipeline.addLast("TestBgpPeerChannelHandler",
+ peerChannelHandler);
+ return pipeline;
+ }
+ };
+
+ peerBootstrap = new ClientBootstrap(channelFactory);
+ peerBootstrap.setOption("child.keepAlive", true);
+ peerBootstrap.setOption("child.tcpNoDelay", true);
+ peerBootstrap.setPipelineFactory(pipelineFactory);
+
+ InetAddress connectToAddress = InetAddresses.forString("127.0.0.1");
+ connectToSocket = new InetSocketAddress(connectToAddress,
+ inetSocketAddress.getPort());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ bgpSessionManager.shutDown();
+ bgpSessionManager = null;
+ }
+
+ /**
+ * Gets BGP RIB-IN routes by waiting until they are received.
+ * <p/>
+ * NOTE: We keep checking once a second the number of received routes,
+ * up to 5 seconds.
+ *
+ * @param bgpSession the BGP session that is expected to receive the
+ * routes
+ * @param expectedRoutes the expected number of routes
+ * @return the BGP RIB-IN routes as received within the expected
+ * time interval
+ */
+ private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
+ long expectedRoutes)
+ throws InterruptedException {
+ Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn();
+
+ final int maxChecks = 5; // Max wait of 5 seconds
+ for (int i = 0; i < maxChecks; i++) {
+ if (bgpRibIn.size() == expectedRoutes) {
+ break;
+ }
+ Thread.sleep(1000);
+ bgpRibIn = bgpSession.getBgpRibIn();
+ }
+
+ return bgpRibIn;
+ }
+
+ /**
+ * Gets BGP merged routes by waiting until they are received.
+ * <p/>
+ * NOTE: We keep checking once a second the number of received routes,
+ * up to 5 seconds.
+ *
+ * @param expectedRoutes the expected number of routes
+ * @return the BGP Session Manager routes as received within the expected
+ * time interval
+ */
+ private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes)
+ throws InterruptedException {
+ Collection<BgpRouteEntry> bgpRoutes = bgpSessionManager.getBgpRoutes();
+
+ final int maxChecks = 5; // Max wait of 5 seconds
+ for (int i = 0; i < maxChecks; i++) {
+ if (bgpRoutes.size() == expectedRoutes) {
+ break;
+ }
+ Thread.sleep(1000);
+ bgpRoutes = bgpSessionManager.getBgpRoutes();
+ }
+
+ return bgpRoutes;
+ }
+
+ /**
+ * Tests that the BGP OPEN messages have been exchanged, followed by
+ * KEEPALIVE.
+ * <p>
+ * The BGP Peer opens the sessions and transmits OPEN Message, eventually
+ * followed by KEEPALIVE. The tested BGP listener should respond by
+ * OPEN Message, followed by KEEPALIVE.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testExchangedBgpOpenMessages()
+ throws InterruptedException, TestUtilsException {
+ // Initiate the connection
+ peerBootstrap.connect(connectToSocket);
+
+ // Wait until the OPEN message is received
+ peerFrameDecoder.receivedOpenMessageLatch.await(2000,
+ TimeUnit.MILLISECONDS);
+ // Wait until the KEEPALIVE message is received
+ peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000,
+ TimeUnit.MILLISECONDS);
+
+ //
+ // Test the fields from the BGP OPEN message:
+ // BGP version, AS number, BGP ID
+ //
+ assertThat(peerFrameDecoder.remoteBgpVersion,
+ is(BgpConstants.BGP_VERSION));
+ assertThat(peerFrameDecoder.remoteAs,
+ is(TestBgpPeerChannelHandler.PEER_AS));
+ assertThat(peerFrameDecoder.remoteBgpIdentifier, is(IP_LOOPBACK_ID));
+
+ //
+ // Test that a BgpSession instance has been created
+ //
+ assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
+ assertThat(bgpSessionManager.getBgpSessions(), hasSize(1));
+ BgpSession bgpSession =
+ bgpSessionManager.getBgpSessions().iterator().next();
+ assertThat(bgpSession, notNullValue());
+ long sessionAs = TestUtils.getField(bgpSession, "localAs");
+ assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS));
+ }
+
+ /**
+ * Tests that the BGP UPDATE messages have been received and processed.
+ */
+ @Test
+ public void testProcessedBgpUpdateMessages() throws InterruptedException {
+ BgpSession bgpSession;
+ IpAddress nextHopRouter;
+ BgpRouteEntry bgpRouteEntry;
+ ChannelBuffer message;
+ Collection<BgpRouteEntry> bgpRibIn;
+ Collection<BgpRouteEntry> bgpRoutes;
+
+ // Initiate the connection
+ peerBootstrap.connect(connectToSocket);
+
+ // Wait until the OPEN message is received
+ peerFrameDecoder.receivedOpenMessageLatch.await(2000,
+ TimeUnit.MILLISECONDS);
+ // Wait until the KEEPALIVE message is received
+ peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000,
+ TimeUnit.MILLISECONDS);
+
+ // Get the BGP Session handler
+ bgpSession = bgpSessionManager.getBgpSessions().iterator().next();
+
+ // Prepare routes to add/delete
+ nextHopRouter = IpAddress.valueOf("10.20.30.40");
+ Collection<IpPrefix> addedRoutes = new LinkedList<>();
+ Collection<IpPrefix> withdrawnRoutes = new LinkedList<>();
+ addedRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
+ addedRoutes.add(IpPrefix.valueOf("20.0.0.0/8"));
+ addedRoutes.add(IpPrefix.valueOf("30.0.0.0/16"));
+ addedRoutes.add(IpPrefix.valueOf("40.0.0.0/24"));
+ addedRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
+ withdrawnRoutes.add(IpPrefix.valueOf("60.0.0.0/8"));
+ withdrawnRoutes.add(IpPrefix.valueOf("70.0.0.0/16"));
+ withdrawnRoutes.add(IpPrefix.valueOf("80.0.0.0/24"));
+ withdrawnRoutes.add(IpPrefix.valueOf("90.0.0.0/32"));
+ // Write the routes
+ message = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
+ addedRoutes,
+ withdrawnRoutes);
+ peerChannelHandler.savedCtx.getChannel().write(message);
+
+ // Check that the routes have been received, processed and stored
+ bgpRibIn = waitForBgpRibIn(bgpSession, 5);
+ assertThat(bgpRibIn, hasSize(5));
+ bgpRoutes = waitForBgpRoutes(5);
+ assertThat(bgpRoutes, hasSize(5));
+
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add((long) 65010);
+ segmentAsNumbers1.add((long) 65020);
+ segmentAsNumbers1.add((long) 65030);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add((long) 65041);
+ segmentAsNumbers2.add((long) 65042);
+ segmentAsNumbers2.add((long) 65043);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("0.0.0.0/0"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("20.0.0.0/8"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("30.0.0.0/16"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("40.0.0.0/24"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("50.0.0.0/32"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+
+ // Delete some routes
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ withdrawnRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
+ withdrawnRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
+
+ // Write the routes
+ message = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
+ addedRoutes,
+ withdrawnRoutes);
+ peerChannelHandler.savedCtx.getChannel().write(message);
+
+ // Check that the routes have been received, processed and stored
+ bgpRibIn = waitForBgpRibIn(bgpSession, 3);
+ assertThat(bgpRibIn, hasSize(3));
+ bgpRoutes = waitForBgpRoutes(3);
+ assertThat(bgpRoutes, hasSize(3));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("20.0.0.0/8"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("30.0.0.0/16"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession,
+ IpPrefix.valueOf("40.0.0.0/24"),
+ nextHopRouter,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPath,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn, hasItem(bgpRouteEntry));
+
+ // Close the channel and test there are no routes
+ peerChannelHandler.closeChannel();
+ bgpRoutes = waitForBgpRoutes(0);
+ assertThat(bgpRoutes, hasSize(0));
+ }
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/PathSegmentTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/PathSegmentTest.java
new file mode 100644
index 0000000..636883e
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/PathSegmentTest.java
@@ -0,0 +1,115 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for the BgpRouteEntry.PathSegment class.
+ */
+public class PathSegmentTest {
+ /**
+ * Generates a Path Segment.
+ *
+ * @return a generated PathSegment
+ */
+ private BgpRouteEntry.PathSegment generatePathSegment() {
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add((long) 1);
+ segmentAsNumbers.add((long) 2);
+ segmentAsNumbers.add((long) 3);
+ BgpRouteEntry.PathSegment pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+ return pathSegment;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ String expectedString =
+ "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}";
+ assertThat(pathSegment.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null Segment AS Numbers.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullSegmentAsNumbers() {
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = null;
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ }
+
+ /**
+ * Tests getting the fields of a Path Segment.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add((long) 1);
+ segmentAsNumbers.add((long) 2);
+ segmentAsNumbers.add((long) 3);
+
+ // Generate the entry to test
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ assertThat(pathSegment.getType(), is(pathSegmentType));
+ assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry.PathSegment}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+ BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment();
+
+ assertThat(pathSegment1, is(pathSegment2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry.PathSegment}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+
+ // Setup Path Segment 2
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add((long) 1);
+ segmentAsNumbers.add((long) 22); // Different
+ segmentAsNumbers.add((long) 3);
+ //
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+ assertThat(pathSegment1, is(not(pathSegment2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ String expectedString =
+ "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}";
+ assertThat(pathSegment.toString(), is(expectedString));
+ }
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java
new file mode 100644
index 0000000..1b51d52
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerChannelHandler.java
@@ -0,0 +1,250 @@
+package org.onlab.onos.sdnip.bgp;
+
+import java.util.Collection;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Class for handling the remote BGP Peer session.
+ */
+class TestBgpPeerChannelHandler extends SimpleChannelHandler {
+ static final long PEER_AS = 65001;
+ static final int PEER_HOLDTIME = 120; // 120 seconds
+ final IpAddress bgpId; // The BGP ID
+ final long localPref; // Local preference for routes
+ final long multiExitDisc = 20; // MED value
+
+ ChannelHandlerContext savedCtx;
+
+ /**
+ * Constructor for given BGP ID.
+ *
+ * @param bgpId the BGP ID to use
+ * @param localPref the local preference for the routes to use
+ */
+ TestBgpPeerChannelHandler(IpAddress bgpId,
+ long localPref) {
+ this.bgpId = bgpId;
+ this.localPref = localPref;
+ }
+
+ /**
+ * Closes the channel.
+ */
+ void closeChannel() {
+ savedCtx.getChannel().close();
+ }
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ this.savedCtx = ctx;
+ // Prepare and transmit BGP OPEN message
+ ChannelBuffer message = prepareBgpOpen();
+ ctx.getChannel().write(message);
+
+ // Prepare and transmit BGP KEEPALIVE message
+ message = prepareBgpKeepalive();
+ ctx.getChannel().write(message);
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ // Nothing to do
+ }
+
+ /**
+ * Prepares BGP OPEN message.
+ *
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpOpen() {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ message.writeByte(BgpConstants.BGP_VERSION);
+ message.writeShort((int) PEER_AS);
+ message.writeShort(PEER_HOLDTIME);
+ message.writeInt(bgpId.toInt());
+ message.writeByte(0); // No Optional Parameters
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
+ }
+
+ /**
+ * Prepares BGP UPDATE message.
+ *
+ * @param nextHopRouter the next-hop router address for the routes to add
+ * @param addedRoutes the routes to add
+ * @param withdrawnRoutes the routes to withdraw
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpUpdate(IpAddress nextHopRouter,
+ Collection<IpPrefix> addedRoutes,
+ Collection<IpPrefix> withdrawnRoutes) {
+ int attrFlags;
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ ChannelBuffer pathAttributes =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ // Encode the Withdrawn Routes
+ ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes);
+ message.writeShort(encodedPrefixes.readableBytes());
+ message.writeBytes(encodedPrefixes);
+
+ // Encode the Path Attributes
+ // ORIGIN: IGP
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE);
+ pathAttributes.writeByte(1); // Data length
+ pathAttributes.writeByte(BgpConstants.Update.Origin.IGP);
+ // AS_PATH: Two Path Segments of 3 ASes each
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE);
+ pathAttributes.writeByte(16); // Data length
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ pathAttributes.writeByte(pathSegmentType1);
+ pathAttributes.writeByte(3); // Three ASes
+ pathAttributes.writeShort(65010); // AS=65010
+ pathAttributes.writeShort(65020); // AS=65020
+ pathAttributes.writeShort(65030); // AS=65030
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ pathAttributes.writeByte(pathSegmentType2);
+ pathAttributes.writeByte(3); // Three ASes
+ pathAttributes.writeShort(65041); // AS=65041
+ pathAttributes.writeShort(65042); // AS=65042
+ pathAttributes.writeShort(65043); // AS=65043
+ // NEXT_HOP: nextHopRouter
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router
+ // LOCAL_PREF: localPref
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt((int) localPref); // Preference value
+ // MULTI_EXIT_DISC: multiExitDisc
+ attrFlags = 0x80; // Optional
+ // Non-Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt((int) multiExitDisc); // Preference value
+ // The NLRI prefixes
+ encodedPrefixes = encodePackedPrefixes(addedRoutes);
+
+ // Write the Path Attributes, beginning with its length
+ message.writeShort(pathAttributes.readableBytes());
+ message.writeBytes(pathAttributes);
+ message.writeBytes(encodedPrefixes);
+
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE, message);
+ }
+
+ /**
+ * Encodes a collection of IPv4 network prefixes in a packed format.
+ * <p>
+ * The IPv4 prefixes are encoded in the form:
+ * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
+ * and Prefix is the IPv4 prefix (padded with trailing bits to the end
+ * of an octet).
+ *
+ * @param prefixes the prefixes to encode
+ * @return the buffer with the encoded prefixes
+ */
+ private ChannelBuffer encodePackedPrefixes(Collection<IpPrefix> prefixes) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ // Write each of the prefixes
+ for (IpPrefix prefix : prefixes) {
+ int prefixBitlen = prefix.prefixLength();
+ int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
+ message.writeByte(prefixBitlen);
+
+ IpAddress address = prefix.toIpAddress();
+ long value = address.toInt() & 0xffffffffL;
+ for (int i = 0; i < IpAddress.INET_LEN; i++) {
+ if (prefixBytelen-- == 0) {
+ break;
+ }
+ long nextByte =
+ (value >> ((IpAddress.INET_LEN - i - 1) * 8)) & 0xff;
+ message.writeByte((int) nextByte);
+ }
+ }
+
+ return message;
+ }
+
+ /**
+ * Prepares BGP KEEPALIVE message.
+ *
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpKeepalive() {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
+ }
+
+ /**
+ * Prepares BGP NOTIFICATION message.
+ *
+ * @param errorCode the BGP NOTIFICATION Error Code
+ * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
+ * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
+ * @param payload the BGP NOTIFICATION Data if applicable, otherwise null
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
+ ChannelBuffer data) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ // Prepare the NOTIFICATION message payload
+ message.writeByte(errorCode);
+ message.writeByte(errorSubcode);
+ if (data != null) {
+ message.writeBytes(data);
+ }
+ return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
+ }
+
+ /**
+ * Prepares BGP message.
+ *
+ * @param type the BGP message type
+ * @param payload the message payload to transmit (BGP header excluded)
+ * @return the message to transmit (BGP header included)
+ */
+ private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
+ payload.readableBytes());
+
+ // Write the marker
+ for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
+ message.writeByte(0xff);
+ }
+
+ // Write the rest of the BGP header
+ message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
+ payload.readableBytes());
+ message.writeByte(type);
+
+ // Write the payload
+ message.writeBytes(payload);
+ return message;
+ }
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java
new file mode 100644
index 0000000..42bbb90
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/bgp/TestBgpPeerFrameDecoder.java
@@ -0,0 +1,163 @@
+package org.onlab.onos.sdnip.bgp;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onlab.packet.IpAddress;
+
+/**
+ * Class for handling the decoding of the BGP messages at the remote
+ * BGP peer session.
+ */
+class TestBgpPeerFrameDecoder extends FrameDecoder {
+ int remoteBgpVersion; // 1 octet
+ long remoteAs; // 2 octets
+ long remoteHoldtime; // 2 octets
+ IpAddress remoteBgpIdentifier; // 4 octets -> IPv4 address
+
+ final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
+ final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx,
+ Channel channel,
+ ChannelBuffer buf) throws Exception {
+ // Test for minimum length of the BGP message
+ if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
+ // No enough data received
+ return null;
+ }
+
+ //
+ // Mark the current buffer position in case we haven't received
+ // the whole message.
+ //
+ buf.markReaderIndex();
+
+ //
+ // Read and check the BGP message Marker field: it must be all ones
+ //
+ byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
+ buf.readBytes(marker);
+ for (int i = 0; i < marker.length; i++) {
+ if (marker[i] != (byte) 0xff) {
+ // ERROR: Connection Not Synchronized. Close the channel.
+ ctx.getChannel().close();
+ return null;
+ }
+ }
+
+ //
+ // Read and check the BGP message Length field
+ //
+ int length = buf.readUnsignedShort();
+ if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
+ (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return null;
+ }
+
+ //
+ // Test whether the rest of the message is received:
+ // So far we have read the Marker (16 octets) and the
+ // Length (2 octets) fields.
+ //
+ int remainingMessageLen =
+ length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
+ if (buf.readableBytes() < remainingMessageLen) {
+ // No enough data received
+ buf.resetReaderIndex();
+ return null;
+ }
+
+ //
+ // Read the BGP message Type field, and process based on that type
+ //
+ int type = buf.readUnsignedByte();
+ remainingMessageLen--; // Adjust after reading the type
+ ChannelBuffer message = buf.readBytes(remainingMessageLen);
+
+ //
+ // Process the remaining of the message based on the message type
+ //
+ switch (type) {
+ case BgpConstants.BGP_TYPE_OPEN:
+ processBgpOpen(ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_UPDATE:
+ // NOTE: Not used as part of the test, because ONOS does not
+ // originate UPDATE messages.
+ break;
+ case BgpConstants.BGP_TYPE_NOTIFICATION:
+ // NOTE: Not used as part of the testing (yet)
+ break;
+ case BgpConstants.BGP_TYPE_KEEPALIVE:
+ processBgpKeepalive(ctx, message);
+ break;
+ default:
+ // ERROR: Bad Message Type. Close the channel.
+ ctx.getChannel().close();
+ return null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Processes BGP OPEN message.
+ *
+ * @param ctx the Channel Handler Context.
+ * @param message the message to process.
+ */
+ private void processBgpOpen(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ int minLength =
+ BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+
+ //
+ // Parse the OPEN message
+ //
+ remoteBgpVersion = message.readUnsignedByte();
+ remoteAs = message.readUnsignedShort();
+ remoteHoldtime = message.readUnsignedShort();
+ remoteBgpIdentifier = IpAddress.valueOf((int) message.readUnsignedInt());
+ // Optional Parameters
+ int optParamLen = message.readUnsignedByte();
+ if (message.readableBytes() < optParamLen) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+ message.readBytes(optParamLen); // NOTE: data ignored
+
+ // BGP OPEN message successfully received
+ receivedOpenMessageLatch.countDown();
+ }
+
+ /**
+ * Processes BGP KEEPALIVE message.
+ *
+ * @param ctx the Channel Handler Context.
+ * @param message the message to process.
+ */
+ private void processBgpKeepalive(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+ BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+ // BGP KEEPALIVE message successfully received
+ receivedKeepaliveMessageLatch.countDown();
+ }
+}
diff --git a/utils/misc/src/main/java/org/onlab/util/TestUtils.java b/utils/misc/src/main/java/org/onlab/util/TestUtils.java
new file mode 100644
index 0000000..7e59564
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/TestUtils.java
@@ -0,0 +1,169 @@
+package org.onlab.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+/**
+ * Utilities for testing.
+ */
+public final class TestUtils {
+
+ /**
+ * Sets the field, bypassing scope restriction.
+ *
+ * @param subject Object where the field belongs
+ * @param fieldName name of the field to set
+ * @param value value to set to the field.
+ * @param <T> subject type
+ * @param <U> value type
+ * @throws TestUtilsException if there are reflection errors while setting
+ * the field
+ */
+ public static <T, U> void setField(T subject, String fieldName, U value)
+ throws TestUtilsException {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) subject.getClass();
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(subject, value);
+ } catch (NoSuchFieldException | SecurityException |
+ IllegalArgumentException | IllegalAccessException e) {
+ throw new TestUtilsException("setField failed", e);
+ }
+ }
+
+ /**
+ * Gets the field, bypassing scope restriction.
+ *
+ * @param subject Object where the field belongs
+ * @param fieldName name of the field to get
+ * @return value of the field.
+ * @param <T> subject type
+ * @param <U> field value type
+ * @throws TestUtilsException if there are reflection errors while getting
+ * the field
+ */
+ public static <T, U> U getField(T subject, String fieldName)
+ throws TestUtilsException {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) subject.getClass();
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+
+ @SuppressWarnings("unchecked")
+ U result = (U) field.get(subject);
+ return result;
+ } catch (NoSuchFieldException | SecurityException |
+ IllegalArgumentException | IllegalAccessException e) {
+ throw new TestUtilsException("getField failed", e);
+ }
+ }
+
+ /**
+ * Calls the method, bypassing scope restriction.
+ *
+ * @param subject Object where the method belongs
+ * @param methodName name of the method to call
+ * @param paramTypes formal parameter type array
+ * @param args arguments
+ * @return return value or null if void
+ * @param <T> subject type
+ * @param <U> return value type
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the method
+ */
+ public static <T, U> U callMethod(T subject, String methodName,
+ Class<?>[] paramTypes, Object...args) throws TestUtilsException {
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) subject.getClass();
+ final Method method;
+ if (paramTypes == null || paramTypes.length == 0) {
+ method = clazz.getDeclaredMethod(methodName);
+ } else {
+ method = clazz.getDeclaredMethod(methodName, paramTypes);
+ }
+ method.setAccessible(true);
+
+ @SuppressWarnings("unchecked")
+ U result = (U) method.invoke(subject, args);
+ return result;
+ } catch (NoSuchMethodException | SecurityException |
+ IllegalAccessException | IllegalArgumentException |
+ InvocationTargetException e) {
+ throw new TestUtilsException("callMethod failed", e);
+ }
+ }
+
+ /**
+ * Calls the method, bypassing scope restriction.
+ *
+ * @param subject Object where the method belongs
+ * @param methodName name of the method to call
+ * @param paramType formal parameter type
+ * @param arg argument
+ * @return return value or null if void
+ * @param <T> subject type
+ * @param <U> return value type
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the method
+ */
+ public static <T, U> U callMethod(T subject, String methodName,
+ Class<?> paramType, Object arg) throws TestUtilsException {
+ return callMethod(subject, methodName, new Class<?>[]{paramType}, arg);
+ }
+
+ /**
+ * Triggers an allocation of an object of type <T> and forces a call to
+ * the private constructor.
+ *
+ * @param constructor Constructor to call
+ * @param <T> type of the object to create
+ * @return created object of type <T>
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the constructor
+ */
+ public static <T> T callConstructor(Constructor<T> constructor)
+ throws TestUtilsException {
+ try {
+ constructor.setAccessible(true);
+ return constructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException |
+ InvocationTargetException error) {
+ throw new TestUtilsException("callConstructor failed", error);
+ }
+ }
+
+ /**
+ * Avoid instantiation.
+ */
+ private TestUtils() {}
+
+ /**
+ * Exception that can be thrown if problems are encountered while executing
+ * the utility method. These are usually problems accessing fields/methods
+ * through reflection. The original exception can be found by examining the
+ * cause.
+ */
+ public static class TestUtilsException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with the specified detail message and
+ * cause.
+ *
+ * @param message the detail message
+ * @param cause the original cause of this exception
+ */
+ public TestUtilsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/utils/misc/src/test/java/org/onlab/util/TestUtilsTest.java b/utils/misc/src/test/java/org/onlab/util/TestUtilsTest.java
new file mode 100644
index 0000000..58e60c1
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/util/TestUtilsTest.java
@@ -0,0 +1,170 @@
+package org.onlab.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.util.TestUtils.TestUtilsException;
+
+/**
+ * Test and usage examples for TestUtils.
+ */
+public class TestUtilsTest {
+
+ /**
+ * Test data.
+ */
+ private static final class TestClass {
+
+ @SuppressWarnings("unused")
+ private int privateField = 42;
+
+ @SuppressWarnings("unused")
+ protected int protectedField = 2501; // CHECKSTYLE IGNORE THIS LINE
+
+ /**
+ * Protected method with multiple argument.
+ *
+ * @param x simply returns
+ * @param y not used
+ * @return x
+ */
+ @SuppressWarnings("unused")
+ private int privateMethod(Number x, Long y) {
+ return x.intValue();
+ }
+
+ /**
+ * Protected method with no argument.
+ *
+ * @return int
+ */
+ @SuppressWarnings("unused")
+ protected int protectedMethod() {
+ return 42;
+ }
+
+ /**
+ * Method returning array.
+ *
+ * @param ary random array
+ * @return ary
+ */
+ @SuppressWarnings("unused")
+ private int[] arrayReturnMethod(int[] ary) {
+ return ary;
+ }
+
+ /**
+ * Method without return value.
+ *
+ * @param s ignored
+ */
+ @SuppressWarnings("unused")
+ private void voidMethod(String s) {
+ System.out.println(s);
+ }
+ }
+
+ private TestClass test;
+
+ /**
+ * Sets up the test fixture.
+ */
+ @Before
+ public void setUp() {
+ test = new TestClass();
+ }
+
+ /**
+ * Example to access private field.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testSetGetPrivateField() throws TestUtilsException {
+
+ assertEquals(42, TestUtils.getField(test, "privateField"));
+ TestUtils.setField(test, "privateField", 0xDEAD);
+ assertEquals(0xDEAD, TestUtils.getField(test, "privateField"));
+ }
+
+ /**
+ * Example to access protected field.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testSetGetProtectedField() throws TestUtilsException {
+
+ assertEquals(2501, TestUtils.getField(test, "protectedField"));
+ TestUtils.setField(test, "protectedField", 0xBEEF);
+ assertEquals(0xBEEF, TestUtils.getField(test, "protectedField"));
+ }
+
+ /**
+ * Example to call private method and multiple parameters.
+ * <p/>
+ * It also illustrates that paramTypes must match declared type,
+ * not the runtime types of arguments.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testCallPrivateMethod() throws TestUtilsException {
+
+ int result = TestUtils.callMethod(test, "privateMethod",
+ new Class<?>[] {Number.class, Long.class},
+ Long.valueOf(42), Long.valueOf(32));
+ assertEquals(42, result);
+ }
+
+ /**
+ * Example to call protected method and no parameters.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testCallProtectedMethod() throws TestUtilsException {
+
+ int result = TestUtils.callMethod(test, "protectedMethod",
+ new Class<?>[] {});
+ assertEquals(42, result);
+ }
+
+ /**
+ * Example to call method returning array.
+ * <p/>
+ * Note: It is not required to receive as Object.
+ * Following is just verifying it is not Boxed arrays.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testCallArrayReturnMethod() throws TestUtilsException {
+
+ int[] array = {1, 2, 3};
+ Object aryResult = TestUtils.callMethod(test, "arrayReturnMethod",
+ new Class<?>[] {int[].class}, array);
+ assertEquals(int[].class, aryResult.getClass());
+ assertArrayEquals(array, (int[]) aryResult);
+ }
+
+
+ /**
+ * Example to call void returning method.
+ * <p/>
+ * Note: Return value will be null for void methods.
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testCallVoidReturnMethod() throws TestUtilsException {
+
+ Object voidResult = TestUtils.callMethod(test, "voidMethod",
+ String.class, "foobar");
+ assertNull(voidResult);
+ }
+}