Pingping
Committed by Pavlin Radoslavov

port SdnIpTest.java to onos-next

Change-Id: Iec9de810b168e3fbc8f1aa447778d3883fba03a1
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>
......