diff --git a/build.xml b/build.xml index c94be94c1475fafe689bd3811b2e9fbfade75047..f5f26f3ebae508894b1f87003e8ccd97e260774f 100644 --- a/build.xml +++ b/build.xml @@ -43,7 +43,6 @@ <include name="logback-core-1.0.0.jar"/> <include name="jackson-core-asl-1.8.6.jar"/> <include name="jackson-mapper-asl-1.8.6.jar"/> - <include name="org.easymock_2.5.2.jar"/> <include name="slf4j-api-1.6.4.jar"/> <include name="org.restlet-2.1-RC1.jar"/> <include name="org.restlet.ext.jackson-2.1-RC1.jar"/> @@ -84,7 +83,9 @@ <patternset id="lib-test"> <include name="junit-4.8.2.jar"/> - <include name="org.easymock_2.5.2.jar"/> + <include name="org.easymock-3.1.jar"/> + <include name="objenesis-1.2.jar"/> <!-- required by easymock to mock classes --> + <include name="cglib-nodep-2.2.2.jar"/> <!-- required by easymock to mock classes --> </patternset> <path id="classpath-test"> <fileset dir="lib"> diff --git a/lib/org.easymock_2.5.2.jar b/lib/org.easymock_2.5.2.jar deleted file mode 100644 index 97c8bf0acbb56bfc38845a25ca03f5d98037169e..0000000000000000000000000000000000000000 Binary files a/lib/org.easymock_2.5.2.jar and /dev/null differ diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index 11212ea084e4aefe0cf6f79d581e2809752e3b01..b6188dcd63131bbeb44de0a3d452a83e41bdb27b 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -51,6 +51,7 @@ public interface IOFSwitch { /** * Writes to the OFMessage to the output stream. * The message will be handed to the floodlightProvider for possible filtering + * and processing by message listeners * @param m * @param bc * @throws IOException @@ -60,6 +61,7 @@ public interface IOFSwitch { /** * Writes the list of messages to the output stream * The message will be handed to the floodlightProvider for possible filtering + * and processing by message listeners. * @param msglist * @param bc * @throws IOException @@ -73,7 +75,8 @@ public interface IOFSwitch { public void disconnectOutputStream(); /** - * + * FIXME: remove getChannel(). All access to the channel should be through + * wrapper functions in IOFSwitch * @return */ public Channel getChannel(); @@ -214,12 +217,6 @@ public interface IOFSwitch { */ public Role getRole(); - /** - * Set the role of the controller for the switch - * @param role controller role - */ - public void setRole(Role role); - /** * Check if the controller is an active controller for the switch. * The controller is active if its role is MASTER or EQUAL. diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index d65972029dc260bb919b783bbd95bb1a53e17861..ae2b192dc07e8c6e9516e7b7a10bd3b1c3b433e3 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -24,9 +24,11 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.nio.channels.ClosedChannelException; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -145,7 +147,9 @@ public class Controller implements IFloodlightProviderService, // connectedSwitches contains all connected switches, including ones where // we're a slave controller. We need to keep track of them so that we can // send role request messages to switches when our role changes to master - protected ConcurrentHashMap<Long, IOFSwitch> connectedSwitches; + // We add a switch to this set after it successfully completes the + // handshake. Access to this Set needs to be synchronized with roleChanger + protected HashSet<OFSwitchImpl> connectedSwitches; // The controllerNodeIPsCache maps Controller IDs to their IP address. // It's only used by handleControllerNodeIPsChanged @@ -173,6 +177,8 @@ public class Controller implements IFloodlightProviderService, // The current role of the controller. // If the controller isn't configured to support roles, then this is null. protected Role role; + // A helper that handles sending and timeout handling for role requests + protected RoleChanger roleChanger; // Storage table names protected static final String CONTROLLER_TABLE_NAME = "controller_controller"; @@ -343,128 +349,29 @@ public class Controller implements IFloodlightProviderService, @Override public synchronized void setRole(Role role) { if (role == null) throw new NullPointerException("Role can not be null."); - Role oldRole = this.role; - this.role = role; - // Send role request messages to all of the connected switches. - // FIXME: Should maybe handle this in an asynchronous task. - for (IOFSwitch sw: connectedSwitches.values()) { + // Need to synchronize to ensure a reliable ordering on role request + // messages send and to ensure the list of connected switches is stable + // RoleChanger will handle the actual sending of the message and + // timeout handling + // @see RoleChanger + synchronized(roleChanger) { + Role oldRole = this.role; + this.role = role; + + log.debug("Submitting role change request to role {}", role); + roleChanger.submitRequest(connectedSwitches, role); + + // Enqueue an update for our listeners. try { - sendRoleRequest(sw, role); - } - catch (IOException exc) { - // FIXME: What's the right thing to do in this case? - // Should we try to send it again later? - // Terminate the switch connection? - log.error("Error sending role request message to switch {}", sw); + this.updates.put(new HARoleUpdate(role, oldRole)); + } catch (InterruptedException e) { + log.error("Failure adding update to queue", e); } } - - // Enqueue an update for our listeners. - try { - this.updates.put(new HARoleUpdate(role, oldRole)); - } catch (InterruptedException e) { - log.error("Failure adding update to queue", e); - } } - /** - * Send NX role request message to the switch requesting the specified role. - * @param sw switch to send the role request message to - * @param role role to request - * @return transaction id of the role request message that was sent - */ - protected int sendNxRoleRequest(IOFSwitch sw, Role role) - throws IOException { - // Convert the role enum to the appropriate integer constant used - // in the NX role request message - int nxRole = 0; - switch (role) { - case EQUAL: - nxRole = OFRoleVendorData.NX_ROLE_OTHER; - break; - case MASTER: - nxRole = OFRoleVendorData.NX_ROLE_MASTER; - break; - case SLAVE: - nxRole = OFRoleVendorData.NX_ROLE_SLAVE; - break; - default: - assert false; - } - - // Construct the role request message - OFVendor roleRequest = (OFVendor)factory.getMessage(OFType.VENDOR); - int xid = sw.getNextTransactionId(); - roleRequest.setXid(xid); - roleRequest.setVendor(OFNiciraVendorData.NX_VENDOR_ID); - OFRoleRequestVendorData roleRequestData = new OFRoleRequestVendorData(); - roleRequestData.setRole(nxRole); - roleRequest.setVendorData(roleRequestData); - roleRequest.setLengthU(OFVendor.MINIMUM_LENGTH + - roleRequestData.getLength()); - - // Send it to the switch - sw.write(roleRequest, null); - - return xid; - } - /** - * Send a role request message to the switch. This checks the capabilities - * of the switch for understanding role request messaging. Currently we only - * support the OVS-style role request message, but once the controller - * supports OF 1.2, this function will also handle sending out the - * OF 1.2-style role request message. - * @param sw the switch to send the role request to - * @param role the role to request - * @throws IOException - */ - protected void sendRoleRequest(IOFSwitch sw, Role role) throws IOException { - // There are three cases to consider: - // - // 1) If the controller role at the point the switch connected was - // null/disabled, then we never sent the role request probe to the - // switch and therefore never set the SWITCH_SUPPORTS_NX_ROLE - // attribute for the switch, so supportsNxRole is null. In that - // case since we're now enabling role support for the controller - // we should send out the role request probe/update to the switch. - // - // 2) If supportsNxRole == Boolean.TRUE then that means we've already - // sent the role request probe to the switch and it replied with - // a role reply message, so we know it supports role request - // messages. Now we're changing the role and we want to send - // it another role request message to inform it of the new role - // for the controller. - // - // 3) If supportsNxRole == Boolean.FALSE, then that means we sent the - // role request probe to the switch but it responded with an error - // indicating that it didn't understand the role request message. - // In that case we don't want to send it another role request that - // it (still) doesn't understand. But if the new role of the - // controller is SLAVE, then we don't want the switch to remain - // connected to this controller. It might support the older serial - // failover model for HA support, so we want to terminate the - // connection and get it to initiate a connection with another - // controller in its list of controllers. Eventually (hopefully, if - // things are configured correctly) it will walk down its list of - // controllers and connect to the current master controller. - Boolean supportsNxRole = (Boolean) - sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE); - if ((supportsNxRole == null) || supportsNxRole) { - // Handle cases #1 and #2 - sendNxRoleRequest(sw, role); - } else { - // Handle case #3 - if (getRole() == Role.SLAVE) { - log.error("Disconnecting switch {} that doesn't support " + - "role request messages from a controller that went to SLAVE mode"); - // Closing the channel should result in a call to - // channelDisconnect which updates all state - sw.getChannel().close(); - } - } - } // ********************** // ChannelUpstreamHandler @@ -514,9 +421,15 @@ public class Controller implements IFloodlightProviderService, public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if (sw != null && state.hsState == HandshakeState.READY) { - if (sw.getRole() != Role.SLAVE) + if (activeSwitches.containsKey(sw.getId())) { + // It's safe to call removeSwitch even though the map might + // not contain this particular switch but another with the + // same DPID removeSwitch(sw); - connectedSwitches.remove(sw.getId()); + } + synchronized(roleChanger) { + connectedSwitches.remove(sw); + } sw.setConnected(false); } log.info("Disconnected switch {}", sw); @@ -701,88 +614,64 @@ public class Controller implements IFloodlightProviderService, sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE, dfuture); - // We need to keep track of all of the switches that are connected - // to the controller, in any role, so that we can later send the - // role request messages when the controller role changes. - connectedSwitches.put(sw.getId(), sw); - - // Send a role request if role support is enabled for the controller - // This is a probe that we'll use to determine if the switch - // actually supports the role request message. If it does we'll - // get back a role reply message. If it doesn't we'll get back an - // OFError message. - if (role != null) { - log.debug("This controllers role is:{}, sending role req msg", - role); - state.nxRoleRequestXid = sendNxRoleRequest(sw, role); - } else { - // if role support isn't enabled for the controller, then we're - // not sending the role request probe to the switch - log.info("This controllers role is null - not sending role-" + - "request-msg"); - // The hasNxRole field is just a flag that's checked before - // advancing the handshake state to READY. In this case, - // we set the flag, so we don't need to wait for a - // reply/error before transitioning to the READY state. - state.hasNxRoleReply = true; - } } protected void checkSwitchReady() { - if (state.hasDescription && state.hasGetConfigReply && - state.hasNxRoleReply) { + if (state.hsState == HandshakeState.FEATURES_REPLY && + state.hasDescription && state.hasGetConfigReply) { state.hsState = HandshakeState.READY; - if (getRole() == Role.SLAVE && sw.getRole() == null) { - // When the controller is in the slave role and - // the switch doesn't understand the role request message - - // we disconnect the switch! The expected behavior is that - // the switch will probably try to reconnect repeatedly - // (with some sort of exponential backoff), but after a - // while will give-up and move on to the next controller-IP - // configured on the switch. This is the serial failover - // mechanism from OpenFlow spec v1.0. - log.error("Disconnecting switch from SLAVE controller." + - " Switch {} doesn't support role request messages", - sw.getId()); - sw.setConnected(false); - connectedSwitches.remove(sw.getId()); - sw.getChannel().close(); - } else { - log.info("Switch handshake successful: {}", sw); + synchronized(roleChanger) { + // We need to keep track of all of the switches that are connected + // to the controller, in any role, so that we can later send the + // role request messages when the controller role changes. + // We need to be synchronized while doing this: we must not + // send a another role request to the connectedSwitches until + // we were able to add this new switch to connectedSwitches + // *and* send the current role to the new switch. + connectedSwitches.add(sw); - if (sw.getRole() != Role.SLAVE) { - // Only add the switch to the active switch list if - // we're not in the slave role. Note that if the role - // attribute is null, then that means that the switch - // doesn't support the role request messages, so in that - // case we're effectively in the EQUAL role and the - // switch should be included in the active switch list. + if (role != null) { + // Send a role request if role support is enabled for the controller + // This is a probe that we'll use to determine if the switch + // actually supports the role request message. If it does we'll + // get back a role reply message. If it doesn't we'll get back an + // OFError message. + // If role is MASTER we will promote switch to active + // list when we receive the switch's role reply messages + log.debug("This controller's role is {}, " + + "sending initial role request msg to {}", + role, sw); + Collection<OFSwitchImpl> swList = new ArrayList<OFSwitchImpl>(1); + swList.add(sw); + roleChanger.submitRequest(swList, role); + } + else { + // Role supported not enabled on controller (for now) + // automatically promote switch to active state. + log.debug("This controller's role is null, " + + "not sending role request msg to {}", + role, sw); addSwitch(sw); - - // Delete all pre-existing flows for new connections to - // the master - // FIXME: Need to think more about what the test should - // be for when we flush the flow-table? For example, - // if all the controllers are temporarily in the backup - // role (e.g. right after a failure of the master - // controller) at the point the switch connects, then - // all of the controllers will initially connect as - // backup controllers and not flush the flow-table. - // Then when one of them is promoted to master following - // the master controller election the flow-table - // will still not be flushed because that's treated as - // a failover event where we don't want to flush the - // flow-table. The end result would be that the flow - // table for a newly connected switch is never - // flushed. Not sure how to handle that case though... sw.clearAllFlowMods(); + state.firstRoleReplyReceived = true; } } } } - + + /* Handle a role reply message we received from the switch. Since + * netty serializes message dispatch we don't need to synchronize + * against other receive operations from the same switch, so no need + * to synchronize addSwitch(), removeSwitch() operations from the same + * connection. + * FIXME: However, when a switch with the same DPID connects we do + * need some synchronization. However, handling switches with same + * DPID needs to be revisited anyways (get rid of r/w-lock and synchronous + * removedSwitch notification):1 + * + */ protected void handleRoleReplyMessage(OFVendor vendorMessage, OFRoleReplyVendorData roleReplyVendorData) { // Map from the role code in the message to our role enum @@ -800,38 +689,68 @@ public class Controller implements IFloodlightProviderService, break; default: log.error("Invalid role value in role reply message"); - break; + sw.getChannel().close(); + return; } - log.info("Received NX role reply message; setting role of " + - "controller to {}", role.name()); + log.debug("Handling role reply for role {} from {}. " + + "Controller's role is {} ", + new Object[] { role, sw, Controller.this.role} + ); - sw.setRole(role); + sw.deliverRoleReply(vendorMessage.getXid(), role); - if (state.hsState == HandshakeState.FEATURES_REPLY) { - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - state.hasNxRoleReply = true; - checkSwitchReady(); - } else if (state.hsState == HandshakeState.READY) { - // FIXME: Need to think more about possible synchronization - // issues here. - boolean isActive = activeSwitches.containsKey(sw.getId()); - String roleName = (role != null) ? role.name() : "none"; - log.debug("Handling role reply; switch is {}; role = {}", - new Object[] {isActive ? "active" : "inactive", - roleName}); - if (role == Role.SLAVE) { - if (isActive) { - removeSwitch(sw); - log.debug("Removed slave switch {} from active switch" + - " list", HexString.toHexString(sw.getId())); - } - } else if (!isActive) { - addSwitch(sw); - log.debug("Added master switch {} to active switch list", + boolean isActive = activeSwitches.containsKey(sw.getId()); + if (!isActive && sw.isActive()) { + // Transition from SLAVE to MASTER. + + // Only add the switch to the active switch list if + // we're not in the slave role. Note that if the role + // attribute is null, then that means that the switch + // doesn't support the role request messages, so in that + // case we're effectively in the EQUAL role and the + // switch should be included in the active switch list. + addSwitch(sw); + log.debug("Added master switch {} to active switch list", + HexString.toHexString(sw.getId())); + + if (!state.firstRoleReplyReceived) { + // This is the first role-reply message we receive from + // this switch or roles were disabled when the switch + // connected: + // Delete all pre-existing flows for new connections to + // the master + // + // FIXME: Need to think more about what the test should + // be for when we flush the flow-table? For example, + // if all the controllers are temporarily in the backup + // role (e.g. right after a failure of the master + // controller) at the point the switch connects, then + // all of the controllers will initially connect as + // backup controllers and not flush the flow-table. + // Then when one of them is promoted to master following + // the master controller election the flow-table + // will still not be flushed because that's treated as + // a failover event where we don't want to flush the + // flow-table. The end result would be that the flow + // table for a newly connected switch is never + // flushed. Not sure how to handle that case though... + sw.clearAllFlowMods(); + log.debug("First role reply from master switch {}, " + + "clear FlowTable to active switch list", HexString.toHexString(sw.getId())); } + } + else if (isActive && !sw.isActive()) { + // Transition from MASTER to SLAVE: remove switch + // from active switch list. + log.debug("Removed slave switch {} from active switch" + + " list", HexString.toHexString(sw.getId())); + removeSwitch(sw); } + + // Indicate that we have received a role reply message. + state.firstRoleReplyReceived = true; } protected boolean handleVendorMessage(OFVendor vendorMessage) { @@ -928,24 +847,57 @@ public class Controller implements IFloodlightProviderService, shouldHandleMessage = handleVendorMessage((OFVendor)m); break; case ERROR: + // TODO: we need better error handling. Especially for + // request/reply style message (stats, roles) we should have + // a unified way to lookup the xid in the error message. + // This will probable involve rewriting the way we handle + // request/reply style messages. OFError error = (OFError) m; boolean shouldLogError = true; - if (state.hsState == HandshakeState.FEATURES_REPLY) { - if (error.getXid() == state.nxRoleRequestXid) { - boolean isBadVendorError = - (error.getErrorType() == OFError.OFErrorType. - OFPET_BAD_REQUEST.getValue()) && - (error.getErrorCode() == OFError. - OFBadRequestCode. - OFPBRC_BAD_VENDOR.ordinal()); - // We expect to receive a bad vendor error when - // we're connected to a switch that doesn't support - // the Nicira vendor extensions (i.e. not OVS or - // derived from OVS). So that's not a real error - // case and we don't want to log those spurious errors. - shouldLogError = !isBadVendorError; - - // FIXME: Is this the right thing to do if we receive + // TODO: should we check that firstRoleReplyReceived is false, + // i.e., check only whether the first request fails? + if (sw.checkFirstPendingRoleRequestXid(error.getXid())) { + boolean isBadVendorError = + (error.getErrorType() == OFError.OFErrorType. + OFPET_BAD_REQUEST.getValue()) && + (error.getErrorCode() == OFError. + OFBadRequestCode. + OFPBRC_BAD_VENDOR.ordinal()); + // We expect to receive a bad vendor error when + // we're connected to a switch that doesn't support + // the Nicira vendor extensions (i.e. not OVS or + // derived from OVS). So that's not a real error + // case and we don't want to log those spurious errors. + shouldLogError = !isBadVendorError; + if (isBadVendorError) { + if (!state.firstRoleReplyReceived) { + log.warn("Received ERROR from sw {} that " + +"indicates roles are not supported " + +"but we have received a valid " + +"role reply earlier", sw); + } + state.firstRoleReplyReceived = true; + sw.deliverRoleRequestNotSupported(error.getXid()); + synchronized(roleChanger) { + if (sw.role == null && Controller.this.role==Role.SLAVE) { + // the switch doesn't understand role request + // messages and the current controller role is + // slave. We need to disconnect the switch. + // @see RoleChanger for rationale + sw.getChannel().close(); + } + else if (sw.role == null) { + // Controller's role is master: add to + // active + addSwitch(sw); + // TODO: check if clearing flow table is + // right choice here. + sw.clearAllFlowMods(); + } + } + } + else { + // TODO: Is this the right thing to do if we receive // some other error besides a bad vendor error? // Presumably that means the switch did actually // understand the role request message, but there @@ -955,15 +907,20 @@ public class Controller implements IFloodlightProviderService, // role request has that. Should check OVS source // code to see if it's possible for any other errors // to be returned. - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, - !isBadVendorError); - state.hasNxRoleReply = true; - checkSwitchReady(); + // 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. + sw.getChannel().close(); } - // Once we support OF 1.2, we'd add code to handle it here. - //if (error.getXid() == state.ofRoleRequestXid) { - //} } + // Once we support OF 1.2, we'd add code to handle it here. + //if (error.getXid() == state.ofRoleRequestXid) { + //} if (shouldLogError) logError(sw, error); break; @@ -1006,7 +963,7 @@ public class Controller implements IFloodlightProviderService, // Check if the controller is in the slave role for the // switch. If it is, then don't dispatch the message to // the listeners. - // FIXME: Should we dispatch messages that we expect to + // TODO: Should we dispatch messages that we expect to // receive when we're in the slave role, e.g. port // status messages? Since we're "hiding" switches from // the listeners when we're in the slave role, then it @@ -1232,80 +1189,6 @@ public class Controller implements IFloodlightProviderService, } } - /** - * Add a switch that has completed the initial handshake with the - * controller to the list of connected switches. - * @param sw the switch to connect - */ - /* - protected void connectSwitch(OFSwitchImpl sw) { - // FIXME: robv: I don't think this code is completely thread-safe. I don't - // think it will work correctly if multiple switches with the same DPID - // connect at the same time. I think to handle that we'd need to have - // a controller-wide lock on the switch lists instead of trying to have - // per-switch locks. Having a single lock would reduce concurrency but - OFSwitchImpl oldSw = (OFSwitchImpl) this.connectedSwitches.get(sw.getId()); - if (sw == oldSw) { - // Note == for object equality, not .equals for value - log.info("New switch connection for switch {} that's already connected", sw); - return; - } - - log.info("Switch handshake successful: {}", sw); - - if (oldSw != null) { - if (oldSw.isActive()) - deactivateSwitch(oldSw); - disconnectSwitch(oldSw); - // Close the channel to disconnect the controller from the switch. - // This will eventually trigger a removeSwitch(), which will cause - // a "Not removing Switch ... already removed debug message. - // FIXME: Should be some better way to handle this to avoid spurious - // debug message. - oldSw.getChannel().close(); - } - - sw.getWriteLock().lock(); - try { - connectedSwitches.put(sw.getId(), sw); - sw.setConnected(true); - } - finally { - sw.getWriteLock().unlock(); - } - } - */ - /** - * Remove a switch from the list of connected switches - * @param sw the switch to disconnect - */ - /* - protected void disconnectSwitch(IOFSwitch sw) { - oldSw.getListenerWriteLock().lock(); - try { - log.error("New switch connection {} for already-connected switch {}", - sw, oldSw); - oldSw.setConnected(false); - if (oldSw) - updateInactiveSwitchInfo(oldSw); - - // we need to clean out old switch state definitively - // before adding the new switch - if (switchListeners != null) { - for (IOFSwitchListener listener : switchListeners) { - listener.removedSwitch(oldSw); - } - } - // will eventually trigger a removeSwitch(), which will cause - // a "Not removing Switch ... already removed debug message. - oldSw.getChannel().close(); - } - finally { - oldSw.getWriteLock().unlock(); - } - } - */ - /** * Add a switch to the active switch list and call the switch listeners. * This happens either when a switch first connects (and the controller is @@ -1313,6 +1196,8 @@ public class Controller implements IFloodlightProviderService, * slave to master. * @param sw the switch that has been added */ + // TODO: need to rethink locking and the synchronous switch update. + // We can / should also handle duplicate DPIDs in connectedSwitches protected void addSwitch(IOFSwitch sw) { // TODO: is it safe to modify the HashMap without holding // the old switch's lock? @@ -1875,13 +1760,14 @@ public class Controller implements IFloodlightProviderService, this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); this.haListeners = new CopyOnWriteArraySet<IHAListener>(); this.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); - this.connectedSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); + this.connectedSwitches = new HashSet<OFSwitchImpl>(); this.controllerNodeIPsCache = new HashMap<String, String>(); this.updates = new LinkedBlockingQueue<IUpdate>(); this.factory = new BasicFactory(); this.providerMap = new HashMap<String, List<IInfoProvider>>(); setConfigParams(configParams); this.role = getInitialRole(configParams); + this.roleChanger = new RoleChanger(); initVendorMessages(); } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java index ac6765b6cfa7c0ec21190c9106b98c7a94999041..ad5a3772fa6e47ae6f1356d93455d597b1a36530 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java @@ -39,6 +39,7 @@ class OFChannelState { /** * We've received the features reply + * Waiting for Config and Description reply */ FEATURES_REPLY, @@ -53,11 +54,11 @@ class OFChannelState { protected boolean hasGetConfigReply = false; protected boolean hasDescription = false; - // The hasNxRoleReply flag doesn't mean that the switch supports the NX - // role messages, just that we've received an answer back from the - // switch (possibly a bad vendor error) in response to our initial - // role request. It's used as a flag to indicate that we've met one - // of the conditions necessary to advance the handshake state to READY. - protected boolean hasNxRoleReply = false; - protected int nxRoleRequestXid; + // The firstRoleReplyRecevied flag indicates if we have received the + // first role reply message on this connection (in response to the + // role request sent after the handshake). If role support is disabled + // on the controller we also set this flag to true. + // The flag is used to decide if the flow table should be wiped + // @see Controller.handleRoleReplyMessage() + protected boolean firstRoleReplyReceived = false; } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java index e23b2488191a37f95b29e314d7e4a569f7df86ab..a48fddfba30a76901e7f390360764e16e3504968 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -47,6 +48,7 @@ import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFPort; import org.openflow.protocol.OFType; +import org.openflow.protocol.OFVendor; import org.openflow.protocol.OFPhysicalPort.OFPortConfig; import org.openflow.protocol.OFPhysicalPort.OFPortState; import org.openflow.protocol.OFStatisticsRequest; @@ -54,6 +56,9 @@ import org.openflow.protocol.statistics.OFDescriptionStatistics; import org.openflow.protocol.statistics.OFStatistics; import org.openflow.util.HexString; import org.openflow.util.U16; +import org.openflow.vendor.nicira.OFNiciraVendorData; +import org.openflow.vendor.nicira.OFRoleRequestVendorData; +import org.openflow.vendor.nicira.OFRoleVendorData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,6 +67,8 @@ import org.slf4j.LoggerFactory; * @author David Erickson (daviderickson@cs.stanford.edu) */ public class OFSwitchImpl implements IOFSwitch { + // TODO: should we really do logging in the class or should we throw + // exception that can then be handled by callers? protected static Logger log = LoggerFactory.getLogger(OFSwitchImpl.class); protected ConcurrentMap<Object, Object> attributes; @@ -82,6 +89,17 @@ public class OFSwitchImpl implements IOFSwitch { protected TimedCache<Long> timedCache; protected ReentrantReadWriteLock listenerLock; protected ConcurrentMap<Short, Long> portBroadcastCacheHitMap; + /** + * When sending a role request message, the role request is added + * to this queue. If a role reply is received this queue is checked to + * verify that the reply matches the expected reply. We require in order + * delivery of replies. That's why we use a Queue. + * The RoleChanger uses a timeout to ensure we receive a timely reply. + * + * Need to synchronize on this instance if a request is sent, received, + * checked. + */ + protected LinkedList<PendingRoleRequestEntry> pendingRoleRequests; public static IOFSwitchFeatures switchFeatures; protected static final ThreadLocal<Map<OFSwitchImpl,List<OFMessage>>> local_msg_buffer = @@ -95,6 +113,18 @@ public class OFSwitchImpl implements IOFSwitch { // for managing our map sizes protected static final int MAX_MACS_PER_SWITCH = 1000; + protected static class PendingRoleRequestEntry { + protected int xid; + protected Role role; + // cookie is used to identify the role "generation". roleChanger uses + protected long cookie; + public PendingRoleRequestEntry(int xid, Role role, long cookie) { + this.xid = xid; + this.role = role; + this.cookie = cookie; + } + } + public OFSwitchImpl() { this.attributes = new ConcurrentHashMap<Object, Object>(); this.connectedSince = new Date(); @@ -108,6 +138,7 @@ public class OFSwitchImpl implements IOFSwitch { this.timedCache = new TimedCache<Long>(100, 5*1000 ); // 5 seconds interval this.listenerLock = new ReentrantReadWriteLock(); this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, Long>(); + this.pendingRoleRequests = new LinkedList<OFSwitchImpl.PendingRoleRequestEntry>(); // Defaults properties for an ideal switch this.setAttribute(PROP_FASTWILDCARDS, (Integer) OFMatch.OFPFW_ALL); @@ -148,6 +179,7 @@ public class OFSwitchImpl implements IOFSwitch { this.channel = channel; } + // TODO: document the difference between the different write functions public void write(OFMessage m, FloodlightContext bc) throws IOException { Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); List<OFMessage> msg_buffer = msg_buffer_map.get(this); @@ -373,17 +405,12 @@ public class OFSwitchImpl implements IOFSwitch { } @Override - public synchronized Role getRole() { + public Role getRole() { return role; } @Override - public synchronized void setRole(Role role) { - this.role = role; - } - - @Override - public synchronized boolean isActive() { + public boolean isActive() { return (role != Role.SLAVE); } @@ -473,4 +500,166 @@ public class OFSwitchImpl implements IOFSwitch { public Lock getListenerWriteLock() { return listenerLock.writeLock(); } + + /** + * Send NX role request message to the switch requesting the specified role. + * + * This method should ONLY be called by @see RoleChanger.submitRequest(). + * + * After sending the request add it to the queue of pending request. We + * use the queue to later verify that we indeed receive the correct reply. + * @param sw switch to send the role request message to + * @param role role to request + * @param cookie an opaque value that will be stored in the pending queue so + * RoleChanger can check for timeouts. + * @return transaction id of the role request message that was sent + */ + protected int sendNxRoleRequest(Role role, long cookie) + throws IOException { + synchronized(pendingRoleRequests) { + // Convert the role enum to the appropriate integer constant used + // in the NX role request message + int nxRole = 0; + switch (role) { + case EQUAL: + nxRole = OFRoleVendorData.NX_ROLE_OTHER; + break; + case MASTER: + nxRole = OFRoleVendorData.NX_ROLE_MASTER; + break; + case SLAVE: + nxRole = OFRoleVendorData.NX_ROLE_SLAVE; + break; + default: + log.error("Invalid Role specified for switch {}." + + " Disconnecting.", this); + // TODO: should throw an error + return 0; + } + + // Construct the role request message + OFVendor roleRequest = (OFVendor)floodlightProvider. + getOFMessageFactory().getMessage(OFType.VENDOR); + int xid = this.getNextTransactionId(); + roleRequest.setXid(xid); + roleRequest.setVendor(OFNiciraVendorData.NX_VENDOR_ID); + OFRoleRequestVendorData roleRequestData = new OFRoleRequestVendorData(); + roleRequestData.setRole(nxRole); + roleRequest.setVendorData(roleRequestData); + roleRequest.setLengthU(OFVendor.MINIMUM_LENGTH + + roleRequestData.getLength()); + + // Send it to the switch + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(roleRequest); + // FIXME: should this use this.write() in order for messages to + // be processed by handleOutgoingMessage() + this.channel.write(msglist); + + pendingRoleRequests.add(new PendingRoleRequestEntry(xid, role, cookie)); + return xid; + } + } + + /** + * Deliver a RoleReply message to this switch. Checks if the reply + * message matches the expected reply (head of the pending request queue). + * We require in-order delivery of replies. If there's any deviation from + * our expectations we disconnect the switch. + * + * We must not check the received role against the controller's current + * role because there's no synchronization but that's fine @see RoleChanger + * + * Will be called by the OFChannelHandler's receive loop + * + * @param xid Xid of the reply message + * @param role The Role in the the reply message + */ + protected void deliverRoleReply(int xid, Role role) { + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.poll(); + if (head == null) { + // Maybe don't disconnect if the role reply we received is + // for the same role we are already in. + log.error("Switch {}: received unexpected role reply for Role {}" + + " Disconnecting switch", this, role ); + this.channel.close(); + } + else if (head.xid != xid) { + // check xid before role!! + log.error("Switch {}: expected role reply with " + + "Xid {}, got {}. Disconnecting switch", + new Object[] { this, head.xid, xid } ); + this.channel.close(); + } + else if (head.role != role) { + log.error("Switch {}: expected role reply with " + + "Role {}, got {}. Disconnecting switch", + new Object[] { this, head.role, role } ); + this.channel.close(); + } + else { + log.debug("Received role reply message from {}, setting role to {}", + this, role); + if (this.role == null && getAttribute(SWITCH_SUPPORTS_NX_ROLE) == null) { + // The first role reply we received. Set the attribute + // that the switch supports roles + setAttribute(SWITCH_SUPPORTS_NX_ROLE, true); + } + this.role = role; + } + } + } + + /** + * Checks whether the given xid matches the xid of the first pending + * role request. + * @param xid + * @return + */ + protected boolean checkFirstPendingRoleRequestXid (int xid) { + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.peek(); + if (head == null) + return false; + else + return head.xid == xid; + } + } + + /** + * Checks whether the given request cookie matches the cookie of the first + * pending request + * @param cookie + * @return + */ + protected boolean checkFirstPendingRoleRequestCookie(long cookie) { + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.peek(); + if (head == null) + return false; + else + return head.cookie == cookie; + } + } + + /** + * Called if we receive a vendor error message indicating that roles + * are not supported by the switch. If the xid matches the first pending + * one, we'll mark the switch as not supporting roles and remove the head. + * Otherwise we ignore it. + * @param xid + */ + protected void deliverRoleRequestNotSupported(int xid) { + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.poll(); + this.role = null; + if (head!=null && head.xid == xid) { + setAttribute(SWITCH_SUPPORTS_NX_ROLE, false); + } + else { + this.channel.close(); + } + } + } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java b/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java index 5af9264be63f31bffb138e62c9ca17a706de0ac4..1a79dfce5e732cbe69688822c34fc21201c6a429 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java +++ b/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java @@ -293,7 +293,7 @@ public class RoleChanger { protected void verifyRoleReplyReceived(Collection<OFSwitchImpl> switches, long cookie) { for (OFSwitchImpl sw: switches) { - if (sw.checkFirstPendingRoleReqeustCookie(cookie)) { + if (sw.checkFirstPendingRoleRequestCookie(cookie)) { sw.getChannel().close(); log.warn("Timeout while waiting for role reply from switch {}." + " Disconnecting", sw); diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java index 1b2b7e9a94b0dfb98e30c1b4a1c665999d3f47b2..93a2dcc1213ae433af586665c0a08a6f72e8c01e 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java @@ -19,9 +19,8 @@ package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.*; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -42,6 +41,7 @@ import net.floodlightcontroller.core.IOFMessageListener.Command; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.OFMessageFilterManager; +import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.core.test.MockThreadPoolService; @@ -60,27 +60,30 @@ import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; +import org.easymock.Capture; import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelStateEvent; import org.junit.Test; import org.openflow.protocol.OFError; +import org.openflow.protocol.OFError.OFBadRequestCode; +import org.openflow.protocol.OFError.OFErrorType; import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPacketOut; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFPort; import org.openflow.protocol.OFStatisticsReply; import org.openflow.protocol.OFType; -import org.openflow.protocol.OFError.OFBadRequestCode; -import org.openflow.protocol.OFError.OFErrorType; import org.openflow.protocol.OFPacketIn.OFPacketInReason; +import org.openflow.protocol.OFVendor; import org.openflow.protocol.action.OFAction; import org.openflow.protocol.action.OFActionOutput; import org.openflow.protocol.factory.BasicFactory; import org.openflow.protocol.statistics.OFFlowStatisticsReply; import org.openflow.protocol.statistics.OFStatistics; import org.openflow.protocol.statistics.OFStatisticsType; +import org.openflow.util.HexString; +import org.openflow.vendor.nicira.OFNiciraVendorData; +import org.openflow.vendor.nicira.OFRoleReplyVendorData; /** * @@ -146,6 +149,25 @@ public class ControllerTest extends FloodlightTestCase { return sr; } + /* Set the mock expectations for sw when sw is passed to addSwitch */ + protected void setupSwitchForAddSwitch(IOFSwitch sw, long dpid) { + String dpidString = HexString.toHexString(dpid); + + OFFeaturesReply featuresReply = new OFFeaturesReply(); + featuresReply.setDatapathId(dpid); + featuresReply.setPorts(new ArrayList<OFPhysicalPort>()); + + expect(sw.getId()).andReturn(dpid).anyTimes(); + expect(sw.getStringId()).andReturn(dpidString).anyTimes(); + expect(sw.getConnectedSince()).andReturn(new Date()); + Channel channel = createMock(Channel.class); + expect(sw.getChannel()).andReturn(channel); + expect(channel.getRemoteAddress()).andReturn(null); + + expect(sw.getFeaturesReply()).andReturn(featuresReply).anyTimes(); + expect(sw.getPorts()).andReturn(new HashMap<Short,OFPhysicalPort>()); + } + /** * Run the controller's main loop so that updates are processed */ @@ -675,9 +697,172 @@ public class ControllerTest extends FloodlightTestCase { expectedCurMap, controller.getControllerNodeIPs()); } + @Test + public void testSetRoleNull() { + try { + controller.setRole(null); + fail("Should have thrown an Exception"); + } + catch (NullPointerException e) { + //exptected + } + } + + @Test + public void testSetRole() { + controller.connectedSwitches.add(new OFSwitchImpl()); + RoleChanger roleChanger = createMock(RoleChanger.class); + roleChanger.submitRequest(controller.connectedSwitches, Role.SLAVE); + controller.roleChanger = roleChanger; + + assertEquals("Check that update queue is empty", 0, + controller.updates.size()); + + replay(roleChanger); + controller.setRole(Role.SLAVE); + verify(roleChanger); + + Controller.IUpdate upd = controller.updates.poll(); + assertNotNull("Check that update queue has an update", upd); + assertTrue("Check that update is HARoleUpdate", + upd instanceof Controller.HARoleUpdate); + Controller.HARoleUpdate roleUpd = (Controller.HARoleUpdate)upd; + assertSame(null, roleUpd.oldRole); + assertSame(Role.SLAVE, roleUpd.newRole); + } + + @Test + public void testCheckSwitchReady() { + OFChannelState state = new OFChannelState(); + Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); + chdlr.sw = createMock(OFSwitchImpl.class); + + // Wrong current state + // Should not go to READY + state.hsState = OFChannelState.HandshakeState.HELLO; + state.hasDescription = true; + state.hasGetConfigReply = true; + replay(chdlr.sw); // nothing called on sw + chdlr.checkSwitchReady(); + verify(chdlr.sw); + assertSame(OFChannelState.HandshakeState.HELLO, state.hsState); + reset(chdlr.sw); + + // Have only config reply + state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; + state.hasDescription = false; + state.hasGetConfigReply = true; + replay(chdlr.sw); + chdlr.checkSwitchReady(); + verify(chdlr.sw); + assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState); + assertTrue(controller.connectedSwitches.isEmpty()); + assertTrue(controller.activeSwitches.isEmpty()); + reset(chdlr.sw); + + // Have only desc reply + state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; + state.hasDescription = true; + state.hasGetConfigReply = false; + replay(chdlr.sw); + chdlr.checkSwitchReady(); + verify(chdlr.sw); + assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState); + assertTrue(controller.connectedSwitches.isEmpty()); + assertTrue(controller.activeSwitches.isEmpty()); + reset(chdlr.sw); + + ////////////////////////////////////////// + // Finally, everything is right. Should advance to READY + ////////////////////////////////////////// + controller.roleChanger = createMock(RoleChanger.class); + state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; + state.hasDescription = true; + state.hasGetConfigReply = true; + // Role support disabled. Switch should be promoted to active switch + // list. + setupSwitchForAddSwitch(chdlr.sw, 0L); + chdlr.sw.clearAllFlowMods(); + replay(controller.roleChanger, chdlr.sw); + chdlr.checkSwitchReady(); + verify(controller.roleChanger, chdlr.sw); + assertSame(OFChannelState.HandshakeState.READY, state.hsState); + assertSame(chdlr.sw, controller.activeSwitches.get(0L)); + assertTrue(controller.connectedSwitches.contains(chdlr.sw)); + assertTrue(state.firstRoleReplyReceived); + reset(chdlr.sw); + reset(controller.roleChanger); + controller.connectedSwitches.clear(); + controller.activeSwitches.clear(); + + + // Role support enabled. + state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; + controller.role = Role.MASTER; + Capture<Collection<OFSwitchImpl>> swListCapture = + new Capture<Collection<OFSwitchImpl>>(); + controller.roleChanger.submitRequest(capture(swListCapture), + same(Role.MASTER)); + replay(controller.roleChanger, chdlr.sw); + chdlr.checkSwitchReady(); + verify(controller.roleChanger, chdlr.sw); + assertSame(OFChannelState.HandshakeState.READY, state.hsState); + assertTrue(controller.activeSwitches.isEmpty()); + assertTrue(controller.connectedSwitches.contains(chdlr.sw)); + assertTrue(state.firstRoleReplyReceived); + Collection<OFSwitchImpl> swList = swListCapture.getValue(); + assertEquals(1, swList.size()); + assertTrue("swList must contain this switch", swList.contains(chdlr.sw)); + } + + + @Test + public void testChannelDisconnected() throws Exception { + OFChannelState state = new OFChannelState(); + state.hsState = OFChannelState.HandshakeState.READY; + Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); + chdlr.sw = createMock(OFSwitchImpl.class); + + // Switch is active + expect(chdlr.sw.getId()).andReturn(0L).anyTimes(); + expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:00") + .anyTimes(); + chdlr.sw.cancelAllStatisticsReplies(); + chdlr.sw.setConnected(false); + expect(chdlr.sw.isConnected()).andReturn(true); + + controller.connectedSwitches.add(chdlr.sw); + controller.activeSwitches.put(0L, chdlr.sw); + + replay(chdlr.sw); + chdlr.channelDisconnected(null, null); + verify(chdlr.sw); + + // Switch is connected but not active + reset(chdlr.sw); + expect(chdlr.sw.getId()).andReturn(0L).anyTimes(); + chdlr.sw.setConnected(false); + replay(chdlr.sw); + chdlr.channelDisconnected(null, null); + verify(chdlr.sw); + + // Not in ready state + state.hsState = HandshakeState.START; + reset(chdlr.sw); + replay(chdlr.sw); + chdlr.channelDisconnected(null, null); + verify(chdlr.sw); + + // Switch is null + state.hsState = HandshakeState.READY; + chdlr.sw = null; + chdlr.channelDisconnected(null, null); + } + + /* @Test public void testRoleChangeForSerialFailoverSwitch() throws Exception { - IOFSwitch newsw = createMock(IOFSwitch.class); + OFSwitchImpl newsw = createMock(OFSwitchImpl.class); expect(newsw.getId()).andReturn(0L).anyTimes(); expect(newsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); Channel channel2 = createMock(Channel.class); @@ -688,7 +873,7 @@ public class ControllerTest extends FloodlightTestCase { expect(newsw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) .andReturn(false); // switch is connected - controller.connectedSwitches.put(0L, newsw); + controller.connectedSwitches.add(newsw); // the switch should get disconnected when role is changed to SLAVE expect(channel2.close()).andReturn(null); @@ -697,42 +882,273 @@ public class ControllerTest extends FloodlightTestCase { controller.setRole(Role.SLAVE); verify(newsw, channel2); } + */ @Test - public void testSlaveRoleHandshakeForSerialFailoverSwitch() - throws Exception { - controller.role = Role.SLAVE; - OFFeaturesReply featuresReply = new OFFeaturesReply(); - featuresReply.setDatapathId(0L); - featuresReply.setPorts(new ArrayList<OFPhysicalPort>()); + public void testRoleNotSupportedError() throws Exception { + int xid = 424242; OFChannelState state = new OFChannelState(); - state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; - state.hasDescription = true; - state.hasGetConfigReply = true; + state.hsState = HandshakeState.READY; Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); - + chdlr.sw = createMock(OFSwitchImpl.class); Channel ch = createMock(Channel.class); - ChannelStateEvent e = createMock(ChannelStateEvent.class); - expect(e.getChannel()).andReturn(ch).anyTimes(); - SocketAddress sa = new InetSocketAddress(45454); - expect(ch.getRemoteAddress()).andReturn(sa); - expect(ch.write(anyObject())).andReturn(null); - + // the error returned when role request message is not supported by sw - OFMessage msg = new OFError(); + OFError msg = new OFError(); msg.setType(OFType.ERROR); - ((OFError) msg).setErrorType(OFErrorType.OFPET_BAD_REQUEST); - ((OFError) msg).setErrorCode(OFBadRequestCode.OFPBRC_BAD_VENDOR); + msg.setXid(xid); + msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST); + msg.setErrorCode(OFBadRequestCode.OFPBRC_BAD_VENDOR); // the switch connection should get disconnected when the controller is // in SLAVE mode and the switch does not support role-request messages + state.firstRoleReplyReceived = false; + controller.role = Role.SLAVE; + expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true); + chdlr.sw.deliverRoleRequestNotSupported(xid); + expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes(); expect(ch.close()).andReturn(null); - replay(ch, e); - chdlr.channelConnected(null, e); - chdlr.sw.setFeaturesReply(featuresReply); + replay(ch, chdlr.sw); chdlr.processOFMessage(msg); - verify(ch, e); + verify(ch, chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + state.firstRoleReplyReceived); + assertTrue("activeSwitches must be empty", + controller.activeSwitches.isEmpty()); + reset(ch, chdlr.sw); + // We are MASTER, the switch should be added to the list of active + // switches. + state.firstRoleReplyReceived = false; + controller.role = Role.MASTER; + expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true); + chdlr.sw.deliverRoleRequestNotSupported(xid); + setupSwitchForAddSwitch(chdlr.sw, 0L); + chdlr.sw.clearAllFlowMods(); + replay(ch, chdlr.sw); + + chdlr.processOFMessage(msg); + verify(ch, chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + state.firstRoleReplyReceived); + assertSame("activeSwitches must contain this switch", + chdlr.sw, controller.activeSwitches.get(0L)); + reset(ch, chdlr.sw); + + + // a different error messge + msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST); + msg.setErrorCode(OFBadRequestCode.OFPBRC_EPERM); + state.firstRoleReplyReceived = false; + controller.role = Role.MASTER; + controller.activeSwitches.clear(); + expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true); + expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes(); + expect(ch.close()).andReturn(null); + replay(ch, chdlr.sw); + + chdlr.processOFMessage(msg); + verify(ch, chdlr.sw); + assertFalse("state.firstRoleReplyReceived must be false", + state.firstRoleReplyReceived); + assertTrue("activeSwitches must be empty", + controller.activeSwitches.isEmpty()); + reset(ch, chdlr.sw); } + + + @Test + public void testVendorMessageUnknown() throws Exception { + // Check behavior with an unknown vendor id + OFChannelState state = new OFChannelState(); + state.hsState = HandshakeState.READY; + Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); + OFVendor msg = new OFVendor(); + msg.setVendor(0); + chdlr.processOFMessage(msg); + } + + + // Helper function. + protected Controller.OFChannelHandler getChannelHandlerForRoleReplyTest() { + OFChannelState state = new OFChannelState(); + state.hsState = HandshakeState.READY; + Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); + chdlr.sw = createMock(OFSwitchImpl.class); + return chdlr; + } + + // Helper function + protected OFVendor getRoleReplyMsgForRoleReplyTest(int xid, int nicira_role) { + OFVendor msg = new OFVendor(); + msg.setXid(xid); + msg.setVendor(OFNiciraVendorData.NX_VENDOR_ID); + OFRoleReplyVendorData roleReplyVendorData = + new OFRoleReplyVendorData(OFRoleReplyVendorData.NXT_ROLE_REPLY); + msg.setVendorData(roleReplyVendorData); + roleReplyVendorData.setRole(nicira_role); + return msg; + } + + /** invalid role in role reply */ + @Test + public void testNiciraRoleReplyInvalidRole() + throws Exception { + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + Channel ch = createMock(Channel.class); + expect(chdlr.sw.getChannel()).andReturn(ch); + expect(ch.close()).andReturn(null); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 232323); + replay(chdlr.sw, ch); + chdlr.processOFMessage(msg); + verify(chdlr.sw, ch); + } + + /** First role reply message received: transition from slave to master */ + @Test + public void testNiciraRoleReplySlave2MasterFristTime() + throws Exception { + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_MASTER); + + chdlr.sw.deliverRoleReply(xid, Role.MASTER); + expect(chdlr.sw.isActive()).andReturn(true); + setupSwitchForAddSwitch(chdlr.sw, 1L); + chdlr.sw.clearAllFlowMods(); + chdlr.state.firstRoleReplyReceived = false; + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertSame("activeSwitches must contain this switch", + chdlr.sw, controller.activeSwitches.get(1L)); + } + + + /** Not first role reply message received: transition from slave to master */ + @Test + public void testNiciraRoleReplySlave2MasterNotFristTime() + throws Exception { + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_MASTER); + + chdlr.sw.deliverRoleReply(xid, Role.MASTER); + expect(chdlr.sw.isActive()).andReturn(true); + setupSwitchForAddSwitch(chdlr.sw, 1L); + chdlr.state.firstRoleReplyReceived = true; + // Flow table shouldn't be wipe + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertSame("activeSwitches must contain this switch", + chdlr.sw, controller.activeSwitches.get(1L)); + } + + /** transition from slave to equal */ + @Test + public void testNiciraRoleReplySlave2Equal() + throws Exception { + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_OTHER); + + chdlr.sw.deliverRoleReply(xid, Role.EQUAL); + expect(chdlr.sw.isActive()).andReturn(true); + setupSwitchForAddSwitch(chdlr.sw, 1L); + chdlr.sw.clearAllFlowMods(); + chdlr.state.firstRoleReplyReceived = false; + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertSame("activeSwitches must contain this switch", + chdlr.sw, controller.activeSwitches.get(1L)); + }; + + @Test + /** Slave2Slave transition ==> no change */ + public void testNiciraRoleReplySlave2Slave() throws Exception{ + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_SLAVE); + + chdlr.sw.deliverRoleReply(xid, Role.SLAVE); + expect(chdlr.sw.getId()).andReturn(1L).anyTimes(); + expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01") + .anyTimes(); + expect(chdlr.sw.isActive()).andReturn(false); + // don't add switch to activeSwitches ==> slave2slave + chdlr.state.firstRoleReplyReceived = false; + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertTrue("activeSwitches must be empty", + controller.activeSwitches.isEmpty()); + } + + @Test + /** Equal2Master transition ==> no change */ + public void testNiciraRoleReplyEqual2Master() throws Exception{ + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_MASTER); + + chdlr.sw.deliverRoleReply(xid, Role.MASTER); + expect(chdlr.sw.getId()).andReturn(1L).anyTimes(); + expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01") + .anyTimes(); + expect(chdlr.sw.isActive()).andReturn(true); + controller.activeSwitches.put(1L, chdlr.sw); + chdlr.state.firstRoleReplyReceived = false; + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertSame("activeSwitches must contain this switch", + chdlr.sw, controller.activeSwitches.get(1L)); + } + + @Test + public void testNiciraRoleReplyMaster2Slave() + throws Exception { + int xid = 424242; + Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); + OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, + OFRoleReplyVendorData.NX_ROLE_SLAVE); + + chdlr.sw.deliverRoleReply(xid, Role.SLAVE); + expect(chdlr.sw.getId()).andReturn(1L).anyTimes(); + expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01") + .anyTimes(); + controller.activeSwitches.put(1L, chdlr.sw); + expect(chdlr.sw.isActive()).andReturn(false); + expect(chdlr.sw.isConnected()).andReturn(true); + chdlr.sw.cancelAllStatisticsReplies(); + chdlr.state.firstRoleReplyReceived = false; + replay(chdlr.sw); + chdlr.processOFMessage(msg); + verify(chdlr.sw); + assertTrue("state.firstRoleReplyReceived must be true", + chdlr.state.firstRoleReplyReceived); + assertTrue("activeSwitches must be empty", + controller.activeSwitches.isEmpty()); + } + + } diff --git a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java index 221110d1d4cae249bc5a3ff4400d1b6c10f12829..991afffc282aef54b03076582dc9cafcef6b1dc6 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java @@ -140,13 +140,13 @@ public class RoleChangerTest { // Add a switch that has received a role reply OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); - expect(sw1.checkFirstPendingRoleReqeustCookie(123456)) + expect(sw1.checkFirstPendingRoleRequestCookie(123456)) .andReturn(false).once(); switches.add(sw1); // Add a switch that has not yet received a role reply OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class); - expect(sw2.checkFirstPendingRoleReqeustCookie(123456)) + expect(sw2.checkFirstPendingRoleRequestCookie(123456)) .andReturn(true).once(); Channel channel2 = createMock(Channel.class); expect(sw2.getChannel()).andReturn(channel2); @@ -201,9 +201,9 @@ public class RoleChangerTest { expect(sw1.sendNxRoleRequest(EasyMock.same(Role.SLAVE), EasyMock.anyLong())) .andReturn(1); // The following calls happen for timeout handling: - expect(sw1.checkFirstPendingRoleReqeustCookie(EasyMock.anyLong())) + expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong())) .andReturn(false); - expect(sw1.checkFirstPendingRoleReqeustCookie(EasyMock.anyLong())) + expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong())) .andReturn(false); switches.add(sw1);