diff --git a/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java b/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java index 68831f46eb0ba9181b948aa28ecbc075b6f0fd96..4bbab302e46009fd6b31670f7dd051c5d88abd5f 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java +++ b/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java @@ -49,6 +49,8 @@ public class OFMatchReconcile { public String newAppInstName; /** The reconcile action. */ public ReconcileAction rcAction; + /** Outport in the event of UPDATE_PATH action**/ + public short outPort; // The context for the reconcile action public FloodlightContext cntx; @@ -72,6 +74,7 @@ public class OFMatchReconcile { appInstName = copy.appInstName; newAppInstName = copy.newAppInstName; rcAction = copy.rcAction; + outPort = copy.outPort; cntx = new FloodlightContext(); } @@ -79,6 +82,6 @@ public class OFMatchReconcile { public String toString() { return "OFMatchReconcile [" + ofmWithSwDpid + " priority=" + priority + " action=" + action + " cookie=" + cookie + " appInstName=" + appInstName + " newAppInstName=" + newAppInstName + - " ReconcileAction=" + rcAction + "]"; + " ReconcileAction=" + rcAction + "outPort=" + outPort + "]"; } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java b/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java new file mode 100644 index 0000000000000000000000000000000000000000..7a4275a33c808bcab9e987aa146cbc57725277dc --- /dev/null +++ b/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java @@ -0,0 +1,499 @@ +/** +* Copyright 2012, Jason Parraga, Marist College +* +* 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.flowcache; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFMatchWithSwDpid; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.OFType; +import org.openflow.protocol.statistics.OFFlowStatisticsReply; +import org.openflow.protocol.statistics.OFFlowStatisticsRequest; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFStatisticsType; +import org.openflow.util.HexString; +import org.openflow.util.U16; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.FloodlightModuleException; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.flowcache.IFlowReconcileListener; +import net.floodlightcontroller.flowcache.IFlowReconcileService; +import net.floodlightcontroller.flowcache.OFMatchReconcile; +import net.floodlightcontroller.linkdiscovery.ILinkDiscovery; +import net.floodlightcontroller.linkdiscovery.LinkInfo; +import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate; +import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; +import net.floodlightcontroller.routing.Link; +import net.floodlightcontroller.topology.ITopologyListener; +import net.floodlightcontroller.topology.ITopologyService; + +/** + * Flow reconciliation module that is triggered by PORT_DOWN events. This module + * will recursively trace back all flows from the immediately affected switch + * and remove them (specifically flows with an idle timeout that would not be + * exhausted). Once the flows are deleted Floodlight will re-evaluate the path + * the traffic should take with it's updated topology map. + * + * @author Jason Parraga + */ + +public class PortDownReconciliation implements IFloodlightModule, + ITopologyListener, IFlowReconcileListener { + protected static Logger log = LoggerFactory + .getLogger(PortDownReconciliation.class); + + protected ITopologyService topology; + protected IFloodlightProviderService floodlightProvider; + protected IFlowReconcileService frm; + protected ILinkDiscoveryService lds; + protected Map<Link, LinkInfo> links; + protected FloodlightContext cntx; + protected static boolean waiting = false; + protected int statsQueryXId; + protected static List<OFFlowStatisticsReply> statsReply; + + // ITopologyListener + @Override + public void topologyChanged() { + for (LDUpdate ldu : topology.getLastLinkUpdates()) { + if (ldu.getOperation().equals( + ILinkDiscovery.UpdateOperation.PORT_DOWN)) { + + // Get the switch ID for the OFMatchWithSwDpid object + long affectedSwitch = floodlightProvider.getSwitches() + .get(ldu.getSrc()).getId(); + + // Create an OFMatchReconcile object + OFMatchReconcile ofmr = new OFMatchReconcile(); + + // Generate an OFMatch objects for the OFMatchWithSwDpid object + OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + + // Generate the OFMatchWithSwDpid + OFMatchWithSwDpid ofmatchsw = new OFMatchWithSwDpid(match, + affectedSwitch); + + // Set the action to update the path to remove flows routing + // towards the downed port + ofmr.rcAction = OFMatchReconcile.ReconcileAction.UPDATE_PATH; + + // Set the match, with the switch dpid + ofmr.ofmWithSwDpid = ofmatchsw; + + // Assign the downed port to the OFMatchReconcile's outPort data member (I added this to + // the OFMatchReconcile class) + ofmr.outPort = ldu.getSrcPort(); + + // Tell the reconcile manager to reconcile matching flows + frm.reconcileFlow(ofmr); + } + } + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + return null; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + return null; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(ITopologyService.class); + l.add(IFlowReconcileService.class); + l.add(ILinkDiscoveryService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + floodlightProvider = context + .getServiceImpl(IFloodlightProviderService.class); + topology = context.getServiceImpl(ITopologyService.class); + frm = context.getServiceImpl(IFlowReconcileService.class); + lds = context.getServiceImpl(ILinkDiscoveryService.class); + cntx = new FloodlightContext(); + } + + @Override + public void startUp(FloodlightModuleContext context) { + topology.addListener((ITopologyListener) this); + frm.addFlowReconcileListener((IFlowReconcileListener) this); + } + + @Override + public String getName() { + return "portdownreconciliation"; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return true; + } + + /** + * Base case for the reconciliation of flows. This is triggered at the + * switch which is immediately affected by the PORT_DOWN event + * + * @return the Command whether to STOP or Continue + */ + @Override + public net.floodlightcontroller.core.IListener.Command reconcileFlows( + ArrayList<OFMatchReconcile> ofmRcList) { + if (lds != null) { + links = new HashMap<Link, LinkInfo>(); + // Get all the switch links from the topology + if (lds.getLinks() != null) + links.putAll(lds.getLinks()); + + for (OFMatchReconcile ofmr : ofmRcList) { + // We only care about OFMatchReconcile objects that wish to + // update the path to a switch + if (ofmr.rcAction + .equals(OFMatchReconcile.ReconcileAction.UPDATE_PATH)) { + // Get the switch object from the OFMatchReconcile + IOFSwitch sw = floodlightProvider.getSwitches().get( + ofmr.ofmWithSwDpid.getSwitchDataPathId()); + + // Map data structure that holds the invalid matches and the ingress ports of those matches + Map<Short, List<OFMatch>> invalidBaseIngressAndMatches = new HashMap<Short, List<OFMatch>>(); + + // Get the invalid flows + List<OFFlowStatisticsReply> flows = getFlows(sw, + ofmr.outPort); + + // Analyze all the flows with outPorts equaling the downed + // port and extract OFMatch's to trace back to neighbors + for (OFFlowStatisticsReply flow : flows) { + // Create a reference to the match for ease + OFMatch match = flow.getMatch(); + + // Here we utilize an index of input ports which point + // to multiple invalid matches + if (invalidBaseIngressAndMatches.containsKey(match + .getInputPort())) + // If the input port is already in the index, add + // the match to it's list + invalidBaseIngressAndMatches.get( + match.getInputPort()).add(match); + else { + // Otherwise create a new list and add it to the + // index + List<OFMatch> matches = new ArrayList<OFMatch>(); + matches.add(match); + invalidBaseIngressAndMatches.put( + match.getInputPort(), matches); + } + } + + // Remove invalid flows from the base switch, if they exist + if (!flows.isEmpty()) { + log.debug("Removing flows on switch : " + sw.getId() + + " with outport: " + ofmr.outPort); + clearFlowMods(sw, ofmr.outPort); + } + + // Create a list of neighboring switches we need to remove + // invalid flows from + Map<IOFSwitch, Map<Short, List<OFMatch>>> neighborSwitches = new HashMap<IOFSwitch, Map<Short, List<OFMatch>>>(); + + // Loop through all the links + for (Link link : links.keySet()) { + // Filter out links we care about + if (link.getDst() == sw.getId()) { + // Loop through the links to neighboring switches + // which have invalid flows + for (Entry<Short, List<OFMatch>> invalidBaseIngressAndMatch : invalidBaseIngressAndMatches + .entrySet()) { + // Find links on the network which link to the + // ingress ports that have invalidly routed + // flows + if (link.getDstPort() == invalidBaseIngressAndMatch + .getKey()) { + Map<Short, List<OFMatch>> invalidNeighborOutportAndMatch = new HashMap<Short, List<OFMatch>>(); + // Insert the neighbor's outPort to the base + // switch and the invalid match + invalidNeighborOutportAndMatch.put(link + .getSrcPort(), + invalidBaseIngressAndMatch + .getValue()); + // Link a neighbor switch's invalid match + // and outport to their Switch object + neighborSwitches.put(floodlightProvider + .getSwitches().get(link.getSrc()), + invalidNeighborOutportAndMatch); + } + } + } + } + log.debug("We have " + neighborSwitches.size() + + " neighboring switches to deal with!"); + // Loop through all the switches we found to have potential + // issues + for (IOFSwitch neighborSwitch : neighborSwitches.keySet()) { + log.debug("NeighborSwitch ID : " + + neighborSwitch.getId()); + if (neighborSwitches.get(neighborSwitch) != null) + deleteInvalidFlows(neighborSwitch, + neighborSwitches.get(neighborSwitch)); + } + } + return Command.CONTINUE; + } + } else { + log.error("Link Discovery Service Is Null"); + } + return Command.CONTINUE; + } + /** + * + * @param sw + * the switch object that we wish to get flows from + * @param outPort + * the output action port we wish to find flows with + * @return a list of OFFlowStatisticsReply objects or essentially flows + */ + public List<OFFlowStatisticsReply> getFlows(IOFSwitch sw, Short outPort) { + + statsReply = new ArrayList<OFFlowStatisticsReply>(); + List<OFStatistics> values = null; + Future<List<OFStatistics>> future; + + // Statistics request object for getting flows + OFStatisticsRequest req = new OFStatisticsRequest(); + req.setStatisticType(OFStatisticsType.FLOW); + int requestLength = req.getLengthU(); + OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest(); + specificReq.setMatch(new OFMatch().setWildcards(0xffffffff)); + specificReq.setOutPort(outPort); + specificReq.setTableId((byte) 0xff); + req.setStatistics(Collections.singletonList((OFStatistics)specificReq)); + requestLength += specificReq.getLength(); + req.setLengthU(requestLength); + + try { + //System.out.println(sw.getStatistics(req)); + future = sw.getStatistics(req); + values = future.get(10, TimeUnit.SECONDS); + if(values != null){ + for(OFStatistics stat : values){ + statsReply.add((OFFlowStatisticsReply)stat); + } + } + } catch (Exception e) { + log.error("Failure retrieving statistics from switch " + sw, e); + } + + return statsReply; + } + + /** + * + * @param sw The switch we wish to remove flows from + * @param outPort The specific Output Action OutPort of specific flows we wish to delete + */ + public void clearFlowMods(IOFSwitch sw, Short outPort) { + // Delete all pre-existing flows with the same output action port or outPort + OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() + .getMessage(OFType.FLOW_MOD)) + .setMatch(match) + .setCommand(OFFlowMod.OFPFC_DELETE) + .setOutPort(outPort) + .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + try { + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(fm); + sw.write(msglist, cntx); + } catch (Exception e) { + log.error("Failed to clear flows on switch {} - {}", this, e); + } + } + + /** + * + * @param sw The switch we wish to remove flows from + * @param match The specific OFMatch object of specific flows we wish to delete + * @param outPort The specific Output Action OutPort of specific flows we wish to delete + */ + public void clearFlowMods(IOFSwitch sw, OFMatch match, Short outPort) { + // Delete pre-existing flows with the same match, and output action port or outPort + match.setWildcards(OFMatch.OFPFW_ALL); + OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() + .getMessage(OFType.FLOW_MOD)) + .setMatch(match) + .setCommand(OFFlowMod.OFPFC_DELETE) + .setOutPort(outPort) + .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + try { + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(fm); + sw.write(msglist, cntx); + } catch (Exception e) { + log.error("Failed to clear flows on switch {} - {}", this, e); + } + } + + /** + * Deletes flows with similar matches and output action ports on the + * specified switch + * + * @param sw + * the switch to query flows on + * @param match + * the problematic OFMatch from the base switch which we wish to + * find and remove + * @param outPort + * the output action port wanted from the flows, which follows + * the route to the base switch + */ + public void deleteInvalidFlows(IOFSwitch sw, + Map<Short, List<OFMatch>> invalidOutportAndMatch) { + log.debug("Deleting invalid flows on switch : " + sw.getId()); + + // A map that holds the input ports and invalid matches on a switch + Map<Short, List<OFMatch>> invalidNeighborIngressAndMatches = new HashMap<Short, List<OFMatch>>(); + + for (Short outPort : invalidOutportAndMatch.keySet()) { + // Get the flows on the switch + List<OFFlowStatisticsReply> flows = getFlows(sw, outPort); + + // Analyze all the flows with outPorts pointing to problematic route + for (OFFlowStatisticsReply flow : flows) { + // Loop through all the problematic matches + for (OFMatch match : invalidOutportAndMatch.get(outPort)) { + // Compare the problematic matches with the match of the flow on the switch + if (HexString.toHexString( + flow.getMatch().getDataLayerDestination()).equals( + HexString.toHexString(match + .getDataLayerDestination())) + && HexString.toHexString( + flow.getMatch().getDataLayerSource()) + .equals(HexString.toHexString(match + .getDataLayerSource())) + && flow.getMatch().getDataLayerType() == match + .getDataLayerType() + && flow.getMatch().getDataLayerVirtualLan() == match + .getDataLayerVirtualLan() + && flow.getMatch().getNetworkDestination() == match + .getNetworkDestination() + && flow.getMatch().getNetworkDestinationMaskLen() == match + .getNetworkDestinationMaskLen() + && flow.getMatch().getNetworkProtocol() == match + .getNetworkProtocol() + && flow.getMatch().getNetworkSource() == match + .getNetworkSource() + && flow.getMatch().getNetworkSourceMaskLen() == match + .getNetworkSourceMaskLen() + && flow.getMatch().getNetworkTypeOfService() == match + .getNetworkTypeOfService()) { + + // Here we utilize an index of input ports which point + // to multiple invalid matches + if (invalidNeighborIngressAndMatches.containsKey(match + .getInputPort())) + // If the input port is already in the index, add + // the match to it's list + invalidNeighborIngressAndMatches.get( + match.getInputPort()).add(match); + else { + // Otherwise create a new list and add it to the + // index + List<OFMatch> matches = new ArrayList<OFMatch>(); + matches.add(match); + invalidNeighborIngressAndMatches.put( + match.getInputPort(), matches); + } + // Remove flows from the switch with the invalid match and outPort + clearFlowMods(sw, flow.getMatch(), outPort); + } + } + } + + // Create a list of neighboring switches we need to check for + // invalid flows + Map<IOFSwitch, Map<Short, List<OFMatch>>> neighborSwitches = new HashMap<IOFSwitch, Map<Short, List<OFMatch>>>(); + + // Loop through all the links + for (Link link : links.keySet()) { + // Filter out links we care about + if (link.getDst() == sw.getId()) { + // Loop through the ingressPorts that are involved in + // invalid flows on neighboring switches + for (Entry<Short, List<OFMatch>> ingressPort : invalidNeighborIngressAndMatches + .entrySet()) { + // Filter out invalid links by matching the link + // destination port to our invalid flows ingress port + if (link.getDstPort() == ingressPort.getKey()) { + // Generate a match and outPort map since I don't + // want to create an object + Map<Short, List<OFMatch>> invalidNeighborOutportAndMatch = new HashMap<Short, List<OFMatch>>(); + invalidNeighborOutportAndMatch.put( + link.getSrcPort(), ingressPort.getValue()); + // Link a neighbor switch's invalid match and + // outport to their Switch object + neighborSwitches.put(floodlightProvider + .getSwitches().get(link.getSrc()), + invalidNeighborOutportAndMatch); + } + } + } + } + log.debug("We have " + neighborSwitches.size() + + " neighbors to deal with!"); + + // Loop through all the neighbor switches we found to have + // invalid matches + for (IOFSwitch neighborSwitch : neighborSwitches.keySet()) { + log.debug("NeighborSwitch ID : " + + neighborSwitch.getId()); + // Recursively seek out and delete invalid flows on the + // neighbor switch + deleteInvalidFlows(neighborSwitch, + neighborSwitches.get(neighborSwitch)); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/net/floodlightcontroller/flowcache/PortDownReconciliationTest.java b/src/test/java/net/floodlightcontroller/flowcache/PortDownReconciliationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..29912bfeb37761e6049918d5ef3f93a8cd27c7d7 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/flowcache/PortDownReconciliationTest.java @@ -0,0 +1,460 @@ +/** + * Copyright 2012, Jason Parraga, Marist College + * + * 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.flowcache; + +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.verify; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import org.easymock.Capture; +import org.easymock.CaptureType; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFMatchWithSwDpid; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.OFType; +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.action.OFActionOutput; +import org.openflow.protocol.statistics.OFFlowStatisticsReply; +import org.openflow.protocol.statistics.OFFlowStatisticsRequest; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.protocol.statistics.OFStatisticsType; +import org.openflow.util.U16; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.test.MockFloodlightProvider; +import net.floodlightcontroller.core.test.MockThreadPoolService; +import net.floodlightcontroller.devicemanager.IEntityClassifierService; +import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; +import net.floodlightcontroller.flowcache.FlowReconcileManager; +import net.floodlightcontroller.flowcache.IFlowReconcileService; +import net.floodlightcontroller.flowcache.OFMatchReconcile; +import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; +import net.floodlightcontroller.linkdiscovery.LinkInfo; +import net.floodlightcontroller.routing.Link; +import net.floodlightcontroller.test.FloodlightTestCase; +import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.topology.ITopologyService; + +/** + * Unit test for PortDownReconciliation. To test the class I have generated + * there very simple network topologies. an OFMatchReconcile object with + * information about the PORT_DOWN event is passed to the class, where it begins + * breaking down the information,analyzing the switches for flows and deleting + * those that are invalid. This Test specifically verifies that each switch is + * queried for flows once and is sent the appropriate OFFlowMod delete + * message. + * + * @author Jason Parraga + */ + +public class PortDownReconciliationTest extends FloodlightTestCase { + + protected MockFloodlightProvider mockFloodlightProvider; + protected FloodlightModuleContext fmc; + protected ILinkDiscoveryService lds; + protected FlowReconcileManager flowReconcileMgr; + protected MockThreadPoolService tps; + protected DefaultEntityClassifier entityClassifier; + protected PortDownReconciliation pdr; + protected ITopologyService topology; + protected IOFSwitch sw1, sw2, sw3, sw4; + protected Map<Long, IOFSwitch> switches; + protected Capture<List<OFMessage>> wc1, wc2, wc3, wc4; + protected Capture<FloodlightContext> bc1, bc2, bc3, bc4; + protected OFMessage fmd, fmd2; + protected ArrayList<OFMatchReconcile> lofmr; + protected OFMatchReconcile ofmr; + protected static Logger log; + protected FloodlightContext cntx; + protected List<OFStatistics> statsReply; + + @Before + public void setUp() throws Exception { + super.setUp(); + + log = LoggerFactory.getLogger(PortDownReconciliation.class); + fmc = new FloodlightModuleContext(); + mockFloodlightProvider = getMockFloodlightProvider(); + pdr = new PortDownReconciliation(); + lds = createMock(ILinkDiscoveryService.class); + entityClassifier = new DefaultEntityClassifier(); + tps = new MockThreadPoolService(); + flowReconcileMgr = new FlowReconcileManager(); + topology = createMock(ITopologyService.class); + cntx = new FloodlightContext(); + statsReply = new ArrayList<OFStatistics>(); + + fmc.addService(IThreadPoolService.class, tps); + fmc.addService(IFloodlightProviderService.class, + getMockFloodlightProvider()); + fmc.addService(IFlowReconcileService.class, flowReconcileMgr); + fmc.addService(ITopologyService.class, topology); + fmc.addService(IEntityClassifierService.class, entityClassifier); + fmc.addService(ILinkDiscoveryService.class, lds); + + tps.init(fmc); + flowReconcileMgr.init(fmc); + entityClassifier.init(fmc); + getMockFloodlightProvider().init(fmc); + pdr.init(fmc); + + tps.startUp(fmc); + flowReconcileMgr.startUp(fmc); + entityClassifier.startUp(fmc); + getMockFloodlightProvider().startUp(fmc); + pdr.startUp(fmc); + + // The STATS_REQUEST object used when querying the switches for flows + OFStatisticsRequest req = new OFStatisticsRequest(); + req.setStatisticType(OFStatisticsType.FLOW); + int requestLength = req.getLengthU(); + OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest(); + specificReq.setMatch(new OFMatch().setWildcards(0xffffffff)); + specificReq.setOutPort((short) 3); + specificReq.setTableId((byte) 0xff); + req.setStatistics(Collections.singletonList((OFStatistics) specificReq)); + requestLength += specificReq.getLength(); + req.setLengthU(requestLength); + + // Actions for the STATS_REPLY object + OFFlowMod flow = new OFFlowMod(); + OFActionOutput action = new OFActionOutput((short) 3, (short) 0xffff); + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(action); + + // Match for the STATS_REPLY object + OFMatch m = new OFMatch(); + // Set the incoming port to 1 so that it will find the connected + m.setInputPort((short) 1); + + // STATS_REPLY object + OFFlowStatisticsReply reply = new OFFlowStatisticsReply(); + reply.setActions(actions); + reply.setMatch(m); + // Add the reply to the list of OFStatistics + statsReply.add(reply); + + // Create the STATS_REPLY asynchronous reply object + Callable<List<OFStatistics>> replyFuture = new ReplyFuture(); + // Assign the callable object to a Futuretask so that it will produce future results + FutureTask<List<OFStatistics>> futureStats = new FutureTask<List<OFStatistics>>( + replyFuture); + + // Assign the results of calling the object (the asynchronous reply) + Future<List<OFStatistics>> results = getResults(futureStats); + + // SW1 -- Mock switch for base and multiple switch test case + sw1 = EasyMock.createNiceMock(IOFSwitch.class); + // Expect that the switch's ID is 1 + expect(sw1.getId()).andReturn(1L).anyTimes(); + expect(sw1.getStatistics(req)).andReturn(results).once(); + // Captures to hold resulting flowmod delete messages + wc1 = new Capture<List<OFMessage>>(CaptureType.ALL); + bc1 = new Capture<FloodlightContext>(CaptureType.ALL); + // Capture the parameters passed when sw1.write is invoked + sw1.write(capture(wc1), capture(bc1)); + expectLastCall().once(); + replay(sw1); + + // SW2 -- Mock switch for extended test cases + sw2 = EasyMock.createNiceMock(IOFSwitch.class); + // Expect that the switch's ID is 2 + expect(sw2.getId()).andReturn(2L).anyTimes(); + expect(sw2.getStatistics(req)).andReturn(results).once(); + wc2 = new Capture<List<OFMessage>>(CaptureType.ALL); + bc2 = new Capture<FloodlightContext>(CaptureType.ALL); + // Capture the parameters passwed when sw1.write is invoked + sw2.write(capture(wc2), capture(bc2)); + expectLastCall().anyTimes(); + replay(sw2); + + // SW3 -- Mock switch for extended test cases + sw3 = EasyMock.createNiceMock(IOFSwitch.class); + // Expect that the switch's ID is 3 + expect(sw3.getId()).andReturn(3L).anyTimes(); + expect(sw3.getStatistics(req)).andReturn(results).once(); + wc3 = new Capture<List<OFMessage>>(CaptureType.ALL); + bc3 = new Capture<FloodlightContext>(CaptureType.ALL); + // Capture the parameters passwed when sw1.write is invoked + sw3.write(capture(wc3), capture(bc3)); + expectLastCall().anyTimes(); + replay(sw3); + + // SW4 -- Mock switch for extended test cases + sw4 = EasyMock.createNiceMock(IOFSwitch.class); + // Expect that the switch's ID is 4 + expect(sw4.getId()).andReturn(4L).anyTimes(); + expect(sw4.getStatistics(req)).andReturn(results).once(); + wc4 = new Capture<List<OFMessage>>(CaptureType.ALL); + bc4 = new Capture<FloodlightContext>(CaptureType.ALL); + // Capture the parameters passed when sw1.write is invoked + sw4.write(capture(wc4), capture(bc4)); + expectLastCall().anyTimes(); + replay(sw4); + + // Here we create the OFMatch Reconcile list we wish to pass + lofmr = new ArrayList<OFMatchReconcile>(); + + // Create the only OFMatch Reconcile object that will be in the list + ofmr = new OFMatchReconcile(); + long affectedSwitch = sw1.getId(); + OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + OFMatchWithSwDpid ofmatchsw = new OFMatchWithSwDpid(match, + affectedSwitch); + ofmr.rcAction = OFMatchReconcile.ReconcileAction.UPDATE_PATH; + ofmr.ofmWithSwDpid = ofmatchsw; + + // We'll say port 3 went down + ofmr.outPort = 3; + + // Add the OFMatch Reconcile object to the list + lofmr.add(ofmr); + + // Expected Flow Mod Deletes Messages + // Flow Mod Delete for base switch + fmd = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory() + .getMessage(OFType.FLOW_MOD)) + .setMatch(new OFMatch().setWildcards(OFMatch.OFPFW_ALL)) + .setCommand(OFFlowMod.OFPFC_DELETE) + // Notice we specify an outPort + .setOutPort((short) 3) + .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + + // Flow Mod Delete for the neighborswitches + fmd2 = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory() + .getMessage(OFType.FLOW_MOD)) + // Notice we used the base switch's flow's match + .setMatch(flow.getMatch()) + .setCommand(OFFlowMod.OFPFC_DELETE) + // Notice we specific an outPort + .setOutPort((short) 3) + .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + + } + + // This generates the asynchronous reply to sw.getStatistics() + public Future<List<OFStatistics>> getResults( + FutureTask<List<OFStatistics>> futureStats) { + Thread t = new Thread(futureStats); + t.start(); + return futureStats; + + } + + // Class for the asynchronous reply + public class ReplyFuture implements Callable<List<OFStatistics>> { + @Override + public List<OFStatistics> call() throws Exception { + // return stats reply defined above + return statsReply; + } + } + + /** + * This tests the port down reconciliation in the event that the base switch + * is the only switch involved in the PORT_DOWN event. It simply deletes + * flows concerning the downed port. + * + * @verify checks to see that a general clearFlowMods(Short outPort) is + * called + * @throws Exception + */ + @Test + public void testSingleSwitchPortDownReconciliation() throws Exception { + log.debug("Starting single switch port down reconciliation test"); + // Load the switch map + switches = new HashMap<Long, IOFSwitch>(); + switches.put(1L, sw1); + mockFloodlightProvider.setSwitches(switches); + + // Reconcile flows with specified OFMatchReconcile + pdr.reconcileFlows(lofmr); + // Validate results + verify(sw1); + + assertTrue(wc1.hasCaptured()); + + List<List<OFMessage>> msglist = wc1.getValues(); + + // Make sure the messages we captures correct + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd, m); + } + } + + /** + * This tests the port down reconciliation in the event that the base switch + * is connected to a chain of three switches. It discovers that is has 1 + * neighbor, which recursively finds out that it has 1 neighbor until the + * final switch "sw4" is evaluated, which has no neighbors. + * + * @verify checks to see that a general clearFlowMods(Short outPort) is + * called on the base switch while specific clearFlowMods(OFMatch + * match, Short outPort) are called on the neighboring switches + * @throws Exception + */ + @Test + public void testLinearLinkPortDownReconciliation() throws Exception { + log.debug("Starting linear link port down reconciliation test"); + + // Load the switch map + switches = new HashMap<Long, IOFSwitch>(); + switches.put(1L, sw1); + switches.put(2L, sw2); + switches.put(3L, sw3); + switches.put(4L, sw4); + mockFloodlightProvider.setSwitches(switches); + + // Create the links between the switches + // (Switch 4) --> (Switch 3) --> (Switch 2) --> (Switch 1) + Map<Link, LinkInfo> links = new HashMap<Link, LinkInfo>(); + Link link = new Link(2L, (short) 3, 1L, (short) 1); + Link link2 = new Link(3L, (short) 3, 2L, (short) 1); + Link link3 = new Link(4L, (short) 3, 3L, (short) 1); + LinkInfo linkinfo = null; + links.put(link, linkinfo); + links.put(link2, linkinfo); + links.put(link3, linkinfo); + + // Make sure that the link discovery service provides the link we made + expect(lds.getLinks()).andReturn(links).anyTimes(); + replay(lds); + + // Reconcile flows with specified OFMatchReconcile + pdr.reconcileFlows(lofmr); + // Validate results + verify(sw1, sw2, sw3, sw4); + + // Make sure each capture is not null + assertTrue(wc2.hasCaptured()); + assertTrue(wc3.hasCaptured()); + assertTrue(wc4.hasCaptured()); + + // Make sure each capture has captured the proper Flow Mod Delete + // message + List<List<OFMessage>> msglist = wc2.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + + msglist = wc3.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + + msglist = wc4.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + } + + /** + * This tests the port down reconciliation in the event that the base switch + * has three separate neighboring switches with invalid flows. It discovers + * that is has 3 neighbors and each of them delete flows with the specific + * OFMatch and outPort. + * + * @verify checks to see that a general clearFlowMods(Short outPort) is + * called on the base switch while specific clearFlowMods(OFMatch + * match, Short outPort) are called on the neighboring switches + * @throws Exception + */ + @Test + public void testMultipleLinkPortDownReconciliation() throws Exception { + log.debug("Starting multiple link port down reconciliation test"); + + // Load the switch map + switches = new HashMap<Long, IOFSwitch>(); + switches.put(1L, sw1); + switches.put(2L, sw2); + switches.put(3L, sw3); + switches.put(4L, sw4); + mockFloodlightProvider.setSwitches(switches); + + // Create the links between the switches + // (Switch 4 output port 3) --> (Switch 1 input port 1) + // (Switch 3 output port 3) --> (Switch 1 input port 1) + // (Switch 2 output port 3) --> (Switch 1 input port 1) + Map<Link, LinkInfo> links = new HashMap<Link, LinkInfo>(); + Link link = new Link(2L, (short) 3, 1L, (short) 1); + Link link2 = new Link(3L, (short) 3, 1L, (short) 1); + Link link3 = new Link(4L, (short) 3, 1L, (short) 1); + LinkInfo linkinfo = null; + links.put(link, linkinfo); + links.put(link2, linkinfo); + links.put(link3, linkinfo); + + // Make sure that the link discovery service provides the link we made + expect(lds.getLinks()).andReturn(links).anyTimes(); + replay(lds); + + // Reconcile flows with specified OFMatchReconcile + pdr.reconcileFlows(lofmr); + // Validate results + verify(sw1, sw2, sw3, sw4); + + // Make sure each capture is not null + assertTrue(wc2.hasCaptured()); + assertTrue(wc3.hasCaptured()); + assertTrue(wc4.hasCaptured()); + + // Make sure each capture has captured the proper Flow Mod Delete + // message + List<List<OFMessage>> msglist = wc2.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + + msglist = wc3.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + + msglist = wc4.getValues(); + for (List<OFMessage> m : msglist) { + if (m instanceof OFFlowMod) + assertEquals(fmd2, m); + } + } +} \ No newline at end of file