From 2a1fbbf2123381cc9a7faae93110a1719b7b4449 Mon Sep 17 00:00:00 2001 From: Ryan Izard <rizard@g.clemson.edu> Date: Mon, 4 Aug 2014 16:39:31 -0700 Subject: [PATCH] Removed BSN-specific handshakes, some debug messages, tweaked static flow entry pusher, and hub to work with new OpenFlowJ-Loxi flow mod commands. --- .../core/GenTableMap.java | 136 - .../core/GenTableNotFoundException.java | 44 - .../floodlightcontroller/core/IOFSwitch.java | 2 - .../core/IOFSwitchBackend.java | 3 - .../core/OFConnection.java | 1 - .../floodlightcontroller/core/OFSwitch.java | 14 - .../core/internal/OFChannelHandler.java | 1402 +++---- .../internal/OFSwitchHandshakeHandler.java | 3307 ++++++++--------- .../core/internal/OFSwitchManager.java | 3 +- .../net/floodlightcontroller/hub/Hub.java | 37 +- .../IStaticFlowEntryPusherService.java | 4 +- .../staticflowentry/StaticFlowEntries.java | 33 +- .../StaticFlowEntryPusher.java | 50 +- .../web/ListStaticFlowEntriesResource.java | 3 +- .../testmodule/TestModule.java | 81 + .../util/FlowModUtils.java | 119 + ...htcontroller.core.module.IFloodlightModule | 4 +- .../resources/floodlightdefault.properties | 5 +- 18 files changed, 2470 insertions(+), 2778 deletions(-) delete mode 100644 src/main/java/net/floodlightcontroller/core/GenTableMap.java delete mode 100644 src/main/java/net/floodlightcontroller/core/GenTableNotFoundException.java create mode 100644 src/main/java/net/floodlightcontroller/testmodule/TestModule.java create mode 100644 src/main/java/net/floodlightcontroller/util/FlowModUtils.java diff --git a/src/main/java/net/floodlightcontroller/core/GenTableMap.java b/src/main/java/net/floodlightcontroller/core/GenTableMap.java deleted file mode 100644 index ef32a1979..000000000 --- a/src/main/java/net/floodlightcontroller/core/GenTableMap.java +++ /dev/null @@ -1,136 +0,0 @@ -package net.floodlightcontroller.core; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nonnull; -import javax.annotation.concurrent.Immutable; - -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsEntry; -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsReply; -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsRequest; -import org.projectfloodlight.openflow.types.GenTableId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableMap; - -/** A registry for GenTables. Initialized during switch handshake, based on a - * {@link OFBsnGentableDescStatsRequest}. The table for a particular switch - * can be retrieved via {@link IOFSwitch#getGenTableMap()}. - * - * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> - */ -@Immutable -public class GenTableMap { - private static final Logger logger = LoggerFactory.getLogger(GenTableMap.class); - - private final Map<GenTableId, OFBsnGentableDescStatsEntry> idMap; - private final Map<String, OFBsnGentableDescStatsEntry> nameMap; - - // an empty gentable map - private GenTableMap() { - idMap = ImmutableMap.of(); - nameMap = ImmutableMap.of(); - } - - public GenTableMap(Iterable<OFBsnGentableDescStatsEntry> entries) { - // note: not using an ImmutableMap.Builder here, because we need to - // check for duplicates - Map<GenTableId, OFBsnGentableDescStatsEntry> idBuildMap = new LinkedHashMap<>(); - Map<String, OFBsnGentableDescStatsEntry> nameBuildMap = new LinkedHashMap<>(); - - for (OFBsnGentableDescStatsEntry e : entries) { - GenTableId id = e.getTableId(); - String name = e.getName(); - if (idBuildMap.containsKey(id)) { - logger.warn("Duplicate table id " + id + " - entry {} present. Ignoring new entry {}", - idBuildMap.get(id), e); - continue; - } - if (nameBuildMap.containsKey(name)) { - logger.warn( - "Duplicate table name " + name + " - entry named {} present. Ignoring new entry {}", - nameBuildMap.get(name), e); - continue; - } - idBuildMap.put(id, e); - nameBuildMap.put(name, e); - } - this.idMap = ImmutableMap.copyOf(idBuildMap); - this.nameMap = ImmutableMap.copyOf(nameBuildMap); - } - - public Set<GenTableId> getIds() { - return idMap.keySet(); - } - - public Set<String> getNames() { - return nameMap.keySet(); - } - - public Collection<OFBsnGentableDescStatsEntry> getEntries() { - return idMap.values(); - } - - public boolean hasEntry(GenTableId id) { - return idMap.containsKey(id); - } - - /** retrieve a GenTable Description from the map by id. - * - * @param id - * @return the retrieved gen table description. - * @throws GenTableNotFoundException if no gentable with the given id was found in the map - */ - @Nonnull - public OFBsnGentableDescStatsEntry getEntry(GenTableId id) throws GenTableNotFoundException { - OFBsnGentableDescStatsEntry entry = idMap.get(id); - if(entry == null) - throw new GenTableNotFoundException(id, idMap.values()); - return entry; - } - - public boolean hasEntry(String name) { - return nameMap.containsKey(name); - } - - /** retrieve a GenTable Description from the map by name. - * - * @param name - * @return the retrieved gen table description. - * @throws GenTableNotFoundException if no gentable with the given name was found in the map - */ - @Nonnull - public OFBsnGentableDescStatsEntry getEntry(String name) throws GenTableNotFoundException { - OFBsnGentableDescStatsEntry entry = nameMap.get(name); - if(entry == null) - throw new GenTableNotFoundException(name, nameMap.values()); - return entry; - } - - public final static GenTableMap empty() { - return new GenTableMap(); - } - - public static GenTableMap of(Iterable<OFBsnGentableDescStatsReply> replies) { - List<OFBsnGentableDescStatsEntry> allEntries = new ArrayList<>(); - for(OFBsnGentableDescStatsReply reply: replies) { - allEntries.addAll(reply.getEntries()); - } - return new GenTableMap(allEntries); - } - - @Override - public String toString() { - return idMap.toString(); - } - - public int size() { - return idMap.size(); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/GenTableNotFoundException.java b/src/main/java/net/floodlightcontroller/core/GenTableNotFoundException.java deleted file mode 100644 index 544fc1e84..000000000 --- a/src/main/java/net/floodlightcontroller/core/GenTableNotFoundException.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.floodlightcontroller.core; - -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsEntry; -import org.projectfloodlight.openflow.types.GenTableId; - -/** A GenTable was not found in the {@link GenTableMap}. - * - * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> - */ -public class GenTableNotFoundException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public GenTableNotFoundException(String name, Iterable<OFBsnGentableDescStatsEntry> available) { - super(getMessageForName(name, available)); - } - - private static String getMessageForName(String name, - Iterable<OFBsnGentableDescStatsEntry> available) { - return String.format("Table not found: %s (available tables: %s)", name, availableDescr(available)); - } - - public GenTableNotFoundException(GenTableId id, Iterable<OFBsnGentableDescStatsEntry> available) { - super(getMessageForId(id, available)); - } - - private static String getMessageForId(GenTableId id, - Iterable<OFBsnGentableDescStatsEntry> available) { - return String.format("Table not found: %s (available tables: %s)", id, availableDescr(available)); - } - - private static String availableDescr(Iterable<OFBsnGentableDescStatsEntry> available) { - StringBuilder b = new StringBuilder(); - boolean first = true; - for(OFBsnGentableDescStatsEntry e: available) { - if(!first) - b.append(", "); - first = false; - b.append(e.getName()).append("=").append(e.getTableId()); - } - return b.toString(); - } - - -} diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index 165df6ea1..1bd6a5e72 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -111,8 +111,6 @@ public interface IOFSwitch extends IOFMessageWriter { Set<OFCapabilities> getCapabilities(); - GenTableMap getGenTableMap(); - short getTables(); /** diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java index 58cc62786..d16c5e20f 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java @@ -61,9 +61,6 @@ public interface IOFSwitchBackend extends IOFSwitch { */ void setFeaturesReply(OFFeaturesReply featuresReply); - /** set the gentable mapping for this switch */ - void setGenTableMap(GenTableMap map); - /** * Add or modify a switch port. * This is called by the core controller diff --git a/src/main/java/net/floodlightcontroller/core/OFConnection.java b/src/main/java/net/floodlightcontroller/core/OFConnection.java index 58a6a1f82..e53c7dfd0 100644 --- a/src/main/java/net/floodlightcontroller/core/OFConnection.java +++ b/src/main/java/net/floodlightcontroller/core/OFConnection.java @@ -114,7 +114,6 @@ public class OFConnection implements IOFConnection, IOFConnectionBackend{ @Override public void write(OFMessage m) { - logger.debug("Got in write() of OFConnection!!!!!"); if (!isConnected()) { if (logger.isDebugEnabled()) logger.debug("{}: not connected - dropping message {}", this, m); diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitch.java b/src/main/java/net/floodlightcontroller/core/OFSwitch.java index 332f4725c..71bc04e6b 100644 --- a/src/main/java/net/floodlightcontroller/core/OFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/OFSwitch.java @@ -113,8 +113,6 @@ public class OFSwitch implements IOFSwitchBackend { protected SwitchDescription description; - private GenTableMap genTableMap; - private SwitchStatus status; public static final int OFSWITCH_APP_ID = ident(5); @@ -142,7 +140,6 @@ public class OFSwitch implements IOFSwitchBackend { this.role = null; this.description = new SwitchDescription(); this.portManager = new PortManager(); - this.genTableMap = GenTableMap.empty(); this.status = SwitchStatus.HANDSHAKE; // Connections @@ -561,7 +558,6 @@ public class OFSwitch implements IOFSwitchBackend { String.format("%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber())); throw new IllegalArgumentException(msg); } - //TODO @Ryan How to handle port state? // Enabled = not down admin (config) or phys (state) if (!p.getConfig().contains(OFPortConfig.PORT_DOWN) && !p.getState().contains(OFPortState.LINK_DOWN)) { @@ -1035,16 +1031,6 @@ public class OFSwitch implements IOFSwitchBackend { throw new SwitchDriverSubHandshakeNotStarted(); } - @Override - public GenTableMap getGenTableMap() { - return genTableMap; - } - - @Override - public void setGenTableMap(GenTableMap map) { - this.genTableMap = map; - } - @Override public void setSwitchProperties(SwitchDescription description) { this.description = description; diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java index 8be6c6745..ca76c6af5 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java @@ -54,706 +54,706 @@ import com.google.common.base.Preconditions; */ class OFChannelHandler extends IdleStateAwareChannelHandler { - private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); - - private final ChannelPipeline pipeline; - private final INewOFConnectionListener newConnectionListener; - private final SwitchManagerCounters counters; - private Channel channel; - private final Timer timer; - private volatile OFChannelState state; - private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); - private OFFeaturesReply featuresReply; - private volatile OFConnection connection; - private final IDebugCounterService debugCounters; - - /** 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; - - - /** - * Default implementation for message handlers in any OFChannelState. - * - * Individual states must override these if they want a behavior - * that differs from the default. - */ - public abstract class OFChannelState { - - void processOFHello(OFHello m) - throws IOException { - // we only expect hello in the WAIT_HELLO state - illegalMessageReceived(m); - } - - void processOFEchoRequest(OFEchoRequest m) - throws IOException { - sendEchoReply(m); - } - - void processOFEchoReply(OFEchoReply m) - throws IOException { - // do nothing - } - - void processOFError(OFErrorMsg m) { - logErrorDisconnect(m); - } - - void processOFExperimenter(OFExperimenter m) { - unhandledMessageReceived(m); - } - - void processOFFeaturesReply(OFFeaturesReply m) - throws IOException { - // we only expect features reply in the WAIT_FEATURES_REPLY state - illegalMessageReceived(m); - } - - private final boolean channelHandshakeComplete; - - OFChannelState(boolean handshakeComplete) { - this.channelHandshakeComplete = handshakeComplete; - } - - void logState() { - log.debug("{} OFConnection Handshake - enter state {}", - getConnectionInfoString(), this.getClass().getSimpleName()); - } - - /** enter this state. Can initialize the handler, send - * the necessary messages, etc. - * @throws IOException - */ - void enterState() throws IOException{ - // Do Nothing - } - - /** - * 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", - getConnectionInfoString(), - 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) { - counters.unhandledMessage.increment(); - if (log.isDebugEnabled()) { - String msg = getSwitchStateMessage(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(OFErrorMsg error) { - log.error("{} from switch {} in state {}", - new Object[] { - error.toString(), - getConnectionInfoString(), - 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(OFErrorMsg error) { - logError(error); - channel.disconnect(); - } - - /** - * 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) - throws IOException { - // Handle Channel Handshake - if (!state.channelHandshakeComplete) { - switch(m.getType()) { - case HELLO: - processOFHello((OFHello)m); - break; - case ERROR: - processOFError((OFErrorMsg)m); - break; - case FEATURES_REPLY: - processOFFeaturesReply((OFFeaturesReply)m); - break; - case EXPERIMENTER: - processOFExperimenter((OFExperimenter)m); - break; - default: - illegalMessageReceived(m); - break; - } - } - else{ - switch(m.getType()){ - // Always handle echos at the channel level! - // Echos should only be sent in the complete. - case ECHO_REPLY: - processOFEchoReply((OFEchoReply)m); - break; - case ECHO_REQUEST: - processOFEchoRequest((OFEchoRequest)m); - break; - // Send to SwitchManager and thus higher orders of control - default: - sendMessageToConnection(m); - break; - } - } - } - } - - /** - * Initial state before channel is connected. - */ - class InitState extends OFChannelState { - - InitState() { - super(false); - } - } - - /** - * We send a HELLO to the switch and wait for a reply. - * Once we receive the reply we send an OFFeaturesRequest - * Next state is WaitFeaturesReplyState - */ - class WaitHelloState extends OFChannelState { - - WaitHelloState() { - super(false); - } - - @Override - void processOFHello(OFHello m) throws IOException { - OFVersion version = m.getVersion(); - factory = OFFactories.getFactory(version); - OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); - decoder.setVersion(version); - setState(new WaitFeaturesReplyState()); - } - - @Override - void enterState() throws IOException { - sendHelloMessage(); - } - } - - /** - * We are waiting for a features reply message. Once we receive it - * we send capture the features reply. - * Next state is CompleteState - */ - class WaitFeaturesReplyState extends OFChannelState{ - - WaitFeaturesReplyState() { - super(false); - } - @Override - void processOFFeaturesReply(OFFeaturesReply m) - throws IOException { - featuresReply = m; - - // Mark handshake as completed - setState(new CompleteState()); - - } - @Override - void enterState() throws IOException { - sendFeaturesRequest(); - } - }; - - /** - * This state denotes that the channel handshaking is complete. - * An of connection is generated and passed to the switch manager - * for handling. - */ - class CompleteState extends OFChannelState{ - - CompleteState() { - super(true); - } - - @Override - void enterState() throws IOException{ - - setSwitchHandshakeTimeout(); - - // Handle non 1.3 connections - if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ - connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, OFAuxId.MAIN, debugCounters, timer); - } - // Handle 1.3 connections - else{ - connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, featuresReply.getAuxiliaryId(), debugCounters, timer); - - // If this is an aux connection, we set a longer echo idle time - if (!featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { - setAuxChannelIdle(); - } - } - // Notify the connection broker - notifyConnectionOpened(connection); - - } - }; - - /** - * Creates a handler for interacting with the switch channel - * - * @param controller - * the controller - * @param newConnectionListener - * the class that listens for new OF connections (switchManager) - * @param pipeline - * the channel pipeline - * @param threadPool - * the thread pool - * @param idleTimer - * the hash wheeled timer used to send idle messages (echo). - * passed to constructor to modify in case of aux connection. - * @param debugCounters - */ - OFChannelHandler(@Nonnull IOFSwitchManager switchManager, - @Nonnull INewOFConnectionListener newConnectionListener, - @Nonnull ChannelPipeline pipeline, - @Nonnull IDebugCounterService debugCounters, - @Nonnull Timer timer) { - - Preconditions.checkNotNull(switchManager, "switchManager"); - Preconditions.checkNotNull(newConnectionListener, "connectionOpenedListener"); - Preconditions.checkNotNull(pipeline, "pipeline"); - Preconditions.checkNotNull(timer, "timer"); - Preconditions.checkNotNull(debugCounters, "debugCounters"); - - this.pipeline = pipeline; - this.debugCounters = debugCounters; - this.newConnectionListener = newConnectionListener; - this.counters = switchManager.getCounters(); - this.state = new InitState(); - this.timer = timer; - - log.debug("constructor on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); - } - - /** - * Determines if the entire switch handshake is complete (channel+switch). - * If the channel handshake is complete the call is forwarded to the - * connection listener/switch manager to be handled by the appropriate - * switch handshake handler. - * - * @return whether or not complete switch handshake is complete - */ - public boolean isSwitchHandshakeComplete() { - if (this.state.channelHandshakeComplete) { - return connection.getListener().isSwitchHandshakeComplete(connection); - } else { - return false; - } - } - - /** - * Notifies the channel listener that we have a valid baseline connection - */ - private final void notifyConnectionOpened(OFConnection connection){ - this.connection = connection; - this.newConnectionListener.connectionOpened(connection, featuresReply); - } - - /** - * Notifies the channel listener that we our connection has been closed - */ - private final void notifyConnectionClosed(OFConnection connection){ - connection.getListener().connectionClosed(connection); - } - - /** - * Notifies the channel listener that we have a valid baseline connection - */ - private final void sendMessageToConnection(OFMessage m) { - connection.messageReceived(m); - } - - @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 { - log.debug("channelConnected on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); - counters.switchConnected.increment(); - channel = e.getChannel(); - log.info("New switch connection from {}", - channel.getRemoteAddress()); - setState(new WaitHelloState()); - } - - @Override - @LogMessageDoc(message="Disconnected switch {switch information}", - explanation="The specified switch has disconnected.") - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - // Only handle cleanup connection is even known - if(this.connection != null){ - // Alert the connection object that the channel has been disconnected - this.connection.disconnected(); - // Punt the cleanup to the Switch Manager - notifyConnectionClosed(this.connection); - } - log.info("[{}] Disconnected connection", getConnectionInfoString()); - } - - @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) { - - if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { - log.error("Disconnecting switch {} due to read timeout on main cxn.", - getConnectionInfoString()); - ctx.getChannel().close(); - } else { - if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { - log.error("Disconnecting switch {} due to read timeout on main cxn.", - getConnectionInfoString()); - ctx.getChannel().close(); - } else { - // We only don't disconnect on aux connections - log.warn("Switch {} encountered read timeout on aux cxn.", - getConnectionInfoString()); - } - } - // Increment counters - counters.switchDisconnectReadTimeout.increment(); - - } else if (e.getCause() instanceof HandshakeTimeoutException) { - log.error("Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}", - getConnectionInfoString(), - this.state.channelHandshakeComplete); - counters.switchDisconnectHandshakeTimeout.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof ClosedChannelException) { - log.debug("Channel for sw {} already closed", getConnectionInfoString()); - } else if (e.getCause() instanceof IOException) { - log.error("Disconnecting switch {} due to IO Error: {}", - getConnectionInfoString(), e.getCause().getMessage()); - if (log.isDebugEnabled()) { - // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); - } - counters.switchDisconnectIOError.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof SwitchStateException) { - log.error("Disconnecting switch {} due to switch state error: {}", - getConnectionInfoString(), e.getCause().getMessage()); - if (log.isDebugEnabled()) { - // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); - } - counters.switchDisconnectSwitchStateException.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof OFAuxException) { - log.error("Disconnecting switch {} due to OF Aux error: {}", - getConnectionInfoString(), e.getCause().getMessage()); - if (log.isDebugEnabled()) { - // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); - } - counters.switchDisconnectSwitchStateException.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof OFParseError) { - log.error("Disconnecting switch " - + getConnectionInfoString() + - " due to message parse failure", - e.getCause()); - counters.switchDisconnectParseError.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof RejectedExecutionException) { - log.warn("Could not process message: queue full"); - counters.rejectedExecutionException.increment(); - } else { - log.error("Error while processing message from switch " - + getConnectionInfoString() - + "state " + this.state, e.getCause()); - counters.switchDisconnectOtherException.increment(); - ctx.getChannel().close(); - } - } - - @Override - public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) - throws Exception { - - log.debug("channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); - OFChannelHandler handler = ctx.getPipeline().get(OFChannelHandler.class); - handler.sendEchoRequest(); - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) - throws Exception { - if (e.getMessage() instanceof List) { - @SuppressWarnings("unchecked") - List<OFMessage> msglist = (List<OFMessage>)e.getMessage(); - for (OFMessage ofm : msglist) { - try { - // Do the actual packet processing - state.processOFMessage(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); - } - } - } - else { - Channels.fireExceptionCaught(ctx.getChannel(), - new AssertionError("Message received from channel is not a list")); - } - } - - /** - * Sets the channel pipeline's idle (Echo) timeouts to a longer interval. - * This is specifically for aux channels. - */ - private void setAuxChannelIdle() { - - IdleStateHandler idleHandler = new IdleStateHandler( - this.timer, - PipelineIdleReadTimeout.AUX, - PipelineIdleWriteTimeout.AUX, - 0); - pipeline.replace(PipelineHandler.MAIN_IDLE, - PipelineHandler.AUX_IDLE, - idleHandler); - } - - /** - * Sets the channel pipeline's handshake timeout to a more appropriate value - * for the remaining part of the switch handshake. - */ - private void setSwitchHandshakeTimeout() { - - HandshakeTimeoutHandler handler = new HandshakeTimeoutHandler( - this, - this.timer, - PipelineHandshakeTimeout.SWITCH); - - pipeline.replace(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, - PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT, handler); - } - - /** - * Return a string describing this switch based on the already available - * information (DPID and/or remote socket) - * @return - */ - private String getConnectionInfoString() { - - String channelString; - if (channel == null || channel.getRemoteAddress() == null) { - channelString = "?"; - } else { - channelString = channel.getRemoteAddress().toString(); - if(channelString.startsWith("/")) - channelString = channelString.substring(1); - } - String dpidString; - if (featuresReply == null) { - dpidString = "?"; - } else { - StringBuilder b = new StringBuilder(); - b.append(featuresReply.getDatapathId()); - if(featuresReply.getVersion().compareTo(OFVersion.OF_13) >= 0) { - b.append("(").append(featuresReply.getAuxiliaryId()).append(")"); - } - dpidString = b.toString(); - } - return String.format("[%s from %s]", dpidString, channelString ); - } - - /** - * Update the channels state. Only called from the state machine. - * @param state - * @throws IOException - */ - private void setState(OFChannelState state) throws IOException { - this.state = state; - state.logState(); - state.enterState(); - } - - /** - * Send a features request message to the switch using the handshake - * transactions ids. - * @throws IOException - */ - private void sendFeaturesRequest() throws IOException { - // Send initial Features Request - OFFeaturesRequest m = factory.buildFeaturesRequest() - .setXid(handshakeTransactionIds--) - .build(); - channel.write(Collections.singletonList(m)); - } - - /** - * Send a hello message to the switch using the handshake transactions ids. - * @throws IOException - */ - private void sendHelloMessage() throws IOException { - // Send initial hello message - // FIXME:LOJI: Haven't negotiated version yet, assume 1.3 - OFHello.Builder builder = factory.buildHello() - .setXid(handshakeTransactionIds--); - // FIXME: Need to add code here to set the version bitmap hello element - OFHello m = builder.build(); - channel.write(Collections.singletonList(m)); - log.debug("Send hello: {}", m); - } - - private void sendEchoRequest() { - OFEchoRequest request = factory.buildEchoRequest() - .setXid(handshakeTransactionIds--) - .build(); - channel.write(Collections.singletonList(request)); - } - - private void sendEchoReply(OFEchoRequest request) { - OFEchoReply reply = factory.buildEchoReply() - .setXid(request.getXid()) - .setData(request.getData()) - .build(); - channel.write(Collections.singletonList(reply)); - } - - OFChannelState getStateForTesting() { - return state; - } - - IOFConnectionBackend getConnectionForTesting() { - return connection; - } - - ChannelPipeline getPipelineForTesting() { - return this.pipeline; - } + private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); + + private final ChannelPipeline pipeline; + private final INewOFConnectionListener newConnectionListener; + private final SwitchManagerCounters counters; + private Channel channel; + private final Timer timer; + private volatile OFChannelState state; + private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + private OFFeaturesReply featuresReply; + private volatile OFConnection connection; + private final IDebugCounterService debugCounters; + + /** 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; + + + /** + * Default implementation for message handlers in any OFChannelState. + * + * Individual states must override these if they want a behavior + * that differs from the default. + */ + public abstract class OFChannelState { + + void processOFHello(OFHello m) + throws IOException { + // we only expect hello in the WAIT_HELLO state + illegalMessageReceived(m); + } + + void processOFEchoRequest(OFEchoRequest m) + throws IOException { + sendEchoReply(m); + } + + void processOFEchoReply(OFEchoReply m) + throws IOException { + // do nothing + } + + void processOFError(OFErrorMsg m) { + logErrorDisconnect(m); + } + + void processOFExperimenter(OFExperimenter m) { + unhandledMessageReceived(m); + } + + void processOFFeaturesReply(OFFeaturesReply m) + throws IOException { + // we only expect features reply in the WAIT_FEATURES_REPLY state + illegalMessageReceived(m); + } + + private final boolean channelHandshakeComplete; + + OFChannelState(boolean handshakeComplete) { + this.channelHandshakeComplete = handshakeComplete; + } + + void logState() { + log.debug("{} OFConnection Handshake - enter state {}", + getConnectionInfoString(), this.getClass().getSimpleName()); + } + + /** enter this state. Can initialize the handler, send + * the necessary messages, etc. + * @throws IOException + */ + void enterState() throws IOException{ + // Do Nothing + } + + /** + * 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", + getConnectionInfoString(), + 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) { + counters.unhandledMessage.increment(); + if (log.isDebugEnabled()) { + String msg = getSwitchStateMessage(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(OFErrorMsg error) { + log.error("{} from switch {} in state {}", + new Object[] { + error.toString(), + getConnectionInfoString(), + 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(OFErrorMsg error) { + logError(error); + channel.disconnect(); + } + + /** + * 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) + throws IOException { + // Handle Channel Handshake + if (!state.channelHandshakeComplete) { + switch(m.getType()) { + case HELLO: + processOFHello((OFHello)m); + break; + case ERROR: + processOFError((OFErrorMsg)m); + break; + case FEATURES_REPLY: + processOFFeaturesReply((OFFeaturesReply)m); + break; + case EXPERIMENTER: + processOFExperimenter((OFExperimenter)m); + break; + default: + illegalMessageReceived(m); + break; + } + } + else{ + switch(m.getType()){ + // Always handle echos at the channel level! + // Echos should only be sent in the complete. + case ECHO_REPLY: + processOFEchoReply((OFEchoReply)m); + break; + case ECHO_REQUEST: + processOFEchoRequest((OFEchoRequest)m); + break; + // Send to SwitchManager and thus higher orders of control + default: + sendMessageToConnection(m); + break; + } + } + } + } + + /** + * Initial state before channel is connected. + */ + class InitState extends OFChannelState { + + InitState() { + super(false); + } + } + + /** + * We send a HELLO to the switch and wait for a reply. + * Once we receive the reply we send an OFFeaturesRequest + * Next state is WaitFeaturesReplyState + */ + class WaitHelloState extends OFChannelState { + + WaitHelloState() { + super(false); + } + + @Override + void processOFHello(OFHello m) throws IOException { + OFVersion version = m.getVersion(); + factory = OFFactories.getFactory(version); + OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); + decoder.setVersion(version); + setState(new WaitFeaturesReplyState()); + } + + @Override + void enterState() throws IOException { + sendHelloMessage(); + } + } + + /** + * We are waiting for a features reply message. Once we receive it + * we send capture the features reply. + * Next state is CompleteState + */ + class WaitFeaturesReplyState extends OFChannelState{ + + WaitFeaturesReplyState() { + super(false); + } + @Override + void processOFFeaturesReply(OFFeaturesReply m) + throws IOException { + featuresReply = m; + + // Mark handshake as completed + setState(new CompleteState()); + + } + @Override + void enterState() throws IOException { + sendFeaturesRequest(); + } + }; + + /** + * This state denotes that the channel handshaking is complete. + * An OF connection is generated and passed to the switch manager + * for handling. + */ + class CompleteState extends OFChannelState{ + + CompleteState() { + super(true); + } + + @Override + void enterState() throws IOException{ + + setSwitchHandshakeTimeout(); + + // Handle non 1.3 connections + if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ + connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, OFAuxId.MAIN, debugCounters, timer); + } + // Handle 1.3 connections + else{ + connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, featuresReply.getAuxiliaryId(), debugCounters, timer); + + // If this is an aux connection, we set a longer echo idle time + if (!featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { + setAuxChannelIdle(); + } + } + // Notify the connection broker + notifyConnectionOpened(connection); + + } + }; + + /** + * Creates a handler for interacting with the switch channel + * + * @param controller + * the controller + * @param newConnectionListener + * the class that listens for new OF connections (switchManager) + * @param pipeline + * the channel pipeline + * @param threadPool + * the thread pool + * @param idleTimer + * the hash wheeled timer used to send idle messages (echo). + * passed to constructor to modify in case of aux connection. + * @param debugCounters + */ + OFChannelHandler(@Nonnull IOFSwitchManager switchManager, + @Nonnull INewOFConnectionListener newConnectionListener, + @Nonnull ChannelPipeline pipeline, + @Nonnull IDebugCounterService debugCounters, + @Nonnull Timer timer) { + + Preconditions.checkNotNull(switchManager, "switchManager"); + Preconditions.checkNotNull(newConnectionListener, "connectionOpenedListener"); + Preconditions.checkNotNull(pipeline, "pipeline"); + Preconditions.checkNotNull(timer, "timer"); + Preconditions.checkNotNull(debugCounters, "debugCounters"); + + this.pipeline = pipeline; + this.debugCounters = debugCounters; + this.newConnectionListener = newConnectionListener; + this.counters = switchManager.getCounters(); + this.state = new InitState(); + this.timer = timer; + + log.debug("constructor on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + } + + /** + * Determines if the entire switch handshake is complete (channel+switch). + * If the channel handshake is complete the call is forwarded to the + * connection listener/switch manager to be handled by the appropriate + * switch handshake handler. + * + * @return whether or not complete switch handshake is complete + */ + public boolean isSwitchHandshakeComplete() { + if (this.state.channelHandshakeComplete) { + return connection.getListener().isSwitchHandshakeComplete(connection); + } else { + return false; + } + } + + /** + * Notifies the channel listener that we have a valid baseline connection + */ + private final void notifyConnectionOpened(OFConnection connection){ + this.connection = connection; + this.newConnectionListener.connectionOpened(connection, featuresReply); + } + + /** + * Notifies the channel listener that we our connection has been closed + */ + private final void notifyConnectionClosed(OFConnection connection){ + connection.getListener().connectionClosed(connection); + } + + /** + * Notifies the channel listener that we have a valid baseline connection + */ + private final void sendMessageToConnection(OFMessage m) { + connection.messageReceived(m); + } + + @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 { + log.debug("channelConnected on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + counters.switchConnected.increment(); + channel = e.getChannel(); + log.info("New switch connection from {}", + channel.getRemoteAddress()); + setState(new WaitHelloState()); + } + + @Override + @LogMessageDoc(message="Disconnected switch {switch information}", + explanation="The specified switch has disconnected.") + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + // Only handle cleanup connection is even known + if(this.connection != null){ + // Alert the connection object that the channel has been disconnected + this.connection.disconnected(); + // Punt the cleanup to the Switch Manager + notifyConnectionClosed(this.connection); + } + log.info("[{}] Disconnected connection", getConnectionInfoString()); + } + + @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) { + + if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { + log.error("Disconnecting switch {} due to read timeout on main cxn.", + getConnectionInfoString()); + ctx.getChannel().close(); + } else { + if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { + log.error("Disconnecting switch {} due to read timeout on main cxn.", + getConnectionInfoString()); + ctx.getChannel().close(); + } else { + // We only don't disconnect on aux connections + log.warn("Switch {} encountered read timeout on aux cxn.", + getConnectionInfoString()); + } + } + // Increment counters + counters.switchDisconnectReadTimeout.increment(); + + } else if (e.getCause() instanceof HandshakeTimeoutException) { + log.error("Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}", + getConnectionInfoString(), + this.state.channelHandshakeComplete); + counters.switchDisconnectHandshakeTimeout.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof ClosedChannelException) { + log.debug("Channel for sw {} already closed", getConnectionInfoString()); + } else if (e.getCause() instanceof IOException) { + log.error("Disconnecting switch {} due to IO Error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectIOError.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof SwitchStateException) { + log.error("Disconnecting switch {} due to switch state error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectSwitchStateException.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof OFAuxException) { + log.error("Disconnecting switch {} due to OF Aux error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectSwitchStateException.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof OFParseError) { + log.error("Disconnecting switch " + + getConnectionInfoString() + + " due to message parse failure", + e.getCause()); + counters.switchDisconnectParseError.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof RejectedExecutionException) { + log.warn("Could not process message: queue full"); + counters.rejectedExecutionException.increment(); + } else { + log.error("Error while processing message from switch " + + getConnectionInfoString() + + "state " + this.state, e.getCause()); + counters.switchDisconnectOtherException.increment(); + ctx.getChannel().close(); + } + } + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) + throws Exception { + + log.debug("channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + OFChannelHandler handler = ctx.getPipeline().get(OFChannelHandler.class); + handler.sendEchoRequest(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + if (e.getMessage() instanceof List) { + @SuppressWarnings("unchecked") + List<OFMessage> msglist = (List<OFMessage>)e.getMessage(); + for (OFMessage ofm : msglist) { + try { + // Do the actual packet processing + state.processOFMessage(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); + } + } + } + else { + Channels.fireExceptionCaught(ctx.getChannel(), + new AssertionError("Message received from channel is not a list")); + } + } + + /** + * Sets the channel pipeline's idle (Echo) timeouts to a longer interval. + * This is specifically for aux channels. + */ + private void setAuxChannelIdle() { + + IdleStateHandler idleHandler = new IdleStateHandler( + this.timer, + PipelineIdleReadTimeout.AUX, + PipelineIdleWriteTimeout.AUX, + 0); + pipeline.replace(PipelineHandler.MAIN_IDLE, + PipelineHandler.AUX_IDLE, + idleHandler); + } + + /** + * Sets the channel pipeline's handshake timeout to a more appropriate value + * for the remaining part of the switch handshake. + */ + private void setSwitchHandshakeTimeout() { + + HandshakeTimeoutHandler handler = new HandshakeTimeoutHandler( + this, + this.timer, + PipelineHandshakeTimeout.SWITCH); + + pipeline.replace(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, + PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT, handler); + } + + /** + * Return a string describing this switch based on the already available + * information (DPID and/or remote socket) + * @return + */ + private String getConnectionInfoString() { + + String channelString; + if (channel == null || channel.getRemoteAddress() == null) { + channelString = "?"; + } else { + channelString = channel.getRemoteAddress().toString(); + if(channelString.startsWith("/")) + channelString = channelString.substring(1); + } + String dpidString; + if (featuresReply == null) { + dpidString = "?"; + } else { + StringBuilder b = new StringBuilder(); + b.append(featuresReply.getDatapathId()); + if(featuresReply.getVersion().compareTo(OFVersion.OF_13) >= 0) { + b.append("(").append(featuresReply.getAuxiliaryId()).append(")"); + } + dpidString = b.toString(); + } + return String.format("[%s from %s]", dpidString, channelString ); + } + + /** + * Update the channels state. Only called from the state machine. + * @param state + * @throws IOException + */ + private void setState(OFChannelState state) throws IOException { + this.state = state; + state.logState(); + state.enterState(); + } + + /** + * Send a features request message to the switch using the handshake + * transactions ids. + * @throws IOException + */ + private void sendFeaturesRequest() throws IOException { + // Send initial Features Request + OFFeaturesRequest m = factory.buildFeaturesRequest() + .setXid(handshakeTransactionIds--) + .build(); + channel.write(Collections.singletonList(m)); + } + + /** + * Send a hello message to the switch using the handshake transactions ids. + * @throws IOException + */ + private void sendHelloMessage() throws IOException { + // Send initial hello message + // FIXME:LOJI: Haven't negotiated version yet, assume 1.3 + OFHello.Builder builder = factory.buildHello() + .setXid(handshakeTransactionIds--); + // FIXME: Need to add code here to set the version bitmap hello element + OFHello m = builder.build(); + channel.write(Collections.singletonList(m)); + log.debug("Send hello: {}", m); + } + + private void sendEchoRequest() { + OFEchoRequest request = factory.buildEchoRequest() + .setXid(handshakeTransactionIds--) + .build(); + channel.write(Collections.singletonList(request)); + } + + private void sendEchoReply(OFEchoRequest request) { + OFEchoReply reply = factory.buildEchoReply() + .setXid(request.getXid()) + .setData(request.getData()) + .build(); + channel.write(Collections.singletonList(reply)); + } + + OFChannelState getStateForTesting() { + return state; + } + + IOFConnectionBackend getConnectionForTesting() { + return connection; + } + + ChannelPipeline getPipelineForTesting() { + return this.pipeline; + } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java index 5f15a1e23..5acd44059 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java @@ -7,13 +7,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import org.jboss.netty.util.Timer; -import net.floodlightcontroller.core.GenTableMap; + import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IOFConnection; import net.floodlightcontroller.core.IOFConnectionBackend; @@ -25,16 +24,10 @@ import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; + import org.projectfloodlight.openflow.protocol.OFBadRequestCode; import org.projectfloodlight.openflow.protocol.OFBarrierReply; import org.projectfloodlight.openflow.protocol.OFBarrierRequest; -import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; -import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsRequest; -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsReply; -import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsRequest; -import org.projectfloodlight.openflow.protocol.OFBsnSetAuxCxnsReply; -import org.projectfloodlight.openflow.protocol.OFBsnSetAuxCxnsRequest; -import org.projectfloodlight.openflow.protocol.OFBsnSetL2TableRequest; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFDescStatsReply; import org.projectfloodlight.openflow.protocol.OFDescStatsRequest; @@ -60,7 +53,6 @@ import org.projectfloodlight.openflow.protocol.OFRoleReply; import org.projectfloodlight.openflow.protocol.OFRoleRequest; 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.OFType; @@ -85,1815 +77,1490 @@ import com.google.common.collect.ImmutableSet; * @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.3 - This is overwritten by the features reply - private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); - private final OFFeaturesReply featuresReply; - private final Timer timer; - - 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 - */ - 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 = factory.nextXid(); - 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)) - .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 - */ - @LogMessageDoc(level="WARN", - message="Reasserting master role on switch {SWITCH}, " + - "likely a configruation error with multiple masters", - explanation="The controller keeps getting permission error " + - "from switch, likely due to switch connected to another " + - "controller also in master mode", - recommendation=LogMessageDoc.CHECK_SWITCH) - synchronized void sendRoleRequestIfNotPending(OFControllerRole role) - 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); - 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) 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); - 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) { - 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 { - // 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 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.", - 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()); - } - } - } - } - - /** - * 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) { - // 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(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) { - if (m instanceof OFBsnControllerConnectionsReply) { - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - } else { - unhandledMessageReceived(m); - } - } - - void processPortDescStatsReply(OFPortDescStatsReply m) { - unhandledMessageReceived(m); - } - - void processOFRoleReply(OFRoleReply 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); - } - } - - /** - * Log an OpenFlow error message from a switch - * @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(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); - } - } - - protected void handleControllerConnectionMessage(OFBsnControllerConnectionsReply m) { - if (sw == null) { - String msg = getSwitchStateMessage(m, - "State machine error: switch is null. Should never " + - "happen"); - throw new SwitchStateException(msg); - } - // Update switches - sw.updateControllerConnections(m); - // Notify role manager - roleManager.notifyControllerConnectionUpdate(); - } - - - /** - * 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; - default: - illegalMessageReceived(m); - 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); - } - } - - public class WaitSetL2TableReplyState extends OFSwitchHandshakeState { - - WaitSetL2TableReplyState() { - super(false); - } - - @Override - void processOFExperimenter(OFExperimenter m) { - // TODO: actually parse the response - setState(new WaitConfigReplyState()); - }; - - @Override - void processOFStatsReply(OFStatsReply m) { - illegalMessageReceived(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 - @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(OFGetConfigReply m) { - if (m.getMissSendLen() == 0xffff) { - log.trace("Config Reply from switch {} confirms " - + "miss length set to 0xffff", - 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 {}", - getSwitchInfoString(), - m.getMissSendLen()); - } - setState(new WaitDescriptionStatReplyState()); - } - - @Override - void processOFStatsReply(OFStatsReply m) { - illegalMessageReceived(m); - } - - @Override - void processOFError(OFErrorMsg m) { - if ((m.getErrType() == OFErrorType.BAD_REQUEST) && - (((OFBadRequestErrorMsg)m).getCode() == OFBadRequestCode.BAD_EXPERIMENTER)) { - log.debug("Switch {} has multiple tables but does not " + - "support L2 table extension", - getSwitchInfoString()); - return; - } - 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 intial 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{ - - WaitDescriptionStatReplyState() { - super(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 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()); - switchManager.switchAdded(sw); - // 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); - } - - // Handle pending messages now that we have a sw object - handlePendingPortStatusMessages(description); - - // Handle non OF 13 connections - if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ - sw.startDriverHandshake(); - if (sw.isDriverHandshakeComplete()) { - setState(new WaitAppHandshakeState()); - } else { - setState(new WaitSwitchDriverSubHandshakeState()); - } - } - // Handle OF 1.3 MAIN Connection - else{ - setState(new WaitOFAuxCxnsReplyState()); - } - } - - 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(); - } - } - - /** - * We are waiting for an OFAuxCxnsReply message from the switch. - * If the switch replies with a status of 0 it has approved of our request. - * If the auxiliary connections haven't already connected we will - * wait for them. - * - * Next state: WAIT_GENTABLE_DESC_STATS_REPLY (if OF1.3), else - * WAIT_INITIAL_ROLE or WAIT_SWITCH_DRIVER_SUB_HANDSHAKE - * - * All following states will have a h.sw instance! - */ - public class WaitOFAuxCxnsReplyState extends OFSwitchHandshakeState { - - // Default number of aux connections - int numAux = 0; - - WaitOFAuxCxnsReplyState() { - super(false); - } - - @Override - void processOFExperimenter(OFExperimenter m) { - if(m instanceof OFBsnSetAuxCxnsReply){ - OFBsnSetAuxCxnsReply auxReply = (OFBsnSetAuxCxnsReply) m; - - if(auxReply.getStatus() == 0){ - if(this.numAux != 0){ - // All aux connections havent arrived yet - if(auxConnections.size() != this.numAux){ - // We do nothing and wait for connections... - } else { - // Looks like all the aux connections have already arrived - gotoNextState(); - } - } - else { - // We don't need any OF Aux Connections - gotoNextState(); - } - } else { - String msg = String.format("Switch: [%s], HandshakeState: [%s], " - + "received unexpected OF Aux Status [%s]. ", - OFSwitchHandshakeHandler.this.getSwitchInfoString(), - OFSwitchHandshakeHandler.this.state.toString(), - String.valueOf(auxReply.getStatus())); - throw new OFAuxException(msg); - } - } else if(m instanceof OFBsnControllerConnectionsReply) { - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - } - } - - @Override - public void auxConnectionOpened(IOFConnectionBackend auxConnection){ - auxConnections.put(auxConnection.getAuxId(), auxConnection); - auxConnection.setListener(OFSwitchHandshakeHandler.this); - - // Only handle success case - if(auxConnections.size() == this.numAux){ - if(log.isDebugEnabled()) { - log.debug("[{}] - all {} required aux connections open. Proceeding", - getDpid(), numAux); - } - gotoNextState(); - } else { - log.debug("[" + getDpid() + "] - {} / {} required aux connections open. Waiting for more connections. ", - auxConnections.size(), numAux); - } - } - - @Override - void processOFError(OFErrorMsg m) { - log.info("Received error on OF Aux handshake - switch does not support OF Aux: {}", m); - gotoNextState(); - } - - void gotoNextState(){ - // Register any aux connections that have been receieved. - for (IOFConnectionBackend conn : auxConnections.values()) { - sw.registerConnection(conn); - } - //TODO @Ryan but we should still add the main connection as one, right? - //sw.registerConnection(mainConnection); // don't think so bc the connection is already associated - // with the ofswitch - setState(new WaitGentableDescStatsReplyState()); - } - - @Override - public void enterState() { - this.numAux = switchManager.getNumRequiredConnections(); - sendOFAuxSetCxnsRequest(this.numAux); - } - - @Override - void processOFPortStatus(OFPortStatus m) { - handlePortStatusMessage(m, false); - } - } - - /** - * We have requested the GenTable mapping (OFBsnGenTableDescStatsRequest) - * and are waiting on the reply / replies (note: it is a multipart message). - * Once we're received all parts, we create a GenTableMapping object and - * set it in the switch. - * - * If we receive an error messsage corresponding to our request, we conclude - * that the switch doesn't support GenTables and leave the empty default - * mapping in the switch. - * - * Next state: WaitSwitchDriverSubHandshakeState() if a driver handshake is required - * WaitAppHandshake else - */ - public class WaitGentableDescStatsReplyState extends OFSwitchHandshakeState { - - private final List<OFBsnGentableDescStatsReply> pendingGenTableDescStatsReplies; - private final long pendingRequestXid; - - WaitGentableDescStatsReplyState() { - super(false); - pendingGenTableDescStatsReplies = new CopyOnWriteArrayList<>(); - pendingRequestXid = sendHandshakeGenTableDescStatsRequest(); - } - - @Override - void processOFStatsReply(OFStatsReply m) { - // Read description, if it has been updated - if (!(m instanceof OFBsnGentableDescStatsReply)) { - illegalMessageReceived(m); - return; - } - - OFBsnGentableDescStatsReply reply = (OFBsnGentableDescStatsReply) m; - - pendingGenTableDescStatsReplies.add(reply); - if(reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { - // more replies to follow, stay in state - return; - } - - if(sw == null) { - throw new IllegalArgumentException("Switch instance unknown at time of GenTableDescStatsReply"); - } - - GenTableMap genTableMap = GenTableMap.of(pendingGenTableDescStatsReplies); - if(log.isDebugEnabled()) - log.debug("[{}] Gen Tables: {}", sw.getId(), genTableMap); - sw.setGenTableMap(genTableMap); - - gotoNextState(); - } - - public void gotoNextState() { - sw.startDriverHandshake(); - if (sw.isDriverHandshakeComplete()) - setState(new WaitAppHandshakeState()); - else - setState(new WaitSwitchDriverSubHandshakeState()); - } - - @Override - void processOFError(OFErrorMsg m) { - if(m.getXid() == pendingRequestXid) { - // let switch continue with an empty set of gentables - log.info("Received error on GenTable handshake - switch does not support gentables: {}", m); - gotoNextState(); - } else { - illegalMessageReceived(m); - } - } - - @Override - void processOFPortStatus(OFPortStatus m) { - handlePortStatusMessage(m, false); - } - } - - public class WaitSwitchDriverSubHandshakeState extends OFSwitchHandshakeState { - - WaitSwitchDriverSubHandshakeState() { - super(false); - } - - @Override - void processOFMessage(OFMessage m) { - // FIXME: other message to handle here? - 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{ - // Non OF 1.3 - if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ - setState(new WaitInitialRoleState()); - } else { - setState(new WaitControllerCxnsReplyState()); - } - } - } - - @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; - } - } - - public class WaitControllerCxnsReplyState extends OFSwitchHandshakeState { - - WaitControllerCxnsReplyState() { - super(false); - } - - @Override - void processOFExperimenter(OFExperimenter m) { - if(m instanceof OFBsnControllerConnectionsReply) { - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - - setState(new WaitInitialRoleState()); - } else { - unhandledMessageReceived(m); - } - } - - @Override - void processOFError(OFErrorMsg m) { - // @Ryan Need to handle case where the switch is not a BSN switch - setState(new WaitInitialRoleState()); - } - - @Override - void processOFPortStatus(OFPortStatus m) { - handlePortStatusMessage(m, false); - } - - @Override - public void enterState() { - // Write out request down - OFBsnControllerConnectionsRequest request = factory.buildBsnControllerConnectionsRequest() - .build(); - mainConnection.write(request); - - } - - } - - /** - * 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 if(m instanceof OFBsnControllerConnectionsReply) { - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - } 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()); - } - } - - /** - * 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); - } - - @Override - void enterState() { - setSwitchStatus(SwitchStatus.MASTER); - } - - @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(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 - // 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 - 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 if (m instanceof OFBsnControllerConnectionsReply){ - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - } else { - dispatchMessage(m); - } - } - - - @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); - } - } - - /** - * 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); - } - - @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 if (m instanceof OFBsnControllerConnectionsReply){ - OFBsnControllerConnectionsReply reply = (OFBsnControllerConnectionsReply) m; - handleControllerConnectionMessage(reply); - } else { - unhandledMessageReceived(m); - } - } - - @Override - void processOFRoleReply(OFRoleReply m) { - roleChanger.deliverRoleReply(m.getXid(), m.getRole()); - } - - @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(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()); - // likely supports L2 table extensions. Send set - //TODO @Ryan is this needed? - sendHandshakeL2TableSet(); - // TODO: no L2 SET reply yet, so fire and forget the set - } - - 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); - } 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); - } 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(); - } - - private void sendOFAuxSetCxnsRequest(int numAux) { - OFBsnSetAuxCxnsRequest request = factory.buildBsnSetAuxCxnsRequest() - .setNumAux(numAux) - .build(); - mainConnection.write(request); - } - - public void processOFMessage(OFMessage m) { - state.processOFMessage(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 - // FIXME: We don't set the reassembly flags. - 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.portDescStatsRequest(ImmutableSet.<OFStatsRequestFlags>of())); - } - - /** - * 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); - } - - //TODO @Ryan can we nuke this as well? - /** send a BSN GenTable DescStats Request, querying for the tables - * available at the switch. - * @return - */ - private long sendHandshakeGenTableDescStatsRequest() { - long xid = handshakeTransactionIds--; - OFBsnGentableDescStatsRequest descStatsRequest = factory.buildBsnGentableDescStatsRequest() - .setXid(xid) - .build(); - - mainConnection.write(descStatsRequest); - return xid; - } - - //TODO: Can we nuke this? - jason - /** - * Send an setL2TableSet message to the switch. - */ - private void sendHandshakeL2TableSet() { - // The BsnSetL2Table message is only supported in OF 1.0, so skip if - // we're using a later version - if (factory.getVersion() != OFVersion.OF_10) - return; - - OFBsnSetL2TableRequest m = factory.buildBsnSetL2TableRequest() - .setXid(handshakeTransactionIds--) - .setL2TableEnable(true) - .setL2TablePriority(1000) - .build(); - mainConnection.write(m); - } - - 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()); - this.state.auxConnectionOpened(connection); - } - - /** - * 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 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); - } - } + 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.3 - This is overwritten by the features reply + private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + private final OFFeaturesReply featuresReply; + private final Timer timer; + + 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 + */ + 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 = factory.nextXid(); + 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)) + .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 + */ + @LogMessageDoc(level="WARN", + message="Reasserting master role on switch {SWITCH}, " + + "likely a configruation error with multiple masters", + explanation="The controller keeps getting permission error " + + "from switch, likely due to switch connected to another " + + "controller also in master mode", + recommendation=LogMessageDoc.CHECK_SWITCH) + synchronized void sendRoleRequestIfNotPending(OFControllerRole role) + 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); + 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) 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); + 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) { + 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 { + // 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 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.", + 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()); + } + } + } + } + + /** + * 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) { + // 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(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); + } + + 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); + } + } + + /** + * Log an OpenFlow error message from a switch + * @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(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); + } + } + + /** + * 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; + default: + illegalMessageReceived(m); + 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 + @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(OFGetConfigReply m) { + if (m.getMissSendLen() == 0xffff) { + log.trace("Config Reply from switch {} confirms " + + "miss length set to 0xffff", + 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 {}", + getSwitchInfoString(), + m.getMissSendLen()); + } + setState(new WaitDescriptionStatReplyState()); + } + + @Override + void processOFStatsReply(OFStatsReply m) { + illegalMessageReceived(m); + } + + @Override + void processOFError(OFErrorMsg m) { + 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 intial 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{ + + WaitDescriptionStatReplyState() { + super(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 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()); + switchManager.switchAdded(sw); + // 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); + } + + // Handle pending messages now that we have a sw object + handlePendingPortStatusMessages(description); + + sw.startDriverHandshake(); + if (sw.isDriverHandshakeComplete()) { + setState(new WaitAppHandshakeState()); + } else { + setState(new WaitSwitchDriverSubHandshakeState()); + } + } + + 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(); + } + } + + public class WaitSwitchDriverSubHandshakeState extends OFSwitchHandshakeState { + + WaitSwitchDriverSubHandshakeState() { + super(false); + } + + @Override + void processOFMessage(OFMessage m) { + // FIXME: other message to handle here? + 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()); + } + } + + /** + * 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); + } + + @Override + void enterState() { + setSwitchStatus(SwitchStatus.MASTER); + } + + @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(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 + // 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 + 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 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); + } + } + + /** + * 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); + } + + @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 + @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(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); + } 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); + } 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); + } + + /** + * 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 + // FIXME: We don't set the reassembly flags. + 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.portDescStatsRequest(ImmutableSet.<OFStatsRequestFlags>of())); + } + + /** + * 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); + } + + 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 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); + } + } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java index 46d90645c..09468075d 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java @@ -381,8 +381,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen * It is needed for the rest of the switch handshake. */ @Override - public void connectionOpened(IOFConnectionBackend connection, - OFFeaturesReply featuresReply) { + public void connectionOpened(IOFConnectionBackend connection, OFFeaturesReply featuresReply) { DatapathId dpid = connection.getDatapathId(); OFAuxId auxId = connection.getAuxId(); diff --git a/src/main/java/net/floodlightcontroller/hub/Hub.java b/src/main/java/net/floodlightcontroller/hub/Hub.java index 5de64574d..d2fceca29 100644 --- a/src/main/java/net/floodlightcontroller/hub/Hub.java +++ b/src/main/java/net/floodlightcontroller/hub/Hub.java @@ -32,6 +32,8 @@ import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPacketOut; @@ -39,12 +41,15 @@ import org.projectfloodlight.openflow.protocol.OFType; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.match.Match; import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sun.awt.OrientableFlowLayout; + /** * * @author David Erickson (daviderickson@cs.stanford.edu) - 04/04/10 @@ -67,7 +72,33 @@ public class Hub implements IFloodlightModule, IOFMessageListener { } public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - OFPacketIn pi = (OFPacketIn) msg; + //OFMessage outMessage = createHubPacketOut(sw, msg); + OFMessage outMessage = createHubFlowMod(sw, msg); + sw.write(outMessage); + + return Command.CONTINUE; + } + + private OFMessage createHubFlowMod(IOFSwitch sw, OFMessage msg) { + OFPacketIn pi = (OFPacketIn) msg; + OFFlowAdd.Builder fmb = /*sw.getOFFactory()*/OFFactories.getFactory(OFVersion.OF_13).buildFlowAdd(); + //Match.Builder mb = OFFactories.getFactory(OFVersion.OF_13).buildMatch(); + + fmb.setBufferId(pi.getBufferId()) + .setXid(pi.getXid()) + /*.setMatch(pi.getMatch())*/; + + // set actions + OFActionOutput.Builder actionBuilder = /*sw.getOFFactory()*/OFFactories.getFactory(OFVersion.OF_13).actions().buildOutput(); + actionBuilder.setPort(OFPort.ALL); + fmb.setActions(Collections.singletonList((OFAction) actionBuilder.build())); + fmb.setOutPort(OFPort.ALL); + + return fmb.build(); + } + + private OFMessage createHubPacketOut(IOFSwitch sw, OFMessage msg) { + OFPacketIn pi = (OFPacketIn) msg; OFPacketOut.Builder pob = /*sw.getOFFactory()*/OFFactories.getFactory(OFVersion.OF_13).buildPacketOut(); pob.setBufferId(pi.getBufferId()).setXid(pi.getXid()).setInPort(pi.getMatch().get(MatchField.IN_PORT)); @@ -81,9 +112,7 @@ public class Hub implements IFloodlightModule, IOFMessageListener { byte[] packetData = pi.getData(); pob.setData(packetData); } - sw.write(pob.build()); - - return Command.CONTINUE; + return pob.build(); } @Override diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java b/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java index efa4321b5..22b60197b 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java @@ -30,7 +30,7 @@ public interface IStaticFlowEntryPusherService extends IFloodlightService { * @param fm The flow to push. * @param swDpid The switch DPID to push it to, in 00:00:00:00:00:00:00:01 notation. */ - public void addFlow(String name, OFFlowMod fm, String swDpid); + public void addFlow(String name, OFFlowMod fm, DatapathId swDpid); /** * Deletes a static flow @@ -57,6 +57,6 @@ public interface IStaticFlowEntryPusherService extends IFloodlightService { /** * Gets a list of flows by switch */ - public Map<String, OFFlowMod> getFlows(String dpid); + public Map<String, OFFlowMod> getFlows(DatapathId dpid); } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java index 96bba96e6..f217b5c7f 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java @@ -165,49 +165,50 @@ public class StaticFlowEntries { if ((fm.getActions() != null) && (fm.getActions().size() > 0)) entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, StaticFlowEntries.flowModActionsToString(fm.getActions())); - if (match.get(MatchField.IN_PORT).getPortNumber() != 0) + if ((match.get(MatchField.IN_PORT) != null) && (match.get(MatchField.IN_PORT).getPortNumber() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, Integer.toString(match.get(MatchField.IN_PORT).getPortNumber())); - if (!match.get(MatchField.ETH_SRC).equals(MacAddress.of(0))) + if ((match.get(MatchField.ETH_SRC) != null) && !match.get(MatchField.ETH_SRC).equals(MacAddress.of(0))) entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, match.get(MatchField.ETH_SRC).toString()); - if (!match.get(MatchField.ETH_DST).equals(MacAddress.of(0))) + if ((match.get(MatchField.ETH_DST) != null) && !match.get(MatchField.ETH_DST).equals(MacAddress.of(0))) entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, match.get(MatchField.ETH_DST).toString()); - if (match.get(MatchField.VLAN_VID).getVlan() != -1) + if ((match.get(MatchField.VLAN_VID) != null) && (match.get(MatchField.VLAN_VID).getVlan() != -1)) entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, match.get(MatchField.VLAN_VID).toString()); - if (match.get(MatchField.VLAN_PCP).getValue() != 0) + if ((match.get(MatchField.VLAN_PCP) != null) && (match.get(MatchField.VLAN_PCP).getValue() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, Byte.toString(match.get(MatchField.VLAN_PCP).getValue())); - if (match.get(MatchField.ETH_TYPE).getValue() != 0) + if ((match.get(MatchField.ETH_TYPE) != null) && (match.get(MatchField.ETH_TYPE).getValue() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, match.get(MatchField.ETH_TYPE).toString()); - if (match.get(MatchField.IP_ECN).getEcnValue() != 0 && match.get(MatchField.IP_DSCP).getDscpValue() != 0) + if ((match.get(MatchField.IP_ECN) != null) && (match.get(MatchField.IP_DSCP) != null) + && (match.get(MatchField.IP_ECN).getEcnValue() != 0) && (match.get(MatchField.IP_DSCP).getDscpValue() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, // TOS = [DSCP bits 0-5] + [ECN bits 6-7] --> bitwise OR to get TOS byte Byte.toString((byte) (match.get(MatchField.IP_ECN).getEcnValue() | match.get(MatchField.IP_DSCP).getDscpValue()))); - if (match.get(MatchField.IP_PROTO).getIpProtocolNumber() != 0) + if ((match.get(MatchField.IP_PROTO) != null) && (match.get(MatchField.IP_PROTO).getIpProtocolNumber() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, Short.toString(match.get(MatchField.IP_PROTO).getIpProtocolNumber())); - if (match.get(MatchField.IPV4_SRC).getInt() != 0) + if ((match.get(MatchField.IPV4_SRC) != null) && (match.get(MatchField.IPV4_SRC).getInt() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, match.get(MatchField.IPV4_SRC).toString()); - if (match.get(MatchField.IPV4_DST).getInt() != 0) + if ((match.get(MatchField.IPV4_DST) != null) && (match.get(MatchField.IPV4_DST).getInt() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, match.get(MatchField.IPV4_DST).toString()); - if (match.get(MatchField.TCP_SRC).getPort() != 0) + if ((match.get(MatchField.TCP_SRC) != null) && (match.get(MatchField.TCP_SRC).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.TCP_SRC).toString()); - else if (match.get(MatchField.UDP_SRC).getPort() != 0) + else if ((match.get(MatchField.UDP_SRC) != null) && (match.get(MatchField.UDP_SRC).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.UDP_SRC).toString()); - else if (match.get(MatchField.SCTP_SRC).getPort() != 0) + else if ((match.get(MatchField.SCTP_SRC) != null) && (match.get(MatchField.SCTP_SRC).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.SCTP_SRC).toString()); - if (match.get(MatchField.TCP_DST).getPort() != 0) + if ((match.get(MatchField.TCP_DST) != null) && (match.get(MatchField.TCP_DST).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, match.get(MatchField.TCP_DST).toString()); - else if (match.get(MatchField.UDP_DST).getPort() != 0) + else if ((match.get(MatchField.UDP_DST) != null) && (match.get(MatchField.UDP_DST).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.UDP_DST).toString()); - else if (match.get(MatchField.SCTP_DST).getPort() != 0) + else if ((match.get(MatchField.SCTP_DST) != null) && (match.get(MatchField.SCTP_DST).getPort() != 0)) entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.SCTP_DST).toString()); return entry; diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java index c9a9e5eff..9276da296 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java @@ -50,12 +50,12 @@ import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.StorageException; +import net.floodlightcontroller.util.FlowModUtils; import net.floodlightcontroller.util.MatchString; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFlowDeleteStrict; import org.projectfloodlight.openflow.protocol.OFFlowMod; -import org.projectfloodlight.openflow.protocol.OFFlowModifyStrict; import org.projectfloodlight.openflow.protocol.OFFlowRemoved; import org.projectfloodlight.openflow.protocol.OFFlowRemovedReason; import org.projectfloodlight.openflow.protocol.OFPortDesc; @@ -75,8 +75,7 @@ import org.slf4j.LoggerFactory; * is responsible for ensuring they make sense for the network. */ public class StaticFlowEntryPusher -implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, -IStorageSourceListener, IOFMessageListener { +implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, IStorageSourceListener, IOFMessageListener { protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class); public static final String StaticFlowName = "staticflowentry"; @@ -397,22 +396,21 @@ IStorageSourceListener, IOFMessageListener { List<OFMessage> outQueue = new ArrayList<OFMessage>(); for (String entry : entriesToAdd.get(dpid).keySet()) { - OFFlowModifyStrict newFlowMod = (OFFlowModifyStrict) entriesToAdd.get(dpid).get(entry); //TODO @Ryan cast valid? - //OFFlowMod oldFlowMod = entriesFromStorage.get(dpid).get(entry); - OFFlowDeleteStrict oldFlowMod = null; + OFFlowMod newFlowMod = entriesToAdd.get(dpid).get(entry); + OFFlowMod oldFlowMod = null; String dpidOldFlowMod = entry2dpid.get(entry); if (dpidOldFlowMod != null) { - oldFlowMod = (OFFlowDeleteStrict) entriesFromStorage.get(dpidOldFlowMod).remove(entry); //TODO @Ryan cast valid? + oldFlowMod = entriesFromStorage.get(dpidOldFlowMod).remove(entry); } if (oldFlowMod != null && newFlowMod != null) { // set the new flow mod to modify a pre-existing rule if these fields match if (oldFlowMod.getMatch().equals(newFlowMod.getMatch()) && oldFlowMod.getCookie() == newFlowMod.getCookie() && oldFlowMod.getPriority() == newFlowMod.getPriority()) { - //newFlowMod = newFlowMod.createBuilder().setCommand(OFFlowModCommand.MODIFY_STRICT).build(); + newFlowMod = FlowModUtils.toFlowModifyStrict(newFlowMod); // if they don't match delete the old flow } else { - //oldFlowMod.createBuilder().setCommand(OFFlowModCommand.DELETE_STRICT); + oldFlowMod = FlowModUtils.toFlowDeleteStrict(oldFlowMod); if (dpidOldFlowMod.equals(dpid)) { outQueue.add(oldFlowMod); } else { @@ -468,15 +466,12 @@ IStorageSourceListener, IOFMessageListener { } // send flow_mod delete - OFFlowDeleteStrict flowMod = (OFFlowDeleteStrict) entriesFromStorage.get(dpid).get(entryName); //TODO @Ryan can this be cast? - //flowMod.createBuilder().setCommand(OFFlowModCommand.DELETE_STRICT); // can't find a way to set command + OFFlowDeleteStrict flowMod = FlowModUtils.toFlowDeleteStrict(entriesFromStorage.get(dpid).get(entryName)); - if (entriesFromStorage.containsKey(dpid) && - entriesFromStorage.get(dpid).containsKey(entryName)) { + if (entriesFromStorage.containsKey(dpid) && entriesFromStorage.get(dpid).containsKey(entryName)) { entriesFromStorage.get(dpid).remove(entryName); } else { - log.debug("Tried to delete non-existent entry {} for switch {}", - entryName, dpid); + log.debug("Tried to delete non-existent entry {} for switch {}", entryName, dpid); return; } @@ -500,7 +495,7 @@ IStorageSourceListener, IOFMessageListener { if (log.isDebugEnabled()) { log.debug("Sending {} new entries to {}", messages.size(), dpid); } - ofswitch.write(messages, null); + ofswitch.write(messages); ofswitch.flush(); } } @@ -521,7 +516,7 @@ IStorageSourceListener, IOFMessageListener { if (log.isDebugEnabled()) { log.debug("Sending 1 new entries to {}", dpid.toString()); } - ofswitch.write(message, null); + ofswitch.write(message); ofswitch.flush(); } } @@ -536,8 +531,7 @@ IStorageSourceListener, IOFMessageListener { IOFSwitch ofSwitch = switchService.getSwitch(dpid); if (ofSwitch == null) { if (log.isDebugEnabled()) { - log.debug("Not deleting key {} :: switch {} not connected", - dpid.toString()); + log.debug("Not deleting key {} :: switch {} not connected", dpid.toString()); } return; } @@ -555,7 +549,7 @@ IStorageSourceListener, IOFMessageListener { "static flow to a switch", recommendation=LogMessageDoc.CHECK_SWITCH) private void writeFlowModToSwitch(IOFSwitch sw, OFFlowMod flowMod) { - sw.write(flowMod, null); + sw.write(flowMod); sw.flush(); } @@ -678,8 +672,8 @@ IStorageSourceListener, IOFMessageListener { // IStaticFlowEntryPusherService methods @Override - public void addFlow(String name, OFFlowMod fm, String swDpid) { - Map<String, Object> fmMap = StaticFlowEntries.flowModToStorageEntry(fm, swDpid, name); + public void addFlow(String name, OFFlowMod fm, DatapathId swDpid) { + Map<String, Object> fmMap = StaticFlowEntries.flowModToStorageEntry(fm, swDpid.toString(), name); storageSourceService.insertRowAsync(TABLE_NAME, fmMap); } @@ -803,8 +797,8 @@ IStorageSourceListener, IOFMessageListener { } @Override - public Map<String, OFFlowMod> getFlows(String dpid) { - return entriesFromStorage.get(dpid); + public Map<String, OFFlowMod> getFlows(DatapathId dpid) { + return entriesFromStorage.get(dpid.toString()); } // IHAListener @@ -834,21 +828,19 @@ IStorageSourceListener, IOFMessageListener { @Override public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, String name) { - // TODO Auto-generated method stub return false; } @Override public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, String name) { - // TODO Auto-generated method stub return false; } @Override - public void transitionToStandby() { - // TODO Auto-generated method stub - + public void transitionToStandby() { + log.debug("Controller is now in STANDBY role. Clearing static flow entries from store."); + deleteAllFlows(); } } } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java b/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java index 617088b14..37a39bc86 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java @@ -23,6 +23,7 @@ import net.floodlightcontroller.core.web.ControllerSwitchesResource; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.data.Status; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -48,7 +49,7 @@ public class ListStaticFlowEntriesResource extends ServerResource { try { Map<String, Map<String, OFFlowMod>> retMap = new HashMap<String, Map<String, OFFlowMod>>(); - retMap.put(param, sfpService.getFlows(param)); + retMap.put(param, sfpService.getFlows(DatapathId.of(param))); return retMap; } catch (NumberFormatException e){ diff --git a/src/main/java/net/floodlightcontroller/testmodule/TestModule.java b/src/main/java/net/floodlightcontroller/testmodule/TestModule.java new file mode 100644 index 000000000..b3f27794d --- /dev/null +++ b/src/main/java/net/floodlightcontroller/testmodule/TestModule.java @@ -0,0 +1,81 @@ +package net.floodlightcontroller.testmodule; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.FloodlightModuleException; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; + +public class TestModule implements IFloodlightModule { + + private static IStaticFlowEntryPusherService sfps; + protected static Logger log; + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + return null; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + return null; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IStaticFlowEntryPusherService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + sfps = context.getServiceImpl(IStaticFlowEntryPusherService.class); + log = LoggerFactory.getLogger(TestModule.class); + if (sfps == null) { + log.error("Static Flow Pusher Service not found!"); + } + } + + @Override + public void startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + OFFlowMod.Builder fmb = OFFactories.getFactory(OFVersion.OF_13).buildFlowModify(); + OFActionOutput.Builder aob = OFFactories.getFactory(OFVersion.OF_13).actions().buildOutput(); + List<OFAction> al = new ArrayList<OFAction>(); + al.add(aob.setPort(OFPort.ALL).build()); + fmb.setActions(al); + fmb.setBufferId(OFBufferId.NO_BUFFER); + fmb.setIdleTimeout(10); + fmb.setHardTimeout(60); + fmb.setOutPort(OFPort.ALL); // let's try and mimic the hub module, but with a flow instead + // is this really necessary with the OFOutputAction explicitly set? + + // we aren't matching anything specifically, so all should be wildcarded by default + // in on any port, with any header attributes, send out all ports = Hub module + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + sfps.addFlow("test-flow", fmb.build(), DatapathId.of(1)); // This should add the flow, regardless whether or not the switch is connected, I believe. If it connects in a second, it should be pushed. + } + +} diff --git a/src/main/java/net/floodlightcontroller/util/FlowModUtils.java b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java new file mode 100644 index 000000000..bd17cafdd --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java @@ -0,0 +1,119 @@ +package net.floodlightcontroller.util; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowDeleteStrict; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowModify; +import org.projectfloodlight.openflow.protocol.OFFlowModifyStrict; +import org.projectfloodlight.openflow.protocol.OFVersion; + +/** + * Convert an OFFlowMod to a specific OFFlowMod-OFFlowModCommand. + * These function as setCommand(OFFlowModCommand) methods for an OFFlowMod. + * Used initially in the static flow pusher, but will likely find merit elsewhere. + * + * @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu> + */ +public class FlowModUtils { + public static OFFlowAdd toFlowAdd(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowAdd.Builder b = OFFactories.getFactory(version).buildFlowAdd(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowDelete toFlowDelete(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowDelete.Builder b = OFFactories.getFactory(version).buildFlowDelete(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowDeleteStrict toFlowDeleteStrict(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowDeleteStrict.Builder b = OFFactories.getFactory(version).buildFlowDeleteStrict(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowModify toFlowModify(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowModify.Builder b = OFFactories.getFactory(version).buildFlowModify(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowModifyStrict toFlowModifyStrict(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowModifyStrict.Builder b = OFFactories.getFactory(version).buildFlowModifyStrict(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } +} diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule index dd3bbfd31..673c92bd2 100644 --- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule +++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule @@ -16,4 +16,6 @@ net.floodlightcontroller.threadpool.ThreadPool net.floodlightcontroller.core.internal.ShutdownServiceImpl org.sdnplatform.sync.internal.SyncManager org.sdnplatform.sync.internal.SyncTorture -net.floodlightcontroller.hub.Hub \ No newline at end of file +net.floodlightcontroller.hub.Hub +net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher +net.floodlightcontroller.testmodule.TestModule \ No newline at end of file diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index 1f4417232..c54c7b5c2 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -7,8 +7,9 @@ net.floodlightcontroller.debugcounter.DebugCounterServiceImpl,\ net.floodlightcontroller.restserver.RestApiServer,\ net.floodlightcontroller.perfmon.PktInProcessingTime,\ net.floodlightcontroller.hub.Hub,\ -net.floodlightcontroller.debugevent.DebugEventService +net.floodlightcontroller.debugevent.DebugEventService,\ +net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/auth_credentials.jceks org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/ -net.floodlightcontroller.core.internal.FloodlightProvider.role=MASTER +net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE -- GitLab