package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import java.util.List; import java.util.concurrent.ExecutionException; import org.easymock.Capture; import org.easymock.EasyMock; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import io.netty.channel.Channel; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; import org.junit.After; import org.junit.Before; import org.junit.Test; import net.floodlightcontroller.core.SwitchDisconnectedException; import net.floodlightcontroller.core.internal.OFConnection; import net.floodlightcontroller.core.internal.OFConnectionCounters; import net.floodlightcontroller.core.internal.OFErrorMsgException; import net.floodlightcontroller.core.test.TestEventLoop; import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; import net.floodlightcontroller.debugcounter.IDebugCounterService; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFEchoReply; import org.projectfloodlight.openflow.protocol.OFEchoRequest; import org.projectfloodlight.openflow.protocol.OFErrorMsg; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; import org.projectfloodlight.openflow.protocol.OFHello; import org.projectfloodlight.openflow.protocol.OFHelloElem; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPacketOut; import org.projectfloodlight.openflow.protocol.OFRoleReply; import org.projectfloodlight.openflow.protocol.OFRoleRequest; import org.projectfloodlight.openflow.protocol.OFRoleRequestFailedCode; import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFAuxId; import org.projectfloodlight.openflow.types.OFPort; import net.floodlightcontroller.util.FutureTestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; public class OFConnectionTest { private static final Logger logger = LoggerFactory.getLogger(OFConnectionTest.class); private OFFactory factory; private Channel channel; private OFConnection conn; private DatapathId switchId; private Timer timer; private TestEventLoop eventLoop; @Before public void setUp() throws Exception { factory = OFFactories.getFactory(OFVersion.OF_13); switchId = DatapathId.of(1); timer = new HashedWheelTimer(); channel = EasyMock.createMock(Channel.class); IDebugCounterService debugCounterService = new DebugCounterServiceImpl(); debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); conn = new OFConnection(switchId, factory, channel, OFAuxId.MAIN, debugCounterService, timer); eventLoop = new TestEventLoop(); expect(channel.eventLoop()).andReturn(eventLoop).anyTimes(); } @After public void tearDown() throws Exception { if (timer != null) { timer.stop(); } } @Test(timeout = 5000) public void testWriteRequestSuccess() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(echoRequest)); assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); OFEchoReply echoReply = factory.buildEchoReply() .setXid(echoRequest.getXid()) .build(); assertThat("Connection should have accepted the response", conn.deliverResponse(echoReply), equalTo(true)); assertThat("Future should be complete ", future.isDone(), equalTo(true)); assertThat(future.get(), equalTo(echoReply)); assertThat("Connection should have no pending requests", conn.getPendingRequestIds().isEmpty(), equalTo(true)); } /** write a stats request message that triggers two responses */ @Test(timeout = 5000) public void testWriteStatsRequestSuccess() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); OFFlowStatsRequest flowStatsRequest = factory.buildFlowStatsRequest().build(); ListenableFuture<List<OFFlowStatsReply>> future = conn.writeStatsRequest(flowStatsRequest); assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(flowStatsRequest)); assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); OFFlowStatsReply statsReply1 = factory.buildFlowStatsReply() .setXid(flowStatsRequest.getXid()) .setFlags(Sets.immutableEnumSet(OFStatsReplyFlags.REPLY_MORE)) .build(); assertThat("Connection should have accepted the response", conn.deliverResponse(statsReply1), equalTo(true)); assertThat("Future should not be complete ", future.isDone(), equalTo(false)); OFFlowStatsReply statsReply2 = factory.buildFlowStatsReply() .setXid(flowStatsRequest.getXid()) .build(); assertThat("Connection should have accepted the response", conn.deliverResponse(statsReply2), equalTo(true)); assertThat("Future should be complete ", future.isDone(), equalTo(true)); assertThat(future.get(), Matchers.contains(statsReply1, statsReply2)); assertThat("Connection should have no pending requests", conn.getPendingRequestIds().isEmpty(), equalTo(true)); } private Capture<List<OFMessage>> prepareChannelForWriteList() { EasyMock.expect(channel.isActive()).andReturn(Boolean.TRUE).anyTimes(); Capture<List<OFMessage>> cMsgList = EasyMock.newCapture(); expect(channel.writeAndFlush(capture(cMsgList))).andReturn(null).once(); replay(channel); return cMsgList; } /** write a request which triggers an OFErrorMsg response */ @Test(timeout = 5000) public void testWriteRequestOFErrorMsg() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); OFRoleRequest roleRequest = factory.buildRoleRequest().setRole(OFControllerRole.ROLE_MASTER).build(); ListenableFuture<OFRoleReply> future = conn.writeRequest(roleRequest); assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(roleRequest)); assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); OFRoleRequestFailedErrorMsg roleError = factory.errorMsgs().buildRoleRequestFailedErrorMsg() .setXid(roleRequest.getXid()) .setCode(OFRoleRequestFailedCode.STALE) .build(); assertThat("Connection should have accepted the response", conn.deliverResponse(roleError), equalTo(true)); OFErrorMsgException e = FutureTestUtils.assertFutureFailedWithException(future, OFErrorMsgException.class); assertThat(e.getErrorMessage(), CoreMatchers.<OFErrorMsg>equalTo(roleError)); } @Test(timeout = 5000) public void testWriteRequestNotConnectedFailure() throws InterruptedException, ExecutionException { EasyMock.expect(channel.isActive()).andReturn(Boolean.FALSE).anyTimes(); replay(channel); OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); SwitchDisconnectedException e = FutureTestUtils.assertFutureFailedWithException(future, SwitchDisconnectedException.class); assertThat(e.getId(), equalTo(switchId)); assertThat("Connection should have no pending requests", conn.getPendingRequestIds().isEmpty(), equalTo(true)); } @Test(timeout = 5000) public void testWriteRequestDisconnectFailure() throws InterruptedException, ExecutionException { prepareChannelForWriteList(); OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); conn.disconnected(); SwitchDisconnectedException e = FutureTestUtils.assertFutureFailedWithException(future, SwitchDisconnectedException.class); assertThat(e.getId(), equalTo(switchId)); assertThat("Connection should have no pending requests", conn.getPendingRequestIds().isEmpty(), equalTo(true)); } /** write a packetOut, which is not buffered */ @Test(timeout = 5000) public void testSingleMessageWrite() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); OFPacketOut packetOut = factory.buildPacketOut() .setData(new byte[] { 0x01, 0x02, 0x03, 0x04 }) .setActions(ImmutableList.<OFAction>of( factory.actions().output(OFPort.of(1), 0))) .build(); conn.write(packetOut); eventLoop.runTasks(); assertThat("Write should have been flushed", cMsgList.hasCaptured(), equalTo(true)); List<OFMessage> value = cMsgList.getValue(); logger.info("Captured channel write: "+value); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(packetOut)); } /** write a list of messages */ @Test(timeout = 5000) public void testMessageWriteList() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); OFHello hello = factory.hello(ImmutableList.<OFHelloElem>of()); OFPacketOut packetOut = factory.buildPacketOut() .setData(new byte[] { 0x01, 0x02, 0x03, 0x04 }) .setActions(ImmutableList.<OFAction>of( factory.actions().output(OFPort.of(1), 0))) .build(); conn.write(ImmutableList.of(hello, packetOut)); eventLoop.runTasks(); assertThat("Write should have been written", cMsgList.hasCaptured(), equalTo(true)); List<OFMessage> value = cMsgList.getValue(); logger.info("Captured channel write: "+value); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(hello, packetOut)); } }