/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 net.floodlightcontroller.linkdiscovery.internal;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IListener.Command;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.ImmutablePort;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.test.MockThreadPoolService;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.linkdiscovery.LinkInfo;
import net.floodlightcontroller.packet.Data;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.IPacket;
import net.floodlightcontroller.packet.IPv4;
import net.floodlightcontroller.packet.UDP;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.restserver.RestApiServer;
import net.floodlightcontroller.routing.IRoutingService;
import net.floodlightcontroller.routing.Link;
import net.floodlightcontroller.storage.IStorageSourceService;
import net.floodlightcontroller.storage.memory.MemoryStorageSource;
import net.floodlightcontroller.test.FloodlightTestCase;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.topology.ITopologyService;
import net.floodlightcontroller.topology.NodePortTuple;
import net.floodlightcontroller.topology.TopologyManager;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketIn.OFPacketInReason;
import org.openflow.protocol.OFPhysicalPort;
import org.openflow.protocol.OFType;
import org.openflow.protocol.factory.BasicFactory;
import org.openflow.util.HexString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author David Erickson (daviderickson@cs.stanford.edu)
*/
public class LinkDiscoveryManagerTest extends FloodlightTestCase {
private TestLinkDiscoveryManager ldm;
protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManagerTest.class);
public class TestLinkDiscoveryManager extends LinkDiscoveryManager {
public boolean isSendLLDPsCalled = false;
public boolean isClearLinksCalled = false;
@Override
protected void discoverOnAllPorts() {
isSendLLDPsCalled = true;
super.discoverOnAllPorts();
}
public void reset() {
isSendLLDPsCalled = false;
isClearLinksCalled = false;
}
@Override
protected void clearAllLinks() {
isClearLinksCalled = true;
super.clearAllLinks();
}
}
public LinkDiscoveryManager getLinkDiscoveryManager() {
return ldm;
}
private IOFSwitch createMockSwitch(Long id) {
IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
expect(mockSwitch.getId()).andReturn(id).anyTimes();
return mockSwitch;
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
FloodlightModuleContext cntx = new FloodlightModuleContext();
ldm = new TestLinkDiscoveryManager();
TopologyManager routingEngine = new TopologyManager();
ldm.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>();
MockThreadPoolService tp = new MockThreadPoolService();
RestApiServer restApi = new RestApiServer();
cntx.addService(IRestApiService.class, restApi);
cntx.addService(IThreadPoolService.class, tp);
cntx.addService(IRoutingService.class, routingEngine);
cntx.addService(ILinkDiscoveryService.class, ldm);
cntx.addService(ITopologyService.class, ldm);
cntx.addService(IStorageSourceService.class, new MemoryStorageSource());
cntx.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
restApi.init(cntx);
tp.init(cntx);
routingEngine.init(cntx);
ldm.init(cntx);
restApi.startUp(cntx);
tp.startUp(cntx);
routingEngine.startUp(cntx);
ldm.startUp(cntx);
IOFSwitch sw1 = createMockSwitch(1L);
IOFSwitch sw2 = createMockSwitch(2L);
Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
switches.put(1L, sw1);
switches.put(2L, sw2);
getMockFloodlightProvider().setSwitches(switches);
replay(sw1, sw2);
}
@Test
public void testAddOrUpdateLink() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 2, 2L, 1);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
NodePortTuple srcNpt = new NodePortTuple(1L, 2);
NodePortTuple dstNpt = new NodePortTuple(2L, 1);
// check invariants hold
assertNotNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertTrue(linkDiscovery.switchLinks.get(lt.getSrc()).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(srcNpt));
assertTrue(linkDiscovery.portLinks.get(srcNpt).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.portLinks.get(dstNpt).contains(lt));
assertTrue(linkDiscovery.links.containsKey(lt));
}
@Test
public void testDeleteLink() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 2, 2L, 1);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
linkDiscovery.deleteLinks(Collections.singletonList(lt), "Test");
// check invariants hold
assertNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertNull(linkDiscovery.switchLinks.get(lt.getDst()));
assertNull(linkDiscovery.portLinks.get(lt.getSrc()));
assertNull(linkDiscovery.portLinks.get(lt.getDst()));
assertTrue(linkDiscovery.links.isEmpty());
}
@Test
public void testAddOrUpdateLinkToSelf() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 2, 2L, 3);
NodePortTuple srcNpt = new NodePortTuple(1L, 2);
NodePortTuple dstNpt = new NodePortTuple(2L, 3);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
// check invariants hold
assertNotNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertTrue(linkDiscovery.switchLinks.get(lt.getSrc()).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(srcNpt));
assertTrue(linkDiscovery.portLinks.get(srcNpt).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.portLinks.get(dstNpt).contains(lt));
assertTrue(linkDiscovery.links.containsKey(lt));
}
@Test
public void testDeleteLinkToSelf() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 2, 1L, 3);
NodePortTuple srcNpt = new NodePortTuple(1L, 2);
NodePortTuple dstNpt = new NodePortTuple(2L, 3);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
linkDiscovery.deleteLinks(Collections.singletonList(lt), "Test to self");
// check invariants hold
assertNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertNull(linkDiscovery.switchLinks.get(lt.getDst()));
assertNull(linkDiscovery.portLinks.get(srcNpt));
assertNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.links.isEmpty());
}
@Test
public void testRemovedSwitch() {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 2, 2L, 1);
NodePortTuple srcNpt = new NodePortTuple(1L, 2);
NodePortTuple dstNpt = new NodePortTuple(2L, 1);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
IOFSwitch sw1 = getMockFloodlightProvider().getSwitch(1L);
IOFSwitch sw2 = getMockFloodlightProvider().getSwitch(2L);
// Mock up our expected behavior
linkDiscovery.switchRemoved(sw1.getId());
verify(sw1, sw2);
// check invariants hold
assertNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertNull(linkDiscovery.switchLinks.get(lt.getDst()));
assertNull(linkDiscovery.portLinks.get(srcNpt));
assertNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.links.isEmpty());
}
@Test
public void testRemovedSwitchSelf() {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
IOFSwitch sw1 = createMockSwitch(1L);
replay(sw1);
Link lt = new Link(1L, 2, 1L, 3);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
// Mock up our expected behavior
linkDiscovery.switchRemoved(sw1.getId());
verify(sw1);
// check invariants hold
assertNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertNull(linkDiscovery.portLinks.get(lt.getSrc()));
assertNull(linkDiscovery.portLinks.get(lt.getDst()));
assertTrue(linkDiscovery.links.isEmpty());
}
@Test
public void testAddUpdateLinks() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link lt = new Link(1L, 1, 2L, 1);
NodePortTuple srcNpt = new NodePortTuple(1L, 1);
NodePortTuple dstNpt = new NodePortTuple(2L, 1);
LinkInfo info;
info = new LinkInfo(System.currentTimeMillis() - 40000,
System.currentTimeMillis() - 40000, null);
linkDiscovery.addOrUpdateLink(lt, info);
// check invariants hold
assertNotNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertTrue(linkDiscovery.switchLinks.get(lt.getSrc()).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(srcNpt));
assertTrue(linkDiscovery.portLinks.get(srcNpt).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.portLinks.get(dstNpt).contains(lt));
assertTrue(linkDiscovery.links.containsKey(lt));
linkDiscovery.timeoutLinks();
info = new LinkInfo(System.currentTimeMillis(),/* firstseen */
null,/* unicast */
System.currentTimeMillis());
linkDiscovery.addOrUpdateLink(lt, info);
assertTrue(linkDiscovery.links.get(lt).getUnicastValidTime() == null);
assertTrue(linkDiscovery.links.get(lt).getMulticastValidTime() != null);
// Add a link info based on info that woudld be obtained from unicast LLDP
// Setting the unicast LLDP reception time to be 40 seconds old, so we can use
// this to test timeout after this test. Although the info is initialized
// with LT_OPENFLOW_LINK, the link property should be changed to LT_NON_OPENFLOW
// by the addOrUpdateLink method.
info = new LinkInfo(System.currentTimeMillis() - 40000,
System.currentTimeMillis() - 40000, null);
linkDiscovery.addOrUpdateLink(lt, info);
// Expect to timeout the unicast Valid Time, but not the multicast Valid time
// So the link type should go back to non-openflow link.
linkDiscovery.timeoutLinks();
assertTrue(linkDiscovery.links.get(lt).getUnicastValidTime() == null);
assertTrue(linkDiscovery.links.get(lt).getMulticastValidTime() != null);
// Set the multicastValidTime to be old and see if that also times out.
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
linkDiscovery.timeoutLinks();
assertTrue(linkDiscovery.links.get(lt) == null);
// Test again only with multicast LLDP
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
assertTrue(linkDiscovery.links.get(lt).getUnicastValidTime() == null);
assertTrue(linkDiscovery.links.get(lt).getMulticastValidTime() != null);
// Call timeout and check if link is no longer present.
linkDiscovery.timeoutLinks();
assertTrue(linkDiscovery.links.get(lt) == null);
// Start clean and see if loops are also added.
lt = new Link(1L, 1, 1L, 2);
srcNpt = new NodePortTuple(1L, 1);
dstNpt = new NodePortTuple(1L, 2);
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
// Start clean and see if loops are also added.
lt = new Link(1L, 1, 1L, 3);
srcNpt = new NodePortTuple(1L, 1);
dstNpt = new NodePortTuple(1L, 3);
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
// Start clean and see if loops are also added.
lt = new Link(1L, 4, 1L, 5);
srcNpt = new NodePortTuple(1L, 4);
dstNpt = new NodePortTuple(1L, 5);
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
// Start clean and see if loops are also added.
lt = new Link(1L, 3, 1L, 5);
srcNpt = new NodePortTuple(1L, 3);
dstNpt = new NodePortTuple(1L, 5);
info = new LinkInfo(System.currentTimeMillis() - 40000,
null, System.currentTimeMillis() - 40000);
linkDiscovery.addOrUpdateLink(lt, info);
}
@Test
public void testHARoleChange() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
IOFSwitch sw1 = createMockSwitch(1L);
IOFSwitch sw2 = createMockSwitch(2L);
replay(sw1, sw2);
Link lt = new Link(1L, 2, 2L, 1);
NodePortTuple srcNpt = new NodePortTuple(1L, 2);
NodePortTuple dstNpt = new NodePortTuple(2L, 1);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), null);
linkDiscovery.addOrUpdateLink(lt, info);
// check invariants hold
assertNotNull(linkDiscovery.switchLinks.get(lt.getSrc()));
assertTrue(linkDiscovery.switchLinks.get(lt.getSrc()).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(srcNpt));
assertTrue(linkDiscovery.portLinks.get(srcNpt).contains(lt));
assertNotNull(linkDiscovery.portLinks.get(dstNpt));
assertTrue(linkDiscovery.portLinks.get(dstNpt).contains(lt));
assertTrue(linkDiscovery.links.containsKey(lt));
/* FIXME: what's the right thing to do here:
// check that it clears from memory
getMockFloodlightProvider().dispatchRoleChanged(Role.SLAVE);
assertTrue(linkDiscovery.switchLinks.isEmpty());
getMockFloodlightProvider().dispatchRoleChanged(Role.MASTER);
// check that lldps were sent
assertTrue(ldm.isSendLLDPsCalled);
assertTrue(ldm.isClearLinksCalled);
ldm.reset();
*/
}
@Test
public void testSwitchAdded() throws Exception {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Capture<OFMessage> wc;
Capture<FloodlightContext> fc;
Set<Short> qPorts;
OFPhysicalPort ofpp = new OFPhysicalPort();
ofpp.setName("eth4242");
ofpp.setPortNumber((short)4242);
ofpp.setHardwareAddress(HexString.fromHexString("5c:16:c7:00:00:01"));
ofpp.setCurrentFeatures(0);
ImmutablePort p1 = ImmutablePort.fromOFPhysicalPort(ofpp);
IOFSwitch sw1 = createMockSwitch(1L);
// Set switch map in floodlightProvider.
Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
switches.put(1L, sw1);
getMockFloodlightProvider().setSwitches(switches);
// Create the set of ports
List<Short> ports = new ArrayList<Short>();
for(short p=1; p<=20; ++p) {
ports.add(p);
}
// Set the captures.
wc = new Capture<OFMessage>(CaptureType.ALL);
fc = new Capture<FloodlightContext>(CaptureType.ALL);
// Expect switch to return those ports.
expect(sw1.getEnabledPortNumbers()).andReturn(ports).anyTimes();
expect(sw1.getPort(EasyMock.anyShort())).andReturn(p1).anyTimes();
sw1.write(capture(wc), capture(fc));
expectLastCall().anyTimes();
replay(sw1);
linkDiscovery.switchActivated(sw1.getId());
verify(sw1);
qPorts = linkDiscovery.getQuarantinedPorts(sw1.getId());
assertNotNull(qPorts);
assertFalse(qPorts.isEmpty());
Thread.sleep(100);
qPorts = linkDiscovery.getQuarantinedPorts(sw1.getId());
assertNotNull(qPorts);
assertFalse(qPorts.isEmpty());
Thread.sleep(200);
qPorts = linkDiscovery.getQuarantinedPorts(sw1.getId());
assertNotNull(qPorts);
assertTrue(qPorts.isEmpty());
// Ensure that through every switch port, an LLDP and BDDP
// packet was sent out. Total # of packets = # of ports * 2.
assertTrue(wc.hasCaptured());
List<OFMessage> msgList = wc.getValues();
assertTrue(msgList.size() == ports.size() * 2);
}
private OFPacketIn createPacketIn(String srcMAC, String dstMAC,
String srcIp, String dstIp, short vlan) {
IPacket testPacket = new Ethernet()
.setDestinationMACAddress(dstMAC)
.setSourceMACAddress(srcMAC)
.setVlanID(vlan)
.setEtherType(Ethernet.TYPE_IPv4)
.setPayload(
new IPv4()
.setTtl((byte) 128)
.setSourceAddress(srcIp)
.setDestinationAddress(dstIp)
.setPayload(new UDP()
.setSourcePort((short) 5000)
.setDestinationPort((short) 5001)
.setPayload(new Data(new byte[] {0x01}))));
byte[] testPacketSerialized = testPacket.serialize();
OFPacketIn pi;
// build out input packet
pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN))
.setBufferId(-1)
.setInPort((short) 1)
.setPacketData(testPacketSerialized)
.setReason(OFPacketInReason.NO_MATCH)
.setTotalLength((short) testPacketSerialized.length);
return pi;
}
@Test
public void testIgnoreSrcMAC() throws Exception {
String mac1 = "00:11:22:33:44:55";
String mac2 = "00:44:33:22:11:00";
String mac3 = "00:44:33:22:11:02";
String srcIp = "192.168.1.1";
String dstIp = "192.168.1.2";
short vlan = 42;
IOFSwitch mockSwitch = createMock(IOFSwitch.class);
expect(mockSwitch.getId()).andReturn(1L).anyTimes();
replay(mockSwitch);
/* TEST1: See basic packet flow */
OFPacketIn pi;
pi = createPacketIn(mac1, mac2, srcIp, dstIp, vlan);
FloodlightContext cntx = new FloodlightContext();
Ethernet eth = new Ethernet();
eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
IFloodlightProviderService.bcStore.put(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
eth);
Command ret;
ret = ldm.receive(mockSwitch, pi, cntx);
assertEquals(Command.CONTINUE, ret);
/* TEST2: Add mac1 to the ignore MAC list and see that the packet is
* dropped
*/
ldm.addMACToIgnoreList(HexString.toLong(mac1), 0);
ret = ldm.receive(mockSwitch, pi, cntx);
assertEquals(Command.STOP, ret);
/* Verify that if we send a packet with another MAC it still works */
pi = createPacketIn(mac2, mac3, srcIp, dstIp, vlan);
cntx = new FloodlightContext();
eth = new Ethernet();
eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
IFloodlightProviderService.bcStore.put(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
eth);
ret = ldm.receive(mockSwitch, pi, cntx);
assertEquals(Command.CONTINUE, ret);
/* TEST3: Add a MAC range and see if that is ignored */
ldm.addMACToIgnoreList(HexString.toLong(mac2), 8);
ret = ldm.receive(mockSwitch, pi, cntx);
assertEquals(Command.STOP, ret);
/* Send a packet with source MAC as mac3 and see that that is ignored
* as well.
*/
pi = createPacketIn(mac3, mac1, srcIp, dstIp, vlan);
cntx = new FloodlightContext();
eth = new Ethernet();
eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
IFloodlightProviderService.bcStore.put(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
eth);
ret = ldm.receive(mockSwitch, pi, cntx);
assertEquals(Command.STOP, ret);
verify(mockSwitch);
}
}