/**
* 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.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.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.protocol.OFFlowDelete;
import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
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.internal.IOFSwitchService;
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.flowcache.PriorityPendingQueue.EventPriority;
import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
import net.floodlightcontroller.linkdiscovery.internal.LinkInfo;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.linkdiscovery.Link;
import net.floodlightcontroller.topology.ITopologyListener;
import net.floodlightcontroller.topology.ITopologyService;
import net.floodlightcontroller.util.OFMatchWithSwDpid;
/**
* 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
*/
@Deprecated
public class PortDownReconciliation implements IFloodlightModule,
ITopologyListener, IFlowReconcileListener {
protected static Logger log = LoggerFactory.getLogger(PortDownReconciliation.class);
protected ITopologyService topology;
protected IOFSwitchService switchService;
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<OFFlowStatsReply> statsReply;
// ITopologyListener
@Override
public void topologyChanged(List<LDUpdate> appliedUpdates) {
for (LDUpdate ldu : appliedUpdates) {
if (ldu.getOperation()
.equals(ILinkDiscovery.UpdateOperation.PORT_DOWN)) {
// Get the switch ID for the OFMatchWithSwDpid object
IOFSwitch affectedSwitch = switchService.getSwitch(ldu.getSrc());
// Create an OFMatchReconcile object
OFMatchReconcile ofmr = new OFMatchReconcile();
// Generate an OFMatch objects for the OFMatchWithSwDpid object
Match match = affectedSwitch.getOFFactory().buildMatch().build(); // nothing specific set, so all wildcarded
// Generate the OFMatchWithSwDpid
OFMatchWithSwDpid ofmatchsw = new OFMatchWithSwDpid(match, affectedSwitch.getId());
// 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, EventPriority.HIGH);
}
}
}
@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 {
switchService = context.getServiceImpl(IOFSwitchService.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(this);
frm.addFlowReconcileListener(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 = switchService.getSwitch(ofmr.ofmWithSwDpid.getDpid());
// Map data structure that holds the invalid matches and the
// ingress ports of those matches
Map<OFPort, List<Match>> invalidBaseIngressAndMatches = new HashMap<OFPort, List<Match>>();
// Get the invalid flows
List<OFFlowStatsReply> 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 (OFFlowStatsReply flow : flows) {
// Create a reference to the match for ease
for (OFFlowStatsEntry entry : flow.getEntries()) {
Match match = entry.getMatch();
// Here we utilize an index of input ports which point
// to multiple invalid matches
if (invalidBaseIngressAndMatches.containsKey(match.get(MatchField.IN_PORT)))
// If the input port is already in the index, add
// the match to it's list
invalidBaseIngressAndMatches.get(match.get(MatchField.IN_PORT))
.add(match);
else {
// Otherwise create a new list and add it to the
// index
List<Match> matches = new ArrayList<Match>();
matches.add(match);
invalidBaseIngressAndMatches.put(match.get(MatchField.IN_PORT), 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<OFPort, List<Match>>> neighborSwitches = new HashMap<IOFSwitch, Map<OFPort, List<Match>>>();
// 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<OFPort, List<Match>> 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<OFPort, List<Match>> invalidNeighborOutportAndMatch = new HashMap<OFPort, List<Match>>();
// 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(switchService.getSwitch(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<OFFlowStatsReply> getFlows(IOFSwitch sw, OFPort outPort) {
statsReply = new ArrayList<OFFlowStatsReply>();
List<OFFlowStatsReply> values = null;
Future<List<OFFlowStatsReply>> future;
// Statistics request object for getting flows
OFFlowStatsRequest req = sw.getOFFactory().buildFlowStatsRequest()
.setMatch(sw.getOFFactory().buildMatch().build())
.setOutPort(outPort)
.setTableId(TableId.ALL)
.build();
try {
// System.out.println(sw.getStatistics(req));
future = sw.writeStatsRequest(req);
values = future.get(10, TimeUnit.SECONDS);
if (values != null) {
for (OFFlowStatsReply stat : values) {
statsReply.add(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, OFPort outPort) {
// Delete all pre-existing flows with the same output action port or
// outPort
Match match = sw.getOFFactory().buildMatch().build();
OFFlowDelete fm = sw.getOFFactory().buildFlowDelete()
.setMatch(match)
.setOutPort(outPort)
.build();
try {
sw.write(fm);
} 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, Match match, OFPort outPort) {
// Delete pre-existing flows with the same match, and output action port
// or outPort
OFFlowDelete fm = sw.getOFFactory().buildFlowDelete()
.setMatch(match)
.setOutPort(outPort)
.build();
try {
sw.write(fm);
} 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<OFPort, List<Match>> invalidOutportAndMatch) {
log.debug("Deleting invalid flows on switch : " + sw.getId());
// A map that holds the input ports and invalid matches on a switch
Map<OFPort, List<Match>> invalidNeighborIngressAndMatches = new HashMap<OFPort, List<Match>>();
for (OFPort outPort : invalidOutportAndMatch.keySet()) {
// Get the flows on the switch
List<OFFlowStatsReply> flows = getFlows(sw, outPort);
// Analyze all the flows with outPorts pointing to problematic route
for (OFFlowStatsReply flow : flows) {
for (OFFlowStatsEntry entry : flow.getEntries()) {
// Loop through all the problematic matches
for (Match match : invalidOutportAndMatch.get(outPort)) {
// Compare the problematic matches with the match of the
// flow on the switch
if (entry.getMatch().get(MatchField.ETH_DST).equals(match.get(MatchField.ETH_DST))
&& entry.getMatch().get(MatchField.ETH_SRC).equals(match.get(MatchField.ETH_SRC))
&& entry.getMatch().get(MatchField.ETH_TYPE).equals(match.get(MatchField.ETH_TYPE))
&& entry.getMatch().get(MatchField.VLAN_VID).equals(match.get(MatchField.VLAN_VID))
&& entry.getMatch().get(MatchField.IPV4_DST).equals(match.get(MatchField.IPV4_DST))
&& entry.getMatch().get(MatchField.IP_PROTO).equals(match.get(MatchField.IP_PROTO))
&& entry.getMatch().get(MatchField.IPV4_SRC).equals(match.get(MatchField.IPV4_SRC))
&& entry.getMatch().get(MatchField.IP_DSCP).equals(match.get(MatchField.IP_DSCP)) // dscp and ecn replace tos
&& entry.getMatch().get(MatchField.IP_ECN).equals(match.get(MatchField.IP_ECN))) {
// Here we utilize an index of input ports which point
// to multiple invalid matches
if (invalidNeighborIngressAndMatches.containsKey(match.get(MatchField.IN_PORT)))
// If the input port is already in the index, add
// the match to it's list
invalidNeighborIngressAndMatches.get(match.get(MatchField.IN_PORT))
.add(match);
else {
// Otherwise create a new list and add it to the
// index
List<Match> matches = new ArrayList<Match>();
matches.add(match);
invalidNeighborIngressAndMatches.put(match.get(MatchField.IN_PORT), matches);
}
// Remove flows from the switch with the invalid match
// and outPort
clearFlowMods(sw, entry.getMatch(), outPort);
}
}
}
}
// Create a list of neighboring switches we need to check for
// invalid flows
Map<IOFSwitch, Map<OFPort, List<Match>>> neighborSwitches = new HashMap<IOFSwitch, Map<OFPort, List<Match>>>();
// Loop through all the links
for (Link link : links.keySet()) {
// Filter out links we care about
if (link.getDst().equals(sw.getId())) {
// Loop through the ingressPorts that are involved in
// invalid flows on neighboring switches
for (Entry<OFPort, List<Match>> ingressPort : invalidNeighborIngressAndMatches.entrySet()) {
// Filter out invalid links by matching the link
// destination port to our invalid flows ingress port
if (link.getDstPort().equals(ingressPort.getKey())) {
// Generate a match and outPort map since I don't
// want to create an object
Map<OFPort, List<Match>> invalidNeighborOutportAndMatch = new HashMap<OFPort, List<Match>>();
invalidNeighborOutportAndMatch.put(link.getSrcPort(),
ingressPort.getValue());
// Link a neighbor switch's invalid match and
// outport to their Switch object
neighborSwitches.put(switchService.getSwitch(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));
}
}
}
}