/**
* 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.staticflowentry;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.junit.Test;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPort;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.openflow.protocol.action.OFActionStripVirtualLan;
import org.openflow.util.HexString;
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.MockFloodlightProvider;
import net.floodlightcontroller.test.FloodlightTestCase;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.restserver.RestApiServer;
import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
import net.floodlightcontroller.storage.IStorageSourceService;
import net.floodlightcontroller.storage.memory.MemoryStorageSource;
import static net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher.*;
import static org.easymock.EasyMock.*;
public class StaticFlowTests extends FloodlightTestCase {
static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
static int TotalTestRules = 3;
/***
* Create TestRuleXXX and the corresponding FlowModXXX
* for X = 1..3
*/
static Map<String,Object> TestRule1;
static OFFlowMod FlowMod1;
static {
FlowMod1 = new OFFlowMod();
TestRule1 = new HashMap<String,Object>();
TestRule1.put(COLUMN_NAME, "TestRule1");
TestRule1.put(COLUMN_SWITCH, TestSwitch1DPID);
// setup match
OFMatch match = new OFMatch();
TestRule1.put(COLUMN_DL_DST, "00:20:30:40:50:60");
match.fromString("dl_dst=00:20:30:40:50:60");
// setup actions
List<OFAction> actions = new LinkedList<OFAction>();
TestRule1.put(COLUMN_ACTIONS, "output=1");
actions.add(new OFActionOutput((short)1, Short.MAX_VALUE));
// done
FlowMod1.setMatch(match);
FlowMod1.setActions(actions);
FlowMod1.setBufferId(-1);
FlowMod1.setOutPort(OFPort.OFPP_NONE.getValue());
FlowMod1.setPriority(Short.MAX_VALUE);
FlowMod1.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
}
static Map<String,Object> TestRule2;
static OFFlowMod FlowMod2;
static {
FlowMod2 = new OFFlowMod();
TestRule2 = new HashMap<String,Object>();
TestRule2.put(COLUMN_NAME, "TestRule2");
TestRule2.put(COLUMN_SWITCH, TestSwitch1DPID);
// setup match
OFMatch match = new OFMatch();
TestRule2.put(COLUMN_NW_DST, "192.168.1.0/24");
match.fromString("nw_dst=192.168.1.0/24");
// setup actions
List<OFAction> actions = new LinkedList<OFAction>();
TestRule2.put(COLUMN_ACTIONS, "output=1");
actions.add(new OFActionOutput((short)1, Short.MAX_VALUE));
// done
FlowMod2.setMatch(match);
FlowMod2.setActions(actions);
FlowMod2.setBufferId(-1);
FlowMod2.setOutPort(OFPort.OFPP_NONE.getValue());
FlowMod2.setPriority(Short.MAX_VALUE);
FlowMod2.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
}
static Map<String,Object> TestRule3;
static OFFlowMod FlowMod3;
private StaticFlowEntryPusher staticFlowEntryPusher;
private IOFSwitch mockSwitch;
private Capture<OFMessage> writeCapture;
private Capture<FloodlightContext> contextCapture;
private Capture<List<OFMessage>> writeCaptureList;
private long dpid;
private IStorageSourceService storage;
static {
FlowMod3 = new OFFlowMod();
TestRule3 = new HashMap<String,Object>();
TestRule3.put(COLUMN_NAME, "TestRule3");
TestRule3.put(COLUMN_SWITCH, TestSwitch1DPID);
// setup match
OFMatch match = new OFMatch();
TestRule3.put(COLUMN_DL_DST, "00:20:30:40:50:60");
TestRule3.put(COLUMN_DL_VLAN, 4096);
match.fromString("dl_dst=00:20:30:40:50:60,dl_vlan=4096");
// setup actions
TestRule3.put(COLUMN_ACTIONS, "output=controller");
List<OFAction> actions = new LinkedList<OFAction>();
actions.add(new OFActionOutput(OFPort.OFPP_CONTROLLER.getValue(), Short.MAX_VALUE));
// done
FlowMod3.setMatch(match);
FlowMod3.setActions(actions);
FlowMod3.setBufferId(-1);
FlowMod3.setOutPort(OFPort.OFPP_NONE.getValue());
FlowMod3.setPriority(Short.MAX_VALUE);
FlowMod3.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
}
private void verifyFlowMod(OFFlowMod testFlowMod,
OFFlowMod goodFlowMod) {
verifyMatch(testFlowMod, goodFlowMod);
verifyActions(testFlowMod, goodFlowMod);
// dont' bother testing the cookie; just copy it over
goodFlowMod.setCookie(testFlowMod.getCookie());
// .. so we can continue to use .equals()
assertEquals(goodFlowMod, testFlowMod);
}
private void verifyMatch(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
assertEquals(goodFlowMod.getMatch(), testFlowMod.getMatch());
}
private void verifyActions(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
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();
staticFlowEntryPusher = new StaticFlowEntryPusher();
storage = createStorageWithFlowEntries();
dpid = HexString.toLong(TestSwitch1DPID);
mockSwitch = createNiceMock(IOFSwitch.class);
writeCapture = new Capture<OFMessage>(CaptureType.ALL);
contextCapture = new Capture<FloodlightContext>(CaptureType.ALL);
writeCaptureList = new Capture<List<OFMessage>>(CaptureType.ALL);
//OFMessageSafeOutStream mockOutStream = createNiceMock(OFMessageSafeOutStream.class);
mockSwitch.write(capture(writeCapture), capture(contextCapture));
expectLastCall().anyTimes();
mockSwitch.write(capture(writeCaptureList), capture(contextCapture));
expectLastCall().anyTimes();
mockSwitch.flush();
expectLastCall().anyTimes();
FloodlightModuleContext fmc = new FloodlightModuleContext();
fmc.addService(IStorageSourceService.class, storage);
MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>();
switchMap.put(dpid, mockSwitch);
// NO ! expect(mockFloodlightProvider.getSwitches()).andReturn(switchMap).anyTimes();
mockFloodlightProvider.setSwitches(switchMap);
fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
RestApiServer restApi = new RestApiServer();
fmc.addService(IRestApiService.class, restApi);
restApi.init(fmc);
staticFlowEntryPusher.init(fmc);
staticFlowEntryPusher.startUp(fmc); // again, to hack unittest
}
@Test
public void testStaticFlowPush() throws Exception {
// verify that flowpusher read all three entries from storage
assertEquals(TotalTestRules, staticFlowEntryPusher.countEntries());
// if someone calls mockSwitch.getOutputStream(), return mockOutStream instead
//expect(mockSwitch.getOutputStream()).andReturn(mockOutStream).anyTimes();
// if someone calls getId(), return this dpid instead
expect(mockSwitch.getId()).andReturn(dpid).anyTimes();
expect(mockSwitch.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
replay(mockSwitch);
// hook the static pusher up to the fake switch
staticFlowEntryPusher.switchAdded(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(1);
verifyFlowMod(secondFlowMod, FlowMod2);
OFFlowMod thirdFlowMod = (OFFlowMod) writeCapture.getValues().get(0);
verifyFlowMod(thirdFlowMod, FlowMod3);
writeCapture.reset();
contextCapture.reset();
// delete two rules and verify they've been removed
// this should invoke staticFlowPusher.rowsDeleted()
storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule1");
storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule2");
assertEquals(1, staticFlowEntryPusher.countEntries());
assertEquals(2, writeCapture.getValues().size());
OFFlowMod firstDelete = (OFFlowMod) writeCapture.getValues().get(0);
FlowMod1.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
verifyFlowMod(firstDelete, FlowMod1);
OFFlowMod secondDelete = (OFFlowMod) writeCapture.getValues().get(1);
FlowMod2.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
verifyFlowMod(secondDelete, FlowMod2);
// add rules back to make sure that staticFlowPusher.rowsInserted() works
writeCapture.reset();
FlowMod2.setCommand(OFFlowMod.OFPFC_ADD);
storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
assertEquals(2, staticFlowEntryPusher.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();
contextCapture.reset();
writeCaptureList.reset();
// now try an overwriting update, calling staticFlowPusher.rowUpdated()
TestRule3.put(COLUMN_DL_VLAN, 333);
storage.updateRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
assertEquals(2, staticFlowEntryPusher.countEntries());
assertEquals(1, writeCaptureList.getValues().size());
outList = writeCaptureList.getValues().get(0);
assertEquals(2, outList.size());
OFFlowMod removeFlowMod = (OFFlowMod) outList.get(0);
FlowMod3.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
verifyFlowMod(removeFlowMod, FlowMod3);
FlowMod3.setCommand(OFFlowMod.OFPFC_ADD);
FlowMod3.getMatch().fromString("dl_dst=00:20:30:40:50:60,dl_vlan=333");
OFFlowMod updateFlowMod = (OFFlowMod) outList.get(1);
verifyFlowMod(updateFlowMod, FlowMod3);
writeCaptureList.reset();
// now try an action modifying update, calling staticFlowPusher.rowUpdated()
TestRule3.put(COLUMN_ACTIONS, "output=controller,strip-vlan"); // added strip-vlan
storage.updateRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
assertEquals(2, staticFlowEntryPusher.countEntries());
assertEquals(1, writeCaptureList.getValues().size());
outList = writeCaptureList.getValues().get(0);
assertEquals(1, outList.size());
OFFlowMod modifyFlowMod = (OFFlowMod) outList.get(0);
FlowMod3.setCommand(OFFlowMod.OFPFC_MODIFY_STRICT);
List<OFAction> modifiedActions = FlowMod3.getActions();
modifiedActions.add(new OFActionStripVirtualLan()); // add the new action to what we should expect
FlowMod3.setActions(modifiedActions);
FlowMod3.setLengthU(OFFlowMod.MINIMUM_LENGTH + 16); // accommodate the addition of new actions
verifyFlowMod(modifyFlowMod, FlowMod3);
}
IStorageSourceService createStorageWithFlowEntries() {
return populateStorageWithFlowEntries(new MemoryStorageSource());
}
IStorageSourceService populateStorageWithFlowEntries(IStorageSourceService storage) {
Set<String> indexedColumns = new HashSet<String>();
indexedColumns.add(COLUMN_NAME);
storage.createTable(StaticFlowEntryPusher.TABLE_NAME, indexedColumns);
storage.setTablePrimaryKeyName(StaticFlowEntryPusher.TABLE_NAME, COLUMN_NAME);
storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule1);
storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
return storage;
}
@Test
public void testHARoleChanged() throws IOException {
assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
assert(staticFlowEntryPusher.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(staticFlowEntryPusher.entry2dpid.isEmpty());
assert(staticFlowEntryPusher.entriesFromStorage.isEmpty());
// Send a notification that we've changed to master
mfp.dispatchRoleChanged(Role.MASTER);
// Make sure we've learned the entries
assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3));
*/
}
}