package net.floodlightcontroller.core.internal;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.anyObject;
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.reset;
import static org.easymock.EasyMock.resetToStrict;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.easymock.EasyMock;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import net.floodlightcontroller.core.HARole;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitch.SwitchStatus;
import net.floodlightcontroller.core.IOFSwitchBackend;
import net.floodlightcontroller.core.PortChangeEvent;
import net.floodlightcontroller.core.PortChangeType;
import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType;
import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.QuarantineState;
import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState;
import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import org.projectfloodlight.openflow.protocol.OFBadActionCode;
import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
import org.projectfloodlight.openflow.protocol.OFBarrierReply;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFErrorMsg;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
import org.projectfloodlight.openflow.protocol.OFFlowRemovedReason;
import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketInReason;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFPortReason;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
import org.projectfloodlight.openflow.protocol.OFSetConfig;
import org.projectfloodlight.openflow.protocol.OFStatsRequest;
import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.projectfloodlight.openflow.protocol.OFTableFeatureProp;
import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsReply;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFAuxId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U32;
import org.projectfloodlight.openflow.types.U64;
import net.floodlightcontroller.util.LinkedHashSetWrapper;
import net.floodlightcontroller.util.OrderedCollection;
import com.google.common.collect.ImmutableList;
public abstract class OFSwitchHandlerTestBase {
protected static final DatapathId dpid = DatapathId.of(0x42L);
protected IOFSwitchManager switchManager;
protected RoleManager roleManager;
private IDebugCounterService debugCounterService;
protected OFSwitchHandshakeHandler switchHandler;
protected MockOFConnection connection;
// Use a 1.0 factory for the 1.0 test
protected final OFFactory factory = getFactory();
protected OFFeaturesReply featuresReply;
protected List<IAppHandshakePluginFactory> plugins;
private HashSet<Long> seenXids = null;
protected IOFSwitchBackend sw;
private Timer timer;
private TestHandshakePlugin handshakePlugin;
private class TestHandshakePlugin extends OFSwitchAppHandshakePlugin {
protected TestHandshakePlugin(PluginResult defaultResult, int timeoutS) {
super(defaultResult, timeoutS);
}
@Override
protected void processOFMessage(OFMessage m) {
}
@Override
protected void enterPlugin() {
}
}
public void setUpFeaturesReply() {
getFeaturesReply();
this.featuresReply = getFeaturesReply();
// Plugin set
IAppHandshakePluginFactory factory = createMock(IAppHandshakePluginFactory.class);
PluginResult result = new PluginResult(PluginResultType.QUARANTINE, "test quarantine");
handshakePlugin = new TestHandshakePlugin(result, 5);
expect(factory.createPlugin()).andReturn(handshakePlugin).anyTimes();
replay(factory);
plugins = ImmutableList.of(factory);
}
@Before
public void setUp() throws Exception {
/*
* This needs to be called explicitly to ensure the featuresReply is not null.
* Otherwise, there is no guarantee @Before will for setUpFeaturesReply() will
* call that function before our @Before setUp() here.
*/
setUpFeaturesReply();
switchManager = createMock(IOFSwitchManager.class);
roleManager = createMock(RoleManager.class);
sw = createMock(IOFSwitchBackend.class);
timer = createMock(Timer.class);
expect(timer.newTimeout(anyObject(TimerTask.class), anyLong(), anyObject(TimeUnit.class))).andReturn(EasyMock.createNiceMock(Timeout.class));
replay(timer);
seenXids = null;
// TODO: should mock IDebugCounterService and make sure
// the expected counters are updated.
debugCounterService = new DebugCounterServiceImpl();
SwitchManagerCounters counters =
new SwitchManagerCounters(debugCounterService);
expect(switchManager.getCounters()).andReturn(counters).anyTimes();
replay(switchManager);
connection = new MockOFConnection(featuresReply.getDatapathId(), OFAuxId.MAIN);
switchHandler = new OFSwitchHandshakeHandler(connection, featuresReply, switchManager, roleManager, timer);
// replay sw. Reset it if you need more specific behavior
replay(sw);
}
@After
public void tearDown() {
verifyAll();
}
private void verifyAll() {
assertThat("Unexpected messages have been captured",
connection.getMessages(),
Matchers.empty());
// verify all mocks.
verify(sw);
}
void verifyUniqueXids(OFMessage... msgs) {
verifyUniqueXids(Arrays.asList(msgs));
}
/** make sure that the transaction ids in the given messages are
* not 0 and differ between each other.
* While it's not a defect per se if the xids are we want to ensure
* we use different ones for each message we send.
*/
void verifyUniqueXids(List<OFMessage> msgs) {
if (seenXids == null)
seenXids = new HashSet<Long>();
for (OFMessage m: msgs) {
long xid = m.getXid();
assertTrue("Xid in messags is 0", xid != 0);
assertFalse("Xid " + xid + " has already been used",
seenXids.contains(xid));
seenXids.add(xid);
}
}
/*************************** abstract phases / utilities to be filled in by the subclasses */
// Factory + messages
/** @return the version-appropriate factory */
public abstract OFFactory getFactory();
/**
* @return a version appropriate features reply (different in 1.3 because it
* doesn't have ports)
*/
abstract OFFeaturesReply getFeaturesReply();
/** @return the class that's used for role requests/replies (OFNiciraRoleRequest vs.
* OFRequest)
*/
/// Role differences
abstract Class<?> getRoleRequestClass();
/** Verify that the given OFMessage is a correct RoleRequest message
* for the given role using the given xid (for the version).
*/
public abstract void verifyRoleRequest(OFMessage m,
OFControllerRole expectedRole);
/** Return a RoleReply message for the given role */
protected abstract OFMessage getRoleReply(long xid, OFControllerRole role);
/// Difference in the handshake sequence
/** OF1.3 has the PortDescStatsRequest, OF1.0 not */
abstract void moveToPreConfigReply() throws Exception;
/**
* Move the channel from scratch to WaitAppHandshakeState
* Different for OF1.0 and OF1.3 because of GenTables.
* @throws Exception
*/
@Test
public abstract void moveToWaitAppHandshakeState() throws Exception;
/**
* Move the channel from scratch to WaitSwitchDriverSubHandshake
* Different for OF1.0 and OF1.3 because of GenTables.
* @throws Exception
*/
@Test
public abstract void moveToWaitSwitchDriverSubHandshake() throws Exception;
/**
* Move the channel from scratch to WaitInitialRole
* Different for OF1.0 and OF1.3 because of Controller Connections.
* @throws Exception
*/
@Test
public abstract void moveToWaitInitialRole() throws Exception;
/*******************************************************************************************/
/** Move the channel from scratch to INIT state
This occurs upon creation of the switch handler
*/
@Test
public void testInitState() throws Exception {
assertThat(connection.getListener(), notNullValue());
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.InitState.class));
}
/** Move the channel from scratch to WAIT_CONFIG_REPLY state
* adds testing for beginHandshake() which moves the state from
* InitState to WaitConfigReply.
*/
@Test
public void moveToWaitConfigReply() throws Exception {
moveToPreConfigReply();
List<OFMessage> msgs = connection.getMessages();
assertEquals(3, msgs.size());
assertEquals(OFType.SET_CONFIG, msgs.get(0).getType());
OFSetConfig sc = (OFSetConfig)msgs.get(0);
assertEquals(0xffff, sc.getMissSendLen());
assertEquals(OFType.BARRIER_REQUEST, msgs.get(1).getType());
assertEquals(OFType.GET_CONFIG_REQUEST, msgs.get(2).getType());
verifyUniqueXids(msgs);
msgs.clear();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitConfigReplyState.class));
verifyAll();
}
/** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state
* Builds on moveToWaitConfigReply()
* adds testing for WAIT_CONFIG_REPLY state
*/
@Test
public void moveToWaitDescriptionStatReply() throws Exception {
moveToWaitConfigReply();
connection.clearMessages();
OFGetConfigReply cr = factory.buildGetConfigReply()
.setMissSendLen(0xFFFF)
.build();
switchHandler.processOFMessage(cr);
OFMessage msg = connection.retrieveMessage();
assertEquals(OFType.STATS_REQUEST, msg.getType());
OFStatsRequest<?> sr = (OFStatsRequest<?>)msg;
assertEquals(OFStatsType.DESC, sr.getStatsType());
verifyUniqueXids(msg);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitDescriptionStatReplyState.class));
}
protected OFDescStatsReply createDescriptionStatsReply() {
OFDescStatsReply statsReply = factory.buildDescStatsReply()
.setDpDesc("Datapath Description")
.setHwDesc("Hardware Description")
.setMfrDesc("Manufacturer Description")
.setSwDesc("Software Description")
.setSerialNum("Serial Number")
.build();
return statsReply;
}
protected OFTableFeaturesStatsReply createTableFeaturesStatsReply() {
OFTableFeaturesStatsReply statsReply = factory.buildTableFeaturesStatsReply()
.setEntries(Collections.singletonList(factory.buildTableFeatures()
.setConfig(0)
.setMaxEntries(100)
.setMetadataMatch(U64.NO_MASK)
.setMetadataWrite(U64.NO_MASK)
.setName("MyTable")
.setTableId(TableId.of(1))
.setProperties(Collections.singletonList((OFTableFeatureProp)factory.buildTableFeaturePropMatch()
.setOxmIds(Collections.singletonList(U32.of(100)))
.build())
).build()
)
).build();
return statsReply;
}
/**
* setup the expectations for the mock switch that are needed
* after the switch is instantiated in the WAIT_DESCRIPTION_STATS STATE
* Will reset the switch
* @throws CounterException
*/
protected void setupSwitchForInstantiationWithReset()
throws Exception {
reset(sw);
sw.setFeaturesReply(featuresReply);
expectLastCall().once();
}
/**
* Tests a situation where a switch returns a QUARANTINE result. This means
* we should move the handshake handler to a quarantine state and also
* quarantine the switch in the controller.
*
* @throws Exception
*/
@Test
public void moveQuarantine() throws Exception {
moveToWaitAppHandshakeState();
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.QUARANTINED);
expectLastCall().once();
replay(switchManager);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(WaitAppHandshakeState.class));
WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting();
assertThat(state.getCurrentPlugin(), CoreMatchers.<OFSwitchAppHandshakePlugin>equalTo(handshakePlugin));
reset(sw);
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE);
sw.setStatus(SwitchStatus.QUARANTINED);
expectLastCall().once();
replay(sw);
PluginResult result = new PluginResult(PluginResultType.QUARANTINE, "test quarantine");
handshakePlugin.exitPlugin(result);
assertThat(switchHandler.getStateForTesting(),
CoreMatchers.instanceOf(QuarantineState.class));
verify(switchManager);
}
/**
* Tests a situation where a plugin returns a DISCONNECT result. This means
* we should disconnect the connection and the state should not change.
*
* @throws Exception
*/
@Test
public void failedAppHandshake() throws Exception {
moveToWaitAppHandshakeState();
assertThat(switchHandler.getStateForTesting(),
CoreMatchers.instanceOf(WaitAppHandshakeState.class));
WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting();
assertThat(state.getCurrentPlugin(), CoreMatchers.<OFSwitchAppHandshakePlugin>equalTo(handshakePlugin));
PluginResult result = new PluginResult(PluginResultType.DISCONNECT);
handshakePlugin.exitPlugin(result);
assertThat(connection.isConnected(), equalTo(false));
}
@Test
public void validAppHandshakePluginReason() throws Exception {
try{
new PluginResult(PluginResultType.QUARANTINE,"This should not cause an exception");
}catch(IllegalStateException e) {
fail("This should cause an illegal state exception");
}
}
@Test
public void invalidAppHandshakePluginReason() throws Exception {
try{
new PluginResult(PluginResultType.CONTINUE,"This should cause an exception");
fail("This should cause an illegal state exception");
}catch(IllegalStateException e) { /* Expected */ }
try{
new PluginResult(PluginResultType.DISCONNECT,"This should cause an exception");
fail("This should cause an illegal state exception");
}catch(IllegalStateException e) { /* Expected */ }
}
/**
* Move the channel from scratch to WAIT_INITIAL_ROLE state via
* WAIT_SWITCH_DRIVER_SUB_HANDSHAKE
* Does extensive testing for the WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state
*
*/
@Test
public void testSwitchDriverSubHandshake()
throws Exception {
moveToWaitSwitchDriverSubHandshake();
//-------------------------------------------------
//-------------------------------------------------
// Send a message to the handler, it should be passed to the
// switch's sub-handshake handling. After this message the
// sub-handshake will be complete
// FIXME:LOJI: With Andi's fix for a default Match object we won't
// need to build/set this match object
Match match = factory.buildMatch().build();
OFMessage m = factory.buildFlowRemoved().setReason(OFFlowRemovedReason.DELETE).setMatch(match).build();
resetToStrict(sw);
sw.processDriverHandshakeMessage(m);
expectLastCall().once();
expect(sw.isDriverHandshakeComplete()).andReturn(true).once();
replay(sw);
switchHandler.processOFMessage(m);
assertThat(switchHandler.getStateForTesting(),
CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitAppHandshakeState.class));
assertThat("Unexpected message captured", connection.getMessages(), Matchers.empty());
verify(sw);
}
@Test
/** Test WaitDescriptionReplyState */
public void testWaitDescriptionReplyState() throws Exception {
moveToWaitInitialRole();
}
/**
* Setup the mock switch and write capture for a role request, set the
* role and verify mocks.
* @param supportsNxRole whether the switch supports role request messages
* to setup the attribute. This must be null (don't yet know if roles
* supported: send to check) or true.
* @param role The role to send
* @throws IOException
*/
private long setupSwitchSendRoleRequestAndVerify(Boolean supportsNxRole,
OFControllerRole role) throws IOException {
assertTrue("This internal test helper method most not be called " +
"with supportsNxRole==false. Test setup broken",
supportsNxRole == null || supportsNxRole == true);
reset(sw);
expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(supportsNxRole).atLeastOnce();
replay(sw);
switchHandler.sendRoleRequest(role);
OFMessage msg = connection.retrieveMessage();
verifyRoleRequest(msg, role);
verify(sw);
return msg.getXid();
}
/**
* Setup the mock switch for a role change request where the switch
* does not support roles.
*
* Needs to verify and reset the controller since we need to set
* an expectation
*/
@SuppressWarnings("unchecked")
private void setupSwitchRoleChangeUnsupported(int xid,
OFControllerRole role) {
SwitchStatus newStatus = role != OFControllerRole.ROLE_SLAVE ? SwitchStatus.MASTER : SwitchStatus.SLAVE;
boolean supportsNxRole = false;
verify(switchManager);
reset(sw, switchManager);
expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(supportsNxRole).atLeastOnce();
// TODO: hmmm. While it's not incorrect that we set the attribute
// again it looks odd. Maybe change
expect(sw.getOFFactory()).andReturn(factory).anyTimes();
expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes();
expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes();
expect(sw.getNumTables()).andStubReturn((short)0);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole);
expectLastCall().anyTimes();
if (SwitchStatus.MASTER == newStatus) {
if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) {
expect(sw.getTables()).andReturn(Collections.EMPTY_LIST).once();
expect(sw.getTableFeatures(TableId.ZERO)).andReturn(TableFeatures.of(createTableFeaturesStatsReply().getEntries().get(0))).anyTimes();
}
}
sw.setControllerRole(role);
expectLastCall().once();
if (role == OFControllerRole.ROLE_SLAVE) {
sw.disconnect();
expectLastCall().once();
} else {
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(newStatus);
expectLastCall().once();
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, newStatus);
}
replay(sw, switchManager);
switchHandler.sendRoleRequest(role);
/* Now, trigger transition to master */
OFBarrierReply br = getFactory().buildBarrierReply()
.build();
switchHandler.processOFMessage(br);
verify(sw, switchManager);
}
/** Return a bad request error message with the given xid/code */
private OFMessage getBadRequestErrorMessage(OFBadRequestCode code, long xid) {
OFErrorMsg msg = factory.errorMsgs().buildBadRequestErrorMsg()
.setXid(xid)
.setCode(code)
.build();
return msg;
}
/** Return a bad action error message with the given xid/code */
private OFMessage getBadActionErrorMessage(OFBadActionCode code, long xid) {
OFErrorMsg msg = factory.errorMsgs().buildBadActionErrorMsg()
.setXid(xid)
.setCode(code)
.build();
return msg;
}
/** Move the channel from scratch to MASTER state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* This method tests only the simple case that the switch supports roles
* and transitions to MASTER
*/
@SuppressWarnings("unchecked")
@Test
public void testInitialMoveToMasterWithRole() throws Exception {
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
expect(sw.getOFFactory()).andReturn(factory).anyTimes();
expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes();
expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes();
expect(sw.getTables()).andStubReturn(Collections.EMPTY_LIST);
expect(sw.getNumTables()).andStubReturn((short) 0);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_MASTER);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(SwitchStatus.MASTER);
expectLastCall().once();
if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) {
//expect(sw.getMaxTableForTableMissFlow()).andReturn(TableId.ZERO).times(1);
expect(sw.getTableFeatures(TableId.ZERO)).andReturn(TableFeatures.of(createTableFeaturesStatsReply().getEntries().get(0))).anyTimes();
}
replay(sw);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER);
expectLastCall().once();
replay(switchManager);
OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER);
/* Go into the MasterState */
switchHandler.processOFMessage(reply);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
}
/** Move the channel from scratch to SLAVE state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* This method tests only the simple case that the switch supports roles
* and transitions to SLAVE
*/
@Test
public void testInitialMoveToSlaveWithRole() throws Exception {
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
sw.setAttribute(IOFSwitchBackend.SWITCH_SUPPORTS_NX_ROLE, true);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_SLAVE);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(SwitchStatus.SLAVE);
expectLastCall().once();
replay(sw);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.SLAVE);
expectLastCall().once();
replay(switchManager);
OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_SLAVE);
// sendMessageToHandler will verify and rest controller mock
switchHandler.processOFMessage(reply);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.SlaveState.class));
}
/** Move the channel from scratch to MASTER state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* This method tests the case that the switch does NOT support roles.
* The channel handler still needs to send the initial request to find
* out that whether the switch supports roles.
*/
@SuppressWarnings("unchecked")
@Test
public void testInitialMoveToMasterNoRole() throws Exception {
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
expect(sw.getOFFactory()).andReturn(factory).anyTimes();
expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes();
expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes();
expect(sw.getNumTables()).andStubReturn((short)0);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_MASTER);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(SwitchStatus.MASTER);
expectLastCall().once();
if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) {
expect(sw.getTables()).andReturn(Collections.EMPTY_LIST).once();
expect(sw.getTableFeatures(TableId.ZERO)).andReturn(TableFeatures.of(createTableFeaturesStatsReply().getEntries().get(0))).anyTimes();
}
replay(sw);
// FIXME: shouldn't use ordinal(), but OFError is broken
// Error with incorrect xid and type. Should be ignored.
OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid+1);
// sendMessageToHandler will verify and rest controller mock
switchHandler.processOFMessage(err);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Error with correct xid. Should trigger state transition
err = getBadRequestErrorMessage(OFBadRequestCode.BAD_EXPERIMENTER, xid);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER);
expectLastCall().once();
replay(switchManager);
/* Go into the MasterState */
switchHandler.processOFMessage(err);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
}
/** Move the channel from scratch to MASTER state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* We let the initial role request time out. Role support should be
* disabled but the switch should be activated.
*/
@SuppressWarnings("unchecked")
@Test
public void testInitialMoveToMasterTimeout() throws Exception {
int timeout = 50;
switchHandler.useRoleChangerWithOtherTimeoutForTesting(timeout);
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER); /* don't care about the XID, since we assume the reply is lost */
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
expect(sw.getOFFactory()).andReturn(factory).anyTimes();
expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes();
expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes();
expect(sw.getNumTables()).andStubReturn((short)0);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_MASTER);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(SwitchStatus.MASTER);
expectLastCall().once();
if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) {
expect(sw.getTables()).andReturn(Collections.EMPTY_LIST).once();
expect(sw.getTableFeatures(TableId.ZERO)).andReturn(TableFeatures.of(createTableFeaturesStatsReply().getEntries().get(0))).anyTimes();
}
replay(sw);
OFMessage m = factory.barrierReply();
Thread.sleep(timeout + 5);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER);
expectLastCall().once();
replay(switchManager);
switchHandler.processOFMessage(m);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
}
/** Move the channel from scratch to SLAVE state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* This method tests the case that the switch does NOT support roles.
* The channel handler still needs to send the initial request to find
* out that whether the switch supports roles.
*
*/
@Test
public void testInitialMoveToSlaveNoRole() throws Exception {
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_SLAVE);
expectLastCall().once();
sw.disconnect(); // Make sure we disconnect
expectLastCall().once();
replay(sw);
// FIXME: shouldn't use ordinal(), but OFError is broken
// Error with incorrect xid and type. Should be ignored.
OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid+1);
// sendMessageToHandler will verify and rest controller mock
switchHandler.processOFMessage(err);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Error with correct xid. Should trigger state transition
err = getBadRequestErrorMessage(OFBadRequestCode.BAD_EXPERIMENTER, xid);
// sendMessageToHandler will verify and rest controller mock
switchHandler.processOFMessage(err);
}
/** Move the channel from scratch to SLAVE state
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*
* We let the initial role request time out. The switch should be
* disconnected
*/
@Test
public void testInitialMoveToSlaveTimeout() throws Exception {
int timeout = 50;
switchHandler.useRoleChangerWithOtherTimeoutForTesting(timeout);
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// prepare mocks and inject the role reply message
reset(sw);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_SLAVE);
expectLastCall().once();
sw.disconnect(); // Make sure we disconnect
expectLastCall().once();
replay(sw);
// Apparently this can be any type of message for this test?!
OFMessage m = factory.buildBarrierReply().build();
Thread.sleep(timeout+5);
switchHandler.processOFMessage(m);
}
/** Move channel from scratch to WAIT_INITIAL_STATE, then MASTER,
* then SLAVE for cases where the switch does not support roles.
* I.e., the final SLAVE transition should disconnect the switch.
*/
@Test
public void testNoRoleInitialToMasterToSlave() throws Exception {
int xid = 46;
// First, lets move the state to MASTER without role support
testInitialMoveToMasterNoRole();
assertThat(switchHandler.getStateForTesting(),
CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
assertThat("Unexpected messages have been captured",
connection.getMessages(),
Matchers.empty());
// try to set master role again. should be a no-op
setupSwitchRoleChangeUnsupported(xid, OFControllerRole.ROLE_MASTER);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
assertThat("Unexpected messages have been captured",
connection.getMessages(),
Matchers.empty());
setupSwitchRoleChangeUnsupported(xid, OFControllerRole.ROLE_SLAVE);
assertThat(connection.isConnected(), equalTo(false));
assertThat("Unexpected messages have been captured",
connection.getMessages(),
Matchers.empty());
}
/** Move the channel to MASTER state
* Expects that the channel is in MASTER or SLAVE state.
*
*/
@SuppressWarnings("unchecked")
public void changeRoleToMasterWithRequest() throws Exception {
assertTrue("This method can only be called when handler is in " +
"MASTER or SLAVE role", switchHandler.isHandshakeComplete());
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(true, OFControllerRole.ROLE_MASTER);
// prepare mocks and inject the role reply message
reset(sw);
expect(sw.getOFFactory()).andReturn(factory).anyTimes();
expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes();
expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes();
expect(sw.getNumTables()).andStubReturn((short)0);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_MASTER);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once();
sw.setStatus(SwitchStatus.MASTER);
expectLastCall().once();
expect(sw.getTables()).andReturn(Collections.EMPTY_LIST).once();
replay(sw);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER);
expectLastCall().once();
replay(switchManager);
OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER);
switchHandler.processOFMessage(reply);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class));
}
/** Move the channel to SLAVE state
* Expects that the channel is in MASTER or SLAVE state.
*
*/
public void changeRoleToSlaveWithRequest() throws Exception {
assertTrue("This method can only be called when handler is in " +
"MASTER or SLAVE role", switchHandler.isHandshakeComplete());
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(true, OFControllerRole.ROLE_SLAVE);
// prepare mocks and inject the role reply message
reset(sw);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
expectLastCall().once();
sw.setControllerRole(OFControllerRole.ROLE_SLAVE);
expectLastCall().once();
expect(sw.getStatus()).andReturn(SwitchStatus.MASTER).once();
sw.setStatus(SwitchStatus.SLAVE);
expectLastCall().once();
replay(sw);
reset(switchManager);
switchManager.switchStatusChanged(sw, SwitchStatus.MASTER, SwitchStatus.SLAVE);
expectLastCall().once();
replay(switchManager);
OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_SLAVE);
connection.getListener().messageReceived(connection, reply);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.SlaveState.class));
}
@Test
public void testMultiRoleChange1() throws Exception {
testInitialMoveToMasterWithRole();
changeRoleToMasterWithRequest();
changeRoleToSlaveWithRequest();
changeRoleToSlaveWithRequest();
changeRoleToMasterWithRequest();
changeRoleToSlaveWithRequest();
}
@Test
public void testMultiRoleChange2() throws Exception {
testInitialMoveToSlaveWithRole();
changeRoleToMasterWithRequest();
changeRoleToSlaveWithRequest();
changeRoleToSlaveWithRequest();
changeRoleToMasterWithRequest();
changeRoleToSlaveWithRequest();
}
/** Start from scratch and reply with an unexpected error to the role
* change request
* Builds on doMoveToWaitInitialRole()
* adds testing for WAIT_INITAL_ROLE state
*/
@Test
public void testInitialRoleChangeOtherError() throws Exception {
// first, move us to WAIT_INITIAL_ROLE_STATE
moveToWaitInitialRole();
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
// Set the role
long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER);
assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class));
OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid);
verifyExceptionCaptured(err, SwitchStateException.class);
}
/**
* Test dispatch of messages while in MASTER role
*/
@Test
public void testMessageDispatchMaster() throws Exception {
testInitialMoveToMasterWithRole();
// Send packet in. expect dispatch
OFPacketIn pi = factory.buildPacketIn()
.setReason(OFPacketInReason.NO_MATCH)
.build();
reset(switchManager);
switchManager.handleMessage(sw, pi, null);
expectLastCall().once();
replay(switchManager);
switchHandler.processOFMessage(pi);
// TODO: many more to go
}
/**
* Test port status message handling while MASTER
*
*/
@Test
public void testPortStatusMessageMaster() throws Exception {
DatapathId dpid = featuresReply.getDatapathId();
testInitialMoveToMasterWithRole();
OFPortDesc portDesc = factory.buildPortDesc()
.setName("Port1")
.setPortNo(OFPort.of(1))
.build();
OFPortStatus.Builder portStatusBuilder = factory.buildPortStatus()
.setDesc(portDesc);
// The events we expect sw.handlePortStatus to return
// We'll just use the same list for all valid OFPortReasons and add
// arbitrary events for arbitrary ports that are not necessarily
// related to the port status message. Our goal
// here is not to return the correct set of events but the make sure
// that a) sw.handlePortStatus is called
// b) the list of events sw.handlePortStatus returns is sent
// as IOFSwitchListener notifications.
OrderedCollection<PortChangeEvent> events =
new LinkedHashSetWrapper<PortChangeEvent>();
OFPortDesc.Builder pb = factory.buildPortDesc();
OFPortDesc p1 = pb.setName("eth1").setPortNo(OFPort.of(1)).build();
OFPortDesc p2 = pb.setName("eth2").setPortNo(OFPort.of(2)).build();
OFPortDesc p3 = pb.setName("eth3").setPortNo(OFPort.of(3)).build();
OFPortDesc p4 = pb.setName("eth4").setPortNo(OFPort.of(4)).build();
OFPortDesc p5 = pb.setName("eth5").setPortNo(OFPort.of(5)).build();
events.add(new PortChangeEvent(p1, PortChangeType.ADD));
events.add(new PortChangeEvent(p2, PortChangeType.DELETE));
events.add(new PortChangeEvent(p3, PortChangeType.UP));
events.add(new PortChangeEvent(p4, PortChangeType.DOWN));
events.add(new PortChangeEvent(p5, PortChangeType.OTHER_UPDATE));
for (OFPortReason reason: OFPortReason.values()) {
OFPortStatus portStatus = portStatusBuilder.setReason(reason).build();
reset(sw);
expect(sw.getId()).andReturn(dpid).anyTimes();
expect(sw.processOFPortStatus(portStatus)).andReturn(events).once();
replay(sw);
reset(switchManager);
switchManager.notifyPortChanged(sw, p1, PortChangeType.ADD);
switchManager.notifyPortChanged(sw, p2, PortChangeType.DELETE);
switchManager.notifyPortChanged(sw, p3, PortChangeType.UP);
switchManager.notifyPortChanged(sw, p4, PortChangeType.DOWN);
switchManager.notifyPortChanged(sw, p5, PortChangeType.OTHER_UPDATE);
replay(switchManager);
switchHandler.processOFMessage(portStatus);
verify(sw);
}
}
/**
* Test re-assert MASTER
*
*/
@Test
public void testReassertMaster() throws Exception {
testInitialMoveToMasterWithRole();
OFMessage err = getBadRequestErrorMessage(OFBadRequestCode.EPERM, 42);
reset(roleManager);
roleManager.reassertRole(switchHandler, HARole.ACTIVE);
expectLastCall().once();
replay(roleManager);
reset(switchManager);
switchManager.handleMessage(sw, err, null);
expectLastCall().once();
replay(switchManager);
switchHandler.processOFMessage(err);
verify(sw);
}
/**
* Verify that the given exception event capture (as returned by
* getAndInitExceptionCapture) has thrown an exception of the given
* expectedExceptionClass.
* Resets the capture
* @param err
*/
void verifyExceptionCaptured(
OFMessage err, Class<? extends Throwable> expectedExceptionClass) {
Throwable caughtEx = null;
// This should purposely cause an exception
try{
switchHandler.processOFMessage(err);
}
catch(Exception e){
// Capture the exception
caughtEx = e;
}
assertThat(caughtEx, CoreMatchers.instanceOf(expectedExceptionClass));
}
/**
* Tests the connection closed functionality before the switch handshake is complete.
* Essentially when the switch handshake is only aware of the IOFConnection.
*/
@Test
public void testConnectionClosedBeforeHandshakeComplete() {
// Test connection closed prior to being finished
reset(switchManager);
switchManager.handshakeDisconnected(dpid);
expectLastCall().once();
replay(switchManager);
switchHandler.connectionClosed(connection);
verify(switchManager);
}
/**
* Tests the connection closed functionality after the switch handshake is complete.
* Essentially when the switch handshake is aware of an IOFSwitch.
* @throws Exception
*/
@Test
public void testConnectionClosedAfterHandshakeComplete() throws Exception {
testInitialMoveToMasterWithRole();
// Test connection closed prior to being finished
reset(switchManager);
switchManager.handshakeDisconnected(dpid);
expectLastCall().once();
switchManager.switchDisconnected(sw);
expectLastCall().once();
replay(switchManager);
reset(sw);
expect(sw.getStatus()).andReturn(SwitchStatus.DISCONNECTED).anyTimes();
replay(sw);
switchHandler.connectionClosed(connection);
verify(switchManager);
verify(sw);
}
}