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