/** * Copyright 2013, Big Switch Networks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. **/ package net.floodlightcontroller.loadbalancer; import static org.easymock.EasyMock.anyLong; import static org.easymock.EasyMock.anyShort; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.easymock.Capture; import org.easymock.CaptureType; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; import org.openflow.protocol.OFFlowMod; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPacketOut; import org.openflow.protocol.OFPort; import org.openflow.protocol.OFType; import org.openflow.protocol.OFPacketIn.OFPacketInReason; import org.openflow.protocol.action.OFAction; import org.openflow.protocol.action.OFActionOutput; import org.openflow.util.HexString; import org.sdnplatform.sync.ISyncService; import org.sdnplatform.sync.test.MockSyncService; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockThreadPoolService; import net.floodlightcontroller.counter.CounterStore; import net.floodlightcontroller.counter.ICounterStoreService; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.IEntityClassifierService; import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; import net.floodlightcontroller.devicemanager.test.MockDeviceManager; import net.floodlightcontroller.flowcache.FlowReconcileManager; import net.floodlightcontroller.flowcache.IFlowReconcileService; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.ICMP; import net.floodlightcontroller.packet.IPacket; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.restserver.RestApiServer; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; public class LoadBalancerTest extends FloodlightTestCase { protected LoadBalancer lb; protected FloodlightContext cntx; protected FloodlightModuleContext fmc; protected MockDeviceManager deviceManager; protected MockThreadPoolService tps; protected FlowReconcileManager frm; protected DefaultEntityClassifier entityClassifier; protected IRoutingService routingEngine; protected ITopologyService topology; protected StaticFlowEntryPusher sfp; protected MemoryStorageSource storage; protected RestApiServer restApi; protected VipsResource vipsResource; protected PoolsResource poolsResource; protected MembersResource membersResource; private MockSyncService mockSyncService; protected LBVip vip1, vip2; protected LBPool pool1, pool2, pool3; protected LBMember member1, member2, member3, member4; @Override @Before public void setUp() throws Exception { super.setUp(); lb = new LoadBalancer(); cntx = new FloodlightContext(); fmc = new FloodlightModuleContext(); entityClassifier = new DefaultEntityClassifier(); // dependency for device manager frm = new FlowReconcileManager(); //dependency for device manager tps = new MockThreadPoolService(); //dependency for device manager deviceManager = new MockDeviceManager(); topology = createMock(ITopologyService.class); routingEngine = createMock(IRoutingService.class); restApi = new RestApiServer(); sfp = new StaticFlowEntryPusher(); storage = new MemoryStorageSource(); //dependency for sfp mockSyncService = new MockSyncService(); fmc.addService(IRestApiService.class, restApi); fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider()); fmc.addService(IEntityClassifierService.class, entityClassifier); fmc.addService(IFlowReconcileService.class, frm); fmc.addService(IThreadPoolService.class, tps); fmc.addService(IDeviceService.class, deviceManager); fmc.addService(ITopologyService.class, topology); fmc.addService(IRoutingService.class, routingEngine); fmc.addService(ICounterStoreService.class, new CounterStore()); fmc.addService(IStaticFlowEntryPusherService.class, sfp); fmc.addService(ILoadBalancerService.class, lb); fmc.addService(IStorageSourceService.class, storage); fmc.addService(ISyncService.class, mockSyncService); lb.init(fmc); getMockFloodlightProvider().init(fmc); entityClassifier.init(fmc); frm.init(fmc); tps.init(fmc); mockSyncService.init(fmc); deviceManager.init(fmc); restApi.init(fmc); sfp.init(fmc); storage.init(fmc); topology.addListener(deviceManager); expectLastCall().times(1); replay(topology); lb.startUp(fmc); getMockFloodlightProvider().startUp(fmc); entityClassifier.startUp(fmc); frm.startUp(fmc); tps.startUp(fmc); mockSyncService.startUp(fmc); deviceManager.startUp(fmc); restApi.startUp(fmc); sfp.startUp(fmc); storage.startUp(fmc); verify(topology); vipsResource = new VipsResource(); poolsResource = new PoolsResource(); membersResource = new MembersResource(); vip1=null; vip2=null; pool1=null; pool2=null; pool3=null; member1=null; member2=null; member3=null; member4=null; } @Test public void testCreateVip() { String postData1, postData2; IOException error = null; postData1 = "{\"id\":\"1\",\"name\":\"vip1\",\"protocol\":\"icmp\",\"address\":\"10.0.0.100\",\"port\":\"8\"}"; postData2 = "{\"id\":\"2\",\"name\":\"vip2\",\"protocol\":\"tcp\",\"address\":\"10.0.0.200\",\"port\":\"100\"}"; try { vip1 = vipsResource.jsonToVip(postData1); } catch (IOException e) { error = e; } try { vip2 = vipsResource.jsonToVip(postData2); } catch (IOException e) { error = e; } // verify correct parsing assertFalse(vip1==null); assertFalse(vip2==null); assertTrue(error==null); lb.createVip(vip1); lb.createVip(vip2); // verify correct creation assertTrue(lb.vips.containsKey(vip1.id)); assertTrue(lb.vips.containsKey(vip2.id)); } @Test public void testRemoveVip() { testCreateVip(); // verify correct initial condition assertFalse(vip1==null); assertFalse(vip2==null); lb.removeVip(vip1.id); lb.removeVip(vip2.id); // verify correct removal assertFalse(lb.vips.containsKey(vip1.id)); assertFalse(lb.vips.containsKey(vip2.id)); } @Test public void testCreatePool() { String postData1, postData2, postData3; IOException error = null; testCreateVip(); postData1 = "{\"id\":\"1\",\"name\":\"pool1\",\"protocol\":\"icmp\",\"vip_id\":\"1\"}"; postData2 = "{\"id\":\"2\",\"name\":\"pool2\",\"protocol\":\"tcp\",\"vip_id\":\"2\"}"; postData3 = "{\"id\":\"3\",\"name\":\"pool3\",\"protocol\":\"udp\",\"vip_id\":\"3\"}"; try { pool1 = poolsResource.jsonToPool(postData1); } catch (IOException e) { error = e; } try { pool2 = poolsResource.jsonToPool(postData2); } catch (IOException e) { error = e; } try { pool3 = poolsResource.jsonToPool(postData3); } catch (IOException e) { error = e; } // verify correct parsing assertFalse(pool1==null); assertFalse(pool2==null); assertFalse(pool3==null); assertTrue(error==null); lb.createPool(pool1); lb.createPool(pool2); lb.createPool(pool3); // verify successful creates; two registered with vips and one not assertTrue(lb.pools.containsKey(pool1.id)); assertTrue(lb.vips.get(pool1.vipId).pools.contains(pool1.id)); assertTrue(lb.pools.containsKey(pool2.id)); assertTrue(lb.vips.get(pool2.vipId).pools.contains(pool2.id)); assertTrue(lb.pools.containsKey(pool3.id)); assertFalse(lb.vips.containsKey(pool3.vipId)); } @Test public void testRemovePool() { testCreateVip(); testCreatePool(); // verify correct initial condition assertFalse(vip1==null); assertFalse(vip2==null); assertFalse(pool1==null); assertFalse(pool2==null); assertFalse(pool3==null); lb.removePool(pool1.id); lb.removePool(pool2.id); lb.removePool(pool3.id); // verify correct removal assertFalse(lb.pools.containsKey(pool1.id)); assertFalse(lb.pools.containsKey(pool2.id)); assertFalse(lb.pools.containsKey(pool3.id)); //verify pool cleanup from vip assertFalse(lb.vips.get(pool1.vipId).pools.contains(pool1.id)); assertFalse(lb.vips.get(pool2.vipId).pools.contains(pool2.id)); } @Test public void testCreateMember() { String postData1, postData2, postData3, postData4; IOException error = null; testCreateVip(); testCreatePool(); postData1 = "{\"id\":\"1\",\"address\":\"10.0.0.3\",\"port\":\"8\",\"pool_id\":\"1\"}"; postData2 = "{\"id\":\"2\",\"address\":\"10.0.0.4\",\"port\":\"8\",\"pool_id\":\"1\"}"; postData3 = "{\"id\":\"3\",\"address\":\"10.0.0.5\",\"port\":\"100\",\"pool_id\":\"2\"}"; postData4 = "{\"id\":\"4\",\"address\":\"10.0.0.6\",\"port\":\"100\",\"pool_id\":\"2\"}"; try { member1 = membersResource.jsonToMember(postData1); } catch (IOException e) { error = e; } try { member2 = membersResource.jsonToMember(postData2); } catch (IOException e) { error = e; } try { member3 = membersResource.jsonToMember(postData3); } catch (IOException e) { error = e; } try { member4 = membersResource.jsonToMember(postData4); } catch (IOException e) { error = e; } // verify correct parsing assertFalse(member1==null); assertFalse(member2==null); assertFalse(member3==null); assertFalse(member4==null); assertTrue(error==null); lb.createMember(member1); lb.createMember(member2); lb.createMember(member3); lb.createMember(member4); // add the same server a second time lb.createMember(member1); // verify successful creates assertTrue(lb.members.containsKey(member1.id)); assertTrue(lb.members.containsKey(member2.id)); assertTrue(lb.members.containsKey(member3.id)); assertTrue(lb.members.containsKey(member4.id)); assertTrue(lb.pools.get(member1.poolId).members.size()==2); assertTrue(lb.pools.get(member3.poolId).members.size()==2); // member1 should inherit valid vipId from pool assertTrue(lb.vips.get(member1.vipId)!=null); } @Test public void testRemoveMember() { testCreateVip(); testCreatePool(); testCreateMember(); // verify correct initial condition assertFalse(vip1==null); assertFalse(vip2==null); assertFalse(pool1==null); assertFalse(pool2==null); assertFalse(pool3==null); assertFalse(member1==null); assertFalse(member2==null); assertFalse(member3==null); assertFalse(member4==null); lb.removeMember(member1.id); lb.removeMember(member2.id); lb.removeMember(member3.id); lb.removeMember(member4.id); // verify correct removal assertFalse(lb.members.containsKey(member1.id)); assertFalse(lb.members.containsKey(member2.id)); assertFalse(lb.members.containsKey(member3.id)); assertFalse(lb.members.containsKey(member4.id)); //verify member cleanup from pool assertFalse(lb.pools.get(member1.poolId).members.contains(member1.id)); assertFalse(lb.pools.get(member2.poolId).members.contains(member2.id)); assertFalse(lb.pools.get(member3.poolId).members.contains(member3.id)); assertFalse(lb.pools.get(member4.poolId).members.contains(member4.id)); } @Test public void testTwoSubsequentIcmpRequests() throws Exception { testCreateVip(); testCreatePool(); testCreateMember(); IOFSwitch sw1; IPacket arpRequest1, arpReply1, icmpPacket1, icmpPacket2; byte[] arpRequest1Serialized; byte[] arpReply1Serialized; byte[] icmpPacket1Serialized, icmpPacket2Serialized; OFPacketIn arpRequestPacketIn1; OFPacketIn icmpPacketIn1, icmpPacketIn2; OFPacketOut arpReplyPacketOut1; Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL); Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL); int fastWildcards = OFMatch.OFPFW_IN_PORT | OFMatch.OFPFW_NW_PROTO | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST | OFMatch.OFPFW_NW_SRC_ALL | OFMatch.OFPFW_NW_DST_ALL | OFMatch.OFPFW_NW_TOS; sw1 = EasyMock.createNiceMock(IOFSwitch.class); expect(sw1.getId()).andReturn(1L).anyTimes(); expect(sw1.getStringId()).andReturn("00:00:00:00:00:01").anyTimes(); expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn(fastWildcards).anyTimes(); expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); sw1.writeThrottled(capture(wc1), capture(bc1)); expectLastCall().anyTimes(); sw1.flush(); expectLastCall().anyTimes(); replay(sw1); sfp.switchAdded(1L); verify(sw1); /* Test plan: * - two clients and two servers on sw1 port 1, 2, 3, 4 * - mock arp request received towards vip1 from (1L, 1) * - proxy arp got pushed out to (1L, 1)- check sw1 getting the packetout * - mock icmp request received towards vip1 from (1L, 1) * - device manager list of devices queried to identify source and dest devices * - routing engine queried to get inbound and outbound routes * - check getRoute calls and responses * - sfp called to install flows * - check sfp calls */ // Build topology reset(topology); expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes(); expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes(); expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes(); expect(topology.isAttachmentPointPort(1L, (short)2)).andReturn(true).anyTimes(); expect(topology.isAttachmentPointPort(1L, (short)3)).andReturn(true).anyTimes(); expect(topology.isAttachmentPointPort(1L, (short)4)).andReturn(true).anyTimes(); replay(topology); // Build arp packets arpRequest1 = new Ethernet() .setSourceMACAddress("00:00:00:00:00:01") .setDestinationMACAddress("ff:ff:ff:ff:ff:ff") .setEtherType(Ethernet.TYPE_ARP) .setVlanID((short) 0) .setPriorityCode((byte) 0) .setPayload( new ARP() .setHardwareType(ARP.HW_TYPE_ETHERNET) .setProtocolType(ARP.PROTO_TYPE_IP) .setHardwareAddressLength((byte) 6) .setProtocolAddressLength((byte) 4) .setOpCode(ARP.OP_REQUEST) .setSenderHardwareAddress(HexString.fromHexString("00:00:00:00:00:01")) .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("10.0.0.1")) .setTargetHardwareAddress(HexString.fromHexString("00:00:00:00:00:00")) .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("10.0.0.100"))); arpRequest1Serialized = arpRequest1.serialize(); arpRequestPacketIn1 = ((OFPacketIn) getMockFloodlightProvider().getOFMessageFactory(). getMessage(OFType.PACKET_IN)) .setBufferId(-1) .setInPort((short) 1) .setPacketData(arpRequest1Serialized) .setReason(OFPacketInReason.NO_MATCH) .setTotalLength((short) arpRequest1Serialized.length); IFloodlightProviderService.bcStore.put(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet) arpRequest1); // Mock proxy arp packet-out arpReply1 = new Ethernet() .setSourceMACAddress(LBVip.LB_PROXY_MAC) .setDestinationMACAddress(HexString.fromHexString("00:00:00:00:00:01")) .setEtherType(Ethernet.TYPE_ARP) .setVlanID((short) 0) .setPriorityCode((byte) 0) .setPayload( new ARP() .setHardwareType(ARP.HW_TYPE_ETHERNET) .setProtocolType(ARP.PROTO_TYPE_IP) .setHardwareAddressLength((byte) 6) .setProtocolAddressLength((byte) 4) .setOpCode(ARP.OP_REPLY) .setSenderHardwareAddress(HexString.fromHexString(LBVip.LB_PROXY_MAC)) .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("10.0.0.100")) .setTargetHardwareAddress(HexString.fromHexString("00:00:00:00:00:01")) .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("10.0.0.1"))); arpReply1Serialized = arpReply1.serialize(); arpReplyPacketOut1 = (OFPacketOut) getMockFloodlightProvider().getOFMessageFactory(). getMessage(OFType.PACKET_OUT); arpReplyPacketOut1.setBufferId(OFPacketOut.BUFFER_ID_NONE) .setInPort(OFPort.OFPP_NONE.getValue()); List<OFAction> poactions = new ArrayList<OFAction>(); poactions.add(new OFActionOutput(arpRequestPacketIn1.getInPort(), (short) 0xffff)); arpReplyPacketOut1.setActions(poactions) .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) .setPacketData(arpReply1Serialized) .setLengthU(OFPacketOut.MINIMUM_LENGTH+ arpReplyPacketOut1.getActionsLength()+ arpReply1Serialized.length); lb.receive(sw1, arpRequestPacketIn1, cntx); verify(sw1, topology); assertTrue(wc1.hasCaptured()); // wc1 should get packetout List<OFMessage> msglist1 = wc1.getValues(); for (OFMessage m: msglist1) { if (m instanceof OFPacketOut) assertEquals(arpReplyPacketOut1, m); else assertTrue(false); // unexpected message } // // Skip arpRequest2 test - in reality this will happen, but for unit test the same logic // is already validated with arpRequest1 test above // // Build icmp packets icmpPacket1 = new Ethernet() .setSourceMACAddress("00:00:00:00:00:01") .setDestinationMACAddress(LBVip.LB_PROXY_MAC) .setEtherType(Ethernet.TYPE_IPv4) .setVlanID((short) 0) .setPriorityCode((byte) 0) .setPayload( new IPv4() .setSourceAddress("10.0.0.1") .setDestinationAddress("10.0.0.100") .setProtocol(IPv4.PROTOCOL_ICMP) .setPayload(new ICMP() .setIcmpCode((byte) 0) .setIcmpType((byte) 0))); icmpPacket1Serialized = icmpPacket1.serialize(); icmpPacketIn1 = ((OFPacketIn) getMockFloodlightProvider().getOFMessageFactory(). getMessage(OFType.PACKET_IN)) .setBufferId(-1) .setInPort((short) 1) .setPacketData(icmpPacket1Serialized) .setReason(OFPacketInReason.NO_MATCH) .setTotalLength((short) icmpPacket1Serialized.length); icmpPacket2 = new Ethernet() .setSourceMACAddress("00:00:00:00:00:02") .setDestinationMACAddress(LBVip.LB_PROXY_MAC) .setEtherType(Ethernet.TYPE_IPv4) .setVlanID((short) 0) .setPriorityCode((byte) 0) .setPayload( new IPv4() .setSourceAddress("10.0.0.2") .setDestinationAddress("10.0.0.100") .setProtocol(IPv4.PROTOCOL_ICMP) .setPayload(new ICMP() .setIcmpCode((byte) 0) .setIcmpType((byte) 0))); icmpPacket2Serialized = icmpPacket2.serialize(); icmpPacketIn2 = ((OFPacketIn) getMockFloodlightProvider().getOFMessageFactory(). getMessage(OFType.PACKET_IN)) .setBufferId(-1) .setInPort((short) 2) .setPacketData(icmpPacket2Serialized) .setReason(OFPacketInReason.NO_MATCH) .setTotalLength((short) icmpPacket2Serialized.length); byte[] dataLayerSource1 = ((Ethernet)icmpPacket1).getSourceMACAddress(); int networkSource1 = ((IPv4)((Ethernet)icmpPacket1).getPayload()).getSourceAddress(); byte[] dataLayerDest1 = HexString.fromHexString("00:00:00:00:00:03"); int networkDest1 = IPv4.toIPv4Address("10.0.0.3"); byte[] dataLayerSource2 = ((Ethernet)icmpPacket2).getSourceMACAddress(); int networkSource2 = ((IPv4)((Ethernet)icmpPacket2).getPayload()).getSourceAddress(); byte[] dataLayerDest2 = HexString.fromHexString("00:00:00:00:00:04"); int networkDest2 = IPv4.toIPv4Address("10.0.0.4"); deviceManager.learnEntity(Ethernet.toLong(dataLayerSource1), null, networkSource1, 1L, 1); deviceManager.learnEntity(Ethernet.toLong(dataLayerSource2), null, networkSource2, 1L, 2); deviceManager.learnEntity(Ethernet.toLong(dataLayerDest1), null, networkDest1, 1L, 3); deviceManager.learnEntity(Ethernet.toLong(dataLayerDest2), null, networkDest2, 1L, 4); // in bound #1 Route route1 = new Route(1L, 1L); List<NodePortTuple> nptList1 = new ArrayList<NodePortTuple>(); nptList1.add(new NodePortTuple(1L, (short)1)); nptList1.add(new NodePortTuple(1L, (short)3)); route1.setPath(nptList1); expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3, 0)).andReturn(route1).atLeastOnce(); // outbound #1 Route route2 = new Route(1L, 1L); List<NodePortTuple> nptList2 = new ArrayList<NodePortTuple>(); nptList2.add(new NodePortTuple(1L, (short)3)); nptList2.add(new NodePortTuple(1L, (short)1)); route2.setPath(nptList2); expect(routingEngine.getRoute(1L, (short)3, 1L, (short)1, 0)).andReturn(route2).atLeastOnce(); // inbound #2 Route route3 = new Route(1L, 1L); List<NodePortTuple> nptList3 = new ArrayList<NodePortTuple>(); nptList3.add(new NodePortTuple(1L, (short)2)); nptList3.add(new NodePortTuple(1L, (short)4)); route3.setPath(nptList3); expect(routingEngine.getRoute(1L, (short)2, 1L, (short)4, 0)).andReturn(route3).atLeastOnce(); // outbound #2 Route route4 = new Route(1L, 1L); List<NodePortTuple> nptList4 = new ArrayList<NodePortTuple>(); nptList4.add(new NodePortTuple(1L, (short)4)); nptList4.add(new NodePortTuple(1L, (short)2)); route4.setPath(nptList3); expect(routingEngine.getRoute(1L, (short)4, 1L, (short)2, 0)).andReturn(route4).atLeastOnce(); replay(routingEngine); wc1.reset(); IFloodlightProviderService.bcStore.put(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet) icmpPacket1); lb.receive(sw1, icmpPacketIn1, cntx); IFloodlightProviderService.bcStore.put(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet) icmpPacket2); lb.receive(sw1, icmpPacketIn2, cntx); assertTrue(wc1.hasCaptured()); // wc1 should get packetout List<OFMessage> msglist2 = wc1.getValues(); assertTrue(msglist2.size()==2); // has inbound and outbound packetouts // TODO: not seeing flowmods yet ... Map<String, OFFlowMod> map = sfp.getFlows("00:00:00:00:00:00:00:01"); assertTrue(map.size()==4); } }