/*
* 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.tunnelmanager;
import static org.easymock.EasyMock.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.Before;
import org.junit.Test;
import org.openflow.protocol.OFPhysicalPort;
import org.sdnplatform.IBetterOFSwitch;
import org.sdnplatform.core.ListenerContext;
import org.sdnplatform.core.IOFSwitch;
import org.sdnplatform.core.IOFSwitch.OFPortType;
import org.sdnplatform.core.test.MockThreadPoolService;
import org.sdnplatform.ovsdb.IOVSDBManagerService;
import org.sdnplatform.storage.IStorageSourceService;
import org.sdnplatform.storage.memory.MemoryStorageSource;
import org.sdnplatform.test.PlatformTestCase;
import org.sdnplatform.tunnelmanager.TunnelManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("unchecked")
public class TunnelManagerTest extends PlatformTestCase {
protected static Logger logger = LoggerFactory.
getLogger(TunnelManagerTest.class);
private TunnelManager tm;
private static final String SWITCH_CONFIG_TABLE_NAME =
"controller_switchconfig";
private static final String SWITCH_DPID = "dpid";
private static final String TUNNEL_ENABLED_OR_NOT = "tunnel_termination";
public TunnelManager getTunnelManager() {
return tm;
}
private MemoryStorageSource stosrc;
public static class TMTest {
ArrayList<Map<String, Object>> swTunnInfoList =
new ArrayList<Map<String, Object>>();
public void addSwTunnInfo(Map<String, Object>... stis) {
for (Map<String, Object> sti : stis) {
swTunnInfoList.add(sti);
}
}
public void writeToStorage(IStorageSourceService stosrc) {
for(Map<String, Object> row : swTunnInfoList) {
stosrc.insertRow(SWITCH_CONFIG_TABLE_NAME, row);
}
}
public void removeFromStorage(IStorageSourceService stosrc, int i) {
stosrc.deleteRow(SWITCH_CONFIG_TABLE_NAME, i);
}
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
MockThreadPoolService tp = new MockThreadPoolService();
tp.init(null);
TunnelManager.TUNNEL_TASK_DELAY = 0;
tm = new TunnelManager();
tm.setControllerProvider(getMockControllerProvider());
tm.threadPool = tp;
stosrc = new MemoryStorageSource();
tm.setStorageSource(stosrc);
Set<String> cols = new HashSet<String>();
cols.add(SWITCH_DPID);
cols.add(TUNNEL_ENABLED_OR_NOT);
stosrc.createTable(SWITCH_CONFIG_TABLE_NAME, cols);
/*
stosrc = createNiceMock(IStorageSource.class);
tm.setStorageSource(stosrc);
stosrc.addListener(SWITCH_CONFIG_TABLE_NAME, tm);
expectLastCall().atLeastOnce();
*/
tm.startUp(null);
}
protected void setupIOFSwitchTunnelMode(IOFSwitch sw) {
// These set of tests do not check for the tunnel-IP retrieval process
// Nevertheless we let TunnelManager know what kind of process
// the switch supports.
expect(sw.attributeEquals(IBetterOFSwitch.SUPPORTS_BSN_SET_TUNNEL_DST_ACTION, true))
.andReturn(true).anyTimes();
expect(sw.attributeEquals(IBetterOFSwitch.SUPPORTS_OVSDB_TUNNEL_SETUP, true))
.andReturn(false).anyTimes();
}
protected void setupIOFSwitchWrite(IOFSwitch sw) throws IOException {
expect(sw.getNextTransactionId()).andReturn(42).anyTimes();
sw.write(anyObject(List.class), anyObject(ListenerContext.class));
expectLastCall().atLeastOnce();
}
protected void setupIOFSwitchPortType(IOFSwitch sw) {
expect(sw.getPortType((short)4)).andReturn(OFPortType.TUNNEL_LOOPBACK).times(2);
expect(sw.getPortType((short)3)).andReturn(OFPortType.TUNNEL).times(1);
expect(sw.getPortType((short)1)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw.getPortType((short)2)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw.getPortType((short)0xfffe)).andReturn(OFPortType.NORMAL).anyTimes();
}
protected void setupAddRemoveIOFSwitchMocks(IOFSwitch sw1, IOFSwitch sw2,
IOFSwitch sw3, IOVSDBManagerService ovsdb,
IStorageSourceService stosrc) throws IOException {
expect(sw1.getId()).andReturn(1L).anyTimes();
expect(sw1.getEnabledPorts()).andReturn(getPorts(true)).times(1);
setupIOFSwitchPortType(sw1);
setupIOFSwitchTunnelMode(sw1);
setupIOFSwitchWrite(sw1);
expect(sw2.getId()).andReturn(2L).anyTimes();
expect(sw2.getEnabledPorts()).andReturn(getPorts(true)).times(1);
setupIOFSwitchPortType(sw2);
setupIOFSwitchTunnelMode(sw2);
setupIOFSwitchWrite(sw2);
expect(sw3.getId()).andReturn(3L).anyTimes();
expect(sw3.getEnabledPorts()).andReturn(getPorts(true)).times(1);
setupIOFSwitchPortType(sw3);
setupIOFSwitchTunnelMode(sw3);
setupIOFSwitchWrite(sw3);
ConcurrentHashMap<Long, IOFSwitch> switchmap =
new ConcurrentHashMap<Long, IOFSwitch>();
switchmap.put(1L, sw1);
switchmap.put(2L, sw2);
switchmap.put(3L, sw3);
getMockControllerProvider().setSwitches(switchmap);
}
protected OFPhysicalPort getPort(String name) {
for(OFPhysicalPort p : getPorts(true)) {
if (p.getName().equals(name)) return p;
}
return null;
}
protected Collection<OFPhysicalPort> getPorts(boolean hasTunnel) {
List<OFPhysicalPort> switchIntfs = new ArrayList<OFPhysicalPort>();
OFPhysicalPort p;
int numports = (hasTunnel) ? 5 : 3;
for (short i=0; i<numports; i++) {
p = new OFPhysicalPort();
p.setConfig(0);
p.setState(0);
p.setCurrentFeatures(0xc0);
p.setAdvertisedFeatures(0);
p.setSupportedFeatures(0);
p.setPeerFeatures(0);
switch (i) {
case 0: // eth1 uplink port
p.setPortNumber((short) 1);
byte[] hardwareAddress1 = new byte[]{
(byte)0xcc,0,0,0,0,(byte)1};
p.setHardwareAddress(hardwareAddress1);
p.setName("eth1");
break;
case 1: // ovs-br0 LOCAL port
p.setPortNumber((short) 0xfffe);
byte[] hardwareAddress2 = new byte[]{
(byte)0xcc,0,0,0,0,(byte)2};
p.setHardwareAddress(hardwareAddress2);
p.setName("ovs-br0");
break;
case 2: // a tap port for a connected VM
p.setPortNumber((short) 2);
byte[] hardwareAddress3 = new byte[]{
(byte)0xcc,0,0,0,0,(byte)3};
p.setHardwareAddress(hardwareAddress3);
p.setName("tap0");
break;
case 3: // a GRE tunnel port
p.setPortNumber((short) 3);
byte[] hardwareAddress4 = new byte[]{
(byte)0xcc,0,0,0,0,(byte)4};
p.setHardwareAddress(hardwareAddress4);
p.setName("tun-bsn");
break;
case 4: // a tunnel-endpoint port
p.setPortNumber((short) 4);
byte[] hardwareAddress5 = new byte[]{
(byte)0xcc,0,0,0,0,(byte)5};
p.setHardwareAddress(hardwareAddress5);
p.setName("tun-loopback");
break;
}
switchIntfs.add(p);
}
return switchIntfs;
}
//************************************************
private static final TMTest emptyTest;
static {
emptyTest = new TMTest();
}
@Test
public void testAddRemoveSwitch() throws IOException {
logger.info("*** Starting test 1: testAddRemoveSwitch ***");
emptyTest.writeToStorage(stosrc);
IOFSwitch sw1 = createMock(IOFSwitch.class);
IOFSwitch sw2 = createMock(IOFSwitch.class);
IOFSwitch sw3 = createMock(IOFSwitch.class);
IOVSDBManagerService ovsdb = createNiceMock(IOVSDBManagerService.class);
tm.setOVSDBManager(ovsdb);
setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
replay(sw1, sw2, sw3, ovsdb);
tm.addedSwitch(sw1);
tm.addedSwitch(sw2);
tm.addedSwitch(sw3);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
tm.removedSwitch(sw2);
}
//************************************************
//enable a switch for tunneling
private static final Map<String, Object> t1;
static {
t1 = new HashMap<String, Object>();
t1.put(SWITCH_DPID, "00:00:00:00:00:00:00:01");
t1.put(TUNNEL_ENABLED_OR_NOT, "enabled");
}
private static final TMTest basicTest1;
static {
basicTest1 = new TMTest();
basicTest1.addSwTunnInfo(t1);
}
@Test
public void testEnableSwitch() throws IOException {
logger.info("*** Starting test 2: testEnableSwitch ***");
IOFSwitch sw1 = createMock(IOFSwitch.class);
IOFSwitch sw2 = createMock(IOFSwitch.class);
IOFSwitch sw3 = createMock(IOFSwitch.class);
IOVSDBManagerService ovsdb = createNiceMock(IOVSDBManagerService.class);
tm.setOVSDBManager(ovsdb);
setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
replay(sw1, sw2, sw3, ovsdb);
tm.addedSwitch(sw1);
tm.addedSwitch(sw2);
tm.addedSwitch(sw3);
basicTest1.writeToStorage(stosrc);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
//reset(sw1, sw2, sw3, ovsdb);
//setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
//replay(sw1, sw2, sw3, ovsdb);
tm.removedSwitch(sw2);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
}
//************************************************
// disable a switch for tunneling
private static final Map<String, Object> t2;
static {
t2 = new HashMap<String, Object>();
t2.put(SWITCH_DPID, "00:00:00:00:00:00:00:03");
t2.put(TUNNEL_ENABLED_OR_NOT, "disabled");
}
private static final TMTest basicTest2;
static {
basicTest2 = new TMTest();
basicTest2.addSwTunnInfo(t1, t2);
}
@Test
public void testDisableSwitch() throws IOException {
logger.info("*** Starting test 3: testDisableSwitch ***");
IOFSwitch sw1 = createMock(IOFSwitch.class);
IOFSwitch sw2 = createMock(IOFSwitch.class);
IOFSwitch sw3 = createMock(IOFSwitch.class);
IOVSDBManagerService ovsdb = createNiceMock(IOVSDBManagerService.class);
tm.setOVSDBManager(ovsdb);
setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
replay(sw1, sw2, sw3, ovsdb);
tm.addedSwitch(sw1);
tm.addedSwitch(sw2);
tm.addedSwitch(sw3);
basicTest2.writeToStorage(stosrc);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(false, tm.outstandingTunnelIP.contains(3L));
tm.removedSwitch(sw2);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(false, tm.outstandingTunnelIP.contains(3L));
assertEquals(false, tm.tunnelCapableSwitches.containsKey(sw2.getId()));
}
//************************************************
// testing transitions of configured state
private static final Map<String, Object> kt1, kt2;
static {
kt1 = new HashMap<String, Object>();
kt1.put(SWITCH_DPID, "00:00:00:00:00:00:00:01");
kt1.put(TUNNEL_ENABLED_OR_NOT, "enabled");
kt2 = new HashMap<String, Object>();
kt2.put(SWITCH_DPID, "00:00:00:00:00:00:00:01");
kt2.put(TUNNEL_ENABLED_OR_NOT, "disabled");
}
private static final TMTest kvmTest1;
static {
kvmTest1 = new TMTest();
kvmTest1.addSwTunnInfo(kt1);
}
private static final TMTest kvmTest2;
static {
kvmTest2 = new TMTest();
kvmTest2.addSwTunnInfo(kt2);
}
private static final TMTest kvmTest3;
static {
kvmTest3 = new TMTest();
kvmTest3.addSwTunnInfo(kt2, kt1);
}
private static final TMTest kvmTest4;
static {
kvmTest4 = new TMTest();
kvmTest4.addSwTunnInfo(kt1, kt2);
}
@Test
public void testConfigTransitions() throws IOException {
logger.info("*** Starting test 4: testConfigTransitions ***");
IOFSwitch sw1 = createMock(IOFSwitch.class);
IOFSwitch sw2 = createMock(IOFSwitch.class);
IOFSwitch sw3 = createMock(IOFSwitch.class);
IOVSDBManagerService ovsdb = createNiceMock(IOVSDBManagerService.class);
tm.setOVSDBManager(ovsdb);
setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
replay(sw1, sw2, sw3, ovsdb);
tm.addedSwitch(sw1);
tm.addedSwitch(sw2);
tm.addedSwitch(sw3);
kvmTest1.writeToStorage(stosrc);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
assertEquals(3, tm.tunnelCapableSwitches.size());
kvmTest1.removeFromStorage(stosrc, 1); //equivalent to "no tunnel termination"
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
verify(sw1, sw2, sw3, ovsdb);
kvmTest2.writeToStorage(stosrc); // disabling switch
assertEquals(false, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.tunnelCapableSwitches.containsKey(sw2.getId()));
kvmTest2.removeFromStorage(stosrc, 2); //equivalent to "no tunnel termination"
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
verify(sw1, sw2, sw3, ovsdb);
//disabled to enabled
kvmTest3.writeToStorage(stosrc);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
assertEquals(3, tm.tunnelCapableSwitches.size());
//enabled to disabled
kvmTest4.writeToStorage(stosrc);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(false, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
assertEquals(3, tm.tunnelCapableSwitches.size());
}
//************************************************
// testing transitions of configured state
@Test
public void testTunnelPortChangedOnSwitch() throws IOException {
logger.info("*** Starting test 5: testTunnelPortChangedOnSwitch ***");
emptyTest.writeToStorage(stosrc);
IOFSwitch sw1 = createMock(IOFSwitch.class);
IOFSwitch sw2 = createMock(IOFSwitch.class);
IOFSwitch sw3 = createMock(IOFSwitch.class);
IOVSDBManagerService ovsdb = createNiceMock(IOVSDBManagerService.class);
tm.setOVSDBManager(ovsdb);
setupAddRemoveIOFSwitchMocks(sw1, sw2, sw3, ovsdb, stosrc);
replay(sw1, sw2, sw3, ovsdb);
tm.addedSwitch(sw1);
tm.addedSwitch(sw2);
tm.addedSwitch(sw3);
verify(sw1, sw2, sw3, ovsdb);
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
//removing tunnel port
reset(sw2);
expect(sw2.getEnabledPorts()).andReturn(getPorts(false)).once();
expect(sw2.getId()).andReturn(2L).once();
// missing port type TUNNEL
expect(sw2.getPortType((short)1)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw2.getPortType((short)2)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw2.getPortType((short)0xfffe)).andReturn(OFPortType.NORMAL).anyTimes();
replay(sw2);
tm.switchPortChanged(2L);
verify(sw2) ;
assertEquals(true, tm.outstandingTunnelIP.contains(1L));
assertEquals(false, tm.outstandingTunnelIP.contains(2L));
assertEquals(true, tm.outstandingTunnelIP.contains(3L));
assertEquals(2, tm.tunnelCapableSwitches.size());
// re-enabling tunnel port while pretending that tunnel IPs for switches
// 1 and 3 have already been learned
reset(sw2);
tm.outstandingTunnelIP.remove(1L);
tm.outstandingTunnelIP.remove(3L);
expect(sw2.getEnabledPorts()).andReturn(getPorts(true)).times(2);
expect(sw2.getId()).andReturn(2L).anyTimes();
// two calls to portType -- once in switchPortChanged
// and the other in addedSwitch
expect(sw2.getPortType((short)3)).andReturn(OFPortType.TUNNEL).times(2);
expect(sw2.getPortType((short)4)).andReturn(OFPortType.TUNNEL_LOOPBACK).anyTimes();
expect(sw2.getPortType((short)1)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw2.getPortType((short)2)).andReturn(OFPortType.NORMAL).anyTimes();
expect(sw2.getPortType((short)0xfffe)).andReturn(OFPortType.NORMAL).anyTimes();
setupIOFSwitchTunnelMode(sw2);
setupIOFSwitchWrite(sw2);
replay(sw2);
tm.switchPortChanged(2L);
verify( sw2);
assertEquals(true, tm.outstandingTunnelIP.contains(2L));
assertEquals(3, tm.tunnelCapableSwitches.size());
}
}