blob: b257dbda6b8b8968027f2e20c0470f13a0826c15 [file] [log] [blame]
package net.onrc.onos.core.flowprogrammer;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import net.floodlightcontroller.core.IOFSwitch;
import net.onrc.onos.core.flowprogrammer.IFlowPusherService.MsgPriority;
import net.onrc.onos.core.flowprogrammer.IFlowSyncService.SyncResult;
import net.onrc.onos.core.intent.FlowEntry;
import net.onrc.onos.core.util.Dpid;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.types.U64;
// Test should be fixed to fit RAMCloud basis
@Ignore
@RunWith(PowerMockRunner.class)
@PrepareForTest({FlowSynchronizer.class })
public class FlowSynchronizerTest {
private FlowPusher pusher;
private FlowSynchronizer sync;
private List<Long> idAdded;
private List<Long> idRemoved;
/*
* OF1.0 Factory for now. Change when we move to
* OF 1.3.
*/
private static OFFactory factory10;
@Before
public void setUp() throws Exception {
factory10 = OFFactories.getFactory(OFVersion.OF_10);
idAdded = new ArrayList<Long>();
idRemoved = new ArrayList<Long>();
pusher = createMock(FlowPusher.class);
expect(pusher.suspend(anyObject(Dpid.class))).andReturn(true).anyTimes();
expect(pusher.resume(anyObject(Dpid.class))).andReturn(true).anyTimes();
pusher.add(anyObject(Dpid.class), anyObject(OFMessage.class),
eq(MsgPriority.HIGH));
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
OFMessage msg = (OFMessage) getCurrentArguments()[1];
if (msg.getType().equals(OFType.FLOW_MOD)) {
OFFlowMod fm = (OFFlowMod) msg;
if (fm.getCommand() == OFFlowModCommand.DELETE_STRICT) {
idRemoved.add(fm.getCookie().getValue());
}
}
return null;
}
}).anyTimes();
pusher.pushFlowEntry(anyObject(Dpid.class), anyObject(FlowEntry.class),
eq(MsgPriority.HIGH));
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
FlowEntry flow = (FlowEntry) getCurrentArguments()[1];
idAdded.add(flow.getFlowEntryId());
return null;
}
}).anyTimes();
replay(pusher);
}
@After
public void tearDown() throws Exception {
}
/**
* Test that synchronization doesn't affect anything in case either DB and
* flow table has the same entries.
*/
@Test
public void testStable() {
// Create mock of flow table : flow 1
IOFSwitch sw = createMockSwitch(new long[]{1});
// Create mock of flow entries : flow 1
initMockGraph(new long[]{1});
// synchronize
doSynchronization(sw);
// check if flow is not changed
assertEquals(0, idAdded.size());
assertEquals(0, idRemoved.size());
}
/**
* Test that an flow is added in case DB has an extra FlowEntry.
*/
@Test
public void testSingleAdd() {
// Create mock of flow table : null
IOFSwitch sw = createMockSwitch(new long[]{});
// Create mock of flow entries : flow 1
initMockGraph(new long[]{1});
// synchronize
doSynchronization(sw);
// check if single flow is installed
assertEquals(1, idAdded.size());
assertTrue(idAdded.contains((long) 1));
assertEquals(0, idRemoved.size());
}
/**
* Test that an flow is deleted in case switch has an extra FlowEntry.
*/
@Test
public void testSingleDelete() {
// Create mock of flow table : flow 1
IOFSwitch sw = createMockSwitch(new long[]{1});
// Create mock of flow entries : null
initMockGraph(new long[]{});
// synchronize
doSynchronization(sw);
// check if single flow is deleted
assertEquals(0, idAdded.size());
assertEquals(1, idRemoved.size());
assertTrue(idRemoved.contains((long) 1));
}
/**
* Test that appropriate flows are added and other appropriate flows are deleted
* in case flows in DB are overlapping flows in switch.
*/
@Test
public void testMixed() {
// Create mock of flow table : flow 1,2,3
IOFSwitch sw = createMockSwitch(new long[]{1, 2, 3});
// Create mock of flow entries : flow 2,3,4,5
initMockGraph(new long[]{2, 3, 4, 5});
// synchronize
doSynchronization(sw);
// check if two flows {4,5} is installed and one flow {1} is deleted
assertEquals(2, idAdded.size());
assertTrue(idAdded.contains((long) 4));
assertTrue(idAdded.contains((long) 5));
assertEquals(1, idRemoved.size());
assertTrue(idRemoved.contains((long) 1));
}
@Test
public void testMassive() {
// Create mock of flow table : flow 0-1999
long[] swIdList = new long[2000];
for (long i = 0; i < 2000; ++i) {
swIdList[(int) i] = i;
}
IOFSwitch sw = createMockSwitch(swIdList);
// Create mock of flow entries : flow 1500-3499
long[] dbIdList = new long[2000];
for (long i = 0; i < 2000; ++i) {
dbIdList[(int) i] = 1500 + i;
}
initMockGraph(dbIdList);
// synchronize
doSynchronization(sw);
// check if 1500 flows {2000-3499} is installed and 1500 flows {0,...,1499} is deleted
assertEquals(1500, idAdded.size());
for (long i = 2000; i < 3500; ++i) {
assertTrue(idAdded.contains(i));
}
assertEquals(1500, idRemoved.size());
for (long i = 0; i < 1500; ++i) {
assertTrue(idRemoved.contains(i));
}
}
/**
* Create mock IOFSwitch with flow table which has arbitrary flows.
*
* @param cookieList List of FlowEntry IDs switch has.
* @return Mock object.
*/
private IOFSwitch createMockSwitch(long[] cookieList) {
IOFSwitch sw = createMock(IOFSwitch.class);
expect(sw.getId()).andReturn((long) 1).anyTimes();
List<OFStatsReply> stats = new ArrayList<OFStatsReply>();
for (long cookie : cookieList) {
stats.add(createReply(cookie));
}
@SuppressWarnings("unchecked")
Future<List<OFStatsReply>> future = createMock(Future.class);
try {
expect(future.get()).andReturn(stats).once();
} catch (InterruptedException e1) {
fail("Failed in Future#get()");
} catch (ExecutionException e1) {
fail("Failed in Future#get()");
}
replay(future);
try {
expect(sw.getStatistics(anyObject(OFFlowStatsRequest.class)))
.andReturn(future).once();
} catch (IOException e) {
fail("Failed in IOFSwitch#getStatistics()");
}
replay(sw);
return sw;
}
/**
* Create single OFFlowStatisticsReply object which is actually obtained from switch.
*
* @param cookie Cookie value, which indicates ID of FlowEntry installed to switch.
* @return Created object.
*/
private OFFlowStatsReply createReply(long cookie) {
OFFlowStatsEntry entry = factory10.buildFlowStatsEntry()
.setCookie(U64.of(cookie))
.setPriority(1)
.setMatch(factory10.buildMatch().build())
.build();
OFFlowStatsReply stat = factory10.buildFlowStatsReply()
.setEntries(Collections.singletonList(entry)).build();
return stat;
}
/**
* Create mock FlowDatabaseOperation to mock DB.
*
* @param idList List of FlowEntry IDs stored in DB.
*/
private void initMockGraph(long[] idList) {
/*
* TODO: The old FlowDatabaseOperation class is gone, so the method
* below needs to be rewritten.
*/
/*
List<IFlowEntry> flowEntryList = new ArrayList<IFlowEntry>();
for (long id : idList) {
IFlowEntry entry = EasyMock.createMock(IFlowEntry.class);
EasyMock.expect(entry.getFlowEntryId()).andReturn(String.valueOf(id)).anyTimes();
EasyMock.replay(entry);
flowEntryList.add(entry);
}
ISwitchObject swObj = EasyMock.createMock(ISwitchObject.class);
EasyMock.expect(swObj.getFlowEntries()).andReturn(flowEntryList).once();
EasyMock.replay(swObj);
DBOperation mockOp = PowerMock.createMock(DBOperation.class);
EasyMock.expect(mockOp.searchSwitch(EasyMock.anyObject(String.class))).andReturn(swObj).once();
PowerMock.mockStatic(FlowDatabaseOperation.class);
for (IFlowEntry entry : flowEntryList) {
EasyMock.expect(FlowDatabaseOperation.extractFlowEntry(EasyMock.eq(entry)))
.andAnswer(new IAnswer<FlowEntry>() {
@Override
public FlowEntry answer() throws Throwable {
IFlowEntry iflow = (IFlowEntry)EasyMock.getCurrentArguments()[0];
long flowEntryId = Long.valueOf(iflow.getFlowEntryId());
FlowEntry flow = EasyMock.createMock(FlowEntry.class);
EasyMock.expect(flow.flowEntryId()).andReturn(new FlowEntryId(flowEntryId)).anyTimes();
EasyMock.replay(flow);
return flow;
}
}).anyTimes();
EasyMock.expect(mockOp.searchFlowEntry(EasyMock.eq(new FlowEntryId(entry.getFlowEntryId()))))
.andReturn(entry);
}
PowerMock.replay(FlowDatabaseOperation.class);
EasyMock.replay(mockOp);
try {
PowerMock.expectNew(DBOperation.class).andReturn(mockOp);
} catch (Exception e) {
fail("Failed to create DBOperation");
}
PowerMock.replay(DBOperation.class);
*/
}
/**
* Instantiate FlowSynchronizer and sync flows.
*
* @param sw Target IOFSwitch object
*/
private void doSynchronization(IOFSwitch sw) {
sync = new FlowSynchronizer();
sync.init(pusher);
Future<SyncResult> future = sync.synchronize(sw);
try {
future.get();
} catch (Exception e) {
fail("Failed to Future#get()");
}
}
}