From 3b554cff3b4e3a31ba1c3d1fdb93efe6efe6702d Mon Sep 17 00:00:00 2001 From: alshabib Date: Mon, 18 Aug 2014 11:19:50 -0500 Subject: [PATCH] added unit tests Change-Id: Ic743a05b907456e1414a9bc587696de631d3f382 commented the controller test class Change-Id: Id9afb0e60afb3839f65a41b04e7129db1010ca19 added OFChannelHandler tests Change-Id: I45169988f0e4242a6e1c0baf34b1104f53873bb7 --- of/ctl/conf/checkstyle/sun_checks.xml | 2 +- of/ctl/pom.xml | 1 - .../java/net/onrc/onos/of/ctl/IOFSwitch.java | 30 +- .../debugcounter/IDebugCounterService.java | 2 +- .../onrc/onos/of/ctl/internal/Controller.java | 32 +- .../of/ctl/internal/OFChannelHandler.java | 2 +- .../onos/of/ctl/internal/ControllerTest.java | 167 ++ .../of/ctl/internal/OFChannelHandlerTest.java | 1569 +++++++++++++++++ 8 files changed, 1744 insertions(+), 61 deletions(-) create mode 100644 of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/ControllerTest.java create mode 100644 of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/OFChannelHandlerTest.java diff --git a/of/ctl/conf/checkstyle/sun_checks.xml b/of/ctl/conf/checkstyle/sun_checks.xml index 7dd9d5779d..b1404c1d41 100644 --- a/of/ctl/conf/checkstyle/sun_checks.xml +++ b/of/ctl/conf/checkstyle/sun_checks.xml @@ -44,7 +44,7 @@ - + -XX:MaxPermSize=256m -XX:-UseSplitVerifier false - net.onrc.onos.core.util.IntegrationTest diff --git a/of/ctl/src/main/java/net/onrc/onos/of/ctl/IOFSwitch.java b/of/ctl/src/main/java/net/onrc/onos/of/ctl/IOFSwitch.java index f274a28136..8015f3f175 100644 --- a/of/ctl/src/main/java/net/onrc/onos/of/ctl/IOFSwitch.java +++ b/of/ctl/src/main/java/net/onrc/onos/of/ctl/IOFSwitch.java @@ -69,8 +69,6 @@ public interface IOFSwitch { /** * Writes to the OFMessage to the output stream. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners * * @param m * @param bc @@ -80,8 +78,6 @@ public interface IOFSwitch { /** * Writes the list of messages to the output stream. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners. * * @param msglist * @param bc @@ -333,8 +329,7 @@ public interface IOFSwitch { /** * Add or modify a switch port. This is called by the core controller - * code in response to a OFPortStatus message. It should not typically be - * called by other floodlight applications. + * code in response to a OFPortStatus message. * * OFPPR_MODIFY and OFPPR_ADD will be treated as equivalent. The OpenFlow * spec is not clear on whether portNames are portNumbers are considered @@ -402,29 +397,6 @@ public interface IOFSwitch { public OrderedCollection setPorts(Collection ports); -// XXX S The odd use of providing an API call to 'set ports' (above) would -// logically suggest that there should be a way to delete or unset the ports. -// Right now we forbid this. We should probably not use setPorts too. -// -// /** -// * Delete a port for the switch. This is called by the core controller -// * code in response to a OFPortStatus message. It should not typically be -// * called by other floodlight applications. -// * -// * @param portNumber -// */ -// public void deletePort(short portNumber); -// -// /** -// * Delete a port for the switch. This is called by the core controller -// * code in response to a OFPortStatus message. It should not typically be -// * called by other floodlight applications. -// * -// * @param portName -// */ -// public void deletePort(String portName); - - //******************************************* // IOFSwitch object attributes //************************ diff --git a/of/ctl/src/main/java/net/onrc/onos/of/ctl/debugcounter/IDebugCounterService.java b/of/ctl/src/main/java/net/onrc/onos/of/ctl/debugcounter/IDebugCounterService.java index 622f647525..81a80b1280 100644 --- a/of/ctl/src/main/java/net/onrc/onos/of/ctl/debugcounter/IDebugCounterService.java +++ b/of/ctl/src/main/java/net/onrc/onos/of/ctl/debugcounter/IDebugCounterService.java @@ -127,7 +127,7 @@ public interface IDebugCounterService { /** * Flush all thread-local counter values (from the current thread) * to the global counter store. This method is not intended for use by any - * module. It's typical usage is from floodlight core and it is meant + * module. It's typical usage is from core and it is meant * to flush those counters that are updated in the packet-processing pipeline, * typically with the 'updateCounterNoFlush" methods in IDebugCounter. */ diff --git a/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java b/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java index 05a841d316..84f090a1c7 100644 --- a/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java +++ b/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java @@ -144,8 +144,8 @@ public class Controller { + "activated: dpid {}. Found in activeMaster: {} " + "Found in activeEqual: {}. Aborting ..", new Object[] { HexString.toHexString(dpid), - (activeMasterSwitches.get(dpid) == null) ? 'Y' : 'N', - (activeEqualSwitches.get(dpid) == null) ? 'Y' : 'N'}); + (activeMasterSwitches.get(dpid) == null) ? 'N' : 'Y', + (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y'}); counters.switchWithSameDpidActivated.updateCounterWithFlush(); return false; } @@ -372,17 +372,13 @@ public class Controller { HexString.toHexString(dpidLong)); return; } - if (h.controlRequested) { + if (registryService != null && h.controlRequested) { + //TODO the above is not good for testing need to change controlrequest to method call. registryService.releaseControl(dpidLong); } } - - // *************** - // IFloodlightProviderService - // *************** - // FIXME: remove this method public Map getSwitches() { return getMasterSwitches(); @@ -472,11 +468,6 @@ public class Controller { } - public void setAlwaysClearFlowsOnSwAdd(boolean value) { - this.alwaysClearFlowsOnSwAdd = value; - } - - public InstanceId getInstanceId() { return instanceId; } @@ -587,15 +578,6 @@ public class Controller { this.counters = new Counters(); this.multiCacheLock = new Object(); - - String option = configParams.get("flushSwitchesOnReconnect"); - if (option != null && option.equalsIgnoreCase("true")) { - this.setAlwaysClearFlowsOnSwActivate(true); - log.info("Flush switches on reconnect -- Enabled."); - } else { - this.setAlwaysClearFlowsOnSwActivate(false); - log.info("Flush switches on reconnect -- Disabled"); - } } /** @@ -819,12 +801,6 @@ public class Controller { // Utility methods // ************** - - public void setAlwaysClearFlowsOnSwActivate(boolean value) { - //this.alwaysClearFlowsOnSwActivate = value; - // XXX S need to be a little more careful about this - } - public Map getMemory() { Map m = new HashMap(); Runtime runtime = Runtime.getRuntime(); diff --git a/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java b/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java index 40de530604..3b18a59596 100644 --- a/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java +++ b/of/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java @@ -785,7 +785,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { * description stats message, we: * - use the switch driver to bind the switch and get an IOFSwitch instance * - setup the IOFSwitch instance - * - add switch to FloodlightProvider(Controller) and send the initial role + * - add switch controller and send the initial role * request to the switch. * Next state: WAIT_INITIAL_ROLE * In the typical case, where switches support role request messages diff --git a/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/ControllerTest.java b/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/ControllerTest.java new file mode 100644 index 0000000000..ea7d8847ee --- /dev/null +++ b/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/ControllerTest.java @@ -0,0 +1,167 @@ +/** + * Copyright 2011, Big Switch Networks, Inc. + * Originally created by David Erickson, Stanford University + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ + +package net.onrc.onos.of.ctl.internal; + +import junit.framework.TestCase; +import net.onrc.onos.of.ctl.IOFSwitch; + +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +public class ControllerTest extends TestCase { + + private Controller controller; + private IOFSwitch sw; + private OFChannelHandler h; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + sw = EasyMock.createMock(IOFSwitch.class); + h = EasyMock.createMock(OFChannelHandler.class); + controller = new Controller(); + ControllerRunThread t = new ControllerRunThread(); + t.start(); + /* + * Making sure the thread is properly started before making calls + * to controller class. + */ + Thread.sleep(200); + } + + /** + * Starts the base mocks used in these tests. + */ + private void startMocks() { + EasyMock.replay(sw, h); + } + + /** + * Reset the mocks to a known state. + * Automatically called after tests. + */ + @After + private void resetMocks() { + EasyMock.reset(sw); + } + + /** + * Fetches the controller instance. + * @return the controller + */ + public Controller getController() { + return controller; + } + + /** + * Run the controller's main loop so that updates are processed. + */ + protected class ControllerRunThread extends Thread { + @Override + public void run() { + controller.openFlowPort = 0; // Don't listen + controller.activate(); + } + } + + /** + * Verify that we are able to add a switch that just connected. + * If it already exists then this should fail + * + * @throws Exception error + */ + @Test + public void testAddConnectedSwitches() throws Exception { + startMocks(); + assertTrue(controller.addConnectedSwitch(0, h)); + assertFalse(controller.addConnectedSwitch(0, h)); + } + + /** + * Add active master but cannot re-add active master. + * @throws Exception an error occurred. + */ + @Test + public void testAddActivatedMasterSwitch() throws Exception { + startMocks(); + controller.addConnectedSwitch(0, h); + assertTrue(controller.addActivatedMasterSwitch(0, sw)); + assertFalse(controller.addActivatedMasterSwitch(0, sw)); + } + + /** + * Tests that an activated switch can be added but cannot be re-added. + * + * @throws Exception an error occurred + */ + @Test + public void testAddActivatedEqualSwitch() throws Exception { + startMocks(); + controller.addConnectedSwitch(0, h); + assertTrue(controller.addActivatedEqualSwitch(0, sw)); + assertFalse(controller.addActivatedEqualSwitch(0, sw)); + } + + /** + * Move an equal switch to master. + * @throws Exception an error occurred + */ + @Test + public void testTranstitionToMaster() throws Exception { + startMocks(); + controller.addConnectedSwitch(0, h); + controller.addActivatedEqualSwitch(0, sw); + controller.transitionToMasterSwitch(0); + assertNotNull(controller.getMasterSwitch(0)); + } + + /** + * Transition a master switch to equal state. + * @throws Exception an error occurred + */ + @Test + public void testTranstitionToEqual() throws Exception { + startMocks(); + controller.addConnectedSwitch(0, h); + controller.addActivatedMasterSwitch(0, sw); + controller.transitionToEqualSwitch(0); + assertNotNull(controller.getEqualSwitch(0)); + } + + /** + * Remove the switch from the controller instance. + * @throws Exception an error occurred + */ + @Test + public void testRemoveSwitch() throws Exception { + sw.cancelAllStatisticsReplies(); + EasyMock.expectLastCall().once(); + sw.setConnected(false); + EasyMock.expectLastCall().once(); + startMocks(); + controller.addConnectedSwitch(0, h); + controller.addActivatedMasterSwitch(0, sw); + controller.removeConnectedSwitch(0); + assertNull(controller.getSwitch(0)); + EasyMock.verify(sw, h); + } +} diff --git a/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/OFChannelHandlerTest.java b/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/OFChannelHandlerTest.java new file mode 100644 index 0000000000..0213cce451 --- /dev/null +++ b/of/ctl/src/test/java/net/onrc/onos/of/ctl/internal/OFChannelHandlerTest.java @@ -0,0 +1,1569 @@ +package net.onrc.onos.of.ctl.internal; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.onrc.onos.of.ctl.IOFSwitch; +import net.onrc.onos.of.ctl.Role; +import net.onrc.onos.of.ctl.debugcounter.DebugCounter; +import net.onrc.onos.of.ctl.debugcounter.IDebugCounterService; +import net.onrc.onos.of.ctl.internal.OFChannelHandler.RoleRecvStatus; + +import org.easymock.Capture; +import org.easymock.CaptureType; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFExperimenter; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFHelloElem; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFSetConfig; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.U32; + +/** + * Channel handler deals with the switch connection and dispatches + * switch messages to the appropriate locations. These Unit Testing cases + * test the channeler state machine and role changer. In the first release, + * we will focus on OF version 1.0. we will add the testing case for + * version 1.3 later. + */ +public class OFChannelHandlerTest { + private Controller controller; + private IDebugCounterService debugCounterService; + private OFChannelHandler handler; + private Channel channel; + private ChannelHandlerContext ctx; + private MessageEvent messageEvent; + private ChannelStateEvent channelStateEvent; + private ChannelPipeline pipeline; + private Capture exceptionEventCapture; + private Capture> writeCapture; + private OFFeaturesReply featuresReply; + private Set seenXids = null; + private IOFSwitch swImplBase; + private OFVersion ofVersion = OFVersion.OF_10; + private OFFactory factory13; + private OFFactory factory10; + private OFFactory factory; + + @Before + public void setUp() throws Exception { + controller = createMock(Controller.class); + ctx = createMock(ChannelHandlerContext.class); + channelStateEvent = createMock(ChannelStateEvent.class); + channel = createMock(Channel.class); + messageEvent = createMock(MessageEvent.class); + exceptionEventCapture = new Capture(CaptureType.ALL); + pipeline = createMock(ChannelPipeline.class); + writeCapture = new Capture>(CaptureType.ALL); + swImplBase = createMock(IOFSwitch.class); + seenXids = null; + factory13 = OFFactories.getFactory(OFVersion.OF_13); + factory10 = OFFactories.getFactory(OFVersion.OF_10); + factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10; + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + debugCounterService = new DebugCounter(); + Controller.Counters counters = + new Controller.Counters(); + counters.createCounters(debugCounterService); + expect(controller.getCounters()).andReturn(counters).anyTimes(); + expect(controller.getOFMessageFactory10()).andReturn(factory10) + .anyTimes(); + expect(controller.getOFMessageFactory13()).andReturn(factory13) + .anyTimes(); + expect(controller.addConnectedSwitch(2000, handler)).andReturn(true) + .anyTimes(); + replay(controller); + handler = new OFChannelHandler(controller); + verify(controller); + reset(controller); + + resetChannel(); + + // replay controller. Reset it if you need more specific behavior + replay(controller); + + // replay switch. Reset it if you need more specific behavior + replay(swImplBase); + + // Mock ctx and channelStateEvent + expect(ctx.getChannel()).andReturn(channel).anyTimes(); + expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); + replay(ctx, channelStateEvent); + + /* Setup an exception event capture on the channel. Right now + * we only expect exception events to be send up the channel. + * However, it's easy to extend to other events if we need it + */ + pipeline.sendUpstream(capture(exceptionEventCapture)); + expectLastCall().anyTimes(); + replay(pipeline); + featuresReply = (OFFeaturesReply) buildOFMessage(OFType.FEATURES_REPLY); + } + + @After + public void tearDown() { + /* ensure no exception was thrown */ + if (exceptionEventCapture.hasCaptured()) { + Throwable ex = exceptionEventCapture.getValue().getCause(); + throw new AssertionError("Unexpected exception: " + + ex.getClass().getName() + "(" + ex + ")"); + } + assertFalse("Unexpected messages have been captured", + writeCapture.hasCaptured()); + // verify all mocks. + verify(channel); + verify(messageEvent); + verify(controller); + verify(ctx); + verify(channelStateEvent); + verify(pipeline); + verify(swImplBase); + + } + + /** + * Reset the channel mock and set basic method call expectations. + * + **/ + void resetChannel() { + reset(channel); + expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); + expect(channel.getRemoteAddress()).andReturn(null).anyTimes(); + } + + /** + * reset, setup, and replay the messageEvent mock for the given + * messages. + */ + void setupMessageEvent(List messages) { + reset(messageEvent); + expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); + replay(messageEvent); + } + + /** + * reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler. + * + * This method will reset, start replay on controller, and then verify + */ + void sendMessageToHandlerWithControllerReset(List messages) + throws Exception { + verify(controller); + reset(controller); + + sendMessageToHandlerNoControllerReset(messages); + } + + /** + * reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler. + * + * This method will start replay on controller, and then verify + */ + void sendMessageToHandlerNoControllerReset(List messages) + throws Exception { + setupMessageEvent(messages); + + expect(controller.addConnectedSwitch(1000, handler)) + .andReturn(true).anyTimes(); + replay(controller); + + handler.messageReceived(ctx, messageEvent); + verify(controller); + } + + /** + * Extract the list of OFMessages that was captured by the Channel.write() + * capture. Will check that something was actually captured first. We'll + * collapse the messages from multiple writes into a single list of + * OFMessages. + * Resets the channelWriteCapture. + */ + List getMessagesFromCapture() { + List msgs = new ArrayList(); + + assertTrue("No write on channel was captured", + writeCapture.hasCaptured()); + List> capturedVals = writeCapture.getValues(); + + for (List oneWriteList: capturedVals) { + msgs.addAll(oneWriteList); + } + writeCapture.reset(); + return msgs; + } + + + /** + * Verify that the given exception event capture (as returned by + * getAndInitExceptionCapture) has thrown an exception of the given + * expectedExceptionClass. + * Resets the capture + */ + void verifyExceptionCaptured( + Class expectedExceptionClass) { + assertTrue("Excpected exception not thrown", + exceptionEventCapture.hasCaptured()); + Throwable caughtEx = exceptionEventCapture.getValue().getCause(); + assertEquals(expectedExceptionClass, caughtEx.getClass()); + exceptionEventCapture.reset(); + } + + /** + * 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 msgs) { + if (seenXids == null) { + seenXids = new HashSet(); + } + for (OFMessage m: msgs) { + int xid = (int) m.getXid(); + assertTrue("Xid in messags is 0", xid != 0); + assertFalse("Xid " + xid + " has already been used", + seenXids.contains(xid)); + seenXids.add(xid); + } + } + + + + public void testInitState() throws Exception { + OFMessage m = buildOFMessage(OFType.HELLO); + + expect(messageEvent.getMessage()).andReturn(null); + replay(channel, messageEvent); + + // We don't expect to receive /any/ messages in init state since + // channelConnected moves us to a different state + sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); + + verifyExceptionCaptured(SwitchStateException.class); + assertEquals(OFChannelHandler.ChannelState.INIT, + handler.getStateForTesting()); + } + + /** + * move the channel from scratch to WAIT_HELLO state. + * + */ + @Test + public void moveToWaitHello() throws Exception { + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).once(); + replay(channel); + // replay unused mocks + replay(messageEvent); + + handler.channelConnected(ctx, channelStateEvent); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.HELLO, msgs.get(0).getType()); + assertEquals(OFChannelHandler.ChannelState.WAIT_HELLO, + handler.getStateForTesting()); + //Should verify that the Hello received from the controller + //is ALWAYS OF1.3 hello regardless of the switch version + assertEquals(OFVersion.OF_13, msgs.get(0).getVersion()); + verifyUniqueXids(msgs); + } + + + /** + * Move the channel from scratch to WAIT_FEATURES_REPLY state. + * Builds on moveToWaitHello(). + * adds testing for WAIT_HELLO state. + */ + @Test + public void moveToWaitFeaturesReply() throws Exception { + moveToWaitHello(); + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + OFMessage hello = buildOFMessage(OFType.HELLO); + sendMessageToHandlerWithControllerReset(Collections.singletonList(hello)); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); + if (ofVersion == OFVersion.OF_10) { + assertEquals(OFVersion.OF_10, msgs.get(0).getVersion()); + } + verifyUniqueXids(msgs); + + assertEquals(OFChannelHandler.ChannelState.WAIT_FEATURES_REPLY, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to WAIT_CONFIG_REPLY state. + * Builds on moveToWaitFeaturesReply. + * adds testing for WAIT_FEATURES_REPLY state. + */ + @Test + public void moveToWaitConfigReply() throws Exception { + moveToWaitFeaturesReply(); + + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + sendMessageToHandlerWithControllerReset(Collections.singletonList(featuresReply)); + List msgs = getMessagesFromCapture(); + assertEquals(3, msgs.size()); + assertEquals(OFType.SET_CONFIG, msgs.get(0).getType()); + OFSetConfig sc = (OFSetConfig) msgs.get(0); + assertEquals((short) 0xffff, sc.getMissSendLen()); + assertEquals(OFType.BARRIER_REQUEST, msgs.get(1).getType()); + assertEquals(OFType.GET_CONFIG_REQUEST, msgs.get(2).getType()); + verifyUniqueXids(msgs); + assertEquals(OFChannelHandler.ChannelState.WAIT_CONFIG_REPLY, + handler.getStateForTesting()); + } + + /** + * 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(); + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + OFGetConfigReply cr = (OFGetConfigReply) buildOFMessage(OFType.GET_CONFIG_REPLY); + + sendMessageToHandlerWithControllerReset(Collections.singletonList(cr)); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.STATS_REQUEST, msgs.get(0).getType()); + OFStatsRequest sr = (OFStatsRequest) msgs.get(0); + assertEquals(OFStatsType.DESC, sr.getStatsType()); + verifyUniqueXids(msgs); + assertEquals(OFChannelHandler.ChannelState.WAIT_DESCRIPTION_STAT_REPLY, + handler.getStateForTesting()); + } + + + private OFStatsReply createDescriptionStatsReply() throws IOException { + OFStatsReply sr = (OFStatsReply) buildOFMessage(OFType.STATS_REPLY); + return sr; + } + + /** + * Move the channel from scratch to WAIT_INITIAL_ROLE state. + * for a switch that does not have a sub-handshake. + * Builds on moveToWaitDescriptionStatReply(). + * adds testing for WAIT_DESCRIPTION_STAT_REPLY state. + * + */ + @Test + public void moveToWaitInitialRole() + throws Exception { + moveToWaitDescriptionStatReply(); + + long xid = 2000; + + // build the stats reply + OFStatsReply sr = createDescriptionStatsReply(); + + resetChannel(); + replay(channel); + + setupMessageEvent(Collections.singletonList(sr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + controller.submitRegistryRequest(1000); + expectLastCall().once(); + replay(controller); + + //TODO: With the description stats message you are sending in the test, + //you will end up with an OFSwitchImplBase object + //which by default does NOT support the nicira role messages. + //If you wish to test the case where Nicira role messages are supported, + //then make a comment here that states that this is different + //from the default behavior of switchImplbase /or/ + //send the right desc-stats (for example send what is expected from OVS 1.0) + + if (ofVersion == OFVersion.OF_10) { + expect(swImplBase.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(true).once(); + + swImplBase.write(capture(writeCapture)); + expectLastCall().anyTimes(); + } + + swImplBase.setOFVersion(ofVersion); + expectLastCall().once(); + swImplBase.setConnected(true); + expectLastCall().once(); + swImplBase.setChannel(channel); + expectLastCall().once(); + swImplBase.setDebugCounterService(controller.getDebugCounter()); + expectLastCall().once(); + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + swImplBase.setRole(Role.EQUAL); + expectLastCall().once(); + + expect(swImplBase.getNextTransactionId()) + .andReturn((int) xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + + swImplBase.setFeaturesReply(featuresReply); + expectLastCall().once(); + swImplBase.setPortDescReply((OFPortDescStatsReply) null); + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.EXPERIMENTER, msgs.get(0).getType()); + verifyNiciraMessage((OFExperimenter) msgs.get(0)); + + verify(controller); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to. + * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state. + * Builds on moveToWaitInitialRole(). + */ + @Test + public void moveToWaitSubHandshake() + throws Exception { + moveToWaitInitialRole(); + + int xid = 2000; + resetChannel(); + replay(channel); + + reset(swImplBase); + // Set the role + setupSwitchSendRoleRequestAndVerify(true, xid, Role.SLAVE); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // build the stats reply + OFStatsReply sr = createDescriptionStatsReply(); + OFMessage rr = getRoleReply(xid, Role.SLAVE); + setupMessageEvent(Collections.singletonList(rr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + swImplBase.setRole(Role.SLAVE); + expectLastCall().once(); + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + swImplBase.startDriverHandshake(); + expectLastCall().once(); + + //when this flag is false, state machine will move to + //WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(false).once(); + + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to WAIT_INITIAL_ROLE state, + * then move the channel to EQUAL state based on the switch Role. + * This test basically test the switch with role support. + * Builds on moveToWaitInitialRole(). + * + * In WAIT_INITIAL_ROLE state, when any messages (except ECHO_REQUEST + * and PORT_STATUS), state machine will transit to MASTER or + * EQUAL state based on the switch role. + */ + @Test + public void moveToSlaveWithHandshakeComplete() + throws Exception { + + moveToWaitInitialRole(); + + int xid = 2000; + resetChannel(); + replay(channel); + + reset(swImplBase); + // Set the role + setupSwitchSendRoleRequestAndVerify(true, xid, Role.SLAVE); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // build the stats reply + OFStatsReply sr = createDescriptionStatsReply(); + OFMessage rr = getRoleReply(xid, Role.SLAVE); + setupMessageEvent(Collections.singletonList(rr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + expect(controller.addActivatedEqualSwitch(1000, swImplBase)) + .andReturn(true).once(); + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + //consult the role in sw to determine the next state. + //in this testing case, we are testing that channel handler + // will move to EQUAL state when switch role is in SLAVE. + expect(swImplBase.getRole()).andReturn(Role.SLAVE).once(); + swImplBase.setRole(Role.SLAVE); + expectLastCall().once(); + + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + swImplBase.startDriverHandshake(); + expectLastCall().once(); + + //when this flag is true, don't need to move interim state + //WAIT_SWITCH_DRIVER_SUB_HANDSHAKE. channel handler will + //move to corresponding state after consulting the role in sw + //This is essentially the same test as the one above, + //except for this line + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(true).once(); + + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.EQUAL, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to WAIT_INITIAL_ROLE state, + * then to MASTERL state based on the switch Role. + * This test basically test the switch with role support. + * Builds on moveToWaitInitialRole(). + * + * In WAIT_INITIAL_ROLE state, when any messages (except ECHO_REQUEST + * and PORT_STATUS), state machine will transit to MASTER or + * EQUAL state based on the switch role. + */ + @Test + public void moveToMasterWithHandshakeComplete() + throws Exception { + + moveToWaitInitialRole(); + + int xid = 2000; + resetChannel(); + replay(channel); + + reset(swImplBase); + // Set the role + setupSwitchSendRoleRequestAndVerify(true, xid, Role.MASTER); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // build the stats reply + OFStatsReply sr = createDescriptionStatsReply(); + OFMessage rr = getRoleReply(xid, Role.MASTER); + setupMessageEvent(Collections.singletonList(rr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + expect(controller.addActivatedMasterSwitch(1000, swImplBase)) + .andReturn(true).once(); + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.MASTER).once(); + swImplBase.setRole(Role.MASTER); + expectLastCall().once(); + + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + swImplBase.startDriverHandshake(); + expectLastCall().once(); + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(true).once(); + + replay(swImplBase); + + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to + * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state. + * Builds on moveToWaitSubHandshake(). + */ + @Test + public void moveToEqualViaWaitSubHandshake() + throws Exception { + moveToWaitSubHandshake(); + + long xid = 2000; + resetChannel(); + replay(channel); + + // build the stats reply + OFStatsReply sr = createDescriptionStatsReply(); + + setupMessageEvent(Collections.singletonList(sr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + expect(controller.addActivatedEqualSwitch(1000, swImplBase)) + .andReturn(true).once(); + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.SLAVE).once(); + expect(swImplBase.getNextTransactionId()) + .andReturn((int) xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + + swImplBase.processDriverHandshakeMessage(sr); + expectLastCall().once(); + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(true).once(); + + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.EQUAL, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to + * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state. + * Builds on moveToWaitSubHandshake(). + */ + @Test + public void moveToMasterViaWaitSubHandshake() + throws Exception { + moveToWaitSubHandshake(); + + long xid = 2000; + resetChannel(); + replay(channel); + + // In this state, any messages except echo request, port status and + // error go to the switch sub driver handshake. Once the switch reports + // that its sub driver handshake is complete (#isDriverHandshakeComplete + // return true) then the channel handle consults the switch role and + // moves the state machine to the appropriate state (MASTER or EQUALS). + // In this test we expect the state machine to end up in MASTER state. + OFStatsReply sr = createDescriptionStatsReply(); + + setupMessageEvent(Collections.singletonList(sr)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + expect(controller.addActivatedMasterSwitch(1000, swImplBase)) + .andReturn(true).once(); + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.MASTER).once(); + expect(swImplBase.getNextTransactionId()) + .andReturn((int) xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + + swImplBase.processDriverHandshakeMessage(sr); + expectLastCall().once(); + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(true).once(); + + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + verify(controller); + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + } + + /** + * Test the behavior in WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state. + * ECHO_REQUEST message received case. + */ + @Test + public void testWaitSwitchDriverSubhandshake() throws Exception { + moveToWaitSubHandshake(); + + long xid = 2000; + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + OFMessage er = buildOFMessage(OFType.ECHO_REQUEST); + + setupMessageEvent(Collections.singletonList(er)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFMessageFactory10()).andReturn(factory10); + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getNextTransactionId()) + .andReturn((int) xid).anyTimes(); + + replay(swImplBase); + + handler.messageReceived(ctx, messageEvent); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); + verifyUniqueXids(msgs); + assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, + handler.getStateForTesting()); + } + + /** + * Helper. + * Verify that the given OFMessage is a correct Nicira RoleRequest message. + */ + private void verifyNiciraMessage(OFExperimenter ofMessage) { + + int vendor = (int) ofMessage.getExperimenter(); + assertEquals(vendor, 0x2320); // magic number representing nicira + } + + /** + * 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 xid The xid to use in the role request + * @param role The role to send + * @throws IOException + */ + private void setupSwitchSendRoleRequestAndVerify(Boolean supportsNxRole, + int xid, + Role role) throws IOException { + + RoleRecvStatus expectation = RoleRecvStatus.MATCHED_SET_ROLE; + + expect(swImplBase.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(supportsNxRole).atLeastOnce(); + + if (supportsNxRole != null && supportsNxRole) { + expect(swImplBase.getNextTransactionId()).andReturn(xid).once(); + swImplBase.write(capture(writeCapture)); + expectLastCall().anyTimes(); + } + replay(swImplBase); + + handler.sendRoleRequest(role, expectation); + + if (supportsNxRole != null && supportsNxRole) { + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + verifyNiciraMessage((OFExperimenter) msgs.get(0)); + } + } + + /** + * 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 + */ + private void setupSwitchRoleChangeUnsupported(int xid, + Role role) { + boolean supportsNxRole = false; + RoleRecvStatus expectation = RoleRecvStatus.NO_REPLY; + reset(swImplBase); + expect(swImplBase.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 + swImplBase.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); + expectLastCall().anyTimes(); + + replay(swImplBase); + + handler.sendRoleRequest(role, expectation); + + verify(swImplBase); + } + + /* + * Return a Nicira RoleReply message for the given role. + */ + private OFMessage getRoleReply(long xid, Role role) { + + OFNiciraControllerRole nr = null; + + switch(role) { + case MASTER: + nr = OFNiciraControllerRole.ROLE_MASTER; + break; + case EQUAL: + nr = OFNiciraControllerRole.ROLE_SLAVE; + break; + case SLAVE: + nr = OFNiciraControllerRole.ROLE_SLAVE; + break; + default: //handled below + } + OFMessage m = factory10.buildNiciraControllerRoleReply() + .setRole(nr) + .setXid(xid) + .build(); + return m; + } + + /** + * Move the channel from scratch to MASTER state. + * Builds on moveToWaitInitialRole(). + * adds testing for WAIT_INITAL_ROLE state. + * + * This method tests the case that the switch does NOT support roles. + * In ONOS if the switch-driver says that nicira-role messages are not + * supported, then ONOS does NOT send role-request messages + * (see handleUnsentRoleMessage()) + */ + @Test + public void testInitialMoveToMasterNoRole() throws Exception { + int xid = 43; + // first, move us to WAIT_INITIAL_ROLE_STATE + + moveToWaitInitialRole(); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + OFStatsReply sr = createDescriptionStatsReply(); + + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + expect(controller.addActivatedMasterSwitch(1000, swImplBase)) + .andReturn(true).once(); + replay(controller); + + reset(swImplBase); + swImplBase.setRole(Role.MASTER); + expectLastCall().once(); + swImplBase.startDriverHandshake(); + expectLastCall().once(); + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(true).once(); + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.MASTER).once(); + + expect(swImplBase.getId()) + .andReturn(1000L).once(); + // Set the role + setupSwitchSendRoleRequestAndVerify(false, xid, Role.MASTER); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + } + + /** + * Move the channel from scratch to WAIT_INITIAL_ROLE state. + * Builds on moveToWaitInitialRole(). + * 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. + */ + /* TBD + @Test + public void testInitialMoveToMasterTimeout() throws Exception { + int timeout = 50; + handler.useRoleChangerWithOtherTimeoutForTesting(timeout); + int xid = 4343; + + // first, move us to WAIT_INITIAL_ROLE_STATE + + moveToWaitInitialRole(); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // prepare mocks and inject the role reply message + reset(swImplBase); + // Set the role + swImplBase.setRole(Role.MASTER); + expectLastCall().once(); + swImplBase.startDriverHandshake(); + expectLastCall().once(); + expect(swImplBase.isDriverHandshakeComplete()) + .andReturn(false).once(); + if (ofVersion == OFVersion.OF_10) { + expect(swImplBase.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(true).once(); + + swImplBase.write(capture(writeCapture), + EasyMock.anyObject()); + expectLastCall().anyTimes(); + } + expect(swImplBase.getNextTransactionId()).andReturn(xid).once(); + + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // Set the role + setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + OFMessage m = buildOFMessage(OFType.ECHO_REPLY); + + setupMessageEvent(Collections.singletonList(m)); + + Thread.sleep(timeout+5); + + verify(controller); + reset(controller); + + expect(controller.addActivatedMasterSwitch(1000, swImplBase)) + .andReturn(true).once(); + controller.flushAll(); + expectLastCall().once(); + + replay(controller); + + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + + } + + */ + /** + * 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 { + int xid = 44; + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + reset(swImplBase); + // Set the role + setupSwitchSendRoleRequestAndVerify(false, xid, Role.SLAVE); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + } + + /** + * 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 + */ + /* TBD + @Test + public void testInitialMoveToSlaveTimeout() throws Exception { + int timeout = 50; + handler.useRoleChangerWithOtherTimeoutForTesting(timeout); + int xid = 4444; + + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // Set the role + setupSwitchSendRoleRequestAndVerify(null, xid, Role.SLAVE); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + expectLastCall().once(); + sw.setRole(Role.SLAVE); + expectLastCall().once(); + sw.disconnectSwitch(); // Make sure we disconnect + expectLastCall().once(); + replay(sw); + + OFMessage m = buildOFMessage(OFType.ECHO_REPLY); + + Thread.sleep(timeout+5); + + sendMessageToHandlerWithControllerReset(Collections.singletonList(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; + reset(swImplBase); + replay(swImplBase); + + reset(controller); + replay(controller); + + // First, lets move the state to MASTER without role support + testInitialMoveToMasterNoRole(); + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + + // try to set master role again. should be a no-op + setupSwitchRoleChangeUnsupported(xid, Role.MASTER); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + + setupSwitchRoleChangeUnsupported(xid, Role.SLAVE); + //switch does not support role message. there is no role set + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + + } + + /** + * Move the channel to MASTER state. + * Expects that the channel is in MASTER or SLAVE state. + * + */ + public void changeRoleToMasterWithRequest() throws Exception { + int xid = 4242; + + assertTrue("This method can only be called when handler is in " + + "MASTER or SLAVE role", handler.isHandshakeComplete()); + + reset(swImplBase); + reset(controller); + // Set the role + setupSwitchSendRoleRequestAndVerify(true, xid, Role.MASTER); + + // prepare mocks and inject the role reply message + + reset(controller); + expect(controller.addActivatedMasterSwitch(1000, swImplBase)) + .andReturn(true).once(); + OFMessage reply = getRoleReply(xid, Role.MASTER); + + // sendMessageToHandler will verify and rest controller mock + + OFStatsReply sr = createDescriptionStatsReply(); + setupMessageEvent(Collections.singletonList(reply)); + + // mock controller + reset(controller); + reset(swImplBase); + + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + controller.transitionToMasterSwitch(1000); + expectLastCall().once(); + + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.EQUAL).atLeastOnce(); + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + + swImplBase.setRole(Role.MASTER); + expectLastCall().once(); + replay(swImplBase); + + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + } + + /** + * Move the channel to SLAVE state. + * Expects that the channel is in MASTER or SLAVE state. + * + */ + public void changeRoleToSlaveWithRequest() throws Exception { + int xid = 2323; + + assertTrue("This method can only be called when handler is in " + + "MASTER or SLAVE role", handler.isHandshakeComplete()); + + // Set the role + reset(controller); + reset(swImplBase); + + swImplBase.write(capture(writeCapture)); + expectLastCall().anyTimes(); + + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + + + if (ofVersion == OFVersion.OF_10) { + expect(swImplBase.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(true).once(); + + swImplBase.write(capture(writeCapture)); + expectLastCall().anyTimes(); + } + replay(swImplBase); + + handler.sendRoleRequest(Role.SLAVE, RoleRecvStatus.MATCHED_SET_ROLE); + + List msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + verifyNiciraMessage((OFExperimenter) msgs.get(0)); + + + OFMessage reply = getRoleReply(xid, Role.SLAVE); + OFStatsReply sr = createDescriptionStatsReply(); + setupMessageEvent(Collections.singletonList(reply)); + + // mock controller + reset(controller); + reset(swImplBase); + + controller.transitionToEqualSwitch(1000); + expectLastCall().once(); + expect(controller.getOFSwitchInstance((OFDescStatsReply) sr, ofVersion)) + .andReturn(swImplBase).anyTimes(); + + expect(controller.getDebugCounter()) + .andReturn(debugCounterService).anyTimes(); + + replay(controller); + + expect(swImplBase.getStringId()) + .andReturn(null).anyTimes(); + expect(swImplBase.getRole()).andReturn(Role.MASTER).atLeastOnce(); + expect(swImplBase.getNextTransactionId()) + .andReturn(xid).anyTimes(); + + // prepare mocks and inject the role reply message + swImplBase.setRole(Role.SLAVE); + expectLastCall().once(); + expect(swImplBase.getId()) + .andReturn(1000L).once(); + replay(swImplBase); + + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.EQUAL, + handler.getStateForTesting()); + } + + @Test + public void testMultiRoleChange1() throws Exception { + moveToMasterWithHandshakeComplete(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + } + + @Test + public void testMultiRoleChange2() throws Exception { + moveToSlaveWithHandshakeComplete(); + 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 + */ + /* TBD + @Test + public void testInitialRoleChangeOtherError() throws Exception { + int xid = 4343; + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + reset(swImplBase); + // Set the role + setupSwitchSendRoleRequestAndVerify(true, xid, Role.MASTER); + assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, + handler.getStateForTesting()); + + + // FIXME: shouldn't use ordinal(), but OFError is broken + + OFMessage err = factory.errorMsgs().buildBadActionErrorMsg() + .setCode(OFBadActionCode.BAD_LEN) + .setXid(2000) + .build(); + verify(swImplBase); + reset(swImplBase); + replay(swImplBase); + sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); + + verifyExceptionCaptured(SwitchStateException.class); + } + */ + /** + * Test dispatch of messages while in MASTER role. + */ + @Test + public void testMessageDispatchMaster() throws Exception { + + moveToMasterWithHandshakeComplete(); + + // Send packet in. expect dispatch + OFPacketIn pi = (OFPacketIn) + buildOFMessage(OFType.PACKET_IN); + setupMessageEvent(Collections.singletonList(pi)); + + reset(swImplBase); + swImplBase.handleMessage(pi); + expectLastCall().once(); + replay(swImplBase); + // send the description stats reply + handler.messageReceived(ctx, messageEvent); + + assertEquals(OFChannelHandler.ChannelState.MASTER, + handler.getStateForTesting()); + + verify(controller); + // TODO: many more to go + } + + /** + * Test port status message handling while MASTER. + * + */ + /* Patrick: TBD + @Test + public void testPortStatusMessageMaster() throws Exception { + long dpid = featuresReply.getDatapathId().getLong(); + testInitialMoveToMasterWithRole(); + List ports = new ArrayList(); + // A dummy port. + OFPortDesc p = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.ofInt(1)) + .build(); + ports.add(p); + + p.setName("Port1"); + p.setPortNumber((short)1); + + OFPortStatus ps = (OFPortStatus)buildOFMessage(OFType.PORT_STATUS); + ps.setDesc(p); + + // 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 events = + new LinkedHashSetWrapper(); + ImmutablePort p1 = ImmutablePort.create("eth1", (short)1); + ImmutablePort p2 = ImmutablePort.create("eth2", (short)2); + ImmutablePort p3 = ImmutablePort.create("eth3", (short)3); + ImmutablePort p4 = ImmutablePort.create("eth4", (short)4); + ImmutablePort p5 = ImmutablePort.create("eth5", (short)5); + 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()) { + ps.setReason(reason.getReasonCode()); + + reset(sw); + expect(sw.getId()).andReturn(dpid).anyTimes(); + + expect(sw.processOFPortStatus(ps)).andReturn(events).once(); + replay(sw); + + reset(controller); + controller.notifyPortChanged(sw, p1, PortChangeType.ADD); + controller.notifyPortChanged(sw, p2, PortChangeType.DELETE); + controller.notifyPortChanged(sw, p3, PortChangeType.UP); + controller.notifyPortChanged(sw, p4, PortChangeType.DOWN); + controller.notifyPortChanged(sw, p5, PortChangeType.OTHER_UPDATE); + sendMessageToHandlerNoControllerReset( + Collections.singletonList(ps)); + verify(sw); + verify(controller); + } + } + + */ + /** + * Build an OF message. + * @throws IOException + */ + private OFMessage buildOFMessage(OFType t) throws IOException { + OFMessage m = null; + switch (t) { + + case HELLO: + // The OF protocol requires us to start things off by sending the highest + // version of the protocol supported. + + // bitmap represents OF1.0 (ofp_version=0x01) and OF1.3 (ofp_version=0x04) + // see Sec. 7.5.1 of the OF1.3.4 spec + if (ofVersion == OFVersion.OF_13) { + U32 bitmap = U32.ofRaw(0x00000012); + OFHelloElem hem = factory13.buildHelloElemVersionbitmap() + .setBitmaps(Collections.singletonList(bitmap)) + .build(); + m = factory13.buildHello() + .setXid(2000) + .setElements(Collections.singletonList(hem)) + .build(); + } else { + m = factory10.buildHello() + .setXid(2000) + .build(); + } + break; + case FEATURES_REQUEST: + m = factory.buildFeaturesRequest() + .setXid(2000) + .build(); + break; + case FEATURES_REPLY: + + m = factory.buildFeaturesReply() + .setDatapathId(DatapathId.of(1000L)) + .setXid(2000) + .build(); + break; + case SET_CONFIG: + m = factory.buildSetConfig() + .setMissSendLen((short) 0xffff) + .setXid(2000) + .build(); + break; + case BARRIER_REQUEST: + m = factory.buildBarrierRequest() + .setXid(2000) + .build(); + break; + case GET_CONFIG_REQUEST: + m = factory.buildGetConfigRequest() + .setXid(2000) + .build(); + break; + case GET_CONFIG_REPLY: + m = factory.buildGetConfigReply() + .setMissSendLen((short) 0xffff) + .setXid(2000) + .build(); + break; + case STATS_REQUEST: + break; + case STATS_REPLY: + m = factory.buildDescStatsReply() + .setDpDesc("Datapath Description") + .setHwDesc("Hardware Secription") + .setMfrDesc("Manufacturer Desctiption") + .setSerialNum("Serial Number") + .setSwDesc("Software Desription") + .build(); + break; + case ECHO_REQUEST: + m = factory.buildEchoRequest() + .setXid(2000) + .build(); + break; + case FLOW_REMOVED: + break; + + case PACKET_IN: + m = factory.buildPacketIn() + .setReason(OFPacketInReason.NO_MATCH) + .setTotalLen(1500) + .setXid(2000) + .build(); + break; + case PORT_STATUS: + m = factory.buildPortStatus() + .setXid(2000) + .build(); + break; + + default: + m = factory.buildFeaturesRequest() + .setXid(2000) + .build(); + break; + } + + return (m); + } +}