/*
* Copyright (c) 2013 Big Switch Networks, Inc.
*
* Licensed under the Eclipse Public License, Version 1.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.sdnplatform.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.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.jboss.netty.channel.Channel;
import org.junit.Before;
import org.junit.Test;
import org.openflow.protocol.OFError;
import org.openflow.protocol.OFError.OFErrorType;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFType;
import org.openflow.protocol.OFVendor;
import org.openflow.protocol.factory.BasicFactory;
import org.openflow.protocol.vendor.OFVendorData;
import org.openflow.vendor.nicira.OFNiciraVendorData;
import org.openflow.vendor.nicira.OFRoleRequestVendorData;
import org.openflow.vendor.nicira.OFRoleVendorData;
import org.sdnplatform.core.ListenerContext;
import org.sdnplatform.core.IOFSwitch;
import org.sdnplatform.core.IControllerService.Role;
import org.sdnplatform.core.internal.Controller;
import org.sdnplatform.core.internal.OFSwitchImpl;
import org.sdnplatform.core.internal.RoleChanger;
import org.sdnplatform.core.internal.RoleChanger.PendingRoleRequestEntry;
import org.sdnplatform.core.internal.RoleChanger.RoleChangeTask;
public class RoleChangerTest {
public RoleChanger roleChanger;
Controller controller;
@Before
public void setUp() throws Exception {
controller = createMock(Controller.class);
roleChanger = new RoleChanger(controller);
BasicFactory factory = new BasicFactory();
expect(controller.getOFMessageFactory()).andReturn(factory).anyTimes();
}
/**
* Send a role request for SLAVE to a switch that doesn't support it.
* The connection should be closed.
*/
@Test
public void testSendRoleRequestSlaveNotSupported() throws Exception {
LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>();
// a switch that doesn't support role requests
IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class);
// No support for NX_ROLE
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(false);
sw1.disconnectOutputStream();
switches.add(sw1);
replay(sw1);
roleChanger.sendRoleRequest(switches, Role.SLAVE, 123456);
verify(sw1);
// sendRoleRequest needs to remove the switch from the list since
// it closed its connection
assertTrue(switches.isEmpty());
}
/**
* Send a role request for MASTER to a switch that doesn't support it.
* The connection should stay open.
*/
@Test
public void testSendRoleRequestMasterNotSupported() throws Exception {
LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>();
// a switch that doesn't support role requests
IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class);
// No support for NX_ROLE
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(false);
sw1.setHARole(Role.MASTER, false);
switches.add(sw1);
replay(sw1);
roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
verify(sw1);
assertEquals(1, switches.size());
}
/**
* Check error handling
* hasn't had a role request send to it yet
*/
@SuppressWarnings("unchecked")
@Test
public void testSendRoleRequestErrorHandling () throws Exception {
LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>();
// a switch that supports role requests
IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class);
// No support for NX_ROLE
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(true);
expect(sw1.getNextTransactionId()).andReturn(1);
sw1.write((List<OFMessage>)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
expectLastCall().andThrow(new IOException());
sw1.disconnectOutputStream();
switches.add(sw1);
replay(sw1, controller);
roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
verify(sw1, controller);
assertTrue(switches.isEmpty());
}
/**
* Send a role request a switch that supports it and one that
* hasn't had a role request send to it yet
*/
@SuppressWarnings("unchecked")
@Test
public void testSendRoleRequestSupported() throws Exception {
LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>();
// a switch that supports role requests
IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class);
// Support for NX_ROLE
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(true);
expect(sw1.getNextTransactionId()).andReturn(1);
sw1.write((List<OFMessage>)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
switches.add(sw1);
// second switch
IOFSwitch sw2 = EasyMock.createMock(IOFSwitch.class);
// No role request yet
expect(sw2.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(null);
expect(sw2.getNextTransactionId()).andReturn(1);
sw2.write((List<OFMessage>)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
switches.add(sw2);
replay(sw1, sw2, controller);
roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
verify(sw1, sw2, controller);
assertEquals(2, switches.size());
}
@Test
public void testVerifyRoleReplyReceived() throws Exception {
Collection<IOFSwitch> switches = new LinkedList<IOFSwitch>();
// Add a switch that has received a role reply
IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class);
LinkedList<PendingRoleRequestEntry> pendingList1 =
new LinkedList<PendingRoleRequestEntry>();
roleChanger.pendingRequestMap.put(sw1, pendingList1);
switches.add(sw1);
// Add a switch that has not yet received a role reply
IOFSwitch sw2 = EasyMock.createMock(IOFSwitch.class);
LinkedList<PendingRoleRequestEntry> pendingList2 =
new LinkedList<PendingRoleRequestEntry>();
roleChanger.pendingRequestMap.put(sw2, pendingList2);
PendingRoleRequestEntry entry =
new PendingRoleRequestEntry(1, Role.MASTER, 123456);
pendingList2.add(entry);
// Timed out switch should become active
expect(sw2.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA))
.andReturn(null);
expect(sw2.getHARole()).andReturn(null);
sw2.setHARole(Role.MASTER, false);
EasyMock.expectLastCall();
switches.add(sw2);
replay(sw1, sw2);
roleChanger.verifyRoleReplyReceived(switches, 123456);
verify(sw1, sw2);
assertEquals(2, switches.size());
}
@Test
public void testRoleChangeTask() {
@SuppressWarnings("unchecked")
Collection<IOFSwitch> switches =
EasyMock.createMock(Collection.class);
long now = System.nanoTime();
long dt1 = 10 * 1000*1000*1000L;
long dt2 = 20 * 1000*1000*1000L;
long dt3 = 15 * 1000*1000*1000L;
RoleChangeTask t1 = new RoleChangeTask(switches, null, now+dt1);
RoleChangeTask t2 = new RoleChangeTask(switches, null, now+dt2);
RoleChangeTask t3 = new RoleChangeTask(switches, null, now+dt3);
// FIXME: cannot test comparison against self. grrr
//assertTrue( t1.compareTo(t1) <= 0 );
assertTrue( t1.compareTo(t2) < 0 );
assertTrue( t1.compareTo(t3) < 0 );
assertTrue( t2.compareTo(t1) > 0 );
//assertTrue( t2.compareTo(t2) <= 0 );
assertTrue( t2.compareTo(t3) > 0 );
}
@SuppressWarnings("unchecked")
@Test
public void testSubmitRequest() throws Exception {
LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>();
roleChanger.timeout = 100*1000*1000; // 100 ms
// a switch that supports role requests
IOFSwitch sw1 = EasyMock.createStrictMock(IOFSwitch.class);
// Support for NX_ROLE
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(true);
expect(sw1.getNextTransactionId()).andReturn(1);
sw1.write((List<OFMessage>)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
// Second request
expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
.andReturn(true);
expect(sw1.getNextTransactionId()).andReturn(2);
sw1.write((List<OFMessage>)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
expect(sw1.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA))
.andReturn(null);
expect(sw1.getHARole()).andReturn(null);
sw1.setHARole(Role.MASTER, false);
expect(sw1.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA))
.andReturn(null);
expect(sw1.getHARole()).andReturn(Role.MASTER);
sw1.setHARole(Role.SLAVE, false);
// Disconnect on timing out SLAVE request
sw1.disconnectOutputStream();
switches.add(sw1);
// Add to switch when timing out the MASTER request
controller.addSwitch(sw1, true);
replay(sw1, controller);
roleChanger.submitRequest(switches, Role.MASTER);
roleChanger.submitRequest(switches, Role.SLAVE);
// Wait until role request has been sent.
synchronized (roleChanger.pendingTasks) {
while (RoleChanger.RoleChangeTask.Type.TIMEOUT !=
roleChanger.pendingTasks.peek().type) {
roleChanger.pendingTasks.wait();
}
}
// Now there should be exactly one timeout task pending for each request
assertEquals(2, roleChanger.pendingTasks.size());
// Check that RoleChanger indeed made a copy of switches collection
assertNotSame(switches, roleChanger.pendingTasks.peek().switches);
// Wait until the timeout triggers
synchronized (roleChanger.pendingTasks) {
while (roleChanger.pendingTasks.size() != 0) {
roleChanger.pendingTasks.wait();
}
}
verify(sw1, controller);
}
// Helper function
protected void setupPendingRoleRequest(IOFSwitch sw, int xid, Role role,
long cookie) {
LinkedList<PendingRoleRequestEntry> pendingList =
new LinkedList<PendingRoleRequestEntry>();
roleChanger.pendingRequestMap.put(sw, pendingList);
PendingRoleRequestEntry entry =
new PendingRoleRequestEntry(xid, role, cookie);
pendingList.add(entry);
}
@Test
public void testDeliverRoleReplyOk() {
// test normal case
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
roleChanger.deliverRoleReply(sw, xid, role);
assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(role, sw.getHARole());
assertEquals(0, roleChanger.pendingRequestMap.get(sw).size());
}
@Test
public void testDeliverRoleReplyOkRepeated() {
// test normal case. Not the first role reply
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
roleChanger.deliverRoleReply(sw, xid, role);
assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(role, sw.getHARole());
assertEquals(0, roleChanger.pendingRequestMap.get(sw).size());
}
@Test
public void testDeliverRoleReplyNonePending() {
// nothing pending
OFSwitchImpl sw = new OFSwitchImpl();
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
sw.setChannel(ch);
roleChanger.deliverRoleReply(sw, 1, Role.MASTER);
assertEquals(null, sw.getHARole());
}
@Test
public void testDeliverRoleReplyWrongXid() {
// wrong xid received
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
sw.setChannel(ch);
expect(ch.close()).andReturn(null);
replay(ch);
roleChanger.deliverRoleReply(sw, xid+1, role);
verify(ch);
assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(0, roleChanger.pendingRequestMap.get(sw).size());
}
@Test
public void testDeliverRoleReplyWrongRole() {
// correct xid but incorrect role received
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
sw.setChannel(ch);
expect(ch.close()).andReturn(null);
replay(ch);
roleChanger.deliverRoleReply(sw, xid, Role.SLAVE);
verify(ch);
assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(0, roleChanger.pendingRequestMap.get(sw).size());
}
@Test
public void testCheckFirstPendingRoleRequestXid() {
int xid = 54321;
long cookie = 232323;
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
assertEquals(true,
roleChanger.checkFirstPendingRoleRequestXid(sw, xid));
assertEquals(false,
roleChanger.checkFirstPendingRoleRequestXid(sw, 0));
roleChanger.pendingRequestMap.get(sw).clear();
assertEquals(false,
roleChanger.checkFirstPendingRoleRequestXid(sw, xid));
}
@Test
public void testCheckFirstPendingRoleRequestNullSw() {
int xid = 54321;
long cookie = 232323;
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
// pass null as sw object, which is true during handshake
assertEquals(false,
roleChanger.checkFirstPendingRoleRequestXid(null, xid));
roleChanger.pendingRequestMap.get(sw).clear();
}
@Test
public void testCheckFirstPendingRoleRequestCookie() {
int xid = 54321;
long cookie = 232323;
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
assertNotSame(null,
roleChanger.checkFirstPendingRoleRequestCookie(sw, cookie));
assertEquals(null,
roleChanger.checkFirstPendingRoleRequestCookie(sw, 0));
roleChanger.pendingRequestMap.get(sw).clear();
assertEquals(null,
roleChanger.checkFirstPendingRoleRequestCookie(sw, cookie));
}
@Test
public void testDeliverRoleRequestError() {
// normal case. xid is pending
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
sw.setChannel(ch);
setupPendingRoleRequest(sw, xid, role, cookie);
OFError error = new OFError();
error.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
error.setXid(xid);
replay(ch);
roleChanger.deliverRoleRequestError(sw, error);
verify(ch);
assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(role, sw.getHARole());
assertEquals(0, roleChanger.pendingRequestMap.get(sw).size());
}
@Test
public void testDeliverRoleRequestErrorNonePending() {
// nothing pending
OFSwitchImpl sw = new OFSwitchImpl();
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
sw.setChannel(ch);
OFError error = new OFError();
error.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
error.setXid(1);
replay(ch);
roleChanger.deliverRoleRequestError(sw, error);
verify(ch);
assertEquals(null, sw.getHARole());
}
@Test
public void testDeliverRoleRequestErrorWrongXid() {
// wrong xid received
// wrong xid received
int xid = (int) System.currentTimeMillis();
long cookie = System.nanoTime();
Role role = Role.MASTER;
OFSwitchImpl sw = new OFSwitchImpl();
setupPendingRoleRequest(sw, xid, role, cookie);
Channel ch = createMock(Channel.class);
SocketAddress sa = new InetSocketAddress(42);
expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
expect(ch.close()).andReturn(null);
sw.setChannel(ch);
replay(ch);
OFError error = new OFError();
error.setErrorCode(OFError.OFErrorType.OFPET_BAD_REQUEST.getValue());
error.setXid(xid + 1);
roleChanger.deliverRoleRequestError(sw, error);
verify(ch);
assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
assertEquals(1, roleChanger.pendingRequestMap.get(sw).size());
}
public void doSendNxRoleRequest(Role role, int nx_role) throws Exception {
long cookie = System.nanoTime();
OFSwitchImpl sw = new OFSwitchImpl();
Channel ch = createMock(Channel.class);
sw.setChannel(ch);
sw.setControllerProvider(controller);
// verify that the correct OFMessage is sent
Capture<List<OFMessage>> msgCapture = new Capture<List<OFMessage>>();
// expect(sw.channel.getRemoteAddress()).andReturn(null);
controller.handleOutgoingMessage(
(IOFSwitch)EasyMock.anyObject(),
(OFMessage)EasyMock.anyObject(),
(ListenerContext)EasyMock.anyObject());
expect(ch.write(capture(msgCapture))).andReturn(null);
replay(ch, controller);
int xid = roleChanger.sendHARoleRequest(sw, role, cookie);
verify(ch, controller);
List<OFMessage> msgList = msgCapture.getValue();
assertEquals(1, msgList.size());
OFMessage msg = msgList.get(0);
assertEquals("Transaction Ids must match", xid, msg.getXid());
assertTrue("Message must be an OFVendor type", msg instanceof OFVendor);
assertEquals(OFType.VENDOR, msg.getType());
OFVendor vendorMsg = (OFVendor)msg;
assertEquals("Vendor message must be vendor Nicira",
OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor());
OFVendorData vendorData = vendorMsg.getVendorData();
assertTrue("Vendor Data must be an OFRoleRequestVendorData",
vendorData instanceof OFRoleRequestVendorData);
OFRoleRequestVendorData roleRequest = (OFRoleRequestVendorData)vendorData;
assertEquals(nx_role, roleRequest.getRole());
reset(ch);
}
@Test
public void testSendNxRoleRequestMaster() throws Exception {
doSendNxRoleRequest(Role.MASTER, OFRoleVendorData.NX_ROLE_MASTER);
}
@Test
public void testSendNxRoleRequestSlave() throws Exception {
doSendNxRoleRequest(Role.SLAVE, OFRoleVendorData.NX_ROLE_SLAVE);
}
@Test
public void testSendNxRoleRequestEqual() throws Exception {
doSendNxRoleRequest(Role.EQUAL, OFRoleVendorData.NX_ROLE_OTHER);
}
}