blob: 50e7b3317021ec8bf4fcef4ad821a30dcbce819c [file] [log] [blame]
package org.onlab.onos.of.controller.impl.internal;
import java.io.IOException;
import java.util.Collections;
import org.onlab.onos.of.controller.RoleState;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
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.OFMessage;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
import org.projectfloodlight.openflow.protocol.OFRoleReply;
import org.projectfloodlight.openflow.protocol.OFRoleRequest;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg;
import org.projectfloodlight.openflow.types.U64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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.
*/
class RoleManager {
protected static final long NICIRA_EXPERIMENTER = 0x2320;
private static Logger log = LoggerFactory.getLogger(RoleManager.class);
// indicates that a request is currently pending
// needs to be volatile to allow correct double-check idiom
private volatile boolean requestPending;
// the transaction Id of the pending request
private int pendingXid;
// the role that's pending
private RoleState pendingRole;
// the expectation set by the caller for the returned role
private RoleRecvStatus expectation;
private AbstractOpenFlowSwitch sw;
public RoleManager(AbstractOpenFlowSwitch sw) {
this.requestPending = false;
this.pendingXid = -1;
this.pendingRole = null;
this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
this.sw = sw;
}
/**
* 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(RoleState role) throws IOException {
// Convert the role enum to the appropriate role to send
OFNiciraControllerRole roleToSend = OFNiciraControllerRole.ROLE_OTHER;
switch (role) {
case MASTER:
roleToSend = OFNiciraControllerRole.ROLE_MASTER;
break;
case SLAVE:
case EQUAL:
default:
// ensuring that the only two roles sent to 1.0 switches with
// Nicira role support, are MASTER and SLAVE
roleToSend = OFNiciraControllerRole.ROLE_SLAVE;
log.warn("Sending Nx Role.SLAVE to switch {}.", sw);
}
int xid = sw.getNextTransactionId();
OFExperimenter roleRequest = OFFactories.getFactory(OFVersion.OF_10)
.buildNiciraControllerRoleRequest()
.setXid(xid)
.setRole(roleToSend)
.build();
sw.write(Collections.<OFMessage>singletonList(roleRequest));
return xid;
}
private int sendOF13RoleRequest(RoleState role) throws IOException {
// Convert the role enum to the appropriate role to send
OFControllerRole roleToSend = OFControllerRole.ROLE_NOCHANGE;
switch (role) {
case EQUAL:
roleToSend = OFControllerRole.ROLE_EQUAL;
break;
case MASTER:
roleToSend = OFControllerRole.ROLE_MASTER;
break;
case SLAVE:
roleToSend = OFControllerRole.ROLE_SLAVE;
break;
default:
log.warn("Sending default role.noChange to switch {}."
+ " Should only be used for queries.", sw);
}
int xid = sw.getNextTransactionId();
OFRoleRequest rrm = OFFactories.getFactory(OFVersion.OF_13)
.buildRoleRequest()
.setRole(roleToSend)
.setXid(xid)
//FIXME fix below when we actually use generation ids
.setGenerationId(U64.ZERO)
.build();
sw.sendMsg(rrm);
return xid;
}
/**
* Send a role request with the given role to the switch and update
* the pending request and timestamp.
* Sends an OFPT_ROLE_REQUEST to an OF1.3 switch, OR
* Sends an NX_ROLE_REQUEST to an OF1.0 switch if configured to support it
* in the IOFSwitch driver. If not supported, this method sends nothing
* and returns 'false'. The caller should take appropriate action.
*
* One other optimization we do here is that for OF1.0 switches with
* Nicira role message support, we force the Role.EQUAL to become
* Role.SLAVE, as there is no defined behavior for the Nicira role OTHER.
* We cannot expect it to behave like SLAVE. We don't have this problem with
* OF1.3 switches, because Role.EQUAL is well defined and we can simulate
* SLAVE behavior by using ASYNC messages.
*
* @param role
* @throws IOException
* @returns false if and only if the switch does not support role-request
* messages, according to the switch driver; true otherwise.
*/
synchronized boolean sendRoleRequest(RoleState role, RoleRecvStatus exp)
throws IOException {
this.expectation = exp;
if (sw.factory().getVersion() == OFVersion.OF_10) {
Boolean supportsNxRole = (Boolean)
sw.supportNxRole();
if (!supportsNxRole) {
log.debug("Switch driver indicates no support for Nicira "
+ "role request messages. Not sending ...");
handleUnsentRoleMessage(role,
expectation);
return false;
}
// OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
// make Role.EQUAL become Role.SLAVE
role = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
pendingXid = sendNxRoleRequest(role);
pendingRole = role;
requestPending = true;
} else {
// OF1.3 switch, use OFPT_ROLE_REQUEST message
pendingXid = sendOF13RoleRequest(role);
pendingRole = role;
requestPending = true;
}
return true;
}
private void handleUnsentRoleMessage(RoleState role,
RoleRecvStatus exp) throws IOException {
// typically this is triggered for a switch where role messages
// are not supported - we confirm that the role being set is
// master
if (exp != RoleRecvStatus.MATCHED_SET_ROLE) {
log.error("Expected MASTER role from registry for switch "
+ "which has no support for role-messages."
+ "Received {}. It is possible that this switch "
+ "is connected to other controllers, in which "
+ "case it should support role messages - not "
+ "moving forward.", role);
}
}
/**
* Deliver a received role reply.
*
* Check if a request is pending and if the received reply matches the
* the expected pending reply (we check both role and xid) we set
* the role for the switch/channel.
*
* If a request is pending but doesn't match the reply we ignore it, and
* return
*
* If no request is pending we disconnect with a SwitchStateException
*
* @param RoleReplyInfo information about role-reply in format that
* controller can understand.
* @throws SwitchStateException if no request is pending
*/
synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
throws SwitchStateException {
if (!requestPending) {
RoleState currentRole = (sw != null) ? sw.getRole() : null;
if (currentRole != null) {
if (currentRole == rri.getRole()) {
// Don't disconnect if the role reply we received is
// for the same role we are already in.
log.debug("Received unexpected RoleReply from "
+ "Switch: {}. "
+ "Role in reply is same as current role of this "
+ "controller for this sw. Ignoring ...",
sw.getStringId());
return RoleRecvStatus.OTHER_EXPECTATION;
} else {
String msg = String.format("Switch: [%s], "
+ "received unexpected RoleReply[%s]. "
+ "No roles are pending, and this controller's "
+ "current role:[%s] does not match reply. "
+ "Disconnecting switch ... ",
sw.getStringId(),
rri, currentRole);
throw new SwitchStateException(msg);
}
}
log.debug("Received unexpected RoleReply {} from "
+ "Switch: {}. "
+ "This controller has no current role for this sw. "
+ "Ignoring ...", new Object[] {rri,
sw.getStringId(), });
return RoleRecvStatus.OTHER_EXPECTATION;
}
int xid = (int) rri.getXid();
RoleState role = rri.getRole();
// XXX S should check generation id meaningfully and other cases of expectations
// U64 genId = rri.getGenId();
if (pendingXid != xid) {
log.debug("Received older role reply from " +
"switch {} ({}). Ignoring. " +
"Waiting for {}, xid={}",
new Object[] {sw.getStringId(), rri,
pendingRole, pendingXid });
return RoleRecvStatus.OLD_REPLY;
}
if (pendingRole == role) {
log.debug("Received role reply message from {} that matched "
+ "expected role-reply {} with expectations {}",
new Object[] {sw.getStringId(), role, expectation});
//setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY); dont want to set state here
if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
return expectation;
} else {
return RoleRecvStatus.OTHER_EXPECTATION;
}
}
// if xids match but role's don't, perhaps its a query (OF1.3)
if (expectation == RoleRecvStatus.REPLY_QUERY) {
return expectation;
}
return RoleRecvStatus.OTHER_EXPECTATION;
}
/**
* Called if we receive an error message. If the xid matches the
* pending request we handle it otherwise we ignore it.
*
* Note: since we only keep the last pending request we might get
* error messages for earlier role requests that we won't be able
* to handle
*/
synchronized RoleRecvStatus deliverError(OFErrorMsg error)
throws SwitchStateException {
if (!requestPending) {
log.debug("Received an error msg from sw {}, but no pending "
+ "requests in role-changer; not handling ...",
sw.getStringId());
return RoleRecvStatus.OTHER_EXPECTATION;
}
if (pendingXid != error.getXid()) {
if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
log.debug("Received an error msg from sw {} for a role request,"
+ " but not for pending request in role-changer; "
+ " ignoring error {} ...",
sw.getStringId(), error);
}
return RoleRecvStatus.OTHER_EXPECTATION;
}
// it is an error related to a currently pending role request message
if (error.getErrType() == OFErrorType.BAD_REQUEST) {
log.error("Received a error msg {} from sw {} for "
+ "pending role request {}. Switch driver indicates "
+ "role-messaging is supported. Possible issues in "
+ "switch driver configuration?", new Object[] {
((OFBadRequestErrorMsg) error).toString(),
sw.getStringId(), pendingRole
});
return RoleRecvStatus.UNSUPPORTED;
}
if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
OFRoleRequestFailedErrorMsg rrerr =
(OFRoleRequestFailedErrorMsg) error;
switch (rrerr.getCode()) {
case BAD_ROLE:
// switch says that current-role-req has bad role?
// for now we disconnect
// fall-thru
case STALE:
// switch says that current-role-req has stale gen-id?
// for now we disconnect
// fall-thru
case UNSUP:
// switch says that current-role-req has role that
// cannot be supported? for now we disconnect
String msgx = String.format("Switch: [%s], "
+ "received Error to for pending role request [%s]. "
+ "Error:[%s]. Disconnecting switch ... ",
sw.getStringId(),
pendingRole, rrerr);
throw new SwitchStateException(msgx);
default:
break;
}
}
// This error message was for a role request message but we dont know
// how to handle errors for nicira role request messages
return RoleRecvStatus.OTHER_EXPECTATION;
}
/**
* Extract the role from an OFVendor message.
*
* Extract the role from an OFVendor message if the message is a
* Nicira role reply. Otherwise return null.
*
* @param h The channel handler receiving the message
* @param vendorMessage The vendor message to parse.
* @return The role in the message if the message is a Nicira role
* reply, null otherwise.
* @throws SwitchStateException If the message is a Nicira role reply
* but the numeric role value is unknown.
*/
protected RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg)
throws SwitchStateException {
int vendor = (int) experimenterMsg.getExperimenter();
if (vendor != 0x2320) {
return null;
}
OFNiciraControllerRoleReply nrr =
(OFNiciraControllerRoleReply) experimenterMsg;
RoleState role = null;
OFNiciraControllerRole ncr = nrr.getRole();
switch(ncr) {
case ROLE_MASTER:
role = RoleState.MASTER;
break;
case ROLE_OTHER:
role = RoleState.EQUAL;
break;
case ROLE_SLAVE:
role = RoleState.SLAVE;
break;
default: //handled below
}
if (role == null) {
String msg = String.format("Switch: [%s], "
+ "received NX_ROLE_REPLY with invalid role "
+ "value %s",
sw.getStringId(),
nrr.getRole());
throw new SwitchStateException(msg);
}
return role;
}
/**
* When we remove a pending role request we use this enum to indicate how we
* arrived at the decision. When we send a role request to the switch, we
* also use this enum to indicate what we expect back from the switch, so the
* role changer can match the reply to our expectation.
*/
public enum RoleRecvStatus {
/** The switch returned an error indicating that roles are not.
* supported*/
UNSUPPORTED,
/** The request timed out. */
NO_REPLY,
/** The reply was old, there is a newer request pending. */
OLD_REPLY,
/**
* The reply's role matched the role that this controller set in the
* request message - invoked either initially at startup or to reassert
* current role.
*/
MATCHED_CURRENT_ROLE,
/**
* The reply's role matched the role that this controller set in the
* request message - this is the result of a callback from the
* global registry, followed by a role request sent to the switch.
*/
MATCHED_SET_ROLE,
/**
* The reply's role was a response to the query made by this controller.
*/
REPLY_QUERY,
/** We received a role reply message from the switch
* but the expectation was unclear, or there was no expectation.
*/
OTHER_EXPECTATION,
}
/**
* Helper class returns role reply information in the format understood
* by the controller.
*/
protected static class RoleReplyInfo {
private RoleState role;
private U64 genId;
private long xid;
RoleReplyInfo(RoleState role, U64 genId, long xid) {
this.role = role;
this.genId = genId;
this.xid = xid;
}
public RoleState getRole() { return role; }
public U64 getGenId() { return genId; }
public long getXid() { return xid; }
@Override
public String toString() {
return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]";
}
}
/**
* Extract the role information from an OF1.3 Role Reply Message.
* @param h
* @param rrmsg
* @return RoleReplyInfo object
* @throws SwitchStateException
*/
protected RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg)
throws SwitchStateException {
OFControllerRole cr = rrmsg.getRole();
RoleState role = null;
switch(cr) {
case ROLE_EQUAL:
role = RoleState.EQUAL;
break;
case ROLE_MASTER:
role = RoleState.MASTER;
break;
case ROLE_SLAVE:
role = RoleState.SLAVE;
break;
case ROLE_NOCHANGE: // switch should send current role
default:
String msg = String.format("Unknown controller role %s "
+ "received from switch %s", cr, sw);
throw new SwitchStateException(msg);
}
return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
}
}
///**
// * We are waiting for a role reply message in response to a role request
// * sent after hearing back from the registry service -- OR -- we are
// * just waiting to hear back from the registry service in the case that
// * the switch does not support role messages. If completed successfully,
// * the controller's role for this switch will be set here.
// * Before we move to the state corresponding to the role, we allow the
// * switch specific driver to complete its configuration. This configuration
// * typically depends on the role the controller is playing for this switch.
// * And so we set the switch role (for 'this' controller) before we start
// * the driver-sub-handshake.
// * Next State: WAIT_SWITCH_DRIVER_SUB_HANDSHAKE
// */
//WAIT_INITIAL_ROLE(false) {
// @Override
// void processOFError(OFChannelHandler h, OFErrorMsg m)
// throws SwitchStateException {
// // role changer will ignore the error if it isn't for it
// RoleRecvStatus rrstatus = h.roleChanger.deliverError(m);
// if (rrstatus == RoleRecvStatus.OTHER_EXPECTATION) {
// logError(h, m);
// }
// }
//
// @Override
// void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
// throws IOException, SwitchStateException {
// Role role = extractNiciraRoleReply(h, m);
// // If role == null it means the vendor (experimenter) message
// // wasn't really a Nicira role reply. We ignore this case.
// if (role != null) {
// RoleReplyInfo rri = new RoleReplyInfo(role, null, m.getXid());
// RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
// if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
// setRoleAndStartDriverHandshake(h, rri.getRole());
// } // else do nothing - wait for the correct expected reply
// } else {
// unhandledMessageReceived(h, m);
// }
// }
//
// @Override
// void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
// throws SwitchStateException, IOException {
// RoleReplyInfo rri = extractOFRoleReply(h, m);
// RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
// if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
// setRoleAndStartDriverHandshake(h, rri.getRole());
// } // else do nothing - wait for the correct expected reply
// }
//
// @Override
// void handleUnsentRoleMessage(OFChannelHandler h, Role role,
// RoleRecvStatus expectation) throws IOException {
// // typically this is triggered for a switch where role messages
// // are not supported - we confirm that the role being set is
// // master and move to the next state
// if (expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
// if (role == Role.MASTER) {
// setRoleAndStartDriverHandshake(h, role);
// } else {
// log.error("Expected MASTER role from registry for switch "
// + "which has no support for role-messages."
// + "Received {}. It is possible that this switch "
// + "is connected to other controllers, in which "
// + "case it should support role messages - not "
// + "moving forward.", role);
// }
// } // else do nothing - wait to hear back from registry
//
// }
//
// private void setRoleAndStartDriverHandshake(OFChannelHandler h,
// Role role) throws IOException {
// h.setSwitchRole(role);
// h.sw.startDriverHandshake();
// if (h.sw.isDriverHandshakeComplete()) {
// Role mySwitchRole = h.sw.getRole();
// if (mySwitchRole == Role.MASTER) {
// log.info("Switch-driver sub-handshake complete. "
// + "Activating switch {} with Role: MASTER",
// h.sw.getStringId());
// handlePendingPortStatusMessages(h); //before activation
// boolean success = h.sw.addActivatedMasterSwitch();
// if (!success) {
// disconnectDuplicate(h);
// return;
// }
// h.setState(MASTER);
// } else {
// log.info("Switch-driver sub-handshake complete. "
// + "Activating switch {} with Role: EQUAL",
// h.sw.getStringId());
// handlePendingPortStatusMessages(h); //before activation
// boolean success = h.sw.addActivatedEqualSwitch();
// if (!success) {
// disconnectDuplicate(h);
// return;
// }
// h.setState(EQUAL);
// }
// } else {
// h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE);
// }
// }
//
// @Override
// void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
// throws IOException, SwitchStateException {
// illegalMessageReceived(h, m);
// }
//
// @Override
// void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
// throws SwitchStateException {
// illegalMessageReceived(h, m);
// }
//
// @Override
// void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
// throws IOException, SwitchStateException {
// h.pendingPortStatusMsg.add(m);
//
// }
//},
///**
// * This controller is in EQUAL role for this switch. We enter this state
// * after some /other/ controller instance wins mastership-role over this
// * switch. The EQUAL role can be considered the same as the SLAVE role
// * if this controller does NOT send commands or packets to the switch.
// * This should always be true for OF1.0 switches. XXX S need to enforce.
// *
// * For OF1.3 switches, choosing this state as EQUAL instead of SLAVE,
// * gives us the flexibility that if an app wants to send commands/packets
// * to switches, it can, even thought it is running on a controller instance
// * that is not in a MASTER role for this switch. Of course, it is the job
// * of the app to ensure that commands/packets sent by this (EQUAL) controller
// * instance does not clash/conflict with commands/packets sent by the MASTER
// * controller for this switch. Neither the controller instances, nor the
// * switch provides any kind of resolution mechanism should conflicts occur.
// */
//EQUAL(true) {
// @Override
// void processOFError(OFChannelHandler h, OFErrorMsg m)
// throws IOException, SwitchStateException {
// // role changer will ignore the error if it isn't for it
// RoleRecvStatus rrstatus = h.roleChanger.deliverError(m);
// if (rrstatus == RoleRecvStatus.OTHER_EXPECTATION) {
// logError(h, m);
// h.dispatchMessage(m);
// }
// }
//
// @Override
// void processOFStatisticsReply(OFChannelHandler h,
// OFStatsReply m) {
// h.sw.handleMessage(m);
// }
//
// @Override
// void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
// throws IOException, SwitchStateException {
// Role role = extractNiciraRoleReply(h, m);
// // If role == null it means the message wasn't really a
// // Nicira role reply. We ignore it in this state.
// if (role != null) {
// RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(
// new RoleReplyInfo(role, null, m.getXid()));
// if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
// checkAndSetRoleTransition(h, role);
// }
// } else {
// unhandledMessageReceived(h, m);
// }
// }
//
// @Override
// void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
// throws SwitchStateException, IOException {
// RoleReplyInfo rri = extractOFRoleReply(h, m);
// RoleRecvStatus rrs = h.roleChanger.deliverRoleReply(rri);
// if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
// checkAndSetRoleTransition(h, rri.getRole());
// }
// }
//
// // XXX S needs more handlers for 1.3 switches in equal role
//
// @Override
// void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
// throws IOException, SwitchStateException {
// handlePortStatusMessage(h, m, true);
// }
//
// @Override
// @LogMessageDoc(level = "WARN",
// message = "Received PacketIn from switch {} while "
// + "being slave. Reasserting slave role.",
// explanation = "The switch has receive a PacketIn despite being "
// + "in slave role indicating inconsistent controller roles",
// recommendation = "This situation can occurs transiently during role"
// + " changes. If, however, the condition persists or happens"
// + " frequently this indicates a role inconsistency. "
// + LogMessageDoc.CHECK_CONTROLLER)
// void processOFPacketIn(OFChannelHandler h, OFPacketIn m) throws IOException {
// // we don't expect packetIn while slave, reassert we are slave
// h.counters.packetInWhileSwitchIsSlave.updateCounterNoFlush();
// log.warn("Received PacketIn from switch {} while" +
// "being slave. Reasserting slave role.", h.sw);
// //h.controller.reassertRole(h, Role.SLAVE);
// // XXX reassert in role changer
// }
//};