Committed by
Pavlin Radoslavov
port SdnIpTest.java to onos-next
Change-Id: Iec9de810b168e3fbc8f1aa447778d3883fba03a1
Showing
7 changed files
with
662 additions
and
0 deletions
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import static org.easymock.EasyMock.createMock; | ||
4 | +import static org.easymock.EasyMock.expect; | ||
5 | +import static org.easymock.EasyMock.expectLastCall; | ||
6 | +import static org.easymock.EasyMock.replay; | ||
7 | +import static org.easymock.EasyMock.reset; | ||
8 | +import static org.easymock.EasyMock.verify; | ||
9 | +import static org.junit.Assert.assertEquals; | ||
10 | +import static org.junit.Assert.assertNotNull; | ||
11 | + | ||
12 | +import java.nio.ByteBuffer; | ||
13 | +import java.util.ArrayList; | ||
14 | +import java.util.HashMap; | ||
15 | +import java.util.HashSet; | ||
16 | +import java.util.List; | ||
17 | +import java.util.Map; | ||
18 | +import java.util.Random; | ||
19 | +import java.util.Set; | ||
20 | +import java.util.concurrent.CountDownLatch; | ||
21 | +import java.util.concurrent.TimeUnit; | ||
22 | + | ||
23 | +import org.easymock.IAnswer; | ||
24 | +import org.junit.Before; | ||
25 | +import org.junit.Test; | ||
26 | +import org.junit.experimental.categories.Category; | ||
27 | +import org.onlab.junit.IntegrationTest; | ||
28 | +import org.onlab.junit.TestUtils; | ||
29 | +import org.onlab.junit.TestUtils.TestUtilsException; | ||
30 | +import org.onlab.onos.core.ApplicationId; | ||
31 | +import org.onlab.onos.net.ConnectPoint; | ||
32 | +import org.onlab.onos.net.DeviceId; | ||
33 | +import org.onlab.onos.net.PortNumber; | ||
34 | +import org.onlab.onos.net.flow.DefaultTrafficSelector; | ||
35 | +import org.onlab.onos.net.flow.DefaultTrafficTreatment; | ||
36 | +import org.onlab.onos.net.flow.TrafficSelector; | ||
37 | +import org.onlab.onos.net.flow.TrafficTreatment; | ||
38 | +import org.onlab.onos.net.host.HostService; | ||
39 | +import org.onlab.onos.net.host.InterfaceIpAddress; | ||
40 | +import org.onlab.onos.net.intent.IntentService; | ||
41 | +import org.onlab.onos.net.intent.MultiPointToSinglePointIntent; | ||
42 | +import org.onlab.onos.sdnip.config.BgpPeer; | ||
43 | +import org.onlab.onos.sdnip.config.Interface; | ||
44 | +import org.onlab.onos.sdnip.config.SdnIpConfigService; | ||
45 | +import org.onlab.packet.Ethernet; | ||
46 | +import org.onlab.packet.Ip4Address; | ||
47 | +import org.onlab.packet.Ip4Prefix; | ||
48 | +import org.onlab.packet.IpAddress; | ||
49 | +import org.onlab.packet.IpPrefix; | ||
50 | +import org.onlab.packet.MacAddress; | ||
51 | + | ||
52 | +import 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) | ||
64 | +public 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) { | ||
210 | + IpAddress nextHopAddress = update.routeEntry().nextHop(); | ||
211 | + | ||
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) { | ||
271 | + IpAddress nextHopAddress = update.routeEntry().nextHop(); | ||
272 | + | ||
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). | ||
345 | + // TODO: | ||
346 | + // The IpPrefix does the comparison using 32 bits length, | ||
347 | + // but we need to compare only the prefix length. So I use | ||
348 | + // Ip4Prefix for this moment and changed to IpPrefix. This | ||
349 | + // can be improved in the future. | ||
350 | + } while (prefixes.contains(prefix)); | ||
351 | + | ||
352 | + prefixes.add(prefix); | ||
353 | + | ||
354 | + // Randomly select a peer to use as the next hop | ||
355 | + BgpPeer nextHop = null; | ||
356 | + int peerNumber = random.nextInt(sdnIpConfigService.getBgpPeers() | ||
357 | + .size()); | ||
358 | + int j = 0; | ||
359 | + for (BgpPeer peer : sdnIpConfigService.getBgpPeers().values()) { | ||
360 | + if (j++ == peerNumber) { | ||
361 | + nextHop = peer; | ||
362 | + break; | ||
363 | + } | ||
364 | + } | ||
365 | + | ||
366 | + assertNotNull(nextHop); | ||
367 | + | ||
368 | + RouteUpdate update = | ||
369 | + new RouteUpdate(RouteUpdate.Type.UPDATE, | ||
370 | + new RouteEntry(prefix, | ||
371 | + nextHop.ipAddress())); | ||
372 | + | ||
373 | + routeUpdates.add(update); | ||
374 | + } | ||
375 | + | ||
376 | + return routeUpdates; | ||
377 | + } | ||
378 | + | ||
379 | + /** | ||
380 | + * Generates the MultiPointToSinglePointIntent that should be | ||
381 | + * submitted/withdrawn for a particular RouteUpdate. | ||
382 | + * | ||
383 | + * @param update the RouteUpdate to generate an intent for | ||
384 | + * @param nextHopMac a MAC address to use as the dst-mac for the intent | ||
385 | + * @param egressConnectPoint the outgoing ConnectPoint for the intent | ||
386 | + * @return the generated intent | ||
387 | + */ | ||
388 | + private MultiPointToSinglePointIntent getIntentForUpdate(RouteUpdate update, | ||
389 | + MacAddress nextHopMac, ConnectPoint egressConnectPoint) { | ||
390 | + IpPrefix ip4Prefix = update.routeEntry().prefix(); | ||
391 | + | ||
392 | + TrafficSelector.Builder selectorBuilder = | ||
393 | + DefaultTrafficSelector.builder(); | ||
394 | + | ||
395 | + selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(ip4Prefix); | ||
396 | + | ||
397 | + TrafficTreatment.Builder treatmentBuilder = | ||
398 | + DefaultTrafficTreatment.builder(); | ||
399 | + treatmentBuilder.setEthDst(nextHopMac); | ||
400 | + | ||
401 | + Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>(); | ||
402 | + for (Interface intf : interfaceService.getInterfaces()) { | ||
403 | + if (!intf.connectPoint().equals(egressConnectPoint)) { | ||
404 | + ConnectPoint srcPort = intf.connectPoint(); | ||
405 | + ingressPoints.add(srcPort); | ||
406 | + } | ||
407 | + } | ||
408 | + | ||
409 | + MultiPointToSinglePointIntent intent = | ||
410 | + new MultiPointToSinglePointIntent(APPID, | ||
411 | + selectorBuilder.build(), treatmentBuilder.build(), | ||
412 | + ingressPoints, egressConnectPoint); | ||
413 | + | ||
414 | + return intent; | ||
415 | + } | ||
416 | + | ||
417 | + /** | ||
418 | + * Generates a MAC address based on an IP address. | ||
419 | + * For the test we need MAC addresses but the actual values don't have any | ||
420 | + * meaning, so we'll just generate them based on the IP address. This means | ||
421 | + * we have a deterministic mapping from IP address to MAC address. | ||
422 | + * | ||
423 | + * @param ipAddress IP address used to generate a MAC address | ||
424 | + * @return generated MAC address | ||
425 | + */ | ||
426 | + static MacAddress generateMacAddress(IpAddress ipAddress) { | ||
427 | + byte[] macAddress = new byte[MAC_ADDRESS_LENGTH]; | ||
428 | + ByteBuffer bb = ByteBuffer.wrap(macAddress); | ||
429 | + | ||
430 | + // Put the IP address bytes into the lower four bytes of the MAC | ||
431 | + // address. Leave the first two bytes set to 0. | ||
432 | + bb.position(2); | ||
433 | + bb.put(ipAddress.toOctets()); | ||
434 | + | ||
435 | + return MacAddress.valueOf(bb.array()); | ||
436 | + } | ||
437 | + | ||
438 | + /** | ||
439 | + * Finds out the ConnectPoint for a BGP peer address. | ||
440 | + * | ||
441 | + * @param bgpPeerAddress the BGP peer address. | ||
442 | + */ | ||
443 | + private ConnectPoint getConnectPoint(IpAddress bgpPeerAddress) { | ||
444 | + ConnectPoint connectPoint = null; | ||
445 | + | ||
446 | + for (BgpPeer bgpPeer: bgpPeers.values()) { | ||
447 | + if (bgpPeer.ipAddress().equals(bgpPeerAddress)) { | ||
448 | + connectPoint = bgpPeer.connectPoint(); | ||
449 | + break; | ||
450 | + } | ||
451 | + } | ||
452 | + return connectPoint; | ||
453 | + } | ||
454 | +} |
1 | +package org.onlab.onos.sdnip; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.Random; | ||
5 | +import java.util.Set; | ||
6 | +import java.util.concurrent.Executors; | ||
7 | +import java.util.concurrent.ScheduledExecutorService; | ||
8 | +import java.util.concurrent.TimeUnit; | ||
9 | + | ||
10 | +import org.onlab.onos.net.ConnectPoint; | ||
11 | +import org.onlab.onos.net.DefaultHost; | ||
12 | +import org.onlab.onos.net.DeviceId; | ||
13 | +import org.onlab.onos.net.Host; | ||
14 | +import org.onlab.onos.net.HostId; | ||
15 | +import org.onlab.onos.net.HostLocation; | ||
16 | +import org.onlab.onos.net.host.HostEvent; | ||
17 | +import org.onlab.onos.net.host.HostListener; | ||
18 | +import org.onlab.onos.net.host.HostService; | ||
19 | +import org.onlab.onos.net.host.PortAddresses; | ||
20 | +import org.onlab.onos.net.provider.ProviderId; | ||
21 | +import org.onlab.onos.sdnip.Router.InternalHostListener; | ||
22 | +import org.onlab.packet.IpAddress; | ||
23 | +import org.onlab.packet.MacAddress; | ||
24 | +import org.onlab.packet.VlanId; | ||
25 | + | ||
26 | +import com.google.common.collect.Sets; | ||
27 | + | ||
28 | +/** | ||
29 | + * Test version of the HostService which is used to simulate delays in | ||
30 | + * receiving ARP replies, as you would see in a real system due to the time | ||
31 | + * it takes to proxy ARP packets to/from the host. Requests are asynchronous, | ||
32 | + * and replies may come back to the requestor in a different order than the | ||
33 | + * requests were sent, which again you would expect to see in a real system. | ||
34 | + */ | ||
35 | +public class TestHostService implements HostService { | ||
36 | + | ||
37 | + /** | ||
38 | + * The maximum possible delay before an ARP reply is received. | ||
39 | + */ | ||
40 | + private static final int MAX_ARP_REPLY_DELAY = 30; // milliseconds | ||
41 | + | ||
42 | + /** | ||
43 | + * The probability that we already have the MAC address cached when the | ||
44 | + * caller calls {@link #getHostsByIp(IpAddress ipAddress)}. | ||
45 | + */ | ||
46 | + private static final float MAC_ALREADY_KNOWN_PROBABILITY = 0.3f; | ||
47 | + | ||
48 | + private final ScheduledExecutorService replyTaskExecutor; | ||
49 | + private final Random random; | ||
50 | + | ||
51 | + /** | ||
52 | + * Class constructor. | ||
53 | + */ | ||
54 | + public TestHostService() { | ||
55 | + replyTaskExecutor = Executors.newSingleThreadScheduledExecutor(); | ||
56 | + random = new Random(); | ||
57 | + } | ||
58 | + | ||
59 | + /** | ||
60 | + * Task used to reply to ARP requests from a different thread. Replies | ||
61 | + * usually come on a different thread in the real system, so we need to | ||
62 | + * ensure we test this behavior. | ||
63 | + */ | ||
64 | + private class ReplyTask implements Runnable { | ||
65 | + private HostListener listener; | ||
66 | + private IpAddress ipAddress; | ||
67 | + | ||
68 | + /** | ||
69 | + * Class constructor. | ||
70 | + * | ||
71 | + * @param listener the client who requests and waits the MAC address | ||
72 | + * @param ipAddress the target IP address of the request | ||
73 | + */ | ||
74 | + public ReplyTask(InternalHostListener listener, | ||
75 | + IpAddress ipAddress) { | ||
76 | + this.listener = listener; | ||
77 | + this.ipAddress = ipAddress; | ||
78 | + } | ||
79 | + | ||
80 | + @Override | ||
81 | + public void run() { | ||
82 | + Host host = getHostsByIp(ipAddress).iterator().next(); | ||
83 | + HostEvent hostevent = | ||
84 | + new HostEvent(HostEvent.Type.HOST_ADDED, host); | ||
85 | + listener.event(hostevent); | ||
86 | + } | ||
87 | + } | ||
88 | + | ||
89 | + @Override | ||
90 | + public Set<Host> getHostsByIp(IpAddress ipAddress) { | ||
91 | + float replyChance = random.nextFloat(); | ||
92 | + | ||
93 | + // We don't care what the attachment point is in the test, | ||
94 | + // so for all the hosts, we use a same ConnectPoint. | ||
95 | + Host host = new DefaultHost(ProviderId.NONE, HostId.NONE, | ||
96 | + SdnIpTest.generateMacAddress(ipAddress), VlanId.NONE, | ||
97 | + new HostLocation(SdnIpTest.SW1_ETH1, 1), | ||
98 | + Sets.newHashSet(ipAddress)); | ||
99 | + | ||
100 | + if (replyChance < MAC_ALREADY_KNOWN_PROBABILITY) { | ||
101 | + // Some percentage of the time we already know the MAC address, so | ||
102 | + // we reply directly when the requestor asks for the MAC address | ||
103 | + return Sets.newHashSet(host); | ||
104 | + } | ||
105 | + return new HashSet<Host>(); | ||
106 | + } | ||
107 | + | ||
108 | + @Override | ||
109 | + public void startMonitoringIp(IpAddress ipAddress) { | ||
110 | + | ||
111 | + // Randomly select an amount of time to delay the reply coming back to | ||
112 | + int delay = random.nextInt(MAX_ARP_REPLY_DELAY); | ||
113 | + ReplyTask replyTask = new ReplyTask( | ||
114 | + (SdnIpTest.router.new InternalHostListener()), ipAddress); | ||
115 | + replyTaskExecutor.schedule(replyTask, delay, TimeUnit.MILLISECONDS); | ||
116 | + } | ||
117 | + | ||
118 | + @Override | ||
119 | + public int getHostCount() { | ||
120 | + return 0; | ||
121 | + } | ||
122 | + | ||
123 | + @Override | ||
124 | + public Iterable<Host> getHosts() { | ||
125 | + return null; | ||
126 | + } | ||
127 | + | ||
128 | + @Override | ||
129 | + public Host getHost(HostId hostId) { | ||
130 | + return null; | ||
131 | + } | ||
132 | + | ||
133 | + @Override | ||
134 | + public Set<Host> getHostsByVlan(VlanId vlanId) { | ||
135 | + return null; | ||
136 | + } | ||
137 | + | ||
138 | + @Override | ||
139 | + public Set<Host> getHostsByMac(MacAddress mac) { | ||
140 | + return null; | ||
141 | + } | ||
142 | + | ||
143 | + @Override | ||
144 | + public Set<Host> getConnectedHosts(ConnectPoint connectPoint) { | ||
145 | + return null; | ||
146 | + } | ||
147 | + | ||
148 | + @Override | ||
149 | + public Set<Host> getConnectedHosts(DeviceId deviceId) { | ||
150 | + return null; | ||
151 | + } | ||
152 | + | ||
153 | + @Override | ||
154 | + public void stopMonitoringIp(IpAddress ip) { | ||
155 | + | ||
156 | + } | ||
157 | + | ||
158 | + @Override | ||
159 | + public void requestMac(IpAddress ip) { | ||
160 | + | ||
161 | + } | ||
162 | + | ||
163 | + @Override | ||
164 | + public Set<PortAddresses> getAddressBindings() { | ||
165 | + return null; | ||
166 | + } | ||
167 | + | ||
168 | + @Override | ||
169 | + public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) { | ||
170 | + return null; | ||
171 | + } | ||
172 | + | ||
173 | + @Override | ||
174 | + public void addListener(HostListener listener) { | ||
175 | + | ||
176 | + } | ||
177 | + | ||
178 | + @Override | ||
179 | + public void removeListener(HostListener listener) { | ||
180 | + | ||
181 | + } | ||
182 | + | ||
183 | +} |
... | @@ -42,6 +42,10 @@ | ... | @@ -42,6 +42,10 @@ |
42 | <groupId>org.onlab.onos</groupId> | 42 | <groupId>org.onlab.onos</groupId> |
43 | <artifactId>onlab-misc</artifactId> | 43 | <artifactId>onlab-misc</artifactId> |
44 | </dependency> | 44 | </dependency> |
45 | + <dependency> | ||
46 | + <groupId>org.onlab.onos</groupId> | ||
47 | + <artifactId>onlab-junit</artifactId> | ||
48 | + </dependency> | ||
45 | </dependencies> | 49 | </dependencies> |
46 | 50 | ||
47 | <build> | 51 | <build> | ... | ... |
... | @@ -388,6 +388,7 @@ | ... | @@ -388,6 +388,7 @@ |
388 | <redirectTestOutputToFile>true | 388 | <redirectTestOutputToFile>true |
389 | </redirectTestOutputToFile> | 389 | </redirectTestOutputToFile> |
390 | <printSummary>true</printSummary> | 390 | <printSummary>true</printSummary> |
391 | + <excludedGroups>org.onlab.junit.IntegrationTest</excludedGroups> | ||
391 | </configuration> | 392 | </configuration> |
392 | </plugin> | 393 | </plugin> |
393 | 394 | ... | ... |
... | @@ -49,6 +49,11 @@ | ... | @@ -49,6 +49,11 @@ |
49 | </dependency> | 49 | </dependency> |
50 | 50 | ||
51 | <dependency> | 51 | <dependency> |
52 | + <groupId>org.onlab.onos</groupId> | ||
53 | + <artifactId>onlab-junit</artifactId> | ||
54 | + </dependency> | ||
55 | + | ||
56 | + <dependency> | ||
52 | <groupId>org.apache.felix</groupId> | 57 | <groupId>org.apache.felix</groupId> |
53 | <artifactId>org.apache.felix.scr.annotations</artifactId> | 58 | <artifactId>org.apache.felix.scr.annotations</artifactId> |
54 | </dependency> | 59 | </dependency> | ... | ... |
1 | +package org.onlab.junit; | ||
2 | + | ||
3 | +/** | ||
4 | + * Marker interface used to separate unit tests from integration tests. All | ||
5 | + * integration tests should be marked with: | ||
6 | + * {@literal @Category}(IntegrationTest.class) | ||
7 | + * so that they can be run separately. | ||
8 | + */ | ||
9 | +public interface IntegrationTest { | ||
10 | +} |
... | @@ -57,6 +57,11 @@ | ... | @@ -57,6 +57,11 @@ |
57 | <artifactId>onlab-rest</artifactId> | 57 | <artifactId>onlab-rest</artifactId> |
58 | <version>${project.version}</version> | 58 | <version>${project.version}</version> |
59 | </dependency> | 59 | </dependency> |
60 | + <dependency> | ||
61 | + <groupId>org.onlab.onos</groupId> | ||
62 | + <artifactId>onlab-junit</artifactId> | ||
63 | + <scope>test</scope> | ||
64 | + </dependency> | ||
60 | 65 | ||
61 | <dependency> | 66 | <dependency> |
62 | <groupId>com.google.guava</groupId> | 67 | <groupId>com.google.guava</groupId> | ... | ... |
-
Please register or login to post a comment