/**
* 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.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import net.floodlightcontroller.core.ControllerId;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.HAListenerTypeMarker;
import net.floodlightcontroller.core.HARole;
import net.floodlightcontroller.core.IControllerCompletionListener;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IHAListener;
import net.floodlightcontroller.core.IInfoProvider;
import net.floodlightcontroller.core.IListener.Command;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IShutdownService;
import net.floodlightcontroller.core.LogicalOFMessageCategory;
import net.floodlightcontroller.core.RoleInfo;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.FloodlightModuleLoader;
import net.floodlightcontroller.core.util.ListenerDispatcher;
import net.floodlightcontroller.core.web.CoreWebRoutable;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.storage.IResultSet;
import net.floodlightcontroller.storage.IStorageSourceListener;
import net.floodlightcontroller.storage.IStorageSourceService;
import net.floodlightcontroller.storage.StorageException;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.util.LoadMonitor;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFType;
import org.sdnplatform.sync.ISyncService;
import org.sdnplatform.sync.ISyncService.Scope;
import org.sdnplatform.sync.error.SyncException;
import org.sdnplatform.sync.internal.config.ClusterConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
/**
* The main controller class
*/
public class Controller implements IFloodlightProviderService, IStorageSourceListener, IInfoProvider {
protected static final Logger log = LoggerFactory.getLogger(Controller.class);
/* OpenFlow message listeners and dispatchers */
protected static ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> messageListeners;
protected static ConcurrentLinkedQueue<IControllerCompletionListener> completionListeners;
/*
* The controllerNodeIPsCache maps Controller IDs to their IP address.
* It's only used by handleControllerNodeIPsChanged
*/
protected static HashMap<String, String> controllerNodeIPsCache;
protected static ListenerDispatcher<HAListenerTypeMarker,IHAListener> haListeners;
protected static Map<String, List<IInfoProvider>> providerMap;
protected static BlockingQueue<IUpdate> updates;
protected static ControllerCounters counters;
/* Module Loader State */
private static ModuleLoaderState moduleLoaderState;
public enum ModuleLoaderState {
INIT, STARTUP, COMPLETE
}
/* Module dependencies */
private static IStorageSourceService storageSourceService;
private static IOFSwitchService switchService;
private static IDebugCounterService debugCounterService;
private static IRestApiService restApiService;
private static IPktInProcessingTimeService pktinProcTimeService;
private static IThreadPoolService threadPoolService;
private static ISyncService syncService;
private static IShutdownService shutdownService;
/*
* The id for this controller node. Should be unique for each controller
* node in a controller cluster.
*/
protected String controllerId = "my-floodlight-controller";
/*
* This controller's current role that modules can use/query to decide
* if they should operate in ACTIVE / STANDBY
*/
protected volatile HARole notifiedRole;
private static final String INITIAL_ROLE_CHANGE_DESCRIPTION = "Controller startup.";
/*
* NOTE: roleManager is not 'final' because it's initialized at run time
* based on parameters that are only available in init()
*/
private static RoleManager roleManager;
protected static boolean shutdownOnTransitionToStandby = false;
/* Storage table names */
protected static final String CONTROLLER_TABLE_NAME = "controller_controller";
protected static final String CONTROLLER_ID = "id";
protected static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig";
protected static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch";
protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface";
protected static final String CONTROLLER_INTERFACE_ID = "id";
protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id";
protected static final String CONTROLLER_INTERFACE_TYPE = "type";
protected static final String CONTROLLER_INTERFACE_NUMBER = "number";
protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip";
private static final String FLOW_PRIORITY_TABLE_NAME = "controller_forwardingconfig";
private static final String FLOW_COLUMN_PRIMARY_KEY = "id";
private static final String FLOW_VALUE_PRIMARY_KEY = "forwarding";
private static final String FLOW_COLUMN_ACCESS_PRIORITY = "access_priority";
private static final String FLOW_COLUMN_CORE_PRIORITY = "core_priority";
private static final String[] FLOW_COLUMN_NAMES = new String[] {
FLOW_COLUMN_PRIMARY_KEY,
FLOW_COLUMN_ACCESS_PRIORITY,
FLOW_COLUMN_CORE_PRIORITY
};
protected static boolean alwaysDecodeEth = true;
@Override
public ModuleLoaderState getModuleLoaderState(){
return moduleLoaderState;
}
// Load monitor for overload protection
protected final boolean overload_drop = Boolean.parseBoolean(System.getProperty("overload_drop", "false"));
protected final LoadMonitor loadmonitor = new LoadMonitor(log);
/**
* Updates handled by the main loop
*/
public interface IUpdate {
/**
* Calls the appropriate listeners
*/
public void dispatch();
}
/**
* Update message indicating
* IPs of controllers in controller cluster have changed.
*/
private class HAControllerNodeIPUpdate implements IUpdate {
public final Map<String,String> curControllerNodeIPs;
public final Map<String,String> addedControllerNodeIPs;
public final Map<String,String> removedControllerNodeIPs;
public HAControllerNodeIPUpdate(
HashMap<String,String> curControllerNodeIPs,
HashMap<String,String> addedControllerNodeIPs,
HashMap<String,String> removedControllerNodeIPs) {
this.curControllerNodeIPs = curControllerNodeIPs;
this.addedControllerNodeIPs = addedControllerNodeIPs;
this.removedControllerNodeIPs = removedControllerNodeIPs;
}
@Override
public void dispatch() {
if (log.isTraceEnabled()) {
log.trace("Dispatching HA Controller Node IP update "
+ "curIPs = {}, addedIPs = {}, removedIPs = {}",
new Object[] { curControllerNodeIPs, addedControllerNodeIPs,
removedControllerNodeIPs }
);
}
if (haListeners != null) {
for (IHAListener listener: haListeners.getOrderedListeners()) {
listener.controllerNodeIPsChanged(curControllerNodeIPs,
addedControllerNodeIPs, removedControllerNodeIPs);
}
}
}
}
// ***************
// Getters/Setters
// ***************
void setStorageSourceService(IStorageSourceService storageSource) {
storageSourceService = storageSource;
}
IStorageSourceService getStorageSourceService() {
return storageSourceService;
}
IShutdownService getShutdownService() {
return shutdownService;
}
void setShutdownService(IShutdownService shutdownService) {
Controller.shutdownService = shutdownService;
}
void setDebugCounter(IDebugCounterService debugCounters) {
debugCounterService = debugCounters;
}
IDebugCounterService getDebugCounter() {
return debugCounterService;
}
void setSyncService(ISyncService syncService) {
Controller.syncService = syncService;
}
void setPktInProcessingService(IPktInProcessingTimeService pits) {
Controller.pktinProcTimeService = pits;
}
void setRestApiService(IRestApiService restApi) {
Controller.restApiService = restApi;
}
void setThreadPoolService(IThreadPoolService tp) {
threadPoolService = tp;
}
IThreadPoolService getThreadPoolService() {
return threadPoolService;
}
public void setSwitchService(IOFSwitchService switchService) {
Controller.switchService = switchService;
}
public IOFSwitchService getSwitchService() {
return Controller.switchService;
}
@Override
public HARole getRole() {
return notifiedRole;
}
@Override
public RoleInfo getRoleInfo() {
return roleManager.getRoleInfo();
}
@Override
public void setRole(HARole role, String changeDescription) {
roleManager.setRole(role, changeDescription);
}
// ****************
// Message handlers
// ****************
// Handler for SwitchPortsChanged was here (notifyPortChanged). Handled in OFSwitchManager
/**
* 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;
}
/**
* 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);
}
/**
*
* 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
*/
@Override
public void handleMessage(IOFSwitch sw, OFMessage m,
FloodlightContext bContext) {
Ethernet eth = null;
log.trace("Dispatching OFMessage to listeners.");
if (this.notifiedRole == HARole.STANDBY) {
counters.dispatchMessageWhileStandby.increment();
// We are SLAVE. Do not dispatch messages to listeners.
return;
}
counters.dispatchMessage.increment();
switch (m.getType()) {
case PACKET_IN:
counters.packetIn.increment();
OFPacketIn pi = (OFPacketIn)m;
if (pi.getData().length <= 0) {
log.error("Ignoring PacketIn (Xid = " + pi.getXid() + ") because the data field is empty.");
return;
}
if (alwaysDecodeEth) {
eth = new Ethernet();
eth.deserialize(pi.getData(), 0, pi.getData().length);
}
// fall through to default case...
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);
}
// Get the starting time (overall and per-component) of
// the processing chain for this packet if performance
// monitoring is turned on
pktinProcTimeService.bootstrap(listeners);
pktinProcTimeService.recordStartTimePktIn();
Command cmd;
for (IOFMessageListener listener : listeners) {
pktinProcTimeService.recordStartTimeComp(listener);
cmd = listener.receive(sw, m, bc);
pktinProcTimeService.recordEndTimeComp(listener);
if (Command.STOP.equals(cmd)) {
break;
}
}
pktinProcTimeService.recordEndTimePktIn(sw, m, bc);
}
// paag
// And just before we exit the controller loop we see if anyone
// is interested in knowing that we are exiting the loop
for (IControllerCompletionListener listener : completionListeners)
listener.onMessageConsumed(sw, m, bc);
if ((bContext == null) && (bc != null)) flcontext_free(bc);
}
}
// ***************
// IFloodlightProvider
// ***************
/**
* Forwards to RoleManager
* @param ofSwitchHandshakeHandler
* @param role
*/
void reassertRole(OFSwitchHandshakeHandler ofSwitchHandshakeHandler, HARole role) {
roleManager.reassertRole(ofSwitchHandshakeHandler, role);
}
@Override
public String getControllerId() {
return controllerId;
}
@Override
public synchronized void addCompletionListener(IControllerCompletionListener listener) {
completionListeners.add(listener);
}
@Override
public synchronized void removeCompletionListener(IControllerCompletionListener listener) {
String listenerName = listener.getName();
if (completionListeners.remove(listener)) {
log.debug("Removing completion listener {}" , listenerName);
} else {
log.warn("Trying to remove unknown completion listener {}" , listenerName);
}
listenerName=null; // help gc
}
@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);
}
}
private void logListeners() {
for (Map.Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> entry : messageListeners.entrySet()) {
OFType type = entry.getKey();
ListenerDispatcher<OFType, IOFMessageListener> ldd = entry.getValue();
StringBuilder sb = new StringBuilder();
sb.append("OFListeners for ");
sb.append(type);
sb.append(": ");
for (IOFMessageListener l : ldd.getOrderedListeners()) {
sb.append(l.getName());
sb.append(",");
}
log.debug(sb.toString());
}
StringBuilder sb = new StringBuilder();
sb.append("HAListeners: ");
for (IHAListener l: haListeners.getOrderedListeners()) {
sb.append(l.getName());
sb.append(", ");
}
log.debug(sb.toString());
}
public void removeOFMessageListeners(OFType type) {
messageListeners.remove(type);
}
@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
public void handleOutgoingMessage(IOFSwitch sw, OFMessage m) {
if (sw == null)
throw new NullPointerException("Switch must not be null");
if (m == null)
throw new NullPointerException("OFMessage must not be null");
FloodlightContext bc = new FloodlightContext();
List<IOFMessageListener> listeners = null;
if (messageListeners.containsKey(m.getType())) {
listeners = messageListeners.get(m.getType()).getOrderedListeners();
}
if (listeners != null) {
for (IOFMessageListener listener : listeners) {
if (Command.STOP.equals(listener.receive(sw, m, bc))) {
break;
}
}
}
}
// **************
// Initialization
// **************
/**
* 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
*/
protected HARole getInitialRole(Map<String, String> configParams) {
HARole role = HARole.STANDBY;
String roleString = configParams.get("role");
if (roleString != null) {
try {
role = HARole.valueOfBackwardsCompatible(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
public void run() {
moduleLoaderState = ModuleLoaderState.COMPLETE;
if (log.isDebugEnabled()) {
logListeners();
}
while (true) {
try {
IUpdate update = updates.take();
update.dispatch();
} catch (InterruptedException e) {
log.error("Received interrupted exception in updates loop;" +
"terminating process");
log.info("Calling System.exit");
System.exit(1);
} catch (StorageException e) {
log.error("Storage exception in controller " +
"updates loop; terminating process", e);
log.info("Calling System.exit");
System.exit(1);
} catch (Exception e) {
log.error("Exception in controller updates loop", e);
}
}
}
private void setConfigParams(Map<String, String> configParams) throws FloodlightModuleException {
/**
* Tulio Ribeiro
*/
String controllerId = configParams.get("controllerId");
if (!Strings.isNullOrEmpty(controllerId)) {
this.controllerId = controllerId;
}
log.info("ControllerId set to {}", this.controllerId);
String shutdown = configParams.get("shutdownOnTransitionToStandby");
if (!Strings.isNullOrEmpty(shutdown)) {
try {
shutdownOnTransitionToStandby = Boolean.parseBoolean(shutdown.trim());
} catch (Exception e) {
log.error("Could not parse 'shutdownOnTransitionToStandby' of {}. Using default setting of {}",
shutdown, shutdownOnTransitionToStandby);
}
}
log.info("Shutdown when controller transitions to STANDBY HA role: {}", shutdownOnTransitionToStandby);
String decodeEth = configParams.get("deserializeEthPacketIns");
if (!Strings.isNullOrEmpty(decodeEth)) {
try {
alwaysDecodeEth = Boolean.parseBoolean(decodeEth.trim());
} catch (Exception e) {
log.error("Could not parse 'deserializeEthPacketIns' of {}. Using default setting of {}", decodeEth, alwaysDecodeEth);
}
}
if (alwaysDecodeEth) {
log.warn("Controller will automatically deserialize all Ethernet packet-in messages. "
+ "Set 'deserializeEthPacketIns' to 'FALSE' if this feature is not "
+ "required or when benchmarking core performance");
} else {
log.info("Controller will not automatically deserialize all Ethernet packet-in messages. "
+ "Set 'deserializeEthPacketIns' to 'TRUE' to enable this feature");
}
}
/**
* Initialize internal data structures
*/
public void init(Map<String, String> configParams) throws FloodlightModuleException {
moduleLoaderState = ModuleLoaderState.INIT;
// These data structures are initialized here because other
// module's startUp() might be called before ours
messageListeners = new ConcurrentHashMap<OFType, ListenerDispatcher<OFType, IOFMessageListener>>();
haListeners = new ListenerDispatcher<HAListenerTypeMarker, IHAListener>();
controllerNodeIPsCache = new HashMap<String, String>();
updates = new LinkedBlockingQueue<IUpdate>();
providerMap = new HashMap<String, List<IInfoProvider>>();
completionListeners = new ConcurrentLinkedQueue<IControllerCompletionListener>();
setConfigParams(configParams);
HARole initialRole = getInitialRole(configParams);
notifiedRole = initialRole;
shutdownService = new ShutdownServiceImpl();
roleManager = new RoleManager(this, shutdownService,
notifiedRole,
INITIAL_ROLE_CHANGE_DESCRIPTION);
// Switch Service Startup
switchService.registerLogicalOFMessageCategory(LogicalOFMessageCategory.MAIN);
counters = new ControllerCounters(debugCounterService);
}
/**
* Startup all of the controller's components
* @param floodlightModuleLoader
*/
public void startupComponents(FloodlightModuleLoader floodlightModuleLoader) throws FloodlightModuleException {
moduleLoaderState = ModuleLoaderState.STARTUP;
// Create the table names we use
storageSourceService.createTable(CONTROLLER_TABLE_NAME, null);
storageSourceService.createTable(CONTROLLER_INTERFACE_TABLE_NAME, null);
storageSourceService.createTable(SWITCH_CONFIG_TABLE_NAME, null);
storageSourceService.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME, CONTROLLER_ID);
storageSourceService.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this);
storageSourceService.createTable(FLOW_PRIORITY_TABLE_NAME, null);
storageSourceService.setTablePrimaryKeyName(FLOW_PRIORITY_TABLE_NAME, FLOW_COLUMN_PRIMARY_KEY);
storageSourceService.addListener(FLOW_PRIORITY_TABLE_NAME, this);
readFlowPriorityConfigurationFromStorage(); //
// Startup load monitoring
if (overload_drop) {
this.loadmonitor.startMonitoring(threadPoolService.getScheduledExecutor());
}
// Add our REST API
restApiService.addRestletRoutable(new CoreWebRoutable());
try {
syncService.registerStore(OFSwitchManager.SWITCH_SYNC_STORE_NAME, Scope.LOCAL);
} catch (SyncException e) {
throw new FloodlightModuleException("Error while setting up sync service", e);
}
addInfoProvider("summary", this);
}
private void readFlowPriorityConfigurationFromStorage() {
try {
Map<String, Object> row;
IResultSet resultSet = storageSourceService.executeQuery(
FLOW_PRIORITY_TABLE_NAME, FLOW_COLUMN_NAMES, null, null);
if (resultSet == null)
return;
for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
row = it.next().getRow();
if (row.containsKey(FLOW_COLUMN_PRIMARY_KEY)) {
String primary_key = (String) row.get(FLOW_COLUMN_PRIMARY_KEY);
if (primary_key.equals(FLOW_VALUE_PRIMARY_KEY)) {
if (row.containsKey(FLOW_COLUMN_ACCESS_PRIORITY)) {
// Not used anymore DEFAULT_ACCESS_PRIORITY = Short.valueOf((String) row.get(FLOW_COLUMN_ACCESS_PRIORITY));
}
if (row.containsKey(FLOW_COLUMN_CORE_PRIORITY)) {
// Not used anymore DEFAULT_CORE_PRIORITY = Short.valueOf((String) row.get(FLOW_COLUMN_CORE_PRIORITY));
}
}
}
}
}
catch (StorageException e) {
log.error("Failed to access storage for forwarding configuration: {}", e.getMessage());
}
catch (NumberFormatException e) {
// log error, no stack-trace
log.error("Failed to read core or access flow priority from " +
"storage. Illegal number: {}", e.getMessage());
}
}
@Override
public void addInfoProvider(String type, IInfoProvider provider) {
if (!providerMap.containsKey(type)) {
providerMap.put(type, new ArrayList<IInfoProvider>());
}
providerMap.get(type).add(provider);
}
@Override
public void removeInfoProvider(String type, IInfoProvider provider) {
if (!providerMap.containsKey(type)) {
log.debug("Provider type {} doesn't exist.", type);
return;
}
providerMap.get(type).remove(provider);
}
@Override
public Map<String, Object> getControllerInfo(String type) {
if (!providerMap.containsKey(type)) return null;
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (IInfoProvider provider : providerMap.get(type)) {
result.putAll(provider.getInfo(type));
}
return result;
}
@Override
public void addHAListener(IHAListener listener) {
haListeners.addListener(null,listener);
}
@Override
public void removeHAListener(IHAListener listener) {
haListeners.removeListener(listener);
}
/**
* Handle changes to the controller nodes IPs and dispatch update.
*/
protected void handleControllerNodeIPChanges() {
HashMap<String,String> curControllerNodeIPs = new HashMap<String,String>();
HashMap<String,String> addedControllerNodeIPs = new HashMap<String,String>();
HashMap<String,String> removedControllerNodeIPs =new HashMap<String,String>();
String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID,
CONTROLLER_INTERFACE_TYPE,
CONTROLLER_INTERFACE_NUMBER,
CONTROLLER_INTERFACE_DISCOVERED_IP };
synchronized(controllerNodeIPsCache) {
// We currently assume that interface Ethernet0 is the relevant
// controller interface. Might change.
// We could (should?) implement this using
// predicates, but creating the individual and compound predicate
// seems more overhead then just checking every row. Particularly,
// since the number of rows is small and changes infrequent
IResultSet res = storageSourceService.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME,
colNames,null, null);
while (res.next()) {
if (res.getString(CONTROLLER_INTERFACE_TYPE).equals("Ethernet") &&
res.getInt(CONTROLLER_INTERFACE_NUMBER) == 0) {
String controllerID = res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID);
String discoveredIP = res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP);
String curIP = controllerNodeIPsCache.get(controllerID);
curControllerNodeIPs.put(controllerID, discoveredIP);
if (curIP == null) {
// new controller node IP
addedControllerNodeIPs.put(controllerID, discoveredIP);
}
else if (!curIP.equals(discoveredIP)) {
// IP changed
removedControllerNodeIPs.put(controllerID, curIP);
addedControllerNodeIPs.put(controllerID, discoveredIP);
}
}
}
// Now figure out if rows have been deleted. We can't use the
// rowKeys from rowsDeleted directly, since the tables primary
// key is a compound that we can't disassemble
Set<String> curEntries = curControllerNodeIPs.keySet();
Set<String> removedEntries = controllerNodeIPsCache.keySet();
removedEntries.removeAll(curEntries);
for (String removedControllerID : removedEntries)
removedControllerNodeIPs.put(removedControllerID,
controllerNodeIPsCache.get(removedControllerID));
controllerNodeIPsCache.clear();
controllerNodeIPsCache.putAll(curControllerNodeIPs);
//counters.controllerNodeIpsChanged.updateCounterWithFlush();
HAControllerNodeIPUpdate update = new HAControllerNodeIPUpdate(
curControllerNodeIPs, addedControllerNodeIPs,
removedControllerNodeIPs);
if (!removedControllerNodeIPs.isEmpty() || !addedControllerNodeIPs.isEmpty()) {
addUpdateToQueue(update);
}
}
}
@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;
}
private static final String FLOW_PRIORITY_CHANGED_AFTER_STARTUP =
"Flow priority configuration has changed after " +
"controller startup. Restart controller for new " +
"configuration to take effect.";
@Override
public void rowsModified(String tableName, Set<Object> rowKeys) {
if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
handleControllerNodeIPChanges();
} else if (tableName.equals(FLOW_PRIORITY_TABLE_NAME)) {
log.warn(FLOW_PRIORITY_CHANGED_AFTER_STARTUP);
}
}
@Override
public void rowsDeleted(String tableName, Set<Object> rowKeys) {
if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
handleControllerNodeIPChanges();
} else if (tableName.equals(FLOW_PRIORITY_TABLE_NAME)) {
log.warn(FLOW_PRIORITY_CHANGED_AFTER_STARTUP);
}
}
@Override
public long getSystemStartTime() {
RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
return rb.getStartTime();
}
@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();
}
@Override
public void addUpdateToQueue(IUpdate update) {
try {
updates.put(update);
} catch (InterruptedException e) {
// This should never happen
log.error("Failure adding update {} to queue.", update);
}
}
/**
* 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();
}
}
/**
* FOR TESTING ONLY
* check if update queue is empty
*/
boolean isUpdateQueueEmptyForTesting() {
return updates.isEmpty();
}
/**
* FOR TESTING ONLY
*/
void resetModuleState() {
moduleLoaderState = ModuleLoaderState.INIT;
}
/**
* FOR TESTING ONLY
* sets module loader state
*/
void setModuleLoaderStateForTesting(ModuleLoaderState state) {
moduleLoaderState = state;
}
@Override
public Map<String, Object> getInfo(String type) {
if (!"summary".equals(type)) return null;
Map<String, Object> info = new HashMap<String, Object>();
info.put("# Switches", switchService.getAllSwitchDpids().size());
return info;
}
protected void setNotifiedRole(HARole newRole) {
notifiedRole = newRole;
}
@Override
public RoleManager getRoleManager() {
return roleManager;
}
public Optional<ControllerId> getId() {
short nodeId = syncService.getLocalNodeId();
if(nodeId == ClusterConfig.NODE_ID_UNCONFIGURED)
return Optional.absent();
else
return Optional.of(ControllerId.of(nodeId));
}
public ControllerCounters getCounters() {
return counters;
}
}