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