Added cubby-holes for new projects.
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java
new file mode 100644
index 0000000..84f090a
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/Controller.java
@@ -0,0 +1,839 @@
+/**
+ * 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.onrc.onos.of.ctl.internal;
+
+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.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+
+import net.onrc.onos.of.ctl.IOFSwitchManager;
+import net.onrc.onos.of.ctl.Role;
+import net.onrc.onos.of.ctl.annotations.LogMessageDoc;
+import net.onrc.onos.of.ctl.annotations.LogMessageDocs;
+import net.onrc.onos.of.ctl.debugcounter.DebugCounter;
+import net.onrc.onos.of.ctl.debugcounter.IDebugCounter;
+import net.onrc.onos.of.ctl.debugcounter.IDebugCounterService;
+import net.onrc.onos.of.ctl.debugcounter.IDebugCounterService.CounterException;
+import net.onrc.onos.of.ctl.debugcounter.IDebugCounterService.CounterType;
+import net.onrc.onos.of.ctl.internal.OFChannelHandler.RoleRecvStatus;
+import net.onrc.onos.of.ctl.registry.IControllerRegistry;
+import net.onrc.onos.of.ctl.registry.RegistryException;
+import net.onrc.onos.of.ctl.registry.IControllerRegistry.ControlChangeCallback;
+import net.onrc.onos.of.ctl.util.Dpid;
+import net.onrc.onos.of.ctl.util.DummySwitchForTesting;
+import net.onrc.onos.of.ctl.util.InstanceId;
+import net.onrc.onos.of.ctl.IOFSwitch;
+import net.onrc.onos.of.ctl.IOFSwitch.PortChangeType;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+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.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+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
+ */
+@Component(immediate = true)
+public class Controller {
+
+ protected static final Logger log = LoggerFactory.getLogger(Controller.class);
+ static final String ERROR_DATABASE =
+ "The controller could not communicate with the system database.";
+ protected static final OFFactory FACTORY13 = OFFactories.getFactory(OFVersion.OF_13);
+ protected static final 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;
+
+ // Module dependencies
+
+ protected IControllerRegistry registryService;
+ protected IDebugCounterService debugCounters;
+
+
+ private IOFSwitchManager switchManager;
+
+ // Configuration options
+ protected int openFlowPort = 6633;
+ protected int workerThreads = 0;
+
+ // defined counters
+ private Counters counters;
+
+ // Start time of the controller
+ protected long systemStartTime;
+
+ // Flag to always flush flow table on switch reconnect (HA or otherwise)
+ protected boolean alwaysClearFlowsOnSwAdd = false;
+ private InstanceId instanceId;
+
+ // 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;
+
+ 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 {
+ log.error("Added switch {}", dpid);
+ connectedSwitches.put(dpid, h);
+ return true;
+ }
+ }
+
+ 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) ? 'N' : 'Y',
+ (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y'});
+ 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();
+
+ 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();
+ 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);
+ }
+ }
+
+
+ /**
+ * 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);
+ }
+
+ }
+
+ /**
+ * Clear all state in controller switch maps for a switch that has
+ * disconnected from the local controller. Also release control for
+ * that switch from the global repository. Notify switch listeners.
+ */
+ protected void removeConnectedSwitch(long dpid) {
+ releaseRegistryControl(dpid);
+ connectedSwitches.remove(dpid);
+ IOFSwitch sw = activeMasterSwitches.remove(dpid);
+ if (sw == null) {
+ sw = activeEqualSwitches.remove(dpid);
+ }
+ if (sw != null) {
+ sw.cancelAllStatisticsReplies();
+ sw.setConnected(false); // do we need this?
+ }
+ counters.switchDisconnected.updateCounterWithFlush();
+
+ }
+
+ /**
+ * Indicates that ports on the given switch have changed. Enqueue a
+ * switch update.
+ * @param sw
+ */
+ protected void notifyPortChanged(long dpid, OFPortDesc port,
+ PortChangeType changeType) {
+ if (port == null || changeType == null) {
+ String msg = String.format("Switch port or changetType must not "
+ + "be null in port change notification");
+ throw new NullPointerException(msg);
+ }
+ if (connectedSwitches.get(dpid) == null || getSwitch(dpid) == null) {
+ log.warn("Port change update on switch {} not connected or activated "
+ + "... Aborting.", HexString.toHexString(dpid));
+ return;
+ }
+
+ }
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+
+ public synchronized void setIOFSwitchManager(IOFSwitchManager swManager) {
+ this.switchManager = swManager;
+ this.registryService = swManager.getRegistry();
+ }
+
+
+ public void setDebugCounter(IDebugCounterService dcs) {
+ this.debugCounters = dcs;
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Submit request to the registry service for mastership of the
+ * switch.
+ * @param dpid this datapath to get role for
+ */
+ public synchronized void submitRegistryRequest(long dpid) {
+ if (registryService == null) {
+ /*
+ * If we have no registry then simply assign
+ * mastership to this controller.
+ */
+ new RoleChangeCallback().controlChanged(dpid, true);
+ return;
+ }
+ 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));
+ connectedSwitches.get(dpid).disconnectSwitch();
+ return;
+ }
+ //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
+ // TODO AAS: this is awful and needs to be fixed
+ Thread.yield();
+ // 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();
+ }
+ }
+
+ /**
+ * Relinquish role for the switch.
+ * @param dpidLong the controlled datapath
+ */
+ 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 (registryService != null && h.controlRequested) {
+ //TODO the above is not good for testing need to change controlrequest to method call.
+ registryService.releaseControl(dpidLong);
+ }
+ }
+
+
+ // FIXME: remove this method
+ public Map<Long, IOFSwitch> getSwitches() {
+ return getMasterSwitches();
+ }
+
+ // FIXME: remove this method
+ public Map<Long, IOFSwitch> getMasterSwitches() {
+ return Collections.unmodifiableMap(activeMasterSwitches);
+ }
+
+
+
+ public Set<Long> getAllSwitchDpids() {
+ Set<Long> dpids = new HashSet<Long>();
+ dpids.addAll(activeMasterSwitches.keySet());
+ dpids.addAll(activeEqualSwitches.keySet());
+ return dpids;
+ }
+
+
+ public Set<Long> getAllMasterSwitchDpids() {
+ Set<Long> dpids = new HashSet<Long>();
+ dpids.addAll(activeMasterSwitches.keySet());
+ return dpids;
+ }
+
+
+ public Set<Long> getAllEqualSwitchDpids() {
+ Set<Long> dpids = new HashSet<Long>();
+ dpids.addAll(activeEqualSwitches.keySet());
+ return dpids;
+ }
+
+
+ public IOFSwitch getSwitch(long dpid) {
+ IOFSwitch sw = null;
+ sw = activeMasterSwitches.get(dpid);
+ if (sw != null) {
+ return sw;
+ }
+ sw = activeEqualSwitches.get(dpid);
+ if (sw != null) {
+ return sw;
+ }
+ return sw;
+ }
+
+
+ public IOFSwitch getMasterSwitch(long dpid) {
+ return activeMasterSwitches.get(dpid);
+ }
+
+
+ public IOFSwitch getEqualSwitch(long dpid) {
+ return activeEqualSwitches.get(dpid);
+ }
+
+
+
+
+
+ public OFFactory getOFMessageFactory10() {
+ return FACTORY10;
+ }
+
+
+ public OFFactory getOFMessageFactory13() {
+ return FACTORY13;
+ }
+
+
+
+ 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;
+ }
+
+
+ public long getSystemStartTime() {
+ return (this.systemStartTime);
+ }
+
+
+ public InstanceId getInstanceId() {
+ return instanceId;
+ }
+
+
+ // **************
+ // Initialization
+ // **************
+
+ /**
+ * Tell controller that we're ready to accept switches loop.
+ *
+ * @throws IOException
+ */
+ @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() {
+
+ 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);
+ }
+
+ }
+
+ 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.instanceId = new InstanceId(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.instanceId = new InstanceId(hostname);
+ } catch (UnknownHostException e) {
+ log.warn("Can't get hostname, using the default");
+ }
+ }
+
+ log.debug("ControllerId set to {}", this.instanceId);
+ }
+
+
+ /**
+ * 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.activeMasterSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+ this.activeEqualSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+ this.connectedSwitches = new ConcurrentHashMap<Long, OFChannelHandler>();
+ this.controllerNodeIPsCache = new HashMap<String, String>();
+
+ setConfigParams(configParams);
+ this.systemStartTime = System.currentTimeMillis();
+ this.setDebugCounter(new DebugCounter());
+ this.counters = new Counters();
+ this.multiCacheLock = new Object();
+
+ }
+
+ /**
+ * Startup all of the controller's components.
+ */
+ @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 synchronized void startupComponents() {
+ try {
+ if (registryService != null) {
+ registryService.registerController(instanceId.toString());
+ }
+ } catch (RegistryException e) {
+ log.warn("Registry service error: {}", e.getMessage());
+ }
+
+ // register counters and events
+ try {
+ this.counters.createCounters(debugCounters);
+ } catch (CounterException e) {
+ log.warn("Counters unavailable: {}", e.getMessage());
+ }
+ }
+
+ // **************
+ // debugCounter registrations
+ // **************
+
+ public static class Counters {
+ public static final String PREFIX = "controller";
+ public IDebugCounter switchActivated;
+ public IDebugCounter switchWithSameDpidActivated; // warn
+ public IDebugCounter switchDisconnected;
+ public IDebugCounter messageReceived;
+ 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 roleReplyTimeout;
+ public IDebugCounter roleReplyReceived; // expected RoleReply received
+ public IDebugCounter roleReplyErrorUnsupported;
+ public IDebugCounter switchCounterRegistrationFailed;
+
+ void createCounters(IDebugCounterService debugCounters) throws CounterException {
+
+ switchActivated =
+ debugCounters.registerCounter(
+ PREFIX, "switch-activated",
+ "A switch connected to this controller is now " +
+ "in MASTER role",
+ CounterType.ALWAYS_COUNT);
+
+ 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);
+
+ switchDisconnected =
+ debugCounters.registerCounter(
+ PREFIX, "switch-disconnected",
+ "FIXME: switch has disconnected",
+ 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);
+
+ 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);
+
+ 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);
+
+
+ }
+ }
+
+ public Counters getCounters() {
+ return this.counters;
+ }
+
+
+ // **************
+ // Utility methods
+ // **************
+
+ 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;
+ }
+
+
+ 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) {
+ if (switchManager == null) {
+ return new DummySwitchForTesting();
+ }
+ return switchManager.getSwitchImpl(desc.getMfrDesc(), desc.getHwDesc(),
+ desc.getSwDesc(), ofv);
+ }
+
+ @Activate
+ public void activate() {
+ log.info("Initialising OpenFlow Lib and IO");
+ this.init(new HashMap<String, String>());
+ this.startupComponents();
+ this.run();
+ }
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutException.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutException.java
new file mode 100644
index 0000000..48856a9
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutException.java
@@ -0,0 +1,29 @@
+/**
+ * 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.onrc.onos.of.ctl.internal;
+
+/**
+ * Exception is thrown when the handshake fails to complete.
+ * before a specified time
+ *
+ */
+public class HandshakeTimeoutException extends Exception {
+
+ private static final long serialVersionUID = 6859880268940337312L;
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutHandler.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutHandler.java
new file mode 100644
index 0000000..2ed3fd2
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/HandshakeTimeoutHandler.java
@@ -0,0 +1,94 @@
+/**
+* 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.onrc.onos.of.ctl.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+
+/**
+ * Trigger a timeout if a switch fails to complete handshake soon enough.
+ */
+public class HandshakeTimeoutHandler
+ extends SimpleChannelUpstreamHandler {
+ static final HandshakeTimeoutException EXCEPTION =
+ new HandshakeTimeoutException();
+
+ final OFChannelHandler channelHandler;
+ final Timer timer;
+ final long timeoutNanos;
+ volatile Timeout timeout;
+
+ public HandshakeTimeoutHandler(OFChannelHandler channelHandler,
+ Timer timer,
+ long timeoutSeconds) {
+ super();
+ this.channelHandler = channelHandler;
+ this.timer = timer;
+ this.timeoutNanos = TimeUnit.SECONDS.toNanos(timeoutSeconds);
+
+ }
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ if (timeoutNanos > 0) {
+ timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx),
+ timeoutNanos, TimeUnit.NANOSECONDS);
+ }
+ ctx.sendUpstream(e);
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ if (timeout != null) {
+ timeout.cancel();
+ timeout = null;
+ }
+ }
+
+ private final class HandshakeTimeoutTask implements TimerTask {
+
+ private final ChannelHandlerContext ctx;
+
+ HandshakeTimeoutTask(ChannelHandlerContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void run(Timeout t) throws Exception {
+ if (t.isCancelled()) {
+ return;
+ }
+
+ if (!ctx.getChannel().isOpen()) {
+ return;
+ }
+ if (!channelHandler.isHandshakeComplete()) {
+ Channels.fireExceptionCaught(ctx, EXCEPTION);
+ }
+ }
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java
new file mode 100644
index 0000000..3b18a59
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFChannelHandler.java
@@ -0,0 +1,2153 @@
+package net.onrc.onos.of.ctl.internal;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.RejectedExecutionException;
+
+import net.onrc.onos.of.ctl.IOFSwitch;
+import net.onrc.onos.of.ctl.IOFSwitch.PortChangeEvent;
+import net.onrc.onos.of.ctl.Role;
+import net.onrc.onos.of.ctl.annotations.LogMessageDoc;
+import net.onrc.onos.of.ctl.annotations.LogMessageDocs;
+import net.onrc.onos.of.ctl.debugcounter.IDebugCounterService.CounterException;
+import net.onrc.onos.of.ctl.internal.Controller.Counters;
+import net.onrc.onos.of.ctl.internal.OFChannelHandler.ChannelState.RoleReplyInfo;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
+import org.jboss.netty.handler.timeout.IdleStateEvent;
+import org.jboss.netty.handler.timeout.ReadTimeoutException;
+import org.projectfloodlight.openflow.exceptions.OFParseError;
+import org.projectfloodlight.openflow.protocol.OFAsyncGetReply;
+import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
+import org.projectfloodlight.openflow.protocol.OFBarrierReply;
+import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
+import org.projectfloodlight.openflow.protocol.OFControllerRole;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFDescStatsRequest;
+import org.projectfloodlight.openflow.protocol.OFEchoReply;
+import org.projectfloodlight.openflow.protocol.OFEchoRequest;
+import org.projectfloodlight.openflow.protocol.OFErrorMsg;
+import org.projectfloodlight.openflow.protocol.OFErrorType;
+import org.projectfloodlight.openflow.protocol.OFExperimenter;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.protocol.OFFlowModFailedCode;
+import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
+import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
+import org.projectfloodlight.openflow.protocol.OFGetConfigRequest;
+import org.projectfloodlight.openflow.protocol.OFHello;
+import org.projectfloodlight.openflow.protocol.OFHelloElem;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
+import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
+import org.projectfloodlight.openflow.protocol.OFPacketIn;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsRequest;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply;
+import org.projectfloodlight.openflow.protocol.OFRoleReply;
+import org.projectfloodlight.openflow.protocol.OFRoleRequest;
+import org.projectfloodlight.openflow.protocol.OFSetConfig;
+import org.projectfloodlight.openflow.protocol.OFStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
+import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
+import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
+import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg;
+import org.projectfloodlight.openflow.types.U32;
+import org.projectfloodlight.openflow.types.U64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * Channel handler deals with the switch connection and dispatches
+ * switch messages to the appropriate locations.
+ */
+class OFChannelHandler extends IdleStateAwareChannelHandler {
+ private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class);
+ private static final long DEFAULT_ROLE_TIMEOUT_MS = 2 * 1000; // 10 sec
+ private final Controller controller;
+ private final Counters counters;
+ private IOFSwitch sw;
+ private long thisdpid; // channelHandler cached value of connected switch id
+ private Channel channel;
+ // State needs to be volatile because the HandshakeTimeoutHandler
+ // needs to check if the handshake is complete
+ private volatile ChannelState state;
+
+ // All role messaging is handled by the roleChanger. The channel state machine
+ // coordinates between the roleChanger and the controller-global-registry-service
+ // to determine controller roles per switch.
+ private RoleChanger roleChanger;
+ // Used to coordinate between the controller and the cleanup thread(?)
+ // for access to the global registry on a per switch basis.
+ volatile Boolean controlRequested;
+ // When a switch with a duplicate dpid is found (i.e we already have a
+ // connected switch with the same dpid), the new switch is immediately
+ // disconnected. At that point netty callsback channelDisconnected() which
+ // proceeds to cleaup switch state - we need to ensure that it does not cleanup
+ // switch state for the older (still connected) switch
+ private volatile Boolean duplicateDpidFound;
+
+ // Temporary storage for switch-features and port-description
+ private OFFeaturesReply featuresReply;
+ private OFPortDescStatsReply portDescReply;
+ // a concurrent ArrayList to temporarily store port status messages
+ // before we are ready to deal with them
+ private final CopyOnWriteArrayList<OFPortStatus> pendingPortStatusMsg;
+
+ //Indicates the openflow version used by this switch
+ protected OFVersion ofVersion;
+ protected OFFactory factory13;
+ protected OFFactory factory10;
+
+ /** transaction Ids to use during handshake. Since only one thread
+ * calls into an OFChannelHandler instance, we don't need atomic.
+ * We will count down
+ */
+ private int handshakeTransactionIds = -1;
+
+ /**
+ * Create a new unconnected OFChannelHandler.
+ * @param controller
+ */
+ OFChannelHandler(Controller controller) {
+ this.controller = controller;
+ this.counters = controller.getCounters();
+ this.roleChanger = new RoleChanger(DEFAULT_ROLE_TIMEOUT_MS);
+ this.state = ChannelState.INIT;
+ this.pendingPortStatusMsg = new CopyOnWriteArrayList<OFPortStatus>();
+ factory13 = controller.getOFMessageFactory13();
+ factory10 = controller.getOFMessageFactory10();
+ controlRequested = Boolean.FALSE;
+ duplicateDpidFound = Boolean.FALSE;
+ }
+
+ //*******************
+ // Role Handling
+ //*******************
+
+ /**
+ * When we remove a pending role request we use this enum to indicate how we
+ * arrived at the decision. When we send a role request to the switch, we
+ * also use this enum to indicate what we expect back from the switch, so the
+ * role changer can match the reply to our expectation.
+ */
+ public enum RoleRecvStatus {
+ /** The switch returned an error indicating that roles are not.
+ * supported*/
+ UNSUPPORTED,
+ /** The request timed out. */
+ NO_REPLY,
+ /** The reply was old, there is a newer request pending. */
+ OLD_REPLY,
+ /**
+ * The reply's role matched the role that this controller set in the
+ * request message - invoked either initially at startup or to reassert
+ * current role.
+ */
+ MATCHED_CURRENT_ROLE,
+ /**
+ * The reply's role matched the role that this controller set in the
+ * request message - this is the result of a callback from the
+ * global registry, followed by a role request sent to the switch.
+ */
+ MATCHED_SET_ROLE,
+ /**
+ * The reply's role was a response to the query made by this controller.
+ */
+ REPLY_QUERY,
+ /** We received a role reply message from the switch
+ * but the expectation was unclear, or there was no expectation.
+ */
+ OTHER_EXPECTATION,
+ }
+
+ /**
+ * Forwards to RoleChanger. See there.
+ * @param role
+ */
+ public void sendRoleRequest(Role role, RoleRecvStatus expectation) {
+ try {
+ roleChanger.sendRoleRequest(role, expectation);
+ } catch (IOException e) {
+ log.error("Disconnecting switch {} due to IO Error: {}",
+ getSwitchInfoString(), e.getMessage());
+ channel.close();
+ }
+ }
+
+ // XXX S consider if necessary
+ public void disconnectSwitch() {
+ sw.disconnectSwitch();
+ }
+
+ /**
+ * A utility class to handle role requests and replies for this channel.
+ * After a role request is submitted the role changer keeps track of the
+ * pending request, collects the reply (if any) and times out the request
+ * if necessary.
+ *
+ * To simplify role handling we only keep track of the /last/ pending
+ * role reply send to the switch. If multiple requests are pending and
+ * we receive replies for earlier requests we ignore them. However, this
+ * way of handling pending requests implies that we could wait forever if
+ * a new request is submitted before the timeout triggers. If necessary
+ * we could work around that though.
+ */
+ private class RoleChanger {
+ // indicates that a request is currently pending
+ // needs to be volatile to allow correct double-check idiom
+ private volatile boolean requestPending;
+ // the transaction Id of the pending request
+ private int pendingXid;
+ // the role that's pending
+ private Role pendingRole;
+ // system time in MS when we send the request
+ private long roleSubmitTime;
+ // the timeout to use
+ private final long roleTimeoutMs;
+ // the expectation set by the caller for the returned role
+ private RoleRecvStatus expectation;
+
+ public RoleChanger(long roleTimeoutMs) {
+ this.requestPending = false;
+ this.roleSubmitTime = 0;
+ this.pendingXid = -1;
+ this.pendingRole = null;
+ this.roleTimeoutMs = roleTimeoutMs;
+ this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
+ }
+
+ /**
+ * Send NX role request message to the switch requesting the specified
+ * role.
+ *
+ * @param sw switch to send the role request message to
+ * @param role role to request
+ */
+ private int sendNxRoleRequest(Role role) throws IOException {
+ // Convert the role enum to the appropriate role to send
+ OFNiciraControllerRole roleToSend = OFNiciraControllerRole.ROLE_OTHER;
+ switch (role) {
+ case MASTER:
+ roleToSend = OFNiciraControllerRole.ROLE_MASTER;
+ break;
+ case SLAVE:
+ case EQUAL:
+ default:
+ // ensuring that the only two roles sent to 1.0 switches with
+ // Nicira role support, are MASTER and SLAVE
+ roleToSend = OFNiciraControllerRole.ROLE_SLAVE;
+ log.warn("Sending Nx Role.SLAVE to switch {}.", sw);
+ }
+ int xid = sw.getNextTransactionId();
+ OFExperimenter roleRequest = factory10
+ .buildNiciraControllerRoleRequest()
+ .setXid(xid)
+ .setRole(roleToSend)
+ .build();
+ sw.write(Collections.<OFMessage>singletonList(roleRequest));
+ return xid;
+ }
+
+ private int sendOF13RoleRequest(Role role) throws IOException {
+ // Convert the role enum to the appropriate role to send
+ OFControllerRole roleToSend = OFControllerRole.ROLE_NOCHANGE;
+ switch (role) {
+ case EQUAL:
+ roleToSend = OFControllerRole.ROLE_EQUAL;
+ break;
+ case MASTER:
+ roleToSend = OFControllerRole.ROLE_MASTER;
+ break;
+ case SLAVE:
+ roleToSend = OFControllerRole.ROLE_SLAVE;
+ break;
+ default:
+ log.warn("Sending default role.noChange to switch {}."
+ + " Should only be used for queries.", sw);
+ }
+
+ int xid = sw.getNextTransactionId();
+ OFRoleRequest rrm = factory13
+ .buildRoleRequest()
+ .setRole(roleToSend)
+ .setXid(xid)
+ .setGenerationId(sw.getNextGenerationId())
+ .build();
+ sw.write(rrm);
+ return xid;
+ }
+
+ /**
+ * Send a role request with the given role to the switch and update
+ * the pending request and timestamp.
+ * Sends an OFPT_ROLE_REQUEST to an OF1.3 switch, OR
+ * Sends an NX_ROLE_REQUEST to an OF1.0 switch if configured to support it
+ * in the IOFSwitch driver. If not supported, this method sends nothing
+ * and returns 'false'. The caller should take appropriate action.
+ *
+ * One other optimization we do here is that for OF1.0 switches with
+ * Nicira role message support, we force the Role.EQUAL to become
+ * Role.SLAVE, as there is no defined behavior for the Nicira role OTHER.
+ * We cannot expect it to behave like SLAVE. We don't have this problem with
+ * OF1.3 switches, because Role.EQUAL is well defined and we can simulate
+ * SLAVE behavior by using ASYNC messages.
+ *
+ * @param role
+ * @throws IOException
+ * @returns false if and only if the switch does not support role-request
+ * messages, according to the switch driver; true otherwise.
+ */
+ synchronized boolean sendRoleRequest(Role role, RoleRecvStatus exp)
+ throws IOException {
+ this.expectation = exp;
+
+ if (ofVersion == OFVersion.OF_10) {
+ Boolean supportsNxRole = (Boolean)
+ sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE);
+ if (!supportsNxRole) {
+ log.debug("Switch driver indicates no support for Nicira "
+ + "role request messages. Not sending ...");
+ state.handleUnsentRoleMessage(OFChannelHandler.this, role,
+ expectation);
+ return false;
+ }
+ // OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
+ // make Role.EQUAL become Role.SLAVE
+ role = (role == Role.EQUAL) ? Role.SLAVE : role;
+ pendingXid = sendNxRoleRequest(role);
+ pendingRole = role;
+ roleSubmitTime = System.currentTimeMillis();
+ requestPending = true;
+ } else {
+ // OF1.3 switch, use OFPT_ROLE_REQUEST message
+ pendingXid = sendOF13RoleRequest(role);
+ pendingRole = role;
+ roleSubmitTime = System.currentTimeMillis();
+ requestPending = true;
+ }
+ return true;
+ }
+
+ /**
+ * Deliver a received role reply.
+ *
+ * Check if a request is pending and if the received reply matches the
+ * the expected pending reply (we check both role and xid) we set
+ * the role for the switch/channel.
+ *
+ * If a request is pending but doesn't match the reply we ignore it, and
+ * return
+ *
+ * If no request is pending we disconnect with a SwitchStateException
+ *
+ * @param RoleReplyInfo information about role-reply in format that
+ * controller can understand.
+ * @throws SwitchStateException if no request is pending
+ */
+ synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
+ throws SwitchStateException {
+ if (!requestPending) {
+ Role currentRole = (sw != null) ? sw.getRole() : null;
+ if (currentRole != null) {
+ if (currentRole == rri.getRole()) {
+ // Don't disconnect if the role reply we received is
+ // for the same role we are already in.
+ log.debug("Received unexpected RoleReply from "
+ + "Switch: {} in State: {}. "
+ + "Role in reply is same as current role of this "
+ + "controller for this sw. Ignoring ...",
+ getSwitchInfoString(), state.toString());
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ } else {
+ String msg = String.format("Switch: [%s], State: [%s], "
+ + "received unexpected RoleReply[%s]. "
+ + "No roles are pending, and this controller's "
+ + "current role:[%s] does not match reply. "
+ + "Disconnecting switch ... ",
+ OFChannelHandler.this.getSwitchInfoString(),
+ OFChannelHandler.this.state.toString(),
+ rri, currentRole);
+ throw new SwitchStateException(msg);
+ }
+ }
+ log.debug("Received unexpected RoleReply {} from "
+ + "Switch: {} in State: {}. "
+ + "This controller has no current role for this sw. "
+ + "Ignoring ...", new Object[] {rri,
+ getSwitchInfoString(), state});
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ int xid = (int) rri.getXid();
+ Role role = rri.getRole();
+ // XXX S should check generation id meaningfully and other cases of expectations
+ // U64 genId = rri.getGenId();
+
+ if (pendingXid != xid) {
+ log.debug("Received older role reply from " +
+ "switch {} ({}). Ignoring. " +
+ "Waiting for {}, xid={}",
+ new Object[] {getSwitchInfoString(), rri,
+ pendingRole, pendingXid });
+ return RoleRecvStatus.OLD_REPLY;
+ }
+
+ if (pendingRole == role) {
+ log.debug("Received role reply message from {} that matched "
+ + "expected role-reply {} with expectations {}",
+ new Object[] {getSwitchInfoString(), role, expectation});
+ counters.roleReplyReceived.updateCounterWithFlush();
+ //setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY); dont want to set state here
+ if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
+ expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
+ return expectation;
+ } else {
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+ }
+
+ // if xids match but role's don't, perhaps its a query (OF1.3)
+ if (expectation == RoleRecvStatus.REPLY_QUERY) {
+ return expectation;
+ }
+
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ /**
+ * Called if we receive an error message. If the xid matches the
+ * pending request we handle it otherwise we ignore it.
+ *
+ * Note: since we only keep the last pending request we might get
+ * error messages for earlier role requests that we won't be able
+ * to handle
+ */
+ synchronized RoleRecvStatus deliverError(OFErrorMsg error)
+ throws SwitchStateException {
+ if (!requestPending) {
+ log.debug("Received an error msg from sw {}, but no pending "
+ + "requests in role-changer; not handling ...",
+ getSwitchInfoString());
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+ if (pendingXid != error.getXid()) {
+ if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
+ log.debug("Received an error msg from sw {} for a role request,"
+ + " but not for pending request in role-changer; "
+ + " ignoring error {} ...",
+ getSwitchInfoString(), error);
+ }
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+ // it is an error related to a currently pending role request message
+ if (error.getErrType() == OFErrorType.BAD_REQUEST) {
+ counters.roleReplyErrorUnsupported.updateCounterWithFlush();
+ log.error("Received a error msg {} from sw {} in state {} for "
+ + "pending role request {}. Switch driver indicates "
+ + "role-messaging is supported. Possible issues in "
+ + "switch driver configuration?", new Object[] {
+ ((OFBadRequestErrorMsg) error).toString(),
+ getSwitchInfoString(), state, pendingRole
+ });
+ return RoleRecvStatus.UNSUPPORTED;
+ }
+
+ if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
+ OFRoleRequestFailedErrorMsg rrerr =
+ (OFRoleRequestFailedErrorMsg) error;
+ switch (rrerr.getCode()) {
+ case BAD_ROLE:
+ // switch says that current-role-req has bad role?
+ // for now we disconnect
+ // fall-thru
+ case STALE:
+ // switch says that current-role-req has stale gen-id?
+ // for now we disconnect
+ // fall-thru
+ case UNSUP:
+ // switch says that current-role-req has role that
+ // cannot be supported? for now we disconnect
+ String msgx = String.format("Switch: [%s], State: [%s], "
+ + "received Error to for pending role request [%s]. "
+ + "Error:[%s]. Disconnecting switch ... ",
+ OFChannelHandler.this.getSwitchInfoString(),
+ OFChannelHandler.this.state.toString(),
+ pendingRole, rrerr);
+ throw new SwitchStateException(msgx);
+ default:
+ break;
+ }
+ }
+
+ // This error message was for a role request message but we dont know
+ // how to handle errors for nicira role request messages
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ /**
+ * Check if a pending role request has timed out.
+ */
+ void checkTimeout() {
+ if (!requestPending) {
+ return;
+ }
+ synchronized (this) {
+ if (!requestPending) {
+ return;
+ }
+ long now = System.currentTimeMillis();
+ if (now - roleSubmitTime > roleTimeoutMs) {
+ // timeout triggered.
+ counters.roleReplyTimeout.updateCounterWithFlush();
+ //setSwitchRole(pendingRole, RoleRecvStatus.NO_REPLY);
+ // XXX S come back to this
+ }
+ }
+ }
+
+ }
+
+ //*************************
+ // Channel State Machine
+ //*************************
+
+ /**
+ * The state machine for handling the switch/channel state. All state
+ * transitions should happen from within the state machine (and not from other
+ * parts of the code)
+ */
+ enum ChannelState {
+ /**
+ * Initial state before channel is connected.
+ */
+ INIT(false) {
+ @Override
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException {
+ // need to implement since its abstract but it will never
+ // be called
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+ },
+
+ /**
+ * We send a OF 1.3 HELLO to the switch and wait for a Hello from the switch.
+ * Once we receive the reply, we decide on OF 1.3 or 1.0 switch - no other
+ * protocol version is accepted.
+ * We send an OFFeaturesRequest depending on the protocol version selected
+ * Next state is WAIT_FEATURES_REPLY
+ */
+ WAIT_HELLO(false) {
+ @Override
+ void processOFHello(OFChannelHandler h, OFHello m)
+ throws IOException {
+ // TODO We could check for the optional bitmap, but for now
+ // we are just checking the version number.
+ if (m.getVersion() == OFVersion.OF_13) {
+ log.info("Received {} Hello from {}", m.getVersion(),
+ h.channel.getRemoteAddress());
+ h.ofVersion = OFVersion.OF_13;
+ } else if (m.getVersion() == OFVersion.OF_10) {
+ log.info("Received {} Hello from {} - switching to OF "
+ + "version 1.0", m.getVersion(),
+ h.channel.getRemoteAddress());
+ h.ofVersion = OFVersion.OF_10;
+ } else {
+ log.error("Received Hello of version {} from switch at {}. "
+ + "This controller works with OF1.0 and OF1.3 "
+ + "switches. Disconnecting switch ...",
+ m.getVersion(), h.channel.getRemoteAddress());
+ h.channel.disconnect();
+ return;
+ }
+ h.sendHandshakeFeaturesRequestMessage();
+ h.setState(WAIT_FEATURES_REPLY);
+ }
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+ },
+
+
+ /**
+ * We are waiting for a features reply message. Once we receive it, the
+ * behavior depends on whether this is a 1.0 or 1.3 switch. For 1.0,
+ * we send a SetConfig request, barrier, and GetConfig request and the
+ * next state is WAIT_CONFIG_REPLY. For 1.3, we send a Port description
+ * request and the next state is WAIT_PORT_DESC_REPLY.
+ */
+ WAIT_FEATURES_REPLY(false) {
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException {
+ h.thisdpid = m.getDatapathId().getLong();
+ log.info("Received features reply for switch at {} with dpid {}",
+ h.getSwitchInfoString(), h.thisdpid);
+ //update the controller about this connected switch
+ boolean success = h.controller.addConnectedSwitch(
+ h.thisdpid, h);
+ if (!success) {
+ disconnectDuplicate(h);
+ return;
+ }
+
+ h.featuresReply = m; //temp store
+ if (h.ofVersion == OFVersion.OF_10) {
+ h.sendHandshakeSetConfig();
+ h.setState(WAIT_CONFIG_REPLY);
+ } else {
+ //version is 1.3, must get switchport information
+ h.sendHandshakeOFPortDescRequest();
+ h.setState(WAIT_PORT_DESC_REPLY);
+ }
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+ },
+
+ /**
+ * We are waiting for a description of the 1.3 switch ports.
+ * Once received, we send a SetConfig request
+ * Next State is WAIT_CONFIG_REPLY
+ */
+ WAIT_PORT_DESC_REPLY(false) {
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws SwitchStateException {
+ // Read port description
+ if (m.getStatsType() != OFStatsType.PORT_DESC) {
+ log.warn("Expecting port description stats but received stats "
+ + "type {} from {}. Ignoring ...", m.getStatsType(),
+ h.channel.getRemoteAddress());
+ return;
+ }
+ if (m.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ log.warn("Stats reply indicates more stats from sw {} for "
+ + "port description - not currently handled",
+ h.getSwitchInfoString());
+ }
+ h.portDescReply = (OFPortDescStatsReply) m; // temp store
+ log.info("Received port desc reply for switch at {}",
+ h.getSwitchInfoString());
+ try {
+ h.sendHandshakeSetConfig();
+ } catch (IOException e) {
+ log.error("Unable to send setConfig after PortDescReply. "
+ + "Error: {}", e.getMessage());
+ }
+ h.setState(WAIT_CONFIG_REPLY);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException {
+ logErrorDisconnect(h, m);
+
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ unhandledMessageReceived(h, m);
+
+ }
+ },
+
+ /**
+ * We are waiting for a config reply message. Once we receive it
+ * we send a DescriptionStatsRequest to the switch.
+ * Next state: WAIT_DESCRIPTION_STAT_REPLY
+ */
+ WAIT_CONFIG_REPLY(false) {
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(level = "WARN",
+ message = "Config Reply from {switch} has "
+ + "miss length set to {length}",
+ explanation = "The controller requires that the switch "
+ + "use a miss length of 0xffff for correct "
+ + "function",
+ recommendation = "Use a different switch to ensure "
+ + "correct function")
+ })
+ void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
+ throws IOException {
+ if (m.getMissSendLen() == 0xffff) {
+ log.trace("Config Reply from switch {} confirms "
+ + "miss length set to 0xffff",
+ h.getSwitchInfoString());
+ } else {
+ // FIXME: we can't really deal with switches that don't send
+ // full packets. Shouldn't we drop the connection here?
+ log.warn("Config Reply from switch {} has"
+ + "miss length set to {}",
+ h.getSwitchInfoString(),
+ m.getMissSendLen());
+ }
+ h.sendHandshakeDescriptionStatsRequest();
+ h.setState(WAIT_DESCRIPTION_STAT_REPLY);
+ }
+
+ @Override
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
+ // do nothing;
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ log.error("Received multipart(stats) message sub-type {}",
+ m.getStatsType());
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+
+ /**
+ * We are waiting for a OFDescriptionStat message from the switch.
+ * Once we receive any stat message we try to parse it. If it's not
+ * a description stats message we disconnect. If its the expected
+ * description stats message, we:
+ * - use the switch driver to bind the switch and get an IOFSwitch instance
+ * - setup the IOFSwitch instance
+ * - add switch controller and send the initial role
+ * request to the switch.
+ * Next state: WAIT_INITIAL_ROLE
+ * In the typical case, where switches support role request messages
+ * the next state is where we expect the role reply message.
+ * In the special case that where the switch does not support any kind
+ * of role request messages, we don't send a role message, but we do
+ * request mastership from the registry service. This controller
+ * should become master once we hear back from the registry service.
+ * All following states will have a h.sw instance!
+ */
+ WAIT_DESCRIPTION_STAT_REPLY(false) {
+ @LogMessageDoc(message = "Switch {switch info} bound to class "
+ + "{switch driver}, description {switch description}",
+ explanation = "The specified switch has been bound to "
+ + "a switch driver based on the switch description"
+ + "received from the switch")
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws SwitchStateException {
+ // Read description, if it has been updated
+ if (m.getStatsType() != OFStatsType.DESC) {
+ log.warn("Expecting Description stats but received stats "
+ + "type {} from {}. Ignoring ...", m.getStatsType(),
+ h.channel.getRemoteAddress());
+ return;
+ }
+ log.info("Received switch description reply from switch at {}",
+ h.channel.getRemoteAddress());
+ OFDescStatsReply drep = (OFDescStatsReply) m;
+ // Here is where we differentiate between different kinds of switches
+ h.sw = h.controller.getOFSwitchInstance(drep, h.ofVersion);
+ // set switch information
+ h.sw.setOFVersion(h.ofVersion);
+ h.sw.setFeaturesReply(h.featuresReply);
+ h.sw.setPortDescReply(h.portDescReply);
+ h.sw.setConnected(true);
+ h.sw.setChannel(h.channel);
+
+ try {
+ h.sw.setDebugCounterService(h.controller.getDebugCounter());
+ } catch (CounterException e) {
+ h.counters.switchCounterRegistrationFailed
+ .updateCounterNoFlush();
+ log.warn("Could not register counters for switch {} ",
+ h.getSwitchInfoString(), e);
+ }
+
+ log.info("Switch {} bound to class {}, description {}",
+ new Object[] {h.sw, h.sw.getClass(), drep });
+ //Put switch in EQUAL mode until we hear back from the global registry
+ log.debug("Setting new switch {} to EQUAL and sending Role request",
+ h.sw.getStringId());
+ h.setSwitchRole(Role.EQUAL);
+ try {
+ boolean supportsRRMsg = h.roleChanger.sendRoleRequest(Role.EQUAL,
+ RoleRecvStatus.MATCHED_CURRENT_ROLE);
+ if (!supportsRRMsg) {
+ log.warn("Switch {} does not support role request messages "
+ + "of any kind. No role messages were sent. "
+ + "This controller instance SHOULD become MASTER "
+ + "from the registry process. ",
+ h.getSwitchInfoString());
+ }
+ h.setState(WAIT_INITIAL_ROLE);
+ // request control of switch from global registry -
+ // necessary even if this is the only controller the
+ // switch is connected to.
+ h.controller.submitRegistryRequest(h.sw.getId());
+ } catch (IOException e) {
+ log.error("Exception when sending role request: {} ",
+ e.getMessage());
+ // FIXME shouldn't we disconnect?
+ }
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+ /**
+ * We are waiting for a role reply message in response to a role request
+ * sent after hearing back from the registry service -- OR -- we are
+ * just waiting to hear back from the registry service in the case that
+ * the switch does not support role messages. If completed successfully,
+ * the controller's role for this switch will be set here.
+ * Before we move to the state corresponding to the role, we allow the
+ * switch specific driver to complete its configuration. This configuration
+ * typically depends on the role the controller is playing for this switch.
+ * And so we set the switch role (for 'this' controller) before we start
+ * the driver-sub-handshake.
+ * Next State: WAIT_SWITCH_DRIVER_SUB_HANDSHAKE
+ */
+ WAIT_INITIAL_ROLE(false) {
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws SwitchStateException {
+ // role changer will ignore the error if it isn't for it
+ RoleRecvStatus rrstatus = h.roleChanger.deliverError(m);
+ if (rrstatus == RoleRecvStatus.OTHER_EXPECTATION) {
+ logError(h, m);
+ }
+ }
+
+ @Override
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws IOException, SwitchStateException {
+ Role role = extractNiciraRoleReply(h, m);
+ // If role == null it means the vendor (experimenter) message
+ // wasn't really a Nicira role reply. We ignore this case.
+ if (role != null) {
+ RoleReplyInfo rri = new RoleReplyInfo(role, null, m.getXid());
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ setRoleAndStartDriverHandshake(h, rri.getRole());
+ } // else do nothing - wait for the correct expected reply
+ } else {
+ unhandledMessageReceived(h, m);
+ }
+ }
+
+ @Override
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException, IOException {
+ RoleReplyInfo rri = extractOFRoleReply(h, m);
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ setRoleAndStartDriverHandshake(h, rri.getRole());
+ } // else do nothing - wait for the correct expected reply
+ }
+
+ @Override
+ void handleUnsentRoleMessage(OFChannelHandler h, Role role,
+ RoleRecvStatus expectation) throws IOException {
+ // typically this is triggered for a switch where role messages
+ // are not supported - we confirm that the role being set is
+ // master and move to the next state
+ if (expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
+ if (role == Role.MASTER) {
+ setRoleAndStartDriverHandshake(h, role);
+ } else {
+ log.error("Expected MASTER role from registry for switch "
+ + "which has no support for role-messages."
+ + "Received {}. It is possible that this switch "
+ + "is connected to other controllers, in which "
+ + "case it should support role messages - not "
+ + "moving forward.", role);
+ }
+ } // else do nothing - wait to hear back from registry
+
+ }
+
+ private void setRoleAndStartDriverHandshake(OFChannelHandler h,
+ Role role) throws IOException {
+ h.setSwitchRole(role);
+ h.sw.startDriverHandshake();
+ if (h.sw.isDriverHandshakeComplete()) {
+ Role mySwitchRole = h.sw.getRole();
+ if (mySwitchRole == Role.MASTER) {
+ log.info("Switch-driver sub-handshake complete. "
+ + "Activating switch {} with Role: MASTER",
+ h.getSwitchInfoString());
+ handlePendingPortStatusMessages(h); //before activation
+ boolean success = h.controller.addActivatedMasterSwitch(
+ h.sw.getId(), h.sw);
+ if (!success) {
+ disconnectDuplicate(h);
+ return;
+ }
+ h.setState(MASTER);
+ } else {
+ log.info("Switch-driver sub-handshake complete. "
+ + "Activating switch {} with Role: EQUAL",
+ h.getSwitchInfoString());
+ handlePendingPortStatusMessages(h); //before activation
+ boolean success = h.controller.addActivatedEqualSwitch(
+ h.sw.getId(), h.sw);
+ if (!success) {
+ disconnectDuplicate(h);
+ return;
+ }
+ h.setState(EQUAL);
+ }
+ } else {
+ h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE);
+ }
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ h.pendingPortStatusMsg.add(m);
+
+ }
+ },
+
+ /**
+ * We are waiting for the respective switch driver to complete its
+ * configuration. Notice that we do not consider this to be part of the main
+ * switch-controller handshake. But we do consider it as a step that comes
+ * before we declare the switch as available to the controller.
+ * Next State: depends on the role of this controller for this switch - either
+ * MASTER or EQUAL.
+ */
+ WAIT_SWITCH_DRIVER_SUB_HANDSHAKE(true) {
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException {
+ // will never be called. We override processOFMessage
+ }
+
+ @Override
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException {
+ if (m.getType() == OFType.ECHO_REQUEST) {
+ processOFEchoRequest(h, (OFEchoRequest) m);
+ } else {
+ // FIXME: other message to handle here?
+ h.sw.processDriverHandshakeMessage(m);
+ if (h.sw.isDriverHandshakeComplete()) {
+ // consult the h.sw role and goto that state
+ Role mySwitchRole = h.sw.getRole();
+ if (mySwitchRole == Role.MASTER) {
+ log.info("Switch-driver sub-handshake complete. "
+ + "Activating switch {} with Role: MASTER",
+ h.getSwitchInfoString());
+ handlePendingPortStatusMessages(h); //before activation
+ boolean success = h.controller.addActivatedMasterSwitch(
+ h.sw.getId(), h.sw);
+ if (!success) {
+ disconnectDuplicate(h);
+ return;
+ }
+ h.setState(MASTER);
+ } else {
+ log.info("Switch-driver sub-handshake complete. "
+ + "Activating switch {} with Role: EQUAL",
+ h.getSwitchInfoString());
+ handlePendingPortStatusMessages(h); //before activation
+ boolean success = h.controller.addActivatedEqualSwitch(
+ h.sw.getId(), h.sw);
+ if (!success) {
+ disconnectDuplicate(h);
+ return;
+ }
+ h.setState(EQUAL);
+ }
+ }
+ }
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+
+ /**
+ * This controller is in MASTER role for this switch. We enter this state
+ * after requesting and winning control from the global registry.
+ * The main handshake as well as the switch-driver sub-handshake
+ * is complete at this point.
+ * // XXX S reconsider below
+ * In the (near) future we may deterministically assign controllers to
+ * switches at startup.
+ * We only leave this state if the switch disconnects or
+ * if we send a role request for SLAVE /and/ receive the role reply for
+ * SLAVE.
+ */
+ MASTER(true) {
+ @LogMessageDoc(level = "WARN",
+ message = "Received permission error from switch {} while"
+ + "being master. Reasserting master role.",
+ explanation = "The switch has denied an operation likely "
+ + "indicating inconsistent controller roles",
+ recommendation = "This situation can occurs transiently during role"
+ + " changes. If, however, the condition persists or happens"
+ + " frequently this indicates a role inconsistency. "
+ + LogMessageDoc.CHECK_CONTROLLER)
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException {
+ // first check if the error msg is in response to a role-request message
+ RoleRecvStatus rrstatus = h.roleChanger.deliverError(m);
+ if (rrstatus != RoleRecvStatus.OTHER_EXPECTATION) {
+ // rolechanger has handled the error message - we are done
+ return;
+ }
+
+ // if we get here, then the error message is for something else
+ if (m.getErrType() == OFErrorType.BAD_REQUEST &&
+ ((OFBadRequestErrorMsg) m).getCode() ==
+ OFBadRequestCode.EPERM) {
+ // We are the master controller and the switch returned
+ // a permission error. This is a likely indicator that
+ // the switch thinks we are slave. Reassert our
+ // role
+ // FIXME: this could be really bad during role transitions
+ // if two controllers are master (even if its only for
+ // a brief period). We might need to see if these errors
+ // persist before we reassert
+ h.counters.epermErrorWhileSwitchIsMaster.updateCounterWithFlush();
+ log.warn("Received permission error from switch {} while" +
+ "being master. Reasserting master role.",
+ h.getSwitchInfoString());
+ //h.controller.reassertRole(h, Role.MASTER);
+ // XXX S reassert in role changer or reconsider if all this
+ // stuff is really needed
+ } else if (m.getErrType() == OFErrorType.FLOW_MOD_FAILED &&
+ ((OFFlowModFailedErrorMsg) m).getCode() ==
+ OFFlowModFailedCode.ALL_TABLES_FULL) {
+ h.sw.setTableFull(true);
+ } else {
+ logError(h, m);
+ }
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m) {
+ h.sw.deliverStatisticsReply(m);
+ }
+
+ @Override
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws IOException, SwitchStateException {
+ Role role = extractNiciraRoleReply(h, m);
+ if (role == null) {
+ // The message wasn't really a Nicira role reply. We just
+ // dispatch it to the OFMessage listeners in this case.
+ h.dispatchMessage(m);
+ return;
+ }
+
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(
+ new RoleReplyInfo(role, null, m.getXid()));
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ checkAndSetRoleTransition(h, role);
+ }
+ }
+
+ @Override
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException, IOException {
+ RoleReplyInfo rri = extractOFRoleReply(h, m);
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ checkAndSetRoleTransition(h, rri.getRole());
+ }
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ handlePortStatusMessage(h, m, true);
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFPacketIn(OFChannelHandler h, OFPacketIn m)
+ throws IOException {
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFFlowRemoved(OFChannelHandler h,
+ OFFlowRemoved m) throws IOException {
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m)
+ throws IOException {
+ h.dispatchMessage(m);
+ }
+
+ },
+
+ /**
+ * This controller is in EQUAL role for this switch. We enter this state
+ * after some /other/ controller instance wins mastership-role over this
+ * switch. The EQUAL role can be considered the same as the SLAVE role
+ * if this controller does NOT send commands or packets to the switch.
+ * This should always be true for OF1.0 switches. XXX S need to enforce.
+ *
+ * For OF1.3 switches, choosing this state as EQUAL instead of SLAVE,
+ * gives us the flexibility that if an app wants to send commands/packets
+ * to switches, it can, even thought it is running on a controller instance
+ * that is not in a MASTER role for this switch. Of course, it is the job
+ * of the app to ensure that commands/packets sent by this (EQUAL) controller
+ * instance does not clash/conflict with commands/packets sent by the MASTER
+ * controller for this switch. Neither the controller instances, nor the
+ * switch provides any kind of resolution mechanism should conflicts occur.
+ */
+ EQUAL(true) {
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException {
+ // role changer will ignore the error if it isn't for it
+ RoleRecvStatus rrstatus = h.roleChanger.deliverError(m);
+ if (rrstatus == RoleRecvStatus.OTHER_EXPECTATION) {
+ logError(h, m);
+ h.dispatchMessage(m);
+ }
+ }
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m) {
+ h.sw.deliverStatisticsReply(m);
+ }
+
+ @Override
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws IOException, SwitchStateException {
+ Role role = extractNiciraRoleReply(h, m);
+ // If role == null it means the message wasn't really a
+ // Nicira role reply. We ignore it in this state.
+ if (role != null) {
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(
+ new RoleReplyInfo(role, null, m.getXid()));
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ checkAndSetRoleTransition(h, role);
+ }
+ } else {
+ unhandledMessageReceived(h, m);
+ }
+ }
+
+ @Override
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException, IOException {
+ RoleReplyInfo rri = extractOFRoleReply(h, m);
+ RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
+ if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
+ checkAndSetRoleTransition(h, rri.getRole());
+ }
+ }
+
+ // XXX S needs more handlers for 1.3 switches in equal role
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ handlePortStatusMessage(h, m, true);
+ }
+
+ @Override
+ @LogMessageDoc(level = "WARN",
+ message = "Received PacketIn from switch {} while "
+ + "being slave. Reasserting slave role.",
+ explanation = "The switch has receive a PacketIn despite being "
+ + "in slave role indicating inconsistent controller roles",
+ recommendation = "This situation can occurs transiently during role"
+ + " changes. If, however, the condition persists or happens"
+ + " frequently this indicates a role inconsistency. "
+ + LogMessageDoc.CHECK_CONTROLLER)
+ void processOFPacketIn(OFChannelHandler h, OFPacketIn m) throws IOException {
+ // we don't expect packetIn while slave, reassert we are slave
+ h.counters.packetInWhileSwitchIsSlave.updateCounterNoFlush();
+ log.warn("Received PacketIn from switch {} while" +
+ "being slave. Reasserting slave role.", h.sw);
+ //h.controller.reassertRole(h, Role.SLAVE);
+ // XXX reassert in role changer
+ }
+ };
+
+ private final boolean handshakeComplete;
+ ChannelState(boolean handshakeComplete) {
+ this.handshakeComplete = handshakeComplete;
+ }
+
+ /**
+ * Is this a state in which the handshake has completed?
+ * @return true if the handshake is complete
+ */
+ public boolean isHandshakeComplete() {
+ return handshakeComplete;
+ }
+
+ /**
+ * Get a string specifying the switch connection, state, and
+ * message received. To be used as message for SwitchStateException
+ * or log messages
+ * @param h The channel handler (to get switch information_
+ * @param m The OFMessage that has just been received
+ * @param details A string giving more details about the exact nature
+ * of the problem.
+ * @return
+ */
+ // needs to be protected because enum members are actually subclasses
+ protected String getSwitchStateMessage(OFChannelHandler h,
+ OFMessage m,
+ String details) {
+ return String.format("Switch: [%s], State: [%s], received: [%s]"
+ + ", details: %s",
+ h.getSwitchInfoString(),
+ this.toString(),
+ m.getType().toString(),
+ details);
+ }
+
+ /**
+ * We have an OFMessage we didn't expect given the current state and
+ * we want to treat this as an error.
+ * We currently throw an exception that will terminate the connection
+ * However, we could be more forgiving
+ * @param h the channel handler that received the message
+ * @param m the message
+ * @throws SwitchStateException
+ * @throws SwitchStateExeption we always through the execption
+ */
+ // needs to be protected because enum members are acutally subclasses
+ protected void illegalMessageReceived(OFChannelHandler h, OFMessage m)
+ throws SwitchStateException {
+ String msg = getSwitchStateMessage(h, m,
+ "Switch should never send this message in the current state");
+ throw new SwitchStateException(msg);
+
+ }
+
+ /**
+ * We have an OFMessage we didn't expect given the current state and
+ * we want to ignore the message.
+ * @param h the channel handler the received the message
+ * @param m the message
+ */
+ protected void unhandledMessageReceived(OFChannelHandler h,
+ OFMessage m) {
+ h.counters.unhandledMessage.updateCounterNoFlush();
+ if (log.isDebugEnabled()) {
+ String msg = getSwitchStateMessage(h, m,
+ "Ignoring unexpected message");
+ log.debug(msg);
+ }
+ }
+
+ /**
+ * Log an OpenFlow error message from a switch.
+ * @param sw The switch that sent the error
+ * @param error The error message
+ */
+ @LogMessageDoc(level = "ERROR",
+ message = "Error {error type} {error code} from {switch} "
+ + "in state {state}",
+ explanation = "The switch responded with an unexpected error"
+ + "to an OpenFlow message from the controller",
+ recommendation = "This could indicate improper network operation. "
+ + "If the problem persists restarting the switch and "
+ + "controller may help."
+ )
+ protected void logError(OFChannelHandler h, OFErrorMsg error) {
+ log.error("{} from switch {} in state {}",
+ new Object[] {
+ error,
+ h.getSwitchInfoString(),
+ this.toString()});
+ }
+
+ /**
+ * Log an OpenFlow error message from a switch and disconnect the
+ * channel.
+ *
+ * @param h the IO channel for this switch.
+ * @param error The error message
+ */
+ protected void logErrorDisconnect(OFChannelHandler h, OFErrorMsg error) {
+ logError(h, error);
+ h.channel.disconnect();
+ }
+
+ /**
+ * log an error message for a duplicate dpid and disconnect this channel.
+ * @param h the IO channel for this switch.
+ */
+ protected void disconnectDuplicate(OFChannelHandler h) {
+ log.error("Duplicated dpid or incompleted cleanup - "
+ + "disconnecting channel {}", h.getSwitchInfoString());
+ h.duplicateDpidFound = Boolean.TRUE;
+ h.channel.disconnect();
+ }
+
+ /**
+ * Extract the role from an OFVendor message.
+ *
+ * Extract the role from an OFVendor message if the message is a
+ * Nicira role reply. Otherwise return null.
+ *
+ * @param h The channel handler receiving the message
+ * @param vendorMessage The vendor message to parse.
+ * @return The role in the message if the message is a Nicira role
+ * reply, null otherwise.
+ * @throws SwitchStateException If the message is a Nicira role reply
+ * but the numeric role value is unknown.
+ */
+ protected Role extractNiciraRoleReply(OFChannelHandler h,
+ OFExperimenter experimenterMsg) throws SwitchStateException {
+ int vendor = (int) experimenterMsg.getExperimenter();
+ if (vendor != 0x2320) {
+ return null;
+ }
+ OFNiciraControllerRoleReply nrr =
+ (OFNiciraControllerRoleReply) experimenterMsg;
+
+ Role role = null;
+ OFNiciraControllerRole ncr = nrr.getRole();
+ switch(ncr) {
+ case ROLE_MASTER:
+ role = Role.MASTER;
+ break;
+ case ROLE_OTHER:
+ role = Role.EQUAL;
+ break;
+ case ROLE_SLAVE:
+ role = Role.SLAVE;
+ break;
+ default: //handled below
+ }
+
+ if (role == null) {
+ String msg = String.format("Switch: [%s], State: [%s], "
+ + "received NX_ROLE_REPLY with invalid role "
+ + "value %s",
+ h.getSwitchInfoString(),
+ this.toString(),
+ nrr.getRole());
+ throw new SwitchStateException(msg);
+ }
+ return role;
+ }
+
+ /**
+ * Helper class returns role reply information in the format understood
+ * by the controller.
+ */
+ protected static class RoleReplyInfo {
+ private Role role;
+ private U64 genId;
+ private long xid;
+
+ RoleReplyInfo(Role role, U64 genId, long xid) {
+ this.role = role;
+ this.genId = genId;
+ this.xid = xid;
+ }
+ public Role getRole() { return role; }
+ public U64 getGenId() { return genId; }
+ public long getXid() { return xid; }
+ @Override
+ public String toString() {
+ return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]";
+ }
+ }
+
+ /**
+ * Extract the role information from an OF1.3 Role Reply Message.
+ * @param h
+ * @param rrmsg
+ * @return RoleReplyInfo object
+ * @throws SwitchStateException
+ */
+ protected RoleReplyInfo extractOFRoleReply(OFChannelHandler h,
+ OFRoleReply rrmsg) throws SwitchStateException {
+ OFControllerRole cr = rrmsg.getRole();
+ Role role = null;
+ switch(cr) {
+ case ROLE_EQUAL:
+ role = Role.EQUAL;
+ break;
+ case ROLE_MASTER:
+ role = Role.MASTER;
+ break;
+ case ROLE_SLAVE:
+ role = Role.SLAVE;
+ break;
+ case ROLE_NOCHANGE: // switch should send current role
+ default:
+ String msg = String.format("Unknown controller role %s "
+ + "received from switch %s", cr, h.sw);
+ throw new SwitchStateException(msg);
+ }
+
+ return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
+ }
+
+ /**
+ * Handles all pending port status messages before a switch is declared
+ * activated in MASTER or EQUAL role. Note that since this handling
+ * precedes the activation (and therefore notification to IOFSwitchListerners)
+ * the changes to ports will already be visible once the switch is
+ * activated. As a result, no notifications are sent out for these
+ * pending portStatus messages.
+ * @param h
+ * @throws SwitchStateException
+ */
+ protected void handlePendingPortStatusMessages(OFChannelHandler h) {
+ try {
+ handlePendingPortStatusMessages(h, 0);
+ } catch (SwitchStateException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ private void handlePendingPortStatusMessages(OFChannelHandler h, int index)
+ throws SwitchStateException {
+ if (h.sw == null) {
+ String msg = "State machine error: switch is null. Should never " +
+ "happen";
+ throw new SwitchStateException(msg);
+ }
+ ArrayList<OFPortStatus> temp = new ArrayList<OFPortStatus>();
+ for (OFPortStatus ps: h.pendingPortStatusMsg) {
+ temp.add(ps);
+ handlePortStatusMessage(h, ps, false);
+ }
+ temp.clear();
+ // expensive but ok - we don't expect too many port-status messages
+ // note that we cannot use clear(), because of the reasons below
+ h.pendingPortStatusMsg.removeAll(temp);
+ // the iterator above takes a snapshot of the list - so while we were
+ // dealing with the pending port-status messages, we could have received
+ // newer ones. Handle them recursively, but break the recursion after
+ // five steps to avoid an attack.
+ if (!h.pendingPortStatusMsg.isEmpty() && ++index < 5) {
+ handlePendingPortStatusMessages(h, index);
+ }
+ }
+
+ /**
+ * Handle a port status message.
+ *
+ * Handle a port status message by updating the port maps in the
+ * IOFSwitch instance and notifying Controller about the change so
+ * it can dispatch a switch update.
+ *
+ * @param h The OFChannelHhandler that received the message
+ * @param m The PortStatus message we received
+ * @param doNotify if true switch port changed events will be
+ * dispatched
+ * @throws SwitchStateException
+ *
+ */
+ protected void handlePortStatusMessage(OFChannelHandler h, OFPortStatus m,
+ boolean doNotify) throws SwitchStateException {
+ if (h.sw == null) {
+ String msg = getSwitchStateMessage(h, m,
+ "State machine error: switch is null. Should never " +
+ "happen");
+ throw new SwitchStateException(msg);
+ }
+
+ Collection<PortChangeEvent> changes = h.sw.processOFPortStatus(m);
+ if (doNotify) {
+ for (PortChangeEvent ev: changes) {
+ h.controller.notifyPortChanged(h.sw.getId(), ev.port, ev.type);
+ }
+ }
+ }
+
+ /**
+ * Checks if the role received (from the role-reply msg) is different
+ * from the existing role in the IOFSwitch object for this controller.
+ * If so, it transitions the controller to the new role. Note that
+ * the caller should have already verified that the role-reply msg
+ * received was in response to a role-request msg sent out by this
+ * controller after hearing from the registry service.
+ *
+ * @param h the ChannelHandler that received the message
+ * @param role the role in the recieved role reply message
+ */
+ protected void checkAndSetRoleTransition(OFChannelHandler h, Role role) {
+ // we received a role-reply in response to a role message
+ // sent after hearing from the registry service. It is
+ // possible that the role of this controller instance for
+ // this switch has changed:
+ // for 1.0 switch: from MASTER to SLAVE
+ // for 1.3 switch: from MASTER to EQUAL
+ if ((h.sw.getRole() == Role.MASTER && role == Role.SLAVE) ||
+ (h.sw.getRole() == Role.MASTER && role == Role.EQUAL)) {
+ // the mastership has changed
+ h.sw.setRole(role);
+ h.setState(EQUAL);
+ h.controller.transitionToEqualSwitch(h.sw.getId());
+ return;
+ }
+
+ // or for both 1.0 and 1.3 switches from EQUAL to MASTER.
+ // note that for 1.0, even though we mean SLAVE,
+ // internally we call the role EQUAL.
+ if (h.sw.getRole() == Role.EQUAL && role == Role.MASTER) {
+ // the mastership has changed
+ h.sw.setRole(role);
+ h.setState(MASTER);
+ h.controller.transitionToMasterSwitch(h.sw.getId());
+ return;
+ }
+ }
+
+ /**
+ * Process an OF message received on the channel and
+ * update state accordingly.
+ *
+ * The main "event" of the state machine. Process the received message,
+ * send follow up message if required and update state if required.
+ *
+ * Switches on the message type and calls more specific event handlers
+ * for each individual OF message type. If we receive a message that
+ * is supposed to be sent from a controller to a switch we throw
+ * a SwitchStateExeption.
+ *
+ * The more specific handlers can also throw SwitchStateExceptions
+ *
+ * @param h The OFChannelHandler that received the message
+ * @param m The message we received.
+ * @throws SwitchStateException
+ * @throws IOException
+ */
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException, SwitchStateException {
+ h.roleChanger.checkTimeout();
+ switch(m.getType()) {
+ case HELLO:
+ processOFHello(h, (OFHello) m);
+ break;
+ case BARRIER_REPLY:
+ processOFBarrierReply(h, (OFBarrierReply) m);
+ break;
+ case ECHO_REPLY:
+ processOFEchoReply(h, (OFEchoReply) m);
+ break;
+ case ECHO_REQUEST:
+ processOFEchoRequest(h, (OFEchoRequest) m);
+ break;
+ case ERROR:
+ processOFError(h, (OFErrorMsg) m);
+ break;
+ case FEATURES_REPLY:
+ processOFFeaturesReply(h, (OFFeaturesReply) m);
+ break;
+ case FLOW_REMOVED:
+ processOFFlowRemoved(h, (OFFlowRemoved) m);
+ break;
+ case GET_CONFIG_REPLY:
+ processOFGetConfigReply(h, (OFGetConfigReply) m);
+ break;
+ case PACKET_IN:
+ processOFPacketIn(h, (OFPacketIn) m);
+ break;
+ case PORT_STATUS:
+ processOFPortStatus(h, (OFPortStatus) m);
+ break;
+ case QUEUE_GET_CONFIG_REPLY:
+ processOFQueueGetConfigReply(h, (OFQueueGetConfigReply) m);
+ break;
+ case STATS_REPLY: // multipart_reply in 1.3
+ processOFStatisticsReply(h, (OFStatsReply) m);
+ break;
+ case EXPERIMENTER:
+ processOFExperimenter(h, (OFExperimenter) m);
+ break;
+ case ROLE_REPLY:
+ processOFRoleReply(h, (OFRoleReply) m);
+ break;
+ case GET_ASYNC_REPLY:
+ processOFGetAsyncReply(h, (OFAsyncGetReply) m);
+ break;
+
+ // The following messages are sent to switches. The controller
+ // should never receive them
+ case SET_CONFIG:
+ case GET_CONFIG_REQUEST:
+ case PACKET_OUT:
+ case PORT_MOD:
+ case QUEUE_GET_CONFIG_REQUEST:
+ case BARRIER_REQUEST:
+ case STATS_REQUEST: // multipart request in 1.3
+ case FEATURES_REQUEST:
+ case FLOW_MOD:
+ case GROUP_MOD:
+ case TABLE_MOD:
+ case GET_ASYNC_REQUEST:
+ case SET_ASYNC:
+ case METER_MOD:
+ default:
+ illegalMessageReceived(h, m);
+ break;
+ }
+ }
+
+ /*-----------------------------------------------------------------
+ * Default implementation for message handlers in any state.
+ *
+ * Individual states must override these if they want a behavior
+ * that differs from the default.
+ *
+ * In general, these handlers simply ignore the message and do
+ * nothing.
+ *
+ * There are some exceptions though, since some messages really
+ * are handled the same way in every state (e.g., ECHO_REQUST) or
+ * that are only valid in a single state (e.g., HELLO, GET_CONFIG_REPLY
+ -----------------------------------------------------------------*/
+
+ void processOFHello(OFChannelHandler h, OFHello m)
+ throws IOException, SwitchStateException {
+ // we only expect hello in the WAIT_HELLO state
+ illegalMessageReceived(h, m);
+ }
+
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m)
+ throws IOException {
+ // Silently ignore.
+ }
+
+ void processOFEchoRequest(OFChannelHandler h, OFEchoRequest m)
+ throws IOException {
+ if (h.ofVersion == null) {
+ log.error("No OF version set for {}. Not sending Echo REPLY",
+ h.channel.getRemoteAddress());
+ return;
+ }
+ OFFactory factory = (h.ofVersion == OFVersion.OF_13) ?
+ h.controller.getOFMessageFactory13() : h.controller.getOFMessageFactory10();
+ OFEchoReply reply = factory
+ .buildEchoReply()
+ .setXid(m.getXid())
+ .setData(m.getData())
+ .build();
+ h.channel.write(Collections.singletonList(reply));
+ }
+
+ void processOFEchoReply(OFChannelHandler h, OFEchoReply m)
+ throws IOException {
+ // Do nothing with EchoReplies !!
+ }
+
+ // no default implementation for OFError
+ // every state must override it
+ abstract void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException;
+
+
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFFlowRemoved(OFChannelHandler h, OFFlowRemoved m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
+ throws IOException, SwitchStateException {
+ // we only expect config replies in the WAIT_CONFIG_REPLY state
+ illegalMessageReceived(h, m);
+ }
+
+ void processOFPacketIn(OFChannelHandler h, OFPacketIn m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ // no default implementation. Every state needs to handle it.
+ abstract void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException;
+
+ void processOFQueueGetConfigReply(OFChannelHandler h,
+ OFQueueGetConfigReply m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws IOException, SwitchStateException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws IOException, SwitchStateException {
+ // TODO: it might make sense to parse the vendor message here
+ // into the known vendor messages we support and then call more
+ // specific event handlers
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException, IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFGetAsyncReply(OFChannelHandler h,
+ OFAsyncGetReply m) {
+ unhandledMessageReceived(h, m);
+ }
+
+ void handleUnsentRoleMessage(OFChannelHandler h, Role role,
+ RoleRecvStatus expectation) throws IOException {
+ // do nothing in most states
+ }
+ }
+
+
+
+ //*************************
+ // Channel handler methods
+ //*************************
+
+ @Override
+ @LogMessageDoc(message = "New switch connection from {ip address}",
+ explanation = "A new switch has connected from the "
+ + "specified IP address")
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ counters.switchConnected.updateCounterWithFlush();
+ channel = e.getChannel();
+ log.info("New switch connection from {}",
+ channel.getRemoteAddress());
+ sendHandshakeHelloMessage();
+ setState(ChannelState.WAIT_HELLO);
+ }
+
+ @Override
+ @LogMessageDoc(message = "Disconnected switch {switch information}",
+ explanation = "The specified switch has disconnected.")
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ log.info("Switch disconnected callback for sw:{}. Cleaning up ...",
+ getSwitchInfoString());
+ if (thisdpid != 0) {
+ if (!duplicateDpidFound) {
+ // if the disconnected switch (on this ChannelHandler)
+ // was not one with a duplicate-dpid, it is safe to remove all
+ // state for it at the controller. Notice that if the disconnected
+ // switch was a duplicate-dpid, calling the method below would clear
+ // all state for the original switch (with the same dpid),
+ // which we obviously don't want.
+ controller.removeConnectedSwitch(thisdpid);
+ } else {
+ // A duplicate was disconnected on this ChannelHandler,
+ // this is the same switch reconnecting, but the original state was
+ // not cleaned up - XXX check liveness of original ChannelHandler
+ duplicateDpidFound = Boolean.FALSE;
+ }
+ } else {
+ log.warn("no dpid in channelHandler registered for "
+ + "disconnected switch {}", getSwitchInfoString());
+ }
+ }
+
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(level = "ERROR",
+ message = "Disconnecting switch {switch} due to read timeout",
+ explanation = "The connected switch has failed to send any "
+ + "messages or respond to echo requests",
+ recommendation = LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level = "ERROR",
+ message = "Disconnecting switch {switch}: failed to "
+ + "complete handshake",
+ explanation = "The switch did not respond correctly "
+ + "to handshake messages",
+ recommendation = LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level = "ERROR",
+ message = "Disconnecting switch {switch} due to IO Error: {}",
+ explanation = "There was an error communicating with the switch",
+ recommendation = LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level = "ERROR",
+ message = "Disconnecting switch {switch} due to switch "
+ + "state error: {error}",
+ explanation = "The switch sent an unexpected message",
+ recommendation = LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level = "ERROR",
+ message = "Disconnecting switch {switch} due to "
+ + "message parse failure",
+ explanation = "Could not parse a message from the switch",
+ recommendation = LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level = "ERROR",
+ message = "Terminating controller due to storage exception",
+ explanation = Controller.ERROR_DATABASE,
+ recommendation = LogMessageDoc.CHECK_CONTROLLER),
+ @LogMessageDoc(level = "ERROR",
+ message = "Could not process message: queue full",
+ explanation = "OpenFlow messages are arriving faster than "
+ + "the controller can process them.",
+ recommendation = LogMessageDoc.CHECK_CONTROLLER),
+ @LogMessageDoc(level = "ERROR",
+ message = "Error while processing message "
+ + "from switch {switch} {cause}",
+ explanation = "An error occurred processing the switch message",
+ recommendation = LogMessageDoc.GENERIC_ACTION)
+ })
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+ throws Exception {
+ if (e.getCause() instanceof ReadTimeoutException) {
+ // switch timeout
+ log.error("Disconnecting switch {} due to read timeout",
+ getSwitchInfoString());
+ counters.switchDisconnectReadTimeout.updateCounterWithFlush();
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof HandshakeTimeoutException) {
+ log.error("Disconnecting switch {}: failed to complete handshake",
+ getSwitchInfoString());
+ counters.switchDisconnectHandshakeTimeout.updateCounterWithFlush();
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof ClosedChannelException) {
+ log.debug("Channel for sw {} already closed", getSwitchInfoString());
+ } else if (e.getCause() instanceof IOException) {
+ log.error("Disconnecting switch {} due to IO Error: {}",
+ getSwitchInfoString(), e.getCause().getMessage());
+ if (log.isDebugEnabled()) {
+ // still print stack trace if debug is enabled
+ log.debug("StackTrace for previous Exception: ", e.getCause());
+ }
+ counters.switchDisconnectIOError.updateCounterWithFlush();
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof SwitchStateException) {
+ log.error("Disconnecting switch {} due to switch state error: {}",
+ getSwitchInfoString(), e.getCause().getMessage());
+ if (log.isDebugEnabled()) {
+ // still print stack trace if debug is enabled
+ log.debug("StackTrace for previous Exception: ", e.getCause());
+ }
+ counters.switchDisconnectSwitchStateException.updateCounterWithFlush();
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof OFParseError) {
+ log.error("Disconnecting switch "
+ + getSwitchInfoString() +
+ " due to message parse failure",
+ e.getCause());
+ counters.switchDisconnectParseError.updateCounterWithFlush();
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof RejectedExecutionException) {
+ log.warn("Could not process message: queue full");
+ counters.rejectedExecutionException.updateCounterWithFlush();
+ } else {
+ log.error("Error while processing message from switch "
+ + getSwitchInfoString()
+ + "state " + this.state, e.getCause());
+ counters.switchDisconnectOtherException.updateCounterWithFlush();
+ ctx.getChannel().close();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getSwitchInfoString();
+ }
+
+ @Override
+ public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
+ throws Exception {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFMessage m = factory.buildEchoRequest().build();
+ log.info("Sending Echo Request on idle channel: {}",
+ e.getChannel().getPipeline().getLast().toString());
+ e.getChannel().write(Collections.singletonList(m));
+ // XXX S some problems here -- echo request has no transaction id, and
+ // echo reply is not correlated to the echo request.
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+ if (e.getMessage() instanceof List) {
+ @SuppressWarnings("unchecked")
+ List<OFMessage> msglist = (List<OFMessage>) e.getMessage();
+
+
+ for (OFMessage ofm : msglist) {
+ counters.messageReceived.updateCounterNoFlush();
+ // Do the actual packet processing
+ state.processOFMessage(this, ofm);
+ }
+ } else {
+ counters.messageReceived.updateCounterNoFlush();
+ state.processOFMessage(this, (OFMessage) e.getMessage());
+ }
+ }
+
+
+
+ //*************************
+ // Channel utility methods
+ //*************************
+
+ /**
+ * Is this a state in which the handshake has completed?
+ * @return true if the handshake is complete
+ */
+ public boolean isHandshakeComplete() {
+ return this.state.isHandshakeComplete();
+ }
+
+ private void dispatchMessage(OFMessage m) throws IOException {
+ sw.handleMessage(m);
+ }
+
+ /**
+ * Return a string describing this switch based on the already available
+ * information (DPID and/or remote socket).
+ * @return
+ */
+ private String getSwitchInfoString() {
+ if (sw != null) {
+ return sw.toString();
+ }
+ String channelString;
+ if (channel == null || channel.getRemoteAddress() == null) {
+ channelString = "?";
+ } else {
+ channelString = channel.getRemoteAddress().toString();
+ }
+ String dpidString;
+ if (featuresReply == null) {
+ dpidString = "?";
+ } else {
+ dpidString = featuresReply.getDatapathId().toString();
+ }
+ return String.format("[%s DPID[%s]]", channelString, dpidString);
+ }
+
+ /**
+ * Update the channels state. Only called from the state machine.
+ * TODO: enforce restricted state transitions
+ * @param state
+ */
+ private void setState(ChannelState state) {
+ this.state = state;
+ }
+
+ /**
+ * Send hello message to the switch using the handshake transactions ids.
+ * @throws IOException
+ */
+ private void sendHandshakeHelloMessage() throws IOException {
+ // The OF protocol requires us to start things off by sending the highest
+ // version of the protocol supported.
+
+ // bitmap represents OF1.0 (ofp_version=0x01) and OF1.3 (ofp_version=0x04)
+ // see Sec. 7.5.1 of the OF1.3.4 spec
+ U32 bitmap = U32.ofRaw(0x00000012);
+ OFHelloElem hem = factory13.buildHelloElemVersionbitmap()
+ .setBitmaps(Collections.singletonList(bitmap))
+ .build();
+ OFMessage.Builder mb = factory13.buildHello()
+ .setXid(this.handshakeTransactionIds--)
+ .setElements(Collections.singletonList(hem));
+ log.info("Sending OF_13 Hello to {}", channel.getRemoteAddress());
+ channel.write(Collections.singletonList(mb.build()));
+ }
+
+ /**
+ * Send featuresRequest msg to the switch using the handshake transactions ids.
+ * @throws IOException
+ */
+ private void sendHandshakeFeaturesRequestMessage() throws IOException {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFMessage m = factory.buildFeaturesRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(m));
+ }
+
+ private void setSwitchRole(Role role) {
+ sw.setRole(role);
+ }
+
+ /**
+ * Send the configuration requests to tell the switch we want full
+ * packets.
+ * @throws IOException
+ */
+ private void sendHandshakeSetConfig() throws IOException {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ //log.debug("Sending CONFIG_REQUEST to {}", channel.getRemoteAddress());
+ List<OFMessage> msglist = new ArrayList<OFMessage>(3);
+
+ // Ensure we receive the full packet via PacketIn
+ // FIXME: We don't set the reassembly flags.
+ OFSetConfig sc = factory
+ .buildSetConfig()
+ .setMissSendLen((short) 0xffff)
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(sc);
+
+ // Barrier
+ OFBarrierRequest br = factory
+ .buildBarrierRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(br);
+
+ // Verify (need barrier?)
+ OFGetConfigRequest gcr = factory
+ .buildGetConfigRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(gcr);
+ channel.write(msglist);
+ }
+
+ /**
+ * send a description state request.
+ * @throws IOException
+ */
+ private void sendHandshakeDescriptionStatsRequest() throws IOException {
+ // Get Description to set switch-specific flags
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFDescStatsRequest dreq = factory
+ .buildDescStatsRequest()
+ .setXid(handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(dreq));
+ }
+
+ private void sendHandshakeOFPortDescRequest() throws IOException {
+ // Get port description for 1.3 switch
+ OFPortDescStatsRequest preq = factory13
+ .buildPortDescStatsRequest()
+ .setXid(handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(preq));
+ }
+
+ ChannelState getStateForTesting() {
+ return state;
+ }
+
+ void useRoleChangerWithOtherTimeoutForTesting(long roleTimeoutMs) {
+ roleChanger = new RoleChanger(roleTimeoutMs);
+ }
+
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageDecoder.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageDecoder.java
new file mode 100644
index 0000000..ff6fbf5
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageDecoder.java
@@ -0,0 +1,56 @@
+/**
+ * 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.onrc.onos.of.ctl.internal;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFMessageReader;
+
+/**
+ * Decode an openflow message from a Channel, for use in a netty pipeline.
+ */
+public class OFMessageDecoder extends FrameDecoder {
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx, Channel channel,
+ ChannelBuffer buffer) throws Exception {
+ if (!channel.isConnected()) {
+ // In testing, I see decode being called AFTER decode last.
+ // This check avoids that from reading corrupted frames
+ return null;
+ }
+
+ // Note that a single call to decode results in reading a single
+ // OFMessage from the channel buffer, which is passed on to, and processed
+ // by, the controller (in OFChannelHandler).
+ // This is different from earlier behavior (with the original openflowj),
+ // where we parsed all the messages in the buffer, before passing on
+ // a list of the parsed messages to the controller.
+ // The performance *may or may not* not be as good as before.
+ OFMessageReader<OFMessage> reader = OFFactories.getGenericReader();
+ OFMessage message = reader.readFrom(buffer);
+
+ return message;
+ }
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageEncoder.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageEncoder.java
new file mode 100644
index 0000000..86933fc
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OFMessageEncoder.java
@@ -0,0 +1,59 @@
+/**
+ * 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.onrc.onos.of.ctl.internal;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+
+
+/**
+ * Encode an openflow message for output into a ChannelBuffer, for use in a
+ * netty pipeline.
+ */
+public class OFMessageEncoder extends OneToOneEncoder {
+
+ @Override
+ protected Object encode(ChannelHandlerContext ctx, Channel channel,
+ Object msg) throws Exception {
+ if (!(msg instanceof List)) {
+ return msg;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<OFMessage> msglist = (List<OFMessage>) msg;
+ /* XXX S can't get length of OFMessage in loxigen's openflowj??
+ int size = 0;
+ for (OFMessage ofm : msglist) {
+ size += ofm.getLengthU();
+ }*/
+
+ ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
+
+ for (OFMessage ofm : msglist) {
+ ofm.writeTo(buf);
+ }
+ return buf;
+ }
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OpenflowPipelineFactory.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OpenflowPipelineFactory.java
new file mode 100644
index 0000000..30f0287
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/OpenflowPipelineFactory.java
@@ -0,0 +1,78 @@
+/**
+* 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.onrc.onos.of.ctl.internal;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+import org.jboss.netty.handler.timeout.IdleStateHandler;
+import org.jboss.netty.handler.timeout.ReadTimeoutHandler;
+import org.jboss.netty.util.ExternalResourceReleasable;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.jboss.netty.util.Timer;
+
+/**
+ * Creates a ChannelPipeline for a server-side openflow channel.
+ */
+public class OpenflowPipelineFactory
+ implements ChannelPipelineFactory, ExternalResourceReleasable {
+
+ protected Controller controller;
+ protected ThreadPoolExecutor pipelineExecutor;
+ protected Timer timer;
+ protected IdleStateHandler idleHandler;
+ protected ReadTimeoutHandler readTimeoutHandler;
+
+ public OpenflowPipelineFactory(Controller controller,
+ ThreadPoolExecutor pipelineExecutor) {
+ super();
+ this.controller = controller;
+ this.pipelineExecutor = pipelineExecutor;
+ this.timer = new HashedWheelTimer();
+ this.idleHandler = new IdleStateHandler(timer, 20, 25, 0);
+ this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30);
+ }
+
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ OFChannelHandler handler = new OFChannelHandler(controller);
+
+ ChannelPipeline pipeline = Channels.pipeline();
+ pipeline.addLast("ofmessagedecoder", new OFMessageDecoder());
+ pipeline.addLast("ofmessageencoder", new OFMessageEncoder());
+ pipeline.addLast("idle", idleHandler);
+ pipeline.addLast("timeout", readTimeoutHandler);
+ // XXX S ONOS: was 15 increased it to fix Issue #296
+ pipeline.addLast("handshaketimeout",
+ new HandshakeTimeoutHandler(handler, timer, 60));
+ if (pipelineExecutor != null) {
+ pipeline.addLast("pipelineExecutor",
+ new ExecutionHandler(pipelineExecutor));
+ }
+ pipeline.addLast("handler", handler);
+ return pipeline;
+ }
+
+ @Override
+ public void releaseExternalResources() {
+ timer.stop();
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeAlreadyStarted.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeAlreadyStarted.java
new file mode 100644
index 0000000..92c673c
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeAlreadyStarted.java
@@ -0,0 +1,14 @@
+package net.onrc.onos.of.ctl.internal;
+
+/**
+ * Thrown when IOFSwitch.startDriverHandshake() is called more than once.
+ *
+ */
+public class SwitchDriverSubHandshakeAlreadyStarted extends
+ SwitchDriverSubHandshakeException {
+ private static final long serialVersionUID = -5491845708752443501L;
+
+ public SwitchDriverSubHandshakeAlreadyStarted() {
+ super();
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeCompleted.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeCompleted.java
new file mode 100644
index 0000000..1600854
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeCompleted.java
@@ -0,0 +1,19 @@
+package net.onrc.onos.of.ctl.internal;
+
+import org.projectfloodlight.openflow.protocol.OFMessage;
+
+
+/**
+ * Indicates that a message was passed to a switch driver's subhandshake
+ * handling code but the driver has already completed the sub-handshake.
+ *
+ */
+public class SwitchDriverSubHandshakeCompleted
+ extends SwitchDriverSubHandshakeException {
+ private static final long serialVersionUID = -8817822245846375995L;
+
+ public SwitchDriverSubHandshakeCompleted(OFMessage m) {
+ super("Sub-Handshake is already complete but received message "
+ + m.getType());
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeException.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeException.java
new file mode 100644
index 0000000..c7d68f3
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeException.java
@@ -0,0 +1,26 @@
+package net.onrc.onos.of.ctl.internal;
+
+/**
+ * Base class for exception thrown by switch driver sub-handshake processing.
+ *
+ */
+public class SwitchDriverSubHandshakeException extends RuntimeException {
+ private static final long serialVersionUID = -6257836781419604438L;
+
+ protected SwitchDriverSubHandshakeException() {
+ super();
+ }
+
+ protected SwitchDriverSubHandshakeException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+ protected SwitchDriverSubHandshakeException(String arg0) {
+ super(arg0);
+ }
+
+ protected SwitchDriverSubHandshakeException(Throwable arg0) {
+ super(arg0);
+ }
+
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeNotStarted.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeNotStarted.java
new file mode 100644
index 0000000..d568cc6
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeNotStarted.java
@@ -0,0 +1,15 @@
+package net.onrc.onos.of.ctl.internal;
+
+/**
+ * Thrown when a switch driver's sub-handshake has not been started but an
+ * operation requiring the sub-handshake has been attempted.
+ *
+ */
+public class SwitchDriverSubHandshakeNotStarted extends
+ SwitchDriverSubHandshakeException {
+ private static final long serialVersionUID = -5491845708752443501L;
+
+ public SwitchDriverSubHandshakeNotStarted() {
+ super();
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeStateException.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeStateException.java
new file mode 100644
index 0000000..6091a86
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchDriverSubHandshakeStateException.java
@@ -0,0 +1,15 @@
+package net.onrc.onos.of.ctl.internal;
+
+/**
+ * Thrown when a switch driver's sub-handshake state-machine receives an
+ * unexpected OFMessage and/or is in an invald state.
+ *
+ */
+public class SwitchDriverSubHandshakeStateException extends
+ SwitchDriverSubHandshakeException {
+ private static final long serialVersionUID = -8249926069195147051L;
+
+ public SwitchDriverSubHandshakeStateException(String msg) {
+ super(msg);
+ }
+}
diff --git a/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchStateException.java b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchStateException.java
new file mode 100644
index 0000000..e51b60d
--- /dev/null
+++ b/of-save/ctl/src/main/java/net/onrc/onos/of/ctl/internal/SwitchStateException.java
@@ -0,0 +1,50 @@
+/**
+ * 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.onrc.onos.of.ctl.internal;
+
+/**
+ * This exception indicates an error or unexpected message during
+ * message handling. E.g., if an OFMessage is received that is illegal or
+ * unexpected given the current handshake state.
+ *
+ * We don't allow wrapping other exception in a switch state exception. We
+ * only log the SwitchStateExceptions message so the causing exceptions
+ * stack trace is generally not available.
+ *
+ */
+public class SwitchStateException extends Exception {
+
+ private static final long serialVersionUID = 9153954512470002631L;
+
+ public SwitchStateException() {
+ super();
+ }
+
+ public SwitchStateException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+ public SwitchStateException(String arg0) {
+ super(arg0);
+ }
+
+ public SwitchStateException(Throwable arg0) {
+ super(arg0);
+ }
+
+}