diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java index a14ca20199c549213544d85c73a4b7ade72058ed..47edf3a08a1db86de881f516ea607134f9ebe34b 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java @@ -27,8 +27,8 @@ import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; import org.projectfloodlight.openflow.protocol.OFPortStatus; -import org.projectfloodlight.openflow.protocol.OFTableFeatures; import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsReply; +import org.projectfloodlight.openflow.types.TableId; import net.floodlightcontroller.util.OrderedCollection; @@ -194,4 +194,35 @@ public interface IOFSwitchBackend extends IOFSwitch { * @return true if another viable master exists */ boolean hasAnotherMaster(); + + /** + * In OF1.3+ switches, the table miss behavior is defined by a flow. + * We assume the default behavior is to forward to the controller, but + * not all tables need that behavior if a limited set of tables are used. + * So, we can cap the number of tables we set this flow in to reduce + * clutter in API output and to reduce memory consumption on the switch. + * + * This gets the TableId cap set for this particular switch. + * + * @return, the highest TableId that should receive a table-miss flow + */ + TableId getMaxTableForTableMissFlow(); + + /** + * In OF1.3+ switches, the table miss behavior is defined by a flow. + * We assume the default behavior is to forward to the controller, but + * not all tables need that behavior if a limited set of tables are used. + * So, we can cap the number of tables we set this flow in to reduce + * clutter in API output and to reduce memory consumption on the switch. + * + * This sets the TableId cap set for this particular switch. If the max + * desired is higher than the number of tables this switch supports, the + * max table supported will be used: + * + * set_max_table = max_supported <= max ? max_supported-1 : max + * + * @param max, the highest TableId that should receive a table-miss flow + * @return the TableId set as the highest + */ + TableId setMaxTableForTableMissFlow(TableId max); } diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitch.java b/src/main/java/net/floodlightcontroller/core/OFSwitch.java index 8b401dad680cdda48eccaa36fad5db0b987d08a3..5360eb33d76c8594c9d246234f8487c9aab67b38 100644 --- a/src/main/java/net/floodlightcontroller/core/OFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/OFSwitch.java @@ -112,8 +112,6 @@ public class OFSwitch implements IOFSwitchBackend { */ private final PortManager portManager; - //private final TableManager tableManager; - private volatile boolean connected; private volatile OFControllerRole role; @@ -125,6 +123,8 @@ public class OFSwitch implements IOFSwitchBackend { private SwitchStatus status; public static final int OFSWITCH_APP_ID = ident(5); + + private TableId maxTableToGetTableMissFlow = TableId.of(4); /* this should cover most HW switches that have a couple SW flow tables */ static { AppCookie.registerApp(OFSwitch.OFSWITCH_APP_ID, "switch"); @@ -816,7 +816,7 @@ public class OFSwitch implements IOFSwitchBackend { this.buffers = featuresReply.getNBuffers(); if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0 ) { - // FIXME:LOJI: OF1.3 has per table actions. This needs to be modeled / handled here + /* OF1.3+ Per-table actions are set later in the OFTableFeaturesRequest/Reply */ this.actions = featuresReply.getActions(); } this.tables = featuresReply.getNTables(); @@ -1208,4 +1208,19 @@ public class OFSwitch implements IOFSwitchBackend { public TableFeatures getTableFeatures(TableId table) { return tableFeaturesByTableId.get(table); } + + @Override + public TableId getMaxTableForTableMissFlow() { + return maxTableToGetTableMissFlow; + } + + @Override + public TableId setMaxTableForTableMissFlow(TableId max) { + if (max.getValue() >= tables) { + maxTableToGetTableMissFlow = TableId.of(tables - 1 < 0 ? 0 : tables - 1); + } else { + maxTableToGetTableMissFlow = max; + } + return maxTableToGetTableMissFlow; + } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java index a17e7d5ae859978d5d7832ae10eed3273d7dc7b2..081f177d3f2a6525731c57f97c4270c55c63b676 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java @@ -475,11 +475,13 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { } /** - * Adds an initial table-miss flow to each - * and every table on the switch. This replaces the default behavior of - * forwarding table-miss packets to the controller. The table-miss flows - * inserted will forward all packets that do not match a flow to the - * controller for processing. + * Adds an initial table-miss flow to tables on the switch. + * This replaces the default behavior of forwarding table-miss packets + * to the controller. The table-miss flows inserted will forward all + * packets that do not match a flow to the controller for processing. + * + * The OFSwitchManager is checked for used-defined behavior and default + * max table to try to use. * * Adding the default flow only applies to OpenFlow 1.3+ switches, which * remove the default forward-to-controller behavior of flow tables. @@ -502,7 +504,7 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { ArrayList<OFAction> actions = new ArrayList<OFAction>(1); actions.add(factory.actions().output(OFPort.CONTROLLER, 0xffFFffFF)); ArrayList<OFMessage> flows = new ArrayList<OFMessage>(); - for (int tableId = 0; tableId < this.sw.getTables(); tableId++) { + for (int tableId = 0; tableId <= this.sw.getMaxTableForTableMissFlow().getValue(); tableId++) { OFFlowAdd defaultFlow = this.factory.buildFlowAdd() .setTableId(TableId.of(tableId)) .setPriority(0) @@ -1014,7 +1016,6 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { OFDescStatsReply descStatsReply = (OFDescStatsReply) m; SwitchDescription description = new SwitchDescription(descStatsReply); sw = switchManager.getOFSwitchInstance(mainConnection, description, factory, featuresReply.getDatapathId()); - switchManager.switchAdded(sw); // set switch information // set features reply and channel first so we a DPID and // channel info. @@ -1022,6 +1023,11 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { if (portDescStats != null) { sw.setPortDescStats(portDescStats); } + /* + * Need to add after setting the features. + */ + switchManager.switchAdded(sw); + // Handle pending messages now that we have a sw object handlePendingPortStatusMessages(description); diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java index 3c5ea1085267f0e30bec55659d0bdd049e5052ec..a1dbced1e2a00696280e56d3b6a6e4843b5bf01e 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java @@ -1,5 +1,6 @@ package net.floodlightcontroller.core.internal; +import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; @@ -55,8 +56,10 @@ import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.OFFeaturesReply; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.TableId; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.IStoreListener; import org.sdnplatform.sync.ISyncService; @@ -65,6 +68,10 @@ import org.sdnplatform.sync.error.SyncException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.MappingJsonFactory; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -93,10 +100,13 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen private static String keyStorePassword; private static String keyStore; private static boolean useSsl = false; - + protected static boolean clearTablesOnInitialConnectAsMaster = false; protected static boolean clearTablesOnEachTransitionToMaster = false; + protected static Map<DatapathId, TableId> forwardToControllerFlowsUpToTableByDpid; + protected static TableId forwardToControllerFlowsUpToTable = TableId.of(4); /* this should cover most HW switches that have a couple SW-based flow tables */ + private ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler> switchHandlers; private ConcurrentHashMap<DatapathId, IOFSwitchBackend> switches; private ConcurrentHashMap<DatapathId, IOFSwitch> syncedSwitches; @@ -167,6 +177,16 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen oldSw.disconnect(); } + /* + * Set other config options for this switch. + */ + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { + if (forwardToControllerFlowsUpToTableByDpid.containsKey(sw.getId())) { + sw.setMaxTableForTableMissFlow(forwardToControllerFlowsUpToTableByDpid.get(sw.getId())); + } else { + sw.setMaxTableForTableMissFlow(forwardToControllerFlowsUpToTable); + } + } } @LogMessageDocs({ @@ -679,13 +699,13 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen OFSwitchManager.keyStore = null; OFSwitchManager.keyStorePassword = null; } else { - log.warn("SSL enabled. Using secure connections between Floodlight and switches."); + log.info("SSL enabled. Using secure connections between Floodlight and switches."); log.info("SSL keystore path: {}, password: {}", path, (pass == null ? "" : pass)); OFSwitchManager.useSsl = true; OFSwitchManager.keyStore = path; OFSwitchManager.keyStorePassword = (pass == null ? "" : pass); } - + /* * Get config to define what to do when a switch connects. * @@ -693,33 +713,122 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen */ String clearInitial = configParams.get("clearTablesOnInitialHandshakeAsMaster"); String clearLater = configParams.get("clearTablesOnEachTransitionToMaster"); - + if (clearInitial == null || clearInitial.isEmpty() || (!clearInitial.equalsIgnoreCase("yes") && !clearInitial.equalsIgnoreCase("true") && - !clearInitial.equalsIgnoreCase("yep") && !clearInitial.equalsIgnoreCase("ja") && - !clearInitial.equalsIgnoreCase("stimmt"))) { - log.warn("Clear switch flow tables on initial handshake as master: FALSE"); + !clearInitial.equalsIgnoreCase("yep") && !clearInitial.equalsIgnoreCase("ja") && + !clearInitial.equalsIgnoreCase("stimmt"))) { + log.info("Clear switch flow tables on initial handshake as master: FALSE"); OFSwitchManager.clearTablesOnInitialConnectAsMaster = false; } else { - log.warn("Clear switch flow tables on initial handshake as master: TRUE"); + log.info("Clear switch flow tables on initial handshake as master: TRUE"); OFSwitchManager.clearTablesOnInitialConnectAsMaster = true; } - + if (clearLater == null || clearLater.isEmpty() || (!clearLater.equalsIgnoreCase("yes") && !clearLater.equalsIgnoreCase("true") && - !clearLater.equalsIgnoreCase("yep") && !clearLater.equalsIgnoreCase("ja") && - !clearLater.equalsIgnoreCase("stimmt"))) { - log.warn("Clear switch flow tables on each transition to master: FALSE"); + !clearLater.equalsIgnoreCase("yep") && !clearLater.equalsIgnoreCase("ja") && + !clearLater.equalsIgnoreCase("stimmt"))) { + log.info("Clear switch flow tables on each transition to master: FALSE"); OFSwitchManager.clearTablesOnEachTransitionToMaster = false; } else { - log.warn("Clear switch flow tables on each transition to master: TRUE"); + log.info("Clear switch flow tables on each transition to master: TRUE"); OFSwitchManager.clearTablesOnEachTransitionToMaster = true; } - - String tablesToGetFTCFlow = configParams.get("addDefaultSendToControllerFlowInTables"); - if (tablesToGetFTCFlow == null || tablesToGetFTCFlow.isEmpty()) { - + + /* + * Get default max table for forward to controller flows. + * Internal default set as class variable at top of OFSwitchManager. + */ + String defaultFlowsUpToTable = configParams.get("defaultMaxTableToReceiveTableMissFlow"); + if (defaultFlowsUpToTable != null && !defaultFlowsUpToTable.isEmpty()) { + defaultFlowsUpToTable = defaultFlowsUpToTable.toLowerCase().trim(); + try { + forwardToControllerFlowsUpToTable = TableId.of(defaultFlowsUpToTable.startsWith("0x") + ? Integer.parseInt(defaultFlowsUpToTable.replaceFirst("0x", ""), 16) + : Integer.parseInt(defaultFlowsUpToTable)); + log.info("Setting {} as the default max table to receive table-miss flow", forwardToControllerFlowsUpToTable.toString()); + } catch (IllegalArgumentException e) { + log.error("Invalid table ID {} for default max table to receive table-miss flow. Using pre-set of {}", + defaultFlowsUpToTable, forwardToControllerFlowsUpToTable.toString()); + } + } else { + log.info("Default max table to receive table-miss flow not configured. Using {}", forwardToControllerFlowsUpToTable.toString()); + } + + /* + * Get config to define which tables per switch will get a + * default forward-to-controller flow. This can be used to + * reduce the number of such flows if only a reduced set of + * tables are being used. + * + * By default, + */ + forwardToControllerFlowsUpToTableByDpid = jsonToSwitchTableIdMap(configParams.get("maxTableToReceiveTableMissFlowPerDpid")); + } + + private static Map<DatapathId, TableId> jsonToSwitchTableIdMap(String json) { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + Map<DatapathId, TableId> retValue = new HashMap<DatapathId, TableId>(); + + if (json == null || json.isEmpty()) { + return retValue; + } + + try { + try { + jp = f.createJsonParser(json); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) { + continue; + } + + DatapathId dpid; + try { + n = n.trim(); + dpid = DatapathId.of(n); + + TableId tablesToGetDefaultFlow; + String value = jp.getText(); + if (value != null && !value.isEmpty()) { + value = value.trim().toLowerCase(); + try { + tablesToGetDefaultFlow = TableId.of( + value.startsWith("0x") + ? Integer.parseInt(value.replaceFirst("0x", ""), 16) + : Integer.parseInt(value) + ); /* will throw exception if outside valid TableId number range */ + retValue.put(dpid, tablesToGetDefaultFlow); + log.info("Setting max table to receive table-miss flow to {} for DPID {}", + tablesToGetDefaultFlow.toString(), dpid.toString()); + } catch (IllegalArgumentException e) { /* catches both IllegalArgumentExcpt. and NumberFormatExcpt. */ + log.error("Invalid value of {} for max table to receive table-miss flow for DPID {}. Using default of {}.", value, dpid.toString()); + } + } + } catch (NumberFormatException e) { + log.error("Invalid DPID format {} for max table to receive table-miss flow for specific DPID. Using default for the intended DPID.", n); + } + } + } catch (IOException e) { + log.error("Using default for remaining DPIDs. JSON formatting error in max table to receive table-miss flow for DPID input String: {}", e); } + return retValue; } @Override diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index 13ce05be948ee7b2cc60665f69491cb92fbc4f18..2a4786bf21b600aa31c233d9f0378eee61d99948 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -23,7 +23,8 @@ org.sdnplatform.sync.internal.SyncManager.port=6642 net.floodlightcontroller.forwarding.Forwarding.match=vlan, mac, ip, transport net.floodlightcontroller.core.internal.FloodlightProvider.openflowPort=6653 net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE -net.floodlightcontroller.core.internal.OFSwitchManager.addDefaultSendToControllerFlowInTables='{"all":"0,1,2"}' +net.floodlightcontroller.core.internal.OFSwitchManager.defaultMaxTableToReceiveTableMissFlow=4 +net.floodlightcontroller.core.internal.OFSwitchManager.maxTableToReceiveTableMissFlowPerDpid={"00:00:00:00:00:00:00:01":"4","2":"4"} net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnInitialHandshakeAsMaster=YES net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnEachTransitionToMaster=YES net.floodlightcontroller.core.internal.OFSwitchManager.keyStorePath=/path/to/your/keystore-file.jks diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java index e7590df1c838b059a9bdfc33f4387bfc8aecc66b..f035adf653f336c244c064f522c1a23fa75fd3c5 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java @@ -66,6 +66,7 @@ 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; @@ -81,1085 +82,1101 @@ 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() { - } - } - - @Before - 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 { - 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 + 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() { + } + } + + @Before + 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 { + 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().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") + */ + @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().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(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); - expect(sw.getTables()).andStubReturn((short)0); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); - expectLastCall().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); - - 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") + 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(); + sw.write(anyObject(OFMessage.class)); + expectLastCall().anyTimes(); + sw.write(anyObject(Iterable.class)); + expectLastCall().anyTimes(); + expect(sw.getTables()).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.getMaxTableForTableMissFlow()).andReturn(TableId.ZERO).times(2); + } + } + + 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); + + 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(); + sw.write(anyObject(OFMessage.class)); + expectLastCall().anyTimes(); + sw.write(anyObject(Iterable.class)); + expectLastCall().anyTimes(); + expect(sw.getTables()).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(2); + } + replay(sw); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + expectLastCall().once(); + replay(switchManager); + OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER); + + // sendMessageToHandler will verify and rest controller mock + 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(); + sw.write(anyObject(OFMessage.class)); + expectLastCall().anyTimes(); + sw.write(anyObject(Iterable.class)); + expectLastCall().anyTimes(); + expect(sw.getTables()).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.getMaxTableForTableMissFlow()).andReturn(TableId.ZERO).times(2); + } + 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); + // sendMessageToHandler will verify and rest controller mock + 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); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + expect(sw.getOFFactory()).andReturn(factory).anyTimes(); + sw.write(anyObject(OFMessage.class)); + expectLastCall().anyTimes(); + sw.write(anyObject(Iterable.class)); + expectLastCall().anyTimes(); + expect(sw.getTables()).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.getMaxTableForTableMissFlow()).andReturn(TableId.ZERO).times(2); + } + replay(sw); + + OFMessage m = factory.buildBarrierReply().build(); + + 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 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(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); - expect(sw.getTables()).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(); - replay(sw); - - reset(switchManager); - switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); - expectLastCall().once(); - replay(switchManager); - OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER); - - // sendMessageToHandler will verify and rest controller mock - 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") + 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 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(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); - expect(sw.getTables()).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(); - 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); - // sendMessageToHandler will verify and rest controller mock - 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") + 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 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); - assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); - expect(sw.getTables()).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(); - replay(sw); - - OFMessage m = factory.buildBarrierReply().build(); - - 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 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(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); - expect(sw.getTables()).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(); - 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); - } + 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(); + sw.write(anyObject(OFMessage.class)); + expectLastCall().anyTimes(); + sw.write(anyObject(Iterable.class)); + expectLastCall().anyTimes(); + expect(sw.getTables()).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.getMaxTableForTableMissFlow()).andReturn(TableId.ZERO).times(2); + 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); + } } diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java index 0a4ff826de222f4307ba98e4e83d2b305768206a..0de8b2b66bae472f183b8255429f7c40bf830bf9 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java @@ -258,6 +258,7 @@ public class OFSwitchManagerTest{ expect(sw.getStatus()).andReturn(SwitchStatus.MASTER).anyTimes(); sw.disconnect(); expectLastCall().once(); + expect(sw.getOFFactory()).andReturn(factory).once(); replay(sw, listener); // nothing recorded switchManager.switchAdded(sw); switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER);