Package net.floodlightcontroller.core.internal

Source Code of net.floodlightcontroller.core.internal.OFChannelHandler$RoleChanger

package net.floodlightcontroller.core.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.Iterator;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;

import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IFloodlightProviderService.Role;
import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
import net.floodlightcontroller.core.annotations.LogMessageDocs;
import net.floodlightcontroller.core.internal.Controller.Counters;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException;
import net.floodlightcontroller.storage.IResultSet;
import net.floodlightcontroller.storage.StorageException;
import net.floodlightcontroller.util.LoadMonitor;

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.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
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.openflow.protocol.OFBarrierReply;
import org.openflow.protocol.OFBarrierRequest;
import org.openflow.protocol.OFEchoReply;
import org.openflow.protocol.OFEchoRequest;
import org.openflow.protocol.OFError;
import org.openflow.protocol.OFFeaturesReply;
import org.openflow.protocol.OFFlowRemoved;
import org.openflow.protocol.OFGetConfigReply;
import org.openflow.protocol.OFGetConfigRequest;
import org.openflow.protocol.OFHello;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPortStatus;
import org.openflow.protocol.OFQueueGetConfigReply;
import org.openflow.protocol.OFSetConfig;
import org.openflow.protocol.OFStatisticsReply;
import org.openflow.protocol.OFStatisticsRequest;
import org.openflow.protocol.OFSwitchConfig;
import org.openflow.protocol.OFType;
import org.openflow.protocol.OFVendor;
import org.openflow.protocol.OFError.OFBadActionCode;
import org.openflow.protocol.OFError.OFBadRequestCode;
import org.openflow.protocol.OFError.OFErrorType;
import org.openflow.protocol.OFError.OFFlowModFailedCode;
import org.openflow.protocol.OFError.OFHelloFailedCode;
import org.openflow.protocol.OFError.OFPortModFailedCode;
import org.openflow.protocol.OFError.OFQueueOpFailedCode;
import org.openflow.protocol.factory.BasicFactory;
import org.openflow.protocol.factory.MessageParseException;
import org.openflow.protocol.statistics.OFDescriptionStatistics;
import org.openflow.protocol.statistics.OFStatistics;
import org.openflow.protocol.statistics.OFStatisticsType;
import org.openflow.util.HexString;
import org.openflow.vendor.nicira.OFNiciraVendorData;
import org.openflow.vendor.nicira.OFRoleReplyVendorData;
import org.openflow.vendor.nicira.OFRoleRequestVendorData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bigswitch.floodlight.vendor.OFBigSwitchVendorData;
import com.bigswitch.floodlight.vendor.OFBsnL2TableSetVendorData;



