OF13 UnitTesting for ChannelHandler and Role Changer

Change-Id: I7b46c8f118f194da80b108e17443031518b50a90
diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java
new file mode 100644
index 0000000..0e50d0c
--- /dev/null
+++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java
@@ -0,0 +1,1609 @@
+package net.floodlightcontroller.core.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.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.internal.OFChannelHandler.RoleRecvStatus;
+import net.floodlightcontroller.debugcounter.DebugCounter;
+import net.floodlightcontroller.debugcounter.IDebugCounterService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.easymock.EasyMock;
+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.
+ *
+ * @author Patrick liu (patrick.liu@huawei.com)
+ */
+public class OFChannelHandlerTest {
+    private Controller controller;
+    private IThreadPoolService threadPool;
+    private IDebugCounterService debugCounterService;
+    private OFChannelHandler handler;
+    private Channel channel;
+    private ChannelHandlerContext ctx;
+    private MessageEvent messageEvent;
+    private ChannelStateEvent channelStateEvent;
+    private ChannelPipeline pipeline;
+    private Capture<ExceptionEvent> exceptionEventCapture;
+    private Capture<List<OFMessage>> writeCapture;
+    private OFFeaturesReply featuresReply;
+    private Set<Integer> seenXids = null;
+    private OFSwitchImplBase 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);
+        threadPool = createMock(IThreadPoolService.class);
+        ctx = createMock(ChannelHandlerContext.class);
+        channelStateEvent = createMock(ChannelStateEvent.class);
+        channel = createMock(Channel.class);
+        messageEvent = createMock(MessageEvent.class);
+        exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL);
+        pipeline = createMock(ChannelPipeline.class);
+        writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL);
+        swImplBase = createMock(OFSwitchImplBase.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.getOFMessageFactory_10()).andReturn(factory10).anyTimes();
+        expect(controller.getOFMessageFactory_13()).andReturn(factory13).anyTimes();
+        expect(controller.addConnectedSwitch(2000,handler)).andReturn(true).anyTimes();
+        replay(controller);
+        handler = new OFChannelHandler(controller);
+        verify(controller);
+        reset(controller);
+
+        resetChannel();
+
+        // thread pool is usually not called, so start empty replay
+        replay(threadPool);
+
+        // 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(threadPool);
+        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<OFMessage> 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<OFMessage> 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<OFMessage> messages)
+            throws Exception {
+        setupMessageEvent(messages);
+
+        // mock controller
+        controller.flushAll();
+        expectLastCall().anyTimes();
+        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<OFMessage> getMessagesFromCapture() {
+        List<OFMessage> msgs = new ArrayList<OFMessage>();
+
+        assertTrue("No write on channel was captured",
+                writeCapture.hasCaptured());
+        List<List<OFMessage>> capturedVals = writeCapture.getValues();
+
+        for (List<OFMessage> 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<? extends Throwable> 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<OFMessage> msgs) {
+        if (seenXids == null)
+            seenXids = new HashSet<Integer>();
+        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);
+        }
+    }
+
+
+    @Test
+    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<OFMessage> 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<OFMessage> 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.<OFMessage>singletonList(featuresReply));
+        List<OFMessage> 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.<OFMessage>singletonList(cr));
+
+        List<OFMessage> 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.<OFMessage>singletonList(sr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr,ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+        controller.submitRegistryRequest(1000);
+        expectLastCall().once();
+        controller.flushAll();
+        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),
+                    EasyMock.<FloodlightContext>anyObject());
+            expectLastCall().anyTimes();
+        }
+
+        swImplBase.setOFVersion(ofVersion);
+        expectLastCall().once();
+        swImplBase.setConnected(true);
+        expectLastCall().once();
+        swImplBase.setChannel(channel);
+        expectLastCall().once();
+        swImplBase.setFloodlightProvider(controller);
+        expectLastCall().once();
+        swImplBase.setThreadPoolService(controller.getThreadPoolService());
+        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<OFMessage> 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());
+    }
+
+    @Test
+    /**
+     * Move the channel from scratch to
+     *  WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state
+     * Builds on moveToWaitInitialRole()
+     */
+    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.<OFMessage>singletonList(rr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+
+        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());
+    }
+
+    @Test
+    /**
+     * 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.
+     */
+    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.<OFMessage>singletonList(rr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+        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());
+    }
+
+
+    @Test
+    /**
+     * 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.
+     */
+    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.<OFMessage>singletonList(rr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+        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());
+    }
+    @Test
+    /** Move the channel from scratch to
+     *  WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state
+     * Builds on moveToWaitSubHandshake()
+     */
+    public void moveToEqualViaWaitSubHandshake()
+            throws Exception {
+        moveToWaitSubHandshake();
+
+        long xid = 2000;
+        resetChannel();
+        replay(channel);
+
+        // build the stats reply
+        OFStatsReply sr = createDescriptionStatsReply();
+
+        setupMessageEvent(Collections.<OFMessage>singletonList(sr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+        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());
+    }
+
+    @Test
+    /** Move the channel from scratch to
+     *  WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state
+     * Builds on moveToWaitSubHandshake()
+     */
+    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.<OFMessage>singletonList(sr));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+        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
+    /**
+     * Test the behavior in WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state.
+     * ECHO_REQUEST message received case
+     */
+    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.<OFMessage>singletonList(er));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+
+        replay(controller);
+
+        expect(swImplBase.getStringId())
+        .andReturn(null).anyTimes();
+        expect(swImplBase.getNextTransactionId())
+        .andReturn((int)xid).anyTimes();
+
+        replay(swImplBase);
+
+        handler.messageReceived(ctx, messageEvent);
+
+        List<OFMessage> 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),
+                    EasyMock.<FloodlightContext>anyObject());
+            expectLastCall().anyTimes();
+        }
+        replay(swImplBase);
+
+        handler.sendRoleRequest(role, expectation);
+
+        if (supportsNxRole != null && supportsNxRole) {
+            List<OFMessage> 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.getThreadPoolService())
+        .andReturn(threadPool).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.<FloodlightContext>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.<OFMessage>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.<OFMessage>singletonList(reply));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+        controller.transitionToMasterSwitch(1000);
+        expectLastCall().once();
+
+        controller.flushAll();
+        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),
+                EasyMock.<FloodlightContext>anyObject());
+        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),
+                    EasyMock.<FloodlightContext>anyObject());
+            expectLastCall().anyTimes();
+        }
+        replay(swImplBase);
+
+        handler.sendRoleRequest(Role.SLAVE, RoleRecvStatus.MATCHED_SET_ROLE);
+
+        List<OFMessage> msgs = getMessagesFromCapture();
+        assertEquals(1, msgs.size());
+        verifyNiciraMessage((OFExperimenter)msgs.get(0));
+
+
+        OFMessage reply = getRoleReply(xid, Role.SLAVE);
+        OFStatsReply sr = createDescriptionStatsReply();
+        setupMessageEvent(Collections.<OFMessage>singletonList(reply));
+
+        // mock controller
+        reset(controller);
+        reset(swImplBase);
+
+        controller.transitionToEqualSwitch(1000);
+        expectLastCall().once();
+        expect(controller.getOFSwitchInstance((OFDescStatsReply)sr, ofVersion))
+        .andReturn(swImplBase).anyTimes();
+        expect(controller.getThreadPoolService())
+        .andReturn(threadPool).anyTimes();
+        expect(controller.getDebugCounter())
+        .andReturn(debugCounterService).anyTimes();
+
+        controller.flushAll();
+        expectLastCall().once();
+        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.<OFMessage>singletonList(pi));
+
+        reset(controller);
+        controller.handleMessage(swImplBase, pi, null);
+        expectLastCall().once();
+        controller.flushAll();
+        expectLastCall().once();
+
+        replay(controller);
+
+
+        // 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<OFPortDesc> ports = new ArrayList<OFPortDesc>();
+            // 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<PortChangeEvent> events =
+                    new LinkedHashSetWrapper<PortChangeEvent>();
+            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.<OFMessage>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);
+    }
+}