| /** |
| * 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.configmanager.INetworkConfigService; |
| 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; |
| protected INetworkConfigService networkConfig; |
| |
| // 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); |
| OFChannelHandler ch = 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(); |
| if (ch != null) { |
| 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 changeType 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 setNetworkConfigService(INetworkConfigService networkConfigService) { |
| this.networkConfig = networkConfigService; |
| } |
| |
| 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.debug("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(); |
| } |
| } |
| |
| public INetworkConfigService getNetworkConfigService() { |
| return networkConfig; |
| } |
| |
| // ************** |
| // 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; |
| } |
| |
| } |