/**
* 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.
**/
/**
* Floodlight
* A BSD licensed, Java based OpenFlow controller
*
* Floodlight is a Java based OpenFlow controller originally written by David Erickson at Stanford
* University. It is available under the BSD license.
*
* For documentation, forums, issue tracking and more visit:
*
* http://www.openflowhub.org/display/Floodlight/Floodlight+Home
**/
package net.floodlightcontroller.learningswitch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IControllerCompletionListener;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
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.core.types.MacVlanPair;
import net.floodlightcontroller.debugcounter.IDebugCounter;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.util.OFMessageUtils;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.U64;
import org.projectfloodlight.openflow.types.VlanVid;
import org.projectfloodlight.openflow.util.LRULinkedHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// paag: with IControllerCompletionListener that logswhen an input event has been consumed
public class LearningSwitch
implements IFloodlightModule, ILearningSwitchService, IOFMessageListener, IControllerCompletionListener {
protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
// Module dependencies
protected IFloodlightProviderService floodlightProviderService;
protected IRestApiService restApiService;
protected IDebugCounterService debugCounterService;
private IDebugCounter counterFlowMod;
private IDebugCounter counterPacketOut;
// Stores the learned state for each switch
protected Map<IOFSwitch, Map<MacVlanPair, OFPort>> macVlanToSwitchPortMap;
// flow-mod - for use in the cookie
public static final int LEARNING_SWITCH_APP_ID = 1;
// LOOK! This should probably go in some class that encapsulates
// the app cookie management
public static final int APP_ID_BITS = 12;
public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
// more flow-mod defaults
protected static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
protected static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
protected static short FLOWMOD_PRIORITY = 100;
// for managing our map sizes
protected static final int MAX_MACS_PER_SWITCH = 1000;
// normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
// set this flag to true if you want to see the completion messages and
// have the switch flushed
protected final boolean flushAtCompletion = false;
/**
* @param floodlightProvider the floodlightProvider to set
*/
public void setFloodlightProvider(IFloodlightProviderService floodlightProviderService) {
this.floodlightProviderService = floodlightProviderService;
}
@Override
public String getName() {
return "learningswitch";
}
/**
* Adds a host to the MAC/VLAN->SwitchPort mapping
* @param sw The switch to add the mapping to
* @param mac The MAC address of the host to add
* @param vlan The VLAN that the host is on
* @param portVal The switchport that the host is on
*/
protected void addToPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan, OFPort portVal) {
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (vlan == VlanVid.FULL_MASK || vlan == null) {
vlan = VlanVid.ofVlan(0);
}
if (swMap == null) {
// May be accessed by REST API so we need to make it thread safe
swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair, OFPort>(MAX_MACS_PER_SWITCH));
macVlanToSwitchPortMap.put(sw, swMap);
}
swMap.put(new MacVlanPair(mac, vlan), portVal);
}
/**
* Removes a host from the MAC/VLAN->SwitchPort mapping
* @param sw The switch to remove the mapping from
* @param mac The MAC address of the host to remove
* @param vlan The VLAN that the host is on
*/
protected void removeFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null) {
swMap.remove(new MacVlanPair(mac, vlan));
}
}
/**
* Get the port that a MAC/VLAN pair is associated with
* @param sw The switch to get the mapping from
* @param mac The MAC address to get
* @param vlan The VLAN number to get
* @return The port the host is on
*/
public OFPort getFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK || vlan == null) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null) {
return swMap.get(new MacVlanPair(mac, vlan));
}
// if none found
return null;
}
/**
* Clears the MAC/VLAN -> SwitchPort map for all switches
*/
public void clearLearnedTable() {
macVlanToSwitchPortMap.clear();
}
/**
* Clears the MAC/VLAN -> SwitchPort map for a single switch
* @param sw The switch to clear the mapping for
*/
public void clearLearnedTable(IOFSwitch sw) {
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null) {
swMap.clear();
}
}
@Override
public synchronized Map<IOFSwitch, Map<MacVlanPair, OFPort>> getTable() {
return macVlanToSwitchPortMap;
}
/**
* Writes a OFFlowMod to a switch.
* @param sw The switch tow rite the flowmod to.
* @param command The FlowMod actions (add, delete, etc).
* @param bufferId The buffer ID if the switch has buffered the packet.
* @param match The OFMatch structure to write.
* @param outPort The switch port to output it to.
*/
private void writeFlowMod(IOFSwitch sw, OFFlowModCommand command, OFBufferId bufferId,
Match match, OFPort outPort) {
// from openflow 1.0 spec - need to set these on a struct ofp_flow_mod:
// struct ofp_flow_mod {
// struct ofp_header header;
// struct ofp_match match; /* Fields to match */
// uint64_t cookie; /* Opaque controller-issued identifier. */
//
// /* Flow actions. */
// uint16_t command; /* One of OFPFC_*. */
// uint16_t idle_timeout; /* Idle time before discarding (seconds). */
// uint16_t hard_timeout; /* Max time before discarding (seconds). */
// uint16_t priority; /* Priority level of flow entry. */
// uint32_t buffer_id; /* Buffered packet to apply to (or -1).
// Not meaningful for OFPFC_DELETE*. */
// uint16_t out_port; /* For OFPFC_DELETE* commands, require
// matching entries to include this as an
// output port. A value of OFPP_NONE
// indicates no restriction. */
// uint16_t flags; /* One of OFPFF_*. */
// struct ofp_action_header actions[0]; /* The action length is inferred
// from the length field in the
// header. */
// };
OFFlowMod.Builder fmb;
if (command == OFFlowModCommand.DELETE) {
fmb = sw.getOFFactory().buildFlowDelete();
} else {
fmb = sw.getOFFactory().buildFlowAdd();
}
fmb.setMatch(match);
fmb.setCookie((U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE)));
fmb.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT);
fmb.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT);
fmb.setPriority(LearningSwitch.FLOWMOD_PRIORITY);
fmb.setBufferId(bufferId);
fmb.setOutPort((command == OFFlowModCommand.DELETE) ? OFPort.ANY : outPort);
Set<OFFlowModFlags> sfmf = new HashSet<OFFlowModFlags>();
if (command != OFFlowModCommand.DELETE) {
sfmf.add(OFFlowModFlags.SEND_FLOW_REM);
}
fmb.setFlags(sfmf);
// set the ofp_action_header/out actions:
// from the openflow 1.0 spec: need to set these on a struct ofp_action_output:
// uint16_t type; /* OFPAT_OUTPUT. */
// uint16_t len; /* Length is 8. */
// uint16_t port; /* Output port. */
// uint16_t max_len; /* Max length to send to controller. */
// type/len are set because it is OFActionOutput,
// and port, max_len are arguments to this constructor
List<OFAction> al = new ArrayList<OFAction>();
al.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).setMaxLen(0xffFFffFF).build());
fmb.setActions(al);
if (log.isTraceEnabled()) {
log.trace("{} {} flow mod {}",
new Object[]{ sw, (command == OFFlowModCommand.DELETE) ? "deleting" : "adding", fmb.build() });
}
counterFlowMod.increment();
// and write it out
sw.write(fmb.build());
}
/**
* Pushes a packet-out to a switch. The assumption here is that
* the packet-in was also generated from the same switch. Thus, if the input
* port of the packet-in and the outport are the same, the function will not
* push the packet-out.
* @param sw switch that generated the packet-in, and from which packet-out is sent
* @param match OFmatch
* @param pi packet-in
* @param outport output port
*/
private void pushPacket(IOFSwitch sw, Match match, OFPacketIn pi, OFPort outport) {
if (pi == null) {
return;
}
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
// The assumption here is (sw) is the switch that generated the
// packet-in. If the input port is the same as output port, then
// the packet-out should be ignored.
if (inPort.equals(outport)) {
if (log.isDebugEnabled()) {
log.debug("Attempting to do packet-out to the same " +
"interface as packet-in. Dropping packet. " +
" SrcSwitch={}, match = {}, pi={}",
new Object[]{sw, match, pi});
return;
}
}
if (log.isTraceEnabled()) {
log.trace("PacketOut srcSwitch={} match={} pi={}",
new Object[] {sw, match, pi});
}
OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut();
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(sw.getOFFactory().actions().buildOutput().setPort(outport).setMaxLen(0xffFFffFF).build());
pob.setActions(actions);
// If the switch doens't support buffering set the buffer id to be none
// otherwise it'll be the the buffer id of the PacketIn
if (sw.getBuffers() == 0) {
// We set the PI buffer id here so we don't have to check again below
pi = pi.createBuilder().setBufferId(OFBufferId.NO_BUFFER).build();
pob.setBufferId(OFBufferId.NO_BUFFER);
} else {
pob.setBufferId(pi.getBufferId());
}
pob.setInPort(inPort);
// If the buffer id is none or the switch doesn's support buffering
// we send the data with the packet out
if (pi.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = pi.getData();
pob.setData(packetData);
}
counterPacketOut.increment();
sw.write(pob.build());
}
/**
* Writes an OFPacketOut message to a switch.
* @param sw The switch to write the PacketOut to.
* @param packetInMessage The corresponding PacketIn.
* @param egressPort The switchport to output the PacketOut.
*/
private void writePacketOutForPacketIn(IOFSwitch sw, OFPacketIn packetInMessage, OFPort egressPort) {
OFMessageUtils.writePacketOutForPacketIn(sw, packetInMessage, egressPort);
counterPacketOut.increment();
}
protected Match createMatchFromPacket(IOFSwitch sw, OFPort inPort, FloodlightContext cntx) {
// The packet in match will only contain the port number.
// We need to add in specifics for the hosts we're routing between.
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
VlanVid vlan = VlanVid.ofVlan(eth.getVlanID());
MacAddress srcMac = eth.getSourceMACAddress();
MacAddress dstMac = eth.getDestinationMACAddress();
Match.Builder mb = sw.getOFFactory().buildMatch();
mb.setExact(MatchField.IN_PORT, inPort)
.setExact(MatchField.ETH_SRC, srcMac)
.setExact(MatchField.ETH_DST, dstMac);
if (!vlan.equals(VlanVid.ZERO)) {
mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan));
}
return mb.build();
}
/**
* Processes a OFPacketIn message. If the switch has learned the MAC/VLAN to port mapping
* for the pair it will write a FlowMod for. If the mapping has not been learned the
* we will flood the packet.
* @param sw
* @param pi
* @param cntx
* @return
*/
private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
/* Read packet header attributes into Match */
Match m = createMatchFromPacket(sw, inPort, cntx);
MacAddress sourceMac = m.get(MatchField.ETH_SRC);
MacAddress destMac = m.get(MatchField.ETH_DST);
VlanVid vlan = m.get(MatchField.VLAN_VID) == null ? VlanVid.ZERO : m.get(MatchField.VLAN_VID).getVlanVid();
if (sourceMac == null) {
sourceMac = MacAddress.NONE;
}
if (destMac == null) {
destMac = MacAddress.NONE;
}
if (vlan == null) {
vlan = VlanVid.ZERO;
}
if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L) {
if (log.isTraceEnabled()) {
log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
new Object[]{ sw, vlan, destMac.toString() });
}
return Command.STOP;
}
if ((sourceMac.getLong() & 0x010000000000L) == 0) {
// If source MAC is a unicast address, learn the port for this MAC/VLAN
this.addToPortMap(sw, sourceMac, vlan, inPort);
}
// Now output flow-mod and/or packet
OFPort outPort = getFromPortMap(sw, destMac, vlan);
if (outPort == null) {
// If we haven't learned the port for the dest MAC/VLAN, flood it
// Don't flood broadcast packets if the broadcast is disabled.
// XXX For LearningSwitch this doesn't do much. The sourceMac is removed
// from port map whenever a flow expires, so you would still see
// a lot of floods.
this.writePacketOutForPacketIn(sw, pi, OFPort.FLOOD);
} else if (outPort.equals(inPort)) {
log.trace("ignoring packet that arrived on same port as learned destination:"
+ " switch {} vlan {} dest MAC {} port {}",
new Object[]{ sw, vlan, destMac.toString(), outPort.getPortNumber() });
} else {
// Add flow table entry matching source MAC, dest MAC, VLAN and input port
// that sends to the port we previously learned for the dest MAC/VLAN. Also
// add a flow table entry with source and destination MACs reversed, and
// input and output ports reversed. When either entry expires due to idle
// timeout, remove the other one. This ensures that if a device moves to
// a different port, a constant stream of packets headed to the device at
// its former location does not keep the stale entry alive forever.
// FIXME: current HP switches ignore DL_SRC and DL_DST fields, so we have to match on
// NW_SRC and NW_DST as well
// We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
this.pushPacket(sw, m, pi, outPort);
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, m, outPort);
if (LEARNING_SWITCH_REVERSE_FLOW) {
Match.Builder mb = m.createBuilder();
mb.setExact(MatchField.ETH_SRC, m.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, m.get(MatchField.ETH_SRC))
.setExact(MatchField.IN_PORT, outPort);
if (m.get(MatchField.VLAN_VID) != null) {
mb.setExact(MatchField.VLAN_VID, m.get(MatchField.VLAN_VID));
}
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, mb.build(), inPort);
}
}
return Command.CONTINUE;
}
/**
* Processes a flow removed message. We will delete the learned MAC/VLAN mapping from
* the switch's table.
* @param sw The switch that sent the flow removed message.
* @param flowRemovedMessage The flow removed message.
* @return Whether to continue processing this message or stop.
*/
private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
if (!flowRemovedMessage.getCookie().equals(U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE))) {
return Command.CONTINUE;
}
if (log.isTraceEnabled()) {
log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
}
Match match = flowRemovedMessage.getMatch();
// When a flow entry expires, it means the device with the matching source
// MAC address and VLAN either stopped sending packets or moved to a different
// port. If the device moved, we can't know where it went until it sends
// another packet, allowing us to re-learn its port. Meanwhile we remove
// it from the macVlanToPortMap to revert to flooding packets to this device.
this.removeFromPortMap(sw, match.get(MatchField.ETH_SRC),
match.get(MatchField.VLAN_VID) == null
? VlanVid.ZERO
: match.get(MatchField.VLAN_VID).getVlanVid());
// Also, if packets keep coming from another device (e.g. from ping), the
// corresponding reverse flow entry will never expire on its own and will
// send the packets to the wrong port (the matching input port of the
// expired flow entry), so we must delete the reverse entry explicitly.
Match.Builder mb = sw.getOFFactory().buildMatch();
mb.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
if (match.get(MatchField.VLAN_VID) != null) {
mb.setExact(MatchField.VLAN_VID, match.get(MatchField.VLAN_VID));
}
this.writeFlowMod(sw, OFFlowModCommand.DELETE, OFBufferId.NO_BUFFER, mb.build(), match.get(MatchField.IN_PORT));
return Command.CONTINUE;
}
// IOFMessageListener
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
case FLOW_REMOVED:
return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
case ERROR:
log.info("received an error {} from switch {}", msg, sw);
return Command.CONTINUE;
default:
log.error("received an unexpected message {} from switch {}", msg, sw);
return Command.CONTINUE;
}
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
return false;
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
return false;
}
// IFloodlightModule
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(ILearningSwitchService.class);
return l;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
Map<Class<? extends IFloodlightService>, IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
m.put(ILearningSwitchService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(IDebugCounterService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
macVlanToSwitchPortMap = new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair, OFPort>>();
floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
debugCounterService = context.getServiceImpl(IDebugCounterService.class);
restApiService = context.getServiceImpl(IRestApiService.class);
}
@Override
public void startUp(FloodlightModuleContext context) {
// paag: register the IControllerCompletionListener
floodlightProviderService.addCompletionListener(this);
floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
restApiService.addRestletRoutable(new LearningSwitchWebRoutable());
// read our config options
Map<String, String> configOptions = context.getConfigParams(this);
try {
String idleTimeout = configOptions.get("idletimeout");
if (idleTimeout != null) {
FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow idle timeout, " +
"using default of {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
}
try {
String hardTimeout = configOptions.get("hardtimeout");
if (hardTimeout != null) {
FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow hard timeout, " +
"using default of {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
}
try {
String priority = configOptions.get("priority");
if (priority != null) {
FLOWMOD_PRIORITY = Short.parseShort(priority);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow priority, " +
"using default of {}",
FLOWMOD_PRIORITY);
}
log.debug("FlowMod idle timeout set to {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
log.debug("FlowMod hard timeout set to {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
log.debug("FlowMod priority set to {}", FLOWMOD_PRIORITY);
debugCounterService.registerModule(this.getName());
counterFlowMod = debugCounterService.registerCounter(this.getName(), "flow-mods-written", "Flow mods written to switches by LearningSwitch", MetaData.WARN);
counterPacketOut = debugCounterService.registerCounter(this.getName(), "packet-outs-written", "Packet outs written to switches by LearningSwitch", MetaData.WARN);
}
// paag: to show the IControllerCompletion concept
// CAVEAT: extremely noisy when tracking enabled
@Override
public void onMessageConsumed(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
if (this.flushAtCompletion) {
log.debug("Learning switch: ended processing packet {}",msg.toString());
}
}
}