/**
* 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.flowcache.PriorityPendingQueue.EventPriority;
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(List<LDUpdate> appliedUpdates) {
for (LDUpdate ldu : appliedUpdates) {
if (ldu.getOperation()
.equals(ILinkDiscovery.UpdateOperation.PORT_DOWN)) {
// Get the switch ID for the OFMatchWithSwDpid object
long affectedSwitch = floodlightProvider.getSwitch(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, 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 {
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(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 = floodlightProvider
.getSwitch(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.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<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.queryStatistics(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.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));
}
}
}
}