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