blob: 06e77dc825e82500f354827fd132d86d4287c7ba [file] [log] [blame]
alshabibab984662014-12-04 18:56:18 -08001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.sdnip;
Pingping32fa30c2014-10-23 15:24:26 -070017
18import static org.easymock.EasyMock.createMock;
19import static org.easymock.EasyMock.expect;
20import static org.easymock.EasyMock.expectLastCall;
21import static org.easymock.EasyMock.replay;
22import static org.easymock.EasyMock.reset;
23import static org.easymock.EasyMock.verify;
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertNotNull;
26
27import java.nio.ByteBuffer;
28import java.util.ArrayList;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Map;
33import java.util.Random;
34import java.util.Set;
35import java.util.concurrent.CountDownLatch;
36import java.util.concurrent.TimeUnit;
37
38import org.easymock.IAnswer;
39import org.junit.Before;
40import org.junit.Test;
41import org.junit.experimental.categories.Category;
42import org.onlab.junit.IntegrationTest;
43import org.onlab.junit.TestUtils;
44import org.onlab.junit.TestUtils.TestUtilsException;
Brian O'Connorabafb502014-12-02 22:26:20 -080045import org.onosproject.core.ApplicationId;
46import org.onosproject.net.ConnectPoint;
47import org.onosproject.net.DeviceId;
48import org.onosproject.net.PortNumber;
49import org.onosproject.net.flow.DefaultTrafficSelector;
50import org.onosproject.net.flow.DefaultTrafficTreatment;
51import org.onosproject.net.flow.TrafficSelector;
52import org.onosproject.net.flow.TrafficTreatment;
53import org.onosproject.net.host.HostService;
54import org.onosproject.net.host.InterfaceIpAddress;
55import org.onosproject.net.intent.IntentService;
56import org.onosproject.net.intent.MultiPointToSinglePointIntent;
57import org.onosproject.net.intent.AbstractIntentTest;
58import org.onosproject.sdnip.config.BgpPeer;
59import org.onosproject.sdnip.config.Interface;
60import org.onosproject.sdnip.config.SdnIpConfigurationService;
Pingping32fa30c2014-10-23 15:24:26 -070061import org.onlab.packet.Ethernet;
62import org.onlab.packet.Ip4Address;
63import org.onlab.packet.Ip4Prefix;
64import org.onlab.packet.IpAddress;
65import org.onlab.packet.IpPrefix;
66import org.onlab.packet.MacAddress;
67
68import com.google.common.collect.Sets;
69
70/**
71 * Integration tests for the SDN-IP application.
72 * <p/>
73 * The tests are very coarse-grained. They feed route updates in to
74 * {@link Router} (simulating routes learnt from iBGP module inside SDN-IP
75 * application), then they check that the correct intents are created and
76 * submitted to the intent service. The entire route processing logic of
77 * Router class is tested.
78 */
79@Category(IntegrationTest.class)
Brian O'Connor520c0522014-11-23 23:50:47 -080080public class SdnIpTest extends AbstractIntentTest {
Pingping32fa30c2014-10-23 15:24:26 -070081 private static final int MAC_ADDRESS_LENGTH = 6;
82 private static final int MIN_PREFIX_LENGTH = 1;
83 private static final int MAX_PREFIX_LENGTH = 32;
84
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080085 private IntentSynchronizer intentSynchronizer;
Pingping32fa30c2014-10-23 15:24:26 -070086 static Router router;
87
Jonathan Hart9965d772014-12-02 10:28:34 -080088 private SdnIpConfigurationService sdnIpConfigService;
Pingping32fa30c2014-10-23 15:24:26 -070089 private InterfaceService interfaceService;
90 private HostService hostService;
91 private IntentService intentService;
92
93 private Map<IpAddress, BgpPeer> bgpPeers;
94
95 private Random random;
96
97 static final ConnectPoint SW1_ETH1 = new ConnectPoint(
98 DeviceId.deviceId("of:0000000000000001"),
99 PortNumber.portNumber(1));
100
101 static final ConnectPoint SW2_ETH1 = new ConnectPoint(
102 DeviceId.deviceId("of:0000000000000002"),
103 PortNumber.portNumber(1));
104
105 static final ConnectPoint SW3_ETH1 = new ConnectPoint(
106 DeviceId.deviceId("of:0000000000000003"),
107 PortNumber.portNumber(1));
108
109 private static final ApplicationId APPID = new ApplicationId() {
110 @Override
111 public short id() {
112 return 1;
113 }
114
115 @Override
116 public String name() {
117 return "SDNIP";
118 }
119 };
120
121 @Before
122 public void setUp() throws Exception {
Brian O'Connor520c0522014-11-23 23:50:47 -0800123 super.setUp();
Pingping32fa30c2014-10-23 15:24:26 -0700124
125 setUpInterfaceService();
126 setUpSdnIpConfigService();
127
128 hostService = new TestHostService();
129 intentService = createMock(IntentService.class);
130 random = new Random();
131
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800132 intentSynchronizer = new IntentSynchronizer(APPID, intentService);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800133 router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
134 interfaceService, hostService);
Pingping32fa30c2014-10-23 15:24:26 -0700135 }
136
137 /**
138 * Sets up InterfaceService and virtual {@link Interface}s.
139 */
140 private void setUpInterfaceService() {
141
142 interfaceService = createMock(InterfaceService.class);
143
144 Set<Interface> interfaces = Sets.newHashSet();
145
146 Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
147 interfaceIpAddresses1.add(new InterfaceIpAddress(
148 IpAddress.valueOf("192.168.10.101"),
149 IpPrefix.valueOf("192.168.10.0/24")));
150 Interface sw1Eth1 = new Interface(SW1_ETH1,
151 interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"));
152 interfaces.add(sw1Eth1);
153
154 Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
155 interfaceIpAddresses2.add(new InterfaceIpAddress(
156 IpAddress.valueOf("192.168.20.101"),
157 IpPrefix.valueOf("192.168.20.0/24")));
158 Interface sw2Eth1 = new Interface(SW2_ETH1,
159 interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"));
160 interfaces.add(sw2Eth1);
161
162 Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
163 interfaceIpAddresses3.add(new InterfaceIpAddress(
164 IpAddress.valueOf("192.168.30.101"),
165 IpPrefix.valueOf("192.168.30.0/24")));
166 Interface sw3Eth1 = new Interface(SW3_ETH1,
167 interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"));
168 interfaces.add(sw3Eth1);
169
170 expect(interfaceService.getInterface(SW1_ETH1)).andReturn(
171 sw1Eth1).anyTimes();
172 expect(interfaceService.getInterface(SW2_ETH1)).andReturn(
173 sw2Eth1).anyTimes();
174 expect(interfaceService.getInterface(SW3_ETH1)).andReturn(
175 sw3Eth1).anyTimes();
176
177 expect(interfaceService.getInterfaces()).andReturn(
178 interfaces).anyTimes();
179 replay(interfaceService);
180 }
181
182 /**
183 * Sets up SdnIpConfigService and BGP peers in external networks.
184 */
185 private void setUpSdnIpConfigService() {
186
Jonathan Hart9965d772014-12-02 10:28:34 -0800187 sdnIpConfigService = createMock(SdnIpConfigurationService.class);
Pingping32fa30c2014-10-23 15:24:26 -0700188
189 bgpPeers = new HashMap<>();
190
191 String peerSw1Eth1 = "192.168.10.1";
192 bgpPeers.put(IpAddress.valueOf(peerSw1Eth1),
193 new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
194
195 String peer1Sw2Eth1 = "192.168.20.1";
196 bgpPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
197 new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
198
199 String peer2Sw2Eth1 = "192.168.30.1";
200 bgpPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
201 new BgpPeer("00:00:00:00:00:00:00:03", 1, peer2Sw2Eth1));
202
203 expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
204 replay(sdnIpConfigService);
205 }
206
207 /**
208 * Tests adding a set of routes into {@link Router}.
209 * <p/>
210 * Random routes are generated and fed in to the route processing
211 * logic (via processRouteAdd in Router class). We check that the correct
212 * intents are generated and submitted to our mock intent service.
213 *
214 * @throws InterruptedException if interrupted while waiting on a latch
215 * @throws TestUtilsException if exceptions when using TestUtils
216 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800217 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700218 public void testAddRoutes() throws InterruptedException, TestUtilsException {
219 int numRoutes = 100;
220
221 final CountDownLatch latch = new CountDownLatch(numRoutes);
222
223 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
224
225 // Set up expectation
226 reset(intentService);
227
228 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800229 Ip4Address nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700230
231 // Find out the egress ConnectPoint
232 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
233
234 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
235 generateMacAddress(nextHopAddress),
236 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800237 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700238
239 expectLastCall().andAnswer(new IAnswer<Object>() {
240 @Override
241 public Object answer() throws Throwable {
242 latch.countDown();
243 return null;
244 }
245 }).once();
246 }
247
248 replay(intentService);
249
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800250 intentSynchronizer.leaderChanged(true);
251 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700252
253 // Add route updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800254 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700255
256 latch.await(5000, TimeUnit.MILLISECONDS);
257
258 assertEquals(router.getRoutes().size(), numRoutes);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800259 assertEquals(intentSynchronizer.getRouteIntents().size(),
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800260 numRoutes);
Pingping32fa30c2014-10-23 15:24:26 -0700261
262 verify(intentService);
263 }
264
265 /**
266 * Tests adding then deleting a set of routes from {@link Router}.
267 * <p/>
268 * Random routes are generated and fed in to the route processing
269 * logic (via processRouteAdd in Router class), and we check that the
270 * correct intents are generated. We then delete the entire set of routes
271 * (by feeding updates to processRouteDelete), and check that the correct
272 * intents are withdrawn from the intent service.
273 *
274 * @throws InterruptedException if interrupted while waiting on a latch
275 * @throws TestUtilsException exceptions when using TestUtils
276 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800277 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700278 public void testDeleteRoutes() throws InterruptedException, TestUtilsException {
279 int numRoutes = 100;
280 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
281
282 final CountDownLatch installCount = new CountDownLatch(numRoutes);
283 final CountDownLatch deleteCount = new CountDownLatch(numRoutes);
284
285 // Set up expectation
286 reset(intentService);
287
288 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800289 Ip4Address nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700290
291 // Find out the egress ConnectPoint
292 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
293 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
294 generateMacAddress(nextHopAddress),
295 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800296 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700297 expectLastCall().andAnswer(new IAnswer<Object>() {
298 @Override
299 public Object answer() throws Throwable {
300 installCount.countDown();
301 return null;
302 }
303 }).once();
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800304 intentService.withdraw(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700305 expectLastCall().andAnswer(new IAnswer<Object>() {
306 @Override
307 public Object answer() throws Throwable {
308 deleteCount.countDown();
309 return null;
310 }
311 }).once();
312 }
313
314 replay(intentService);
315
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800316 intentSynchronizer.leaderChanged(true);
317 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700318
319 // Send the add updates first
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800320 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700321
322 // Give some time to let the intents be submitted
323 installCount.await(5000, TimeUnit.MILLISECONDS);
324
325 // Send the DELETE updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800326 List<RouteUpdate> deleteRouteUpdates = new ArrayList<>();
Pingping32fa30c2014-10-23 15:24:26 -0700327 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800328 RouteUpdate deleteUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
329 update.routeEntry());
330 deleteRouteUpdates.add(deleteUpdate);
Pingping32fa30c2014-10-23 15:24:26 -0700331 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800332 router.processRouteUpdates(deleteRouteUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700333
334 deleteCount.await(5000, TimeUnit.MILLISECONDS);
335
336 assertEquals(0, router.getRoutes().size());
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800337 assertEquals(0, intentSynchronizer.getRouteIntents().size());
Pingping32fa30c2014-10-23 15:24:26 -0700338 verify(intentService);
339 }
340
341 /**
342 * This methods generates random route updates.
343 *
344 * @param numRoutes the number of route updates to generate
345 * @return a list of route update
346 */
347 private List<RouteUpdate> generateRouteUpdates(int numRoutes) {
348 List<RouteUpdate> routeUpdates = new ArrayList<>(numRoutes);
349
350 Set<Ip4Prefix> prefixes = new HashSet<>();
351
352 for (int i = 0; i < numRoutes; i++) {
353 Ip4Prefix prefix;
354 do {
355 // Generate a random prefix length between MIN_PREFIX_LENGTH
356 // and MAX_PREFIX_LENGTH
357 int prefixLength = random.nextInt(
358 (MAX_PREFIX_LENGTH - MIN_PREFIX_LENGTH) + 1)
359 + MIN_PREFIX_LENGTH;
360 prefix =
361 Ip4Prefix.valueOf(Ip4Address.valueOf(random.nextInt()),
362 prefixLength);
363 // We have to ensure we don't generate the same prefix twice
364 // (this is quite easy to happen with small prefix lengths).
Pingping32fa30c2014-10-23 15:24:26 -0700365 } while (prefixes.contains(prefix));
366
367 prefixes.add(prefix);
368
369 // Randomly select a peer to use as the next hop
370 BgpPeer nextHop = null;
371 int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers()
372 .size());
373 int j = 0;
374 for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) {
375 if (j++ == peerNumber) {
376 nextHop = peer;
377 break;
378 }
379 }
380
381 assertNotNull(nextHop);
382
383 RouteUpdate update =
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800384 new RouteUpdate(RouteUpdate.Type.UPDATE,
385 new RouteEntry(prefix,
386 nextHop.ipAddress().getIp4Address()));
Pingping32fa30c2014-10-23 15:24:26 -0700387
388 routeUpdates.add(update);
389 }
390
391 return routeUpdates;
392 }
393
394 /**
395 * Generates the MultiPointToSinglePointIntent that should be
396 * submitted/withdrawn for a particular RouteUpdate.
397 *
398 * @param update the RouteUpdate to generate an intent for
399 * @param nextHopMac a MAC address to use as the dst-mac for the intent
400 * @param egressConnectPoint the outgoing ConnectPoint for the intent
401 * @return the generated intent
402 */
403 private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update,
404 MacAddress nextHopMac, ConnectPoint egressConnectPoint) {
405 IpPrefix ip4Prefix = update.routeEntry().prefix();
406
407 TrafficSelector.Builder selectorBuilder =
408 DefaultTrafficSelector.builder();
409
410 selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix);
411
412 TrafficTreatment.Builder treatmentBuilder =
413 DefaultTrafficTreatment.builder();
414 treatmentBuilder.setEthDst(nextHopMac);
415
416 Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
417 for (Interface intf : interfaceService.getInterfaces()) {
418 if (!intf.connectPoint().equals(egressConnectPoint)) {
419 ConnectPoint srcPort = intf.connectPoint();
420 ingressPoints.add(srcPort);
421 }
422 }
423
424 MultiPointToSinglePointIntent intent =
425 new MultiPointToSinglePointIntent(APPID,
426 selectorBuilder.build(), treatmentBuilder.build(),
427 ingressPoints, egressConnectPoint);
428
429 return intent;
430 }
431
432 /**
433 * Generates a MAC address based on an IP address.
434 * For the test we need MAC addresses but the actual values don't have any
435 * meaning, so we'll just generate them based on the IP address. This means
436 * we have a deterministic mapping from IP address to MAC address.
437 *
438 * @param ipAddress IP address used to generate a MAC address
439 * @return generated MAC address
440 */
441 static MacAddress generateMacAddress(IpAddress ipAddress) {
442 byte[] macAddress = new byte[MAC_ADDRESS_LENGTH];
443 ByteBuffer bb = ByteBuffer.wrap(macAddress);
444
445 // Put the IP address bytes into the lower four bytes of the MAC
446 // address. Leave the first two bytes set to 0.
447 bb.position(2);
448 bb.put(ipAddress.toOctets());
449
450 return MacAddress.valueOf(bb.array());
451 }
452
453 /**
454 * Finds out the ConnectPoint for a BGP peer address.
455 *
456 * @param bgpPeerAddress the BGP peer address.
457 */
458 private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) {
459 ConnectPoint connectPoint = null;
460
461 for (BgpPeer bgpPeer: bgpPeers.values()) {
462 if (bgpPeer.ipAddress().equals(bgpPeerAddress)) {
463 connectPoint = bgpPeer.connectPoint();
464 break;
465 }
466 }
467 return connectPoint;
468 }
469}