/** * 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)); } }