package net.floodlightcontroller.core.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import io.netty.util.Timer;
import net.floodlightcontroller.core.HARole;
import net.floodlightcontroller.core.IOFConnection;
import net.floodlightcontroller.core.IOFConnectionBackend;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitch.SwitchStatus;
import net.floodlightcontroller.core.IOFSwitchBackend;
import net.floodlightcontroller.core.PortChangeEvent;
import net.floodlightcontroller.core.SwitchDescription;
import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType;
import org.projectfloodlight.openflow.protocol.OFActionType;
import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
import org.projectfloodlight.openflow.protocol.OFBarrierReply;
import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
import org.projectfloodlight.openflow.protocol.OFBundleCtrlMsg;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFDescStatsRequest;
import org.projectfloodlight.openflow.protocol.OFErrorMsg;
import org.projectfloodlight.openflow.protocol.OFErrorType;
import org.projectfloodlight.openflow.protocol.OFExperimenter;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
import org.projectfloodlight.openflow.protocol.OFFlowAdd;
import org.projectfloodlight.openflow.protocol.OFFlowDelete;
import org.projectfloodlight.openflow.protocol.OFFlowDeleteStrict;
import org.projectfloodlight.openflow.protocol.OFFlowModFailedCode;
import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
import org.projectfloodlight.openflow.protocol.OFGetConfigRequest;
import org.projectfloodlight.openflow.protocol.OFGroupBucket;
import org.projectfloodlight.openflow.protocol.OFGroupDelete;
import org.projectfloodlight.openflow.protocol.OFGroupType;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply;
import org.projectfloodlight.openflow.protocol.OFRoleReply;
import org.projectfloodlight.openflow.protocol.OFRoleRequest;
import org.projectfloodlight.openflow.protocol.OFRoleStatus;
import org.projectfloodlight.openflow.protocol.OFSetConfig;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
import org.projectfloodlight.openflow.protocol.OFStatsRequestFlags;
import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsReply;
import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsRequest;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.actionid.OFActionId;
import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFAuxId;
import org.projectfloodlight.openflow.types.OFGroup;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
/**
* Switch handler deals with the switch connection and dispatches
* switch messages to the appropriate locations. These messages
* are typically received by the channel handler first and piped here.
*
* @author Jason Parraga <jason.parraga@bigswitch.com>
*/
public class OFSwitchHandshakeHandler implements IOFConnectionListener {
private static final Logger log = LoggerFactory.getLogger(OFSwitchHandshakeHandler.class);
private final IOFSwitchManager switchManager;
private final RoleManager roleManager;
private final IOFConnectionBackend mainConnection;
private final SwitchManagerCounters switchManagerCounters;
private IOFSwitchBackend sw;
private final Map<OFAuxId, IOFConnectionBackend> auxConnections;
private volatile OFSwitchHandshakeState state;
private RoleChanger roleChanger;
// Default to 1.4 - This is overwritten by the features reply
private OFFactory factory = OFFactories.getFactory(OFVersion.OF_14);
private final OFFeaturesReply featuresReply;
private final Timer timer;
private volatile OFControllerRole initialRole = null;
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 long handshakeTransactionIds = 0x00FFFFFFFFL;
/* Exponential backoff of master role assertion */
private final long MAX_ASSERT_TIME_INTERVAL_NS = TimeUnit.SECONDS.toNanos(120);
private final long DEFAULT_ROLE_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10);
protected OFPortDescStatsReply portDescStats;
/**
* 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 received 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 transaction Id of the pending request
private long pendingXid;
// the role that's pending
private OFControllerRole pendingRole;
// system time in NS when we send the request
private long roleSubmitTimeNs;
// the timeout to use
private final long roleTimeoutNs;
private long lastAssertTimeNs;
private long assertTimeIntervalNs = TimeUnit.SECONDS.toNanos(1);
public RoleChanger(long roleTimeoutNs) {
this.roleTimeoutNs = roleTimeoutNs;
// System.nanoTime() may be negative -- prime the roleSubmitTime as
// "long ago in the past" to be robust against it.
this.roleSubmitTimeNs = System.nanoTime() - (2 * roleTimeoutNs);
this.lastAssertTimeNs = System.nanoTime() - (2 * assertTimeIntervalNs);
this.requestPending = false;
this.pendingXid = -1;
this.pendingRole = null;
}
/**
* Send Nicira role request message to the switch requesting the
* specified role.
*
* @param role, role to request
* @param xid, if greater than 0, the XID to use in the request
*/
private long sendNiciraRoleRequest(OFControllerRole role, long xid){
// Construct the role request message
if(factory.getVersion().compareTo(OFVersion.OF_12) < 0) {
OFNiciraControllerRoleRequest.Builder builder =
factory.buildNiciraControllerRoleRequest();
xid = xid <= 0 ? factory.nextXid() : xid;
builder.setXid(xid);
OFNiciraControllerRole niciraRole = NiciraRoleUtils.ofRoleToNiciraRole(role);
builder.setRole(niciraRole);
OFNiciraControllerRoleRequest roleRequest = builder.build();
// Send it to the switch
mainConnection.write(roleRequest);
} else {
// send an OF 1.2+ role request
OFRoleRequest roleRequest = factory.buildRoleRequest()
// we don't use the generation id scheme for now,
// switch initializes to 0, we keep it at 0
.setGenerationId(U64.of(0))
.setXid(xid <= 0 ? factory.nextXid() : xid)
.setRole(role)
.build();
xid = roleRequest.getXid();
mainConnection.write(roleRequest);
}
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(OFControllerRole role, long xid)
throws IOException {
long now = System.nanoTime();
if (now - lastAssertTimeNs < assertTimeIntervalNs) {
return;
}
lastAssertTimeNs = now;
if (assertTimeIntervalNs < MAX_ASSERT_TIME_INTERVAL_NS) { // 2 minutes max
assertTimeIntervalNs <<= 1;
} else if (role == OFControllerRole.ROLE_MASTER){
log.warn("Reasserting master role on switch {}, " +
"likely a switch config error with multiple masters",
role, sw);
}
if (!requestPending)
sendRoleRequest(role, xid);
else
switchManagerCounters.roleNotResentBecauseRolePending.increment();
}
/**
* 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(OFControllerRole role, long xid) 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 = sendNiciraRoleRequest(role, xid);
pendingRole = role;
this.roleSubmitTimeNs = System.nanoTime();
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(long xid, OFControllerRole role) {
log.debug("DELIVERING ROLE REPLY {}", role.toString());
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",
OFSwitchHandshakeHandler.this.getSwitchInfoString(),
OFSwitchHandshakeHandler.this.state.toString(),
role);
throw new SwitchStateException(msg);
}
if (pendingXid == xid && pendingRole == role) {
log.debug("[{}] Received role reply message setting role to {}",
getDpid(), role);
switchManagerCounters.roleReplyReceived.increment();
setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY);
} else {
log.debug("[{}] Received stale or unexpected role reply " +
"{}, xid={}. Ignoring. " +
"Waiting for {}, xid={}",
new Object[] { getDpid(), 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(OFErrorMsg error) {
if (!requestPending)
return false;
if (pendingXid == error.getXid()) {
if (error.getErrType() == OFErrorType.BAD_REQUEST) {
switchManagerCounters.roleReplyErrorUnsupported.increment();
setSwitchRole(pendingRole, RoleRecvStatus.UNSUPPORTED);
} else {
String msg = String.format("Switch: [%s], State: [%s], "
+ "Unexpected error %s in respone to our "
+ "role request for %s.",
OFSwitchHandshakeHandler.this.getSwitchInfoString(),
OFSwitchHandshakeHandler.this.state.toString(),
error.toString(),
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.nanoTime();
if (now - this.roleSubmitTimeNs > roleTimeoutNs) {
// timeout triggered.
switchManagerCounters.roleReplyTimeout.increment();
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(OFControllerRole 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.setControllerRole(role);
if (role != OFControllerRole.ROLE_SLAVE) {
OFSwitchHandshakeHandler.this.setState(new MasterState());
} else {
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.disconnect();
} else {
OFSwitchHandshakeHandler.this.setState(new SlaveState());
}
}
}
}
/**
* Removes all present flows
*/
private void clearAllTables() {
/*
* No tables for OF1.0, so omit that field for flow deletion.
*/
if (this.sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) == 0) {
OFFlowDelete deleteFlows = this.factory.buildFlowDelete()
.build();
this.sw.write(deleteFlows);
} else { /* All other OFVersions support multiple tables and groups. */
OFFlowDelete deleteFlows = this.factory.buildFlowDelete()
.setTableId(TableId.ALL)
.build();
this.sw.write(deleteFlows);
/*
* Clear all groups.
* We have to do this for all types manually as of Loxi 0.9.0.
*/
OFGroupDelete.Builder delgroup = this.sw.getOFFactory().buildGroupDelete()
.setGroup(OFGroup.ALL)
.setGroupType(OFGroupType.ALL);
if (this.sw.getOFFactory().getVersion().compareTo(OFVersion.OF_15) >= 0) {
delgroup.setCommandBucketId(OFGroupBucket.BUCKET_ALL);
}
this.sw.write(delgroup.build());
delgroup.setGroupType(OFGroupType.FF);
this.sw.write(delgroup.build());
delgroup.setGroupType(OFGroupType.INDIRECT);
this.sw.write(delgroup.build());
delgroup.setGroupType(OFGroupType.SELECT);
this.sw.write(delgroup.build());
/*
* Make sure we allow these operations to complete before proceeding.
*/
OFBarrierRequest barrier = factory.buildBarrierRequest()
.setXid(handshakeTransactionIds--)
.build();
sw.write(barrier);
}
}
/**
* Adds an initial table-miss flow to tables on the switch.
* This replaces the default behavior of forwarding table-miss packets
* to the controller. The table-miss flows inserted will forward all
* packets that do not match a flow to the controller for processing.
*
* The OFSwitchManager is checked for used-defined behavior and default
* max table to try to use.
*
* Adding the default flow only applies to OpenFlow 1.3+ switches, which
* remove the default forward-to-controller behavior of flow tables.
*/
private void addDefaultFlows() {
/*
* Only for OF1.3+, insert the default forward-to-controller flow for
* each table. This is priority=0 with no Match.
*/
if (this.sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) {
/*
* Remove the default flow if it's present.
*/
OFFlowDeleteStrict deleteFlow = this.factory.buildFlowDeleteStrict()
.setTableId(TableId.ALL)
.setOutPort(OFPort.CONTROLLER)
.build();
this.sw.write(deleteFlow);
ArrayList<OFAction> actions = new ArrayList<OFAction>(1);
actions.add(factory.actions().output(OFPort.CONTROLLER, 0xffFFffFF));
ArrayList<OFMessage> flows = new ArrayList<OFMessage>();
/* If we received a table features reply, iterate over the tables */
if (!this.sw.getTables().isEmpty()) {
short missCount = 0;
for (TableId tid : this.sw.getTables()) {
/* Only add the flow if the table exists and if it supports sending to the controller */
TableFeatures tf = this.sw.getTableFeatures(tid);
if (tf != null && (missCount < this.sw.getMaxTableForTableMissFlow().getValue())) {
if (tf.getPropApplyActionsMiss() != null) {
for (OFActionId aid : tf.getPropApplyActionsMiss().getActionIds()) {
if (aid.getType() == OFActionType.OUTPUT) { /* The assumption here is that OUTPUT includes the special port CONTROLLER... */
OFFlowAdd defaultFlow = this.factory.buildFlowAdd()
.setTableId(tid)
.setPriority(0)
.setInstructions(Collections.singletonList((OFInstruction) this.factory.instructions().buildApplyActions().setActions(actions).build()))
.build();
flows.add(defaultFlow);
break; /* Stop searching for actions and go to the next table in the list */
}
}
}
}
missCount++;
}
} else { /* Otherwise, use the number of tables starting at TableId=0 as indicated in the features reply */
short missCount = 0;
for (short tid = 0; tid < this.sw.getNumTables(); tid++, missCount++) {
if (missCount < this.sw.getMaxTableForTableMissFlow().getValue()) { /* Only insert if we want it */
OFFlowAdd defaultFlow = this.factory.buildFlowAdd()
.setTableId(TableId.of(tid))
.setPriority(0)
.setActions(actions)
.build();
flows.add(defaultFlow);
}
}
}
this.sw.write(flows);
}
}
/**
* 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
*/
public abstract class OFSwitchHandshakeState {
void processOFBarrierReply(OFBarrierReply m) {
// do nothing
}
void processOFError(OFErrorMsg m) {
logErrorDisconnect(m);
}
void processOFFlowRemoved(OFFlowRemoved m) {
unhandledMessageReceived(m);
}
void processOFGetConfigReply(OFGetConfigReply m) {
illegalMessageReceived(m);
}
void processOFPacketIn(OFPacketIn m) {
unhandledMessageReceived(m);
}
// By default add port status messages to a pending list
void processOFPortStatus(OFPortStatus m) {
pendingPortStatusMsg.add(m);
}
void processOFQueueGetConfigReply(OFQueueGetConfigReply m) {
unhandledMessageReceived(m);
}
void processOFStatsReply(OFStatsReply m) {
switch(m.getStatsType()) {
case PORT_DESC:
processPortDescStatsReply((OFPortDescStatsReply) m);
break;
default:
unhandledMessageReceived(m);
}
}
void processOFExperimenter(OFExperimenter m) {
unhandledMessageReceived(m);
}
void processPortDescStatsReply(OFPortDescStatsReply m) {
unhandledMessageReceived(m);
}
void processOFRoleReply(OFRoleReply m) {
unhandledMessageReceived(m);
}
void processOFRoleRequest(OFRoleRequest m) {
unhandledMessageWritten(m);
}
/**
* Tulio Ribeiro
*/
void processOFRoleStatus(OFRoleStatus m){
unhandledMessageReceived(m);
}
void processOFNiciraControllerRoleRequest(OFNiciraControllerRoleRequest m) {
unhandledMessageWritten(m);
}
void processOFBundleCtrl(OFBundleCtrlMsg m) {
unhandledMessageReceived(m);
}
private final boolean handshakeComplete;
OFSwitchHandshakeState(boolean handshakeComplete) {
this.handshakeComplete = handshakeComplete;
}
void logState() {
if(log.isDebugEnabled())
log.debug("[{}] - Switch Handshake - enter state {}", mainConnection.getDatapathId(), this.getClass().getSimpleName());
}
/** enter this state. Can initialize the handler, send
* the necessary messages, etc.
*/
void enterState(){
}
/**
* Is this a state in which the handshake has completed?
* @return true if the handshake is complete
*/
public boolean isHandshakeComplete() {
return handshakeComplete;
}
/**
* Used to notify the WAIT OF AUX state that
* a new connection has been added
* @param connection
*/
public void auxConnectionOpened(IOFConnectionBackend connection) {
// Should only be handled in wait of aux
log.debug("[{}] - Switch Handshake - unhandled aux connection event",
getDpid());
}
/**
* 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(OFMessage m,
String details) {
return String.format("Switch: [%s], State: [%s], received: [%s]"
+ ", details: %s",
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(OFMessage m) {
String msg = getSwitchStateMessage(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(OFMessage m) {
switchManagerCounters.unhandledMessage.increment();
if (log.isDebugEnabled()) {
String msg = getSwitchStateMessage(m,
"Ignoring unexpected message");
log.debug(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 that wrote the message
* @param m the message
*/
protected void unhandledMessageWritten(OFMessage m) {
switchManagerCounters.unhandledMessage.increment();
if (log.isDebugEnabled()) {
String msg = getSwitchStateMessage(m,
"Ignoring unexpected written message");
log.debug(msg);
}
}
/**
* Log an OpenFlow error message from a switch
* @param error The error message
*/
protected void logError(OFErrorMsg error) {
log.error("{} from switch {} in state {}",
new Object[] {
error.toString(),
getSwitchInfoString(),
this.toString()});
}
/**
* Log an OpenFlow error message from a switch and disconnect the
* channel
* @param error The error message
*/
protected void logErrorDisconnect(OFErrorMsg error) {
logError(error);
mainConnection.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.
*/
protected OFControllerRole extractNiciraRoleReply(OFMessage vendorMessage) {
if (!(vendorMessage instanceof OFNiciraControllerRoleReply))
return null;
OFNiciraControllerRoleReply roleReply =
(OFNiciraControllerRoleReply) vendorMessage;
return NiciraRoleUtils.niciraToOFRole(roleReply);
}
/**
* 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(OFPortStatus m, boolean doNotify) {
if (sw == null) {
String msg = getSwitchStateMessage(m, "State machine error: switch is null. Should never happen");
throw new SwitchStateException(msg);
}
Collection<PortChangeEvent> changes = sw.processOFPortStatus(m);
if (doNotify) {
for (PortChangeEvent ev: changes)
switchManager.notifyPortChanged(sw, ev.port, ev.type);
}
}
/**
* Handle a table features message.
*
* Handle a table features message by updating the tables in the
* IOFSwitch instance and notifying Controller about the change so
* it can dispatch a switch update.
*
* @param h The OFChannelHandler that received the message
* @param m The OFTableFeatures message we received
* @param doNotify if true switch table changed events will be
* dispatched
*/
protected void handleTableFeaturesMessage(List<OFTableFeaturesStatsReply> replies, boolean doNotify) {
if (sw == null) {
String msg = getSwitchStateMessage(!replies.isEmpty() ? replies.get(0) : null, "State machine error: switch is null. Should never happen");
throw new SwitchStateException(msg);
}
sw.processOFTableFeatures(replies);
}
/**
* 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(OFMessage m) {
roleChanger.checkTimeout();
switch(m.getType()) {
case BARRIER_REPLY:
processOFBarrierReply((OFBarrierReply) m);
break;
case ERROR:
processOFError((OFErrorMsg) m);
break;
case FLOW_REMOVED:
processOFFlowRemoved((OFFlowRemoved) m);
break;
case GET_CONFIG_REPLY:
processOFGetConfigReply((OFGetConfigReply) m);
break;
case PACKET_IN:
processOFPacketIn((OFPacketIn) m);
break;
case PORT_STATUS:
processOFPortStatus((OFPortStatus) m);
break;
case QUEUE_GET_CONFIG_REPLY:
processOFQueueGetConfigReply((OFQueueGetConfigReply) m);
break;
case STATS_REPLY:
processOFStatsReply((OFStatsReply) m);
break;
case ROLE_REPLY:
processOFRoleReply((OFRoleReply) m);
break;
case EXPERIMENTER:
processOFExperimenter((OFExperimenter) m);
break;
case ROLE_STATUS:
processOFRoleStatus((OFRoleStatus) m);
break;
case BUNDLE_CONTROL:
processOFBundleCtrl((OFBundleCtrlMsg) m);
break;
default:
illegalMessageReceived(m);
break;
}
}
void processWrittenOFMessage(OFMessage m) {
switch(m.getType()) {
case ROLE_REQUEST:
processOFRoleRequest((OFRoleRequest) m);
break;
case EXPERIMENTER:
if (m instanceof OFNiciraControllerRoleRequest) {
processOFNiciraControllerRoleRequest((OFNiciraControllerRoleRequest) m);
}
break;
default:
break;
}
}
}
/**
* Initial state before channel is connected. Should not handle any messages.
*/
public class InitState extends OFSwitchHandshakeState {
InitState() {
super(false);
}
@Override
public void logState() {
log.debug("[{}] - Switch Handshake - Initiating from {}",
getDpid(), mainConnection.getRemoteInetAddress());
}
}
/**
* 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
*/
public class WaitPortDescStatsReplyState extends OFSwitchHandshakeState {
WaitPortDescStatsReplyState() {
super(false);
}
@Override
void enterState(){
sendPortDescRequest();
}
@Override
void processPortDescStatsReply(OFPortDescStatsReply m) {
portDescStats = m;
setState(new WaitConfigReplyState());
}
@Override
void processOFExperimenter(OFExperimenter m) {
unhandledMessageReceived(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
*/
public class WaitConfigReplyState extends OFSwitchHandshakeState {
WaitConfigReplyState() {
super(false);
}
@Override
void processOFGetConfigReply(OFGetConfigReply m) {
if (m.getMissSendLen() == 0xffff) {
log.trace("Config Reply from switch {} confirms "
+ "miss length set to 0xffff",
getSwitchInfoString());
} else {
log.warn("Config Reply from switch {} has"
+ "miss length set to {}",
getSwitchInfoString(),
m.getMissSendLen());
}
setState(new WaitDescriptionStatReplyState());
}
@Override
void processOFStatsReply(OFStatsReply m) {
illegalMessageReceived(m);
}
@Override
void processOFError(OFErrorMsg m) {
/*
* HP ProCurve switches do not support
* the ofpt_barrier_request message.
*
* Look for an error from a bad ofpt_barrier_request,
* log a warning, but proceed.
*/
if (m.getErrType() == OFErrorType.BAD_REQUEST &&
((OFBadRequestErrorMsg) m).getCode() == OFBadRequestCode.BAD_TYPE) {
if (((OFBadRequestErrorMsg) m).getData().getParsedMessage().isPresent() &&
((OFBadRequestErrorMsg) m).getData().getParsedMessage().get() instanceof OFBarrierRequest) {
log.warn("Switch does not support Barrier Request messages. Could be an HP ProCurve.");
} else if (!((OFBadRequestErrorMsg) m).getData().getParsedMessage().isPresent()) {
log.warn("Switch may not support Barrier Request messages (we can't know for sure if it's a barrier or not). Could be a Brocade...");
}
} else {
logErrorDisconnect(m);
}
}
@Override
void enterState() {
sendHandshakeSetConfig();
}
}
/**
* We are waiting for a OFDescriptionStat message from the switch.
* Once we receive any stat message we try to parse it. If it's not
* a description stats message we disconnect. If its the expected
* description stats message, we:
* - use the switch driver to bind the switch and get an IOFSwitch
* instance, setup the switch instance
* - setup the IOFSwitch instance
* - add switch to FloodlightProvider and send the initial role
* request to the switch.
*
* Next state: WaitOFAuxCxnsReplyState (if OF1.3), else
* WaitInitialRoleState or WaitSwitchDriverSubHandshake
*
* All following states will have a h.sw instance!
*/
public class WaitDescriptionStatReplyState extends OFSwitchHandshakeState{
long timestamp;
WaitDescriptionStatReplyState() {
super(false);
}
@Override
void processOFStatsReply(OFStatsReply m) {
// Read description, if it has been updated
if (m.getStatsType() != OFStatsType.DESC) {
illegalMessageReceived(m);
return;
}
OFDescStatsReply descStatsReply = (OFDescStatsReply) m;
SwitchDescription description = new SwitchDescription(descStatsReply);
sw = switchManager.getOFSwitchInstance(mainConnection, description, factory, featuresReply.getDatapathId());
// set switch information
// set features reply and channel first so we a DPID and
// channel info.
sw.setFeaturesReply(featuresReply);
if (portDescStats != null) {
sw.setPortDescStats(portDescStats);
}
/*
* Need to add after setting the features.
*/
switchManager.switchAdded(sw);
// Handle pending messages now that we have a sw object
handlePendingPortStatusMessages(description);
setState(new WaitTableFeaturesReplyState());
}
void handlePendingPortStatusMessages(SwitchDescription description){
for (OFPortStatus ps: pendingPortStatusMsg) {
handlePortStatusMessage(ps, false);
}
pendingPortStatusMsg.clear();
log.info("Switch {} bound to class {}, description {}", new Object[] { sw, sw.getClass(), description });
}
@Override
void enterState() {
sendHandshakeDescriptionStatsRequest();
}
}
/*
* New state: WaitSwitchTableFeaturesReplyState
*/
public class WaitTableFeaturesReplyState extends OFSwitchHandshakeState {
private ArrayList<OFTableFeaturesStatsReply> replies;
WaitTableFeaturesReplyState() {
super(false);
replies = new ArrayList<OFTableFeaturesStatsReply>();
}
@Override
/**
* Accumulate a list of the OFTableFeaturesStatsReply's until there
* are no more remaining. Then, pass the list to the switch for
* parsing and configuration.
*
* The assumption is that the OFMessage dispatcher will call this each
* time, which it does. We don't loop and receive here.
*
* @param m, The potential OFTableFeaturesStatsReply message we want to include
*/
void processOFStatsReply(OFStatsReply m) {
if (m.getStatsType() == OFStatsType.TABLE_FEATURES) {
replies.add((OFTableFeaturesStatsReply) m);
if (!((OFTableFeaturesStatsReply)m).getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
handleTableFeaturesMessage(replies, false);
nextState();
}
} else {
/* should only receive TABLE_FEATURES here */
log.error("Received {} message but expected TABLE_FEATURES.", m.getStatsType().toString());
}
}
@Override
void processOFError(OFErrorMsg m) {
if ((m.getErrType() == OFErrorType.BAD_REQUEST) &&
((((OFBadRequestErrorMsg)m).getCode() == OFBadRequestCode.MULTIPART_BUFFER_OVERFLOW)
|| ((OFBadRequestErrorMsg)m).getCode() == OFBadRequestCode.BAD_STAT)) {
log.warn("Switch {} is {} but does not support OFTableFeaturesStats. Assuming all tables can perform any match, action, and instruction in the spec.",
sw.getId().toString(), sw.getOFFactory().getVersion().toString());
} else {
log.error("Received unexpected OFErrorMsg {} on switch {}.", m.toString(), sw.getId().toString());
}
nextState();
}
private void nextState() {
/* move on to the next state */
sw.startDriverHandshake();
if (sw.isDriverHandshakeComplete()) {
setState(new WaitAppHandshakeState());
} else {
setState(new WaitSwitchDriverSubHandshakeState());
}
}
@Override
void enterState() {
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) < 0
|| sw.getOFFactory().getVersion().compareTo(OFVersion.OF_15) == 0) { //FIXME must skip for OVS
nextState();
} else {
sendHandshakeTableFeaturesRequest();
}
}
}
public class WaitSwitchDriverSubHandshakeState extends OFSwitchHandshakeState {
WaitSwitchDriverSubHandshakeState() {
super(false);
}
@Override
void processOFMessage(OFMessage m) {
sw.processDriverHandshakeMessage(m);
if (sw.isDriverHandshakeComplete()) {
setState(new WaitAppHandshakeState());
}
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, false);
}
}
public class WaitAppHandshakeState extends OFSwitchHandshakeState {
private final Iterator<IAppHandshakePluginFactory> pluginIterator;
private OFSwitchAppHandshakePlugin plugin;
WaitAppHandshakeState() {
super(false);
this.pluginIterator = switchManager.getHandshakePlugins().iterator();
}
@Override
void processOFMessage(OFMessage m) {
if(m.getType() == OFType.PORT_STATUS){
OFPortStatus status = (OFPortStatus) m;
handlePortStatusMessage(status, false);
}
else if(plugin != null){
this.plugin.processOFMessage(m);
}
else{
super.processOFMessage(m);
}
}
/**
* Called by handshake plugins to signify that they have finished their
* sub handshake.
*
* @param result
* the result of the sub handshake
*/
void exitPlugin(PluginResult result) {
// Proceed
if (result.getResultType() == PluginResultType.CONTINUE) {
if (log.isDebugEnabled()) {
log.debug("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}."
+ " Proceeding normally..",
this.plugin.getClass().getSimpleName(), result);
}
enterNextPlugin();
// Stop
} else if (result.getResultType() == PluginResultType.DISCONNECT) {
log.error("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}. "
+ "Disconnecting switch.",
this.plugin.getClass().getSimpleName(), result);
mainConnection.disconnect();
} else if (result.getResultType() == PluginResultType.QUARANTINE) {
log.warn("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}. "
+ "Putting switch into quarantine state.",
this.plugin.getClass().getSimpleName(),
result);
setState(new QuarantineState(result.getReason()));
}
}
@Override
public void enterState() {
enterNextPlugin();
}
/**
* Initialize the plugin and begin.
*
* @param plugin the of switch app handshake plugin
*/
public void enterNextPlugin() {
if(this.pluginIterator.hasNext()){
this.plugin = pluginIterator.next().createPlugin();
this.plugin.init(this, sw, timer);
this.plugin.enterPlugin();
}
// No more plugins left...
else{
setState(new WaitInitialRoleState());
}
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, false);
}
OFSwitchAppHandshakePlugin getCurrentPlugin() {
return plugin;
}
}
/**
* Switch is in a quarantine state. Essentially the handshake is complete.
*/
public class QuarantineState extends OFSwitchHandshakeState {
private final String quarantineReason;
QuarantineState(String reason) {
super(true);
this.quarantineReason = reason;
}
@Override
public void enterState() {
setSwitchStatus(SwitchStatus.QUARANTINED);
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, false);
}
public String getQuarantineReason() {
return this.quarantineReason;
}
}
/**
* We are waiting for the initial role reply message (or error indication)
* from the switch. Next State: MASTER or SLAVE
*/
public class WaitInitialRoleState extends OFSwitchHandshakeState {
WaitInitialRoleState() {
super(false);
}
@Override
void processOFError(OFErrorMsg m) {
// role changer will ignore the error if it isn't for it
boolean didHandle = roleChanger.deliverError(m);
if (!didHandle) {
logError(m);
}
}
@Override
void processOFExperimenter(OFExperimenter m) {
OFControllerRole role = extractNiciraRoleReply(m);
// If role == null it measn the message wasn't really a
// Nicira role reply. We ignore this case.
if (role != null) {
roleChanger.deliverRoleReply(m.getXid(), role);
} else {
unhandledMessageReceived(m);
}
}
@Override
void processOFRoleReply(OFRoleReply m) {
roleChanger.deliverRoleReply(m.getXid(), m.getRole());
}
@Override
void processOFStatsReply(OFStatsReply m) {
illegalMessageReceived(m);
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, false);
}
@Override
void enterState(){
//sendRoleRequest(roleManager.getOFControllerRole());//original
/**
* Tulio Ribeiro
* Retrieve role from floodlightdefault.properties configuration file
* swId;Role 00:00:00:00:00:00:00:01;ROLE_SLAVE
* If not defined there, the role will be set as MASTER
*/
OFControllerRole role = OFControllerRole.ROLE_MASTER;
if(OFSwitchManager.switchInitialRole != null)
if(OFSwitchManager.switchInitialRole.containsKey(mainConnection.getDatapathId())){
role = OFSwitchManager.switchInitialRole.get(mainConnection.getDatapathId());
log.info("Defining switch role from config file: {}", role);
}
sendRoleRequest(role);
}
}
/**
* 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.
*/
public class MasterState extends OFSwitchHandshakeState {
MasterState() {
super(true);
}
private long sendBarrier() {
long xid = handshakeTransactionIds--;
OFBarrierRequest barrier = factory.buildBarrierRequest()
.setXid(xid)
.build();
sw.write(barrier); /* don't use ListenableFuture here; we receive via barrier reply OR error (barrier unsupported) */
return xid;
}
@Override
void enterState() {
if (OFSwitchManager.clearTablesOnEachTransitionToMaster) {
log.info("Clearing flow tables of {} on upcoming transition to MASTER.", sw.getId().toString());
clearAllTables();
} else if (OFSwitchManager.clearTablesOnInitialConnectAsMaster && initialRole == null) { /* don't do it if we were slave first */
initialRole = OFControllerRole.ROLE_MASTER;
log.info("Clearing flow tables of {} on upcoming initial role as MASTER.", sw.getId().toString());
clearAllTables();
}
sendBarrier(); /* Need to make sure the tables are clear before adding default flows */
addDefaultFlows();
/*
* We also need a barrier between adding flows and notifying modules of the
* transition to master. Some modules might modify the flow tables and expect
* the clear/default flow operations above to have completed.
*/
sendBarrier();
setSwitchStatus(SwitchStatus.MASTER);
}
@Override
void processOFError(OFErrorMsg m) {
// role changer will ignore the error if it isn't for it
boolean didHandle = roleChanger.deliverError(m);
if (didHandle)
return;
if ((m.getErrType() == OFErrorType.BAD_REQUEST) &&
(((OFBadRequestErrorMsg)m).getCode() == OFBadRequestCode.EPERM)) {
// We are the master controller and the switch returned
// a permission error. This is a likely indicator that
// the switch thinks we are slave. Reassert our
// role
switchManagerCounters.epermErrorWhileSwitchIsMaster.increment();
log.warn("Received permission error from switch {} while" +
"being master. Reasserting master role.",
getSwitchInfoString());
reassertRole(OFControllerRole.ROLE_MASTER);
}
else if ((m.getErrType() == OFErrorType.FLOW_MOD_FAILED) &&
(((OFFlowModFailedErrorMsg)m).getCode() == OFFlowModFailedCode.ALL_TABLES_FULL)) {
sw.setTableFull(true);
}
else {
logError(m);
}
dispatchMessage(m);
}
@Override
void processOFExperimenter(OFExperimenter m) {
OFControllerRole role = extractNiciraRoleReply(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) {
roleChanger.deliverRoleReply(m.getXid(), role);
} else {
dispatchMessage(m);
}
}
@Override
void processOFRoleRequest(OFRoleRequest m) {
sendRoleRequest(m);
}
@Override
void processOFRoleStatus(OFRoleStatus m) {
/**
* Tulio Ribeiro
*
* Controller roles.
* enum ofp_controller_role {
* OFPCR_ROLE_NOCHANGE = 0, Don't change current role.
* OFPCR_ROLE_EQUAL = 1, Default role, full access.
* OFPCR_ROLE_MASTER = 2, Full access, at most one master.
* OFPCR_ROLE_SLAVE = 3, Read-only access.
* };
*/
//log.info("Processing roleStatus from MasterState...");
OFControllerRole role = m.getRole();
if (role == OFControllerRole.ROLE_SLAVE)
sendRoleRequest(OFControllerRole.ROLE_SLAVE);
else if (role == OFControllerRole.ROLE_MASTER)
sendRoleRequest(OFControllerRole.ROLE_MASTER);
else if (role == OFControllerRole.ROLE_EQUAL)
sendRoleRequest(OFControllerRole.ROLE_EQUAL);
else
sendRoleRequest(OFControllerRole.ROLE_NOCHANGE);
}
@Override
void processOFNiciraControllerRoleRequest(OFNiciraControllerRoleRequest m) {
OFControllerRole role;
switch (m.getRole()) {
case ROLE_MASTER:
role = OFControllerRole.ROLE_MASTER;
break;
case ROLE_SLAVE:
role = OFControllerRole.ROLE_SLAVE;
break;
case ROLE_OTHER:
role = OFControllerRole.ROLE_EQUAL;
break;
default:
log.error("Attempted to change to invalid Nicira role {}.", m.getRole().toString());
return;
}
/*
* This will get converted back to the correct factory of the switch later.
* We will use OFRoleRequest though to simplify the API between OF versions.
*/
sendRoleRequest(OFFactories.getFactory(OFVersion.OF_13).buildRoleRequest()
.setGenerationId(U64.ZERO)
.setXid(m.getXid())
.setRole(role)
.build());
}
@Override
void processOFRoleReply(OFRoleReply m) {
roleChanger.deliverRoleReply(m.getXid(), m.getRole());
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, true);
}
@Override
void processOFPacketIn(OFPacketIn m) {
dispatchMessage(m);
}
@Override
void processOFFlowRemoved(OFFlowRemoved m) {
dispatchMessage(m);
}
@Override
void processOFStatsReply(OFStatsReply m) {
super.processOFStatsReply(m);
}
@Override
void processOFBundleCtrl(OFBundleCtrlMsg m) {
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.
*/
public class SlaveState extends OFSwitchHandshakeState {
SlaveState() {
super(true);
}
@Override
void enterState() {
setSwitchStatus(SwitchStatus.SLAVE);
if (initialRole == null) {
initialRole = OFControllerRole.ROLE_SLAVE;
}
}
@Override
void processOFError(OFErrorMsg m) {
// role changer will ignore the error if it isn't for it
boolean didHandle = roleChanger.deliverError(m);
if (!didHandle) {
logError(m);
}
}
@Override
void processOFStatsReply(OFStatsReply m) {
}
@Override
void processOFPortStatus(OFPortStatus m) {
handlePortStatusMessage(m, true);
}
@Override
void processOFExperimenter(OFExperimenter m) {
OFControllerRole role = extractNiciraRoleReply(m);
// If role == null it means the message wasn't really a
// Nicira role reply. We ignore it.
if (role != null) {
roleChanger.deliverRoleReply(m.getXid(), role);
} else {
unhandledMessageReceived(m);
}
}
@Override
void processOFRoleReply(OFRoleReply m) {
roleChanger.deliverRoleReply(m.getXid(), m.getRole());
}
@Override
void processOFRoleRequest(OFRoleRequest m) {
sendRoleRequest(m);
}
@Override
void processOFRoleStatus(OFRoleStatus m) {
/**
* Tulio Ribeiro
*
* Controller roles.
* enum ofp_controller_role {
* OFPCR_ROLE_NOCHANGE = 0, Don't change current role.
* OFPCR_ROLE_EQUAL = 1, Default role, full access.
* OFPCR_ROLE_MASTER = 2, Full access, at most one master.
* OFPCR_ROLE_SLAVE = 3, Read-only access.
* };
*/
OFControllerRole role = m.getRole();
if(role == OFControllerRole.ROLE_SLAVE)
sendRoleRequest(OFControllerRole.ROLE_SLAVE);
else if (role == OFControllerRole.ROLE_MASTER)
sendRoleRequest(OFControllerRole.ROLE_MASTER);
else if (role == OFControllerRole.ROLE_EQUAL)
sendRoleRequest(OFControllerRole.ROLE_EQUAL);
else
sendRoleRequest(OFControllerRole.ROLE_NOCHANGE);
}
@Override
void processOFNiciraControllerRoleRequest(OFNiciraControllerRoleRequest m) {
OFControllerRole role;
switch (m.getRole()) {
case ROLE_MASTER:
role = OFControllerRole.ROLE_MASTER;
break;
case ROLE_SLAVE:
role = OFControllerRole.ROLE_SLAVE;
break;
case ROLE_OTHER:
role = OFControllerRole.ROLE_EQUAL;
break;
default:
log.error("Attempted to change to invalid Nicira role {}.", m.getRole().toString());
return;
}
/*
* This will get converted back to the correct factory of the switch later.
* We will use OFRoleRequest though to simplify the API between OF versions.
*/
sendRoleRequest(OFFactories.getFactory(OFVersion.OF_13).buildRoleRequest()
.setGenerationId(U64.ZERO)
.setXid(m.getXid())
.setRole(role)
.build());
}
@Override
void processOFPacketIn(OFPacketIn m) {
// we don't expect packetIn while slave, reassert we are slave
switchManagerCounters.packetInWhileSwitchIsSlave.increment();
log.warn("Received PacketIn from switch {} while" +
"being slave. Reasserting slave role.", sw);
reassertRole(OFControllerRole.ROLE_SLAVE);
}
};
/**
* Create a new unconnected OFChannelHandler.
* @param controller
* @param broker
* @throws SwitchHandshakeHandlerException
*/
OFSwitchHandshakeHandler(@Nonnull IOFConnectionBackend connection,
@Nonnull OFFeaturesReply featuresReply,
@Nonnull IOFSwitchManager switchManager,
@Nonnull RoleManager roleManager,
@Nonnull Timer timer) {
Preconditions.checkNotNull(connection, "connection");
Preconditions.checkNotNull(featuresReply, "featuresReply");
Preconditions.checkNotNull(switchManager, "switchManager");
Preconditions.checkNotNull(roleManager, "roleManager");
Preconditions.checkNotNull(timer, "timer");
Preconditions.checkArgument(connection.getAuxId().equals(OFAuxId.MAIN),
"connection must be MAIN connection but is %s", connection);
this.switchManager = switchManager;
this.roleManager = roleManager;
this.mainConnection = connection;
this.auxConnections = new ConcurrentHashMap<OFAuxId, IOFConnectionBackend>();
this.featuresReply = featuresReply;
this.timer = timer;
this.switchManagerCounters = switchManager.getCounters();
this.factory = OFFactories.getFactory(featuresReply.getVersion());
this.roleChanger = new RoleChanger(DEFAULT_ROLE_TIMEOUT_NS);
setState(new InitState());
this.pendingPortStatusMsg = new ArrayList<OFPortStatus>();
connection.setListener(this);
}
/**
* This begins the switch handshake. We start where the OFChannelHandler
* left off, right after receiving the OFFeaturesReply.
*/
public void beginHandshake() {
Preconditions.checkState(state instanceof InitState, "must be in InitState");
if (this.featuresReply.getNTables() > 1) {
log.debug("Have {} table(s) for switch {}", this.featuresReply.getNTables(),
getSwitchInfoString());
}
if (this.featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) {
setState(new WaitConfigReplyState());
} else {
// OF 1.3. Ask for Port Descriptions
setState(new WaitPortDescStatsReplyState());
}
}
public DatapathId getDpid(){
return this.featuresReply.getDatapathId();
}
public OFAuxId getOFAuxId(){
return this.featuresReply.getAuxiliaryId();
}
/**
* Is this a state in which the handshake has completed?
* @return true if the handshake is complete
*/
public boolean isHandshakeComplete() {
return this.state.isHandshakeComplete();
}
/**
* Forwards to RoleChanger. See there.
* @param role
*/
void sendRoleRequestIfNotPending(OFControllerRole role) {
try {
roleChanger.sendRoleRequestIfNotPending(role, 0);
} catch (IOException e) {
log.error("Disconnecting switch {} due to IO Error: {}",
getSwitchInfoString(), e.getMessage());
mainConnection.disconnect();
}
}
void sendRoleRequestIfNotPending(OFRoleRequest role) {
try {
roleChanger.sendRoleRequestIfNotPending(role.getRole(), 0);
} catch (IOException e) {
log.error("Disconnecting switch {} due to IO Error: {}",
getSwitchInfoString(), e.getMessage());
mainConnection.disconnect();
}
}
/**
* Forwards to RoleChanger. See there.
* @param role
*/
void sendRoleRequest(OFControllerRole role) {
try {
roleChanger.sendRoleRequest(role, 0);
} catch (IOException e) {
log.error("Disconnecting switch {} due to IO Error: {}",
getSwitchInfoString(), e.getMessage());
mainConnection.disconnect();
}
}
void sendRoleRequest(OFRoleRequest role) {
try {
roleChanger.sendRoleRequest(role.getRole(), role.getXid());
} catch (IOException e) {
log.error("Disconnecting switch {} due to IO Error: {}",
getSwitchInfoString(), e.getMessage());
mainConnection.disconnect();
}
}
/**
* Dispatches the message to the controller packet pipeline
*/
private void dispatchMessage(OFMessage m) {
this.switchManager.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 (mainConnection == null || mainConnection.getRemoteInetAddress() == null) {
channelString = "?";
} else {
channelString = mainConnection.getRemoteInetAddress().toString();
}
String dpidString;
if (featuresReply == null) {
dpidString = "?";
} else {
dpidString = featuresReply.getDatapathId().toString();
}
return String.format("[%s DPID[%s]]", channelString, dpidString);
}
/**
* Update the channels state. Only called from the state machine.
* TODO: enforce restricted state transitions
* @param state
*/
private void setState(OFSwitchHandshakeState state) {
this.state = state;
state.logState();
state.enterState();
}
public void processOFMessage(OFMessage m) {
state.processOFMessage(m);
}
public void processWrittenOFMessage(OFMessage m) {
state.processWrittenOFMessage(m);
}
/**
* Send the configuration requests to tell the switch we want full
* packets
* @throws IOException
*/
private void sendHandshakeSetConfig() {
// Ensure we receive the full packet via PacketIn
OFSetConfig configSet = factory.buildSetConfig()
.setXid(handshakeTransactionIds--)
.setMissSendLen(0xffff)
.build();
// Barrier
OFBarrierRequest barrier = factory.buildBarrierRequest()
.setXid(handshakeTransactionIds--)
.build();
// Verify (need barrier?)
OFGetConfigRequest configReq = factory.buildGetConfigRequest()
.setXid(handshakeTransactionIds--)
.build();
List<OFMessage> msgList = ImmutableList.<OFMessage>of(configSet, barrier, configReq);
mainConnection.write(msgList);
}
protected void sendPortDescRequest() {
mainConnection.write(factory.buildPortDescStatsRequest().setFlags(ImmutableSet.<OFStatsRequestFlags>of()).build());
}
/**
* send a description state request
*/
private void sendHandshakeDescriptionStatsRequest() {
// Send description stats request to set switch-specific flags
OFDescStatsRequest descStatsRequest = factory.buildDescStatsRequest()
.setXid(handshakeTransactionIds--)
.build();
mainConnection.write(descStatsRequest);
}
/**
* send a table features request
*/
private void sendHandshakeTableFeaturesRequest() {
OFTableFeaturesStatsRequest tfsr = factory.buildTableFeaturesStatsRequest()
/* leave entries blank --> just ask, don't set */
.setXid(handshakeTransactionIds--)
.build();
mainConnection.write(tfsr);
}
OFSwitchHandshakeState getStateForTesting() {
return state;
}
void reassertRole(OFControllerRole role){
this.roleManager.reassertRole(this, HARole.ofOFRole(role));
}
void useRoleChangerWithOtherTimeoutForTesting(long roleTimeoutMs) {
roleChanger = new RoleChanger(TimeUnit.MILLISECONDS.toNanos(roleTimeoutMs));
}
/**
* Called by the switch manager when new aux connections have connected.
* This alerts the state machine of an aux connection.
*
* @param connection
* the aux connection
*/
public synchronized void auxConnectionOpened(IOFConnectionBackend connection) {
if(log.isDebugEnabled())
log.debug("[{}] - Switch Handshake - new aux connection {}", this.getDpid(), connection.getAuxId());
// Handle new Auxiliary connections if the main connection has completed (i.e. in ACTIVE or STANDBY state)
if (this.getState().equals("ACTIVE") || this.getState().equals("STANDBY")) {
auxConnections.put(connection.getAuxId(), connection);
connection.setListener(OFSwitchHandshakeHandler.this);
log.info("Auxiliary connection {} added for {}.", connection.getAuxId().getValue(), connection.getDatapathId().toString());
} else {
log.info("Auxiliary connection {} initiated for {} before main connection handshake complete. Ignorning aux connection attempt.", connection.getAuxId().getValue(), connection.getDatapathId().toString());
}
}
/**
* Gets the main connection
*
* @return the main connection
*/
public IOFConnectionBackend getMainConnection() {
return this.mainConnection;
}
/**
* Determines if this handshake handler is responsible for the supplied
* connection.
*
* @param connection
* an OF connection
* @return true if the handler has the connection
*/
public boolean hasConnection(IOFConnectionBackend connection) {
if (this.mainConnection.equals(connection)
|| this.auxConnections.get(connection.getAuxId()) == connection) {
return true;
} else {
return false;
}
}
void cleanup() {
for (IOFConnectionBackend conn : this.auxConnections.values()) {
conn.disconnect();
}
this.mainConnection.disconnect();
}
public String getState() {
return this.state.getClass().getSimpleName();
}
public String getQuarantineReason() {
if(this.state instanceof QuarantineState) {
QuarantineState qs = (QuarantineState) this.state;
return qs.getQuarantineReason();
}
return null;
}
/**
* Gets the current connections that this switch handshake handler is
* responsible for. Used primarily by the REST API.
* @return an immutable list of IOFConnections
*/
public ImmutableList<IOFConnection> getConnections() {
ImmutableList.Builder<IOFConnection> builder = ImmutableList.builder();
builder.add(mainConnection);
builder.addAll(auxConnections.values());
return builder.build();
}
/** IOFConnectionListener */
@Override
public void connectionClosed(IOFConnectionBackend connection) {
// Disconnect handler's remaining connections
cleanup();
// Only remove the switch handler when the main connection is
// closed
if (connection == this.mainConnection) {
switchManager.handshakeDisconnected(connection.getDatapathId());
if(sw != null) {
log.debug("[{}] - main connection {} closed - disconnecting switch",
connection);
setSwitchStatus(SwitchStatus.DISCONNECTED);
switchManager.switchDisconnected(sw);
}
}
}
@Override
public void messageReceived(IOFConnectionBackend connection, OFMessage m) {
processOFMessage(m);
}
@Override
public void messageWritten(IOFConnectionBackend connection, OFMessage m) {
processWrittenOFMessage(m);
}
@Override
public boolean isSwitchHandshakeComplete(IOFConnectionBackend connection) {
return state.isHandshakeComplete();
}
public void setSwitchStatus(SwitchStatus status) {
if(sw != null) {
SwitchStatus oldStatus = sw.getStatus();
if(oldStatus != status) {
log.debug("[{}] SwitchStatus change to {} requested, switch is in status " + oldStatus,
mainConnection.getDatapathId(), status);
sw.setStatus(status);
switchManager.switchStatusChanged(sw, oldStatus, status);
} else {
log.warn("[{}] SwitchStatus change to {} requested, switch is already in status",
mainConnection.getDatapathId(), status);
}
} else {
log.warn("[{}] SwitchStatus change to {} requested, but switch is not allocated yet",
mainConnection.getDatapathId(), status);
}
}
}