package net.floodlightcontroller.core.internal; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent; import net.floodlightcontroller.core.IOFSwitch.PortChangeType; import net.floodlightcontroller.core.ImmutablePort; import net.floodlightcontroller.debugcounter.DebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.util.OrderedCollection; import net.floodlightcontroller.util.LinkedHashSetWrapper; 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.openflow.protocol.OFError; import org.openflow.protocol.OFError.OFBadRequestCode; import org.openflow.protocol.OFError.OFErrorType; import org.openflow.protocol.OFFeaturesReply; import org.openflow.protocol.OFGetConfigReply; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFPortStatus; import org.openflow.protocol.OFPortStatus.OFPortReason; import org.openflow.protocol.OFSetConfig; import org.openflow.protocol.OFStatisticsReply; import org.openflow.protocol.OFStatisticsRequest; import org.openflow.protocol.OFType; import org.openflow.protocol.OFVendor; import org.openflow.protocol.factory.BasicFactory; import org.openflow.protocol.statistics.OFDescriptionStatistics; import org.openflow.protocol.statistics.OFStatisticsType; import org.openflow.util.HexString; import org.openflow.vendor.nicira.OFNiciraVendorData; import org.openflow.vendor.nicira.OFRoleReplyVendorData; import org.openflow.vendor.nicira.OFRoleRequestVendorData; import static org.easymock.EasyMock.*; import static org.junit.Assert.*; public class OFChannelHandlerTest { private static final short CORE_PRIORITY = 4242; private static final short ACCESS_PRIORITY = 42; 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 IStorageSourceService storageSource; private IResultSet storageResultSet; private IOFSwitch sw; @Before public void setUpFeaturesReply() { featuresReply = (OFFeaturesReply)BasicFactory.getInstance() .getMessage(OFType.FEATURES_REPLY); featuresReply.setDatapathId(0x42L); featuresReply.setBuffers(1); featuresReply.setTables((byte)1); featuresReply.setCapabilities(3); featuresReply.setActions(4); List<OFPhysicalPort> ports = new ArrayList<OFPhysicalPort>(); // A dummy port. OFPhysicalPort p = new OFPhysicalPort(); p.setName("Eth1"); p.setPortNumber((short)1); ports.add(p); featuresReply.setPorts(ports); } @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); sw = createMock(IOFSwitch.class); seenXids = null; // 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(); 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(sw); // 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); } @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(sw); } /** 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().atLeastOnce(); 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 = 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 { // Message event needs to be list expect(messageEvent.getMessage()).andReturn(null); replay(channel, messageEvent); handler.messageReceived(ctx, messageEvent); verify(channel, messageEvent); verifyExceptionCaptured(AssertionError.class); // Message event needs to be list *of OFMessages* // TODO: messageReceived can throw exceptions that don't get send // back into the channel (e.g., the ClassCastException below). // Do we need to care? /* reset(channel, messageEvent); List<String> listOfWrongType = Collections.singletonList("FooBar"); expect(messageEvent.getMessage()).andReturn(listOfWrongType) .atLeastOnce(); replay(channel, messageEvent); handler.messageReceived(ctx, messageEvent); verify(channel, messageEvent); verifyExceptionCaptured(ClassCastException.class); */ // We don't expect to receive /any/ messages in init state since // channelConnected moves us to a different state OFMessage m = BasicFactory.getInstance().getMessage(OFType.HELLO); 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()); 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 = BasicFactory.getInstance().getMessage(OFType.HELLO); sendMessageToHandlerWithControllerReset(Collections.singletonList(hello)); List<OFMessage> msgs = getMessagesFromCapture(); assertEquals(1, msgs.size()); assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); 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.getMissSendLength()); 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)BasicFactory.getInstance() .getMessage(OFType.GET_CONFIG_REPLY); cr.setMissSendLength((short)0xffff); sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(cr)); List<OFMessage> msgs = getMessagesFromCapture(); assertEquals(1, msgs.size()); assertEquals(OFType.STATS_REQUEST, msgs.get(0).getType()); OFStatisticsRequest sr = (OFStatisticsRequest)msgs.get(0); assertEquals(OFStatisticsType.DESC, sr.getStatisticType()); // no idea why an OFStatisticsRequest even /has/ a getStatistics() // methods. It really shouldn't assertNull(sr.getStatistics()); verifyUniqueXids(msgs); assertEquals(OFChannelHandler.ChannelState.WAIT_DESCRIPTION_STAT_REPLY, handler.getStateForTesting()); } /** A helper bean that represents the config for a particular switch in * the storage source. */ private class MockStorageSourceConfig { // the dpid public String dpid; // true if the given dpid should be present in the storage source // if false the storage source will return an empty result public boolean isPresent; // the value of isCoreSwitch public boolean isCoreSwitch; } /** setup and replay a mock storage source and result set that * contains the IsCoreSwitch setting */ private void setupMockStorageSource(MockStorageSourceConfig cfg) { storageSource = createMock(IStorageSourceService.class); storageResultSet = createMock(IResultSet.class); Iterator<IResultSet> it = null; if (cfg.isPresent) { storageResultSet.getBoolean(Controller.SWITCH_CONFIG_CORE_SWITCH); expectLastCall().andReturn(cfg.isCoreSwitch).atLeastOnce(); it = Collections.singletonList(storageResultSet).iterator(); } else { it = Collections.<IResultSet>emptyList().iterator(); } storageResultSet.close(); expectLastCall().atLeastOnce(); expect(storageResultSet.iterator()).andReturn(it).atLeastOnce(); storageSource.getRow(Controller.SWITCH_CONFIG_TABLE_NAME, cfg.dpid); expectLastCall().andReturn(storageResultSet).atLeastOnce(); replay(storageResultSet, storageSource); } private void verifyStorageSource() { verify(storageSource); verify(storageResultSet); } private static OFStatisticsReply createDescriptionStatsReply() { OFStatisticsReply sr = (OFStatisticsReply)BasicFactory.getInstance() .getMessage(OFType.STATS_REPLY); sr.setStatisticType(OFStatisticsType.DESC); OFDescriptionStatistics desc = new OFDescriptionStatistics(); desc.setDatapathDescription("Datapath Description"); desc.setHardwareDescription("Hardware Description"); desc.setManufacturerDescription("Manufacturer Description"); desc.setSerialNumber("Serial Number"); desc.setSoftwareDescription("Software Description"); sr.setStatistics(Collections.singletonList(desc)); return sr; } /** * setup the expectations for the mock switch that are needed * after the switch is instantiated in the WAIT_DESCRIPTION_STATS STATE * Will reset the switch * @throws CounterException */ private void setupSwitchForInstantiationWithReset(String dpid) throws Exception { reset(sw); sw.setChannel(channel); expectLastCall().once(); sw.setFloodlightProvider(controller); expectLastCall().once(); sw.setThreadPoolService(threadPool); expectLastCall().once(); sw.setDebugCounterService(debugCounterService); expectLastCall().once(); sw.setFeaturesReply(featuresReply); expectLastCall().once(); sw.setConnected(true); expectLastCall().once(); sw.getStringId(); expectLastCall().andReturn(dpid).atLeastOnce(); sw.isWriteThrottleEnabled(); // used for log message only expectLastCall().andReturn(false).anyTimes(); sw.setAccessFlowPriority(ACCESS_PRIORITY); expectLastCall().once(); sw.setCoreFlowPriority(CORE_PRIORITY); expectLastCall().once(); } /** 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 * @param storageSourceConfig paramterizes the contents of the storage * source (for IS_CORE_SWITCH) */ public void doMoveToWaitInitialRole(MockStorageSourceConfig cfg) throws Exception { moveToWaitDescriptionStatReply(); // We do not expect a write to the channel for the role request. We add // the channel to the controller and the controller would in turn // call handler.sendRoleRequest(). Since we mock the controller so // we won't see the handler.sendRoleRequest() call and therefore we // won't see any calls on the channel. resetChannel(); replay(channel); // build the stats reply OFStatisticsReply sr = createDescriptionStatsReply(); OFDescriptionStatistics desc = (OFDescriptionStatistics) sr.getFirstStatistics(); setupMessageEvent(Collections.<OFMessage>singletonList(sr)); setupMockStorageSource(cfg); setupSwitchForInstantiationWithReset(cfg.dpid); sw.startDriverHandshake(); expectLastCall().once(); sw.isDriverHandshakeComplete(); expectLastCall().andReturn(true).once(); if (cfg.isPresent) sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, cfg.isCoreSwitch); replay(sw); // mock controller reset(controller); expect(controller.getDebugCounter()).andReturn(debugCounterService) .once(); controller.flushAll(); expectLastCall().once(); expect(controller.getThreadPoolService()) .andReturn(threadPool).once(); expect(controller.getOFSwitchInstance(eq(desc))) .andReturn(sw).once(); expect(controller.getCoreFlowPriority()) .andReturn(CORE_PRIORITY).once(); expect(controller.getAccessFlowPriority()) .andReturn(ACCESS_PRIORITY).once(); controller.addSwitchChannelAndSendInitialRole(handler); expectLastCall().once(); expect(controller.getStorageSourceService()) .andReturn(storageSource).atLeastOnce(); replay(controller); // send the description stats reply handler.messageReceived(ctx, messageEvent); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); verifyStorageSource(); } /** * Move the channel from scratch to WAIT_INITIAL_ROLE state via * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE * Does extensive testing for the WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state * */ @Test public void testSwitchDriverSubHandshake() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; moveToWaitDescriptionStatReply(); resetChannel(); channel.write(capture(writeCapture)); expectLastCall().andReturn(null).atLeastOnce(); replay(channel); // build the stats reply OFStatisticsReply sr = createDescriptionStatsReply(); OFDescriptionStatistics desc = (OFDescriptionStatistics) sr.getFirstStatistics(); setupMessageEvent(Collections.<OFMessage>singletonList(sr)); setupMockStorageSource(cfg); // Start the sub-handshake. Switch will indicate that it's not // complete yet setupSwitchForInstantiationWithReset(cfg.dpid); sw.startDriverHandshake(); expectLastCall().once(); sw.isDriverHandshakeComplete(); expectLastCall().andReturn(false).once(); if (cfg.isPresent) sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, cfg.isCoreSwitch); replay(sw); // mock controller reset(controller); expect(controller.getDebugCounter()).andReturn(debugCounterService) .once(); controller.flushAll(); expectLastCall().once(); expect(controller.getThreadPoolService()) .andReturn(threadPool).once(); expect(controller.getOFSwitchInstance(eq(desc))) .andReturn(sw).once(); expect(controller.getCoreFlowPriority()) .andReturn(CORE_PRIORITY).once(); expect(controller.getAccessFlowPriority()) .andReturn(ACCESS_PRIORITY).once(); expect(controller.getStorageSourceService()) .andReturn(storageSource).atLeastOnce(); replay(controller); // send the description stats reply handler.messageReceived(ctx, messageEvent); assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, handler.getStateForTesting()); assertFalse("Unexpected message captured", writeCapture.hasCaptured()); verifyStorageSource(); verify(sw); //------------------------------------------------- // Send a message to the handler, it should be passed to the // switch's sub-handshake handling. OFMessage m = BasicFactory.getInstance().getMessage(OFType.HELLO); resetToStrict(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.processDriverHandshakeMessage(m); expectLastCall().once(); expect(sw.isDriverHandshakeComplete()).andReturn(false).once(); replay(sw); sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, handler.getStateForTesting()); assertFalse("Unexpected message captured", writeCapture.hasCaptured()); verify(sw); //------------------------------------------------- // Send a ECHO_REQUEST. This should be handled by the OFChannelHandler // and *not* passed to switch sub-handshake // TODO: should this be also passed to the switch handshake instead? m = BasicFactory.getInstance().getMessage(OFType.ECHO_REQUEST); m.setXid(0x042042); reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); replay(sw); sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, handler.getStateForTesting()); List<OFMessage> msgs = getMessagesFromCapture(); assertEquals(1, msgs.size()); assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); assertEquals(0x42042, msgs.get(0).getXid()); verify(sw); //------------------------------------------------- //------------------------------------------------- // Send a message to the handler, it should be passed to the // switch's sub-handshake handling. After this message the // sub-handshake will be complete m = BasicFactory.getInstance().getMessage(OFType.FLOW_REMOVED); resetToStrict(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.processDriverHandshakeMessage(m); expectLastCall().once(); expect(sw.isDriverHandshakeComplete()).andReturn(true).once(); replay(sw); verify(controller); reset(controller); controller.addSwitchChannelAndSendInitialRole(handler); expectLastCall().once(); sendMessageToHandlerNoControllerReset(Collections.singletonList(m)); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); assertFalse("Unexpected message captured", writeCapture.hasCaptured()); verify(sw); //------------------------------------------------- //------------------------------------------------- } @Test /** Test WaitDescriptionReplyState. No config for switch in storage */ public void testWaitDescriptionReplyState1() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); } @Test /** Test WaitDescriptionReplyState. switch is core switch */ public void testWaitDescriptionReplyState2() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = true; cfg.isCoreSwitch = true; doMoveToWaitInitialRole(cfg); } @Test /** Test WaitDescriptionReplyState. switch is NOT core switch */ public void testWaitDescriptionReplyState3() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = true; cfg.isCoreSwitch = true; doMoveToWaitInitialRole(cfg); } /** * Helper * Verify that the given OFMessage is a correct Nicira RoleRequest message * for the given role using the given xid. */ private void verifyRoleRequest(OFMessage m, int expectedXid, Role expectedRole) { assertEquals(OFType.VENDOR, m.getType()); OFVendor vendorMsg = (OFVendor)m; assertEquals(expectedXid, vendorMsg.getXid()); assertEquals(OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor()); assertTrue("Vendor data is not an instance of OFRoleRequestVendorData" + " its class is: " + vendorMsg.getVendorData().getClass().getName(), vendorMsg.getVendorData() instanceof OFRoleRequestVendorData); OFRoleRequestVendorData requestData = (OFRoleRequestVendorData)vendorMsg.getVendorData(); assertEquals(expectedRole.toNxRole(), requestData.getRole()); } /** * 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 { assertTrue("This internal test helper method most not be called " + "with supportsNxRole==false. Test setup broken", supportsNxRole == null || supportsNxRole == true); reset(sw); expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) .andReturn(supportsNxRole).atLeastOnce(); expect(sw.getNextTransactionId()).andReturn(xid).once(); sw.write(capture(writeCapture), EasyMock.<FloodlightContext>anyObject()); expectLastCall().anyTimes(); replay(sw); handler.sendRoleRequest(role); List<OFMessage> msgs = getMessagesFromCapture(); assertEquals(1, msgs.size()); verifyRoleRequest(msgs.get(0), xid, role); verify(sw); } /** * 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; reset(sw); expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) .andReturn(supportsNxRole).atLeastOnce(); // TODO: hmmm. While it's not incorrect that we set the attribute // again it looks odd. Maybe change sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); expectLastCall().anyTimes(); sw.setHARole(role); expectLastCall().once(); if (role == Role.SLAVE) { sw.disconnectOutputStream(); expectLastCall().once(); } else { verify(controller); reset(controller); controller.switchActivated(sw); replay(controller); } replay(sw); handler.sendRoleRequest(role); verify(sw); } /** Return a Nicira RoleReply message for the given role */ private OFMessage getRoleReply(int xid, Role role) { OFVendor vm = (OFVendor)BasicFactory.getInstance() .getMessage(OFType.VENDOR); vm.setXid(xid); vm.setVendor(OFNiciraVendorData.NX_VENDOR_ID); OFRoleReplyVendorData replyData = new OFRoleReplyVendorData(); replyData.setRole(role.toNxRole()); vm.setVendorData(replyData); return vm; } /** Return an OFError of the given type with the given xid */ private OFMessage getErrorMessage(OFErrorType type, int i, int xid) { OFError e = (OFError) BasicFactory.getInstance() .getMessage(OFType.ERROR); e.setErrorType(type); e.setErrorCode((short)i); e.setXid(xid); return e; } /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * * This method tests only the simple case that the switch supports roles * and transitions to MASTER */ @Test public void testInitialMoveToMasterWithRole() throws Exception { int xid = 42; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Set the role setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // prepare mocks and inject the role reply message reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); expectLastCall().once(); sw.setHARole(Role.MASTER); expectLastCall().once(); replay(sw); verify(controller); reset(controller); controller.switchActivated(sw); expectLastCall().once(); OFMessage reply = getRoleReply(xid, Role.MASTER); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); 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 only the simple case that the switch supports roles * and transitions to SLAVE */ @Test public void testInitialMoveToSlaveWithRole() throws Exception { int xid = 42; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); 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); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); expectLastCall().once(); sw.setHARole(Role.SLAVE); expectLastCall().once(); replay(sw); verify(controller); reset(controller); controller.switchDeactivated(sw); expectLastCall().once(); OFMessage reply = getRoleReply(xid, Role.SLAVE); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); assertEquals(OFChannelHandler.ChannelState.SLAVE, handler.getStateForTesting()); } /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * * This method tests the case that the switch does NOT support roles. * The channel handler still needs to send the initial request to find * out that whether the switch supports roles. */ @Test public void testInitialMoveToMasterNoRole() throws Exception { int xid = 43; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Set the role setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // prepare mocks and inject the role reply message reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); sw.setHARole(Role.MASTER); expectLastCall().once(); replay(sw); // FIXME: shouldn't use ordinal(), but OFError is broken // Error with incorrect xid and type. Should be ignored. OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, 0, xid+1); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Error with correct xid. Should trigger state transition err = getErrorMessage(OFErrorType.OFPET_BAD_REQUEST, OFError.OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal(), xid); verify(controller); reset(controller); controller.switchActivated(sw); expectLastCall().once(); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerNoControllerReset(Collections.singletonList(err)); assertEquals(OFChannelHandler.ChannelState.MASTER, handler.getStateForTesting()); } /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * * We let the initial role request time out. Role support should be * disabled but the switch should be activated. */ @Test public void testInitialMoveToMasterTimeout() throws Exception { int timeout = 50; handler.useRoleChangerWithOtherTimeoutForTesting(timeout); int xid = 4343; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Set the role setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // prepare mocks and inject the role reply message reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); sw.setHARole(Role.MASTER); expectLastCall().once(); replay(sw); OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY); Thread.sleep(timeout+5); verify(controller); reset(controller); controller.switchActivated(sw); expectLastCall().once(); sendMessageToHandlerNoControllerReset(Collections.singletonList(m)); 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 MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); 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); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); sw.setHARole(Role.SLAVE); expectLastCall().once(); sw.disconnectOutputStream(); // Make sure we disconnect expectLastCall().once(); replay(sw); // FIXME: shouldn't use ordinal(), but OFError is broken // Error with incorrect xid and type. Should be ignored. OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, 0, xid+1); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Error with correct xid. Should trigger state transition err = getErrorMessage(OFErrorType.OFPET_BAD_REQUEST, OFError.OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal(), xid); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); } /** Move the channel from scratch to SLAVE state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * * We let the initial role request time out. The switch should be * disconnected */ @Test public void testInitialMoveToSlaveTimeout() throws Exception { int timeout = 50; handler.useRoleChangerWithOtherTimeoutForTesting(timeout); int xid = 4444; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); 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); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); sw.setHARole(Role.SLAVE); expectLastCall().once(); sw.disconnectOutputStream(); // Make sure we disconnect expectLastCall().once(); replay(sw); OFMessage m = BasicFactory.getInstance().getMessage(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; // 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); assertEquals(OFChannelHandler.ChannelState.SLAVE, 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()); // Set the role setupSwitchSendRoleRequestAndVerify(true, xid, Role.MASTER); // prepare mocks and inject the role reply message reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); expectLastCall().once(); sw.setHARole(Role.MASTER); expectLastCall().once(); replay(sw); verify(controller); reset(controller); controller.switchActivated(sw); expectLastCall().once(); OFMessage reply = getRoleReply(xid, Role.MASTER); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); 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 setupSwitchSendRoleRequestAndVerify(true, xid, Role.SLAVE); // prepare mocks and inject the role reply message reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); expectLastCall().once(); sw.setHARole(Role.SLAVE); expectLastCall().once(); replay(sw); verify(controller); reset(controller); controller.switchDeactivated(sw); expectLastCall().once(); OFMessage reply = getRoleReply(xid, Role.SLAVE); // sendMessageToHandler will verify and rest controller mock sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); assertEquals(OFChannelHandler.ChannelState.SLAVE, handler.getStateForTesting()); } @Test public void testMultiRoleChange1() throws Exception { testInitialMoveToMasterWithRole(); changeRoleToMasterWithRequest(); changeRoleToSlaveWithRequest(); changeRoleToSlaveWithRequest(); changeRoleToMasterWithRequest(); changeRoleToSlaveWithRequest(); } @Test public void testMultiRoleChange2() throws Exception { testInitialMoveToSlaveWithRole(); changeRoleToMasterWithRequest(); changeRoleToSlaveWithRequest(); changeRoleToSlaveWithRequest(); changeRoleToMasterWithRequest(); changeRoleToSlaveWithRequest(); } /** Start from scratch and reply with an unexpected error to the role * change request * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state */ @Test public void testInitialRoleChangeOtherError() throws Exception { int xid = 4343; // first, move us to WAIT_INITIAL_ROLE_STATE MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); cfg.isPresent = false; doMoveToWaitInitialRole(cfg); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // Set the role setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, handler.getStateForTesting()); // FIXME: shouldn't use ordinal(), but OFError is broken OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, 0, xid); verify(sw); reset(sw); expect(sw.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); replay(sw); sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); verifyExceptionCaptured(SwitchStateException.class); } /** * Test dispatch of messages while in MASTER role */ @Test public void testMessageDispatchMaster() throws Exception { testInitialMoveToMasterWithRole(); // Send packet in. expect dispatch OFPacketIn pi = (OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN); reset(controller); controller.handleMessage(sw, pi, null); expectLastCall().once(); sendMessageToHandlerNoControllerReset( Collections.<OFMessage>singletonList(pi)); verify(controller); // TODO: many more to go } /** * Test port status message handling while MASTER * */ @Test public void testPortStatusMessageMaster() throws Exception { long dpid = featuresReply.getDatapathId(); testInitialMoveToMasterWithRole(); OFPhysicalPort p = new OFPhysicalPort(); p.setName("Port1"); p.setPortNumber((short)1); OFPortStatus ps = (OFPortStatus) BasicFactory.getInstance().getMessage(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.inputThrottled(anyObject(OFMessage.class))) .andReturn(false).anyTimes(); 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); } } /** * Test re-assert MASTER * */ @Test public void testReassertMaster() throws Exception { testInitialMoveToMasterWithRole(); OFError err = (OFError) BasicFactory.getInstance().getMessage(OFType.ERROR); err.setXid(42); err.setErrorType(OFErrorType.OFPET_BAD_REQUEST); err.setErrorCode(OFBadRequestCode.OFPBRC_EPERM); reset(controller); controller.reassertRole(handler, Role.MASTER); expectLastCall().once(); controller.handleMessage(sw, err, null); expectLastCall().once(); sendMessageToHandlerNoControllerReset( Collections.<OFMessage>singletonList(err)); verify(sw); verify(controller); } }