Showing
9 changed files
with
1947 additions
and
0 deletions
| ... | @@ -42,6 +42,16 @@ | ... | @@ -42,6 +42,16 @@ |
| 42 | <artifactId>onlab-thirdparty</artifactId> | 42 | <artifactId>onlab-thirdparty</artifactId> |
| 43 | </dependency> | 43 | </dependency> |
| 44 | 44 | ||
| 45 | + <dependency> | ||
| 46 | + <groupId>org.onlab.onos</groupId> | ||
| 47 | + <artifactId>onlab-misc</artifactId> | ||
| 48 | + </dependency> | ||
| 49 | + | ||
| 50 | + <dependency> | ||
| 51 | + <groupId>org.easymock</groupId> | ||
| 52 | + <artifactId>easymock</artifactId> | ||
| 53 | + <scope>test</scope> | ||
| 54 | + </dependency> | ||
| 45 | </dependencies> | 55 | </dependencies> |
| 46 | 56 | ||
| 47 | </project> | 57 | </project> | ... | ... |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import static org.hamcrest.Matchers.is; | ||
| 4 | +import static org.hamcrest.Matchers.not; | ||
| 5 | +import static org.junit.Assert.assertThat; | ||
| 6 | + | ||
| 7 | +import java.util.ArrayList; | ||
| 8 | + | ||
| 9 | +import org.junit.Test; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Unit tests for the BgpRouteEntry.AsPath class. | ||
| 13 | + */ | ||
| 14 | +public class AsPathTest { | ||
| 15 | + /** | ||
| 16 | + * Generates an AS Path. | ||
| 17 | + * | ||
| 18 | + * @return a generated AS Path | ||
| 19 | + */ | ||
| 20 | + private BgpRouteEntry.AsPath generateAsPath() { | ||
| 21 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 22 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 23 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 24 | + segmentAsNumbers1.add((long) 1); | ||
| 25 | + segmentAsNumbers1.add((long) 2); | ||
| 26 | + segmentAsNumbers1.add((long) 3); | ||
| 27 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 28 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 29 | + pathSegments.add(pathSegment1); | ||
| 30 | + // | ||
| 31 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 32 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 33 | + segmentAsNumbers2.add((long) 4); | ||
| 34 | + segmentAsNumbers2.add((long) 5); | ||
| 35 | + segmentAsNumbers2.add((long) 6); | ||
| 36 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 37 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 38 | + pathSegments.add(pathSegment2); | ||
| 39 | + // | ||
| 40 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 41 | + | ||
| 42 | + return asPath; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * Tests valid class constructor. | ||
| 47 | + */ | ||
| 48 | + @Test | ||
| 49 | + public void testConstructor() { | ||
| 50 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
| 51 | + | ||
| 52 | + String expectedString = | ||
| 53 | + "AsPath{pathSegments=" + | ||
| 54 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
| 55 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}"; | ||
| 56 | + assertThat(asPath.toString(), is(expectedString)); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + /** | ||
| 60 | + * Tests invalid class constructor for null Path Segments. | ||
| 61 | + */ | ||
| 62 | + @Test(expected = NullPointerException.class) | ||
| 63 | + public void testInvalidConstructorNullPathSegments() { | ||
| 64 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = null; | ||
| 65 | + new BgpRouteEntry.AsPath(pathSegments); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * Tests getting the fields of an AS Path. | ||
| 70 | + */ | ||
| 71 | + @Test | ||
| 72 | + public void testGetFields() { | ||
| 73 | + // Create the fields to compare against | ||
| 74 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 75 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 76 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 77 | + segmentAsNumbers1.add((long) 1); | ||
| 78 | + segmentAsNumbers1.add((long) 2); | ||
| 79 | + segmentAsNumbers1.add((long) 3); | ||
| 80 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 81 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 82 | + pathSegments.add(pathSegment1); | ||
| 83 | + // | ||
| 84 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 85 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 86 | + segmentAsNumbers2.add((long) 4); | ||
| 87 | + segmentAsNumbers2.add((long) 5); | ||
| 88 | + segmentAsNumbers2.add((long) 6); | ||
| 89 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 90 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 91 | + pathSegments.add(pathSegment2); | ||
| 92 | + | ||
| 93 | + // Generate the entry to test | ||
| 94 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
| 95 | + | ||
| 96 | + assertThat(asPath.getPathSegments(), is(pathSegments)); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 100 | + * Tests getting the AS Path Length. | ||
| 101 | + */ | ||
| 102 | + @Test | ||
| 103 | + public void testGetAsPathLength() { | ||
| 104 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
| 105 | + assertThat(asPath.getAsPathLength(), is(4)); | ||
| 106 | + | ||
| 107 | + // Create an empty AS Path | ||
| 108 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 109 | + asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 110 | + assertThat(asPath.getAsPathLength(), is(0)); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + /** | ||
| 114 | + * Tests equality of {@link BgpRouteEntry.AsPath}. | ||
| 115 | + */ | ||
| 116 | + @Test | ||
| 117 | + public void testEquality() { | ||
| 118 | + BgpRouteEntry.AsPath asPath1 = generateAsPath(); | ||
| 119 | + BgpRouteEntry.AsPath asPath2 = generateAsPath(); | ||
| 120 | + | ||
| 121 | + assertThat(asPath1, is(asPath2)); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /** | ||
| 125 | + * Tests non-equality of {@link BgpRouteEntry.AsPath}. | ||
| 126 | + */ | ||
| 127 | + @Test | ||
| 128 | + public void testNonEquality() { | ||
| 129 | + BgpRouteEntry.AsPath asPath1 = generateAsPath(); | ||
| 130 | + | ||
| 131 | + // Setup AS Path 2 | ||
| 132 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 133 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 134 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 135 | + segmentAsNumbers1.add((long) 1); | ||
| 136 | + segmentAsNumbers1.add((long) 2); | ||
| 137 | + segmentAsNumbers1.add((long) 3); | ||
| 138 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 139 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 140 | + pathSegments.add(pathSegment1); | ||
| 141 | + // | ||
| 142 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 143 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 144 | + segmentAsNumbers2.add((long) 4); | ||
| 145 | + segmentAsNumbers2.add((long) 55); // Different | ||
| 146 | + segmentAsNumbers2.add((long) 6); | ||
| 147 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 148 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 149 | + pathSegments.add(pathSegment2); | ||
| 150 | + // | ||
| 151 | + BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments); | ||
| 152 | + | ||
| 153 | + assertThat(asPath1, is(not(asPath2))); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + /** | ||
| 157 | + * Tests object string representation. | ||
| 158 | + */ | ||
| 159 | + @Test | ||
| 160 | + public void testToString() { | ||
| 161 | + BgpRouteEntry.AsPath asPath = generateAsPath(); | ||
| 162 | + | ||
| 163 | + String expectedString = | ||
| 164 | + "AsPath{pathSegments=" + | ||
| 165 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
| 166 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}"; | ||
| 167 | + assertThat(asPath.toString(), is(expectedString)); | ||
| 168 | + } | ||
| 169 | +} |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import static org.easymock.EasyMock.createMock; | ||
| 4 | +import static org.easymock.EasyMock.expect; | ||
| 5 | +import static org.easymock.EasyMock.replay; | ||
| 6 | +import static org.hamcrest.Matchers.is; | ||
| 7 | +import static org.hamcrest.Matchers.not; | ||
| 8 | +import static org.junit.Assert.assertThat; | ||
| 9 | + | ||
| 10 | +import java.util.ArrayList; | ||
| 11 | + | ||
| 12 | +import org.junit.Before; | ||
| 13 | +import org.junit.Test; | ||
| 14 | +import org.onlab.packet.IpAddress; | ||
| 15 | +import org.onlab.packet.IpPrefix; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * Unit tests for the BgpRouteEntry class. | ||
| 19 | + */ | ||
| 20 | +public class BgpRouteEntryTest { | ||
| 21 | + private BgpSession bgpSession; | ||
| 22 | + private static final IpAddress BGP_SESSION_BGP_ID = | ||
| 23 | + IpAddress.valueOf("10.0.0.1"); | ||
| 24 | + private static final IpAddress BGP_SESSION_IP_ADDRESS = | ||
| 25 | + IpAddress.valueOf("20.0.0.1"); | ||
| 26 | + | ||
| 27 | + private BgpSession bgpSession2; | ||
| 28 | + private static final IpAddress BGP_SESSION_BGP_ID2 = | ||
| 29 | + IpAddress.valueOf("10.0.0.2"); | ||
| 30 | + private static final IpAddress BGP_SESSION_IP_ADDRESS2 = | ||
| 31 | + IpAddress.valueOf("20.0.0.1"); | ||
| 32 | + | ||
| 33 | + private BgpSession bgpSession3; | ||
| 34 | + private static final IpAddress BGP_SESSION_BGP_ID3 = | ||
| 35 | + IpAddress.valueOf("10.0.0.1"); | ||
| 36 | + private static final IpAddress BGP_SESSION_IP_ADDRESS3 = | ||
| 37 | + IpAddress.valueOf("20.0.0.2"); | ||
| 38 | + | ||
| 39 | + @Before | ||
| 40 | + public void setUp() throws Exception { | ||
| 41 | + // Mock objects for testing | ||
| 42 | + bgpSession = createMock(BgpSession.class); | ||
| 43 | + bgpSession2 = createMock(BgpSession.class); | ||
| 44 | + bgpSession3 = createMock(BgpSession.class); | ||
| 45 | + | ||
| 46 | + // Setup the BGP Sessions | ||
| 47 | + expect(bgpSession.getRemoteBgpId()) | ||
| 48 | + .andReturn(BGP_SESSION_BGP_ID).anyTimes(); | ||
| 49 | + expect(bgpSession.getRemoteIp4Address()) | ||
| 50 | + .andReturn(BGP_SESSION_IP_ADDRESS).anyTimes(); | ||
| 51 | + // | ||
| 52 | + expect(bgpSession2.getRemoteBgpId()) | ||
| 53 | + .andReturn(BGP_SESSION_BGP_ID2).anyTimes(); | ||
| 54 | + expect(bgpSession2.getRemoteIp4Address()) | ||
| 55 | + .andReturn(BGP_SESSION_IP_ADDRESS2).anyTimes(); | ||
| 56 | + // | ||
| 57 | + expect(bgpSession3.getRemoteBgpId()) | ||
| 58 | + .andReturn(BGP_SESSION_BGP_ID3).anyTimes(); | ||
| 59 | + expect(bgpSession3.getRemoteIp4Address()) | ||
| 60 | + .andReturn(BGP_SESSION_IP_ADDRESS3).anyTimes(); | ||
| 61 | + | ||
| 62 | + replay(bgpSession); | ||
| 63 | + replay(bgpSession2); | ||
| 64 | + replay(bgpSession3); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * Generates a BGP Route Entry. | ||
| 69 | + * | ||
| 70 | + * @return a generated BGP Route Entry | ||
| 71 | + */ | ||
| 72 | + private BgpRouteEntry generateBgpRouteEntry() { | ||
| 73 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 74 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 75 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 76 | + // Setup the AS Path | ||
| 77 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 78 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 79 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 80 | + segmentAsNumbers1.add((long) 1); | ||
| 81 | + segmentAsNumbers1.add((long) 2); | ||
| 82 | + segmentAsNumbers1.add((long) 3); | ||
| 83 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 84 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 85 | + pathSegments.add(pathSegment1); | ||
| 86 | + // | ||
| 87 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 88 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 89 | + segmentAsNumbers2.add((long) 4); | ||
| 90 | + segmentAsNumbers2.add((long) 5); | ||
| 91 | + segmentAsNumbers2.add((long) 6); | ||
| 92 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 93 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 94 | + pathSegments.add(pathSegment2); | ||
| 95 | + // | ||
| 96 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 97 | + // | ||
| 98 | + long localPref = 100; | ||
| 99 | + long multiExitDisc = 20; | ||
| 100 | + | ||
| 101 | + BgpRouteEntry bgpRouteEntry = | ||
| 102 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 103 | + localPref); | ||
| 104 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
| 105 | + | ||
| 106 | + return bgpRouteEntry; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + /** | ||
| 110 | + * Tests valid class constructor. | ||
| 111 | + */ | ||
| 112 | + @Test | ||
| 113 | + public void testConstructor() { | ||
| 114 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 115 | + | ||
| 116 | + String expectedString = | ||
| 117 | + "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " + | ||
| 118 | + "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" + | ||
| 119 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
| 120 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " + | ||
| 121 | + "localPref=100, multiExitDisc=20}"; | ||
| 122 | + assertThat(bgpRouteEntry.toString(), is(expectedString)); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + /** | ||
| 126 | + * Tests invalid class constructor for null BGP Session. | ||
| 127 | + */ | ||
| 128 | + @Test(expected = NullPointerException.class) | ||
| 129 | + public void testInvalidConstructorNullBgpSession() { | ||
| 130 | + BgpSession bgpSessionNull = null; | ||
| 131 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 132 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 133 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 134 | + // Setup the AS Path | ||
| 135 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 136 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 137 | + // | ||
| 138 | + long localPref = 100; | ||
| 139 | + | ||
| 140 | + new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath, | ||
| 141 | + localPref); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + /** | ||
| 145 | + * Tests invalid class constructor for null AS Path. | ||
| 146 | + */ | ||
| 147 | + @Test(expected = NullPointerException.class) | ||
| 148 | + public void testInvalidConstructorNullAsPath() { | ||
| 149 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 150 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 151 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 152 | + BgpRouteEntry.AsPath asPath = null; | ||
| 153 | + long localPref = 100; | ||
| 154 | + | ||
| 155 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 156 | + localPref); | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + /** | ||
| 160 | + * Tests getting the fields of a BGP route entry. | ||
| 161 | + */ | ||
| 162 | + @Test | ||
| 163 | + public void testGetFields() { | ||
| 164 | + // Create the fields to compare against | ||
| 165 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 166 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 167 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 168 | + // Setup the AS Path | ||
| 169 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 170 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 171 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 172 | + segmentAsNumbers1.add((long) 1); | ||
| 173 | + segmentAsNumbers1.add((long) 2); | ||
| 174 | + segmentAsNumbers1.add((long) 3); | ||
| 175 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 176 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 177 | + pathSegments.add(pathSegment1); | ||
| 178 | + // | ||
| 179 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 180 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 181 | + segmentAsNumbers2.add((long) 4); | ||
| 182 | + segmentAsNumbers2.add((long) 5); | ||
| 183 | + segmentAsNumbers2.add((long) 6); | ||
| 184 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 185 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 186 | + pathSegments.add(pathSegment2); | ||
| 187 | + // | ||
| 188 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 189 | + // | ||
| 190 | + long localPref = 100; | ||
| 191 | + long multiExitDisc = 20; | ||
| 192 | + | ||
| 193 | + // Generate the entry to test | ||
| 194 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 195 | + | ||
| 196 | + assertThat(bgpRouteEntry.prefix(), is(prefix)); | ||
| 197 | + assertThat(bgpRouteEntry.nextHop(), is(nextHop)); | ||
| 198 | + assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession)); | ||
| 199 | + assertThat(bgpRouteEntry.getOrigin(), is(origin)); | ||
| 200 | + assertThat(bgpRouteEntry.getAsPath(), is(asPath)); | ||
| 201 | + assertThat(bgpRouteEntry.getLocalPref(), is(localPref)); | ||
| 202 | + assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc)); | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + /** | ||
| 206 | + * Tests whether a BGP route entry is a local route. | ||
| 207 | + */ | ||
| 208 | + @Test | ||
| 209 | + public void testIsLocalRoute() { | ||
| 210 | + // | ||
| 211 | + // Test non-local route | ||
| 212 | + // | ||
| 213 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 214 | + assertThat(bgpRouteEntry.isLocalRoute(), is(false)); | ||
| 215 | + | ||
| 216 | + // | ||
| 217 | + // Test local route with AS Path that begins with AS_SET | ||
| 218 | + // | ||
| 219 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 220 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 221 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 222 | + // Setup the AS Path | ||
| 223 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 224 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 225 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 226 | + segmentAsNumbers1.add((long) 1); | ||
| 227 | + segmentAsNumbers1.add((long) 2); | ||
| 228 | + segmentAsNumbers1.add((long) 3); | ||
| 229 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 230 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 231 | + pathSegments.add(pathSegment1); | ||
| 232 | + // | ||
| 233 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 234 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 235 | + segmentAsNumbers2.add((long) 4); | ||
| 236 | + segmentAsNumbers2.add((long) 5); | ||
| 237 | + segmentAsNumbers2.add((long) 6); | ||
| 238 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 239 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 240 | + pathSegments.add(pathSegment2); | ||
| 241 | + // | ||
| 242 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 243 | + // | ||
| 244 | + long localPref = 100; | ||
| 245 | + long multiExitDisc = 20; | ||
| 246 | + // | ||
| 247 | + bgpRouteEntry = | ||
| 248 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 249 | + localPref); | ||
| 250 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
| 251 | + assertThat(bgpRouteEntry.isLocalRoute(), is(true)); | ||
| 252 | + | ||
| 253 | + // | ||
| 254 | + // Test local route with empty AS Path | ||
| 255 | + // | ||
| 256 | + pathSegments = new ArrayList<>(); | ||
| 257 | + asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 258 | + bgpRouteEntry = | ||
| 259 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 260 | + localPref); | ||
| 261 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
| 262 | + assertThat(bgpRouteEntry.isLocalRoute(), is(true)); | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + /** | ||
| 266 | + * Tests getting the BGP Neighbor AS number for a route. | ||
| 267 | + */ | ||
| 268 | + @Test | ||
| 269 | + public void testGetNeighborAs() { | ||
| 270 | + // | ||
| 271 | + // Get neighbor AS for non-local route | ||
| 272 | + // | ||
| 273 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 274 | + assertThat(bgpRouteEntry.getNeighborAs(), is((long) 1)); | ||
| 275 | + | ||
| 276 | + // | ||
| 277 | + // Get neighbor AS for a local route | ||
| 278 | + // | ||
| 279 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 280 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 281 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 282 | + // Setup the AS Path | ||
| 283 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 284 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 285 | + // | ||
| 286 | + long localPref = 100; | ||
| 287 | + long multiExitDisc = 20; | ||
| 288 | + // | ||
| 289 | + bgpRouteEntry = | ||
| 290 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 291 | + localPref); | ||
| 292 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
| 293 | + assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0)); | ||
| 294 | + } | ||
| 295 | + | ||
| 296 | + /** | ||
| 297 | + * Tests whether a BGP route entry has AS Path loop. | ||
| 298 | + */ | ||
| 299 | + @Test | ||
| 300 | + public void testHasAsPathLoop() { | ||
| 301 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 302 | + | ||
| 303 | + // Test for loops: test each AS number in the interval [1, 6] | ||
| 304 | + for (int i = 1; i <= 6; i++) { | ||
| 305 | + assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true)); | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + // Test for non-loops | ||
| 309 | + assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false)); | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + /** | ||
| 313 | + * Tests the BGP Decision Process comparison of BGP routes. | ||
| 314 | + */ | ||
| 315 | + @Test | ||
| 316 | + public void testBgpDecisionProcessComparison() { | ||
| 317 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
| 318 | + BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry(); | ||
| 319 | + | ||
| 320 | + // | ||
| 321 | + // Compare two routes that are same | ||
| 322 | + // | ||
| 323 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 324 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true)); | ||
| 325 | + | ||
| 326 | + // | ||
| 327 | + // Compare two routes with different LOCAL_PREF | ||
| 328 | + // | ||
| 329 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 330 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 331 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 332 | + // Setup the AS Path | ||
| 333 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 334 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 335 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 336 | + segmentAsNumbers1.add((long) 1); | ||
| 337 | + segmentAsNumbers1.add((long) 2); | ||
| 338 | + segmentAsNumbers1.add((long) 3); | ||
| 339 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 340 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 341 | + pathSegments.add(pathSegment1); | ||
| 342 | + // | ||
| 343 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 344 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 345 | + segmentAsNumbers2.add((long) 4); | ||
| 346 | + segmentAsNumbers2.add((long) 5); | ||
| 347 | + segmentAsNumbers2.add((long) 6); | ||
| 348 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 349 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 350 | + pathSegments.add(pathSegment2); | ||
| 351 | + // | ||
| 352 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 353 | + // | ||
| 354 | + long localPref = 50; // Different | ||
| 355 | + long multiExitDisc = 20; | ||
| 356 | + bgpRouteEntry2 = | ||
| 357 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 358 | + localPref); | ||
| 359 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 360 | + // | ||
| 361 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 362 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
| 363 | + localPref = bgpRouteEntry1.getLocalPref(); // Restore | ||
| 364 | + | ||
| 365 | + // | ||
| 366 | + // Compare two routes with different AS_PATH length | ||
| 367 | + // | ||
| 368 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>(); | ||
| 369 | + pathSegments2.add(pathSegment1); | ||
| 370 | + // Different AS Path | ||
| 371 | + BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2); | ||
| 372 | + bgpRouteEntry2 = | ||
| 373 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2, | ||
| 374 | + localPref); | ||
| 375 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 376 | + // | ||
| 377 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false)); | ||
| 378 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true)); | ||
| 379 | + | ||
| 380 | + // | ||
| 381 | + // Compare two routes with different ORIGIN | ||
| 382 | + // | ||
| 383 | + origin = BgpConstants.Update.Origin.EGP; // Different | ||
| 384 | + bgpRouteEntry2 = | ||
| 385 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 386 | + localPref); | ||
| 387 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 388 | + // | ||
| 389 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 390 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
| 391 | + origin = bgpRouteEntry1.getOrigin(); // Restore | ||
| 392 | + | ||
| 393 | + // | ||
| 394 | + // Compare two routes with different MULTI_EXIT_DISC | ||
| 395 | + // | ||
| 396 | + multiExitDisc = 10; // Different | ||
| 397 | + bgpRouteEntry2 = | ||
| 398 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 399 | + localPref); | ||
| 400 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 401 | + // | ||
| 402 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 403 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
| 404 | + multiExitDisc = bgpRouteEntry1.getMultiExitDisc(); // Restore | ||
| 405 | + | ||
| 406 | + // | ||
| 407 | + // Compare two routes with different BGP ID | ||
| 408 | + // | ||
| 409 | + bgpRouteEntry2 = | ||
| 410 | + new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath, | ||
| 411 | + localPref); | ||
| 412 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 413 | + // | ||
| 414 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 415 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
| 416 | + | ||
| 417 | + // | ||
| 418 | + // Compare two routes with different BGP address | ||
| 419 | + // | ||
| 420 | + bgpRouteEntry2 = | ||
| 421 | + new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath, | ||
| 422 | + localPref); | ||
| 423 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 424 | + // | ||
| 425 | + assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true)); | ||
| 426 | + assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false)); | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + /** | ||
| 430 | + * Tests equality of {@link BgpRouteEntry}. | ||
| 431 | + */ | ||
| 432 | + @Test | ||
| 433 | + public void testEquality() { | ||
| 434 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
| 435 | + BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry(); | ||
| 436 | + | ||
| 437 | + assertThat(bgpRouteEntry1, is(bgpRouteEntry2)); | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + /** | ||
| 441 | + * Tests non-equality of {@link BgpRouteEntry}. | ||
| 442 | + */ | ||
| 443 | + @Test | ||
| 444 | + public void testNonEquality() { | ||
| 445 | + BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry(); | ||
| 446 | + | ||
| 447 | + // Setup BGP Route 2 | ||
| 448 | + IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24"); | ||
| 449 | + IpAddress nextHop = IpAddress.valueOf("5.6.7.8"); | ||
| 450 | + byte origin = BgpConstants.Update.Origin.IGP; | ||
| 451 | + // Setup the AS Path | ||
| 452 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 453 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 454 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 455 | + segmentAsNumbers1.add((long) 1); | ||
| 456 | + segmentAsNumbers1.add((long) 2); | ||
| 457 | + segmentAsNumbers1.add((long) 3); | ||
| 458 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 459 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 460 | + pathSegments.add(pathSegment1); | ||
| 461 | + // | ||
| 462 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 463 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 464 | + segmentAsNumbers2.add((long) 4); | ||
| 465 | + segmentAsNumbers2.add((long) 5); | ||
| 466 | + segmentAsNumbers2.add((long) 6); | ||
| 467 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 468 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 469 | + pathSegments.add(pathSegment2); | ||
| 470 | + // | ||
| 471 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 472 | + // | ||
| 473 | + long localPref = 500; // Different | ||
| 474 | + long multiExitDisc = 20; | ||
| 475 | + BgpRouteEntry bgpRouteEntry2 = | ||
| 476 | + new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath, | ||
| 477 | + localPref); | ||
| 478 | + bgpRouteEntry2.setMultiExitDisc(multiExitDisc); | ||
| 479 | + | ||
| 480 | + assertThat(bgpRouteEntry1, is(not(bgpRouteEntry2))); | ||
| 481 | + } | ||
| 482 | + | ||
| 483 | + /** | ||
| 484 | + * Tests object string representation. | ||
| 485 | + */ | ||
| 486 | + @Test | ||
| 487 | + public void testToString() { | ||
| 488 | + BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry(); | ||
| 489 | + | ||
| 490 | + String expectedString = | ||
| 491 | + "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " + | ||
| 492 | + "bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" + | ||
| 493 | + "[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " + | ||
| 494 | + "PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " + | ||
| 495 | + "localPref=100, multiExitDisc=20}"; | ||
| 496 | + assertThat(bgpRouteEntry.toString(), is(expectedString)); | ||
| 497 | + } | ||
| 498 | +} |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import static org.hamcrest.Matchers.hasItem; | ||
| 4 | +import static org.hamcrest.Matchers.hasSize; | ||
| 5 | +import static org.hamcrest.Matchers.is; | ||
| 6 | +import static org.hamcrest.Matchers.notNullValue; | ||
| 7 | +import static org.junit.Assert.assertThat; | ||
| 8 | + | ||
| 9 | +import java.net.InetAddress; | ||
| 10 | +import java.net.InetSocketAddress; | ||
| 11 | +import java.net.SocketAddress; | ||
| 12 | +import java.util.ArrayList; | ||
| 13 | +import java.util.Collection; | ||
| 14 | +import java.util.LinkedList; | ||
| 15 | +import java.util.concurrent.Executors; | ||
| 16 | +import java.util.concurrent.TimeUnit; | ||
| 17 | + | ||
| 18 | +import org.jboss.netty.bootstrap.ClientBootstrap; | ||
| 19 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
| 20 | +import org.jboss.netty.channel.Channel; | ||
| 21 | +import org.jboss.netty.channel.ChannelFactory; | ||
| 22 | +import org.jboss.netty.channel.ChannelPipeline; | ||
| 23 | +import org.jboss.netty.channel.ChannelPipelineFactory; | ||
| 24 | +import org.jboss.netty.channel.Channels; | ||
| 25 | +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; | ||
| 26 | +import org.junit.After; | ||
| 27 | +import org.junit.Before; | ||
| 28 | +import org.junit.Test; | ||
| 29 | +import org.onlab.onos.sdnip.RouteListener; | ||
| 30 | +import org.onlab.onos.sdnip.RouteUpdate; | ||
| 31 | +import org.onlab.packet.IpAddress; | ||
| 32 | +import org.onlab.packet.IpPrefix; | ||
| 33 | +import org.onlab.util.TestUtils; | ||
| 34 | +import org.onlab.util.TestUtils.TestUtilsException; | ||
| 35 | + | ||
| 36 | +import com.google.common.net.InetAddresses; | ||
| 37 | + | ||
| 38 | +/** | ||
| 39 | + * Unit tests for the BgpSessionManager class. | ||
| 40 | + */ | ||
| 41 | +public class BgpSessionManagerTest { | ||
| 42 | + private static final IpAddress IP_LOOPBACK_ID = | ||
| 43 | + IpAddress.valueOf("127.0.0.1"); | ||
| 44 | + private static final IpAddress BGP_PEER1_ID = IpAddress.valueOf("10.0.0.1"); | ||
| 45 | + private static final long DEFAULT_LOCAL_PREF = 10; | ||
| 46 | + private static final long DEFAULT_MULTI_EXIT_DISC = 20; | ||
| 47 | + | ||
| 48 | + // The BGP Session Manager to test | ||
| 49 | + private BgpSessionManager bgpSessionManager; | ||
| 50 | + | ||
| 51 | + // Remote Peer state | ||
| 52 | + private ClientBootstrap peerBootstrap; | ||
| 53 | + private TestBgpPeerChannelHandler peerChannelHandler = | ||
| 54 | + new TestBgpPeerChannelHandler(BGP_PEER1_ID, DEFAULT_LOCAL_PREF); | ||
| 55 | + private TestBgpPeerFrameDecoder peerFrameDecoder = | ||
| 56 | + new TestBgpPeerFrameDecoder(); | ||
| 57 | + | ||
| 58 | + // The socket that the Remote Peer should connect to | ||
| 59 | + private InetSocketAddress connectToSocket; | ||
| 60 | + | ||
| 61 | + private final DummyRouteListener dummyRouteListener = | ||
| 62 | + new DummyRouteListener(); | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * Dummy implementation for the RouteListener interface. | ||
| 66 | + */ | ||
| 67 | + private class DummyRouteListener implements RouteListener { | ||
| 68 | + @Override | ||
| 69 | + public void update(RouteUpdate routeUpdate) { | ||
| 70 | + // Nothing to do | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + @Before | ||
| 75 | + public void setUp() throws Exception { | ||
| 76 | + // | ||
| 77 | + // Setup the BGP Session Manager to test, and start listening for BGP | ||
| 78 | + // connections. | ||
| 79 | + // | ||
| 80 | + bgpSessionManager = new BgpSessionManager(dummyRouteListener); | ||
| 81 | + // NOTE: We use port 0 to bind on any available port | ||
| 82 | + bgpSessionManager.startUp(0); | ||
| 83 | + | ||
| 84 | + // Get the port number the BGP Session Manager is listening on | ||
| 85 | + Channel serverChannel = TestUtils.getField(bgpSessionManager, | ||
| 86 | + "serverChannel"); | ||
| 87 | + SocketAddress socketAddress = serverChannel.getLocalAddress(); | ||
| 88 | + InetSocketAddress inetSocketAddress = | ||
| 89 | + (InetSocketAddress) socketAddress; | ||
| 90 | + | ||
| 91 | + // | ||
| 92 | + // Setup the BGP Peer, i.e., the "remote" BGP router that will | ||
| 93 | + // initiate the BGP connection, send BGP UPDATE messages, etc. | ||
| 94 | + // | ||
| 95 | + ChannelFactory channelFactory = | ||
| 96 | + new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), | ||
| 97 | + Executors.newCachedThreadPool()); | ||
| 98 | + ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() { | ||
| 99 | + @Override | ||
| 100 | + public ChannelPipeline getPipeline() throws Exception { | ||
| 101 | + // Setup the transmitting pipeline | ||
| 102 | + ChannelPipeline pipeline = Channels.pipeline(); | ||
| 103 | + pipeline.addLast("TestBgpPeerFrameDecoder", | ||
| 104 | + peerFrameDecoder); | ||
| 105 | + pipeline.addLast("TestBgpPeerChannelHandler", | ||
| 106 | + peerChannelHandler); | ||
| 107 | + return pipeline; | ||
| 108 | + } | ||
| 109 | + }; | ||
| 110 | + | ||
| 111 | + peerBootstrap = new ClientBootstrap(channelFactory); | ||
| 112 | + peerBootstrap.setOption("child.keepAlive", true); | ||
| 113 | + peerBootstrap.setOption("child.tcpNoDelay", true); | ||
| 114 | + peerBootstrap.setPipelineFactory(pipelineFactory); | ||
| 115 | + | ||
| 116 | + InetAddress connectToAddress = InetAddresses.forString("127.0.0.1"); | ||
| 117 | + connectToSocket = new InetSocketAddress(connectToAddress, | ||
| 118 | + inetSocketAddress.getPort()); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @After | ||
| 122 | + public void tearDown() throws Exception { | ||
| 123 | + bgpSessionManager.shutDown(); | ||
| 124 | + bgpSessionManager = null; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + /** | ||
| 128 | + * Gets BGP RIB-IN routes by waiting until they are received. | ||
| 129 | + * <p/> | ||
| 130 | + * NOTE: We keep checking once a second the number of received routes, | ||
| 131 | + * up to 5 seconds. | ||
| 132 | + * | ||
| 133 | + * @param bgpSession the BGP session that is expected to receive the | ||
| 134 | + * routes | ||
| 135 | + * @param expectedRoutes the expected number of routes | ||
| 136 | + * @return the BGP RIB-IN routes as received within the expected | ||
| 137 | + * time interval | ||
| 138 | + */ | ||
| 139 | + private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession, | ||
| 140 | + long expectedRoutes) | ||
| 141 | + throws InterruptedException { | ||
| 142 | + Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn(); | ||
| 143 | + | ||
| 144 | + final int maxChecks = 5; // Max wait of 5 seconds | ||
| 145 | + for (int i = 0; i < maxChecks; i++) { | ||
| 146 | + if (bgpRibIn.size() == expectedRoutes) { | ||
| 147 | + break; | ||
| 148 | + } | ||
| 149 | + Thread.sleep(1000); | ||
| 150 | + bgpRibIn = bgpSession.getBgpRibIn(); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + return bgpRibIn; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + /** | ||
| 157 | + * Gets BGP merged routes by waiting until they are received. | ||
| 158 | + * <p/> | ||
| 159 | + * NOTE: We keep checking once a second the number of received routes, | ||
| 160 | + * up to 5 seconds. | ||
| 161 | + * | ||
| 162 | + * @param expectedRoutes the expected number of routes | ||
| 163 | + * @return the BGP Session Manager routes as received within the expected | ||
| 164 | + * time interval | ||
| 165 | + */ | ||
| 166 | + private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes) | ||
| 167 | + throws InterruptedException { | ||
| 168 | + Collection<BgpRouteEntry> bgpRoutes = bgpSessionManager.getBgpRoutes(); | ||
| 169 | + | ||
| 170 | + final int maxChecks = 5; // Max wait of 5 seconds | ||
| 171 | + for (int i = 0; i < maxChecks; i++) { | ||
| 172 | + if (bgpRoutes.size() == expectedRoutes) { | ||
| 173 | + break; | ||
| 174 | + } | ||
| 175 | + Thread.sleep(1000); | ||
| 176 | + bgpRoutes = bgpSessionManager.getBgpRoutes(); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + return bgpRoutes; | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + /** | ||
| 183 | + * Tests that the BGP OPEN messages have been exchanged, followed by | ||
| 184 | + * KEEPALIVE. | ||
| 185 | + * <p> | ||
| 186 | + * The BGP Peer opens the sessions and transmits OPEN Message, eventually | ||
| 187 | + * followed by KEEPALIVE. The tested BGP listener should respond by | ||
| 188 | + * OPEN Message, followed by KEEPALIVE. | ||
| 189 | + * | ||
| 190 | + * @throws TestUtilsException TestUtils error | ||
| 191 | + */ | ||
| 192 | + @Test | ||
| 193 | + public void testExchangedBgpOpenMessages() | ||
| 194 | + throws InterruptedException, TestUtilsException { | ||
| 195 | + // Initiate the connection | ||
| 196 | + peerBootstrap.connect(connectToSocket); | ||
| 197 | + | ||
| 198 | + // Wait until the OPEN message is received | ||
| 199 | + peerFrameDecoder.receivedOpenMessageLatch.await(2000, | ||
| 200 | + TimeUnit.MILLISECONDS); | ||
| 201 | + // Wait until the KEEPALIVE message is received | ||
| 202 | + peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000, | ||
| 203 | + TimeUnit.MILLISECONDS); | ||
| 204 | + | ||
| 205 | + // | ||
| 206 | + // Test the fields from the BGP OPEN message: | ||
| 207 | + // BGP version, AS number, BGP ID | ||
| 208 | + // | ||
| 209 | + assertThat(peerFrameDecoder.remoteBgpVersion, | ||
| 210 | + is(BgpConstants.BGP_VERSION)); | ||
| 211 | + assertThat(peerFrameDecoder.remoteAs, | ||
| 212 | + is(TestBgpPeerChannelHandler.PEER_AS)); | ||
| 213 | + assertThat(peerFrameDecoder.remoteBgpIdentifier, is(IP_LOOPBACK_ID)); | ||
| 214 | + | ||
| 215 | + // | ||
| 216 | + // Test that a BgpSession instance has been created | ||
| 217 | + // | ||
| 218 | + assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID)); | ||
| 219 | + assertThat(bgpSessionManager.getBgpSessions(), hasSize(1)); | ||
| 220 | + BgpSession bgpSession = | ||
| 221 | + bgpSessionManager.getBgpSessions().iterator().next(); | ||
| 222 | + assertThat(bgpSession, notNullValue()); | ||
| 223 | + long sessionAs = TestUtils.getField(bgpSession, "localAs"); | ||
| 224 | + assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS)); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + /** | ||
| 228 | + * Tests that the BGP UPDATE messages have been received and processed. | ||
| 229 | + */ | ||
| 230 | + @Test | ||
| 231 | + public void testProcessedBgpUpdateMessages() throws InterruptedException { | ||
| 232 | + BgpSession bgpSession; | ||
| 233 | + IpAddress nextHopRouter; | ||
| 234 | + BgpRouteEntry bgpRouteEntry; | ||
| 235 | + ChannelBuffer message; | ||
| 236 | + Collection<BgpRouteEntry> bgpRibIn; | ||
| 237 | + Collection<BgpRouteEntry> bgpRoutes; | ||
| 238 | + | ||
| 239 | + // Initiate the connection | ||
| 240 | + peerBootstrap.connect(connectToSocket); | ||
| 241 | + | ||
| 242 | + // Wait until the OPEN message is received | ||
| 243 | + peerFrameDecoder.receivedOpenMessageLatch.await(2000, | ||
| 244 | + TimeUnit.MILLISECONDS); | ||
| 245 | + // Wait until the KEEPALIVE message is received | ||
| 246 | + peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000, | ||
| 247 | + TimeUnit.MILLISECONDS); | ||
| 248 | + | ||
| 249 | + // Get the BGP Session handler | ||
| 250 | + bgpSession = bgpSessionManager.getBgpSessions().iterator().next(); | ||
| 251 | + | ||
| 252 | + // Prepare routes to add/delete | ||
| 253 | + nextHopRouter = IpAddress.valueOf("10.20.30.40"); | ||
| 254 | + Collection<IpPrefix> addedRoutes = new LinkedList<>(); | ||
| 255 | + Collection<IpPrefix> withdrawnRoutes = new LinkedList<>(); | ||
| 256 | + addedRoutes.add(IpPrefix.valueOf("0.0.0.0/0")); | ||
| 257 | + addedRoutes.add(IpPrefix.valueOf("20.0.0.0/8")); | ||
| 258 | + addedRoutes.add(IpPrefix.valueOf("30.0.0.0/16")); | ||
| 259 | + addedRoutes.add(IpPrefix.valueOf("40.0.0.0/24")); | ||
| 260 | + addedRoutes.add(IpPrefix.valueOf("50.0.0.0/32")); | ||
| 261 | + withdrawnRoutes.add(IpPrefix.valueOf("60.0.0.0/8")); | ||
| 262 | + withdrawnRoutes.add(IpPrefix.valueOf("70.0.0.0/16")); | ||
| 263 | + withdrawnRoutes.add(IpPrefix.valueOf("80.0.0.0/24")); | ||
| 264 | + withdrawnRoutes.add(IpPrefix.valueOf("90.0.0.0/32")); | ||
| 265 | + // Write the routes | ||
| 266 | + message = peerChannelHandler.prepareBgpUpdate(nextHopRouter, | ||
| 267 | + addedRoutes, | ||
| 268 | + withdrawnRoutes); | ||
| 269 | + peerChannelHandler.savedCtx.getChannel().write(message); | ||
| 270 | + | ||
| 271 | + // Check that the routes have been received, processed and stored | ||
| 272 | + bgpRibIn = waitForBgpRibIn(bgpSession, 5); | ||
| 273 | + assertThat(bgpRibIn, hasSize(5)); | ||
| 274 | + bgpRoutes = waitForBgpRoutes(5); | ||
| 275 | + assertThat(bgpRoutes, hasSize(5)); | ||
| 276 | + | ||
| 277 | + // Setup the AS Path | ||
| 278 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
| 279 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 280 | + ArrayList<Long> segmentAsNumbers1 = new ArrayList<>(); | ||
| 281 | + segmentAsNumbers1.add((long) 65010); | ||
| 282 | + segmentAsNumbers1.add((long) 65020); | ||
| 283 | + segmentAsNumbers1.add((long) 65030); | ||
| 284 | + BgpRouteEntry.PathSegment pathSegment1 = | ||
| 285 | + new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1); | ||
| 286 | + pathSegments.add(pathSegment1); | ||
| 287 | + // | ||
| 288 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 289 | + ArrayList<Long> segmentAsNumbers2 = new ArrayList<>(); | ||
| 290 | + segmentAsNumbers2.add((long) 65041); | ||
| 291 | + segmentAsNumbers2.add((long) 65042); | ||
| 292 | + segmentAsNumbers2.add((long) 65043); | ||
| 293 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 294 | + new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2); | ||
| 295 | + pathSegments.add(pathSegment2); | ||
| 296 | + // | ||
| 297 | + BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments); | ||
| 298 | + | ||
| 299 | + // | ||
| 300 | + bgpRouteEntry = | ||
| 301 | + new BgpRouteEntry(bgpSession, | ||
| 302 | + IpPrefix.valueOf("0.0.0.0/0"), | ||
| 303 | + nextHopRouter, | ||
| 304 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 305 | + asPath, | ||
| 306 | + DEFAULT_LOCAL_PREF); | ||
| 307 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 308 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 309 | + // | ||
| 310 | + bgpRouteEntry = | ||
| 311 | + new BgpRouteEntry(bgpSession, | ||
| 312 | + IpPrefix.valueOf("20.0.0.0/8"), | ||
| 313 | + nextHopRouter, | ||
| 314 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 315 | + asPath, | ||
| 316 | + DEFAULT_LOCAL_PREF); | ||
| 317 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 318 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 319 | + // | ||
| 320 | + bgpRouteEntry = | ||
| 321 | + new BgpRouteEntry(bgpSession, | ||
| 322 | + IpPrefix.valueOf("30.0.0.0/16"), | ||
| 323 | + nextHopRouter, | ||
| 324 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 325 | + asPath, | ||
| 326 | + DEFAULT_LOCAL_PREF); | ||
| 327 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 328 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 329 | + // | ||
| 330 | + bgpRouteEntry = | ||
| 331 | + new BgpRouteEntry(bgpSession, | ||
| 332 | + IpPrefix.valueOf("40.0.0.0/24"), | ||
| 333 | + nextHopRouter, | ||
| 334 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 335 | + asPath, | ||
| 336 | + DEFAULT_LOCAL_PREF); | ||
| 337 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 338 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 339 | + // | ||
| 340 | + bgpRouteEntry = | ||
| 341 | + new BgpRouteEntry(bgpSession, | ||
| 342 | + IpPrefix.valueOf("50.0.0.0/32"), | ||
| 343 | + nextHopRouter, | ||
| 344 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 345 | + asPath, | ||
| 346 | + DEFAULT_LOCAL_PREF); | ||
| 347 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 348 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 349 | + | ||
| 350 | + // Delete some routes | ||
| 351 | + addedRoutes = new LinkedList<>(); | ||
| 352 | + withdrawnRoutes = new LinkedList<>(); | ||
| 353 | + withdrawnRoutes.add(IpPrefix.valueOf("0.0.0.0/0")); | ||
| 354 | + withdrawnRoutes.add(IpPrefix.valueOf("50.0.0.0/32")); | ||
| 355 | + | ||
| 356 | + // Write the routes | ||
| 357 | + message = peerChannelHandler.prepareBgpUpdate(nextHopRouter, | ||
| 358 | + addedRoutes, | ||
| 359 | + withdrawnRoutes); | ||
| 360 | + peerChannelHandler.savedCtx.getChannel().write(message); | ||
| 361 | + | ||
| 362 | + // Check that the routes have been received, processed and stored | ||
| 363 | + bgpRibIn = waitForBgpRibIn(bgpSession, 3); | ||
| 364 | + assertThat(bgpRibIn, hasSize(3)); | ||
| 365 | + bgpRoutes = waitForBgpRoutes(3); | ||
| 366 | + assertThat(bgpRoutes, hasSize(3)); | ||
| 367 | + // | ||
| 368 | + bgpRouteEntry = | ||
| 369 | + new BgpRouteEntry(bgpSession, | ||
| 370 | + IpPrefix.valueOf("20.0.0.0/8"), | ||
| 371 | + nextHopRouter, | ||
| 372 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 373 | + asPath, | ||
| 374 | + DEFAULT_LOCAL_PREF); | ||
| 375 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 376 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 377 | + // | ||
| 378 | + bgpRouteEntry = | ||
| 379 | + new BgpRouteEntry(bgpSession, | ||
| 380 | + IpPrefix.valueOf("30.0.0.0/16"), | ||
| 381 | + nextHopRouter, | ||
| 382 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 383 | + asPath, | ||
| 384 | + DEFAULT_LOCAL_PREF); | ||
| 385 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 386 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 387 | + // | ||
| 388 | + bgpRouteEntry = | ||
| 389 | + new BgpRouteEntry(bgpSession, | ||
| 390 | + IpPrefix.valueOf("40.0.0.0/24"), | ||
| 391 | + nextHopRouter, | ||
| 392 | + (byte) BgpConstants.Update.Origin.IGP, | ||
| 393 | + asPath, | ||
| 394 | + DEFAULT_LOCAL_PREF); | ||
| 395 | + bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC); | ||
| 396 | + assertThat(bgpRibIn, hasItem(bgpRouteEntry)); | ||
| 397 | + | ||
| 398 | + // Close the channel and test there are no routes | ||
| 399 | + peerChannelHandler.closeChannel(); | ||
| 400 | + bgpRoutes = waitForBgpRoutes(0); | ||
| 401 | + assertThat(bgpRoutes, hasSize(0)); | ||
| 402 | + } | ||
| 403 | +} |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import static org.hamcrest.Matchers.is; | ||
| 4 | +import static org.hamcrest.Matchers.not; | ||
| 5 | +import static org.junit.Assert.assertThat; | ||
| 6 | + | ||
| 7 | +import java.util.ArrayList; | ||
| 8 | + | ||
| 9 | +import org.junit.Test; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Unit tests for the BgpRouteEntry.PathSegment class. | ||
| 13 | + */ | ||
| 14 | +public class PathSegmentTest { | ||
| 15 | + /** | ||
| 16 | + * Generates a Path Segment. | ||
| 17 | + * | ||
| 18 | + * @return a generated PathSegment | ||
| 19 | + */ | ||
| 20 | + private BgpRouteEntry.PathSegment generatePathSegment() { | ||
| 21 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 22 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
| 23 | + segmentAsNumbers.add((long) 1); | ||
| 24 | + segmentAsNumbers.add((long) 2); | ||
| 25 | + segmentAsNumbers.add((long) 3); | ||
| 26 | + BgpRouteEntry.PathSegment pathSegment = | ||
| 27 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
| 28 | + | ||
| 29 | + return pathSegment; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * Tests valid class constructor. | ||
| 34 | + */ | ||
| 35 | + @Test | ||
| 36 | + public void testConstructor() { | ||
| 37 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
| 38 | + | ||
| 39 | + String expectedString = | ||
| 40 | + "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}"; | ||
| 41 | + assertThat(pathSegment.toString(), is(expectedString)); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * Tests invalid class constructor for null Segment AS Numbers. | ||
| 46 | + */ | ||
| 47 | + @Test(expected = NullPointerException.class) | ||
| 48 | + public void testInvalidConstructorNullSegmentAsNumbers() { | ||
| 49 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 50 | + ArrayList<Long> segmentAsNumbers = null; | ||
| 51 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * Tests getting the fields of a Path Segment. | ||
| 56 | + */ | ||
| 57 | + @Test | ||
| 58 | + public void testGetFields() { | ||
| 59 | + // Create the fields to compare against | ||
| 60 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 61 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
| 62 | + segmentAsNumbers.add((long) 1); | ||
| 63 | + segmentAsNumbers.add((long) 2); | ||
| 64 | + segmentAsNumbers.add((long) 3); | ||
| 65 | + | ||
| 66 | + // Generate the entry to test | ||
| 67 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
| 68 | + | ||
| 69 | + assertThat(pathSegment.getType(), is(pathSegmentType)); | ||
| 70 | + assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers)); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * Tests equality of {@link BgpRouteEntry.PathSegment}. | ||
| 75 | + */ | ||
| 76 | + @Test | ||
| 77 | + public void testEquality() { | ||
| 78 | + BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment(); | ||
| 79 | + BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment(); | ||
| 80 | + | ||
| 81 | + assertThat(pathSegment1, is(pathSegment2)); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + /** | ||
| 85 | + * Tests non-equality of {@link BgpRouteEntry.PathSegment}. | ||
| 86 | + */ | ||
| 87 | + @Test | ||
| 88 | + public void testNonEquality() { | ||
| 89 | + BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment(); | ||
| 90 | + | ||
| 91 | + // Setup Path Segment 2 | ||
| 92 | + byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 93 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
| 94 | + segmentAsNumbers.add((long) 1); | ||
| 95 | + segmentAsNumbers.add((long) 22); // Different | ||
| 96 | + segmentAsNumbers.add((long) 3); | ||
| 97 | + // | ||
| 98 | + BgpRouteEntry.PathSegment pathSegment2 = | ||
| 99 | + new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers); | ||
| 100 | + | ||
| 101 | + assertThat(pathSegment1, is(not(pathSegment2))); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * Tests object string representation. | ||
| 106 | + */ | ||
| 107 | + @Test | ||
| 108 | + public void testToString() { | ||
| 109 | + BgpRouteEntry.PathSegment pathSegment = generatePathSegment(); | ||
| 110 | + | ||
| 111 | + String expectedString = | ||
| 112 | + "PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}"; | ||
| 113 | + assertThat(pathSegment.toString(), is(expectedString)); | ||
| 114 | + } | ||
| 115 | +} |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import java.util.Collection; | ||
| 4 | + | ||
| 5 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
| 6 | +import org.jboss.netty.buffer.ChannelBuffers; | ||
| 7 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
| 8 | +import org.jboss.netty.channel.ChannelStateEvent; | ||
| 9 | +import org.jboss.netty.channel.SimpleChannelHandler; | ||
| 10 | +import org.onlab.packet.IpAddress; | ||
| 11 | +import org.onlab.packet.IpPrefix; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Class for handling the remote BGP Peer session. | ||
| 15 | + */ | ||
| 16 | +class TestBgpPeerChannelHandler extends SimpleChannelHandler { | ||
| 17 | + static final long PEER_AS = 65001; | ||
| 18 | + static final int PEER_HOLDTIME = 120; // 120 seconds | ||
| 19 | + final IpAddress bgpId; // The BGP ID | ||
| 20 | + final long localPref; // Local preference for routes | ||
| 21 | + final long multiExitDisc = 20; // MED value | ||
| 22 | + | ||
| 23 | + ChannelHandlerContext savedCtx; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * Constructor for given BGP ID. | ||
| 27 | + * | ||
| 28 | + * @param bgpId the BGP ID to use | ||
| 29 | + * @param localPref the local preference for the routes to use | ||
| 30 | + */ | ||
| 31 | + TestBgpPeerChannelHandler(IpAddress bgpId, | ||
| 32 | + long localPref) { | ||
| 33 | + this.bgpId = bgpId; | ||
| 34 | + this.localPref = localPref; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * Closes the channel. | ||
| 39 | + */ | ||
| 40 | + void closeChannel() { | ||
| 41 | + savedCtx.getChannel().close(); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Override | ||
| 45 | + public void channelConnected(ChannelHandlerContext ctx, | ||
| 46 | + ChannelStateEvent channelEvent) { | ||
| 47 | + this.savedCtx = ctx; | ||
| 48 | + // Prepare and transmit BGP OPEN message | ||
| 49 | + ChannelBuffer message = prepareBgpOpen(); | ||
| 50 | + ctx.getChannel().write(message); | ||
| 51 | + | ||
| 52 | + // Prepare and transmit BGP KEEPALIVE message | ||
| 53 | + message = prepareBgpKeepalive(); | ||
| 54 | + ctx.getChannel().write(message); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Override | ||
| 58 | + public void channelDisconnected(ChannelHandlerContext ctx, | ||
| 59 | + ChannelStateEvent channelEvent) { | ||
| 60 | + // Nothing to do | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * Prepares BGP OPEN message. | ||
| 65 | + * | ||
| 66 | + * @return the message to transmit (BGP header included) | ||
| 67 | + */ | ||
| 68 | + ChannelBuffer prepareBgpOpen() { | ||
| 69 | + ChannelBuffer message = | ||
| 70 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 71 | + message.writeByte(BgpConstants.BGP_VERSION); | ||
| 72 | + message.writeShort((int) PEER_AS); | ||
| 73 | + message.writeShort(PEER_HOLDTIME); | ||
| 74 | + message.writeInt(bgpId.toInt()); | ||
| 75 | + message.writeByte(0); // No Optional Parameters | ||
| 76 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * Prepares BGP UPDATE message. | ||
| 81 | + * | ||
| 82 | + * @param nextHopRouter the next-hop router address for the routes to add | ||
| 83 | + * @param addedRoutes the routes to add | ||
| 84 | + * @param withdrawnRoutes the routes to withdraw | ||
| 85 | + * @return the message to transmit (BGP header included) | ||
| 86 | + */ | ||
| 87 | + ChannelBuffer prepareBgpUpdate(IpAddress nextHopRouter, | ||
| 88 | + Collection<IpPrefix> addedRoutes, | ||
| 89 | + Collection<IpPrefix> withdrawnRoutes) { | ||
| 90 | + int attrFlags; | ||
| 91 | + ChannelBuffer message = | ||
| 92 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 93 | + ChannelBuffer pathAttributes = | ||
| 94 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 95 | + | ||
| 96 | + // Encode the Withdrawn Routes | ||
| 97 | + ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes); | ||
| 98 | + message.writeShort(encodedPrefixes.readableBytes()); | ||
| 99 | + message.writeBytes(encodedPrefixes); | ||
| 100 | + | ||
| 101 | + // Encode the Path Attributes | ||
| 102 | + // ORIGIN: IGP | ||
| 103 | + attrFlags = 0x40; // Transitive flag | ||
| 104 | + pathAttributes.writeByte(attrFlags); | ||
| 105 | + pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE); | ||
| 106 | + pathAttributes.writeByte(1); // Data length | ||
| 107 | + pathAttributes.writeByte(BgpConstants.Update.Origin.IGP); | ||
| 108 | + // AS_PATH: Two Path Segments of 3 ASes each | ||
| 109 | + attrFlags = 0x40; // Transitive flag | ||
| 110 | + pathAttributes.writeByte(attrFlags); | ||
| 111 | + pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE); | ||
| 112 | + pathAttributes.writeByte(16); // Data length | ||
| 113 | + byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE; | ||
| 114 | + pathAttributes.writeByte(pathSegmentType1); | ||
| 115 | + pathAttributes.writeByte(3); // Three ASes | ||
| 116 | + pathAttributes.writeShort(65010); // AS=65010 | ||
| 117 | + pathAttributes.writeShort(65020); // AS=65020 | ||
| 118 | + pathAttributes.writeShort(65030); // AS=65030 | ||
| 119 | + byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET; | ||
| 120 | + pathAttributes.writeByte(pathSegmentType2); | ||
| 121 | + pathAttributes.writeByte(3); // Three ASes | ||
| 122 | + pathAttributes.writeShort(65041); // AS=65041 | ||
| 123 | + pathAttributes.writeShort(65042); // AS=65042 | ||
| 124 | + pathAttributes.writeShort(65043); // AS=65043 | ||
| 125 | + // NEXT_HOP: nextHopRouter | ||
| 126 | + attrFlags = 0x40; // Transitive flag | ||
| 127 | + pathAttributes.writeByte(attrFlags); | ||
| 128 | + pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE); | ||
| 129 | + pathAttributes.writeByte(4); // Data length | ||
| 130 | + pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router | ||
| 131 | + // LOCAL_PREF: localPref | ||
| 132 | + attrFlags = 0x40; // Transitive flag | ||
| 133 | + pathAttributes.writeByte(attrFlags); | ||
| 134 | + pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE); | ||
| 135 | + pathAttributes.writeByte(4); // Data length | ||
| 136 | + pathAttributes.writeInt((int) localPref); // Preference value | ||
| 137 | + // MULTI_EXIT_DISC: multiExitDisc | ||
| 138 | + attrFlags = 0x80; // Optional | ||
| 139 | + // Non-Transitive flag | ||
| 140 | + pathAttributes.writeByte(attrFlags); | ||
| 141 | + pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE); | ||
| 142 | + pathAttributes.writeByte(4); // Data length | ||
| 143 | + pathAttributes.writeInt((int) multiExitDisc); // Preference value | ||
| 144 | + // The NLRI prefixes | ||
| 145 | + encodedPrefixes = encodePackedPrefixes(addedRoutes); | ||
| 146 | + | ||
| 147 | + // Write the Path Attributes, beginning with its length | ||
| 148 | + message.writeShort(pathAttributes.readableBytes()); | ||
| 149 | + message.writeBytes(pathAttributes); | ||
| 150 | + message.writeBytes(encodedPrefixes); | ||
| 151 | + | ||
| 152 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE, message); | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + /** | ||
| 156 | + * Encodes a collection of IPv4 network prefixes in a packed format. | ||
| 157 | + * <p> | ||
| 158 | + * The IPv4 prefixes are encoded in the form: | ||
| 159 | + * <Length, Prefix> where Length is the length in bits of the IPv4 prefix, | ||
| 160 | + * and Prefix is the IPv4 prefix (padded with trailing bits to the end | ||
| 161 | + * of an octet). | ||
| 162 | + * | ||
| 163 | + * @param prefixes the prefixes to encode | ||
| 164 | + * @return the buffer with the encoded prefixes | ||
| 165 | + */ | ||
| 166 | + private ChannelBuffer encodePackedPrefixes(Collection<IpPrefix> prefixes) { | ||
| 167 | + ChannelBuffer message = | ||
| 168 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 169 | + | ||
| 170 | + // Write each of the prefixes | ||
| 171 | + for (IpPrefix prefix : prefixes) { | ||
| 172 | + int prefixBitlen = prefix.prefixLength(); | ||
| 173 | + int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up | ||
| 174 | + message.writeByte(prefixBitlen); | ||
| 175 | + | ||
| 176 | + IpAddress address = prefix.toIpAddress(); | ||
| 177 | + long value = address.toInt() & 0xffffffffL; | ||
| 178 | + for (int i = 0; i < IpAddress.INET_LEN; i++) { | ||
| 179 | + if (prefixBytelen-- == 0) { | ||
| 180 | + break; | ||
| 181 | + } | ||
| 182 | + long nextByte = | ||
| 183 | + (value >> ((IpAddress.INET_LEN - i - 1) * 8)) & 0xff; | ||
| 184 | + message.writeByte((int) nextByte); | ||
| 185 | + } | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + return message; | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * Prepares BGP KEEPALIVE message. | ||
| 193 | + * | ||
| 194 | + * @return the message to transmit (BGP header included) | ||
| 195 | + */ | ||
| 196 | + ChannelBuffer prepareBgpKeepalive() { | ||
| 197 | + ChannelBuffer message = | ||
| 198 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 199 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + /** | ||
| 203 | + * Prepares BGP NOTIFICATION message. | ||
| 204 | + * | ||
| 205 | + * @param errorCode the BGP NOTIFICATION Error Code | ||
| 206 | + * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable, | ||
| 207 | + * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC | ||
| 208 | + * @param payload the BGP NOTIFICATION Data if applicable, otherwise null | ||
| 209 | + * @return the message to transmit (BGP header included) | ||
| 210 | + */ | ||
| 211 | + ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode, | ||
| 212 | + ChannelBuffer data) { | ||
| 213 | + ChannelBuffer message = | ||
| 214 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
| 215 | + // Prepare the NOTIFICATION message payload | ||
| 216 | + message.writeByte(errorCode); | ||
| 217 | + message.writeByte(errorSubcode); | ||
| 218 | + if (data != null) { | ||
| 219 | + message.writeBytes(data); | ||
| 220 | + } | ||
| 221 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + /** | ||
| 225 | + * Prepares BGP message. | ||
| 226 | + * | ||
| 227 | + * @param type the BGP message type | ||
| 228 | + * @param payload the message payload to transmit (BGP header excluded) | ||
| 229 | + * @return the message to transmit (BGP header included) | ||
| 230 | + */ | ||
| 231 | + private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) { | ||
| 232 | + ChannelBuffer message = | ||
| 233 | + ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH + | ||
| 234 | + payload.readableBytes()); | ||
| 235 | + | ||
| 236 | + // Write the marker | ||
| 237 | + for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) { | ||
| 238 | + message.writeByte(0xff); | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + // Write the rest of the BGP header | ||
| 242 | + message.writeShort(BgpConstants.BGP_HEADER_LENGTH + | ||
| 243 | + payload.readableBytes()); | ||
| 244 | + message.writeByte(type); | ||
| 245 | + | ||
| 246 | + // Write the payload | ||
| 247 | + message.writeBytes(payload); | ||
| 248 | + return message; | ||
| 249 | + } | ||
| 250 | +} |
| 1 | +package org.onlab.onos.sdnip.bgp; | ||
| 2 | + | ||
| 3 | +import java.util.concurrent.CountDownLatch; | ||
| 4 | + | ||
| 5 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
| 6 | +import org.jboss.netty.channel.Channel; | ||
| 7 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
| 8 | +import org.jboss.netty.handler.codec.frame.FrameDecoder; | ||
| 9 | +import org.onlab.packet.IpAddress; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Class for handling the decoding of the BGP messages at the remote | ||
| 13 | + * BGP peer session. | ||
| 14 | + */ | ||
| 15 | +class TestBgpPeerFrameDecoder extends FrameDecoder { | ||
| 16 | + int remoteBgpVersion; // 1 octet | ||
| 17 | + long remoteAs; // 2 octets | ||
| 18 | + long remoteHoldtime; // 2 octets | ||
| 19 | + IpAddress remoteBgpIdentifier; // 4 octets -> IPv4 address | ||
| 20 | + | ||
| 21 | + final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1); | ||
| 22 | + final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1); | ||
| 23 | + | ||
| 24 | + @Override | ||
| 25 | + protected Object decode(ChannelHandlerContext ctx, | ||
| 26 | + Channel channel, | ||
| 27 | + ChannelBuffer buf) throws Exception { | ||
| 28 | + // Test for minimum length of the BGP message | ||
| 29 | + if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) { | ||
| 30 | + // No enough data received | ||
| 31 | + return null; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + // | ||
| 35 | + // Mark the current buffer position in case we haven't received | ||
| 36 | + // the whole message. | ||
| 37 | + // | ||
| 38 | + buf.markReaderIndex(); | ||
| 39 | + | ||
| 40 | + // | ||
| 41 | + // Read and check the BGP message Marker field: it must be all ones | ||
| 42 | + // | ||
| 43 | + byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH]; | ||
| 44 | + buf.readBytes(marker); | ||
| 45 | + for (int i = 0; i < marker.length; i++) { | ||
| 46 | + if (marker[i] != (byte) 0xff) { | ||
| 47 | + // ERROR: Connection Not Synchronized. Close the channel. | ||
| 48 | + ctx.getChannel().close(); | ||
| 49 | + return null; | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + // | ||
| 54 | + // Read and check the BGP message Length field | ||
| 55 | + // | ||
| 56 | + int length = buf.readUnsignedShort(); | ||
| 57 | + if ((length < BgpConstants.BGP_HEADER_LENGTH) || | ||
| 58 | + (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) { | ||
| 59 | + // ERROR: Bad Message Length. Close the channel. | ||
| 60 | + ctx.getChannel().close(); | ||
| 61 | + return null; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + // | ||
| 65 | + // Test whether the rest of the message is received: | ||
| 66 | + // So far we have read the Marker (16 octets) and the | ||
| 67 | + // Length (2 octets) fields. | ||
| 68 | + // | ||
| 69 | + int remainingMessageLen = | ||
| 70 | + length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2; | ||
| 71 | + if (buf.readableBytes() < remainingMessageLen) { | ||
| 72 | + // No enough data received | ||
| 73 | + buf.resetReaderIndex(); | ||
| 74 | + return null; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + // | ||
| 78 | + // Read the BGP message Type field, and process based on that type | ||
| 79 | + // | ||
| 80 | + int type = buf.readUnsignedByte(); | ||
| 81 | + remainingMessageLen--; // Adjust after reading the type | ||
| 82 | + ChannelBuffer message = buf.readBytes(remainingMessageLen); | ||
| 83 | + | ||
| 84 | + // | ||
| 85 | + // Process the remaining of the message based on the message type | ||
| 86 | + // | ||
| 87 | + switch (type) { | ||
| 88 | + case BgpConstants.BGP_TYPE_OPEN: | ||
| 89 | + processBgpOpen(ctx, message); | ||
| 90 | + break; | ||
| 91 | + case BgpConstants.BGP_TYPE_UPDATE: | ||
| 92 | + // NOTE: Not used as part of the test, because ONOS does not | ||
| 93 | + // originate UPDATE messages. | ||
| 94 | + break; | ||
| 95 | + case BgpConstants.BGP_TYPE_NOTIFICATION: | ||
| 96 | + // NOTE: Not used as part of the testing (yet) | ||
| 97 | + break; | ||
| 98 | + case BgpConstants.BGP_TYPE_KEEPALIVE: | ||
| 99 | + processBgpKeepalive(ctx, message); | ||
| 100 | + break; | ||
| 101 | + default: | ||
| 102 | + // ERROR: Bad Message Type. Close the channel. | ||
| 103 | + ctx.getChannel().close(); | ||
| 104 | + return null; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + return null; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + /** | ||
| 111 | + * Processes BGP OPEN message. | ||
| 112 | + * | ||
| 113 | + * @param ctx the Channel Handler Context. | ||
| 114 | + * @param message the message to process. | ||
| 115 | + */ | ||
| 116 | + private void processBgpOpen(ChannelHandlerContext ctx, | ||
| 117 | + ChannelBuffer message) { | ||
| 118 | + int minLength = | ||
| 119 | + BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH; | ||
| 120 | + if (message.readableBytes() < minLength) { | ||
| 121 | + // ERROR: Bad Message Length. Close the channel. | ||
| 122 | + ctx.getChannel().close(); | ||
| 123 | + return; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + // | ||
| 127 | + // Parse the OPEN message | ||
| 128 | + // | ||
| 129 | + remoteBgpVersion = message.readUnsignedByte(); | ||
| 130 | + remoteAs = message.readUnsignedShort(); | ||
| 131 | + remoteHoldtime = message.readUnsignedShort(); | ||
| 132 | + remoteBgpIdentifier = IpAddress.valueOf((int) message.readUnsignedInt()); | ||
| 133 | + // Optional Parameters | ||
| 134 | + int optParamLen = message.readUnsignedByte(); | ||
| 135 | + if (message.readableBytes() < optParamLen) { | ||
| 136 | + // ERROR: Bad Message Length. Close the channel. | ||
| 137 | + ctx.getChannel().close(); | ||
| 138 | + return; | ||
| 139 | + } | ||
| 140 | + message.readBytes(optParamLen); // NOTE: data ignored | ||
| 141 | + | ||
| 142 | + // BGP OPEN message successfully received | ||
| 143 | + receivedOpenMessageLatch.countDown(); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + /** | ||
| 147 | + * Processes BGP KEEPALIVE message. | ||
| 148 | + * | ||
| 149 | + * @param ctx the Channel Handler Context. | ||
| 150 | + * @param message the message to process. | ||
| 151 | + */ | ||
| 152 | + private void processBgpKeepalive(ChannelHandlerContext ctx, | ||
| 153 | + ChannelBuffer message) { | ||
| 154 | + if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH != | ||
| 155 | + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) { | ||
| 156 | + // ERROR: Bad Message Length. Close the channel. | ||
| 157 | + ctx.getChannel().close(); | ||
| 158 | + return; | ||
| 159 | + } | ||
| 160 | + // BGP KEEPALIVE message successfully received | ||
| 161 | + receivedKeepaliveMessageLatch.countDown(); | ||
| 162 | + } | ||
| 163 | +} |
| 1 | +package org.onlab.util; | ||
| 2 | + | ||
| 3 | +import java.lang.reflect.Constructor; | ||
| 4 | +import java.lang.reflect.Field; | ||
| 5 | +import java.lang.reflect.InvocationTargetException; | ||
| 6 | +import java.lang.reflect.Method; | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * Utilities for testing. | ||
| 11 | + */ | ||
| 12 | +public final class TestUtils { | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * Sets the field, bypassing scope restriction. | ||
| 16 | + * | ||
| 17 | + * @param subject Object where the field belongs | ||
| 18 | + * @param fieldName name of the field to set | ||
| 19 | + * @param value value to set to the field. | ||
| 20 | + * @param <T> subject type | ||
| 21 | + * @param <U> value type | ||
| 22 | + * @throws TestUtilsException if there are reflection errors while setting | ||
| 23 | + * the field | ||
| 24 | + */ | ||
| 25 | + public static <T, U> void setField(T subject, String fieldName, U value) | ||
| 26 | + throws TestUtilsException { | ||
| 27 | + @SuppressWarnings("unchecked") | ||
| 28 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
| 29 | + try { | ||
| 30 | + Field field = clazz.getDeclaredField(fieldName); | ||
| 31 | + field.setAccessible(true); | ||
| 32 | + field.set(subject, value); | ||
| 33 | + } catch (NoSuchFieldException | SecurityException | | ||
| 34 | + IllegalArgumentException | IllegalAccessException e) { | ||
| 35 | + throw new TestUtilsException("setField failed", e); | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Gets the field, bypassing scope restriction. | ||
| 41 | + * | ||
| 42 | + * @param subject Object where the field belongs | ||
| 43 | + * @param fieldName name of the field to get | ||
| 44 | + * @return value of the field. | ||
| 45 | + * @param <T> subject type | ||
| 46 | + * @param <U> field value type | ||
| 47 | + * @throws TestUtilsException if there are reflection errors while getting | ||
| 48 | + * the field | ||
| 49 | + */ | ||
| 50 | + public static <T, U> U getField(T subject, String fieldName) | ||
| 51 | + throws TestUtilsException { | ||
| 52 | + try { | ||
| 53 | + @SuppressWarnings("unchecked") | ||
| 54 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
| 55 | + Field field = clazz.getDeclaredField(fieldName); | ||
| 56 | + field.setAccessible(true); | ||
| 57 | + | ||
| 58 | + @SuppressWarnings("unchecked") | ||
| 59 | + U result = (U) field.get(subject); | ||
| 60 | + return result; | ||
| 61 | + } catch (NoSuchFieldException | SecurityException | | ||
| 62 | + IllegalArgumentException | IllegalAccessException e) { | ||
| 63 | + throw new TestUtilsException("getField failed", e); | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * Calls the method, bypassing scope restriction. | ||
| 69 | + * | ||
| 70 | + * @param subject Object where the method belongs | ||
| 71 | + * @param methodName name of the method to call | ||
| 72 | + * @param paramTypes formal parameter type array | ||
| 73 | + * @param args arguments | ||
| 74 | + * @return return value or null if void | ||
| 75 | + * @param <T> subject type | ||
| 76 | + * @param <U> return value type | ||
| 77 | + * @throws TestUtilsException if there are reflection errors while calling | ||
| 78 | + * the method | ||
| 79 | + */ | ||
| 80 | + public static <T, U> U callMethod(T subject, String methodName, | ||
| 81 | + Class<?>[] paramTypes, Object...args) throws TestUtilsException { | ||
| 82 | + | ||
| 83 | + try { | ||
| 84 | + @SuppressWarnings("unchecked") | ||
| 85 | + Class<T> clazz = (Class<T>) subject.getClass(); | ||
| 86 | + final Method method; | ||
| 87 | + if (paramTypes == null || paramTypes.length == 0) { | ||
| 88 | + method = clazz.getDeclaredMethod(methodName); | ||
| 89 | + } else { | ||
| 90 | + method = clazz.getDeclaredMethod(methodName, paramTypes); | ||
| 91 | + } | ||
| 92 | + method.setAccessible(true); | ||
| 93 | + | ||
| 94 | + @SuppressWarnings("unchecked") | ||
| 95 | + U result = (U) method.invoke(subject, args); | ||
| 96 | + return result; | ||
| 97 | + } catch (NoSuchMethodException | SecurityException | | ||
| 98 | + IllegalAccessException | IllegalArgumentException | | ||
| 99 | + InvocationTargetException e) { | ||
| 100 | + throw new TestUtilsException("callMethod failed", e); | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /** | ||
| 105 | + * Calls the method, bypassing scope restriction. | ||
| 106 | + * | ||
| 107 | + * @param subject Object where the method belongs | ||
| 108 | + * @param methodName name of the method to call | ||
| 109 | + * @param paramType formal parameter type | ||
| 110 | + * @param arg argument | ||
| 111 | + * @return return value or null if void | ||
| 112 | + * @param <T> subject type | ||
| 113 | + * @param <U> return value type | ||
| 114 | + * @throws TestUtilsException if there are reflection errors while calling | ||
| 115 | + * the method | ||
| 116 | + */ | ||
| 117 | + public static <T, U> U callMethod(T subject, String methodName, | ||
| 118 | + Class<?> paramType, Object arg) throws TestUtilsException { | ||
| 119 | + return callMethod(subject, methodName, new Class<?>[]{paramType}, arg); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + /** | ||
| 123 | + * Triggers an allocation of an object of type <T> and forces a call to | ||
| 124 | + * the private constructor. | ||
| 125 | + * | ||
| 126 | + * @param constructor Constructor to call | ||
| 127 | + * @param <T> type of the object to create | ||
| 128 | + * @return created object of type <T> | ||
| 129 | + * @throws TestUtilsException if there are reflection errors while calling | ||
| 130 | + * the constructor | ||
| 131 | + */ | ||
| 132 | + public static <T> T callConstructor(Constructor<T> constructor) | ||
| 133 | + throws TestUtilsException { | ||
| 134 | + try { | ||
| 135 | + constructor.setAccessible(true); | ||
| 136 | + return constructor.newInstance(); | ||
| 137 | + } catch (InstantiationException | IllegalAccessException | | ||
| 138 | + InvocationTargetException error) { | ||
| 139 | + throw new TestUtilsException("callConstructor failed", error); | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + /** | ||
| 144 | + * Avoid instantiation. | ||
| 145 | + */ | ||
| 146 | + private TestUtils() {} | ||
| 147 | + | ||
| 148 | + /** | ||
| 149 | + * Exception that can be thrown if problems are encountered while executing | ||
| 150 | + * the utility method. These are usually problems accessing fields/methods | ||
| 151 | + * through reflection. The original exception can be found by examining the | ||
| 152 | + * cause. | ||
| 153 | + */ | ||
| 154 | + public static class TestUtilsException extends Exception { | ||
| 155 | + | ||
| 156 | + private static final long serialVersionUID = 1L; | ||
| 157 | + | ||
| 158 | + /** | ||
| 159 | + * Constructs a new exception with the specified detail message and | ||
| 160 | + * cause. | ||
| 161 | + * | ||
| 162 | + * @param message the detail message | ||
| 163 | + * @param cause the original cause of this exception | ||
| 164 | + */ | ||
| 165 | + public TestUtilsException(String message, Throwable cause) { | ||
| 166 | + super(message, cause); | ||
| 167 | + } | ||
| 168 | + } | ||
| 169 | +} |
| 1 | +package org.onlab.util; | ||
| 2 | + | ||
| 3 | +import static org.junit.Assert.assertArrayEquals; | ||
| 4 | +import static org.junit.Assert.assertEquals; | ||
| 5 | +import static org.junit.Assert.assertNull; | ||
| 6 | + | ||
| 7 | +import org.junit.Before; | ||
| 8 | +import org.junit.Test; | ||
| 9 | +import org.onlab.util.TestUtils.TestUtilsException; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Test and usage examples for TestUtils. | ||
| 13 | + */ | ||
| 14 | +public class TestUtilsTest { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * Test data. | ||
| 18 | + */ | ||
| 19 | + private static final class TestClass { | ||
| 20 | + | ||
| 21 | + @SuppressWarnings("unused") | ||
| 22 | + private int privateField = 42; | ||
| 23 | + | ||
| 24 | + @SuppressWarnings("unused") | ||
| 25 | + protected int protectedField = 2501; // CHECKSTYLE IGNORE THIS LINE | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Protected method with multiple argument. | ||
| 29 | + * | ||
| 30 | + * @param x simply returns | ||
| 31 | + * @param y not used | ||
| 32 | + * @return x | ||
| 33 | + */ | ||
| 34 | + @SuppressWarnings("unused") | ||
| 35 | + private int privateMethod(Number x, Long y) { | ||
| 36 | + return x.intValue(); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * Protected method with no argument. | ||
| 41 | + * | ||
| 42 | + * @return int | ||
| 43 | + */ | ||
| 44 | + @SuppressWarnings("unused") | ||
| 45 | + protected int protectedMethod() { | ||
| 46 | + return 42; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * Method returning array. | ||
| 51 | + * | ||
| 52 | + * @param ary random array | ||
| 53 | + * @return ary | ||
| 54 | + */ | ||
| 55 | + @SuppressWarnings("unused") | ||
| 56 | + private int[] arrayReturnMethod(int[] ary) { | ||
| 57 | + return ary; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * Method without return value. | ||
| 62 | + * | ||
| 63 | + * @param s ignored | ||
| 64 | + */ | ||
| 65 | + @SuppressWarnings("unused") | ||
| 66 | + private void voidMethod(String s) { | ||
| 67 | + System.out.println(s); | ||
| 68 | + } | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + private TestClass test; | ||
| 72 | + | ||
| 73 | + /** | ||
| 74 | + * Sets up the test fixture. | ||
| 75 | + */ | ||
| 76 | + @Before | ||
| 77 | + public void setUp() { | ||
| 78 | + test = new TestClass(); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + /** | ||
| 82 | + * Example to access private field. | ||
| 83 | + * | ||
| 84 | + * @throws TestUtilsException TestUtils error | ||
| 85 | + */ | ||
| 86 | + @Test | ||
| 87 | + public void testSetGetPrivateField() throws TestUtilsException { | ||
| 88 | + | ||
| 89 | + assertEquals(42, TestUtils.getField(test, "privateField")); | ||
| 90 | + TestUtils.setField(test, "privateField", 0xDEAD); | ||
| 91 | + assertEquals(0xDEAD, TestUtils.getField(test, "privateField")); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * Example to access protected field. | ||
| 96 | + * | ||
| 97 | + * @throws TestUtilsException TestUtils error | ||
| 98 | + */ | ||
| 99 | + @Test | ||
| 100 | + public void testSetGetProtectedField() throws TestUtilsException { | ||
| 101 | + | ||
| 102 | + assertEquals(2501, TestUtils.getField(test, "protectedField")); | ||
| 103 | + TestUtils.setField(test, "protectedField", 0xBEEF); | ||
| 104 | + assertEquals(0xBEEF, TestUtils.getField(test, "protectedField")); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + /** | ||
| 108 | + * Example to call private method and multiple parameters. | ||
| 109 | + * <p/> | ||
| 110 | + * It also illustrates that paramTypes must match declared type, | ||
| 111 | + * not the runtime types of arguments. | ||
| 112 | + * | ||
| 113 | + * @throws TestUtilsException TestUtils error | ||
| 114 | + */ | ||
| 115 | + @Test | ||
| 116 | + public void testCallPrivateMethod() throws TestUtilsException { | ||
| 117 | + | ||
| 118 | + int result = TestUtils.callMethod(test, "privateMethod", | ||
| 119 | + new Class<?>[] {Number.class, Long.class}, | ||
| 120 | + Long.valueOf(42), Long.valueOf(32)); | ||
| 121 | + assertEquals(42, result); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + /** | ||
| 125 | + * Example to call protected method and no parameters. | ||
| 126 | + * | ||
| 127 | + * @throws TestUtilsException TestUtils error | ||
| 128 | + */ | ||
| 129 | + @Test | ||
| 130 | + public void testCallProtectedMethod() throws TestUtilsException { | ||
| 131 | + | ||
| 132 | + int result = TestUtils.callMethod(test, "protectedMethod", | ||
| 133 | + new Class<?>[] {}); | ||
| 134 | + assertEquals(42, result); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + /** | ||
| 138 | + * Example to call method returning array. | ||
| 139 | + * <p/> | ||
| 140 | + * Note: It is not required to receive as Object. | ||
| 141 | + * Following is just verifying it is not Boxed arrays. | ||
| 142 | + * | ||
| 143 | + * @throws TestUtilsException TestUtils error | ||
| 144 | + */ | ||
| 145 | + @Test | ||
| 146 | + public void testCallArrayReturnMethod() throws TestUtilsException { | ||
| 147 | + | ||
| 148 | + int[] array = {1, 2, 3}; | ||
| 149 | + Object aryResult = TestUtils.callMethod(test, "arrayReturnMethod", | ||
| 150 | + new Class<?>[] {int[].class}, array); | ||
| 151 | + assertEquals(int[].class, aryResult.getClass()); | ||
| 152 | + assertArrayEquals(array, (int[]) aryResult); | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + | ||
| 156 | + /** | ||
| 157 | + * Example to call void returning method. | ||
| 158 | + * <p/> | ||
| 159 | + * Note: Return value will be null for void methods. | ||
| 160 | + * | ||
| 161 | + * @throws TestUtilsException TestUtils error | ||
| 162 | + */ | ||
| 163 | + @Test | ||
| 164 | + public void testCallVoidReturnMethod() throws TestUtilsException { | ||
| 165 | + | ||
| 166 | + Object voidResult = TestUtils.callMethod(test, "voidMethod", | ||
| 167 | + String.class, "foobar"); | ||
| 168 | + assertNull(voidResult); | ||
| 169 | + } | ||
| 170 | +} |
-
Please register or login to post a comment