blob: aaa9ba4ca06495c3b7007768e2785ab775b24ecd [file] [log] [blame]
package net.onrc.onos.ofcontroller.flowprogrammer;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import io.netty.util.concurrent.Future;
import net.floodlightcontroller.core.IOFSwitch;
import net.onrc.onos.graph.GraphDBOperation;
import net.onrc.onos.ofcontroller.core.INetMapTopologyObjects.IFlowEntry;
import net.onrc.onos.ofcontroller.core.INetMapTopologyObjects.ISwitchObject;
import net.onrc.onos.ofcontroller.flowmanager.FlowDatabaseOperation;
import net.onrc.onos.ofcontroller.util.FlowEntry;
import net.onrc.onos.ofcontroller.util.FlowEntryId;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFStatisticsRequest;
import org.openflow.protocol.OFType;
import org.openflow.protocol.statistics.OFFlowStatisticsReply;
import org.openflow.protocol.statistics.OFStatistics;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({FlowSynchronizer.class, GraphDBOperation.class, FlowDatabaseOperation.class})
public class FlowSynchronizerTest {
private FlowPusher pusher;
private FlowSynchronizer sync;
private List<Long> idAdded;
private List<Long> idRemoved;
@Before
public void setUp() throws Exception {
idAdded = new ArrayList<Long>();
idRemoved = new ArrayList<Long>();
pusher = EasyMock.createMock(FlowPusher.class);
EasyMock.expect(pusher.add(EasyMock.anyObject(IOFSwitch.class), EasyMock.anyObject(OFMessage.class)))
.andAnswer(new IAnswer<Boolean>() {
@Override
public Boolean answer() throws Throwable {
OFMessage msg = (OFMessage)EasyMock.getCurrentArguments()[1];
if (msg.getType().equals(OFType.FLOW_MOD)) {
OFFlowMod fm = (OFFlowMod)msg;
if (fm.getCommand() == OFFlowMod.OFPFC_DELETE_STRICT) {
idRemoved.add(fm.getCookie());
}
}
return null;
}
}).anyTimes();
EasyMock.expect(pusher.add(EasyMock.anyObject(IOFSwitch.class), EasyMock.anyObject(FlowEntry.class)))
.andAnswer(new IAnswer<Boolean>() {
@Override
public Boolean answer() throws Throwable {
FlowEntry flow = (FlowEntry)EasyMock.getCurrentArguments()[1];
idAdded.add(flow.flowEntryId().value());
return null;
}
}).anyTimes();
EasyMock.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,100);
// 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,100);
// 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,100);
// 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,100);
// 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, 3000);
// 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 = EasyMock.createMock(IOFSwitch.class);
EasyMock.expect(sw.getId()).andReturn((long)1).anyTimes();
List<OFStatistics> stats = new ArrayList<OFStatistics>();
for (long cookie : cookieList) {
stats.add(createReply(cookie));
}
@SuppressWarnings("unchecked")
Future<List<OFStatistics>> future = EasyMock.createMock(Future.class);
try {
EasyMock.expect(future.get()).andReturn(stats).once();
} catch (InterruptedException e1) {
fail("Failed in Future#get()");
} catch (ExecutionException e1) {
fail("Failed in Future#get()");
}
EasyMock.replay(future);
try {
EasyMock.expect(sw.getStatistics(EasyMock.anyObject(OFStatisticsRequest.class)))
.andReturn(future).once();
} catch (IOException e) {
fail("Failed in IOFSwitch#getStatistics()");
}
EasyMock.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 OFFlowStatisticsReply createReply(long cookie) {
OFFlowStatisticsReply stat = new OFFlowStatisticsReply();
OFMatch match = new OFMatch();
stat.setCookie(cookie);
stat.setMatch(match);
stat.setPriority((short)1);
return stat;
}
/**
* Create mock GraphDBOperation and FlowDatabaseOperation to mock DB.
* @param idList List of FlowEntry IDs stored in DB.
*/
private void initMockGraph(long[] idList) {
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);
GraphDBOperation mockOp = PowerMock.createMock(GraphDBOperation.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(GraphDBOperation.class, "").andReturn(mockOp);
} catch (Exception e) {
fail("Failed to create GraphDBOperation");
}
PowerMock.replay(GraphDBOperation.class);
}
/**
* Instantiate FlowSynchronizer and sync flows.
* @param sw Target IOFSwitch object
*/
private void doSynchronization(IOFSwitch sw, long wait) {
sync = new FlowSynchronizer();
sync.init(pusher);
sync.synchronize(sw);
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
fail("Failed to sleep");
}
}
}