/** * 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.staticentry; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugcounter.MockDebugCounterService; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.restserver.RestApiServer; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.util.FlowModUtils; import net.floodlightcontroller.util.MatchUtils; import net.floodlightcontroller.util.OFMessageUtils; import org.easymock.Capture; import org.easymock.CaptureType; import org.easymock.EasyMock; import org.junit.Ignore; import org.junit.Test; import org.projectfloodlight.openflow.protocol.*; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.match.Match; import org.projectfloodlight.openflow.protocol.match.MatchFields; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.util.HexString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.*; import static net.floodlightcontroller.staticentry.StaticEntryPusher.Columns; import static org.easymock.EasyMock.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class StaticFlowTests extends FloodlightTestCase { protected static Logger log = LoggerFactory.getLogger(StaticFlowTests.class); static String TestSwitch1DPID = "00:00:00:00:00:00:00:01"; static int TotalTestRules = 3; static OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); /*** * Create TestRuleXXX and the corresponding FlowModXXX * for X = 1..3 */ static Map<String,Object> TestRule1; static OFFlowMod FlowMod1; static { FlowMod1 = factory.buildFlowModify().build(); TestRule1 = new HashMap<String,Object>(); TestRule1.put(Columns.COLUMN_NAME, "TestRule1"); TestRule1.put(Columns.COLUMN_SWITCH, TestSwitch1DPID); // setup match Match match; TestRule1.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.ETH_DST), "00:20:30:40:50:60"); match = MatchUtils.fromString("eth_dst=00:20:30:40:50:60", factory.getVersion()); // setup actions List<OFAction> actions = new LinkedList<OFAction>(); TestRule1.put(Columns.COLUMN_ACTIONS, "output=1"); actions.add(factory.actions().output(OFPort.of(1), Integer.MAX_VALUE)); // done FlowMod1 = FlowMod1.createBuilder().setMatch(match) .setActions(actions) .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) .setBufferId(OFBufferId.NO_BUFFER) .setOutPort(OFPort.ANY) .setPriority(Integer.MAX_VALUE) .setXid(4) .build(); } static Map<String,Object> TestRule2; static OFFlowMod FlowMod2; static { FlowMod2 = factory.buildFlowModify().build(); TestRule2 = new HashMap<String,Object>(); TestRule2.put(Columns.COLUMN_NAME, "TestRule2"); TestRule2.put(Columns.COLUMN_SWITCH, TestSwitch1DPID); // setup match Match match; TestRule2.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.ETH_TYPE), "0x800"); TestRule2.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IPV4_DST), "192.168.1.0/24"); match = MatchUtils.fromString("eth_type=0x800,ipv4_dst=192.168.1.0/24", factory.getVersion()); // setup actions List<OFAction> actions = new LinkedList<OFAction>(); TestRule2.put(Columns.COLUMN_ACTIONS, "output=1"); actions.add(factory.actions().output(OFPort.of(1), Integer.MAX_VALUE)); // done FlowMod2 = FlowMod2.createBuilder().setMatch(match) .setActions(actions) .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) .setBufferId(OFBufferId.NO_BUFFER) .setOutPort(OFPort.ANY) .setPriority(Integer.MAX_VALUE) .setXid(5) .build(); } static Map<String,Object> TestRule3; static OFFlowMod FlowMod3; private StaticEntryPusher pusher; private IOFSwitchService switchService; private IOFSwitch mockSwitch; private MockDebugCounterService debugCounterService; private Capture<OFMessage> writeCapture; private Capture<List<OFMessage>> writeCaptureList; private long dpid; private MemoryStorageSource storage; static { FlowMod3 = factory.buildFlowModify().build(); TestRule3 = new HashMap<String,Object>(); TestRule3.put(Columns.COLUMN_NAME, "TestRule3"); TestRule3.put(Columns.COLUMN_SWITCH, TestSwitch1DPID); // setup match Match match; TestRule3.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.ETH_DST), "00:20:30:40:50:60"); TestRule3.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.VLAN_VID), 96); match = MatchUtils.fromString("eth_dst=00:20:30:40:50:60,eth_vlan_vid=96", factory.getVersion()); // setup actions TestRule3.put(Columns.COLUMN_ACTIONS, "output=controller"); List<OFAction> actions = new LinkedList<OFAction>(); actions.add(factory.actions().output(OFPort.CONTROLLER, Integer.MAX_VALUE)); // done FlowMod3 = FlowMod3.createBuilder().setMatch(match) .setActions(actions) .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) .setBufferId(OFBufferId.NO_BUFFER) .setOutPort(OFPort.ANY) .setPriority(Integer.MAX_VALUE) .setXid(6) .build(); } private void verifyFlowMod(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) { verifyMatch(testFlowMod, goodFlowMod); verifyActions(testFlowMod, goodFlowMod); // dont' bother testing the cookie; just copy it over goodFlowMod = goodFlowMod.createBuilder().setCookie(testFlowMod.getCookie()).build(); // .. so we can continue to use .equals() assertEquals(OFMessageUtils.OFMessageIgnoreXid.of(goodFlowMod), OFMessageUtils.OFMessageIgnoreXid.of(testFlowMod)); } private void verifyMatch(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) { assertEquals(goodFlowMod.getMatch(), testFlowMod.getMatch()); } private void verifyActions(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) { if ((testFlowMod instanceof OFFlowDelete || testFlowMod instanceof OFFlowDeleteStrict) && (goodFlowMod instanceof OFFlowDelete || goodFlowMod instanceof OFFlowDeleteStrict)) { log.warn("Not verifying actions of two flow-delete messages (they're undefined by design)"); } else { List<OFAction> goodActions = goodFlowMod.getActions(); List<OFAction> testActions = testFlowMod.getActions(); assertNotNull(goodActions); assertNotNull(testActions); assertEquals(goodActions.size(), testActions.size()); // assumes actions are marshalled in same order; should be safe for(int i = 0; i < goodActions.size(); i++) { assertEquals(goodActions.get(i), testActions.get(i)); } } } @Override public void setUp() throws Exception { super.setUp(); debugCounterService = new MockDebugCounterService(); pusher = new StaticEntryPusher(); switchService = getMockSwitchService(); storage = new MemoryStorageSource(); dpid = HexString.toLong(TestSwitch1DPID); mockSwitch = createNiceMock(IOFSwitch.class); writeCapture = EasyMock.newCapture(CaptureType.ALL); writeCaptureList = EasyMock.newCapture(CaptureType.ALL); expect(mockSwitch.write(capture(writeCapture))).andReturn(true).anyTimes(); expect(mockSwitch.write(capture(writeCaptureList))).andReturn(Collections.<OFMessage>emptyList()).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); replay(mockSwitch); FloodlightModuleContext fmc = new FloodlightModuleContext(); fmc.addService(IStorageSourceService.class, storage); fmc.addService(IOFSwitchService.class, getMockSwitchService()); fmc.addService(IDebugCounterService.class, debugCounterService); MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider(); Map<DatapathId, IOFSwitch> switchMap = new HashMap<DatapathId, IOFSwitch>(); switchMap.put(DatapathId.of(dpid), mockSwitch); getMockSwitchService().setSwitches(switchMap); fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider); RestApiServer restApi = new RestApiServer(); fmc.addService(IRestApiService.class, restApi); fmc.addService(IOFSwitchService.class, switchService); restApi.init(fmc); debugCounterService.init(fmc); storage.init(fmc); pusher.init(fmc); debugCounterService.init(fmc); storage.startUp(fmc); createStorageWithFlowEntries(); pusher.startUp(fmc); // again, to hack unittest } @Test public void testStaticFlowPush() throws Exception { // verify that flowpusher read all three entries from storage assertEquals(TotalTestRules, pusher.countEntries()); // if someone calls mockSwitch.getOutputStream(), return mockOutStream instead //expect(mockSwitch.getOutputStream()).andReturn(mockOutStream).anyTimes(); // if someone calls getId(), return this dpid instead resetToNice(mockSwitch); expect(mockSwitch.write(capture(writeCapture))).andReturn(true).anyTimes(); expect(mockSwitch.write(capture(writeCaptureList))).andReturn(Collections.<OFMessage> emptyList()).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); expect(mockSwitch.getId()).andReturn(DatapathId.of(dpid)).anyTimes(); replay(mockSwitch); // hook the static pusher up to the fake switch pusher.switchAdded(DatapathId.of(dpid)); verify(mockSwitch); // Verify that the switch has gotten some flow_mods assertEquals(true, writeCapture.hasCaptured()); assertEquals(TotalTestRules, writeCapture.getValues().size()); // Order assumes how things are stored in hash bucket; // should be fixed because OFMessage.hashCode() is deterministic OFFlowMod firstFlowMod = (OFFlowMod) writeCapture.getValues().get(2); verifyFlowMod(firstFlowMod, FlowMod1); OFFlowMod secondFlowMod = (OFFlowMod) writeCapture.getValues().get(0); /* Java 8 stores 2-0-1 */ verifyFlowMod(secondFlowMod, FlowMod2); OFFlowMod thirdFlowMod = (OFFlowMod) writeCapture.getValues().get(1); verifyFlowMod(thirdFlowMod, FlowMod3); writeCapture.reset(); // delete two rules and verify they've been removed // this should invoke staticFlowPusher.rowsDeleted() storage.deleteRow(StaticEntryPusher.TABLE_NAME, "TestRule1"); storage.deleteRow(StaticEntryPusher.TABLE_NAME, "TestRule2"); assertEquals(1, pusher.countEntries()); assertEquals(2, writeCapture.getValues().size()); OFFlowMod firstDelete = (OFFlowMod) writeCapture.getValues().get(0); FlowMod1 = FlowModUtils.toFlowDeleteStrict(FlowMod1); verifyFlowMod(firstDelete, FlowMod1); OFFlowMod secondDelete = (OFFlowMod) writeCapture.getValues().get(1); FlowMod2 = FlowModUtils.toFlowDeleteStrict(FlowMod2); verifyFlowMod(secondDelete, FlowMod2); // add rules back to make sure that staticFlowPusher.rowsInserted() works writeCapture.reset(); FlowMod2 = FlowModUtils.toFlowAdd(FlowMod2); FlowMod2 = FlowMod2.createBuilder().setXid(12).build(); storage.insertRow(StaticEntryPusher.TABLE_NAME, TestRule2); assertEquals(2, pusher.countEntries()); assertEquals(1, writeCaptureList.getValues().size()); List<OFMessage> outList = writeCaptureList.getValues().get(0); assertEquals(1, outList.size()); OFFlowMod firstAdd = (OFFlowMod) outList.get(0); verifyFlowMod(firstAdd, FlowMod2); writeCapture.reset(); writeCaptureList.reset(); // now try an overwriting update, calling staticFlowPusher.rowUpdated() TestRule3.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.VLAN_VID), 333); storage.updateRow(StaticEntryPusher.TABLE_NAME, TestRule3); assertEquals(2, pusher.countEntries()); assertEquals(1, writeCaptureList.getValues().size()); outList = writeCaptureList.getValues().get(0); assertEquals(2, outList.size()); OFFlowMod removeFlowMod = (OFFlowMod) outList.get(0); FlowMod3 = FlowModUtils.toFlowDeleteStrict(FlowMod3); verifyFlowMod(removeFlowMod, FlowMod3); FlowMod3 = FlowModUtils.toFlowAdd(FlowMod3); FlowMod3 = FlowMod3.createBuilder().setMatch(MatchUtils.fromString("eth_dst=00:20:30:40:50:60,eth_vlan_vid=333", factory.getVersion())).setXid(14).build(); OFFlowMod updateFlowMod = (OFFlowMod) outList.get(1); verifyFlowMod(updateFlowMod, FlowMod3); writeCaptureList.reset(); // now try an action modifying update, calling staticFlowPusher.rowUpdated() TestRule3.put(StaticEntryPusher.Columns.COLUMN_ACTIONS, "output=controller,pop_vlan"); // added pop-vlan storage.updateRow(StaticEntryPusher.TABLE_NAME, TestRule3); assertEquals(2, pusher.countEntries()); assertEquals(1, writeCaptureList.getValues().size()); outList = writeCaptureList.getValues().get(0); assertEquals(1, outList.size()); OFFlowMod modifyFlowMod = (OFFlowMod) outList.get(0); FlowMod3 = FlowModUtils.toFlowModifyStrict(FlowMod3); List<OFAction> modifiedActions = FlowMod3.getActions(); modifiedActions.add(factory.actions().popVlan()); // add the new action to what we should expect FlowMod3 = FlowMod3.createBuilder().setActions(modifiedActions).setXid(19).build(); verifyFlowMod(modifyFlowMod, FlowMod3); } IStorageSourceService createStorageWithFlowEntries() { return populateStorageWithFlowEntries(); } IStorageSourceService populateStorageWithFlowEntries() { Set<String> indexedColumns = new HashSet<String>(); indexedColumns.add(Columns.COLUMN_NAME); storage.createTable(StaticEntryPusher.TABLE_NAME, indexedColumns); storage.setTablePrimaryKeyName(StaticEntryPusher.TABLE_NAME, Columns.COLUMN_NAME); storage.insertRow(StaticEntryPusher.TABLE_NAME, TestRule1); storage.insertRow(StaticEntryPusher.TABLE_NAME, TestRule2); storage.insertRow(StaticEntryPusher.TABLE_NAME, TestRule3); return storage; } @Ignore @Test public void testHARoleChanged() throws IOException { assert(pusher.entry2dpid.containsValue(TestSwitch1DPID)); assert(pusher.entriesFromStorage.containsValue(FlowMod1)); assert(pusher.entriesFromStorage.containsValue(FlowMod2)); assert(pusher.entriesFromStorage.containsValue(FlowMod3)); /* FIXME: what's the right behavior here ?? // Send a notification that we've changed to slave mfp.dispatchRoleChanged(Role.SLAVE); // Make sure we've removed all our entries assert(StaticEntryPusher.entry2dpid.isEmpty()); assert(StaticEntryPusher.entriesFromStorage.isEmpty()); // Send a notification that we've changed to master mfp.dispatchRoleChanged(Role.MASTER); // Make sure we've learned the entries assert(StaticEntryPusher.entry2dpid.containsValue(TestSwitch1DPID)); assert(StaticEntryPusher.entriesFromStorage.containsValue(FlowMod1)); assert(StaticEntryPusher.entriesFromStorage.containsValue(FlowMod2)); assert(StaticEntryPusher.entriesFromStorage.containsValue(FlowMod3)); */ } }