blob: 157c73f11a1f67cb47643bc94729e00d7aa0d6d9 [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
Jonathan Hart552e31f2015-02-06 11:11:59 -080018import com.google.common.collect.Sets;
Pingping32fa30c2014-10-23 15:24:26 -070019import org.easymock.IAnswer;
20import org.junit.Before;
21import org.junit.Test;
22import org.junit.experimental.categories.Category;
23import org.onlab.junit.IntegrationTest;
24import org.onlab.junit.TestUtils;
25import org.onlab.junit.TestUtils.TestUtilsException;
Jonathan Hart6cd2f352015-01-13 17:44:45 -080026import org.onlab.packet.Ethernet;
27import org.onlab.packet.Ip4Address;
28import org.onlab.packet.Ip4Prefix;
29import org.onlab.packet.IpAddress;
30import org.onlab.packet.IpPrefix;
31import org.onlab.packet.MacAddress;
32import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080033import org.onosproject.core.ApplicationId;
34import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.PortNumber;
37import org.onosproject.net.flow.DefaultTrafficSelector;
38import org.onosproject.net.flow.DefaultTrafficTreatment;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.host.HostService;
42import org.onosproject.net.host.InterfaceIpAddress;
Jonathan Hart6cd2f352015-01-13 17:44:45 -080043import org.onosproject.net.intent.AbstractIntentTest;
Brian O'Connorabafb502014-12-02 22:26:20 -080044import org.onosproject.net.intent.IntentService;
45import org.onosproject.net.intent.MultiPointToSinglePointIntent;
Brian O'Connorabafb502014-12-02 22:26:20 -080046import org.onosproject.sdnip.config.BgpPeer;
47import org.onosproject.sdnip.config.Interface;
48import org.onosproject.sdnip.config.SdnIpConfigurationService;
Pingping32fa30c2014-10-23 15:24:26 -070049
Jonathan Hart552e31f2015-02-06 11:11:59 -080050import java.nio.ByteBuffer;
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.List;
55import java.util.Map;
56import java.util.Random;
57import java.util.Set;
58import java.util.concurrent.CountDownLatch;
59import java.util.concurrent.TimeUnit;
60
61import static org.easymock.EasyMock.*;
62import static org.junit.Assert.assertEquals;
63import static org.junit.Assert.assertNotNull;
Pingping32fa30c2014-10-23 15:24:26 -070064
65/**
66 * Integration tests for the SDN-IP application.
67 * <p/>
68 * The tests are very coarse-grained. They feed route updates in to
69 * {@link Router} (simulating routes learnt from iBGP module inside SDN-IP
70 * application), then they check that the correct intents are created and
71 * submitted to the intent service. The entire route processing logic of
72 * Router class is tested.
73 */
74@Category(IntegrationTest.class)
Brian O'Connor520c0522014-11-23 23:50:47 -080075public class SdnIpTest extends AbstractIntentTest {
Pingping32fa30c2014-10-23 15:24:26 -070076 private static final int MAC_ADDRESS_LENGTH = 6;
77 private static final int MIN_PREFIX_LENGTH = 1;
78 private static final int MAX_PREFIX_LENGTH = 32;
79
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080080 private IntentSynchronizer intentSynchronizer;
Pingping32fa30c2014-10-23 15:24:26 -070081 static Router router;
82
Jonathan Hart9965d772014-12-02 10:28:34 -080083 private SdnIpConfigurationService sdnIpConfigService;
Pingping32fa30c2014-10-23 15:24:26 -070084 private InterfaceService interfaceService;
85 private HostService hostService;
86 private IntentService intentService;
87
88 private Map<IpAddress, BgpPeer> bgpPeers;
89
90 private Random random;
91
92 static final ConnectPoint SW1_ETH1 = new ConnectPoint(
93 DeviceId.deviceId("of:0000000000000001"),
94 PortNumber.portNumber(1));
95
96 static final ConnectPoint SW2_ETH1 = new ConnectPoint(
97 DeviceId.deviceId("of:0000000000000002"),
98 PortNumber.portNumber(1));
99
100 static final ConnectPoint SW3_ETH1 = new ConnectPoint(
101 DeviceId.deviceId("of:0000000000000003"),
102 PortNumber.portNumber(1));
103
104 private static final ApplicationId APPID = new ApplicationId() {
105 @Override
106 public short id() {
107 return 1;
108 }
109
110 @Override
111 public String name() {
112 return "SDNIP";
113 }
114 };
115
116 @Before
117 public void setUp() throws Exception {
Brian O'Connor520c0522014-11-23 23:50:47 -0800118 super.setUp();
Pingping32fa30c2014-10-23 15:24:26 -0700119
120 setUpInterfaceService();
121 setUpSdnIpConfigService();
122
123 hostService = new TestHostService();
124 intentService = createMock(IntentService.class);
125 random = new Random();
126
Jonathan Hart552e31f2015-02-06 11:11:59 -0800127 intentSynchronizer = new IntentSynchronizer(APPID, intentService,
128 sdnIpConfigService,
129 interfaceService);
130 router = new Router(intentSynchronizer, hostService);
Pingping32fa30c2014-10-23 15:24:26 -0700131 }
132
133 /**
134 * Sets up InterfaceService and virtual {@link Interface}s.
135 */
136 private void setUpInterfaceService() {
137
138 interfaceService = createMock(InterfaceService.class);
139
140 Set<Interface> interfaces = Sets.newHashSet();
141
142 Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
143 interfaceIpAddresses1.add(new InterfaceIpAddress(
144 IpAddress.valueOf("192.168.10.101"),
145 IpPrefix.valueOf("192.168.10.0/24")));
146 Interface sw1Eth1 = new Interface(SW1_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800147 interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
148 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700149 interfaces.add(sw1Eth1);
150
151 Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
152 interfaceIpAddresses2.add(new InterfaceIpAddress(
153 IpAddress.valueOf("192.168.20.101"),
154 IpPrefix.valueOf("192.168.20.0/24")));
155 Interface sw2Eth1 = new Interface(SW2_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800156 interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
157 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700158 interfaces.add(sw2Eth1);
159
160 Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
161 interfaceIpAddresses3.add(new InterfaceIpAddress(
162 IpAddress.valueOf("192.168.30.101"),
163 IpPrefix.valueOf("192.168.30.0/24")));
164 Interface sw3Eth1 = new Interface(SW3_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800165 interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
166 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700167 interfaces.add(sw3Eth1);
168
169 expect(interfaceService.getInterface(SW1_ETH1)).andReturn(
170 sw1Eth1).anyTimes();
171 expect(interfaceService.getInterface(SW2_ETH1)).andReturn(
172 sw2Eth1).anyTimes();
173 expect(interfaceService.getInterface(SW3_ETH1)).andReturn(
174 sw3Eth1).anyTimes();
175
176 expect(interfaceService.getInterfaces()).andReturn(
177 interfaces).anyTimes();
178 replay(interfaceService);
179 }
180
181 /**
182 * Sets up SdnIpConfigService and BGP peers in external networks.
183 */
184 private void setUpSdnIpConfigService() {
185
Jonathan Hart9965d772014-12-02 10:28:34 -0800186 sdnIpConfigService = createMock(SdnIpConfigurationService.class);
Pingping32fa30c2014-10-23 15:24:26 -0700187
188 bgpPeers = new HashMap<>();
189
190 String peerSw1Eth1 = "192.168.10.1";
191 bgpPeers.put(IpAddress.valueOf(peerSw1Eth1),
192 new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
193
194 String peer1Sw2Eth1 = "192.168.20.1";
195 bgpPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
196 new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
197
198 String peer2Sw2Eth1 = "192.168.30.1";
199 bgpPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
200 new BgpPeer("00:00:00:00:00:00:00:03", 1, peer2Sw2Eth1));
201
202 expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
203 replay(sdnIpConfigService);
204 }
205
206 /**
207 * Tests adding a set of routes into {@link Router}.
208 * <p/>
209 * Random routes are generated and fed in to the route processing
210 * logic (via processRouteAdd in Router class). We check that the correct
211 * intents are generated and submitted to our mock intent service.
212 *
213 * @throws InterruptedException if interrupted while waiting on a latch
214 * @throws TestUtilsException if exceptions when using TestUtils
215 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800216 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700217 public void testAddRoutes() throws InterruptedException, TestUtilsException {
218 int numRoutes = 100;
219
220 final CountDownLatch latch = new CountDownLatch(numRoutes);
221
222 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
223
224 // Set up expectation
225 reset(intentService);
226
227 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800228 IpAddress nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700229
230 // Find out the egress ConnectPoint
231 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
232
233 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
234 generateMacAddress(nextHopAddress),
235 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800236 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700237
238 expectLastCall().andAnswer(new IAnswer<Object>() {
239 @Override
240 public Object answer() throws Throwable {
241 latch.countDown();
242 return null;
243 }
244 }).once();
245 }
246
247 replay(intentService);
248
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800249 intentSynchronizer.leaderChanged(true);
250 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700251
252 // Add route updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800253 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700254
255 latch.await(5000, TimeUnit.MILLISECONDS);
256
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800257 assertEquals(router.getRoutes4().size(), numRoutes);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800258 assertEquals(intentSynchronizer.getRouteIntents().size(),
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800259 numRoutes);
Pingping32fa30c2014-10-23 15:24:26 -0700260
261 verify(intentService);
262 }
263
264 /**
265 * Tests adding then deleting a set of routes from {@link Router}.
266 * <p/>
267 * Random routes are generated and fed in to the route processing
268 * logic (via processRouteAdd in Router class), and we check that the
269 * correct intents are generated. We then delete the entire set of routes
270 * (by feeding updates to processRouteDelete), and check that the correct
271 * intents are withdrawn from the intent service.
272 *
273 * @throws InterruptedException if interrupted while waiting on a latch
274 * @throws TestUtilsException exceptions when using TestUtils
275 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800276 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700277 public void testDeleteRoutes() throws InterruptedException, TestUtilsException {
278 int numRoutes = 100;
279 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
280
281 final CountDownLatch installCount = new CountDownLatch(numRoutes);
282 final CountDownLatch deleteCount = new CountDownLatch(numRoutes);
283
284 // Set up expectation
285 reset(intentService);
286
287 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800288 IpAddress nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700289
290 // Find out the egress ConnectPoint
291 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
292 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
293 generateMacAddress(nextHopAddress),
294 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800295 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700296 expectLastCall().andAnswer(new IAnswer<Object>() {
297 @Override
298 public Object answer() throws Throwable {
299 installCount.countDown();
300 return null;
301 }
302 }).once();
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800303 intentService.withdraw(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700304 expectLastCall().andAnswer(new IAnswer<Object>() {
305 @Override
306 public Object answer() throws Throwable {
307 deleteCount.countDown();
308 return null;
309 }
310 }).once();
311 }
312
313 replay(intentService);
314
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800315 intentSynchronizer.leaderChanged(true);
316 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700317
318 // Send the add updates first
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800319 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700320
321 // Give some time to let the intents be submitted
322 installCount.await(5000, TimeUnit.MILLISECONDS);
323
324 // Send the DELETE updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800325 List<RouteUpdate> deleteRouteUpdates = new ArrayList<>();
Pingping32fa30c2014-10-23 15:24:26 -0700326 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800327 RouteUpdate deleteUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
328 update.routeEntry());
329 deleteRouteUpdates.add(deleteUpdate);
Pingping32fa30c2014-10-23 15:24:26 -0700330 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800331 router.processRouteUpdates(deleteRouteUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700332
333 deleteCount.await(5000, TimeUnit.MILLISECONDS);
334
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800335 assertEquals(0, router.getRoutes4().size());
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800336 assertEquals(0, intentSynchronizer.getRouteIntents().size());
Pingping32fa30c2014-10-23 15:24:26 -0700337 verify(intentService);
338 }
339
340 /**
341 * This methods generates random route updates.
342 *
343 * @param numRoutes the number of route updates to generate
344 * @return a list of route update
345 */
346 private List<RouteUpdate> generateRouteUpdates(int numRoutes) {
347 List<RouteUpdate> routeUpdates = new ArrayList<>(numRoutes);
348
349 Set<Ip4Prefix> prefixes = new HashSet<>();
350
351 for (int i = 0; i < numRoutes; i++) {
352 Ip4Prefix prefix;
353 do {
354 // Generate a random prefix length between MIN_PREFIX_LENGTH
355 // and MAX_PREFIX_LENGTH
356 int prefixLength = random.nextInt(
357 (MAX_PREFIX_LENGTH - MIN_PREFIX_LENGTH) + 1)
358 + MIN_PREFIX_LENGTH;
359 prefix =
360 Ip4Prefix.valueOf(Ip4Address.valueOf(random.nextInt()),
361 prefixLength);
362 // We have to ensure we don't generate the same prefix twice
363 // (this is quite easy to happen with small prefix lengths).
Pingping32fa30c2014-10-23 15:24:26 -0700364 } while (prefixes.contains(prefix));
365
366 prefixes.add(prefix);
367
368 // Randomly select a peer to use as the next hop
369 BgpPeer nextHop = null;
370 int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers()
371 .size());
372 int j = 0;
373 for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) {
374 if (j++ == peerNumber) {
375 nextHop = peer;
376 break;
377 }
378 }
379
380 assertNotNull(nextHop);
381
382 RouteUpdate update =
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800383 new RouteUpdate(RouteUpdate.Type.UPDATE,
384 new RouteEntry(prefix,
385 nextHop.ipAddress().getIp4Address()));
Pingping32fa30c2014-10-23 15:24:26 -0700386
387 routeUpdates.add(update);
388 }
389
390 return routeUpdates;
391 }
392
393 /**
394 * Generates the MultiPointToSinglePointIntent that should be
395 * submitted/withdrawn for a particular RouteUpdate.
396 *
397 * @param update the RouteUpdate to generate an intent for
398 * @param nextHopMac a MAC address to use as the dst-mac for the intent
399 * @param egressConnectPoint the outgoing ConnectPoint for the intent
400 * @return the generated intent
401 */
402 private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update,
403 MacAddress nextHopMac, ConnectPoint egressConnectPoint) {
404 IpPrefix ip4Prefix = update.routeEntry().prefix();
405
406 TrafficSelector.Builder selectorBuilder =
407 DefaultTrafficSelector.builder();
408
409 selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix);
410
411 TrafficTreatment.Builder treatmentBuilder =
412 DefaultTrafficTreatment.builder();
413 treatmentBuilder.setEthDst(nextHopMac);
414
415 Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
416 for (Interface intf : interfaceService.getInterfaces()) {
417 if (!intf.connectPoint().equals(egressConnectPoint)) {
418 ConnectPoint srcPort = intf.connectPoint();
419 ingressPoints.add(srcPort);
420 }
421 }
422
423 MultiPointToSinglePointIntent intent =
424 new MultiPointToSinglePointIntent(APPID,
425 selectorBuilder.build(), treatmentBuilder.build(),
426 ingressPoints, egressConnectPoint);
427
428 return intent;
429 }
430
431 /**
432 * Generates a MAC address based on an IP address.
433 * For the test we need MAC addresses but the actual values don't have any
434 * meaning, so we'll just generate them based on the IP address. This means
435 * we have a deterministic mapping from IP address to MAC address.
436 *
437 * @param ipAddress IP address used to generate a MAC address
438 * @return generated MAC address
439 */
440 static MacAddress generateMacAddress(IpAddress ipAddress) {
441 byte[] macAddress = new byte[MAC_ADDRESS_LENGTH];
442 ByteBuffer bb = ByteBuffer.wrap(macAddress);
443
444 // Put the IP address bytes into the lower four bytes of the MAC
445 // address. Leave the first two bytes set to 0.
446 bb.position(2);
447 bb.put(ipAddress.toOctets());
448
449 return MacAddress.valueOf(bb.array());
450 }
451
452 /**
453 * Finds out the ConnectPoint for a BGP peer address.
454 *
455 * @param bgpPeerAddress the BGP peer address.
456 */
457 private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) {
458 ConnectPoint connectPoint = null;
459
460 for (BgpPeer bgpPeer: bgpPeers.values()) {
461 if (bgpPeer.ipAddress().equals(bgpPeerAddress)) {
462 connectPoint = bgpPeer.connectPoint();
463 break;
464 }
465 }
466 return connectPoint;
467 }
468}