/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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 org.sdnplatform.forwarding; import static org.easymock.EasyMock.*; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.easymock.Capture; import org.easymock.CaptureType; import org.easymock.EasyMock; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.openflow.protocol.OFFlowMod; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMatchWithSwDpid; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPacketIn.OFPacketInReason; import org.openflow.protocol.OFPacketOut; import org.openflow.protocol.OFType; import org.openflow.protocol.action.OFAction; import org.openflow.protocol.action.OFActionDataLayerDestination; import org.openflow.protocol.action.OFActionDataLayerSource; import org.openflow.protocol.action.OFActionOutput; import org.openflow.protocol.action.OFActionStripVirtualLan; import org.openflow.protocol.action.OFActionVirtualLanIdentifier; import org.openflow.util.HexString; import org.openflow.util.U8; import org.sdnplatform.IBetterOFSwitch; import org.sdnplatform.core.ListenerContext; import org.sdnplatform.core.IControllerService; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.core.module.ModuleContext; import org.sdnplatform.core.test.MockControllerProvider; import org.sdnplatform.core.test.MockThreadPoolService; import org.sdnplatform.counter.CounterStore; import org.sdnplatform.counter.ICounterStoreService; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.devicemanager.IDeviceService; import org.sdnplatform.devicemanager.IEntityClassifierService; import org.sdnplatform.devicemanager.SwitchPort; import org.sdnplatform.devicemanager.internal.DefaultEntityClassifier; import org.sdnplatform.devicemanager.internal.Device; import org.sdnplatform.devicemanager.test.MockDeviceManager; import org.sdnplatform.flowcache.FlowCacheObj; import org.sdnplatform.flowcache.IFlowCacheService; import org.sdnplatform.flowcache.IFlowReconcileService; import org.sdnplatform.forwarding.Forwarding; import org.sdnplatform.forwarding.IRewriteService; import org.sdnplatform.netvirt.virtualrouting.internal.VirtualRouting; import org.sdnplatform.packet.Data; import org.sdnplatform.packet.Ethernet; import org.sdnplatform.packet.IPacket; import org.sdnplatform.packet.IPv4; import org.sdnplatform.packet.UDP; import org.sdnplatform.restserver.IRestApiService; import org.sdnplatform.restserver.RestApiServer; import org.sdnplatform.routing.IRoutingDecision; import org.sdnplatform.routing.IRoutingService; import org.sdnplatform.routing.Route; import org.sdnplatform.routing.RouteId; import org.sdnplatform.storage.IStorageSourceService; import org.sdnplatform.storage.memory.MemoryStorageSource; import org.sdnplatform.threadpool.IThreadPoolService; import org.sdnplatform.topology.ITopologyListener; import org.sdnplatform.topology.ITopologyService; import org.sdnplatform.topology.NodePortTuple; import org.sdnplatform.tunnelmanager.ITunnelManagerService; import org.sdnplatform.util.OFMessageDamper; import org.sdnplatform.vendor.OFActionNiciraTtlDecrement; public class PushRewriteRouteTest { /* * This class tests Forwarding's pushRewriteRoute. We use the following * basic topology with 4 swiches connected in a "line" * * Switch0 0x10: in port 1, out port 2 * link0 * Switch1 0x11: in port 11, out port 12 * link1 * Switch2 0x12: in port 21, out port 22 * link2 * Switch3 0x13: in port 31, out port 32 * * I.e., ingress port is 1, egress port is 32 * * It follows that we have 3 links between the switches. These can be * internal or external links. If we add the input and output port we * have 5 "zones". Let's call them: ingress, link0, link1, link2, egress. * * Each zone has a ZoneVlanMode associated with it. This indicates if we * would expect our test packet to be TAGGED or UNTAGGED in this zone. * We'll use zones and their VlanMode to help us automate test verification * * Our parameter space is as follows: * - tagged/untagged ingress * - tagged/untagged egress * - MAC rewrite * - PacketIn switch (sw 0--3 and a switch not on the route) * - Link-type: * + internal (always tagged) * + external untagged (transport VLAN is ports native Vlan) * + external tagged (transport VLAN is not native) * * TODO: now that we have TestConfig, the verify* and setup* should just * take a TestConfig as parameter.... */ protected enum ZoneVlanMode { TAGGED, UNTAGGED }; protected enum LinkType { INT, EXT_TAGGED, EXT_UNTAGGED }; protected MockControllerProvider mockControllerProvider; protected ListenerContext cntx; protected MockThreadPoolService threadPool; protected MockDeviceManager deviceManager; protected IRoutingService routingEngine; protected ITopologyService topology; protected ITunnelManagerService tunnelManager; protected RestApiServer restApi; protected Forwarding forwarding; protected IFlowReconcileService flowReconcileMgr; protected IFlowCacheService flowCacheService; protected Capture<OFMatchWithSwDpid> flowCacheOfmCapture; protected DefaultEntityClassifier entityClassifier; protected IRewriteService rewriteService; protected IOFSwitch[] switches; // swithes for use in multi-action packet out for netVirt-broadcast protected Capture<OFMessage>[] writeCaptures; // Capture writes to switches protected Capture<ListenerContext>[] cntxCaptures; // Capture writes to switches protected IDevice srcDevice, dstDevice; protected OFPacketIn packetIn; protected IPacket testPacket; protected byte[] testPacketSerialized; protected Long packetSrcMac; protected Long packetDstMac; protected static long cookie = 0x42a23a42; protected String curTestId; protected int expected_wildcards; protected Short vlan; private int networkSource; private int networkDest; private short initialPacketTtl; @SuppressWarnings("unchecked") @Before public void setUp() throws Exception { // Mock context cntx = new ListenerContext(); mockControllerProvider = new MockControllerProvider(); forwarding = new Forwarding(); threadPool = new MockThreadPoolService(); deviceManager = new MockDeviceManager(); routingEngine = createMock(IRoutingService.class); topology = createMock(ITopologyService.class); tunnelManager = createMock(ITunnelManagerService.class); flowReconcileMgr = createMock(IFlowReconcileService.class); rewriteService = createMock(IRewriteService.class); entityClassifier = new DefaultEntityClassifier(); restApi = new RestApiServer(); flowCacheService = createMock(IFlowCacheService.class); flowCacheOfmCapture = new Capture<OFMatchWithSwDpid>(CaptureType.ALL); ModuleContext fmc = new ModuleContext(); fmc.addService(IControllerService.class, mockControllerProvider); fmc.addService(ITopologyService.class, topology); fmc.addService(IThreadPoolService.class, threadPool); fmc.addService(IRoutingService.class, routingEngine); fmc.addService(ICounterStoreService.class, new CounterStore()); fmc.addService(IDeviceService.class, deviceManager); fmc.addService(IFlowCacheService.class, flowCacheService); fmc.addService(ITunnelManagerService.class, tunnelManager); fmc.addService(IFlowReconcileService.class, flowReconcileMgr); fmc.addService(IRewriteService.class, rewriteService); fmc.addService(IEntityClassifierService.class, entityClassifier); fmc.addService(IRestApiService.class, restApi); fmc.addService(IStorageSourceService.class, new MemoryStorageSource()); deviceManager.init(fmc); threadPool.init(fmc); forwarding.init(fmc); forwarding.setMessageDamper( new OFMessageDamper(1, EnumSet.noneOf(OFType.class), 0)); entityClassifier.init(fmc); restApi.init(fmc); threadPool.startUp(fmc); deviceManager.startUp(fmc); forwarding.startUp(fmc); entityClassifier.startUp(fmc); restApi.startUp(fmc); // Mock tunnel service expect(tunnelManager.isTunnelEndpoint(anyObject(IDevice.class))) .andReturn(false).anyTimes(); expect(tunnelManager.isTunnelEndpoint(null)).andReturn(false).anyTimes(); expect(tunnelManager.getTunnelPortNumber(EasyMock.anyLong())).andReturn(null).anyTimes(); expect(tunnelManager.getTunnelLoopbackPort(EasyMock.anyLong())).andReturn(null).anyTimes(); replay(tunnelManager); IFlowCacheService.fcStore.put(cntx, IFlowCacheService.FLOWCACHE_APP_NAME, "netVirt"); IFlowCacheService.fcStore.put(cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME, "netVirt1"); vlan = 23; // Load the switch map Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>(); switches = new IOFSwitch[4]; writeCaptures = new Capture[4]; cntxCaptures = new Capture[4]; for (int i=0; i<4; i++) { switches[i] = createMock(IOFSwitch.class); switchMap.put(16L+i, switches[i]); writeCaptures[i] = new Capture<OFMessage>(CaptureType.ALL); cntxCaptures[i] = new Capture<ListenerContext>(CaptureType.ALL); } mockControllerProvider.setSwitches(switchMap); // Build test packet initialPacketTtl = (short)128; testPacket = new Ethernet() .setDestinationMACAddress("00:11:22:33:44:55") .setSourceMACAddress("00:44:33:22:11:00") .setEtherType(Ethernet.TYPE_IPv4) .setPayload( new IPv4() .setTtl(U8.t(initialPacketTtl)) .setSourceAddress("192.168.1.1") .setDestinationAddress("192.168.1.2") .setPayload(new UDP() .setSourcePort((short) 5000) .setDestinationPort((short) 5001) .setPayload(new Data(new byte[] {0x01})))); testPacketSerialized = testPacket.serialize(); // Build src and dest devices packetSrcMac = Ethernet.toLong(((Ethernet)testPacket) .getSourceMACAddress()); packetDstMac = Ethernet.toLong(((Ethernet)testPacket) .getDestinationMACAddress()); networkSource = ((IPv4)((Ethernet)testPacket).getPayload()) .getSourceAddress(); networkDest = ((IPv4)((Ethernet)testPacket).getPayload()) .getDestinationAddress(); expect(topology.isAttachmentPointPort(EasyMock.anyLong(), EasyMock.anyShort())).andReturn(true).anyTimes(); topology.addListener(anyObject(ITopologyListener.class)); expectLastCall().anyTimes(); expect(topology.getL2DomainId(anyLong())).andReturn(0x10L).anyTimes(); replay(topology); srcDevice = deviceManager.learnEntity(packetSrcMac, null, networkSource, 0x10L, 1); dstDevice = deviceManager.learnEntity(packetDstMac, null, networkDest, 0x13L, 32); // Mock Packet-in packetIn = ((OFPacketIn) mockControllerProvider .getOFMessageFactory() .getMessage(OFType.PACKET_IN)) .setBufferId(0x42) .setInPort((short) 1) .setPacketData(testPacketSerialized) .setReason(OFPacketInReason.NO_MATCH) .setTotalLength((short) testPacketSerialized.length); int expected_match_fields = OFMatch.OFPFW_DL_SRC | OFMatch.OFPFW_DL_DST | OFMatch.OFPFW_DL_VLAN | OFMatch.OFPFW_IN_PORT | OFMatch.OFPFW_DL_TYPE | OFMatch.OFPFW_NW_SRC_MASK | OFMatch.OFPFW_NW_DST_MASK; expected_wildcards = OFMatch.OFPFW_ALL & ~expected_match_fields; curTestId = "<foo>"; } @After public void tearDown() { verify(tunnelManager); } /* change the global packet in to make it tagged with the global vlan */ protected void tagPacketIn() { Ethernet eth = (Ethernet)testPacket; eth.setVlanID(vlan); testPacketSerialized = testPacket.serialize(); srcDevice = deviceManager.learnEntity(packetSrcMac, vlan, null, 0x10L, 1); packetIn.setPacketData(testPacketSerialized); packetIn.setTotalLength((short) testPacketSerialized.length); } /* non-javadoc * * Resets all mock switches and sets their expectations. Also sets * up captures for sw.write() * @param doReplay if true replay() will be called on each switch * @throws Exception */ protected void setupMockSwitches(boolean doFlush, boolean doReplay, boolean expectPacketIgnored) throws Exception { //fastWilcards mocked as this constant int fastWildcards = OFMatch.OFPFW_ALL; long clusterId = 16L; for (int i=0; i<switches.length; i++) { IOFSwitch sw = switches[i]; reset(sw); long swId = 16+i; // also change the switchMap in setUp if you change here String strId = HexString.toHexString(swId); expect(sw.getId()).andReturn(swId).anyTimes(); expect(sw.getStringId()).andReturn(strId).anyTimes(); expect(topology.getL2DomainId(swId)).andReturn(clusterId).anyTimes(); if (i == 0) { // first hop switch expect(topology.isAttachmentPointPort(swId, (short)1)) .andReturn(true).anyTimes(); expect(topology.isInSameBroadcastDomain(swId, (short)1, swId, (short)1, false)) .andReturn(true).anyTimes(); } if (! expectPacketIgnored) { if (doFlush) { sw.flush(); expectLastCall().atLeastOnce(); } } // Setup switch properties expect(sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)) .andReturn(fastWildcards).anyTimes(); expect(sw.hasAttribute(IOFSwitch.PROP_FASTWILDCARDS)) .andReturn(true).anyTimes(); expect(sw.hasAttribute(IOFSwitch.PROP_REQUIRES_L3_MATCH)) .andReturn(false).anyTimes(); expect(sw.attributeEquals(IBetterOFSwitch.SUPPORTS_NX_TTL_DECREMENT, true)) .andReturn(true).anyTimes(); sw.write(capture(writeCaptures[i]), capture(cntxCaptures[i])); expectLastCall().anyTimes(); if (doReplay) replay(sw); } } /* Reset the IOFSwitch.write() captures */ protected void resetCaptures() { flowCacheOfmCapture.reset(); for (int i=0; i<writeCaptures.length; i++) { writeCaptures[i].reset(); cntxCaptures[i].reset(); } } /* reset expectations on all mocks */ protected void resetAllMocks() { resetCaptures(); reset(topology, rewriteService, flowCacheService); for (IOFSwitch sw: switches) reset(sw); } /* non-javadoc * Creates new easyMock'ed IRoutingDecision * @param doReplay if true will call replay on the IRoutingDecision * @return */ protected IRoutingDecision getMockRoutingDecision(SwitchPort packetInSwp, boolean doReplay) { IRoutingDecision d = createMock(IRoutingDecision.class); ArrayList<IDevice> dstDeviceList = new ArrayList<IDevice>(1); dstDeviceList.add(dstDevice); expect(d.getSourceDevice()).andReturn(srcDevice).anyTimes(); expect(d.getDestinationDevices()).andReturn(dstDeviceList).anyTimes(); expect(d.getWildcards()).andReturn(expected_wildcards).anyTimes(); expect(d.getSourcePort()).andReturn(packetInSwp).anyTimes(); if (doReplay) replay(d); return d; } /* Returns the route for our sample topology */ protected Route getTestRoute() { RouteId rid = new RouteId(packetSrcMac, packetDstMac); ArrayList<NodePortTuple> switchPorts = new ArrayList<NodePortTuple>(); assertEquals(4, switches.length); // make sure nobody changes the number // of switches without changing the rte /* We have 4 switches on the route and thus 3 links */ switchPorts.add(new NodePortTuple(0x10L, (short)1)); switchPorts.add(new NodePortTuple(0x10L, (short)2)); // link 0 is here switchPorts.add(new NodePortTuple(0x11L, (short)11)); switchPorts.add(new NodePortTuple(0x11L, (short)12)); // link 1 is here switchPorts.add(new NodePortTuple(0x12L, (short)21)); switchPorts.add(new NodePortTuple(0x12L, (short)22)); // link2 is here switchPorts.add(new NodePortTuple(0x13L, (short)31)); switchPorts.add(new NodePortTuple(0x13L, (short)32)); return new Route(rid, switchPorts); } protected enum SwitchPortDirection { INPUT, OUTPUT }; /* Given the idx of one of the switches in our topology and a port * direction will return the appropriate SwitchPort */ protected SwitchPort getSwitchPort(int swIdx, SwitchPortDirection dir) { if (dir == SwitchPortDirection.INPUT) return new SwitchPort(16 + swIdx, 10*swIdx + 1); else return new SwitchPort(16 + swIdx, 10*swIdx + 2); } /* Sets the topology and rewriteService mock expectations for the given * SwitchPort and transportVlan for a port that connects to an * "external" link were we wan the vlan to be tagged" */ protected void setExpectExternalTagged(SwitchPort swp, Short transportVlan) { long dpid = swp.getSwitchDPID(); short port = (short) swp.getPort(); expect(topology.isAttachmentPointPort(eq(dpid), eq(port), anyBoolean())) .andReturn(true).anyTimes(); expect(rewriteService.getSwitchPortVlanMode(eq(swp), anyObject(String.class), anyShort(), anyBoolean())) .andReturn(transportVlan).anyTimes(); } /* Sets the topology and rewriteService mock expectations for the given * SwitchPort and transportVlan for a port that connects to an * "external" link were we wan the vlan to be untagged/native" */ protected void setExpectExternalUntagged(SwitchPort swp, Short transportVlan) { long dpid = swp.getSwitchDPID(); short port = (short) swp.getPort(); expect(topology.isAttachmentPointPort(eq(dpid), eq(port), anyBoolean())) .andReturn(true).anyTimes(); expect(rewriteService.getSwitchPortVlanMode(eq(swp), anyObject(String.class), anyShort(), anyBoolean())) .andReturn(Ethernet.VLAN_UNTAGGED).anyTimes(); } /* Sets the topology and rewriteService mock expectations for the given * SwitchPort and transportVlan for a port that connects to an * "internal" link */ protected void setExpectInternal(SwitchPort swp, Short transportVlan) { long dpid = swp.getSwitchDPID(); short port = (short) swp.getPort(); expect(topology.isAttachmentPointPort(eq(dpid), eq(port), anyBoolean())) .andReturn(false).anyTimes(); expect(rewriteService.getSwitchPortVlanMode(eq(swp), anyObject(String.class), anyShort(), anyBoolean())) .andReturn(transportVlan).anyTimes(); } /* Set the expectations for rewriteService and topology assuming the * given transportVlan, Mac rewrite config, ZoneVlanModes and linkTypes. */ protected void setupZoneExpectations(Short transportVlan, Long origDstMac, Long finalDstMac, Long origSrcMac, Long finalSrcMac, Integer ttlDecrement, ZoneVlanMode[] vlanModes, LinkType[] linkTypes) { expect(rewriteService.getTransportVlan(cntx)) .andReturn(transportVlan).anyTimes(); expect(rewriteService.getOrigIngressDstMac(cntx)) .andReturn(origDstMac).anyTimes(); expect(rewriteService.getFinalIngressDstMac(cntx)) .andReturn(finalDstMac).anyTimes(); expect(rewriteService.getOrigEgressSrcMac(cntx)) .andReturn(origSrcMac).anyTimes(); expect(rewriteService.getFinalEgressSrcMac(cntx)) .andReturn(finalSrcMac).anyTimes(); expect(rewriteService.getTtlDecrement(cntx)) .andReturn(ttlDecrement).anyTimes(); // Make sure we have the right number of zones / links assertEquals("inconsistent test setup", 5, vlanModes.length); assertEquals("inconsistent test setup", 3, linkTypes.length); // setup expectations for links for (int i=0; i < linkTypes.length; i++) { SwitchPort srcSwp = getSwitchPort(i, SwitchPortDirection.OUTPUT); SwitchPort dstSwp = getSwitchPort(i+1, SwitchPortDirection.INPUT); if (linkTypes[i] == LinkType.INT) { setExpectInternal(srcSwp, transportVlan); setExpectInternal(dstSwp, transportVlan); // link i <==> zone i+1 assertEquals("inconsistent test setup link " + i, ZoneVlanMode.TAGGED, vlanModes[i+1]); } else if (linkTypes[i] == LinkType.EXT_TAGGED) { setExpectExternalTagged(srcSwp, transportVlan); setExpectExternalTagged(dstSwp, transportVlan); // link i <==> zone i+1 assertEquals("inconsistent test setup link " + i, ZoneVlanMode.TAGGED, vlanModes[i+1]); } else { setExpectExternalUntagged(srcSwp, transportVlan); setExpectExternalUntagged(dstSwp, transportVlan); // link i <==> zone i+1 assertEquals("inconsistent test setup link " + i, ZoneVlanMode.UNTAGGED, vlanModes[i+1]); } } // setup expectation for egress port SwitchPort egressSwp = getSwitchPort(3, SwitchPortDirection.OUTPUT); if (vlanModes[4] == ZoneVlanMode.TAGGED) setExpectExternalTagged(egressSwp, transportVlan); else setExpectExternalUntagged(egressSwp, transportVlan); // no-op for ingress port } /* * Set the expectations for flowCacheService */ protected void setupFlowCacheExpectations(boolean expectPacketIgnored) { // The switch/port will always be for the ingress port on the first // hop switch if (expectPacketIgnored) return; flowCacheService.addFlow(eq(cntx), capture(flowCacheOfmCapture), eq(forwarding.appCookie), eq(new SwitchPort(0x10L, (short)1)), EasyMock.anyShort(), eq(FlowCacheObj.FCActionPERMIT)); expectLastCall().andReturn(true).once(); } /* * Verify that the given OFActions of the given FlowMod are consistent * with the given config */ protected void verifyFlodModActions(OFFlowMod fm, int outPort, Short transportVlan, Long origDstMac, Long finalDstMac, Long origSrcMac, Long finalSrcMac, Integer ttlDecrement, ZoneVlanMode inVlanMode, ZoneVlanMode outVlanMode) { List<OFAction> actions = new ArrayList<OFAction>(fm.getActions()); OFAction a; // output action must be the last action in the array a = new OFActionOutput((short)outPort); assertEquals(curTestId, true, actions.size()>0); assertEquals(curTestId, a, actions.get(actions.size()-1)); actions.remove(a); // check that dstMac rewrite action is present if necessary if (origDstMac != null && !origDstMac.equals(finalDstMac)) { a = new OFActionDataLayerDestination( Ethernet.toByteArray(finalDstMac)); assertEquals(curTestId, true, actions.remove(a)); } // check that srcMac rewrite action is present if necessary if (origSrcMac != null && !origSrcMac.equals(finalSrcMac)) { a = new OFActionDataLayerSource(Ethernet.toByteArray(finalSrcMac)); assertEquals(curTestId, true, actions.remove(a)); } // check that TTL decrement is present if (ttlDecrement != null) { a = new OFActionNiciraTtlDecrement(); assertEquals(curTestId, true, actions.remove(a)); } // check vlan rewrite action if (inVlanMode == ZoneVlanMode.TAGGED && outVlanMode == ZoneVlanMode.UNTAGGED) { a = new OFActionStripVirtualLan(); assertEquals(curTestId, true, actions.remove(a)); } else if (inVlanMode == ZoneVlanMode.UNTAGGED && outVlanMode == ZoneVlanMode.TAGGED) { a = new OFActionVirtualLanIdentifier(transportVlan); assertEquals(curTestId, true, actions.remove(a)); } else { // no vlan change ==> no action } // no more actions should be present assertEquals(curTestId, 0, actions.size()); } /* * Verify that the given FlodMod matches the given non OFActions fields */ protected void verifyFlowModNonActionFields(OFFlowMod fm, OFMatch expectedMatch, int expectedBufferId, short expectedCommand, boolean expectFlowRemovedNotif ) { assertEquals(curTestId, expectedBufferId, fm.getBufferId()); assertEquals(curTestId, expectedCommand, fm.getCommand()); assertEquals(curTestId, expectedMatch, fm.getMatch()); if (expectFlowRemovedNotif) assertEquals(curTestId, OFFlowMod.OFPFF_SEND_FLOW_REM, fm.getFlags()); else assertEquals(curTestId, 0, fm.getFlags()); assertEquals(curTestId, 0, fm.getHardTimeout()); assertEquals(curTestId, 5, fm.getIdleTimeout()); assertEquals(curTestId, forwarding.getAccessPriority(), fm.getPriority()); assertEquals(curTestId, cookie, fm.getCookie()); } /* * Verify that the given OFPacketOut matches the given config */ protected void verifyPacketOut(OFPacketOut po, Ethernet eth, int expectedBufferId, int expectedInPort, int expectedOutPort, Short transportVlan, ZoneVlanMode inVlanMode, ZoneVlanMode outVlanMode) { assertEquals(curTestId, expectedInPort, po.getInPort()); assertEquals(curTestId, expectedBufferId, po.getBufferId()); if (outVlanMode == ZoneVlanMode.UNTAGGED) eth.setVlanID(Ethernet.VLAN_UNTAGGED); else eth.setVlanID(transportVlan); assertArrayEquals(curTestId, eth.serialize(), po.getPacketData()); List<OFAction> actions = po.getActions(); assertEquals(curTestId, 1, actions.size()); OFActionOutput expectedAction = new OFActionOutput((short)expectedOutPort, (short)0xffff); assertEquals(curTestId, expectedAction, actions.get(0)); } /* * Verifies the writeCaptures and makes sure we received what we * expected */ protected void doVerify(Short transportVlan, int bufferId, Long origDstMac, Long finalDstMac, Long origSrcMac, Long finalSrcMac, Integer ttlDecrement, Long packetInSwitchId, boolean haveRemoveNotif, short command, ZoneVlanMode[] vlanModes, LinkType[] linkTypes, boolean isOFMatchTest) { String saveCurTestId = curTestId; boolean expectPacketOut = !isOFMatchTest; for(int i=0; i<switches.length; i++) { curTestId = saveCurTestId + "SwitchIdx=" + i; int inputZoneIdx = i; // zone index of input switch port int outputZoneIdx = i+1; // zone index of output switch port long curSwitchId = 0x10L + i; int expectedBufferId; SwitchPort outSwp = getSwitchPort(i, SwitchPortDirection.OUTPUT); SwitchPort inSwp = getSwitchPort(i, SwitchPortDirection.INPUT); // create the expected OFMatch OFMatch expectedMatch = new OFMatch(); expectedMatch.loadFromPacket(testPacketSerialized, (short)inSwp.getPort()); if (vlanModes[inputZoneIdx] == ZoneVlanMode.TAGGED) { expectedMatch.setDataLayerVirtualLan(transportVlan); } else { expectedMatch.setDataLayerVirtualLan(Ethernet.VLAN_UNTAGGED); } if (i>0 && finalDstMac != null) { expectedMatch.setDataLayerDestination( Ethernet.toByteArray(finalDstMac)); } expectedMatch.setWildcards(expected_wildcards); // get other expected fields in flow mod & packet out // TODO if (packetInSwitchId.equals(curSwitchId)) expectedBufferId = -1; else expectedBufferId = -1; Long curSwitchOrigDstMac; Long curSwitchFinalDstMac; Long curSwitchOrigSrcMac; Long curSwitchFinalSrcMac; Integer curSwitchTtlDecrement; Ethernet eth = (Ethernet)testPacket.clone(); // we always set the dest mac in the reference ether // since we never expect a packetOut // with the original mac if (finalDstMac != null) eth.setDestinationMACAddress(Ethernet.toByteArray(finalDstMac)); // on the same note, we always decrement the TTL in the reference // eth if (ttlDecrement != null) { // NOTE: we expect that forwarding.decrementTtl is correct. // there's a test for it in ForwardingTest boolean notExpired = forwarding.decrementTtl(eth, ttlDecrement); // quick cross-check to make sure decrementTtl did the right thing assertEquals(curTestId, notExpired, initialPacketTtl > ttlDecrement); if (ttlDecrement >= initialPacketTtl && !isOFMatchTest) { // TTL will expire. Expect drop. assertEquals(curTestId, false, writeCaptures[i].hasCaptured()); continue; } } // Check that we have captured something. assertEquals(curTestId, true, writeCaptures[i].hasCaptured()); List <OFMessage> msgs = writeCaptures[i].getValues(); Iterator<OFMessage> it = msgs.iterator(); int flowModCount = 0; int packetOutCount = 0; // Fill out our expectations based on the switch Idx if (i == 0) { // FIRST HOP SWITCH. might have mac rewriting curSwitchOrigDstMac = origDstMac; curSwitchFinalDstMac = finalDstMac; // might have TTL decrement curSwitchTtlDecrement = ttlDecrement; // We also check the flow cache addFlow call here since the // expectedMatch is properly constructed. We could also do // this check outside the loop. assertEquals(curTestId, 1, flowCacheOfmCapture.getValues().size()); OFMatchWithSwDpid fcMatch = flowCacheOfmCapture.getValue(); assertEquals(curTestId, curSwitchId, fcMatch.getSwitchDataPathId()); assertEquals(curTestId, expectedMatch, fcMatch.getOfMatch()); } else { // not the first hop. DstMac will have been rewritten before curSwitchOrigDstMac = finalDstMac; // [sic] set both to finalDstMac curSwitchFinalDstMac = finalDstMac; curSwitchTtlDecrement = null; } if (i == switches.length-1) { // LAST HOP SWITCH // might have src mac rewrite curSwitchOrigSrcMac = origSrcMac; curSwitchFinalSrcMac = finalSrcMac; if (finalSrcMac != null) { eth.setSourceMACAddress(Ethernet.toByteArray(finalSrcMac)); } } else { curSwitchOrigSrcMac = origSrcMac; // [sic] set both the origSrcMac curSwitchFinalSrcMac = origSrcMac; } // Check the OFMessages we captured while (it.hasNext()) { OFMessage ofm = it.next(); if (ofm instanceof OFFlowMod) { flowModCount++; OFFlowMod offm = (OFFlowMod)ofm; verifyFlowModNonActionFields(offm, expectedMatch, expectedBufferId, command, haveRemoveNotif && (i==0)); verifyFlodModActions((OFFlowMod)ofm, outSwp.getPort(), transportVlan, curSwitchOrigDstMac, curSwitchFinalDstMac, curSwitchOrigSrcMac, curSwitchFinalSrcMac, curSwitchTtlDecrement, vlanModes[inputZoneIdx], vlanModes[outputZoneIdx]); it.remove(); } else if (expectPacketOut && ofm instanceof OFPacketOut) { packetOutCount++; verifyPacketOut((OFPacketOut)ofm, (Ethernet)eth.clone(), expectedBufferId, inSwp.getPort(), outSwp.getPort(), transportVlan, vlanModes[inputZoneIdx], vlanModes[outputZoneIdx]); it.remove(); } } // we expect exactly one flow mode assertEquals(curTestId, 1, flowModCount); if (packetInSwitchId == curSwitchId && expectPacketOut) assertEquals(curTestId, 1, packetOutCount); else assertEquals(curTestId, 0, packetOutCount); assertEquals(curTestId, 0, msgs.size()); } } /* calls verify() on all mocks */ protected void verifyAllMocks() { verify(topology, rewriteService, flowCacheService); for (IOFSwitch sw: switches) verify(sw); } /* * Collects the config for a particular test run */ class TestConfig { public TestConfig() { // setup some defaults this.vlan = PushRewriteRouteTest.this.vlan; this.packet = (Ethernet)PushRewriteRouteTest.this.testPacket; this.packetIn = PushRewriteRouteTest.this.packetIn; } public boolean requestFlowRemovedNotifn; public boolean doFlush; public int bufferId; public Long origDstMac; public Long finalDstMac; public Long origSrcMac; public Long finalSrcMac; public Integer ttlDecrement; public ZoneVlanMode[] vlanModes; public LinkType[] linkTypes; public SwitchPort packetInSwp; public Short vlan; public Ethernet packet; public OFPacketIn packetIn; public short flowModCmd; public void setupDstMac(Long origMac, Long finalMac) { if (finalMac != null ) { this.origDstMac = origMac; this.finalDstMac = finalMac; } else { this.origDstMac = null; this.finalDstMac = null; } } public void setupSrcMac(Long origMac, Long finalMac) { if (finalMac != null ) { this.origSrcMac = origMac; this.finalSrcMac = finalMac; } else { this.origSrcMac = null; this.finalSrcMac = null; } } @Override public String toString() { String rv = "Config<"; rv += "vlan=" + this.vlan; rv += ", zones="; for (ZoneVlanMode vm: vlanModes) rv += (vm==ZoneVlanMode.TAGGED) ? "T" : "U"; rv += ", links="; for (LinkType lt: linkTypes) { if (lt == LinkType.INT) rv += "I"; else if (lt == LinkType.EXT_TAGGED) rv += "T"; else rv += "U"; } rv += ", pinSwitch=" + packetInSwp.getSwitchDPID(); if (finalSrcMac != null) rv += ", SrcMacRewrite=" + HexString.toHexString(finalSrcMac, 6); if (finalDstMac != null) rv += ", DstMacRewrite=" + HexString.toHexString(finalDstMac, 6); if (ttlDecrement != null) rv += ", TTLDec=" + ttlDecrement; rv += ", FlowModCmd=" + flowModCmd; rv += ", bufferId=" + bufferId; if (doFlush) rv += ", flush"; if (requestFlowRemovedNotifn) rv += ", notif"; rv += ">"; return rv; } } /* * Evil hack: juggles some not-so-important test parameters based * on the given idx */ protected void setupMinorParameters(TestConfig c, int idx) { switch (idx % 4) { case 0: c.doFlush = true; c.requestFlowRemovedNotifn = true; c.flowModCmd = OFFlowMod.OFPFC_ADD; break; case 1: c.doFlush = true; c.requestFlowRemovedNotifn = true; c.flowModCmd = OFFlowMod.OFPFC_MODIFY; break; case 2: c.doFlush = true; c.requestFlowRemovedNotifn = false; c.flowModCmd = OFFlowMod.OFPFC_MODIFY; case 3: c.doFlush = false; c.requestFlowRemovedNotifn = true; c.flowModCmd = OFFlowMod.OFPFC_ADD; break; } } /* * Run a single test with the given config * If checkWithOFMatch is true we test the behavior of pushRewriteRoute * in cases were we act on a OFMatch as input (i.e., flow reconciliation) * rather than a packet in */ protected void doOneTest(TestConfig c, boolean checkWithOFMatch) throws Exception { boolean tunnelEnabled = false; Ethernet origEth = (Ethernet)testPacket.clone(); byte[] origPacketData = testPacketSerialized; curTestId = c.toString(); Route route = getTestRoute(); IRoutingDecision decision = getMockRoutingDecision(c.packetInSwp, true); boolean expectPacketIgnored = false; if (c.ttlDecrement != null && c.ttlDecrement >= initialPacketTtl) expectPacketIgnored = true; if (checkWithOFMatch) curTestId += " <mode: OFMatch> "; else curTestId += " <mode: PacketIn> "; OFMatch match = null; OFPacketIn packetIn = c.packetIn; // When creating a packet-in, ensure that the packet-in port // matches the expected input port. packetIn.setInPort((short)c.packetInSwp.getPort()); if (checkWithOFMatch) { match = new OFMatch(); match.loadFromPacket(origPacketData, (short)c.packetInSwp.getPort()); match.setWildcards(VirtualRouting.DEFAULT_HINT); packetIn = null; expectPacketIgnored = false; } setupMockSwitches(c.doFlush, true, expectPacketIgnored); setupZoneExpectations(c.vlan, c.origDstMac, c.finalDstMac, c.origSrcMac, c.finalSrcMac, c.ttlDecrement, c.vlanModes, c.linkTypes); setupFlowCacheExpectations(expectPacketIgnored); IControllerService.bcStore.put( cntx, IControllerService.CONTEXT_PI_PAYLOAD, c.packet); replay(topology, rewriteService, flowCacheService); forwarding.pushRewriteRoute(route, decision.getSourceDevice(), match, packetIn, c.packetInSwp.getSwitchDPID(), cookie, decision.getWildcards(), c.requestFlowRemovedNotifn, c.doFlush, c.flowModCmd, tunnelEnabled, cntx); doVerify(c.vlan, c.bufferId, c.origDstMac, c.finalDstMac, c.origSrcMac, c.finalSrcMac, c.ttlDecrement, c.packetInSwp.getSwitchDPID(), c.requestFlowRemovedNotifn, c.flowModCmd, c.vlanModes, c.linkTypes, checkWithOFMatch); verifyAllMocks(); assertEquals(origEth, testPacket); assertArrayEquals(origPacketData, testPacketSerialized); } protected void doTestForEachSwitch(TestConfig c) throws Exception { for (int pinSwIdx=0; pinSwIdx<switches.length; pinSwIdx++) { c.packetInSwp = getSwitchPort(pinSwIdx, SwitchPortDirection.INPUT); resetAllMocks(); doOneTest(c, false); resetAllMocks(); doOneTest(c, true); } c.packetInSwp = new SwitchPort (4200L, 1); resetAllMocks(); doOneTest(c, false); resetAllMocks(); doOneTest(c, true); } /* Run several tests with the given base config. * We currently take the ZoneVlanModes, LinkTypes, and bufferId from the * baseConfig and modify the other parameters */ protected void doMultiTests(TestConfig c) throws Exception { int i = 0; setupMinorParameters(c, i); for (Long dstMac: new Long[] { null, 0x424242L }) { c.setupDstMac(packetDstMac, dstMac); for (Long srcMac: new Long[] { null, 0x232323L }) { c.setupSrcMac(packetSrcMac, srcMac); for (Integer ttlDecrement: new Integer[] { null, 1, 255, (int) initialPacketTtl }) { c.ttlDecrement = ttlDecrement; doTestForEachSwitch(c); } } } } @Test public void testZones1() throws Exception { TestConfig c = new TestConfig(); c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED }; c.linkTypes = new LinkType[] { LinkType.INT, LinkType.EXT_TAGGED, LinkType.INT }; doMultiTests(c); } @Test public void testZones2() throws Exception { TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_TAGGED, LinkType.EXT_UNTAGGED, LinkType.INT }; doMultiTests(c); } @Test public void testZones3() throws Exception { TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED }; doMultiTests(c); } @Test public void testZones4() throws Exception { TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED }; doMultiTests(c); } @Test public void testZones5() throws Exception { tagPacketIn(); TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_TAGGED, LinkType.EXT_UNTAGGED, LinkType.INT }; doMultiTests(c); } @Test public void testZones6() throws Exception { tagPacketIn(); TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED, LinkType.INT }; doMultiTests(c); } /* Test that we find the correct input vlan when then packet in is * from BD */ @Test public void testInputVlan() throws Exception { // We learn the src device on sw 0x20, port 1 reset(topology); expect(topology.isAttachmentPointPort(EasyMock.anyLong(), EasyMock.anyShort())).andReturn(true).anyTimes(); topology.addListener(anyObject(ITopologyListener.class)); expectLastCall().anyTimes(); expect(topology.getL2DomainId(anyLong())).andReturn(0x10L).anyTimes(); replay(topology); deviceManager.deleteDevice((Device)srcDevice); srcDevice = deviceManager.learnEntity(packetSrcMac, null, networkSource, 0x20L, 1); // Packet in is on some other switch tagPacketIn(); TestConfig c = new TestConfig(); c.bufferId = -1; c.vlanModes = new ZoneVlanMode[] { ZoneVlanMode.TAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.UNTAGGED, ZoneVlanMode.TAGGED, ZoneVlanMode.TAGGED }; c.linkTypes = new LinkType[] { LinkType.EXT_UNTAGGED, LinkType.EXT_UNTAGGED, LinkType.INT }; setupMinorParameters(c, 0); c.packetInSwp = new SwitchPort(4200, 1); resetAllMocks(); expect(topology.isAttachmentPointPort(0x20L, (short)1)) .andReturn(true).anyTimes(); expect(topology.isInSameBroadcastDomain(0x20, (short)1, 0x10, (short)1, false)) .andReturn(true).anyTimes(); expect(topology.isInSameBroadcastDomain(0x10, (short)1, 0x20, (short)1, false)) .andReturn(true).anyTimes(); doOneTest(c, false); } }