/**
* Channel handler deals with the switch connection and dispatches
* switch messages to the appropriate locations.
* @author readams
*/
class OFChannelHandler
    extends IdleStateAwareChannelHandler {

    private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class);

    private static final long DEFAULT_ROLE_TIMEOUT_MS = 10*1000; // 10 sec

    private final Controller controller;
    private final Counters counters;
    private IOFSwitch sw;
    private Channel channel;
    // State needs to be volatile because the HandshakeTimeoutHandler
    // needs to check if the handshake is complete
    private volatile ChannelState state;
    private RoleChanger roleChanger;
    private OFFeaturesReply featuresReply;

    private final ArrayList<OFPortStatus> pendingPortStatusMsg;

    /** transaction Ids to use during handshake. Since only one thread
     * calls into the OFChannelHandler we don't need atomic.
     * We will count down
     */
    private int handshakeTransactionIds = -1;



    /**
     * When we remove a pending role request and set the role on the switch
     * we use this enum to indicate how we arrived at the decision.
     * @author gregor
     */
    private enum RoleRecvStatus {
        /** We receveived a role reply message from the switch */
        RECEIVED_REPLY,
        /** The switch returned an error indicated that roles are not
         * supported*/
        UNSUPPORTED,
        /** The request timed out */
        NO_REPLY;
    }
    /**
     * 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.
     * @author gregor
     */
    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 transactiong 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;

        public RoleChanger(long roleTimeoutMs) {
            this.requestPending = false;
            this.roleSubmitTime = 0;
            this.pendingXid = -1;
            this.pendingRole = null;
            this.roleTimeoutMs = roleTimeoutMs;
        }

        /**
         * 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 {

            int xid = sw.getNextTransactionId();
            // Convert the role enum to the appropriate integer constant used
            // in the NX role request message
            int nxRole = role.toNxRole();

            // Construct the role request message
            OFVendor roleRequest = (OFVendor)BasicFactory.getInstance()
                    .getMessage(OFType.VENDOR);
            roleRequest.setXid(xid);
            roleRequest.setVendor(OFNiciraVendorData.NX_VENDOR_ID);
            OFRoleRequestVendorData roleRequestData = new OFRoleRequestVendorData();
            roleRequestData.setRole(nxRole);
            roleRequest.setVendorData(roleRequestData);
            roleRequest.setLengthU(OFVendor.MINIMUM_LENGTH +
                    roleRequestData.getLength());

            // Send it to the switch
            sw.write(Collections.<OFMessage>singletonList(roleRequest),
                     new FloodlightContext());

            return xid;
        }

        /**
         * Send a role request for the given role only if no other role
         * request is currently pending.
         * @param role The role to send to the switch.
         * @throws IOException
         */
        synchronized void sendRoleRequestIfNotPending(Role role)
                throws IOException {
            if (!requestPending)
                sendRoleRequest(role);
            else
                counters.roleNotResentBecauseRolePending.updateCounterWithFlush();
        }

        /**
         * Send a role request with the given role to the switch.
         *
         * Send a role request with the given role to the switch and update
         * the pending request and timestamp.
         *
         * @param role
         * @throws IOException
         */
        synchronized void sendRoleRequest(Role role) throws IOException {
            /*
             * There are three cases to consider for SUPPORTS_NX_ROLE:
             *
             * 1) unset. We have neither received a role reply from the
             *    switch nor has a request timed out. Send a request.
             * 2) TRUE: We've already send a request earlier and received
             *    a reply. The switch supports role and we should send one.
             * 3) FALSE: We have already send a role and received an error.
             *    The switch does not support roles. Don't send a role request,
             *    set the switch's role directly.
             */
            Boolean supportsNxRole = (Boolean)
                    sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE);
            if ((supportsNxRole != null) && !supportsNxRole) {
                setSwitchRole(role, RoleRecvStatus.UNSUPPORTED);
            } else {
                pendingXid = sendNxRoleRequest(role);
                pendingRole = role;
                roleSubmitTime = System.currentTimeMillis();
                requestPending = true;
            }
        }

        /**
         * Deliver a received role reply and set SWITCH_SUPPORTS_NX_ROLE.
         *
         * 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.
         *
         * If no request is pending we disconnect.
         *
         * @param xid
         * @param role
         * @throws SwitchStateException if no request is pending
         */
        synchronized void deliverRoleReply(int xid, Role role) {
            if (!requestPending) {
                // Maybe don't disconnect if the role reply we received is
                // for the same role we are already in.
                String msg = String.format("Switch: [%s], State: [%s], "
                                + "received unexpected RoleReply[%s]. "
                                + "No roles are pending",
                                OFChannelHandler.this.getSwitchInfoString(),
                                OFChannelHandler.this.state.toString(),
                                role);
                throw new SwitchStateException(msg);
            }

            if (pendingXid == xid && pendingRole == role) {
                log.debug("Received role reply message from {}, setting role to {}",
                          getSwitchInfoString(), role);
                counters.roleReplyReceived.updateCounterWithFlush();
                setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY);
            } else {
                log.debug("Received stale or unexpected role reply from " +
                          "switch {} ({}, xid={}). Ignoring. " +
                          "Waiting for {}, xid={}",
                          new Object[] { getSwitchInfoString(), role, xid,
                                         pendingRole, pendingXid });
            }
        }

        /**
         * Called if we receive an  error message. If the xid matches the
         * pending request we handle it otherwise we ignore it. We also
         * set SWITCH_SUPPORTS_NX_ROLE to false.
         *
         * 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
         * @param xid
         * @return true if the error was handled by us, false otherwise
         * @throws SwitchStateException if the error was for the pending
         * role request but was unexpected
         */
        synchronized boolean deliverError(OFError error) {
            if (!requestPending)
                return false;

            if (pendingXid == error.getXid()) {
                boolean isBadRequestError =
                        (error.getErrorType() == OFError.OFErrorType.
                        OFPET_BAD_REQUEST.getValue());
                if (isBadRequestError) {
                    counters.roleReplyErrorUnsupported.updateCounterWithFlush();
                    setSwitchRole(pendingRole, RoleRecvStatus.UNSUPPORTED);
                } else {
                    // TODO: Is this the right thing to do if we receive
                    // some other error besides a bad request error?
                    // Presumably that means the switch did actually
                    // understand the role request message, but there
                    // was some other error from processing the message.
                    // OF 1.2 specifies a OFPET_ROLE_REQUEST_FAILED
                    // error code, but it doesn't look like the Nicira
                    // role request has that. Should check OVS source
                    // code to see if it's possible for any other errors
                    // to be returned.
                    // If we received an error the switch is not
                    // in the correct role, so we need to disconnect it.
                    // We could also resend the request but then we need to
                    // check if there are other pending request in which
                    // case we shouldn't resend. If we do resend we need
                    // to make sure that the switch eventually accepts one
                    // of our requests or disconnect the switch. This feels
                    // cumbersome.
                    String msg = String.format("Switch: [%s], State: [%s], "
                                    + "Unexpected error %s in respone to our "
                                    + "role request for %s.",
                                    OFChannelHandler.this.getSwitchInfoString(),
                                    OFChannelHandler.this.state.toString(),
                                    getErrorString(error),
                                    pendingRole);
                    throw new SwitchStateException(msg);
                }
                return true;
            }
            return false;
        }

        /**
         * 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);
                }
            }
        }

        /**
         * Set the role for this switch / channel.
         *
         * If the status indicates that we received a reply we set the role.
         * If the status indicates otherwise we disconnect the switch if
         * the role is SLAVE.
         *
         * "Setting a role" means setting the appropriate ChannelState,
         * setting the flags on the switch and
         * notifying Controller.java about new role of the switch
         *
         * @param role The role to set.
         * @param status How we derived at the decision to set this status.
         */
        synchronized private void setSwitchRole(Role role, RoleRecvStatus status) {
            requestPending = false;
            if (status == RoleRecvStatus.RECEIVED_REPLY)
                sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
            else
                sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false);
            sw.setHARole(role);

            if (role != Role.SLAVE) {
                OFChannelHandler.this.setState(ChannelState.MASTER);
                // TODO: should we really activate the switch again if it's
                // already master??
                if (log.isDebugEnabled()) {
                    log.debug("Switch {} activated. Role is now MASTER",
                              getSwitchInfoString());
                }
                controller.switchActivated(OFChannelHandler.this.sw);
            } else {
                OFChannelHandler.this.setState(ChannelState.SLAVE);
                if (status != RoleRecvStatus.RECEIVED_REPLY) {
                    if (log.isDebugEnabled()) {
                        log.debug("Disconnecting switch {}. Doesn't support role"
                              + "({}) request and controller is now SLAVE",
                              getSwitchInfoString(), status);
                    }
                    // the disconnect will trigger a switch removed to
                    // controller so no need to signal anything else
                    sw.disconnectOutputStream();
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Switch {} is now SLAVE",
                                  getSwitchInfoString());
                    }
                    controller.switchDeactivated(OFChannelHandler.this.sw);
                }
            }
        }
    }


    /**
     * The state machine for handling the switch/channel state.
     * @author gregor
     */
    enum ChannelState {
        /**
         * Initial state before channel is connected.
         */
        INIT(false) {
            @Override
            void
            processOFMessage(OFChannelHandler h, OFMessage m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFError(OFChannelHandler h, OFError 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 HELLO to the switch and wait for a reply.
         * Once we receive the reply we send an OFFeaturesRequest and
         * a request to clear all FlowMods.
         * Next state is WAIT_FEATURES_REPLY
         */
        WAIT_HELLO(false) {
            @Override
            void processOFHello(OFChannelHandler h, OFHello m)
                    throws IOException {
                h.sendHandShakeMessage(OFType.FEATURES_REQUEST);
                h.setState(WAIT_FEATURES_REPLY);
            }
            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }
            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply  m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }
            @Override
            void processOFError(OFChannelHandler h, OFError 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
         * we send a SetConfig request, barrier, and GetConfig request.
         * Next stats is WAIT_CONFIG_REPLY or WAIT_SET_L2_TABLE_REPLY
         */
        WAIT_FEATURES_REPLY(false) {
            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                h.featuresReply = m;
                if (m.getTables() > 1) {
                    log.debug("Have {} table for switch {}", m.getTables(),
                              h.getSwitchInfoString());
                    // likely supports L2 table extensions. Send set
                    h.sendHandshakeL2TableSet();
                    // TODO: no L2 SET reply yet, so fire and forget the set
                    // table message and move directly to sendHandshakeConfig
                    h.sendHandshakeSetConfig();
                    h.setState(WAIT_CONFIG_REPLY);
                    //h.setState(WAIT_SET_L2_TABLE_REPLY);
                } else {
                    h.sendHandshakeSetConfig();
                    h.setState(WAIT_CONFIG_REPLY);
                }
            }
            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply  m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }
            @Override
            void processOFError(OFChannelHandler h, OFError m) {
                logErrorDisconnect(h, m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                unhandledMessageReceived(h, m);
            }
        },

        WAIT_SET_L2_TABLE_REPLY(false) {
            @Override void processOFVendor(OFChannelHandler h, OFVendor m)
                    throws IOException {
                // TODO: actually parse the response
                h.sendHandshakeSetConfig();
                h.setState(WAIT_CONFIG_REPLY);
            };

            @Override
            void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
                // do nothing;
            }

            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                // TODO: we could re-set the features reply
                illegalMessageReceived(h, m);
            }
            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply  m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFError(OFChannelHandler h, OFError m) {
                logErrorDisconnect(h, m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                h.pendingPortStatusMsg.add(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.getMissSendLength() == (short)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?
                    // FIXME: count??
                    log.warn("Config Reply from switch {} has"
                            + "miss length set to {}",
                            h.getSwitchInfoString(),
                            m.getMissSendLength());
                }
                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 {
                // TODO: we could re-set the features reply
                illegalMessageReceived(h, m);
            }
            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply  m)
                    throws IOException {
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFError(OFChannelHandler h, OFError m) {
                if (m.getErrorType() == OFErrorType.OFPET_BAD_REQUEST.getValue()
                        && m.getErrorCode() ==
                            OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal()) {
                    log.debug("Switch {} has multiple tables but does not " +
                            "support L2 table extension",
                            h.getSwitchInfoString());
                    return;
                }
                logErrorDisconnect(h, m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                h.pendingPortStatusMsg.add(m);
            }
        },

        /**
         * We are waiting for a OFDescriptionStat message from teh 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 switch instance
         *    - setup the IOFSwitch instance
         *    - add switch to FloodlightProvider and send the intial role
         *      request to the switch.
         * Next state: WAIT_INITIAL_ROLE
         * 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,
                                          OFStatisticsReply m) {
                // Read description, if it has been updated
                OFDescriptionStatistics description =
                        new OFDescriptionStatistics();
                ChannelBuffer data =
                        ChannelBuffers.buffer(description.getLength());
                OFStatistics f = m.getFirstStatistics();
                f.writeTo(data);
                description.readFrom(data);
                h.sw = h.controller.getOFSwitchInstance(description);
                // set switch information
                // set features reply and channel first so we a DPID and
                // channel info.
                h.sw.setFeaturesReply(h.featuresReply);
                h.sw.setConnected(true);
                h.sw.setChannel(h.channel);
                h.sw.setFloodlightProvider(h.controller);
                h.sw.setThreadPoolService(h.controller.getThreadPoolService());
                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);
                }
                h.sw.setAccessFlowPriority(h.controller.getAccessFlowPriority());
                h.sw.setCoreFlowPriority(h.controller.getCoreFlowPriority());
                for (OFPortStatus ps: h.pendingPortStatusMsg)
                    handlePortStatusMessage(h, ps, false);
                h.pendingPortStatusMsg.clear();
                h.readPropertyFromStorage();
                log.info("Switch {} bound to class {}, writeThrottle={}," +
                        " description {}",
                         new Object[] { h.sw, h.sw.getClass(),
                                        h.sw.isWriteThrottleEnabled(),
                                    description });
                h.sw.startDriverHandshake();
                if (h.sw.isDriverHandshakeComplete())
                    h.gotoWaitInitialRoleState();
                else
                    h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE);
            }

            @Override
            void processOFError(OFChannelHandler h, OFError m) {
                logErrorDisconnect(h, m);
            }

            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                // TODO: we could re-set the features reply
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                h.pendingPortStatusMsg.add(m);
            }
        },

        WAIT_SWITCH_DRIVER_SUB_HANDSHAKE(false) {
            @Override
            void processOFError(OFChannelHandler h, OFError 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()) {
                        h.gotoWaitInitialRoleState();
                    }
                }
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                handlePortStatusMessage(h, m, false);
            }
        },

        /**
         * We are waiting for the intial role reply message (or error
         * indication) from the switch.
         * Next State: MASTER or SLAVE
         */
        WAIT_INITIAL_ROLE(false) {
            @Override
            void processOFError(OFChannelHandler h, OFError m) {
                // role changer will ignore the error if it isn't for it
                boolean didHandle = h.roleChanger.deliverError(m);
                if (!didHandle) {
                    logError(h, m);
                }
            }

            @Override
            void processOFVendor(OFChannelHandler h, OFVendor m)
                    throws IOException {
                Role role = extractNiciraRoleReply(h, m);
                // If role == null it measn the message wasn't really a
                // Nicira role reply. We ignore this case.
                if (role != null)
                    h.roleChanger.deliverRoleReply(m.getXid(), role);
                else
                    unhandledMessageReceived(h, m);
            }

            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                // TODO: we could re-set the features reply
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply m) {
                illegalMessageReceived(h, m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                handlePortStatusMessage(h, m, false);

            }
        },

        /**
         * The switch is in MASTER role. We enter this state after a role
         * reply from the switch is received (or the controller is MASTER
         * and the switch doesn't support roles). The handshake is complete at
         * this point. 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, OFError m)
                    throws IOException {
                // role changer will ignore the error if it isn't for it
                boolean didHandle = h.roleChanger.deliverError(m);
                if (didHandle)
                    return;
                if (m.getErrorType() ==
                        OFErrorType.OFPET_BAD_REQUEST.getValue() &&
                   m.getErrorCode() ==
                        OFBadRequestCode.OFPBRC_EPERM.ordinal()) {
                    // 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);
                }
                else if (m.getErrorType() ==
                        OFErrorType.OFPET_PORT_MOD_FAILED.getValue() &&
                    m.getErrorCode() ==
                        OFFlowModFailedCode.OFPFMFC_ALL_TABLES_FULL.ordinal()) {
                    h.sw.setTableFull(true);
                }
                else {
                    logError(h, m);
                }
                h.dispatchMessage(m);
            }

            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply m) {
                h.sw.deliverStatisticsReply(m);
            }

            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                h.sw.setFeaturesReply(m);
                h.sw.deliverOFFeaturesReply(m);
            }

            @Override
            void processOFVendor(OFChannelHandler h, OFVendor m)
                    throws IOException {
                Role role = extractNiciraRoleReply(h, m);
                // If role == null it means the message wasn't really a
                // Nicira role reply. We ignore just dispatch it to the
                // OFMessage listenersa in this case.
                if (role != null)
                    h.roleChanger.deliverRoleReply(m.getXid(), role);
                else
                    h.dispatchMessage(m);
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                handlePortStatusMessage(h, m, true);
            }

            @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);
            }
        },

        /**
         * The switch is in SLAVE role. We enter this state after a role
         * reply from the switch is received. The handshake is complete at
         * this point. We only leave this state if the switch disconnects or
         * if we send a role request for MASTER /and/ receive the role reply for
         * MASTER.
         * TODO: CURRENTLY, WE DO NOT DISPATCH ANY MESSAGE IN SLAVE.
         */
        SLAVE(true) {
            @Override
            void processOFError(OFChannelHandler h, OFError m)
                    throws IOException {
                // role changer will ignore the error if it isn't for it
                boolean didHandle = h.roleChanger.deliverError(m);
                if (!didHandle) {
                    logError(h, m);
                }
            }



            @Override
            void processOFStatisticsReply(OFChannelHandler h,
                                          OFStatisticsReply m) {
                // FIXME.
                h.sw.deliverStatisticsReply(m);
            }

            @Override
            void processOFVendor(OFChannelHandler h, OFVendor m)
                    throws IOException {
                Role role = extractNiciraRoleReply(h, m);
                // If role == null it means the message wasn't really a
                // Nicira role reply. We ignore it.
                if (role != null)
                    h.roleChanger.deliverRoleReply(m.getXid(), role);
                else
                    unhandledMessageReceived(h, m);
            }

            @Override
            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                    throws IOException {
                // do nothing
            }

            @Override
            void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                    throws IOException {
                // do nothing
            }

            @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);
            }
        };

        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 acutally 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 SwitchStateExeption we always through the execption
         */
        // needs to be protected because enum members are acutally subclasses
        protected void illegalMessageReceived(OFChannelHandler h, OFMessage m) {
            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, OFError error) {
            log.error("{} from switch {} in state {}",
                      new Object[] {
                          getErrorString(error),
                          h.getSwitchInfoString(),
                          this.toString()});
        }

        /**
         * Log an OpenFlow error message from a switch and disconnect the
         * channel
         * @param sw The switch that sent the error
         * @param error The error message
         */
        protected void logErrorDisconnect(OFChannelHandler h, OFError error) {
            logError(h, error);
            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.
         * FIXME: The message parser should make sure that the Nicira role is
         * actually valid. Why do we need to take care of it ?!?
         */
        protected Role extractNiciraRoleReply(OFChannelHandler h,
                                              OFVendor vendorMessage) {
            int vendor = vendorMessage.getVendor();
            if (vendor != OFNiciraVendorData.NX_VENDOR_ID)
                return null;
            if (! (vendorMessage.getVendorData() instanceof OFRoleReplyVendorData))
                return null;
            OFRoleReplyVendorData roleReplyVendorData =
                    (OFRoleReplyVendorData) vendorMessage.getVendorData();
            Role role = Role.fromNxRole(roleReplyVendorData.getRole());
            if (role == null) {
                String msg = String.format("Switch: [%s], State: [%s], "
                        + "received NX_ROLE_REPLY with invalid role "
                        + "value %d",
                        h.getSwitchInfoString(),
                        this.toString(),
                        roleReplyVendorData.getRole());
                throw new SwitchStateException(msg);
            }
            return role;
        }

        /**
         * 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
         */
        protected void handlePortStatusMessage(OFChannelHandler h,
                                               OFPortStatus m,
                                               boolean doNotify) {
            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, ev.port, ev.type);
            }
        }

        /**
         * 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 {
            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, (OFError)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:
                    processOFStatisticsReply(h, (OFStatisticsReply)m);
                    break;
                case VENDOR:
                    processOFVendor(h, (OFVendor)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:
                case FEATURES_REQUEST:
                case FLOW_MOD:
                    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 {
            // 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 {
            OFEchoReply reply = (OFEchoReply)
                    BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY);
            reply.setXid(m.getXid());
            reply.setPayload(m.getPayload());
            reply.setLengthU(m.getLengthU());
            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, OFError m)
                throws IOException;


        void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                throws IOException {
            unhandledMessageReceived(h, m);
        }

        void processOFFlowRemoved(OFChannelHandler h, OFFlowRemoved m)
            throws IOException {
            unhandledMessageReceived(h, m);
        }

        void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
                throws IOException {
            // we only expect config replies in the WAIT_CONFIG_REPLY state
            // TODO: might use two different strategies depending on whether
            // we got a miss length of 64k or not.
            illegalMessageReceived(h, m);
        }

        void processOFPacketIn(OFChannelHandler h, OFPacketIn m)
                throws IOException {
            unhandledMessageReceived(h, m);
        }

        // bi default implementation. Every state needs to handle it.
        abstract void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
                throws IOException;

        void processOFQueueGetConfigReply(OFChannelHandler h,
                                          OFQueueGetConfigReply m)
                throws IOException {
            unhandledMessageReceived(h, m);
        }

        void processOFStatisticsReply(OFChannelHandler h, OFStatisticsReply m)
                throws IOException {
            unhandledMessageReceived(h, m);
        }

        void processOFVendor(OFChannelHandler h, OFVendor m)
                throws IOException {
            // TODO: it might make sense to parse the vendor message here
            // into the known vendor messages we support and then call more
            // spefic event handlers
            unhandledMessageReceived(h, m);
        }
    }


    /**
     * Create a new unconnecte 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 ArrayList<OFPortStatus>();
    }

    /**
     * Is this a state in which the handshake has completed?
     * @return true if the handshake is complete
     */
    boolean isHandshakeComplete() {
        return this.state.isHandshakeComplete();
    }

    /**
     * Forwards to RoleChanger. See there.
     * @param role
     */
    void sendRoleRequestIfNotPending(Role role) {
        try {
            roleChanger.sendRoleRequestIfNotPending(role);
        } catch (IOException e) {
             log.error("Disconnecting switch {} due to IO Error: {}",
                              getSwitchInfoString(), e.getMessage());
             channel.close();
        }
    }

    /**
     * Forwards to RoleChanger. See there.
     * @param role
     */
    void sendRoleRequest(Role role) {
        try {
            roleChanger.sendRoleRequest(role);
        } catch (IOException e) {
             log.error("Disconnecting switch {} due to IO Error: {}",
                              getSwitchInfoString(), e.getMessage());
             channel.close();
        }
    }


    @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());
        sendHandShakeMessage(OFType.HELLO);
        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 {
        controller.removeSwitchChannel(this);
        if (this.sw != null) {
            // TODO: switchDisconnected() will check if we've previously
            // activated the switch. Nevertheless, we might want to check
            // here as well.
            controller.switchDisconnected(this.sw);
            this.sw.setConnected(false);
        }

        log.info("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 MessageParseException) {
            log.error("Disconnecting switch "
                                 + getSwitchInfoString() +
                                 " due to message parse failure",
                                 e.getCause());
            counters.switchDisconnectParseError.updateCounterWithFlush();
            ctx.getChannel().close();
        } else if (e.getCause() instanceof StorageException) {
            log.error("Terminating controller due to storage exception",
                      e.getCause());
            this.controller.terminate();
        } 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 void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
            throws Exception {
        OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REQUEST);
        e.getChannel().write(Collections.singletonList(m));
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {
        if (e.getMessage() instanceof List) {
            @SuppressWarnings("unchecked")
            List<OFMessage> msglist = (List<OFMessage>)e.getMessage();

            LoadMonitor.LoadLevel loadlevel;
            int packets_dropped = 0;
            int packets_allowed = 0;
            int lldps_allowed = 0;

            if (this.controller.overload_drop) {
                loadlevel = this.controller.loadmonitor.getLoadLevel();
            }
            else {
                loadlevel = LoadMonitor.LoadLevel.OK;
            }

            for (OFMessage ofm : msglist) {
                counters.messageReceived.updateCounterNoFlush();
                // Per-switch input throttling
                if (sw != null && sw.inputThrottled(ofm)) {
                    counters.messageInputThrottled.updateCounterNoFlush();
                    continue;
                }
                try {
                    if (this.controller.overload_drop &&
                        !loadlevel.equals(LoadMonitor.LoadLevel.OK)) {
                        switch (ofm.getType()) {
                        case PACKET_IN:
                            switch (loadlevel) {
                            case VERYHIGH:
                                // Drop all packet-ins, including LLDP/BDDPs
                                packets_dropped++;
                                continue;
                            case HIGH:
                                // Drop all packet-ins, except LLDP/BDDPs
                                byte[] data = ((OFPacketIn)ofm).getPacketData();
                                if (data.length > 14) {
                                    if (((data[12] == (byte)0x88) &&
                                         (data[13] == (byte)0xcc)) ||
                                        ((data[12] == (byte)0x89) &&
                                         (data[13] == (byte)0x42))) {
                                        lldps_allowed++;
                                        packets_allowed++;
                                        break;
                                    }
                                }
                                packets_dropped++;
                                continue;
                            default:
                                // Load not high, go ahead and process msg
                                packets_allowed++;
                                break;
                            }
                            break;
                        default:
                            // Process all non-packet-ins
                            packets_allowed++;
                            break;
                        }
                    }

                    // Do the actual packet processing
                    state.processOFMessage(this, ofm);

                }
                catch (Exception ex) {
                    // We are the last handler in the stream, so run the
                    // exception through the channel again by passing in
                    // ctx.getChannel().
                    Channels.fireExceptionCaught(ctx.getChannel(), ex);
                }
            }

            if (loadlevel != LoadMonitor.LoadLevel.OK) {
                if (log.isDebugEnabled()) {
                    log.debug(
                        "Overload: Detected {}, packets dropped={}",
                        loadlevel.toString(), packets_dropped);
                    log.debug(
                        "Overload: Packets allowed={} (LLDP/BDDPs allowed={})",
                        packets_allowed, lldps_allowed);
                }
            }
            // Flush all thread local queues etc. generated by this train
            // of messages.
            this.controller.flushAll();
        }
        else {
            Channels.fireExceptionCaught(ctx.getChannel(),
                                         new AssertionError("Message received from Channel is not a list"));
        }
    }


    /**
     * Get a useable error string from the OFError.
     * @param error
     * @return
     */
    public static String getErrorString(OFError error) {
        // TODO: this really should be OFError.toString. Sigh.
        int etint = 0xffff & error.getErrorType();
        if (etint < 0 || etint >= OFErrorType.values().length) {
            return String.format("Unknown error type %d", etint);
        }
        OFErrorType et = OFErrorType.values()[etint];
        switch (et) {
            case OFPET_HELLO_FAILED:
                OFHelloFailedCode hfc =
                    OFHelloFailedCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, hfc);
            case OFPET_BAD_REQUEST:
                OFBadRequestCode brc =
                    OFBadRequestCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, brc);
            case OFPET_BAD_ACTION:
                OFBadActionCode bac =
                    OFBadActionCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, bac);
            case OFPET_FLOW_MOD_FAILED:
                OFFlowModFailedCode fmfc =
                    OFFlowModFailedCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, fmfc);
            case OFPET_PORT_MOD_FAILED:
                OFPortModFailedCode pmfc =
                    OFPortModFailedCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, pmfc);
            case OFPET_QUEUE_OP_FAILED:
                OFQueueOpFailedCode qofc =
                    OFQueueOpFailedCode.values()[0xffff & error.getErrorCode()];
                return String.format("Error %s %s", et, qofc);
            case OFPET_VENDOR_ERROR:
                // no codes known for vendor error
                return String.format("Error %s", et);
        }
        return null;
    }

    private void dispatchMessage(OFMessage m) throws IOException {
        // handleMessage will count
        this.controller.handleMessage(this.sw, m, null);
    }

    /**
     * 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 = HexString.toHexString(featuresReply.getDatapathId());
        }
        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 a message to the switch using the handshake transactions ids.
     * @throws IOException
     */
    private void sendHandShakeMessage(OFType type) throws IOException {
        // Send initial Features Request
        OFMessage m = BasicFactory.getInstance().getMessage(type);
        m.setXid(handshakeTransactionIds--);
        channel.write(Collections.singletonList(m));
    }

    /**
     * Send an setL2TableSet message to the switch.
     */
    private void sendHandshakeL2TableSet() {
        OFVendor l2TableSet = (OFVendor)
                BasicFactory.getInstance().getMessage(OFType.VENDOR);
        l2TableSet.setXid(handshakeTransactionIds--);
        OFBsnL2TableSetVendorData l2TableSetData =
                new OFBsnL2TableSetVendorData(true,
                                              controller.getCoreFlowPriority());
        l2TableSet.setVendor(OFBigSwitchVendorData.BSN_VENDOR_ID);
        l2TableSet.setVendorData(l2TableSetData);
        l2TableSet.setLengthU(OFVendor.MINIMUM_LENGTH +
                              l2TableSetData.getLength());
        channel.write(Collections.singletonList(l2TableSet));
    }


    private void gotoWaitInitialRoleState() {
        // We need to set the new state /before/ we call addSwitchChannel
        // because addSwitchChannel will eventually call setRole
        // which can in turn decide that the switch doesn't support
        // roles and transition the state straight to MASTER.
        setState(ChannelState.WAIT_INITIAL_ROLE);
        controller.addSwitchChannelAndSendInitialRole(this);
    }

    /**
     * Send the configuration requests to tell the switch we want full
     * packets
     * @throws IOException
     */
    private void sendHandshakeSetConfig() throws IOException {
        List<OFMessage> msglist = new ArrayList<OFMessage>(3);

        // Ensure we receive the full packet via PacketIn
        // FIXME: We don't set the reassembly flags.
        OFSetConfig configSet = (OFSetConfig) BasicFactory.getInstance()
                .getMessage(OFType.SET_CONFIG);
        configSet.setMissSendLength((short) 0xffff)
            .setLengthU(OFSwitchConfig.MINIMUM_LENGTH);
        configSet.setXid(handshakeTransactionIds--);
        msglist.add(configSet);

        // Barrier
        OFBarrierRequest barrier = (OFBarrierRequest) BasicFactory.getInstance()
                .getMessage(OFType.BARRIER_REQUEST);
        barrier.setXid(handshakeTransactionIds--);
        msglist.add(barrier);

        // Verify (need barrier?)
        OFGetConfigRequest configReq = (OFGetConfigRequest)
                BasicFactory.getInstance().getMessage(OFType.GET_CONFIG_REQUEST);
        configReq.setXid(handshakeTransactionIds--);
        msglist.add(configReq);
        channel.write(msglist);
    }

    /**
     * send a description state request
     * @throws IOException
     */
    private void sendHandshakeDescriptionStatsRequest() throws IOException {
        // Get Description to set switch-specific flags
        OFStatisticsRequest req = new OFStatisticsRequest();
        req.setStatisticType(OFStatisticsType.DESC);
        req.setXid(handshakeTransactionIds--);

        channel.write(Collections.singletonList(req));
    }


    /**
     * Read switch properties from storage and set switch attributes accordingly
     */
    private void readPropertyFromStorage() {
        // At this time, also set other switch properties from storage
        boolean is_core_switch = false;
        IResultSet resultSet = null;
        try {
            String swid = sw.getStringId();
            resultSet = this.controller.getStorageSourceService()
                    .getRow(Controller.SWITCH_CONFIG_TABLE_NAME, swid);
            for (Iterator<IResultSet> it =
                    resultSet.iterator(); it.hasNext();) {
                is_core_switch = it.next()
                        .getBoolean(Controller.SWITCH_CONFIG_CORE_SWITCH);
                if (log.isDebugEnabled()) {
                    log.debug("Reading SWITCH_IS_CORE_SWITCH " +
                            "config for switch={}, is-core={}",
                            sw, is_core_switch);
                }
            }
        }
        finally {
            if (resultSet != null)
                resultSet.close();
        }
        if (is_core_switch) {
            sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH,
                            Boolean.valueOf(true));
        }
    }

    ChannelState getStateForTesting() {
        return state;
    }

    void useRoleChangerWithOtherTimeoutForTesting(long roleTimeoutMs) {
        roleChanger = new RoleChanger(roleTimeoutMs);
    }
}
TOP

Related Classes of net.floodlightcontroller.core.internal.OFChannelHandler$RoleChanger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.