blob: 2152875b0f54d619747379ba9a680d28d7537ccc [file] [log] [blame]
/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
package net.floodlightcontroller.core.internal;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IListener.Command;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitch.PortChangeType;
import net.floodlightcontroller.core.IOFSwitchFilter;
import net.floodlightcontroller.core.IOFSwitchListener;
import net.floodlightcontroller.core.IUpdate;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
import net.floodlightcontroller.core.annotations.LogMessageDocs;
import net.floodlightcontroller.core.internal.OFChannelHandler.RoleRecvStatus;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.util.ListenerDispatcher;
import net.floodlightcontroller.core.web.CoreWebRoutable;
import net.floodlightcontroller.debugcounter.IDebugCounter;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType;
import net.floodlightcontroller.debugevent.IDebugEventService;
import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn;
import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType;
import net.floodlightcontroller.debugevent.IDebugEventService.EventType;
import net.floodlightcontroller.debugevent.IDebugEventService.MaxEventsRegistered;
import net.floodlightcontroller.debugevent.IEventUpdater;
import net.floodlightcontroller.debugevent.NullDebugEvent;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.util.LoadMonitor;
import net.onrc.onos.core.drivermanager.DriverManager;
import net.onrc.onos.core.linkdiscovery.ILinkDiscoveryService;
import net.onrc.onos.core.packet.Ethernet;
import net.onrc.onos.core.registry.IControllerRegistryService;
import net.onrc.onos.core.registry.IControllerRegistryService.ControlChangeCallback;
import net.onrc.onos.core.registry.RegistryException;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.OnosInstanceId;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.protocol.match.MatchFields;
import org.projectfloodlight.openflow.types.EthType;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.util.HexString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The main controller class. Handles all setup and network listeners -
* Distributed ownership control of switch through IControllerRegistryService
*/
public class Controller implements IFloodlightProviderService {
protected final static Logger log = LoggerFactory.getLogger(Controller.class);
static final String ERROR_DATABASE =
"The controller could not communicate with the system database.";
protected static OFFactory factory13 = OFFactories.getFactory(OFVersion.OF_13);
protected static OFFactory factory10 = OFFactories.getFactory(OFVersion.OF_10);
// connectedSwitches cache contains all connected switch's channelHandlers
// including ones where this controller is a master/equal/slave controller
// as well as ones that have not been activated yet
protected ConcurrentHashMap<Long, OFChannelHandler> connectedSwitches;
// These caches contains only those switches that are active
protected ConcurrentHashMap<Long, IOFSwitch> activeMasterSwitches;
protected ConcurrentHashMap<Long, IOFSwitch> activeEqualSwitches;
// lock to synchronize on, when manipulating multiple caches above
private Object multiCacheLock;
// The controllerNodeIPsCache maps Controller IDs to their IP address.
// It's only used by handleControllerNodeIPsChanged
protected HashMap<String, String> controllerNodeIPsCache;
protected BlockingQueue<IUpdate> updates;
protected ConcurrentMap<OFType, ListenerDispatcher<OFType, IOFMessageListener>> messageListeners;
protected Set<IOFSwitchListener> switchListeners;
// Module dependencies
protected IRestApiService restApi;
protected IThreadPoolService threadPool;
protected IControllerRegistryService registryService;
protected IDebugCounterService debugCounters;
protected IDebugEventService debugEvents;
protected ILinkDiscoveryService linkDiscovery;
// Configuration options
protected int openFlowPort = 6633;
protected int workerThreads = 0;
// The id for this controller node. Should be unique for each controller
// node in a controller cluster.
private OnosInstanceId onosInstanceId = new OnosInstanceId("localhost");
// defined counters
private Counters counters;
// Event IDs for debug events
protected IEventUpdater<SwitchEvent> evSwitch;
// Load monitor for overload protection
protected final boolean overload_drop =
Boolean.parseBoolean(System.getProperty("overload_drop", "false"));
protected final LoadMonitor loadmonitor = new LoadMonitor(log);
// Start time of the controller
protected long systemStartTime;
// Flag to always flush flow table on switch reconnect (HA or otherwise)
protected boolean alwaysClearFlowsOnSwAdd = false;
// Perf. related configuration
protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
protected static final int BATCH_MAX_SIZE = 100;
protected static final boolean ALWAYS_DECODE_ETH = true;
// ******************************
// Switch Management and Updates
// ******************************
/**
* Switch updates are sent to all IOFSwitchListeners. A switch that is
* connected to this controller instance, but not activated, is not
* available for updates.
*
* In ONOS, each controller instance can simultaneously serve in a MASTER
* role for some connected switches, and in a EQUAL role for other connected
* switches. The EQUAL role can be treated as a SLAVE role, by ensuring that
* the controller instance never sends packets or commands out to the
* switch. Activated switches, either with Controller Role MASTER or EQUAL
* are announced as updates. We also support announcements of controller
* role transitions from MASTER --> EQUAL, and EQUAL --> MASTER, for an
* individual switch.
*
* Disconnection of only activated switches are announced. Finally, changes
* to switch ports are announced with a portChangeType (see @IOFSwitch)
*
* @author saurav
*/
public enum SwitchUpdateType {
/** switch activated with this controller's role as MASTER */
ACTIVATED_MASTER,
/**
* switch activated with this controller's role as EQUAL. listener can
* treat this controller's role as SLAVE by not sending packets or
* commands to the switch
*/
ACTIVATED_EQUAL,
/** this controller's role for this switch changed from Master to Equal */
MASTER_TO_EQUAL,
/** this controller's role for this switch changed form Equal to Master */
EQUAL_TO_MASTER,
/** A previously activated switch disconnected */
DISCONNECTED,
/** Port changed on a previously activated switch */
PORTCHANGED,
}
/**
* Update message indicating a switch was added or removed ONOS: This
* message extended to indicate Port add or removed event.
*/
protected class SwitchUpdate implements IUpdate {
public long getSwId() {
return swId;
}
public SwitchUpdateType getSwitchUpdateType() {
return switchUpdateType;
}
public PortChangeType getPortChangeType() {
return changeType;
}
private final long swId;
private final SwitchUpdateType switchUpdateType;
private final OFPortDesc port;
private final PortChangeType changeType;
public SwitchUpdate(long swId, SwitchUpdateType switchUpdateType) {
this(swId, switchUpdateType, null, null);
}
public SwitchUpdate(long swId,
SwitchUpdateType switchUpdateType,
OFPortDesc port,
PortChangeType changeType) {
if (switchUpdateType == SwitchUpdateType.PORTCHANGED) {
if (port == null) {
throw new NullPointerException("Port must not be null " +
"for PORTCHANGED updates");
}
if (changeType == null) {
throw new NullPointerException("ChangeType must not be " +
"null for PORTCHANGED updates");
}
} else {
if (port != null || changeType != null) {
throw new IllegalArgumentException("port and changeType " +
"must be null for " + switchUpdateType +
" updates");
}
}
this.swId = swId;
this.switchUpdateType = switchUpdateType;
this.port = port;
this.changeType = changeType;
}
@Override
public void dispatch() {
if (log.isTraceEnabled()) {
log.trace("Dispatching switch update {} {}",
HexString.toHexString(swId), switchUpdateType);
}
if (switchListeners != null) {
for (IOFSwitchListener listener : switchListeners) {
switch (switchUpdateType) {
case ACTIVATED_MASTER:
// don't count here. We have more specific
// counters before the update is created
listener.switchActivatedMaster(swId);
break;
case ACTIVATED_EQUAL:
// don't count here. We have more specific
// counters before the update is created
listener.switchActivatedEqual(swId);
break;
case MASTER_TO_EQUAL:
listener.switchMasterToEqual(swId);
break;
case EQUAL_TO_MASTER:
listener.switchEqualToMaster(swId);
break;
case DISCONNECTED:
// don't count here. We have more specific
// counters before the update is created
listener.switchDisconnected(swId);
break;
case PORTCHANGED:
counters.switchPortChanged.updateCounterWithFlush();
listener.switchPortChanged(swId, port, changeType);
break;
}
}
}
}
}
protected boolean addConnectedSwitch(long dpid, OFChannelHandler h) {
if (connectedSwitches.get(dpid) != null) {
log.error("Trying to add connectedSwitch but found a previous "
+ "value for dpid: {}", dpid);
return false;
} else {
connectedSwitches.put(dpid, h);
return true;
}
}
/**
* Switch Events
*/
@Override
public void addSwitchEvent(long dpid, String reason, boolean flushNow) {
if (flushNow)
evSwitch.updateEventWithFlush(new SwitchEvent(dpid, reason));
else
evSwitch.updateEventNoFlush(new SwitchEvent(dpid, reason));
}
private boolean validActivation(long dpid) {
if (connectedSwitches.get(dpid) == null) {
log.error("Trying to activate switch but is not in "
+ "connected switches: dpid {}. Aborting ..",
HexString.toHexString(dpid));
return false;
}
if (activeMasterSwitches.get(dpid) != null ||
activeEqualSwitches.get(dpid) != null) {
log.error("Trying to activate switch but it is already "
+ "activated: dpid {}. Found in activeMaster: {} "
+ "Found in activeEqual: {}. Aborting ..", new Object[] {
HexString.toHexString(dpid),
(activeMasterSwitches.get(dpid) == null) ? 'Y' : 'N',
(activeEqualSwitches.get(dpid) == null) ? 'Y' : 'N'});
counters.switchWithSameDpidActivated.updateCounterWithFlush();
return false;
}
return true;
}
/**
* Called when a switch is activated, with this controller's role as MASTER
*/
protected boolean addActivatedMasterSwitch(long dpid, IOFSwitch sw) {
synchronized (multiCacheLock) {
if (!validActivation(dpid))
return false;
activeMasterSwitches.put(dpid, sw);
}
// update counters and events
counters.switchActivated.updateCounterWithFlush();
evSwitch.updateEventWithFlush(new SwitchEvent(dpid, "activeMaster"));
addUpdateToQueue(new SwitchUpdate(dpid,
SwitchUpdateType.ACTIVATED_MASTER));
return true;
}
/**
* Called when a switch is activated, with this controller's role as EQUAL
*/
protected boolean addActivatedEqualSwitch(long dpid, IOFSwitch sw) {
synchronized (multiCacheLock) {
if (!validActivation(dpid))
return false;
activeEqualSwitches.put(dpid, sw);
}
// update counters and events
counters.switchActivated.updateCounterWithFlush();
evSwitch.updateEventWithFlush(new SwitchEvent(dpid, "activeEqual"));
addUpdateToQueue(new SwitchUpdate(dpid,
SwitchUpdateType.ACTIVATED_EQUAL));
return true;
}
/**
* Called when this controller's role for a switch transitions from equal to
* master. For 1.0 switches, we internally refer to the role 'slave' as
* 'equal' - so this transition is equivalent to 'addActivatedMasterSwitch'.
*/
protected void transitionToMasterSwitch(long dpid) {
synchronized (multiCacheLock) {
IOFSwitch sw = activeEqualSwitches.remove(dpid);
if (sw == null) {
log.error("Transition to master called on sw {}, but switch "
+ "was not found in controller-cache", dpid);
return;
}
activeMasterSwitches.put(dpid, sw);
}
addUpdateToQueue(new SwitchUpdate(dpid,
SwitchUpdateType.EQUAL_TO_MASTER));
}
/**
* Called when this controller's role for a switch transitions to equal. For
* 1.0 switches, we internally refer to the role 'slave' as 'equal'.
*/
protected void transitionToEqualSwitch(long dpid) {
synchronized (multiCacheLock) {
IOFSwitch sw = activeMasterSwitches.remove(dpid);
if (sw == null) {
log.error("Transition to equal called on sw {}, but switch "
+ "was not found in controller-cache", dpid);
return;
}
activeEqualSwitches.put(dpid, sw);
}
addUpdateToQueue(new SwitchUpdate(dpid,
SwitchUpdateType.MASTER_TO_EQUAL));
}
/**
* Clear all state in controller switch maps for a switch that has
* disconnected from the local controller. Also release control for that
* switch from the global repository. Notify switch listeners.
*/
protected void removeConnectedSwitch(long dpid) {
releaseRegistryControl(dpid);
connectedSwitches.remove(dpid);
IOFSwitch sw = activeMasterSwitches.remove(dpid);
if (sw == null) {
sw = activeEqualSwitches.remove(dpid);
}
if (sw != null) {
sw.cancelAllStatisticsReplies();
sw.setConnected(false); // do we need this?
}
evSwitch.updateEventWithFlush(new SwitchEvent(dpid, "disconnected"));
counters.switchDisconnected.updateCounterWithFlush();
addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.DISCONNECTED));
}
/**
* Indicates that ports on the given switch have changed. Enqueue a switch
* update.
*
* @param sw
*/
protected void notifyPortChanged(long dpid, OFPortDesc port,
PortChangeType changeType) {
if (port == null || changeType == null) {
String msg = String.format("Switch port or changetType must not "
+ "be null in port change notification");
throw new NullPointerException(msg);
}
if (connectedSwitches.get(dpid) == null || getSwitch(dpid) == null) {
log.warn("Port change update on switch {} not connected or activated "
+ "... Aborting.", HexString.toHexString(dpid));
return;
}
SwitchUpdate update = new SwitchUpdate(dpid, SwitchUpdateType.PORTCHANGED,
port, changeType);
addUpdateToQueue(update);
}
// ***************
// Getters/Setters
// ***************
public void setRestApiService(IRestApiService restApi) {
this.restApi = restApi;
}
public void setThreadPoolService(IThreadPoolService tp) {
this.threadPool = tp;
}
public void setMastershipService(IControllerRegistryService serviceImpl) {
this.registryService = serviceImpl;
}
public void setLinkDiscoveryService(ILinkDiscoveryService linkDiscovery) {
this.linkDiscovery = linkDiscovery;
}
public void setDebugCounter(IDebugCounterService debugCounters) {
this.debugCounters = debugCounters;
}
public void setDebugEvent(IDebugEventService debugEvents) {
this.debugEvents = debugEvents;
}
IDebugCounterService getDebugCounter() {
return this.debugCounters;
}
// **********************
// Role Handling
// **********************
/**
* created by ONOS - works with registry service
*/
protected class RoleChangeCallback implements ControlChangeCallback {
@Override
public void controlChanged(long dpidLong, boolean hasControl) {
Dpid dpid = new Dpid(dpidLong);
log.info("Role change callback for switch {}, hasControl {}",
dpid, hasControl);
Role role = null;
/*
* issue #229
* Cannot rely on sw.getRole() as it can be behind due to pending
* role changes in the queue. Just submit it and late the
* RoleChanger handle duplicates.
*/
if (hasControl) {
role = Role.MASTER;
} else {
role = Role.EQUAL; // treat the same as Role.SLAVE
}
OFChannelHandler swCh = connectedSwitches.get(dpid.value());
if (swCh == null) {
log.warn("Switch {} not found in connected switches", dpid);
return;
}
log.debug("Sending role request {} msg to {}", role, dpid);
swCh.sendRoleRequest(role, RoleRecvStatus.MATCHED_SET_ROLE);
}
}
public synchronized void submitRegistryRequest(long dpid) {
OFChannelHandler h = connectedSwitches.get(dpid);
if (h == null) {
log.error("Trying to request registry control for switch {} "
+ "not in connected switches. Aborting.. ",
HexString.toHexString(dpid));
// FIXME shouldn't we immediately return here?
}
// Request control of the switch from the global registry
try {
h.controlRequested = Boolean.TRUE;
registryService.requestControl(dpid, new RoleChangeCallback());
} catch (RegistryException e) {
log.debug("Registry error: {}", e.getMessage());
h.controlRequested = Boolean.FALSE;
}
if (!h.controlRequested) { // XXX what is being attempted here?
// yield to allow other thread(s) to release control
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// Ignore interruptions
}
// safer to bounce the switch to reconnect here than proceeding
// further
// XXX S why? can't we just try again a little later?
log.debug("Closing sw:{} because we weren't able to request control " +
"successfully" + dpid);
connectedSwitches.get(dpid).disconnectSwitch();
}
}
public synchronized void releaseRegistryControl(long dpidLong) {
OFChannelHandler h = connectedSwitches.get(dpidLong);
if (h == null) {
log.error("Trying to release registry control for switch {} "
+ "not in connected switches. Aborting.. ",
HexString.toHexString(dpidLong));
return;
}
if (h.controlRequested) {
registryService.releaseControl(dpidLong);
}
}
// *******************
// OF Message Handling
// *******************
/**
* Handle and dispatch a message to IOFMessageListeners.
*
* We only dispatch messages to listeners if the controller's role is
* MASTER.
*
* @param sw The switch sending the message
* @param m The message the switch sent
* @param flContext The floodlight context to use for this message. If null,
* a new context will be allocated.
* @throws IOException
*
* FIXME: this method and the ChannelHandler disagree on which
* messages should be dispatched and which shouldn't
*/
@LogMessageDocs({
@LogMessageDoc(level = "ERROR",
message = "Ignoring PacketIn (Xid = {xid}) because the data" +
" field is empty.",
explanation = "The switch sent an improperly-formatted PacketIn" +
" message",
recommendation = LogMessageDoc.CHECK_SWITCH),
@LogMessageDoc(level = "WARN",
message = "Unhandled OF Message: {} from {}",
explanation = "The switch sent a message not handled by " +
"the controller")
})
@SuppressWarnings({"fallthrough", "unchecked"})
protected void handleMessage(IOFSwitch sw, OFMessage m,
FloodlightContext bContext)
throws IOException {
Ethernet eth = null;
// FIXME losing port number precision here
short inport = -1;
switch (m.getType()) {
case PACKET_IN:
OFPacketIn pi = (OFPacketIn) m;
// log.info("saw packet in from sw {}", sw.getStringId());
if (pi.getData().length <= 0) {
log.error("Ignoring PacketIn (Xid = " + pi.getXid() +
") because/* the data field is empty.");
return;
}
// get incoming port to store in floodlight context
if (sw.getOFVersion() == OFVersion.OF_10) {
inport = pi.getInPort().getShortPortNumber();
} else if (sw.getOFVersion() == OFVersion.OF_13) {
for (MatchField<?> mf : pi.getMatch().getMatchFields()) {
if (mf.id == MatchFields.IN_PORT) {
inport = pi.getMatch().get((MatchField<OFPort>) mf)
.getShortPortNumber();
break;
}
}
if (inport == -1) {
log.error("Match field for incoming port missing in "
+ "packet-in from sw {}. Ignoring msg",
sw.getStringId());
return;
}
} else {
// should have been taken care of earlier in handshake
log.error("OFVersion {} not supported for "
+ "packet-in from sw {}. Ignoring msg",
sw.getOFVersion(), sw.getStringId());
return;
}
// decode enclosed ethernet packet to store in floodlight context
if (Controller.ALWAYS_DECODE_ETH) {
eth = new Ethernet();
eth.deserialize(pi.getData(), 0,
pi.getData().length);
}
// fall through to default case...
/*log.debug("Sw:{} packet-in: {}", sw.getStringId(),
String.format("0x%x", eth.getEtherType()));*/
if (eth.getEtherType() != (short) EthType.LLDP.getValue())
log.trace("Sw:{} packet-in: {}", sw.getStringId(), pi);
default:
List<IOFMessageListener> listeners = null;
if (messageListeners.containsKey(m.getType())) {
listeners = messageListeners.get(m.getType()).
getOrderedListeners();
}
FloodlightContext bc = null;
if (listeners != null) {
// Check if floodlight context is passed from the calling
// function, if so use that floodlight context, otherwise
// allocate one
if (bContext == null) {
bc = flcontext_alloc();
} else {
bc = bContext;
}
if (eth != null) {
IFloodlightProviderService.bcStore.put(bc,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
eth);
}
if (inport != -1) {
bc.getStorage().put(
IFloodlightProviderService.CONTEXT_PI_INPORT,
inport);
}
// Get the starting time (overall and per-component) of
// the processing chain for this packet if performance
// monitoring is turned on
Command cmd = null;
for (IOFMessageListener listener : listeners) {
if (listener instanceof IOFSwitchFilter) {
if (!((IOFSwitchFilter) listener).isInterested(sw)) {
continue;
}
}
cmd = listener.receive(sw, m, bc);
if (Command.STOP.equals(cmd)) {
break;
}
}
} else {
log.warn("Unhandled OF Message: {} from {}", m, sw);
}
if ((bContext == null) && (bc != null))
flcontext_free(bc);
}
}
// ***************
// IFloodlightProviderService
// ***************
// FIXME: remove this method
@Override
public Map<Long, IOFSwitch> getSwitches() {
return getMasterSwitches();
}
// FIXME: remove this method
public Map<Long, IOFSwitch> getMasterSwitches() {
return Collections.unmodifiableMap(activeMasterSwitches);
}
@Override
public Set<Long> getAllSwitchDpids() {
Set<Long> dpids = new HashSet<Long>();
dpids.addAll(activeMasterSwitches.keySet());
dpids.addAll(activeEqualSwitches.keySet());
return dpids;
}
@Override
public Set<Long> getAllMasterSwitchDpids() {
Set<Long> dpids = new HashSet<Long>();
dpids.addAll(activeMasterSwitches.keySet());
return dpids;
}
@Override
public Set<Long> getAllEqualSwitchDpids() {
Set<Long> dpids = new HashSet<Long>();
dpids.addAll(activeEqualSwitches.keySet());
return dpids;
}
@Override
public IOFSwitch getSwitch(long dpid) {
IOFSwitch sw = null;
if ((sw = activeMasterSwitches.get(dpid)) != null)
return sw;
if ((sw = activeEqualSwitches.get(dpid)) != null)
return sw;
return sw;
}
@Override
public IOFSwitch getMasterSwitch(long dpid) {
return activeMasterSwitches.get(dpid);
}
@Override
public IOFSwitch getEqualSwitch(long dpid) {
return activeEqualSwitches.get(dpid);
}
@Override
public synchronized void addOFMessageListener(OFType type,
IOFMessageListener listener) {
ListenerDispatcher<OFType, IOFMessageListener> ldd =
messageListeners.get(type);
if (ldd == null) {
ldd = new ListenerDispatcher<OFType, IOFMessageListener>();
messageListeners.put(type, ldd);
}
ldd.addListener(type, listener);
}
@Override
public synchronized void removeOFMessageListener(OFType type,
IOFMessageListener listener) {
ListenerDispatcher<OFType, IOFMessageListener> ldd =
messageListeners.get(type);
if (ldd != null) {
ldd.removeListener(listener);
}
}
public void removeOFMessageListeners(OFType type) {
messageListeners.remove(type);
}
private void logListeners() {
for (Map.Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> entry : messageListeners
.entrySet()) {
OFType type = entry.getKey();
ListenerDispatcher<OFType, IOFMessageListener> ldd =
entry.getValue();
StringBuffer sb = new StringBuffer();
sb.append("OFMessageListeners for ");
sb.append(type);
sb.append(": ");
for (IOFMessageListener l : ldd.getOrderedListeners()) {
sb.append(l.getName());
sb.append(",");
}
log.debug(sb.toString());
}
StringBuffer sl = new StringBuffer();
sl.append("SwitchUpdate Listeners: ");
for (IOFSwitchListener swlistener : switchListeners) {
sl.append(swlistener.getName());
sl.append(",");
}
log.debug(sl.toString());
}
@Override
public void addOFSwitchListener(IOFSwitchListener listener) {
this.switchListeners.add(listener);
}
@Override
public void removeOFSwitchListener(IOFSwitchListener listener) {
this.switchListeners.remove(listener);
}
@Override
public Map<OFType, List<IOFMessageListener>> getListeners() {
Map<OFType, List<IOFMessageListener>> lers =
new HashMap<OFType, List<IOFMessageListener>>();
for (Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : messageListeners
.entrySet()) {
lers.put(e.getKey(), e.getValue().getOrderedListeners());
}
return Collections.unmodifiableMap(lers);
}
/*@Override
@LogMessageDocs({
@LogMessageDoc(message = "Failed to inject OFMessage {message} onto " +
"a null switch",
explanation = "Failed to process a message because the switch " +
" is no longer connected."),
@LogMessageDoc(level = "ERROR",
message = "Error reinjecting OFMessage on switch {switch}",
explanation = "An I/O error occured while attempting to " +
"process an OpenFlow message",
recommendation = LogMessageDoc.CHECK_SWITCH)
})
public boolean injectOfMessage(IOFSwitch sw, OFMessage msg,
FloodlightContext bc) {
if (sw == null) {
log.info("Failed to inject OFMessage {} onto a null switch", msg);
return false;
}
// FIXME: Do we need to be able to inject messages to switches
// where we're the slave controller (i.e. they're connected but
// not active)?
// FIXME: Don't we need synchronization logic here so we're holding
// the listener read lock when we call handleMessage? After some
// discussions it sounds like the right thing to do here would be to
// inject the message as a netty upstream channel event so it goes
// through the normal netty event processing, including being
// handled
if (!activeSwitches.containsKey(sw.getId())) return false;
try {
// Pass Floodlight context to the handleMessages()
handleMessage(sw, msg, bc);
} catch (IOException e) {
log.error("Error reinjecting OFMessage on switch {}",
HexString.toHexString(sw.getId()));
return false;
}
return true;
}*/
// @Override
// public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) {
// // call the overloaded version with floodlight context set to null
// return injectOfMessage(sw, msg, null);
// }
// @Override
// public void handleOutgoingMessage(IOFSwitch sw, OFMessage m,
// FloodlightContext bc) {
//
// List<IOFMessageListener> listeners = null;
// if (messageListeners.containsKey(m.getType())) {
// listeners =
// messageListeners.get(m.getType()).getOrderedListeners();
// }
//
// if (listeners != null) {
// for (IOFMessageListener listener : listeners) {
// if (listener instanceof IOFSwitchFilter) {
// if (!((IOFSwitchFilter) listener).isInterested(sw)) {
// continue;
// }
// }
// if (Command.STOP.equals(listener.receive(sw, m, bc))) {
// break;
// }
// }
// }
// }
/**
* Gets an OpenFlow message factory for version 1.0.
*
* @return an OpenFlow 1.0 message factory
*/
public OFFactory getOFMessageFactory_10() {
return factory10;
}
/**
* Gets an OpenFlow message factory for version 1.3.
*
* @return an OpenFlow 1.3 message factory
*/
public OFFactory getOFMessageFactory_13() {
return factory13;
}
@Override
public void publishUpdate(IUpdate update) {
try {
this.updates.put(update);
} catch (InterruptedException e) {
log.error("Failure adding update to queue", e);
}
}
@Override
public Map<String, String> getControllerNodeIPs() {
// We return a copy of the mapping so we can guarantee that
// the mapping return is the same as one that will be (or was)
// dispatched to IHAListeners
HashMap<String, String> retval = new HashMap<String, String>();
synchronized (controllerNodeIPsCache) {
retval.putAll(controllerNodeIPsCache);
}
return retval;
}
@Override
public long getSystemStartTime() {
return (this.systemStartTime);
}
@Override
public void setAlwaysClearFlowsOnSwAdd(boolean value) {
this.alwaysClearFlowsOnSwAdd = value;
}
@Override
public OnosInstanceId getOnosInstanceId() {
return onosInstanceId;
}
/**
* FOR TESTING ONLY. Dispatch all updates in the update queue until queue is
* empty
*/
void processUpdateQueueForTesting() {
while (!updates.isEmpty()) {
IUpdate update = updates.poll();
if (update != null)
update.dispatch();
}
}
// **************
// Initialization
// **************
// XXX S This should probably go away OR it should be edited to handle
// controller roles per switch! Then it could be a way to
// deterministically configure a switch to a MASTER controller instance
/**
* Sets the initial role based on properties in the config params. It looks
* for two different properties. If the "role" property is specified then
* the value should be either "EQUAL", "MASTER", or "SLAVE" and the role of
* the controller is set to the specified value. If the "role" property is
* not specified then it looks next for the "role.path" property. In this
* case the value should be the path to a property file in the file system
* that contains a property called "floodlight.role" which can be one of the
* values listed above for the "role" property. The idea behind the
* "role.path" mechanism is that you have some separate heartbeat and master
* controller election algorithm that determines the role of the controller.
* When a role transition happens, it updates the current role in the file
* specified by the "role.path" file. Then if floodlight restarts for some
* reason it can get the correct current role of the controller from the
* file.
*
* @param configParams The config params for the FloodlightProvider service
* @return A valid role if role information is specified in the config
* params, otherwise null
*/
@LogMessageDocs({
@LogMessageDoc(message = "Controller role set to {role}",
explanation = "Setting the initial HA role to "),
@LogMessageDoc(level = "ERROR",
message = "Invalid current role value: {role}",
explanation = "An invalid HA role value was read from the " +
"properties file",
recommendation = LogMessageDoc.CHECK_CONTROLLER)
})
protected Role getInitialRole(Map<String, String> configParams) {
Role role = null;
String roleString = configParams.get("role");
FileInputStream fs = null;
if (roleString == null) {
String rolePath = configParams.get("rolepath");
if (rolePath != null) {
Properties properties = new Properties();
try {
fs = new FileInputStream(rolePath);
properties.load(fs);
roleString = properties.getProperty("floodlight.role");
} catch (IOException exc) {
// Don't treat it as an error if the file specified by the
// rolepath property doesn't exist. This lets us enable the
// HA mechanism by just creating/setting the floodlight.role
// property in that file without having to modify the
// floodlight properties.
} finally {
if (fs != null) {
try {
fs.close();
} catch (IOException e) {
log.error("Exception while closing resource ", e);
}
}
}
}
}
if (roleString != null) {
// Canonicalize the string to the form used for the enum constants
roleString = roleString.trim().toUpperCase();
try {
role = Role.valueOf(roleString);
} catch (IllegalArgumentException exc) {
log.error("Invalid current role value: {}", roleString);
}
}
log.info("Controller role set to {}", role);
return role;
}
/**
* Tell controller that we're ready to accept switches loop
*
* @throws IOException
*/
@Override
@LogMessageDocs({
@LogMessageDoc(message = "Listening for switch connections on {address}",
explanation = "The controller is ready and listening for new" +
" switch connections"),
@LogMessageDoc(message = "Storage exception in controller " +
"updates loop; terminating process",
explanation = ERROR_DATABASE,
recommendation = LogMessageDoc.CHECK_CONTROLLER),
@LogMessageDoc(level = "ERROR",
message = "Exception in controller updates loop",
explanation = "Failed to dispatch controller event",
recommendation = LogMessageDoc.GENERIC_ACTION)
})
public void run() {
if (log.isDebugEnabled()) {
logListeners();
}
try {
final ServerBootstrap bootstrap = createServerBootStrap();
bootstrap.setOption("reuseAddr", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE);
ChannelPipelineFactory pfact =
new OpenflowPipelineFactory(this, null);
bootstrap.setPipelineFactory(pfact);
InetSocketAddress sa = new InetSocketAddress(openFlowPort);
final ChannelGroup cg = new DefaultChannelGroup();
cg.add(bootstrap.bind(sa));
log.info("Listening for switch connections on {}", sa);
} catch (Exception e) {
throw new RuntimeException(e);
}
// main loop
while (true) {
try {
IUpdate update = updates.take();
update.dispatch();
} catch (InterruptedException e) {
log.error("Received interrupted exception in updates loop;" +
"terminating process");
terminate();
} catch (Exception e) {
log.error("Exception in controller updates loop", e);
}
}
}
private ServerBootstrap createServerBootStrap() {
if (workerThreads == 0) {
return new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
} else {
return new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool(), workerThreads));
}
}
public void setConfigParams(Map<String, String> configParams) {
String ofPort = configParams.get("openflowport");
if (ofPort != null) {
this.openFlowPort = Integer.parseInt(ofPort);
}
log.debug("OpenFlow port set to {}", this.openFlowPort);
String threads = configParams.get("workerthreads");
if (threads != null) {
this.workerThreads = Integer.parseInt(threads);
}
log.debug("Number of worker threads set to {}", this.workerThreads);
String controllerId = configParams.get("controllerid");
if (controllerId != null) {
this.onosInstanceId = new OnosInstanceId(controllerId);
} else {
// Try to get the hostname of the machine and use that for
// controller ID
try {
String hostname = java.net.InetAddress.getLocalHost().getHostName();
this.onosInstanceId = new OnosInstanceId(hostname);
} catch (UnknownHostException e) {
// Can't get hostname, we'll just use the default
}
}
String useOnly10 = configParams.get("useOnly10");
if (useOnly10 != null && useOnly10.equalsIgnoreCase("true")) {
OFChannelHandler.useOnly10 = true;
log.info("Setting controller to only use OpenFlow 1.0");
}
log.debug("ControllerId set to {}", this.onosInstanceId);
}
/**
* Initialize internal data structures
*/
public void init(Map<String, String> configParams) {
// These data structures are initialized here because other
// module's startUp() might be called before ours
this.messageListeners =
new ConcurrentHashMap<OFType, ListenerDispatcher<OFType,
IOFMessageListener>>();
this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>();
this.activeMasterSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
this.activeEqualSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
this.connectedSwitches = new ConcurrentHashMap<Long, OFChannelHandler>();
this.controllerNodeIPsCache = new HashMap<String, String>();
this.updates = new LinkedBlockingQueue<IUpdate>();
setConfigParams(configParams);
this.systemStartTime = System.currentTimeMillis();
this.counters = new Counters();
this.multiCacheLock = new Object();
String option = configParams.get("flushSwitchesOnReconnect");
if (option != null && option.equalsIgnoreCase("true")) {
this.setAlwaysClearFlowsOnSwActivate(true);
log.info("Flush switches on reconnect -- Enabled.");
} else {
this.setAlwaysClearFlowsOnSwActivate(false);
log.info("Flush switches on reconnect -- Disabled");
}
option = configParams.get("cpqdUsePipeline13");
if (option != null && option.equalsIgnoreCase("true")) {
DriverManager.setConfigForCpqd(true);
log.info("Using OF1.3 pipeline for the CPqD software switch");
} else {
log.info("Using OF1.0 pipeline for the CPqD software switch");
}
String disableOvsClassification =
configParams.get("disableOvsClassification");
if (disableOvsClassification != null &&
disableOvsClassification.equalsIgnoreCase("true")) {
DriverManager.setDisableOvsClassification(true);
log.info("OVS switches will be classified as default switches");
}
}
/**
* Startup all of the controller's components
*
* @throws FloodlightModuleException
*/
@LogMessageDoc(message = "Waiting for storage source",
explanation = "The system database is not yet ready",
recommendation = "If this message persists, this indicates " +
"that the system database has failed to start. " +
LogMessageDoc.CHECK_CONTROLLER)
public void startupComponents() throws FloodlightModuleException {
try {
registryService.registerController(onosInstanceId.toString());
} catch (RegistryException e) {
log.warn("Registry service error: {}", e.getMessage());
}
// Add our REST API
restApi.addRestletRoutable(new CoreWebRoutable());
// Startup load monitoring
if (overload_drop) {
this.loadmonitor.startMonitoring(
this.threadPool.getScheduledExecutor());
}
// register counters and events
try {
this.counters.createCounters(debugCounters);
} catch (CounterException e) {
throw new FloodlightModuleException(e.getMessage());
}
registerControllerDebugEvents();
}
// **************
// debugCounter registrations
// **************
public static class Counters {
public static final String prefix = "controller";
public IDebugCounter setRoleEqual;
public IDebugCounter setSameRole;
public IDebugCounter setRoleMaster;
public IDebugCounter remoteStoreNotification;
public IDebugCounter invalidPortsChanged;
public IDebugCounter invalidSwitchActivatedWhileSlave;
public IDebugCounter invalidStoreEventWhileMaster;
public IDebugCounter switchDisconnectedWhileSlave;
public IDebugCounter switchActivated;
public IDebugCounter errorSameSwitchReactivated; // err
public IDebugCounter switchWithSameDpidActivated; // warn
public IDebugCounter newSwitchActivated; // new switch
public IDebugCounter syncedSwitchActivated;
public IDebugCounter readyForReconcile;
public IDebugCounter newSwitchFromStore;
public IDebugCounter updatedSwitchFromStore;
public IDebugCounter switchDisconnected;
public IDebugCounter syncedSwitchRemoved;
public IDebugCounter unknownSwitchRemovedFromStore;
public IDebugCounter consolidateStoreRunCount;
public IDebugCounter consolidateStoreInconsistencies;
public IDebugCounter storeSyncError;
public IDebugCounter switchesNotReconnectingToNewMaster;
public IDebugCounter switchPortChanged;
public IDebugCounter switchOtherChange;
public IDebugCounter dispatchMessageWhileSlave;
public IDebugCounter dispatchMessage; // does this cnt make sense? more
// specific?? per type? count
// stops?
public IDebugCounter controllerNodeIpsChanged;
public IDebugCounter messageReceived;
public IDebugCounter messageInputThrottled;
public IDebugCounter switchDisconnectReadTimeout;
public IDebugCounter switchDisconnectHandshakeTimeout;
public IDebugCounter switchDisconnectIOError;
public IDebugCounter switchDisconnectParseError;
public IDebugCounter switchDisconnectSwitchStateException;
public IDebugCounter rejectedExecutionException;
public IDebugCounter switchDisconnectOtherException;
public IDebugCounter switchConnected;
public IDebugCounter unhandledMessage;
public IDebugCounter packetInWhileSwitchIsSlave;
public IDebugCounter epermErrorWhileSwitchIsMaster;
public IDebugCounter roleNotResentBecauseRolePending;
public IDebugCounter roleRequestSent;
public IDebugCounter roleReplyTimeout;
public IDebugCounter roleReplyReceived; // expected RoleReply received
public IDebugCounter roleReplyErrorUnsupported;
public IDebugCounter switchCounterRegistrationFailed;
public IDebugCounter packetParsingError;
void createCounters(IDebugCounterService debugCounters) throws CounterException {
setRoleEqual =
debugCounters.registerCounter(
prefix, "set-role-equal",
"Controller received a role request with role of " +
"EQUAL which is unusual",
CounterType.ALWAYS_COUNT);
setSameRole =
debugCounters.registerCounter(
prefix, "set-same-role",
"Controller received a role request for the same " +
"role the controller already had",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
setRoleMaster =
debugCounters.registerCounter(
prefix, "set-role-master",
"Controller received a role request with role of " +
"MASTER. This counter can be at most 1.",
CounterType.ALWAYS_COUNT);
remoteStoreNotification =
debugCounters.registerCounter(
prefix, "remote-store-notification",
"Received a notification from the sync service " +
"indicating that switch information has changed",
CounterType.ALWAYS_COUNT);
invalidPortsChanged =
debugCounters.registerCounter(
prefix, "invalid-ports-changed",
"Received an unexpected ports changed " +
"notification while the controller was in " +
"SLAVE role.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
invalidSwitchActivatedWhileSlave =
debugCounters.registerCounter(
prefix, "invalid-switch-activated-while-slave",
"Received an unexpected switchActivated " +
"notification while the controller was in " +
"SLAVE role.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
invalidStoreEventWhileMaster =
debugCounters.registerCounter(
prefix, "invalid-store-event-while-master",
"Received an unexpected notification from " +
"the sync store while the controller was in " +
"MASTER role.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
switchDisconnectedWhileSlave =
debugCounters.registerCounter(
prefix, "switch-disconnected-while-slave",
"A switch disconnected and the controller was " +
"in SLAVE role.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
switchActivated =
debugCounters.registerCounter(
prefix, "switch-activated",
"A switch connected to this controller is now " +
"in MASTER role",
CounterType.ALWAYS_COUNT);
errorSameSwitchReactivated = // err
debugCounters.registerCounter(
prefix, "error-same-switch-reactivated",
"A switch that was already in active state " +
"was activated again. This indicates a " +
"controller defect",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchWithSameDpidActivated = // warn
debugCounters.registerCounter(
prefix, "switch-with-same-dpid-activated",
"A switch with the same DPID as another switch " +
"connected to the controller. This can be " +
"caused by multiple switches configured with " +
"the same DPID or by a switch reconnecting very " +
"quickly.",
CounterType.COUNT_ON_DEMAND,
IDebugCounterService.CTR_MDATA_WARN);
newSwitchActivated = // new switch
debugCounters.registerCounter(
prefix, "new-switch-activated",
"A new switch has completed the handshake as " +
"MASTER. The switch was not known to any other " +
"controller in the cluster",
CounterType.ALWAYS_COUNT);
syncedSwitchActivated =
debugCounters.registerCounter(
prefix, "synced-switch-activated",
"A switch has completed the handshake as " +
"MASTER. The switch was known to another " +
"controller in the cluster",
CounterType.ALWAYS_COUNT);
readyForReconcile =
debugCounters.registerCounter(
prefix, "ready-for-reconcile",
"Controller is ready for flow reconciliation " +
"after Slave to Master transition. Either all " +
"previously known switches are now active " +
"or they have timed out and have been removed." +
"This counter will be 0 or 1.",
CounterType.ALWAYS_COUNT);
newSwitchFromStore =
debugCounters.registerCounter(
prefix, "new-switch-from-store",
"A new switch has connected to another " +
"another controller in the cluster. This " +
"controller instance has received a sync store " +
"notification for it.",
CounterType.ALWAYS_COUNT);
updatedSwitchFromStore =
debugCounters.registerCounter(
prefix, "updated-switch-from-store",
"Information about a switch connected to " +
"another controller instance was updated in " +
"the sync store. This controller instance has " +
"received a notification for it",
CounterType.ALWAYS_COUNT);
switchDisconnected =
debugCounters.registerCounter(
prefix, "switch-disconnected",
"FIXME: switch has disconnected",
CounterType.ALWAYS_COUNT);
syncedSwitchRemoved =
debugCounters.registerCounter(
prefix, "synced-switch-removed",
"A switch connected to another controller " +
"instance has disconnected from the controller " +
"cluster. This controller instance has " +
"received a notification for it",
CounterType.ALWAYS_COUNT);
unknownSwitchRemovedFromStore =
debugCounters.registerCounter(
prefix, "unknown-switch-removed-from-store",
"This controller instances has received a sync " +
"store notification that a switch has " +
"disconnected but this controller instance " +
"did not have the any information about the " +
"switch", // might be less than warning
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
consolidateStoreRunCount =
debugCounters.registerCounter(
prefix, "consolidate-store-run-count",
"This controller has transitioned from SLAVE " +
"to MASTER and waited for switches to reconnect. " +
"The controller has finished waiting and has " +
"reconciled switch entries in the sync store " +
"with live state",
CounterType.ALWAYS_COUNT);
consolidateStoreInconsistencies =
debugCounters.registerCounter(
prefix, "consolidate-store-inconsistencies",
"During switch sync store consolidation: " +
"Number of switches that were in the store " +
"but not otherwise known plus number of " +
"switches that were in the store previously " +
"but are now missing plus number of " +
"connected switches that were absent from " +
"the store although this controller has " +
"written them. A non-zero count " +
"indicates a brief split-brain dual MASTER " +
"situation during fail-over",
CounterType.ALWAYS_COUNT);
storeSyncError =
debugCounters.registerCounter(
prefix, "store-sync-error",
"Number of times a sync store operation failed " +
"due to a store sync exception or an entry in " +
"in the store had invalid data.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchesNotReconnectingToNewMaster =
debugCounters.registerCounter(
prefix, "switches-not-reconnecting-to-new-master",
"Switches that were connected to another " +
"controller instance in the cluster but that " +
"did not reconnect to this controller after it " +
"transitioned to MASTER", // might be less
// than warning
CounterType.ALWAYS_COUNT);
switchPortChanged =
debugCounters.registerCounter(
prefix, "switch-port-changed",
"Number of times switch ports have changed",
CounterType.ALWAYS_COUNT);
switchOtherChange =
debugCounters.registerCounter(
prefix, "switch-other-change",
"Number of times other information of a switch " +
"has changed.",
CounterType.ALWAYS_COUNT);
dispatchMessageWhileSlave =
debugCounters.registerCounter(
prefix, "dispatch-message-while-slave",
"Number of times an OF message was received " +
"and supposed to be dispatched but the " +
"controller was in SLAVE role and the message " +
"was not dispatched",
CounterType.ALWAYS_COUNT);
dispatchMessage = // does this cnt make sense? more specific?? per
// type? count stops?
debugCounters.registerCounter(
prefix, "dispatch-message",
"Number of times an OF message was dispatched " +
"to registered modules",
CounterType.ALWAYS_COUNT);
controllerNodeIpsChanged =
debugCounters.registerCounter(
prefix, "controller-nodes-ips-changed",
"IP addresses of controller nodes have changed",
CounterType.ALWAYS_COUNT);
// ------------------------
// channel handler counters. Factor them out ??
messageReceived =
debugCounters.registerCounter(
prefix, "message-received",
"Number of OpenFlow messages received. Some of " +
"these might be throttled",
CounterType.ALWAYS_COUNT);
messageInputThrottled =
debugCounters.registerCounter(
prefix, "message-input-throttled",
"Number of OpenFlow messages that were " +
"throttled due to high load from the sender",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
// TODO: more counters in messageReceived ??
switchDisconnectReadTimeout =
debugCounters.registerCounter(
prefix, "switch-disconnect-read-timeout",
"Number of times a switch was disconnected due " +
"due the switch failing to send OpenFlow " +
"messages or responding to OpenFlow ECHOs",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchDisconnectHandshakeTimeout =
debugCounters.registerCounter(
prefix, "switch-disconnect-handshake-timeout",
"Number of times a switch was disconnected " +
"because it failed to complete the handshake " +
"in time.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchDisconnectIOError =
debugCounters.registerCounter(
prefix, "switch-disconnect-io-error",
"Number of times a switch was disconnected " +
"due to IO errors on the switch connection.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchDisconnectParseError =
debugCounters.registerCounter(
prefix, "switch-disconnect-parse-error",
"Number of times a switch was disconnected " +
"because it sent an invalid packet that could " +
"not be parsed",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchDisconnectSwitchStateException =
debugCounters.registerCounter(
prefix, "switch-disconnect-switch-state-exception",
"Number of times a switch was disconnected " +
"because it sent messages that were invalid " +
"given the switch connection's state.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
rejectedExecutionException =
debugCounters.registerCounter(
prefix, "rejected-execution-exception",
"TODO",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchDisconnectOtherException =
debugCounters.registerCounter(
prefix, "switch-disconnect-other-exception",
"Number of times a switch was disconnected " +
"due to an exceptional situation not covered " +
"by other counters",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
switchConnected =
debugCounters.registerCounter(
prefix, "switch-connected",
"Number of times a new switch connection was " +
"established",
CounterType.ALWAYS_COUNT);
unhandledMessage =
debugCounters.registerCounter(
prefix, "unhandled-message",
"Number of times an OpenFlow message was " +
"received that the controller ignored because " +
"it was inapproriate given the switch " +
"connection's state.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
// might be less than warning
packetInWhileSwitchIsSlave =
debugCounters.registerCounter(
prefix, "packet-in-while-switch-is-slave",
"Number of times a packet in was received " +
"from a switch that was in SLAVE role. " +
"Possibly inidicates inconsistent roles.",
CounterType.ALWAYS_COUNT);
epermErrorWhileSwitchIsMaster =
debugCounters.registerCounter(
prefix, "eperm-error-while-switch-is-master",
"Number of times a permission error was " +
"received while the switch was in MASTER role. " +
"Possibly inidicates inconsistent roles.",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
roleNotResentBecauseRolePending =
debugCounters.registerCounter(
prefix, "role-not-resent-because-role-pending",
"The controller tried to reestablish a role " +
"with a switch but did not do so because a " +
"previous role request was still pending",
CounterType.ALWAYS_COUNT);
roleRequestSent =
debugCounters.registerCounter(
prefix, "role-request-sent",
"Number of times the controller sent a role " +
"request to a switch.",
CounterType.ALWAYS_COUNT);
roleReplyTimeout =
debugCounters.registerCounter(
prefix, "role-reply-timeout",
"Number of times a role request message did not " +
"receive the expected reply from a switch",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
roleReplyReceived = // expected RoleReply received
debugCounters.registerCounter(
prefix, "role-reply-received",
"Number of times the controller received the " +
"expected role reply message from a switch",
CounterType.ALWAYS_COUNT);
roleReplyErrorUnsupported =
debugCounters.registerCounter(
prefix, "role-reply-error-unsupported",
"Number of times the controller received an " +
"error from a switch in response to a role " +
"request indicating that the switch does not " +
"support roles.",
CounterType.ALWAYS_COUNT);
switchCounterRegistrationFailed =
debugCounters.registerCounter(prefix,
"switch-counter-registration-failed",
"Number of times the controller failed to " +
"register per-switch debug counters",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
packetParsingError =
debugCounters.registerCounter(prefix,
"packet-parsing-error",
"Number of times the packet parsing " +
"encountered an error",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_ERROR);
}
}
@Override
public Counters getCounters() {
return this.counters;
}
// **************
// debugEvent registrations
// **************
private void registerControllerDebugEvents() throws FloodlightModuleException {
if (debugEvents == null) {
debugEvents = new NullDebugEvent();
}
try {
evSwitch = debugEvents.registerEvent(
Counters.prefix, "switchevent",
"Switch connected, disconnected or port changed",
EventType.ALWAYS_LOG, SwitchEvent.class, 100);
} catch (MaxEventsRegistered e) {
throw new FloodlightModuleException("Max events registered", e);
}
}
public class SwitchEvent {
@EventColumn(name = "dpid", description = EventFieldType.DPID)
long dpid;
@EventColumn(name = "reason", description = EventFieldType.STRING)
String reason;
public SwitchEvent(long dpid, String reason) {
this.dpid = dpid;
this.reason = reason;
}
}
// **************
// Utility methods
// **************
@Override
public void setAlwaysClearFlowsOnSwActivate(boolean value) {
// this.alwaysClearFlowsOnSwActivate = value;
// XXX S need to be a little more careful about this
}
@Override
public Map<String, Long> getMemory() {
Map<String, Long> m = new HashMap<String, Long>();
Runtime runtime = Runtime.getRuntime();
m.put("total", runtime.totalMemory());
m.put("free", runtime.freeMemory());
return m;
}
@Override
public Long getUptime() {
RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
return rb.getUptime();
}
/**
* Forward to the driver-manager to get an IOFSwitch instance.
*
* @param desc
* @return
*/
protected IOFSwitch getOFSwitchInstance(OFDescStatsReply desc, OFVersion ofv) {
return DriverManager.getOFSwitchImpl(desc, ofv);
}
protected IThreadPoolService getThreadPoolService() {
return this.threadPool;
}
/**
* Part of the controller updates framework (see 'run()' method) Use this
* method to add an IUpdate. A thread-pool will serve the update by
* dispatching it to all listeners for that update.
*
* @param update
*/
@LogMessageDoc(level = "WARN",
message = "Failure adding update {} to queue",
explanation = "The controller tried to add an internal notification" +
" to its message queue but the add failed.",
recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG)
private void addUpdateToQueue(IUpdate update) {
try {
this.updates.put(update);
} catch (InterruptedException e) {
// This should never happen
log.error("Failure adding update {} to queue.", update);
}
}
void flushAll() {
// Flush all flow-mods/packet-out/stats generated from this "train"
OFSwitchImplBase.flush_all();
debugCounters.flushCounters();
debugEvents.flushEvents();
}
/**
* flcontext_free - Free the context to the current thread
*
* @param flcontext
*/
protected void flcontext_free(FloodlightContext flcontext) {
flcontext.getStorage().clear();
flcontext_cache.get().push(flcontext);
}
@LogMessageDoc(message = "Calling System.exit",
explanation = "The controller is terminating")
private synchronized void terminate() {
log.info("Calling System.exit");
System.exit(1);
}
// ***************
// Floodlight context related
// ***************
/**
* flcontext_cache - Keep a thread local stack of contexts
*/
protected static final ThreadLocal<Stack<FloodlightContext>> flcontext_cache =
new ThreadLocal<Stack<FloodlightContext>>() {
@Override
protected Stack<FloodlightContext> initialValue() {
return new Stack<FloodlightContext>();
}
};
/**
* flcontext_alloc - pop a context off the stack, if required create a new
* one
*
* @return FloodlightContext
*/
protected static FloodlightContext flcontext_alloc() {
FloodlightContext flcontext = null;
if (flcontext_cache.get().empty()) {
flcontext = new FloodlightContext();
} else {
flcontext = flcontext_cache.get().pop();
}
return flcontext;
}
}