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