Skip to content
Snippets Groups Projects
Commit 8068aa8c authored by abat's avatar abat
Browse files

Merge into master from pull request #38:

Re-write of the Static Flow Entry Pusher. This one uses memory storag… (https://github.com/floodlight/floodlight/pull/38)
parents 0d36d755 669443a8
No related branches found
No related tags found
No related merge requests found
/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* 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.util.HashMap;
import java.util.List;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.module.IFloodlightService;
import org.openflow.protocol.OFFlowMod;
/**
* Represents the parts of the staticflowentry that are exposed as a service to other floodlight apps
*
*/
public interface IStaticFlowEntryPusherService extends IFloodlightService {
/**
* Pushes a flow-mod to this switch as a one-time push
*
* @param dpid
* @param flowMods
*/
public void pushEntry(long dpid, OFFlowMod fm);
/**
* Adds a flow-mod to the list of flow-mods being pushed regularly
* (and also pushes it right away calling pushEntry, as appropriate)
*
* @param dpid
* @param name
* @param active
* @param fm
*/
public void addEntry(long dpid, String name, boolean active, OFFlowMod fm);
/**
* Remove a flow-mod entry that has been added previously
* returns the flow-mod that has just been removed
*
* @param sw
* @param name
*/
public OFFlowMod removeEntry(IOFSwitch sw, String name);
/**
* Get all flow-mod entries that have been pushed previously (for all switches)
*
* returns a HashMap with:
* key = switch-id
* value = HashMap of entries where each entry in the entry HashMap has
* key = flow-mod-name
* value = OFFlowMod
*
*/
public HashMap<Long, HashMap<String, OFFlowMod>> getEntries();
// JSON based interfaces
/**
* Adds a flow-mod (as JSON string) to the list of flow-mods being pushed regularly.
* If a flow-mod already exists for that switch/name, update it based on new entry.
* (and also pushes it right away calling pushEntry, as appropriate)
*
* @param fmJson
*/
public void addEntry(String fmJson);
/**
* Remove a flow-mod entry that has been added previously.
* Only the switch name and flow-mod name are read from the input argument fmJson,
* the full JSON for the flow-mod entry being removed is returned.
*
* @param fmJson
*/
public String removeEntry(String fmJson);
/**
* Get an array JSON strings representing of all flow-mod entries that have been pushed previously
* (for all switches)
*
*/
public List<String> getEntryList();
/**
* Get the list of all active switches
*/
public List<IOFSwitch> getActiveSwitches();
}
......@@ -17,27 +17,56 @@
package net.floodlightcontroller.staticflowentry.web;
import java.io.IOException;
import java.util.Map;
import org.restlet.resource.Delete;
import org.restlet.resource.Post;
import org.restlet.resource.ServerResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
import net.floodlightcontroller.staticflowentry.StaticFlowEntries;
import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
import net.floodlightcontroller.storage.IStorageSourceService;
/**
* Pushes a static flow entry to the storage source
* @author alexreimers
*
*/
public class StaticFlowEntryPusherResource extends ServerResource {
protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusherResource.class);
@Post
public void store(String flowmod) {
IStaticFlowEntryPusherService sfp =
(IStaticFlowEntryPusherService)getContext().getAttributes().
get(IStaticFlowEntryPusherService.class.getCanonicalName());
sfp.addEntry(flowmod);
public void store(String fmJson) {
IStorageSourceService storageSource =
(IStorageSourceService)getContext().getAttributes().
get(IStorageSourceService.class.getCanonicalName());
Map<String, Object> rowValues;
try {
rowValues = StaticFlowEntries.jsonToStorageEntry(fmJson);
storageSource.insertRowAsync(StaticFlowEntryPusher.TABLE_NAME, rowValues);
} catch (IOException e) {
log.error("Error parsing push flow mod request: " + fmJson, e);
e.printStackTrace();
}
}
@Delete
public void del(String flowmod) {
IStaticFlowEntryPusherService sfp =
(IStaticFlowEntryPusherService)getContext().getAttributes().
get(IStaticFlowEntryPusherService.class.getCanonicalName());
sfp.removeEntry(flowmod);
public void del(String fmJson) {
IStorageSourceService storageSource =
(IStorageSourceService)getContext().getAttributes().
get(IStorageSourceService.class.getCanonicalName());
try {
String fmName = StaticFlowEntries.getEntryNameFromJson(fmJson);
storageSource.deleteRow(StaticFlowEntryPusher.TABLE_NAME, fmName);
} catch (IOException e) {
log.error("Error deleting flow mod request: " + fmJson, e);
e.printStackTrace();
}
}
}
package net.floodlightcontroller.staticflowentry;
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.verify;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.restserver.IRestApiService;
import net.floodlightcontroller.restserver.RestApiServer;
import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
import net.floodlightcontroller.test.FloodlightTestCase;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.junit.Before;
import org.junit.Test;
import org.openflow.protocol.OFMessage;
import org.openflow.util.HexString;
public class StaticFlowEntryPusherTest extends FloodlightTestCase {
String flowMod1, flowMod2;
static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
StaticFlowEntryPusher staticFlowEntryPusher;
@Before
public void setUp() throws Exception {
super.setUp();
flowMod1 = "{\"switch\": \"00:00:00:00:00:00:00:01\", " +
"\"name\": \"flow-mod-1\", " +
"\"cookie\": \"0\", " +
"\"priority\": \"32768\", " +
"\"ingress-port\": \"1\"," +
"\"active\": \"true\", " +
"\"actions\": \"output=3\"}";
flowMod2 = "{\"switch\": \"00:00:00:00:00:00:00:01\", " +
"\"name\": \"flow-mod-2\", " +
"\"cookie\": \"0\", " +
"\"priority\": \"32768\", " +
"\"ingress-port\": \"2\"," +
"\"active\": \"true\", " +
"\"actions\": \"output=3\"}";
FloodlightModuleContext fmc = new FloodlightModuleContext();
fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
RestApiServer restApi = new RestApiServer();
fmc.addService(IRestApiService.class, restApi);
staticFlowEntryPusher = new StaticFlowEntryPusher();
staticFlowEntryPusher.init(fmc);
restApi.init(fmc);
staticFlowEntryPusher.setFlowPushTime(500);
staticFlowEntryPusher.startUp(fmc);
restApi.startUp(fmc);
}
@Test
public void testAddAndRemoveEntries() throws Exception {
IOFSwitch mockSwitch = createMock(IOFSwitch.class);
long dpid = HexString.toLong(TestSwitch1DPID);
Capture<OFMessage> writeCapture = new Capture<OFMessage>(CaptureType.ALL);
Capture<FloodlightContext> contextCapture =
new Capture<FloodlightContext>(CaptureType.ALL);
Capture<List<OFMessage>> writeCaptureList =
new Capture<List<OFMessage>>(CaptureType.ALL);
mockSwitch.write(capture(writeCapture), capture(contextCapture));
expectLastCall().anyTimes();
mockSwitch.write(capture(writeCaptureList), capture(contextCapture));
expectLastCall().anyTimes();
mockSwitch.flush();
expectLastCall().anyTimes();
MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>();
switchMap.put(dpid, mockSwitch);
mockFloodlightProvider.setSwitches(switchMap);
// if someone calls getId(), return this dpid instead
expect(mockSwitch.getId()).andReturn(dpid).anyTimes();
replay(mockSwitch);
// hook the static pusher up to the fake switch
staticFlowEntryPusher.addedSwitch(mockSwitch);
verify(mockSwitch);
// Add entries
staticFlowEntryPusher.addEntry(flowMod1);
staticFlowEntryPusher.addEntry(flowMod2);
// Verify that the switch has gotten some flow_mods
assertEquals(2, writeCapture.getValues().size());
int count = 5;
while (count >= 0) {
Thread.sleep(staticFlowEntryPusher.getFlowPushTime());
if (writeCapture.getValues().size() >= 4) {
break;
}
count -= 1;
}
int newsize = writeCapture.getValues().size();
// Make sure the entries were pushed again
assertTrue(newsize >= 4);
// Remove entries
staticFlowEntryPusher.removeEntry(flowMod1);
staticFlowEntryPusher.removeEntry(flowMod2);
Thread.sleep(staticFlowEntryPusher.getFlowPushTime());
// Make sure the entries were NOT pushed again
assertEquals(newsize, writeCapture.getValues().size());
}
}
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.util.HexString;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.test.MockFloodlightProvider;
import net.floodlightcontroller.test.FloodlightTestCase;
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) 0));
// 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) 0));
// 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;
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) 0));
// 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();
}
@Test
public void testStaticFlowPush() throws IOException {
StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher();
IStorageSourceService storage = createStorageWithFlowEntries();
long dpid = HexString.toLong(TestSwitch1DPID);
// Create a Switch and attach a switch
IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
Capture<OFMessage> writeCapture = new Capture<OFMessage>(CaptureType.ALL);
Capture<FloodlightContext> contextCapture = new Capture<FloodlightContext>(CaptureType.ALL);
Capture<List<OFMessage>> 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();
staticFlowEntryPusher.setStorageSource(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);
staticFlowEntryPusher.setFloodlightProvider(mockFloodlightProvider);
RestApiServer restApi = new RestApiServer();
try {
restApi.init(null);
} catch (FloodlightModuleException e) {
e.printStackTrace();
}
staticFlowEntryPusher.restApi = restApi;
staticFlowEntryPusher.startUp(null); // again, to hack unittest
// 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.addedSwitch(mockSwitch);
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 =
(List<OFMessage>) 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 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 = (List<OFMessage>) 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);
}
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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment