package net.floodlightcontroller.core.internal;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.floodlightcontroller.core.*;
import net.floodlightcontroller.core.internal.Controller.IUpdate;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.types.DatapathId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.Date;
import java.util.Map.Entry;
/**
* A utility class to manage the <i>controller roles</i>.
*
* A utility class to manage the <i>controller roles</i> as opposed
* to the switch roles. The class manages the controllers current role,
* handles role change requests, and maintains the list of connected
* switch(-channel) so it can notify the switches of role changes.
*
* We need to ensure that every connected switch is always send the
* correct role. Therefore, switch add, sending of the initial role, and
* changing role need to use mutexes to ensure this. This has the ugly
* side-effect of requiring calls between controller and OFChannelHandler
*
* This class is fully thread safe. Its method can safely be called from
* any thread.
*
* @author gregor
*
*/
public class RoleManager {
private volatile RoleInfo currentRoleInfo;
private final Controller controller;
private final IShutdownService shutdownService;
private final RoleManagerCounters counters;
private static final Logger log =
LoggerFactory.getLogger(RoleManager.class);
/**
* @param role initial role
* @param roleChangeDescription initial value of the change description
* @throws NullPointerException if role or roleChangeDescription is null
*/
public RoleManager(@Nonnull Controller controller,
@Nonnull IShutdownService shutdownService,
@Nonnull HARole role,
@Nonnull String roleChangeDescription) {
Preconditions.checkNotNull(controller, "controller must not be null");
Preconditions.checkNotNull(role, "role must not be null");
Preconditions.checkNotNull(roleChangeDescription, "roleChangeDescription must not be null");
Preconditions.checkNotNull(shutdownService, "shutdownService must not be null");
this.currentRoleInfo = new RoleInfo(role,
roleChangeDescription,
new Date());
this.controller = controller;
this.shutdownService = shutdownService;
this.counters = new RoleManagerCounters(controller.getDebugCounter());
}
/**
* Re-assert a role for the given channel handler.
*
* The caller specifies the role that should be reasserted. We only
* reassert the role if the controller's current role matches the
* reasserted role and there is no role request for the reasserted role
* pending.
* @param ofSwitchHandshakeHandler The OFChannelHandler on which we should reassert.
* @param role The role to reassert
*/
public synchronized void reassertRole(OFSwitchHandshakeHandler ofSwitchHandshakeHandler, HARole role) {
// check if the requested reassertion actually makes sense
if (this.getRole() != role)
return;
ofSwitchHandshakeHandler.sendRoleRequestIfNotPending(this.getRole().getOFRole());
}
/**
* Set the controller's new role and notify switches.
*
* This method updates the controllers current role and notifies all
* connected switches of the new role is different from the current
* role. We dampen calls to this method. See class description for
* details.
*
* @param role The new role.
* @param roleChangeDescription A textual description of why the role
* was changed. For information purposes only.
* @throws NullPointerException if role or roleChangeDescription is null
*/
public synchronized void setRole(HARole role, String roleChangeDescription) {
Preconditions.checkNotNull(role, "role must not be null");
Preconditions.checkNotNull(roleChangeDescription, "roleChangeDescription must not be null");
if (role == getRole()) {
counters.setSameRole.increment();
log.debug("Received role request for {} but controller is "
+ "already {}. Ignoring it.", role, this.getRole());
return;
}
if (this.getRole() == HARole.STANDBY && role == HARole.ACTIVE) {
// At this point we are guaranteed that we will execute the code
// below exactly once during the lifetime of this process! And
// it will be a to MASTER transition
counters.setRoleMaster.increment();
}
log.info("Received role request for {} (reason: {})."
+ " Initiating transition", role, roleChangeDescription);
currentRoleInfo =
new RoleInfo(role, roleChangeDescription, new Date());
controller.addUpdateToQueue(new HARoleUpdate(role));
controller.addUpdateToQueue(new SwitchRoleUpdate(role));
}
@SuppressFBWarnings(value="UG_SYNC_SET_UNSYNC_GET",
justification = "setter is synchronized for mutual exclusion, "
+ "currentRoleInfo is volatile, so no sync on getter needed")
public synchronized HARole getRole() {
return currentRoleInfo.getRole();
}
public synchronized OFControllerRole getOFControllerRole() {
return getRole().getOFRole();
}
/**
* Return the RoleInfo object describing the current role.
*
* Return the RoleInfo object describing the current role. The
* RoleInfo object is used by REST API users.
* @return the current RoleInfo object
*/
public RoleInfo getRoleInfo() {
return currentRoleInfo;
}
private void attemptActiveTransition() {
if(!switchesHaveAnotherMaster()){
// No valid cluster controller connections found, become ACTIVE!
setRole(HARole.ACTIVE, "Leader election assigned ACTIVE role");
}
}
/**
* Iterates over all the switches and checks to see if they have controller
* connections that points towards another master controller.
* @return
*/
private boolean switchesHaveAnotherMaster() {
IOFSwitchService switchService = controller.getSwitchService();
for(Entry<DatapathId, IOFSwitch> switchMap : switchService.getAllSwitchMap().entrySet()){
IOFSwitchBackend sw = (IOFSwitchBackend) switchMap.getValue();
if(sw.hasAnotherMaster()){
return true;
}
}
return false;
}
public void notifyControllerConnectionUpdate() {
if(currentRoleInfo.getRole() != HARole.ACTIVE) {
attemptActiveTransition();
}
}
/**
* Update message indicating controller's role has changed.
* RoleManager, which enqueues these updates guarantees that we will
* only have a single transition from SLAVE to MASTER.
*
* When the role update from master to slave is complete, the HARoleUpdate
* will terminate floodlight.
*/
private class HARoleUpdate implements IUpdate {
private final HARole newRole;
public HARoleUpdate(HARole newRole) {
this.newRole = newRole;
}
@Override
public void dispatch() {
if (log.isDebugEnabled()) {
log.debug("Dispatching HA Role update newRole = {}",
newRole);
}
for (IHAListener listener : Controller.haListeners.getOrderedListeners()) {
if (log.isTraceEnabled()) {
log.trace("Calling HAListener {} with transitionTo{}",
listener.getName(), newRole);
}
switch(newRole) {
case ACTIVE:
listener.transitionToActive();
break;
case STANDBY:
listener.transitionToStandby();
break;
}
}
controller.setNotifiedRole(newRole);
if (newRole == HARole.STANDBY && Controller.shutdownOnTransitionToStandby) {
String reason = String.format("Received role request to "
+ "transition from ACTIVE to STANDBY (reason: %s)",
getRoleInfo().getRoleChangeDescription());
shutdownService.terminate(reason, 0);
}
}
}
public class SwitchRoleUpdate implements IUpdate {
private final HARole role;
public SwitchRoleUpdate(HARole role) {
this.role = role;
}
@Override
public void dispatch() {
if (log.isDebugEnabled()) {
log.debug("Dispatching switch role update newRole = {}, switch role = {}",
this.role, this.role.getOFRole());
}
for (OFSwitchHandshakeHandler h: controller.getSwitchService().getSwitchHandshakeHandlers())
h.sendRoleRequest(this.role.getOFRole());
}
}
public RoleManagerCounters getCounters() {
return this.counters;
}
}