blob: cbbbcfc0f488d0e3ff138b6e38364650ff1e99fd [file] [log] [blame]
/**
* 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.onrc.onos.core.linkdiscovery;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyShort;
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.reportMatcher;
import static org.easymock.EasyMock.verify;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
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.module.FloodlightModuleContext;
import net.floodlightcontroller.core.test.MockThreadPoolService;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.restserver.RestApiServer;
import net.floodlightcontroller.test.FloodlightTestCase;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.onrc.onos.core.packet.Ethernet;
import net.onrc.onos.core.packet.OnosLldp;
import net.onrc.onos.core.registry.IControllerRegistryService;
import org.easymock.IArgumentMatcher;
import org.junit.Before;
import org.junit.Test;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFPortConfig;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFPortReason;
import org.projectfloodlight.openflow.protocol.OFPortState;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFPort;
// CHECKSTYLE IGNORE WriteTag FOR NEXT 2 LINES
/**
* @author David Erickson (daviderickson@cs.stanford.edu)
*/
public class LinkDiscoveryManagerTest extends FloodlightTestCase {
private LinkDiscoveryManager ldm;
private static final Set<OFPortState> EMPTY_PORT_STATE =
Collections.<OFPortState>emptySet();
// Arbitrary MAC address that we can feed in to our mock objects. This
// value is never actually checked during the tests so it doesn't matter if
// all ports have the same MAC address.
private static final byte[] DEFAULT_MAC_ADDRESS =
new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x1};
private OFFactory factory10 = OFFactories.getFactory(OFVersion.OF_10);
/**
* EasyMock matcher to verify the value of the output port of a packet out.
* This is used to verify that the packet out messages generated by
* LinkDiscoveryManager contain the correct output port.
*
*/
private static final class PacketOutPortMatcher implements IArgumentMatcher {
private final int portNumber;
public PacketOutPortMatcher(int portNumber) {
this.portNumber = portNumber;
}
@Override
public void appendTo(StringBuffer strBuffer) {
strBuffer.append("PacketOutPortMatcher failed to verify output port");
}
@Override
public boolean matches(Object object) {
if (!(object instanceof OFPacketOut)) {
return false;
}
OFPacketOut po = (OFPacketOut) object;
if (po.getActions().size() != 1
|| !(po.getActions().get(0) instanceof OFActionOutput)) {
return false;
}
OFActionOutput action = (OFActionOutput) po.getActions().get(0);
return action.getPort().getPortNumber() == portNumber;
}
}
/**
* Matcher method to match a given output port against a packet out message
* passed as an argument to a mock switch.
*
* @param outPort the output port to check in the packet out
* @return anything of type OFPacketOut
*/
private static OFPacketOut matchOutPort(int outPort) {
reportMatcher(new PacketOutPortMatcher(outPort));
return null;
}
private LinkDiscoveryManager getLinkDiscoveryManager() {
return ldm;
}
private IOFSwitch createMockSwitch(Long id) {
IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
expect(mockSwitch.getId()).andReturn(id).anyTimes();
expect(mockSwitch.portEnabled(anyShort())).andReturn(true).anyTimes();
expect(mockSwitch.getFactory()).andReturn(factory10).anyTimes();
return mockSwitch;
}
private OFPortDesc createMockPort(short portNumber) {
return createMockPortWithState(portNumber,
Collections.<OFPortState>emptySet());
}
private OFPortDesc createMockPortWithState(short portNumber,
Set<OFPortState> state) {
OFPort ofPort = createMock(OFPort.class);
expect(ofPort.getShortPortNumber()).andReturn(portNumber).anyTimes();
OFPortDesc ofPortDesc = createMock(OFPortDesc.class);
expect(ofPortDesc.getPortNo()).andReturn(ofPort).anyTimes();
expect(ofPortDesc.getHwAddr()).andReturn(
MacAddress.of(DEFAULT_MAC_ADDRESS)).anyTimes();
expect(ofPortDesc.getConfig()).
andReturn(Collections.<OFPortConfig>emptySet()).anyTimes();
expect(ofPortDesc.getState()).andReturn(state).anyTimes();
replay(ofPort);
replay(ofPortDesc);
return ofPortDesc;
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
FloodlightModuleContext cntx = new FloodlightModuleContext();
ldm = new LinkDiscoveryManager();
MockThreadPoolService tp = new MockThreadPoolService();
RestApiServer restApi = new RestApiServer();
IControllerRegistryService registry =
createMock(IControllerRegistryService.class);
expect(registry.hasControl(anyLong())).andReturn(true).anyTimes();
replay(registry);
cntx.addService(IControllerRegistryService.class, registry);
cntx.addService(IRestApiService.class, restApi);
cntx.addService(IThreadPoolService.class, tp);
cntx.addService(ILinkDiscoveryService.class, ldm);
cntx.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
restApi.init(cntx);
tp.init(cntx);
ldm.init(cntx);
restApi.startUp(cntx);
tp.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);
long firstSeenTime = System.currentTimeMillis();
LinkInfo info = new LinkInfo(firstSeenTime,
System.currentTimeMillis(), EMPTY_PORT_STATE,
EMPTY_PORT_STATE);
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));
LinkInfo infoToVerify = linkDiscovery.links.get(lt);
assertEquals(firstSeenTime, infoToVerify.getFirstSeenTime());
assertEquals(EMPTY_PORT_STATE, infoToVerify.getSrcPortState());
assertEquals(EMPTY_PORT_STATE, infoToVerify.getDstPortState());
// Arbitrary new port states to verify that the port state is updated
final Set<OFPortState> newSrcPortState =
Collections.singleton(OFPortState.STP_BLOCK);
final Set<OFPortState> newDstPortState =
Collections.singleton(OFPortState.LINK_DOWN);
// Update the last received probe timestamp and the port states
LinkInfo infoWithStateChange = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), newSrcPortState, newDstPortState);
linkDiscovery.addOrUpdateLink(lt, infoWithStateChange);
assertNotNull(linkDiscovery.links.get(lt));
infoToVerify = linkDiscovery.links.get(lt);
// First seen time should be the original time, not the second update time
assertEquals(firstSeenTime, infoToVerify.getFirstSeenTime());
// Both port states should have been updated
assertEquals(newSrcPortState, infoToVerify.getSrcPortState());
assertEquals(newDstPortState, infoToVerify.getDstPortState());
}
@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(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
linkDiscovery.deleteLinks(Collections.singletonList(lt));
// 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(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
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(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
linkDiscovery.deleteLinks(Collections.singletonList(lt));
// 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(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
IOFSwitch sw1 = getMockFloodlightProvider().getSwitches().get(1L);
IOFSwitch sw2 = getMockFloodlightProvider().getSwitches().get(2L);
// Mock up our expected behavior
linkDiscovery.switchDisconnected(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(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
// Mock up our expected behavior
linkDiscovery.switchDisconnected(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;
// Setting the last LLDP reception time to be 40 seconds old, so we
// can use this to test that an old link times out correctly
info = new LinkInfo(System.currentTimeMillis() - 40000,
System.currentTimeMillis() - 40000,
EMPTY_PORT_STATE, EMPTY_PORT_STATE);
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();
// Setting the last LLDP reception time to be 40 seconds old, so we
// can use this to test that an old link times out correctly
info = new LinkInfo(System.currentTimeMillis() - 40000,
System.currentTimeMillis() - 40000,
EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
// Expect to timeout the unicast Valid Time, so the link should disappear
linkDiscovery.timeOutLinks();
assertTrue(linkDiscovery.links.get(lt) == null);
}
/**
* This test case verifies that LinkDiscoveryManager.sendDiscoveryMessage()
* performs "write" operation on the specified IOFSwitch object
* with a LLDP packet.
*
* @throws IOException
*/
@Test
public void testSendDiscoveryMessage() throws IOException {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
// Mock up our expected behavior
IOFSwitch swTest = createMockSwitch(3L);
getMockFloodlightProvider().getSwitches().put(3L, swTest);
short portNum = 1;
OFPortDesc ofPortDesc = createMockPort(portNum);
expect(swTest.getPort(portNum)).andReturn(ofPortDesc).atLeastOnce();
swTest.write(matchOutPort(portNum),
anyObject(FloodlightContext.class));
expectLastCall().times(1);
swTest.flush();
expectLastCall().once();
replay(swTest);
linkDiscovery.sendDiscoveryMessage(3L, portNum, false);
verify(swTest);
}
@Test
public void testHandlePortStatusForNewPort() throws IOException {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
long dpid = 3L;
IOFSwitch sw = createMockSwitch(dpid);
getMockFloodlightProvider().getSwitches().put(dpid, sw);
short portNum = 1;
OFPortDesc ofPortDesc = createMockPort(portNum);
OFPortStatus portStatus = factory10.buildPortStatus()
.setDesc(ofPortDesc)
.setReason(OFPortReason.ADD)
.build();
expect(sw.getPort(portNum)).andReturn(ofPortDesc).once();
sw.write(matchOutPort(portNum),
anyObject(FloodlightContext.class));
sw.flush();
replay(sw);
linkDiscovery.handlePortStatus(sw, portStatus);
verify(sw);
}
@Test
public void testHandlePortStatusForExistingPort() {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
// Add a link that we can update later during the test
Link lt = new Link(1L, 1, 2L, 1);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
short portNum = 1;
// Arbitrary states to test state changes
Set<OFPortState> srcPortState =
Collections.singleton(OFPortState.STP_FORWARD);
Set<OFPortState> dstPortState =
Collections.singleton(OFPortState.STP_LISTEN);
OFPortDesc srcPortDesc = createMockPortWithState(portNum, srcPortState);
OFPortDesc dstPortDesc = createMockPortWithState(portNum, dstPortState);
OFPortStatus srcPortStatus = factory10.buildPortStatus()
.setDesc(srcPortDesc)
.setReason(OFPortReason.MODIFY)
.build();
OFPortStatus dstPortStatus = factory10.buildPortStatus()
.setDesc(dstPortDesc)
.setReason(OFPortReason.MODIFY)
.build();
linkDiscovery.handlePortStatus(
getMockFloodlightProvider().getSwitches().get(1L), srcPortStatus);
LinkInfo newInfo = linkDiscovery.links.get(lt);
assertEquals(srcPortState, newInfo.getSrcPortState());
assertEquals(EMPTY_PORT_STATE, newInfo.getDstPortState());
linkDiscovery.handlePortStatus(
getMockFloodlightProvider().getSwitches().get(2L), dstPortStatus);
newInfo = linkDiscovery.links.get(lt);
assertEquals(srcPortState, newInfo.getSrcPortState());
assertEquals(dstPortState, newInfo.getDstPortState());
}
@Test
public void testHandlePortStatusForDeletePort() {
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
// Add a link that we can delete later during the test
Link lt = new Link(1L, 1, 2L, 2);
LinkInfo info = new LinkInfo(System.currentTimeMillis(),
System.currentTimeMillis(), EMPTY_PORT_STATE, EMPTY_PORT_STATE);
linkDiscovery.addOrUpdateLink(lt, info);
short portNum = 1;
OFPortDesc srcPortDesc = createMockPort(portNum);
OFPortStatus srcPortStatus = factory10.buildPortStatus()
.setDesc(srcPortDesc)
.setReason(OFPortReason.DELETE)
.build();
assertNotNull(linkDiscovery.getLinks().get(lt));
// Send a delete port status for the source port, which should result
// in the link being deleted
linkDiscovery.handlePortStatus(
getMockFloodlightProvider().getSwitches().get(1L), srcPortStatus);
assertNull(linkDiscovery.getLinks().get(lt));
}
@Test
public void testReceive() {
OnosLldp lldpPacket = new OnosLldp();
lldpPacket.setPort((short) 1);
lldpPacket.setSwitch(1L);
lldpPacket.setReverse(false);
Ethernet ethPacket = new Ethernet();
ethPacket.setEtherType(Ethernet.TYPE_LLDP);
ethPacket.setSourceMACAddress(DEFAULT_MAC_ADDRESS);
ethPacket.setDestinationMACAddress(
LinkDiscoveryManager.LLDP_STANDARD_DST_MAC_STRING);
ethPacket.setPayload(lldpPacket);
ethPacket.setPad(true);
OFPacketIn pi = createMock(OFPacketIn.class);
expect(pi.getData()).andReturn(ethPacket.serialize()).anyTimes();
replay(pi);
LinkDiscoveryManager linkDiscovery = getLinkDiscoveryManager();
Link expectedLink = new Link(1L, 1, 2L, 2);
assertNull(linkDiscovery.links.get(expectedLink));
// Sending in the LLDP packet should cause the link to be created
Command command = linkDiscovery.handleLldp(lldpPacket, 2L, pi, (short) 2);
assertEquals(Command.STOP, command);
assertNotNull(linkDiscovery.links.get(expectedLink));
}
}