blob: 4dfe3cc7d2bc53e9654c069791bd868ec5022b9c [file] [log] [blame]
Pingping32fa30c2014-10-23 15:24:26 -07001package org.onlab.onos.sdnip;
2
3import static org.easymock.EasyMock.createMock;
4import static org.easymock.EasyMock.expect;
5import static org.easymock.EasyMock.expectLastCall;
6import static org.easymock.EasyMock.replay;
7import static org.easymock.EasyMock.reset;
8import static org.easymock.EasyMock.verify;
9import static org.junit.Assert.assertEquals;
10import static org.junit.Assert.assertNotNull;
11
12import java.nio.ByteBuffer;
13import java.util.ArrayList;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Random;
19import java.util.Set;
20import java.util.concurrent.CountDownLatch;
21import java.util.concurrent.TimeUnit;
22
23import org.easymock.IAnswer;
24import org.junit.Before;
25import org.junit.Test;
26import org.junit.experimental.categories.Category;
27import org.onlab.junit.IntegrationTest;
28import org.onlab.junit.TestUtils;
29import org.onlab.junit.TestUtils.TestUtilsException;
30import org.onlab.onos.core.ApplicationId;
31import org.onlab.onos.net.ConnectPoint;
32import org.onlab.onos.net.DeviceId;
33import org.onlab.onos.net.PortNumber;
34import org.onlab.onos.net.flow.DefaultTrafficSelector;
35import org.onlab.onos.net.flow.DefaultTrafficTreatment;
36import org.onlab.onos.net.flow.TrafficSelector;
37import org.onlab.onos.net.flow.TrafficTreatment;
38import org.onlab.onos.net.host.HostService;
39import org.onlab.onos.net.host.InterfaceIpAddress;
40import org.onlab.onos.net.intent.IntentService;
41import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
Brian O'Connor520c0522014-11-23 23:50:47 -080042import org.onlab.onos.net.intent.AbstractIntentTest;
Pingping32fa30c2014-10-23 15:24:26 -070043import org.onlab.onos.sdnip.config.BgpPeer;
44import org.onlab.onos.sdnip.config.Interface;
45import org.onlab.onos.sdnip.config.SdnIpConfigService;
46import org.onlab.packet.Ethernet;
47import org.onlab.packet.Ip4Address;
48import org.onlab.packet.Ip4Prefix;
49import org.onlab.packet.IpAddress;
50import org.onlab.packet.IpPrefix;
51import org.onlab.packet.MacAddress;
52
53import com.google.common.collect.Sets;
54
55/**
56 * Integration tests for the SDN-IP application.
57 * <p/>
58 * The tests are very coarse-grained. They feed route updates in to
59 * {@link Router} (simulating routes learnt from iBGP module inside SDN-IP
60 * application), then they check that the correct intents are created and
61 * submitted to the intent service. The entire route processing logic of
62 * Router class is tested.
63 */
64@Category(IntegrationTest.class)
Brian O'Connor520c0522014-11-23 23:50:47 -080065public class SdnIpTest extends AbstractIntentTest {
Pingping32fa30c2014-10-23 15:24:26 -070066 private static final int MAC_ADDRESS_LENGTH = 6;
67 private static final int MIN_PREFIX_LENGTH = 1;
68 private static final int MAX_PREFIX_LENGTH = 32;
69
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080070 private IntentSynchronizer intentSynchronizer;
Pingping32fa30c2014-10-23 15:24:26 -070071 static Router router;
72
73 private SdnIpConfigService sdnIpConfigService;
74 private InterfaceService interfaceService;
75 private HostService hostService;
76 private IntentService intentService;
77
78 private Map<IpAddress, BgpPeer> bgpPeers;
79
80 private Random random;
81
82 static final ConnectPoint SW1_ETH1 = new ConnectPoint(
83 DeviceId.deviceId("of:0000000000000001"),
84 PortNumber.portNumber(1));
85
86 static final ConnectPoint SW2_ETH1 = new ConnectPoint(
87 DeviceId.deviceId("of:0000000000000002"),
88 PortNumber.portNumber(1));
89
90 static final ConnectPoint SW3_ETH1 = new ConnectPoint(
91 DeviceId.deviceId("of:0000000000000003"),
92 PortNumber.portNumber(1));
93
94 private static final ApplicationId APPID = new ApplicationId() {
95 @Override
96 public short id() {
97 return 1;
98 }
99
100 @Override
101 public String name() {
102 return "SDNIP";
103 }
104 };
105
106 @Before
107 public void setUp() throws Exception {
Brian O'Connor520c0522014-11-23 23:50:47 -0800108 super.setUp();
Pingping32fa30c2014-10-23 15:24:26 -0700109
110 setUpInterfaceService();
111 setUpSdnIpConfigService();
112
113 hostService = new TestHostService();
114 intentService = createMock(IntentService.class);
115 random = new Random();
116
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800117 intentSynchronizer = new IntentSynchronizer(APPID, intentService);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800118 router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
119 interfaceService, hostService);
Pingping32fa30c2014-10-23 15:24:26 -0700120 }
121
122 /**
123 * Sets up InterfaceService and virtual {@link Interface}s.
124 */
125 private void setUpInterfaceService() {
126
127 interfaceService = createMock(InterfaceService.class);
128
129 Set<Interface> interfaces = Sets.newHashSet();
130
131 Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
132 interfaceIpAddresses1.add(new InterfaceIpAddress(
133 IpAddress.valueOf("192.168.10.101"),
134 IpPrefix.valueOf("192.168.10.0/24")));
135 Interface sw1Eth1 = new Interface(SW1_ETH1,
136 interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"));
137 interfaces.add(sw1Eth1);
138
139 Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
140 interfaceIpAddresses2.add(new InterfaceIpAddress(
141 IpAddress.valueOf("192.168.20.101"),
142 IpPrefix.valueOf("192.168.20.0/24")));
143 Interface sw2Eth1 = new Interface(SW2_ETH1,
144 interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"));
145 interfaces.add(sw2Eth1);
146
147 Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
148 interfaceIpAddresses3.add(new InterfaceIpAddress(
149 IpAddress.valueOf("192.168.30.101"),
150 IpPrefix.valueOf("192.168.30.0/24")));
151 Interface sw3Eth1 = new Interface(SW3_ETH1,
152 interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"));
153 interfaces.add(sw3Eth1);
154
155 expect(interfaceService.getInterface(SW1_ETH1)).andReturn(
156 sw1Eth1).anyTimes();
157 expect(interfaceService.getInterface(SW2_ETH1)).andReturn(
158 sw2Eth1).anyTimes();
159 expect(interfaceService.getInterface(SW3_ETH1)).andReturn(
160 sw3Eth1).anyTimes();
161
162 expect(interfaceService.getInterfaces()).andReturn(
163 interfaces).anyTimes();
164 replay(interfaceService);
165 }
166
167 /**
168 * Sets up SdnIpConfigService and BGP peers in external networks.
169 */
170 private void setUpSdnIpConfigService() {
171
172 sdnIpConfigService = createMock(SdnIpConfigService.class);
173
174 bgpPeers = new HashMap<>();
175
176 String peerSw1Eth1 = "192.168.10.1";
177 bgpPeers.put(IpAddress.valueOf(peerSw1Eth1),
178 new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
179
180 String peer1Sw2Eth1 = "192.168.20.1";
181 bgpPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
182 new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
183
184 String peer2Sw2Eth1 = "192.168.30.1";
185 bgpPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
186 new BgpPeer("00:00:00:00:00:00:00:03", 1, peer2Sw2Eth1));
187
188 expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
189 replay(sdnIpConfigService);
190 }
191
192 /**
193 * Tests adding a set of routes into {@link Router}.
194 * <p/>
195 * Random routes are generated and fed in to the route processing
196 * logic (via processRouteAdd in Router class). We check that the correct
197 * intents are generated and submitted to our mock intent service.
198 *
199 * @throws InterruptedException if interrupted while waiting on a latch
200 * @throws TestUtilsException if exceptions when using TestUtils
201 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800202 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700203 public void testAddRoutes() throws InterruptedException, TestUtilsException {
204 int numRoutes = 100;
205
206 final CountDownLatch latch = new CountDownLatch(numRoutes);
207
208 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
209
210 // Set up expectation
211 reset(intentService);
212
213 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800214 Ip4Address nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700215
216 // Find out the egress ConnectPoint
217 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
218
219 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
220 generateMacAddress(nextHopAddress),
221 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800222 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700223
224 expectLastCall().andAnswer(new IAnswer<Object>() {
225 @Override
226 public Object answer() throws Throwable {
227 latch.countDown();
228 return null;
229 }
230 }).once();
231 }
232
233 replay(intentService);
234
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800235 intentSynchronizer.leaderChanged(true);
236 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700237
238 // Add route updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800239 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700240
241 latch.await(5000, TimeUnit.MILLISECONDS);
242
243 assertEquals(router.getRoutes().size(), numRoutes);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800244 assertEquals(intentSynchronizer.getRouteIntents().size(),
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800245 numRoutes);
Pingping32fa30c2014-10-23 15:24:26 -0700246
247 verify(intentService);
248 }
249
250 /**
251 * Tests adding then deleting a set of routes from {@link Router}.
252 * <p/>
253 * Random routes are generated and fed in to the route processing
254 * logic (via processRouteAdd in Router class), and we check that the
255 * correct intents are generated. We then delete the entire set of routes
256 * (by feeding updates to processRouteDelete), and check that the correct
257 * intents are withdrawn from the intent service.
258 *
259 * @throws InterruptedException if interrupted while waiting on a latch
260 * @throws TestUtilsException exceptions when using TestUtils
261 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800262 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700263 public void testDeleteRoutes() throws InterruptedException, TestUtilsException {
264 int numRoutes = 100;
265 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
266
267 final CountDownLatch installCount = new CountDownLatch(numRoutes);
268 final CountDownLatch deleteCount = new CountDownLatch(numRoutes);
269
270 // Set up expectation
271 reset(intentService);
272
273 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800274 Ip4Address nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700275
276 // Find out the egress ConnectPoint
277 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
278 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
279 generateMacAddress(nextHopAddress),
280 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800281 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700282 expectLastCall().andAnswer(new IAnswer<Object>() {
283 @Override
284 public Object answer() throws Throwable {
285 installCount.countDown();
286 return null;
287 }
288 }).once();
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800289 intentService.withdraw(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700290 expectLastCall().andAnswer(new IAnswer<Object>() {
291 @Override
292 public Object answer() throws Throwable {
293 deleteCount.countDown();
294 return null;
295 }
296 }).once();
297 }
298
299 replay(intentService);
300
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800301 intentSynchronizer.leaderChanged(true);
302 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700303
304 // Send the add updates first
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800305 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700306
307 // Give some time to let the intents be submitted
308 installCount.await(5000, TimeUnit.MILLISECONDS);
309
310 // Send the DELETE updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800311 List<RouteUpdate> deleteRouteUpdates = new ArrayList<>();
Pingping32fa30c2014-10-23 15:24:26 -0700312 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800313 RouteUpdate deleteUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
314 update.routeEntry());
315 deleteRouteUpdates.add(deleteUpdate);
Pingping32fa30c2014-10-23 15:24:26 -0700316 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800317 router.processRouteUpdates(deleteRouteUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700318
319 deleteCount.await(5000, TimeUnit.MILLISECONDS);
320
321 assertEquals(0, router.getRoutes().size());
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800322 assertEquals(0, intentSynchronizer.getRouteIntents().size());
Pingping32fa30c2014-10-23 15:24:26 -0700323 verify(intentService);
324 }
325
326 /**
327 * This methods generates random route updates.
328 *
329 * @param numRoutes the number of route updates to generate
330 * @return a list of route update
331 */
332 private List<RouteUpdate> generateRouteUpdates(int numRoutes) {
333 List<RouteUpdate> routeUpdates = new ArrayList<>(numRoutes);
334
335 Set<Ip4Prefix> prefixes = new HashSet<>();
336
337 for (int i = 0; i < numRoutes; i++) {
338 Ip4Prefix prefix;
339 do {
340 // Generate a random prefix length between MIN_PREFIX_LENGTH
341 // and MAX_PREFIX_LENGTH
342 int prefixLength = random.nextInt(
343 (MAX_PREFIX_LENGTH - MIN_PREFIX_LENGTH) + 1)
344 + MIN_PREFIX_LENGTH;
345 prefix =
346 Ip4Prefix.valueOf(Ip4Address.valueOf(random.nextInt()),
347 prefixLength);
348 // We have to ensure we don't generate the same prefix twice
349 // (this is quite easy to happen with small prefix lengths).
Pingping32fa30c2014-10-23 15:24:26 -0700350 } while (prefixes.contains(prefix));
351
352 prefixes.add(prefix);
353
354 // Randomly select a peer to use as the next hop
355 BgpPeer nextHop = null;
356 int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers()
357 .size());
358 int j = 0;
359 for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) {
360 if (j++ == peerNumber) {
361 nextHop = peer;
362 break;
363 }
364 }
365
366 assertNotNull(nextHop);
367
368 RouteUpdate update =
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800369 new RouteUpdate(RouteUpdate.Type.UPDATE,
370 new RouteEntry(prefix,
371 nextHop.ipAddress().getIp4Address()));
Pingping32fa30c2014-10-23 15:24:26 -0700372
373 routeUpdates.add(update);
374 }
375
376 return routeUpdates;
377 }
378
379 /**
380 * Generates the MultiPointToSinglePointIntent that should be
381 * submitted/withdrawn for a particular RouteUpdate.
382 *
383 * @param update the RouteUpdate to generate an intent for
384 * @param nextHopMac a MAC address to use as the dst-mac for the intent
385 * @param egressConnectPoint the outgoing ConnectPoint for the intent
386 * @return the generated intent
387 */
388 private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update,
389 MacAddress nextHopMac, ConnectPoint egressConnectPoint) {
390 IpPrefix ip4Prefix = update.routeEntry().prefix();
391
392 TrafficSelector.Builder selectorBuilder =
393 DefaultTrafficSelector.builder();
394
395 selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix);
396
397 TrafficTreatment.Builder treatmentBuilder =
398 DefaultTrafficTreatment.builder();
399 treatmentBuilder.setEthDst(nextHopMac);
400
401 Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
402 for (Interface intf : interfaceService.getInterfaces()) {
403 if (!intf.connectPoint().equals(egressConnectPoint)) {
404 ConnectPoint srcPort = intf.connectPoint();
405 ingressPoints.add(srcPort);
406 }
407 }
408
409 MultiPointToSinglePointIntent intent =
410 new MultiPointToSinglePointIntent(APPID,
411 selectorBuilder.build(), treatmentBuilder.build(),
412 ingressPoints, egressConnectPoint);
413
414 return intent;
415 }
416
417 /**
418 * Generates a MAC address based on an IP address.
419 * For the test we need MAC addresses but the actual values don't have any
420 * meaning, so we'll just generate them based on the IP address. This means
421 * we have a deterministic mapping from IP address to MAC address.
422 *
423 * @param ipAddress IP address used to generate a MAC address
424 * @return generated MAC address
425 */
426 static MacAddress generateMacAddress(IpAddress ipAddress) {
427 byte[] macAddress = new byte[MAC_ADDRESS_LENGTH];
428 ByteBuffer bb = ByteBuffer.wrap(macAddress);
429
430 // Put the IP address bytes into the lower four bytes of the MAC
431 // address. Leave the first two bytes set to 0.
432 bb.position(2);
433 bb.put(ipAddress.toOctets());
434
435 return MacAddress.valueOf(bb.array());
436 }
437
438 /**
439 * Finds out the ConnectPoint for a BGP peer address.
440 *
441 * @param bgpPeerAddress the BGP peer address.
442 */
443 private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) {
444 ConnectPoint connectPoint = null;
445
446 for (BgpPeer bgpPeer: bgpPeers.values()) {
447 if (bgpPeer.ipAddress().equals(bgpPeerAddress)) {
448 connectPoint = bgpPeer.connectPoint();
449 break;
450 }
451 }
452 return connectPoint;
453 }
454}