blob: 36bd3b775cf34fceed495e4b846dfdcbea956f95 [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;
Jonathan Hart6cd2f352015-01-13 17:44:45 -080045import org.onlab.packet.Ethernet;
46import org.onlab.packet.Ip4Address;
47import org.onlab.packet.Ip4Prefix;
48import org.onlab.packet.IpAddress;
49import org.onlab.packet.IpPrefix;
50import org.onlab.packet.MacAddress;
51import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080052import org.onosproject.core.ApplicationId;
53import org.onosproject.net.ConnectPoint;
54import org.onosproject.net.DeviceId;
55import org.onosproject.net.PortNumber;
56import org.onosproject.net.flow.DefaultTrafficSelector;
57import org.onosproject.net.flow.DefaultTrafficTreatment;
58import org.onosproject.net.flow.TrafficSelector;
59import org.onosproject.net.flow.TrafficTreatment;
60import org.onosproject.net.host.HostService;
61import org.onosproject.net.host.InterfaceIpAddress;
Jonathan Hart6cd2f352015-01-13 17:44:45 -080062import org.onosproject.net.intent.AbstractIntentTest;
Brian O'Connorabafb502014-12-02 22:26:20 -080063import org.onosproject.net.intent.IntentService;
64import org.onosproject.net.intent.MultiPointToSinglePointIntent;
Brian O'Connorabafb502014-12-02 22:26:20 -080065import org.onosproject.sdnip.config.BgpPeer;
66import org.onosproject.sdnip.config.Interface;
67import org.onosproject.sdnip.config.SdnIpConfigurationService;
Pingping32fa30c2014-10-23 15:24:26 -070068
69import com.google.common.collect.Sets;
70
71/**
72 * Integration tests for the SDN-IP application.
73 * <p/>
74 * The tests are very coarse-grained. They feed route updates in to
75 * {@link Router} (simulating routes learnt from iBGP module inside SDN-IP
76 * application), then they check that the correct intents are created and
77 * submitted to the intent service. The entire route processing logic of
78 * Router class is tested.
79 */
80@Category(IntegrationTest.class)
Brian O'Connor520c0522014-11-23 23:50:47 -080081public class SdnIpTest extends AbstractIntentTest {
Pingping32fa30c2014-10-23 15:24:26 -070082 private static final int MAC_ADDRESS_LENGTH = 6;
83 private static final int MIN_PREFIX_LENGTH = 1;
84 private static final int MAX_PREFIX_LENGTH = 32;
85
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -080086 private IntentSynchronizer intentSynchronizer;
Pingping32fa30c2014-10-23 15:24:26 -070087 static Router router;
88
Jonathan Hart9965d772014-12-02 10:28:34 -080089 private SdnIpConfigurationService sdnIpConfigService;
Pingping32fa30c2014-10-23 15:24:26 -070090 private InterfaceService interfaceService;
91 private HostService hostService;
92 private IntentService intentService;
93
94 private Map<IpAddress, BgpPeer> bgpPeers;
95
96 private Random random;
97
98 static final ConnectPoint SW1_ETH1 = new ConnectPoint(
99 DeviceId.deviceId("of:0000000000000001"),
100 PortNumber.portNumber(1));
101
102 static final ConnectPoint SW2_ETH1 = new ConnectPoint(
103 DeviceId.deviceId("of:0000000000000002"),
104 PortNumber.portNumber(1));
105
106 static final ConnectPoint SW3_ETH1 = new ConnectPoint(
107 DeviceId.deviceId("of:0000000000000003"),
108 PortNumber.portNumber(1));
109
110 private static final ApplicationId APPID = new ApplicationId() {
111 @Override
112 public short id() {
113 return 1;
114 }
115
116 @Override
117 public String name() {
118 return "SDNIP";
119 }
120 };
121
122 @Before
123 public void setUp() throws Exception {
Brian O'Connor520c0522014-11-23 23:50:47 -0800124 super.setUp();
Pingping32fa30c2014-10-23 15:24:26 -0700125
126 setUpInterfaceService();
127 setUpSdnIpConfigService();
128
129 hostService = new TestHostService();
130 intentService = createMock(IntentService.class);
131 random = new Random();
132
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800133 intentSynchronizer = new IntentSynchronizer(APPID, intentService);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800134 router = new Router(APPID, intentSynchronizer, sdnIpConfigService,
135 interfaceService, hostService);
Pingping32fa30c2014-10-23 15:24:26 -0700136 }
137
138 /**
139 * Sets up InterfaceService and virtual {@link Interface}s.
140 */
141 private void setUpInterfaceService() {
142
143 interfaceService = createMock(InterfaceService.class);
144
145 Set<Interface> interfaces = Sets.newHashSet();
146
147 Set<InterfaceIpAddress> interfaceIpAddresses1 = Sets.newHashSet();
148 interfaceIpAddresses1.add(new InterfaceIpAddress(
149 IpAddress.valueOf("192.168.10.101"),
150 IpPrefix.valueOf("192.168.10.0/24")));
151 Interface sw1Eth1 = new Interface(SW1_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800152 interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
153 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700154 interfaces.add(sw1Eth1);
155
156 Set<InterfaceIpAddress> interfaceIpAddresses2 = Sets.newHashSet();
157 interfaceIpAddresses2.add(new InterfaceIpAddress(
158 IpAddress.valueOf("192.168.20.101"),
159 IpPrefix.valueOf("192.168.20.0/24")));
160 Interface sw2Eth1 = new Interface(SW2_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800161 interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
162 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700163 interfaces.add(sw2Eth1);
164
165 Set<InterfaceIpAddress> interfaceIpAddresses3 = Sets.newHashSet();
166 interfaceIpAddresses3.add(new InterfaceIpAddress(
167 IpAddress.valueOf("192.168.30.101"),
168 IpPrefix.valueOf("192.168.30.0/24")));
169 Interface sw3Eth1 = new Interface(SW3_ETH1,
Jonathan Hart6cd2f352015-01-13 17:44:45 -0800170 interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
171 VlanId.NONE);
Pingping32fa30c2014-10-23 15:24:26 -0700172 interfaces.add(sw3Eth1);
173
174 expect(interfaceService.getInterface(SW1_ETH1)).andReturn(
175 sw1Eth1).anyTimes();
176 expect(interfaceService.getInterface(SW2_ETH1)).andReturn(
177 sw2Eth1).anyTimes();
178 expect(interfaceService.getInterface(SW3_ETH1)).andReturn(
179 sw3Eth1).anyTimes();
180
181 expect(interfaceService.getInterfaces()).andReturn(
182 interfaces).anyTimes();
183 replay(interfaceService);
184 }
185
186 /**
187 * Sets up SdnIpConfigService and BGP peers in external networks.
188 */
189 private void setUpSdnIpConfigService() {
190
Jonathan Hart9965d772014-12-02 10:28:34 -0800191 sdnIpConfigService = createMock(SdnIpConfigurationService.class);
Pingping32fa30c2014-10-23 15:24:26 -0700192
193 bgpPeers = new HashMap<>();
194
195 String peerSw1Eth1 = "192.168.10.1";
196 bgpPeers.put(IpAddress.valueOf(peerSw1Eth1),
197 new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
198
199 String peer1Sw2Eth1 = "192.168.20.1";
200 bgpPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
201 new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
202
203 String peer2Sw2Eth1 = "192.168.30.1";
204 bgpPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
205 new BgpPeer("00:00:00:00:00:00:00:03", 1, peer2Sw2Eth1));
206
207 expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
208 replay(sdnIpConfigService);
209 }
210
211 /**
212 * Tests adding a set of routes into {@link Router}.
213 * <p/>
214 * Random routes are generated and fed in to the route processing
215 * logic (via processRouteAdd in Router class). We check that the correct
216 * intents are generated and submitted to our mock intent service.
217 *
218 * @throws InterruptedException if interrupted while waiting on a latch
219 * @throws TestUtilsException if exceptions when using TestUtils
220 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800221 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700222 public void testAddRoutes() throws InterruptedException, TestUtilsException {
223 int numRoutes = 100;
224
225 final CountDownLatch latch = new CountDownLatch(numRoutes);
226
227 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
228
229 // Set up expectation
230 reset(intentService);
231
232 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800233 IpAddress nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700234
235 // Find out the egress ConnectPoint
236 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
237
238 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
239 generateMacAddress(nextHopAddress),
240 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800241 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700242
243 expectLastCall().andAnswer(new IAnswer<Object>() {
244 @Override
245 public Object answer() throws Throwable {
246 latch.countDown();
247 return null;
248 }
249 }).once();
250 }
251
252 replay(intentService);
253
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800254 intentSynchronizer.leaderChanged(true);
255 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700256
257 // Add route updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800258 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700259
260 latch.await(5000, TimeUnit.MILLISECONDS);
261
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800262 assertEquals(router.getRoutes4().size(), numRoutes);
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800263 assertEquals(intentSynchronizer.getRouteIntents().size(),
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800264 numRoutes);
Pingping32fa30c2014-10-23 15:24:26 -0700265
266 verify(intentService);
267 }
268
269 /**
270 * Tests adding then deleting a set of routes from {@link Router}.
271 * <p/>
272 * Random routes are generated and fed in to the route processing
273 * logic (via processRouteAdd in Router class), and we check that the
274 * correct intents are generated. We then delete the entire set of routes
275 * (by feeding updates to processRouteDelete), and check that the correct
276 * intents are withdrawn from the intent service.
277 *
278 * @throws InterruptedException if interrupted while waiting on a latch
279 * @throws TestUtilsException exceptions when using TestUtils
280 */
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800281 @Test
Pingping32fa30c2014-10-23 15:24:26 -0700282 public void testDeleteRoutes() throws InterruptedException, TestUtilsException {
283 int numRoutes = 100;
284 List<RouteUpdate> routeUpdates = generateRouteUpdates(numRoutes);
285
286 final CountDownLatch installCount = new CountDownLatch(numRoutes);
287 final CountDownLatch deleteCount = new CountDownLatch(numRoutes);
288
289 // Set up expectation
290 reset(intentService);
291
292 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800293 IpAddress nextHopAddress = update.routeEntry().nextHop();
Pingping32fa30c2014-10-23 15:24:26 -0700294
295 // Find out the egress ConnectPoint
296 ConnectPoint egressConnectPoint = getConnectPoint(nextHopAddress);
297 MultiPointToSinglePointIntent intent = getIntentForUpdate(update,
298 generateMacAddress(nextHopAddress),
299 egressConnectPoint);
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800300 intentService.submit(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700301 expectLastCall().andAnswer(new IAnswer<Object>() {
302 @Override
303 public Object answer() throws Throwable {
304 installCount.countDown();
305 return null;
306 }
307 }).once();
Pavlin Radoslavov97e8a8b2014-11-24 17:51:28 -0800308 intentService.withdraw(TestIntentServiceHelper.eqExceptId(intent));
Pingping32fa30c2014-10-23 15:24:26 -0700309 expectLastCall().andAnswer(new IAnswer<Object>() {
310 @Override
311 public Object answer() throws Throwable {
312 deleteCount.countDown();
313 return null;
314 }
315 }).once();
316 }
317
318 replay(intentService);
319
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800320 intentSynchronizer.leaderChanged(true);
321 TestUtils.setField(intentSynchronizer, "isActivatedLeader", true);
Pingping32fa30c2014-10-23 15:24:26 -0700322
323 // Send the add updates first
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800324 router.processRouteUpdates(routeUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700325
326 // Give some time to let the intents be submitted
327 installCount.await(5000, TimeUnit.MILLISECONDS);
328
329 // Send the DELETE updates
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800330 List<RouteUpdate> deleteRouteUpdates = new ArrayList<>();
Pingping32fa30c2014-10-23 15:24:26 -0700331 for (RouteUpdate update : routeUpdates) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800332 RouteUpdate deleteUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
333 update.routeEntry());
334 deleteRouteUpdates.add(deleteUpdate);
Pingping32fa30c2014-10-23 15:24:26 -0700335 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800336 router.processRouteUpdates(deleteRouteUpdates);
Pingping32fa30c2014-10-23 15:24:26 -0700337
338 deleteCount.await(5000, TimeUnit.MILLISECONDS);
339
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -0800340 assertEquals(0, router.getRoutes4().size());
Pavlin Radoslavova7243cc2014-11-22 21:38:02 -0800341 assertEquals(0, intentSynchronizer.getRouteIntents().size());
Pingping32fa30c2014-10-23 15:24:26 -0700342 verify(intentService);
343 }
344
345 /**
346 * This methods generates random route updates.
347 *
348 * @param numRoutes the number of route updates to generate
349 * @return a list of route update
350 */
351 private List<RouteUpdate> generateRouteUpdates(int numRoutes) {
352 List<RouteUpdate> routeUpdates = new ArrayList<>(numRoutes);
353
354 Set<Ip4Prefix> prefixes = new HashSet<>();
355
356 for (int i = 0; i < numRoutes; i++) {
357 Ip4Prefix prefix;
358 do {
359 // Generate a random prefix length between MIN_PREFIX_LENGTH
360 // and MAX_PREFIX_LENGTH
361 int prefixLength = random.nextInt(
362 (MAX_PREFIX_LENGTH - MIN_PREFIX_LENGTH) + 1)
363 + MIN_PREFIX_LENGTH;
364 prefix =
365 Ip4Prefix.valueOf(Ip4Address.valueOf(random.nextInt()),
366 prefixLength);
367 // We have to ensure we don't generate the same prefix twice
368 // (this is quite easy to happen with small prefix lengths).
Pingping32fa30c2014-10-23 15:24:26 -0700369 } while (prefixes.contains(prefix));
370
371 prefixes.add(prefix);
372
373 // Randomly select a peer to use as the next hop
374 BgpPeer nextHop = null;
375 int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers()
376 .size());
377 int j = 0;
378 for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) {
379 if (j++ == peerNumber) {
380 nextHop = peer;
381 break;
382 }
383 }
384
385 assertNotNull(nextHop);
386
387 RouteUpdate update =
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800388 new RouteUpdate(RouteUpdate.Type.UPDATE,
389 new RouteEntry(prefix,
390 nextHop.ipAddress().getIp4Address()));
Pingping32fa30c2014-10-23 15:24:26 -0700391
392 routeUpdates.add(update);
393 }
394
395 return routeUpdates;
396 }
397
398 /**
399 * Generates the MultiPointToSinglePointIntent that should be
400 * submitted/withdrawn for a particular RouteUpdate.
401 *
402 * @param update the RouteUpdate to generate an intent for
403 * @param nextHopMac a MAC address to use as the dst-mac for the intent
404 * @param egressConnectPoint the outgoing ConnectPoint for the intent
405 * @return the generated intent
406 */
407 private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update,
408 MacAddress nextHopMac, ConnectPoint egressConnectPoint) {
409 IpPrefix ip4Prefix = update.routeEntry().prefix();
410
411 TrafficSelector.Builder selectorBuilder =
412 DefaultTrafficSelector.builder();
413
414 selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix);
415
416 TrafficTreatment.Builder treatmentBuilder =
417 DefaultTrafficTreatment.builder();
418 treatmentBuilder.setEthDst(nextHopMac);
419
420 Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
421 for (Interface intf : interfaceService.getInterfaces()) {
422 if (!intf.connectPoint().equals(egressConnectPoint)) {
423 ConnectPoint srcPort = intf.connectPoint();
424 ingressPoints.add(srcPort);
425 }
426 }
427
428 MultiPointToSinglePointIntent intent =
429 new MultiPointToSinglePointIntent(APPID,
430 selectorBuilder.build(), treatmentBuilder.build(),
431 ingressPoints, egressConnectPoint);
432
433 return intent;
434 }
435
436 /**
437 * Generates a MAC address based on an IP address.
438 * For the test we need MAC addresses but the actual values don't have any
439 * meaning, so we'll just generate them based on the IP address. This means
440 * we have a deterministic mapping from IP address to MAC address.
441 *
442 * @param ipAddress IP address used to generate a MAC address
443 * @return generated MAC address
444 */
445 static MacAddress generateMacAddress(IpAddress ipAddress) {
446 byte[] macAddress = new byte[MAC_ADDRESS_LENGTH];
447 ByteBuffer bb = ByteBuffer.wrap(macAddress);
448
449 // Put the IP address bytes into the lower four bytes of the MAC
450 // address. Leave the first two bytes set to 0.
451 bb.position(2);
452 bb.put(ipAddress.toOctets());
453
454 return MacAddress.valueOf(bb.array());
455 }
456
457 /**
458 * Finds out the ConnectPoint for a BGP peer address.
459 *
460 * @param bgpPeerAddress the BGP peer address.
461 */
462 private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) {
463 ConnectPoint connectPoint = null;
464
465 for (BgpPeer bgpPeer: bgpPeers.values()) {
466 if (bgpPeer.ipAddress().equals(bgpPeerAddress)) {
467 connectPoint = bgpPeer.connectPoint();
468 break;
469 }
470 }
471 return connectPoint;
472 }
473}