diff --git a/build.xml b/build.xml index b141553d79e18362ce9fc1b1a839bf3ea53c12ec..c28b90b5fda6d0782f2c01a5b299142f8b03e901 100644 --- a/build.xml +++ b/build.xml @@ -64,6 +64,7 @@ <include name="concurrentlinkedhashmap-lru-1.2.jar"/> <include name="jython-2.5.2.jar"/> <include name="libthrift-0.7.0.jar"/> + <include name="guava-13.0.1.jar" /> </patternset> <path id="classpath"> diff --git a/lib/guava-13.0.1.jar b/lib/guava-13.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..09c5449115df66fdd2611e2c4f8c362fbb6aaff3 Binary files /dev/null and b/lib/guava-13.0.1.jar differ diff --git a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java index b9a628ba84d16c812d6b7309696eb05251f885f1..e73972ac6fe5f857e7de69fec56b47b79851cdb5 100644 --- a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java +++ b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java @@ -215,5 +215,4 @@ public interface IFloodlightProviderService extends */ public void addOFSwitchDriver(String desc, IOFSwitchDriver driver); - } diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index 68e025b807cc8212a432f343a732714497eadd97..902dfdf5f9055b442f5d917e4ce1dbac4184d130 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -18,6 +18,7 @@ package net.floodlightcontroller.core; import java.io.IOException; +import java.net.SocketAddress; import java.util.Collection; import java.util.Date; import java.util.List; @@ -53,7 +54,57 @@ public interface IOFSwitch { public static final String PROP_SUPPORTS_OFPP_TABLE = "supportsOfppTable"; public static final String PROP_SUPPORTS_OFPP_FLOOD = "supportsOfppFlood"; public static final String PROP_SUPPORTS_NETMASK_TBL = "supportsNetmaskTbl"; - + + public enum OFPortType { + NORMAL("normal"), // normal port (default) + TUNNEL("tunnel"), // tunnel port + UPLINK("uplink"), // uplink port (on a virtual switch) + MANAGEMENT("management"); // for in-band management + + private String value; + OFPortType(String v) { + value = v; + } + + @Override + public String toString() { + return value; + } + + public static OFPortType fromString(String str) { + for (OFPortType m : OFPortType.values()) { + if (m.value.equals(str)) { + return m; + } + } + return OFPortType.NORMAL; + } + } + + /** + * Set IFloodlightProviderService for this switch instance + * Called immediately after instantiation + * + * @param controller + */ + public void setFloodlightProvider(Controller controller); + + /** + * Set IThreadPoolService for this switch instance + * Called immediately after instantiation + * + * @param threadPool + */ + public void setThreadPoolService(IThreadPoolService threadPool); + + /** + * Set the netty Channel this switch instance is associated with + * Called immediately after instantiation + * + * @param channel + */ + public void setChannel(Channel channel); + /** * Writes to the OFMessage to the output stream. * The message will be handed to the floodlightProvider for possible filtering @@ -80,13 +131,6 @@ 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(); - /** * Returns switch features from features Reply * @return @@ -106,12 +150,6 @@ public interface IOFSwitch { */ public void setFeaturesReply(OFFeaturesReply featuresReply); - /** - * Set the SwitchProperties based on it's description - * @param description - */ - public void setSwitchProperties(OFDescriptionStatistics description); - /** * Get list of all enabled ports. This will typically be different from * the list of ports in the OFFeaturesReply, since that one is a static @@ -287,15 +325,19 @@ public interface IOFSwitch { * Get the current role of the controller for the switch * @return the role of the controller */ - public Role getRole(); + public Role getHARole(); /** - * Check if the controller is an active controller for the switch. - * The controller is active if its role is MASTER or EQUAL. - * @return whether the controller is active + * Set switch's HA role to role. The haRoleReplyReceived indicates + * if a reply was received from the switch (error replies excluded). + * + * If role is null, the switch should close the channel connection. + * + * @param role + * @param haRoleReplyReceived */ - public boolean isActive(); - + public void setHARole(Role role, boolean haRoleReplyReceived); + /** * Deliver the statistics future reply * @param reply the reply to deliver @@ -382,36 +424,59 @@ public interface IOFSwitch { public void flush(); /** - * Send HA role request - * - * @param role - * @param cookie + * Return a read lock that must be held while calling the listeners for + * messages from the switch. Holding the read lock prevents the active + * switch list from being modified out from under the listeners. * @return - * @throws IOException */ - public int sendNxRoleRequest(Role role, long cookie) throws IOException; + public Lock getListenerReadLock(); /** - * Check HA role request cookie - * - * @param cookie + * Return a write lock that must be held when the controllers modifies the + * list of active switches. This is to ensure that the active switch list + * doesn't change out from under the listeners as they are handling a + * message from the switch. * @return */ - public boolean checkFirstPendingRoleRequestCookie(long cookie); - - public void setChannel(Channel channel); - - public void setFloodlightProvider(Controller controller); - - public void setThreadPoolService(IThreadPoolService threadPool); + public Lock getListenerWriteLock(); - public void deliverRoleReply(int xid, Role role); + /** + * Get the IP Address for the switch + * @return the inet address + */ + public SocketAddress getInetAddress(); - public void deliverRoleRequestNotSupported(int xid); - public Lock getListenerReadLock(); + /*********************************************** + * The following method can be overridden by + * specific types of switches + *********************************************** + */ + + /** + * Set the SwitchProperties based on it's description + * @param description + */ + public void setSwitchProperties(OFDescriptionStatistics description); - public boolean checkFirstPendingRoleRequestXid(int xid); + /** + * Return the type of OFPort + * @param port_num + * @return + */ + public OFPortType getPortType(short port_num); + + /** + * Can the port be turned on without forming a new loop? + * @param port_num + * @return + */ + public boolean isFastPort(short port_num); - public Lock getListenerWriteLock(); + /** + * Retun a list of uplink port (for virtual switches only) + * @return + */ + public List<Short> getUplinkPorts(); + } diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java index 197642959516a2b2353cdf695d2217efbcd856c1..918ddfb0d4fcc9c95363f3593c0e9cf410268479 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java @@ -6,7 +6,9 @@ public interface IOFSwitchDriver { /** * Return an IOFSwitch object based on switch's manufacturer description * from OFDescriptionStatitics. - * @param description + * @param registered_desc string used to register this driver + * @param description DescriptionStatistics from the switch instance */ - public IOFSwitch getOFSwitchImpl(OFDescriptionStatistics description); + public IOFSwitch getOFSwitchImpl(String registered_desc, + OFDescriptionStatistics description); } diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java new file mode 100644 index 0000000000000000000000000000000000000000..95f20f424e1a8e9b698067ab64a711a9be0a3f09 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java @@ -0,0 +1,651 @@ +/** +* Copyright 2012, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core; + +import java.io.IOException; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFMessageListener; +import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.internal.Controller; +import net.floodlightcontroller.core.internal.OFFeaturesReplyFuture; +import net.floodlightcontroller.core.internal.OFStatisticsFuture; +import net.floodlightcontroller.core.web.serializers.DPIDSerializer; +import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.util.TimedCache; + +import org.codehaus.jackson.annotate.JsonIgnore; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.codehaus.jackson.map.ser.ToStringSerializer; +import org.jboss.netty.channel.Channel; +import org.openflow.protocol.OFFeaturesReply; +import org.openflow.protocol.OFFlowMod; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPhysicalPort; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFType; +import org.openflow.protocol.OFPhysicalPort.OFPortConfig; +import org.openflow.protocol.OFPhysicalPort.OFPortState; +import org.openflow.protocol.OFStatisticsRequest; +import org.openflow.protocol.statistics.OFStatistics; +import org.openflow.util.HexString; +import org.openflow.util.U16; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the internal representation of an openflow switch. + */ +public abstract class OFSwitchBase 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(OFSwitchBase.class); + + protected ConcurrentMap<Object, Object> attributes; + protected IFloodlightProviderService floodlightProvider; + protected IThreadPoolService threadPool; + protected Date connectedSince; + + /* Switch features from initial featuresReply */ + protected int capabilities; + protected int buffers; + protected int actions; + protected byte tables; + protected long datapathId; + protected Role role; + protected String stringId; + + /** + * Members hidden from subclasses + */ + private Channel channel; + private AtomicInteger transactionIdSource; + // Lock to protect modification of the port maps. We only need to + // synchronize on modifications. For read operations we are fine since + // we rely on ConcurrentMaps which works for our use case. + private Object portLock; + // Map port numbers to the appropriate OFPhysicalPort + private ConcurrentHashMap<Short, OFPhysicalPort> portsByNumber; + // Map port names to the appropriate OFPhyiscalPort + // XXX: The OF spec doesn't specify if port names need to be unique but + // according it's always the case in practice. + private ConcurrentHashMap<String, OFPhysicalPort> portsByName; + private Map<Integer,OFStatisticsFuture> statsFutureMap; + private Map<Integer, IOFMessageListener> iofMsgListenersMap; + private Map<Integer,OFFeaturesReplyFuture> featuresFutureMap; + private volatile boolean connected; + private TimedCache<Long> timedCache; + private ReentrantReadWriteLock listenerLock; + private ConcurrentMap<Short, Long> portBroadcastCacheHitMap; + + + protected static ThreadLocal<Map<IOFSwitch,List<OFMessage>>> local_msg_buffer = + new ThreadLocal<Map<IOFSwitch,List<OFMessage>>>() { + @Override + protected Map<IOFSwitch,List<OFMessage>> initialValue() { + return new HashMap<IOFSwitch,List<OFMessage>>(); + } + }; + + // for managing our map sizes + protected static int MAX_MACS_PER_SWITCH = 1000; + + public OFSwitchBase() { + this.stringId = null; + this.attributes = new ConcurrentHashMap<Object, Object>(); + this.connectedSince = new Date(); + this.transactionIdSource = new AtomicInteger(); + this.portLock = new Object(); + this.portsByNumber = new ConcurrentHashMap<Short, OFPhysicalPort>(); + this.portsByName = new ConcurrentHashMap<String, OFPhysicalPort>(); + this.connected = true; + this.statsFutureMap = new ConcurrentHashMap<Integer,OFStatisticsFuture>(); + this.featuresFutureMap = new ConcurrentHashMap<Integer,OFFeaturesReplyFuture>(); + this.iofMsgListenersMap = new ConcurrentHashMap<Integer,IOFMessageListener>(); + this.role = null; + this.timedCache = new TimedCache<Long>(100, 5*1000 ); // 5 seconds interval + this.listenerLock = new ReentrantReadWriteLock(); + this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, Long>(); + + // Defaults properties for an ideal switch + this.setAttribute(PROP_FASTWILDCARDS, OFMatch.OFPFW_ALL); + this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, new Boolean(true)); + this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, new Boolean(true)); + } + + + @Override + public Object getAttribute(String name) { + if (this.attributes.containsKey(name)) { + return this.attributes.get(name); + } + return null; + } + + @Override + public void setAttribute(String name, Object value) { + this.attributes.put(name, value); + return; + } + + @Override + public Object removeAttribute(String name) { + return this.attributes.remove(name); + } + + @Override + public boolean hasAttribute(String name) { + return this.attributes.containsKey(name); + } + + @JsonIgnore + public void setChannel(Channel channel) { + this.channel = channel; + } + + @Override + public void write(OFMessage m, FloodlightContext bc) + throws IOException { + Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); + List<OFMessage> msg_buffer = msg_buffer_map.get(this); + if (msg_buffer == null) { + msg_buffer = new ArrayList<OFMessage>(); + msg_buffer_map.put(this, msg_buffer); + } + + this.floodlightProvider.handleOutgoingMessage(this, m, bc); + msg_buffer.add(m); + + if ((msg_buffer.size() >= Controller.BATCH_MAX_SIZE) || + ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) { + this.write(msg_buffer); + msg_buffer.clear(); + } + } + + @Override + @LogMessageDoc(level="WARN", + message="Sending OF message that modifies switch " + + "state while in the slave role: {switch}", + explanation="An application has sent a message to a switch " + + "that is not valid when the switch is in a slave role", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public void write(List<OFMessage> msglist, + FloodlightContext bc) throws IOException { + for (OFMessage m : msglist) { + if (role == Role.SLAVE) { + switch (m.getType()) { + case PACKET_OUT: + case FLOW_MOD: + case PORT_MOD: + log.warn("Sending OF message that modifies switch " + + "state while in the slave role: {}", + m.getType().name()); + break; + default: + break; + } + } + this.floodlightProvider.handleOutgoingMessage(this, m, bc); + } + this.write(msglist); + } + + private void write(List<OFMessage> msglist) throws IOException { + this.channel.write(msglist); + } + + @Override + public void disconnectOutputStream() { + channel.close(); + } + + @Override + @JsonIgnore + public void setFeaturesReply(OFFeaturesReply featuresReply) { + synchronized(portLock) { + if (stringId == null) { + /* ports are updated via port status message, so we + * only fill in ports on initial connection. + */ + for (OFPhysicalPort port : featuresReply.getPorts()) { + setPort(port); + } + } + this.datapathId = featuresReply.getDatapathId(); + this.capabilities = featuresReply.getCapabilities(); + this.buffers = featuresReply.getBuffers(); + this.actions = featuresReply.getActions(); + this.tables = featuresReply.getTables(); + this.stringId = HexString.toHexString(this.datapathId); + } + } + + @Override + @JsonIgnore + public Collection<OFPhysicalPort> getEnabledPorts() { + List<OFPhysicalPort> result = new ArrayList<OFPhysicalPort>(); + for (OFPhysicalPort port : portsByNumber.values()) { + if (portEnabled(port)) { + result.add(port); + } + } + return result; + } + + @Override + @JsonIgnore + public Collection<Short> getEnabledPortNumbers() { + List<Short> result = new ArrayList<Short>(); + for (OFPhysicalPort port : portsByNumber.values()) { + if (portEnabled(port)) { + result.add(port.getPortNumber()); + } + } + return result; + } + + @Override + public OFPhysicalPort getPort(short portNumber) { + return portsByNumber.get(portNumber); + } + + @Override + public OFPhysicalPort getPort(String portName) { + return portsByName.get(portName); + } + + @Override + @JsonIgnore + public void setPort(OFPhysicalPort port) { + synchronized(portLock) { + portsByNumber.put(port.getPortNumber(), port); + portsByName.put(port.getName(), port); + } + } + + @Override + @JsonProperty("ports") + public Collection<OFPhysicalPort> getPorts() { + return Collections.unmodifiableCollection(portsByNumber.values()); + } + + @Override + public void deletePort(short portNumber) { + synchronized(portLock) { + portsByName.remove(portsByNumber.get(portNumber).getName()); + portsByNumber.remove(portNumber); + } + } + + @Override + public void deletePort(String portName) { + synchronized(portLock) { + portsByNumber.remove(portsByName.get(portName).getPortNumber()); + portsByName.remove(portName); + } + } + + @Override + public boolean portEnabled(short portNumber) { + if (portsByNumber.get(portNumber) == null) return false; + return portEnabled(portsByNumber.get(portNumber)); + } + + @Override + public boolean portEnabled(String portName) { + if (portsByName.get(portName) == null) return false; + return portEnabled(portsByName.get(portName)); + } + + @Override + public boolean portEnabled(OFPhysicalPort port) { + if (port == null) + return false; + if ((port.getConfig() & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0) + return false; + if ((port.getState() & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0) + return false; + // Port STP state doesn't work with multiple VLANs, so ignore it for now + //if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue()) + // return false; + return true; + } + + @Override + @JsonSerialize(using=DPIDSerializer.class) + @JsonProperty("dpid") + public long getId() { + if (this.stringId == null) + throw new RuntimeException("Features reply has not yet been set"); + return this.datapathId; + } + + @JsonIgnore + @Override + public String getStringId() { + return stringId; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "OFSwitchBase [" + channel.getRemoteAddress() + " DPID[" + ((stringId != null) ? stringId : "?") + "]]"; + } + + @Override + public ConcurrentMap<Object, Object> getAttributes() { + return this.attributes; + } + + @Override + public Date getConnectedSince() { + return connectedSince; + } + + @JsonIgnore + @Override + public int getNextTransactionId() { + return this.transactionIdSource.incrementAndGet(); + } + + @Override + public void sendStatsQuery(OFStatisticsRequest request, int xid, + IOFMessageListener caller) throws IOException { + request.setXid(xid); + this.iofMsgListenersMap.put(xid, caller); + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(request); + this.channel.write(msglist); + return; + } + + @Override + public Future<List<OFStatistics>> getStatistics(OFStatisticsRequest request) throws IOException { + request.setXid(getNextTransactionId()); + OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this, request.getXid()); + this.statsFutureMap.put(request.getXid(), future); + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(request); + this.channel.write(msglist); + return future; + } + + @Override + public void deliverStatisticsReply(OFMessage reply) { + OFStatisticsFuture future = this.statsFutureMap.get(reply.getXid()); + if (future != null) { + future.deliverFuture(this, reply); + // The future will ultimately unregister itself and call + // cancelStatisticsReply + return; + } + /* Transaction id was not found in statsFutureMap.check the other map */ + IOFMessageListener caller = this.iofMsgListenersMap.get(reply.getXid()); + if (caller != null) { + caller.receive(this, reply, null); + } + } + + @Override + public void cancelStatisticsReply(int transactionId) { + if (null == this.statsFutureMap.remove(transactionId)) { + this.iofMsgListenersMap.remove(transactionId); + } + } + + @Override + public void cancelAllStatisticsReplies() { + /* we don't need to be synchronized here. Even if another thread + * modifies the map while we're cleaning up the future will eventuall + * timeout */ + for (OFStatisticsFuture f : statsFutureMap.values()) { + f.cancel(true); + } + statsFutureMap.clear(); + iofMsgListenersMap.clear(); + } + + + /** + * @param floodlightProvider the floodlightProvider to set + */ + @JsonIgnore + public void setFloodlightProvider( + IFloodlightProviderService floodlightProvider) { + this.floodlightProvider = floodlightProvider; + } + + @JsonIgnore + public void setThreadPoolService(IThreadPoolService tp) { + this.threadPool = tp; + } + + @JsonIgnore + @Override + public boolean isConnected() { + // No lock needed since we use volatile + return connected; + } + + @Override + @JsonIgnore + public void setConnected(boolean connected) { + // No lock needed since we use volatile + this.connected = connected; + } + + @Override + public Role getHARole() { + return role; + } + + @JsonIgnore + @Override + public void setHARole(Role role, boolean replyReceived) { + 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, replyReceived); + } + this.role = role; + } + + @Override + @LogMessageDoc(level="ERROR", + message="Failed to clear all flows on switch {switch}", + explanation="An I/O error occured while trying to clear " + + "flows on the switch.", + recommendation=LogMessageDoc.CHECK_SWITCH) + public void clearAllFlowMods() { + // Delete all pre-existing flows + OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() + .getMessage(OFType.FLOW_MOD)) + .setMatch(match) + .setCommand(OFFlowMod.OFPFC_DELETE) + .setOutPort(OFPort.OFPP_NONE) + .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + try { + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(fm); + channel.write(msglist); + } catch (Exception e) { + log.error("Failed to clear all flows on switch " + this, e); + } + } + + @Override + public boolean updateBroadcastCache(Long entry, Short port) { + if (timedCache.update(entry)) { + Long count = portBroadcastCacheHitMap.putIfAbsent(port, new Long(1)); + if (count != null) { + count++; + } + return true; + } else { + return false; + } + } + + @Override + @JsonIgnore + public Map<Short, Long> getPortBroadcastHits() { + return this.portBroadcastCacheHitMap; + } + + + @Override + public void flush() { + Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); + List<OFMessage> msglist = msg_buffer_map.get(this); + if ((msglist != null) && (msglist.size() > 0)) { + try { + this.write(msglist); + } catch (IOException e) { + // TODO: log exception + e.printStackTrace(); + } + msglist.clear(); + } + } + + public static void flush_all() { + Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); + for (IOFSwitch sw : msg_buffer_map.keySet()) { + sw.flush(); + } + } + + /** + * Return a read lock that must be held while calling the listeners for + * messages from the switch. Holding the read lock prevents the active + * switch list from being modified out from under the listeners. + * @return + */ + @JsonIgnore + public Lock getListenerReadLock() { + return listenerLock.readLock(); + } + + /** + * Return a write lock that must be held when the controllers modifies the + * list of active switches. This is to ensure that the active switch list + * doesn't change out from under the listeners as they are handling a + * message from the switch. + * @return + */ + @JsonIgnore + public Lock getListenerWriteLock() { + return listenerLock.writeLock(); + } + + /** + * Get the IP Address for the switch + * @return the inet address + */ + @JsonSerialize(using=ToStringSerializer.class) + public SocketAddress getInetAddress() { + return channel.getRemoteAddress(); + } + + @Override + public Future<OFFeaturesReply> querySwitchFeaturesReply() + throws IOException { + OFMessage request = + floodlightProvider.getOFMessageFactory(). + getMessage(OFType.FEATURES_REQUEST); + request.setXid(getNextTransactionId()); + OFFeaturesReplyFuture future = + new OFFeaturesReplyFuture(threadPool, this, request.getXid()); + this.featuresFutureMap.put(request.getXid(), future); + List<OFMessage> msglist = new ArrayList<OFMessage>(1); + msglist.add(request); + this.channel.write(msglist); + return future; + } + + @Override + public void deliverOFFeaturesReply(OFMessage reply) { + OFFeaturesReplyFuture future = this.featuresFutureMap.get(reply.getXid()); + if (future != null) { + future.deliverFuture(this, reply); + // The future will ultimately unregister itself and call + // cancelFeaturesReply + return; + } + log.error("Switch {}: received unexpected featureReply", this); + } + + @Override + public void cancelFeaturesReply(int transactionId) { + this.featuresFutureMap.remove(transactionId); + } + + + @Override + public int getBuffers() { + return buffers; + } + + + @Override + public int getActions() { + return actions; + } + + + @Override + public int getCapabilities() { + return capabilities; + } + + + @Override + public byte getTables() { + return tables; + } + + + @Override + public void setFloodlightProvider(Controller controller) { + floodlightProvider = controller; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java b/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java index c703687a1d92362638b0e77098d9a47f9e27d2df..08b48dc849009d62de8f04a6d948eac8f36dc39f 100644 --- a/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java +++ b/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java @@ -35,6 +35,10 @@ public @interface LogMessageDoc { public static final String CHECK_SWITCH = "Check the health of the indicated switch. " + "Test and troubleshoot IP connectivity."; + public static final String HA_CHECK_SWITCH = + "Check the health of the indicated switch. If the problem " + + "persists or occurs repeatedly, it likely indicates a defect " + + "in the switch HA implementation."; public static final String CHECK_CONTROLLER = "Verify controller system health, CPU usage, and memory. " + "Rebooting the controller node may help if the controller " + diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index cfe1d85bb60a96ef5e60ec732fd7b1953560c328..b7b048874a874b508e327bd87998832898f2c93f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -20,8 +20,8 @@ package net.floodlightcontroller.core.internal; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.nio.channels.ClosedChannelException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -41,16 +41,18 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; + import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; -import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IListener.Command; +import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchDriver; import net.floodlightcontroller.core.IOFSwitchFilter; import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.OFSwitchBase; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState; @@ -113,14 +115,10 @@ import org.openflow.protocol.factory.MessageParseException; import org.openflow.protocol.statistics.OFDescriptionStatistics; import org.openflow.protocol.statistics.OFStatistics; import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.protocol.vendor.OFBasicVendorDataType; -import org.openflow.protocol.vendor.OFBasicVendorId; -import org.openflow.protocol.vendor.OFVendorId; import org.openflow.util.HexString; import org.openflow.vendor.nicira.OFNiciraVendorData; +import org.openflow.vendor.nicira.OFNiciraVendorExtensions; import org.openflow.vendor.nicira.OFRoleReplyVendorData; -import org.openflow.vendor.nicira.OFRoleRequestVendorData; -import org.openflow.vendor.nicira.OFRoleVendorData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -128,22 +126,22 @@ import org.slf4j.LoggerFactory; /** * The main controller class. Handles all setup and network listeners */ -public class Controller implements IFloodlightProviderService, +public class Controller implements IFloodlightProviderService, IStorageSourceListener { - + protected static Logger log = LoggerFactory.getLogger(Controller.class); - private static final String ERROR_DATABASE = + private static final String ERROR_DATABASE = "The controller could not communicate with the system database."; - + protected BasicFactory factory; protected ConcurrentMap<OFType, - ListenerDispatcher<OFType,IOFMessageListener>> + ListenerDispatcher<OFType,IOFMessageListener>> messageListeners; // OFSwitch driver binding map and order protected Map<String, IOFSwitchDriver>switchBindingMap; protected List<String> switchDescSortedList; - + // The activeSwitches map contains only those switches that are actively // being controlled by us -- it doesn't contain switches that are // in the slave role @@ -154,16 +152,16 @@ public class Controller implements IFloodlightProviderService, // 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<IOFSwitch> connectedSwitches; - - // The controllerNodeIPsCache maps Controller IDs to their IP address. + + // The controllerNodeIPsCache maps Controller IDs to their IP address. // It's only used by handleControllerNodeIPsChanged protected HashMap<String, String> controllerNodeIPsCache; - + protected Set<IOFSwitchListener> switchListeners; protected Set<IHAListener> haListeners; protected Map<String, List<IInfoProvider>> providerMap; protected BlockingQueue<IUpdate> updates; - + // Module dependencies protected IRestApiService restApi; protected ICounterStoreService counterStore = null; @@ -171,43 +169,43 @@ public class Controller implements IFloodlightProviderService, protected IStorageSourceService storageSource; protected IPktInProcessingTimeService pktinProcTime; protected IThreadPoolService threadPool; - + // Configuration options protected int openFlowPort = 6633; protected int workerThreads = 0; // The id for this controller node. Should be unique for each controller // node in a controller cluster. protected String controllerId = "localhost"; - + // 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; - + // Start time of the controller protected long systemStartTime; - + // Flag to always flush flow table on switch reconnect (HA or otherwise) protected boolean alwaysClearFlowsOnSwAdd = false; - + // Storage table names protected static final String CONTROLLER_TABLE_NAME = "controller_controller"; protected static final String CONTROLLER_ID = "id"; - + protected static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig"; protected static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch"; - + protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface"; protected static final String CONTROLLER_INTERFACE_ID = "id"; protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id"; protected static final String CONTROLLER_INTERFACE_TYPE = "type"; protected static final String CONTROLLER_INTERFACE_NUMBER = "number"; protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip"; - + // Perf. related configuration protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024; - protected static final int BATCH_MAX_SIZE = 100; + public static final int BATCH_MAX_SIZE = 100; protected static final boolean ALWAYS_DECODE_ETH = true; // Load monitor for overload protection @@ -216,10 +214,10 @@ public class Controller implements IFloodlightProviderService, protected final LoadMonitor loadmonitor = new LoadMonitor(log); /** - * Updates handled by the main loop + * Updates handled by the main loop */ protected interface IUpdate { - /** + /** * Calls the appropriate listeners */ public void dispatch(); @@ -230,7 +228,7 @@ public class Controller implements IFloodlightProviderService, PORTCHANGED } /** - * Update message indicating a switch was added or removed + * Update message indicating a switch was added or removed */ protected class SwitchUpdate implements IUpdate { public IOFSwitch sw; @@ -239,6 +237,7 @@ public class Controller implements IFloodlightProviderService, this.sw = sw; this.switchUpdateType = switchUpdateType; } + @Override public void dispatch() { if (log.isTraceEnabled()) { log.trace("Dispatching switch update {} {}", @@ -261,7 +260,7 @@ public class Controller implements IFloodlightProviderService, } } } - + /** * Update message indicating controller's role has changed */ @@ -272,6 +271,7 @@ public class Controller implements IFloodlightProviderService, this.oldRole = oldRole; this.newRole = newRole; } + @Override public void dispatch() { // Make sure that old and new roles are different. if (oldRole == newRole) { @@ -293,7 +293,7 @@ public class Controller implements IFloodlightProviderService, } } } - + /** * Update message indicating * IPs of controllers in controller cluster have changed. @@ -303,13 +303,14 @@ public class Controller implements IFloodlightProviderService, public Map<String,String> addedControllerNodeIPs; public Map<String,String> removedControllerNodeIPs; public HAControllerNodeIPUpdate( - HashMap<String,String> curControllerNodeIPs, - HashMap<String,String> addedControllerNodeIPs, + HashMap<String,String> curControllerNodeIPs, + HashMap<String,String> addedControllerNodeIPs, HashMap<String,String> removedControllerNodeIPs) { this.curControllerNodeIPs = curControllerNodeIPs; this.addedControllerNodeIPs = addedControllerNodeIPs; this.removedControllerNodeIPs = removedControllerNodeIPs; } + @Override public void dispatch() { if (log.isTraceEnabled()) { log.trace("Dispatching HA Controller Node IP update " @@ -326,31 +327,31 @@ public class Controller implements IFloodlightProviderService, } } } - + // *************** // Getters/Setters // *************** - + public void setStorageSourceService(IStorageSourceService storageSource) { this.storageSource = storageSource; } - + public void setCounterStore(ICounterStoreService counterStore) { this.counterStore = counterStore; } - + public void setFlowCacheMgr(IFlowCacheService flowCacheMgr) { this.bigFlowCacheMgr = flowCacheMgr; } - + public void setPktInProcessingService(IPktInProcessingTimeService pits) { this.pktinProcTime = pits; } - + public void setRestApiService(IRestApiService restApi) { this.restApi = restApi; } - + public void setThreadPoolService(IThreadPoolService tp) { this.threadPool = tp; } @@ -361,14 +362,14 @@ public class Controller implements IFloodlightProviderService, return role; } } - + @Override public void setRole(Role role) { if (role == null) throw new NullPointerException("Role can not be null."); - + // 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 + // RoleChanger will handle the actual sending of the message and // timeout handling // @see RoleChanger synchronized(roleChanger) { @@ -379,10 +380,10 @@ public class Controller implements IFloodlightProviderService, 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 { this.updates.put(new HARoleUpdate(role, oldRole)); @@ -391,13 +392,13 @@ public class Controller implements IFloodlightProviderService, } } } - - - + + + // ********************** // ChannelUpstreamHandler // ********************** - + /** * Return a new channel handler for processing a switch connections * @param state The channel state object for the connection @@ -406,28 +407,28 @@ public class Controller implements IFloodlightProviderService, protected ChannelUpstreamHandler getChannelHandler(OFChannelState state) { return new OFChannelHandler(state); } - + /** * Channel handler deals with the switch connection and dispatches * switch messages to the appropriate locations. * @author readams */ - protected class OFChannelHandler + protected class OFChannelHandler extends IdleStateAwareChannelUpstreamHandler { protected IOFSwitch sw; protected Channel channel; protected OFChannelState state; - + public OFChannelHandler(OFChannelState state) { this.state = state; } @Override @LogMessageDoc(message="New switch connection from {ip address}", - explanation="A new switch has connected from the " + + explanation="A new switch has connected from the " + "specified IP address") public void channelConnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + ChannelStateEvent e) throws Exception { channel = e.getChannel(); log.info("New switch connection from {}", channel.getRemoteAddress()); @@ -442,12 +443,13 @@ public class Controller implements IFloodlightProviderService, if (sw != null && state.hsState == HandshakeState.READY) { 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 + // not contain this particular switch but another with the // same DPID removeSwitch(sw); } synchronized(roleChanger) { connectedSwitches.remove(sw); + roleChanger.removePendingRequests(sw); } sw.setConnected(false); } @@ -458,13 +460,13 @@ public class Controller implements IFloodlightProviderService, @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="Disconnecting switch {switch} due to read timeout", - explanation="The connected switch has failed to send any " + + 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 " + + message="Disconnecting switch {switch}: failed to " + "complete handshake", - explanation="The switch did not respond correctly " + + explanation="The switch did not respond correctly " + "to handshake messages", recommendation=LogMessageDoc.CHECK_SWITCH), @LogMessageDoc(level="ERROR", @@ -472,7 +474,7 @@ public class Controller implements IFloodlightProviderService, explanation="There was an error communicating with the switch", recommendation=LogMessageDoc.CHECK_SWITCH), @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch} due to switch " + + message="Disconnecting switch {switch} due to switch " + "state error: {error}", explanation="The switch sent an unexpected message", recommendation=LogMessageDoc.CHECK_SWITCH), @@ -503,7 +505,7 @@ public class Controller implements IFloodlightProviderService, log.error("Disconnecting switch {} due to read timeout", sw); ctx.getChannel().close(); } else if (e.getCause() instanceof HandshakeTimeoutException) { - log.error("Disconnecting switch {}: failed to complete handshake", + log.error("Disconnecting switch {}: failed to complete handshake", sw); ctx.getChannel().close(); } else if (e.getCause() instanceof ClosedChannelException) { @@ -513,16 +515,16 @@ public class Controller implements IFloodlightProviderService, sw, e.getCause().getMessage()); ctx.getChannel().close(); } else if (e.getCause() instanceof SwitchStateException) { - log.error("Disconnecting switch {} due to switch state error: {}", + log.error("Disconnecting switch {} due to switch state error: {}", sw, e.getCause().getMessage()); ctx.getChannel().close(); } else if (e.getCause() instanceof MessageParseException) { log.error("Disconnecting switch " + sw + - " due to message parse failure", + " due to message parse failure", e.getCause()); ctx.getChannel().close(); } else if (e.getCause() instanceof StorageException) { - log.error("Terminating controller due to storage exception", + log.error("Terminating controller due to storage exception", e.getCause()); terminate(); } else if (e.getCause() instanceof RejectedExecutionException) { @@ -605,8 +607,8 @@ public class Controller implements IFloodlightProviderService, } catch (Exception ex) { - // We are the last handler in the stream, so run the - // exception through the channel again by passing in + // 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); } @@ -624,26 +626,26 @@ public class Controller implements IFloodlightProviderService, } // Flush all flow-mods/packet-out/stats generated from this "train" - OFSwitchImpl.flush_all(); + OFSwitchBase.flush_all(); counterStore.updateFlush(); bigFlowCacheMgr.updateFlush(); } } - + /** * Process the request for the switch description */ @LogMessageDoc(level="ERROR", - message="Exception in reading description " + + message="Exception in reading description " + " during handshake {exception}", explanation="Could not process the switch description string", recommendation=LogMessageDoc.CHECK_SWITCH) void processSwitchDescReply(OFStatisticsReply m) { try { // Read description, if it has been updated - OFDescriptionStatistics description = + OFDescriptionStatistics description = new OFDescriptionStatistics(); - ChannelBuffer data = + ChannelBuffer data = ChannelBuffers.buffer(description.getLength()); OFStatistics f = m.getFirstStatistics(); f.writeTo(data); @@ -653,7 +655,7 @@ public class Controller implements IFloodlightProviderService, checkSwitchReady(); } catch (Exception ex) { - log.error("Exception in reading description " + + log.error("Exception in reading description " + " during handshake", ex); } } @@ -669,7 +671,7 @@ public class Controller implements IFloodlightProviderService, msglist.add(factory.getMessage(type)); channel.write(msglist); } - + /** * Send the configuration requests we can only do after we have * the features reply @@ -685,7 +687,7 @@ public class Controller implements IFloodlightProviderService, .setLengthU(OFSwitchConfig.MINIMUM_LENGTH); configSet.setXid(-4); msglist.add(configSet); - + // Verify (need barrier?) OFGetConfigRequest configReq = (OFGetConfigRequest) factory.getMessage(OFType.GET_CONFIG_REQUEST); @@ -698,10 +700,10 @@ public class Controller implements IFloodlightProviderService, req.setXid(-2); // something "large" req.setLengthU(req.getLengthU()); msglist.add(req); - + channel.write(msglist); } - + protected void checkSwitchReady() { if (!state.switchBindingDone) { bindSwitchToDriver(); @@ -709,49 +711,47 @@ public class Controller implements IFloodlightProviderService, if (state.hsState == HandshakeState.FEATURES_REPLY && state.switchBindingDone) { - + state.hsState = HandshakeState.READY; - + + // replay queued port status messages + for (OFMessage m : state.queuedOFMessages) { + try { + processOFMessage(m); + } catch (Exception e) { + log.error("Failed to process delayed OFMessage {} {}", + m, e.getCause()); + } + } + + state.queuedOFMessages.clear(); 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 + // 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 + // 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 + // we were able to add this new switch to connectedSwitches // *and* send the current role to the new switch. connectedSwitches.add(sw); - - 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<IOFSwitch> swList = new ArrayList<IOFSwitch>(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); - // Need to clear FlowMods before we add the switch - // and dispatch updates otherwise we have a race condition. - addSwitch(sw, true); - state.firstRoleReplyReceived = true; - } + + // Send a role request. + // 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<IOFSwitch> swList = new ArrayList<IOFSwitch>(1); + swList.add(sw); + roleChanger.submitRequest(swList, role); } } } - + protected void bindSwitchToDriver() { if (!state.hasGetConfigReply) { log.debug("Waiting for config reply from switch {}", @@ -763,12 +763,12 @@ public class Controller implements IFloodlightProviderService, channel.getRemoteAddress()); return; } - + for (String desc : switchDescSortedList) { if (state.description.getManufacturerDescription() .startsWith(desc)) { sw = switchBindingMap.get(desc) - .getOFSwitchImpl(state.description); + .getOFSwitchImpl(desc, state.description); if (sw != null) { break; } @@ -783,40 +783,38 @@ public class Controller implements IFloodlightProviderService, sw.setFloodlightProvider(Controller.this); sw.setThreadPoolService(threadPool); sw.setFeaturesReply(state.featuresReply); - sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA, - state.description); sw.setSwitchProperties(state.description); readPropertyFromStorage(); + log.info("Switch {} bound to class {}", + HexString.toHexString(sw.getId()), sw.getClass().getName()); + log.info("{}", state.description); + state.featuresReply = null; state.description = null; state.switchBindingDone = true; - - log.info("Switch {} bound to class {}", - HexString.toHexString(sw.getId()), sw.getClass().getName()); - return; } - - private void readPropertyFromStorage() { + + private void readPropertyFromStorage() { // At this time, also set other switch properties from storage boolean is_core_switch = false; IResultSet resultSet = null; try { String swid = sw.getStringId(); - resultSet = + resultSet = storageSource.getRow(SWITCH_CONFIG_TABLE_NAME, swid); - for (Iterator<IResultSet> it = + for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { // In case of multiple rows, use the status // in last row? Map<String, Object> row = it.next().getRow(); if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) { if (log.isDebugEnabled()) { - log.debug("Reading SWITCH_IS_CORE_SWITCH " + + log.debug("Reading SWITCH_IS_CORE_SWITCH " + "config for switch={}, is-core={}", sw, row.get(SWITCH_CONFIG_CORE_SWITCH)); } - String ics = + String ics = (String)row.get(SWITCH_CONFIG_CORE_SWITCH); is_core_switch = ics.equals("true"); } @@ -827,109 +825,11 @@ public class Controller implements IFloodlightProviderService, resultSet.close(); } if (is_core_switch) { - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, + sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, new Boolean(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 - * - */ - @LogMessageDoc(level="ERROR", - message="Invalid role value in role reply message", - explanation="Was unable to set the HA role (master or slave) " + - "for the controller.", - recommendation=LogMessageDoc.CHECK_CONTROLLER) - protected void handleRoleReplyMessage(OFVendor vendorMessage, - OFRoleReplyVendorData roleReplyVendorData) { - // Map from the role code in the message to our role enum - int nxRole = roleReplyVendorData.getRole(); - Role role = null; - switch (nxRole) { - case OFRoleVendorData.NX_ROLE_OTHER: - role = Role.EQUAL; - break; - case OFRoleVendorData.NX_ROLE_MASTER: - role = Role.MASTER; - break; - case OFRoleVendorData.NX_ROLE_SLAVE: - role = Role.SLAVE; - break; - default: - log.error("Invalid role value in role reply message"); - sw.getChannel().close(); - return; - } - - log.debug("Handling role reply for role {} from {}. " + - "Controller's role is {} ", - new Object[] { role, sw, Controller.this.role} - ); - - sw.deliverRoleReply(vendorMessage.getXid(), role); - - if (sw.isActive()) { - // Transition from SLAVE to MASTER. - boolean shouldClearFlowMods = false; - if (!state.firstRoleReplyReceived || - getAlwaysClearFlowsOnSwAdd()) { - // 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... - shouldClearFlowMods = true; - log.debug("First role reply from master switch {}, " + - "clear FlowTable to active switch list", - HexString.toHexString(sw.getId())); - } - - // 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, shouldClearFlowMods); - log.debug("Added master switch {} to active switch list", - HexString.toHexString(sw.getId())); - - } - else { - // 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) { boolean shouldHandleMessage = false; int vendor = vendorMessage.getVendor(); @@ -942,8 +842,8 @@ public class Controller implements IFloodlightProviderService, case OFRoleReplyVendorData.NXT_ROLE_REPLY: OFRoleReplyVendorData roleReplyVendorData = (OFRoleReplyVendorData) niciraVendorData; - handleRoleReplyMessage(vendorMessage, - roleReplyVendorData); + roleChanger.handleRoleReplyMessage(sw, + vendorMessage, roleReplyVendorData); break; default: log.warn("Unhandled Nicira VENDOR message; " + @@ -955,7 +855,7 @@ public class Controller implements IFloodlightProviderService, log.warn("Unhandled VENDOR message; vendor id = {}", vendor); break; } - + return shouldHandleMessage; } @@ -964,7 +864,7 @@ public class Controller implements IFloodlightProviderService, * handler. * @param m The message to process * @throws IOException - * @throws SwitchStateException + * @throws SwitchStateException */ @LogMessageDocs({ @LogMessageDoc(level="WARN", @@ -986,17 +886,17 @@ public class Controller implements IFloodlightProviderService, protected void processOFMessage(OFMessage m) throws IOException, SwitchStateException { boolean shouldHandleMessage = false; - + switch (m.getType()) { case HELLO: if (log.isTraceEnabled()) log.trace("HELLO from {}", sw); - + if (state.hsState.equals(HandshakeState.START)) { state.hsState = HandshakeState.HELLO; sendHandShakeMessage(OFType.FEATURES_REQUEST); } else { - throw new SwitchStateException("Unexpected HELLO from " + throw new SwitchStateException("Unexpected HELLO from " + sw); } break; @@ -1013,14 +913,11 @@ public class Controller implements IFloodlightProviderService, case FEATURES_REPLY: if (log.isTraceEnabled()) log.trace("Features Reply from {}", sw); - + if (state.hsState.equals(HandshakeState.HELLO)) { sendFeatureReplyConfiguration(); state.featuresReply = (OFFeaturesReply) m; state.hsState = HandshakeState.FEATURES_REPLY; - // uncomment to enable "dumb" switches like cbench - // state.hsState = HandshakeState.READY; - // addSwitch(sw); } else { // return results to rest api caller sw.setFeaturesReply((OFFeaturesReply) m); @@ -1030,18 +927,18 @@ public class Controller implements IFloodlightProviderService, case GET_CONFIG_REPLY: if (log.isTraceEnabled()) log.trace("Get config reply from {}", sw); - + if (!state.hsState.equals(HandshakeState.FEATURES_REPLY)) { String em = "Unexpected GET_CONFIG_REPLY from " + sw; throw new SwitchStateException(em); } OFGetConfigReply cr = (OFGetConfigReply) m; if (cr.getMissSendLength() == (short)0xffff) { - log.trace("Config Reply from {} confirms " + + log.trace("Config Reply from {} confirms " + "miss length set to 0xffff", sw); } else { log.warn("Config Reply from {} has " + - "miss length set to {}", + "miss length set to {}", sw, cr.getMissSendLength() & 0xffff); } state.hasGetConfigReply = true; @@ -1051,134 +948,73 @@ public class Controller implements IFloodlightProviderService, shouldHandleMessage = handleVendorMessage((OFVendor)m); break; case ERROR: - // TODO: we need better error handling. Especially for + // 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. + // 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; - // 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()); - // 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). By protocol, it should also be - // BAD_VENDOR, but too many switch implementations - // get it wrong and we can already check the xid() - // so we can ignore the type with confidence that this - // is not a spurious error - shouldLogError = !isBadVendorError; - if (isBadVendorError) { - if (state.firstRoleReplyReceived && (role != null)) { - 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.getRole() == 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.getRole() == null) { - // Controller's role is master: add to - // active - // TODO: check if clearing flow table is - // right choice here. - // Need to clear FlowMods before we add the switch - // and dispatch updates otherwise we have a race condition. - // TODO: switch update is async. Won't we still have a potential - // race condition? - addSwitch(sw, true); - } - } - } - 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 - // was some other error from processing the message. - // OF 1.2 specifies a OFPET_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. - sw.getChannel().close(); - } + + if (roleChanger.checkFirstPendingRoleRequestXid( + sw, error.getXid())) { + roleChanger.deliverRoleRequestError(sw, error); } - // Once we support OF 1.2, we'd add code to handle it here. - //if (error.getXid() == state.ofRoleRequestXid) { - //} - if (shouldLogError) + else { logError(sw, error); + } break; case STATS_REPLY: - if (state.hsState.ordinal() < + if (state.hsState.ordinal() < HandshakeState.FEATURES_REPLY.ordinal()) { String em = "Unexpected STATS_REPLY from " + sw; throw new SwitchStateException(em); } - if (sw == null) { + if (sw == null) { processSwitchDescReply((OFStatisticsReply) m); } else { sw.deliverStatisticsReply(m); } break; case PORT_STATUS: - handlePortStatusMessage(sw, (OFPortStatus)m); - shouldHandleMessage = true; + if (sw != null) { + handlePortStatusMessage(sw, (OFPortStatus)m); + shouldHandleMessage = true; + } else { + // Queue till we complete driver binding + state.queuedOFMessages.add((OFPortStatus) m); + } break; default: shouldHandleMessage = true; break; } - + if (shouldHandleMessage) { // WARNING: sw is null if handshake is not complete if (!state.hsState.equals(HandshakeState.READY)) { - log.debug("Ignoring message type {} received " + - "from switch {} before switch is " + + log.debug("Ignoring message type {} received " + + "from switch {} before switch is " + "fully configured.", m.getType(), sw); } else { sw.getListenerReadLock().lock(); try { if (sw.isConnected()) { - // Check if the controller is in the slave role for the - // switch. If it is, then don't dispatch the message to + // Check if the controller is in the slave role for the + // switch. If it is, then don't dispatch the message to // the listeners. - // 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 + // 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 // seems a little weird to dispatch port status messages - // to them. On the other hand there might be special + // to them. On the other hand there might be special // modules that care about all of the connected switches // and would like to receive port status notifications. - if (sw.getRole() == Role.SLAVE) { - // Don't log message if it's a port status message - // since we expect to receive those from the switch + if (sw.getHARole() == Role.SLAVE) { + // Don't log message if it's a port status message + // since we expect to receive those from the switch // and don't want to emit spurious messages. if (m.getType() != OFType.PORT_STATUS) { log.debug("Ignoring message type {} received " + @@ -1201,7 +1037,7 @@ public class Controller implements IFloodlightProviderService, // **************** // Message handlers // **************** - + protected void handlePortStatusMessage(IOFSwitch sw, OFPortStatus m) { short portNumber = m.getDesc().getPortNumber(); OFPhysicalPort port = m.getDesc(); @@ -1211,7 +1047,7 @@ public class Controller implements IFloodlightProviderService, } else if (m.getReason() == (byte)OFPortReason.OFPPR_ADD.ordinal()) { sw.setPort(port); log.debug("Port #{} added for {}", portNumber, sw); - } else if (m.getReason() == + } else if (m.getReason() == (byte)OFPortReason.OFPPR_DELETE.ordinal()) { sw.deletePort(portNumber); log.debug("Port #{} deleted for {}", portNumber, sw); @@ -1223,7 +1059,7 @@ public class Controller implements IFloodlightProviderService, log.error("Failure adding update to queue", e); } } - + /** * flcontext_cache - Keep a thread local stack of contexts */ @@ -1289,13 +1125,13 @@ public class Controller implements IFloodlightProviderService, switch (m.getType()) { case PACKET_IN: OFPacketIn pi = (OFPacketIn)m; - + if (pi.getPacketData().length <= 0) { - log.error("Ignoring PacketIn (Xid = " + pi.getXid() + + log.error("Ignoring PacketIn (Xid = " + pi.getXid() + ") because the data field is empty."); return; } - + if (Controller.ALWAYS_DECODE_ETH) { eth = new Ethernet(); eth.deserialize(pi.getPacketData(), 0, @@ -1305,17 +1141,17 @@ public class Controller implements IFloodlightProviderService, // fall through to default case... default: - + List<IOFMessageListener> listeners = null; if (messageListeners.containsKey(m.getType())) { listeners = messageListeners.get(m.getType()). getOrderedListeners(); } - + FloodlightContext bc = null; if (listeners != null) { - // Check if floodlight context is passed from the calling - // function, if so use that floodlight context, otherwise + // Check if floodlight context is passed from the calling + // function, if so use that floodlight context, otherwise // allocate one if (bContext == null) { bc = flcontext_alloc(); @@ -1323,16 +1159,16 @@ public class Controller implements IFloodlightProviderService, bc = bContext; } if (eth != null) { - IFloodlightProviderService.bcStore.put(bc, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD, + IFloodlightProviderService.bcStore.put(bc, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD, eth); } - - // Get the starting time (overall and per-component) of + + // Get the starting time (overall and per-component) of // the processing chain for this packet if performance // monitoring is turned on pktinProcTime.bootstrap(listeners); - pktinProcTime.recordStartTimePktIn(); + pktinProcTime.recordStartTimePktIn(); Command cmd; for (IOFMessageListener listener : listeners) { if (listener instanceof IOFSwitchFilter) { @@ -1344,7 +1180,7 @@ public class Controller implements IFloodlightProviderService, pktinProcTime.recordStartTimeComp(listener); cmd = listener.receive(sw, m, bc); pktinProcTime.recordEndTimeComp(listener); - + if (Command.STOP.equals(cmd)) { break; } @@ -1353,11 +1189,11 @@ public class Controller implements IFloodlightProviderService, } else { log.warn("Unhandled OF Message: {} from {}", m, sw); } - + if ((bContext == null) && (bc != null)) flcontext_free(bc); } } - + /** * Log an OpenFlow error message from a switch * @param sw The switch that sent the error @@ -1379,12 +1215,12 @@ public class Controller implements IFloodlightProviderService, OFErrorType et = OFErrorType.values()[etint]; switch (et) { case OFPET_HELLO_FAILED: - OFHelloFailedCode hfc = + OFHelloFailedCode hfc = OFHelloFailedCode.values()[0xffff & error.getErrorCode()]; log.error("Error {} {} from {}", new Object[] {et, hfc, sw}); break; case OFPET_BAD_REQUEST: - OFBadRequestCode brc = + OFBadRequestCode brc = OFBadRequestCode.values()[0xffff & error.getErrorCode()]; log.error("Error {} {} from {}", new Object[] {et, brc, sw}); break; @@ -1412,16 +1248,16 @@ public class Controller implements IFloodlightProviderService, break; } } - + /** * 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 * not in the slave role) or when the role of the controller changes from * slave to master. - * - * FIXME: remove shouldReadSwitchPortStateFromStorage argument once + * + * FIXME: remove shouldReadSwitchPortStateFromStorage argument once * performance problems are solved. We should call it always or never. - * + * * @param sw the switch that has been added */ // TODO: need to rethink locking and the synchronous switch update. @@ -1440,7 +1276,7 @@ public class Controller implements IFloodlightProviderService, "network problem that can be ignored." ) protected void addSwitch(IOFSwitch sw, boolean shouldClearFlowMods) { - // TODO: is it safe to modify the HashMap without holding + // TODO: is it safe to modify the HashMap without holding // the old switch's lock? IOFSwitch oldSw = this.activeSwitches.put(sw.getId(), sw); if (sw == oldSw) { @@ -1448,9 +1284,9 @@ public class Controller implements IFloodlightProviderService, log.info("New add switch for pre-existing switch {}", sw); return; } - - - + + + if (oldSw != null) { oldSw.getListenerWriteLock().lock(); try { @@ -1459,10 +1295,10 @@ public class Controller implements IFloodlightProviderService, // Set the connected flag to false to suppress calling // the listeners for this switch in processOFMessage oldSw.setConnected(false); - + oldSw.cancelAllStatisticsReplies(); - - // we need to clean out old switch state definitively + + // we need to clean out old switch state definitively // before adding the new switch // FIXME: It seems not completely kosher to call the // switch listeners here. I thought one of the points of @@ -1479,16 +1315,16 @@ public class Controller implements IFloodlightProviderService, // a "Not removing Switch ... already removed debug message. // TODO: Figure out a way to handle this that avoids the // spurious debug message. - oldSw.getChannel().close(); + oldSw.disconnectOutputStream(); } finally { oldSw.getListenerWriteLock().unlock(); } } - + if (shouldClearFlowMods) sw.clearAllFlowMods(); - + SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.ADDED); try { this.updates.put(update); @@ -1513,11 +1349,11 @@ public class Controller implements IFloodlightProviderService, return; } // We cancel all outstanding statistics replies if the switch transition - // from active. In the future we might allow statistics requests + // from active. In the future we might allow statistics requests // from slave controllers. Then we need to move this cancelation // to switch disconnect sw.cancelAllStatisticsReplies(); - + // FIXME: I think there's a race condition if we call updateInactiveSwitchInfo // here if role support is enabled. In that case if the switch is being // removed because we've been switched to being in the slave role, then I think @@ -1526,7 +1362,7 @@ public class Controller implements IFloodlightProviderService, // updateInactiveSwitchInfo we may wipe out all of the state that was // written out by the new master. Maybe need to revisit how we handle all // of the switch state that's written to storage. - + SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.REMOVED); try { this.updates.put(update); @@ -1534,15 +1370,15 @@ public class Controller implements IFloodlightProviderService, log.error("Failure adding update to queue", e); } } - + // *************** // IFloodlightProvider // *************** - + @Override - public synchronized void addOFMessageListener(OFType type, + public synchronized void addOFMessageListener(OFType type, IOFMessageListener listener) { - ListenerDispatcher<OFType, IOFMessageListener> ldd = + ListenerDispatcher<OFType, IOFMessageListener> ldd = messageListeners.get(type); if (ldd == null) { ldd = new ListenerDispatcher<OFType, IOFMessageListener>(); @@ -1554,23 +1390,23 @@ public class Controller implements IFloodlightProviderService, @Override public synchronized void removeOFMessageListener(OFType type, IOFMessageListener listener) { - ListenerDispatcher<OFType, IOFMessageListener> ldd = + ListenerDispatcher<OFType, IOFMessageListener> ldd = messageListeners.get(type); if (ldd != null) { ldd.removeListener(listener); } } - + private void logListeners() { for (Map.Entry<OFType, - ListenerDispatcher<OFType, + ListenerDispatcher<OFType, IOFMessageListener>> entry : messageListeners.entrySet()) { - + OFType type = entry.getKey(); - ListenerDispatcher<OFType, IOFMessageListener> ldd = + ListenerDispatcher<OFType, IOFMessageListener> ldd = entry.getValue(); - + StringBuffer sb = new StringBuffer(); sb.append("OFListeners for "); sb.append(type); @@ -1579,10 +1415,10 @@ public class Controller implements IFloodlightProviderService, sb.append(l.getName()); sb.append(","); } - log.debug(sb.toString()); + log.debug(sb.toString()); } } - + public void removeOFMessageListeners(OFType type) { messageListeners.remove(type); } @@ -1604,15 +1440,15 @@ public class Controller implements IFloodlightProviderService, @Override public Map<OFType, List<IOFMessageListener>> getListeners() { - Map<OFType, List<IOFMessageListener>> lers = + Map<OFType, List<IOFMessageListener>> lers = new HashMap<OFType, List<IOFMessageListener>>(); - for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : + for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : messageListeners.entrySet()) { lers.put(e.getKey(), e.getValue().getOrderedListeners()); } return Collections.unmodifiableMap(lers); } - + @Override @LogMessageDocs({ @LogMessageDoc(message="Failed to inject OFMessage {message} onto " + @@ -1631,7 +1467,7 @@ public class Controller implements IFloodlightProviderService, log.info("Failed to inject OFMessage {} onto a null switch", msg); return false; } - + // FIXME: Do we need to be able to inject messages to switches // where we're the slave controller (i.e. they're connected but // not active)? @@ -1640,14 +1476,14 @@ public class Controller implements IFloodlightProviderService, // discussions it sounds like the right thing to do here would be to // inject the message as a netty upstream channel event so it goes // through the normal netty event processing, including being - // handled + // handled if (!activeSwitches.containsKey(sw.getId())) return false; - + try { // Pass Floodlight context to the handleMessages() handleMessage(sw, msg, bc); } catch (IOException e) { - log.error("Error reinjecting OFMessage on switch {}", + log.error("Error reinjecting OFMessage on switch {}", HexString.toHexString(sw.getId())); return false; } @@ -1661,13 +1497,13 @@ public class Controller implements IFloodlightProviderService, log.info("Calling System.exit"); System.exit(1); } - + @Override public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) { - // call the overloaded version with floodlight context set to null + // call the overloaded version with floodlight context set to null return injectOfMessage(sw, msg, null); } - + @Override public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, FloodlightContext bc) { @@ -1678,11 +1514,11 @@ public class Controller implements IFloodlightProviderService, List<IOFMessageListener> listeners = null; if (messageListeners.containsKey(m.getType())) { - listeners = + listeners = messageListeners.get(m.getType()).getOrderedListeners(); } - - if (listeners != null) { + + if (listeners != null) { for (IOFMessageListener listener : listeners) { if (listener instanceof IOFSwitchFilter) { if (!((IOFSwitchFilter)listener).isInterested(sw)) { @@ -1700,12 +1536,12 @@ public class Controller implements IFloodlightProviderService, public BasicFactory getOFMessageFactory() { return factory; } - + @Override public String getControllerId() { return controllerId; } - + // ************** // Initialization // ************** @@ -1717,7 +1553,7 @@ public class Controller implements IFloodlightProviderService, controllerInfo.put(CONTROLLER_ID, id); storageSource.updateRow(CONTROLLER_TABLE_NAME, controllerInfo); } - + /** * Sets the initial role based on properties in the config params. @@ -1744,12 +1580,12 @@ public class Controller implements IFloodlightProviderService, explanation="Setting the initial HA role to "), @LogMessageDoc(level="ERROR", message="Invalid current role value: {role}", - explanation="An invalid HA role value was read from the " + + explanation="An invalid HA role value was read from the " + "properties file", recommendation=LogMessageDoc.CHECK_CONTROLLER) }) protected Role getInitialRole(Map<String, String> configParams) { - Role role = null; + Role role = Role.MASTER; String roleString = configParams.get("role"); if (roleString == null) { String rolePath = configParams.get("rolepath"); @@ -1768,7 +1604,7 @@ public class Controller implements IFloodlightProviderService, } } } - + if (roleString != null) { // Canonicalize the string to the form used for the enum constants roleString = roleString.trim().toUpperCase(); @@ -1779,21 +1615,22 @@ public class Controller implements IFloodlightProviderService, log.error("Invalid current role value: {}", roleString); } } - + log.info("Controller role set to {}", role); - + return role; } - + /** * Tell controller that we're ready to accept switches loop - * @throws IOException + * @throws IOException */ + @Override @LogMessageDocs({ @LogMessageDoc(message="Listening for switch connections on {address}", explanation="The controller is ready and listening for new" + " switch connections"), - @LogMessageDoc(message="Storage exception in controller " + + @LogMessageDoc(message="Storage exception in controller " + "updates loop; terminating process", explanation=ERROR_DATABASE, recommendation=LogMessageDoc.CHECK_CONTROLLER), @@ -1806,8 +1643,8 @@ public class Controller implements IFloodlightProviderService, if (log.isDebugEnabled()) { logListeners(); } - - try { + + try { final ServerBootstrap bootstrap = createServerBootStrap(); bootstrap.setOption("reuseAddr", true); @@ -1815,13 +1652,13 @@ public class Controller implements IFloodlightProviderService, bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE); - ChannelPipelineFactory pfact = + ChannelPipelineFactory pfact = new OpenflowPipelineFactory(this, null); bootstrap.setPipelineFactory(pfact); InetSocketAddress sa = new InetSocketAddress(openFlowPort); final ChannelGroup cg = new DefaultChannelGroup(); cg.add(bootstrap.bind(sa)); - + log.info("Listening for switch connections on {}", sa); } catch (Exception e) { throw new RuntimeException(e); @@ -1835,7 +1672,7 @@ public class Controller implements IFloodlightProviderService, } catch (InterruptedException e) { return; } catch (StorageException e) { - log.error("Storage exception in controller " + + log.error("Storage exception in controller " + "updates loop; terminating process", e); return; } catch (Exception e) { @@ -1857,7 +1694,7 @@ public class Controller implements IFloodlightProviderService, Executors.newCachedThreadPool(), workerThreads)); } } - + public void setConfigParams(Map<String, String> configParams) { String ofPort = configParams.get("openflowport"); if (ofPort != null) { @@ -1879,21 +1716,9 @@ public class Controller implements IFloodlightProviderService, private void initVendorMessages() { // Configure openflowj to be able to parse the role request/reply // vendor messages. - OFBasicVendorId niciraVendorId = new OFBasicVendorId( - OFNiciraVendorData.NX_VENDOR_ID, 4); - OFVendorId.registerVendorId(niciraVendorId); - OFBasicVendorDataType roleRequestVendorData = - new OFBasicVendorDataType( - OFRoleRequestVendorData.NXT_ROLE_REQUEST, - OFRoleRequestVendorData.getInstantiable()); - niciraVendorId.registerVendorDataType(roleRequestVendorData); - OFBasicVendorDataType roleReplyVendorData = - new OFBasicVendorDataType( - OFRoleReplyVendorData.NXT_ROLE_REPLY, - OFRoleReplyVendorData.getInstantiable()); - niciraVendorId.registerVendorDataType(roleReplyVendorData); + OFNiciraVendorExtensions.initialize(); } - + /** * Initialize internal data structures */ @@ -1901,8 +1726,8 @@ public class Controller implements IFloodlightProviderService, // These data structures are initialized here because other // module's startUp() might be called before ours this.messageListeners = - new ConcurrentHashMap<OFType, - ListenerDispatcher<OFType, + new ConcurrentHashMap<OFType, + ListenerDispatcher<OFType, IOFMessageListener>>(); this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); this.haListeners = new CopyOnWriteArraySet<IHAListener>(); @@ -1917,11 +1742,11 @@ public class Controller implements IFloodlightProviderService, this.providerMap = new HashMap<String, List<IInfoProvider>>(); setConfigParams(configParams); this.role = getInitialRole(configParams); - this.roleChanger = new RoleChanger(); + this.roleChanger = new RoleChanger(this); initVendorMessages(); this.systemStartTime = System.currentTimeMillis(); } - + /** * Startup all of the controller's components */ @@ -1938,7 +1763,7 @@ public class Controller implements IFloodlightProviderService, storageSource.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME, CONTROLLER_ID); storageSource.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this); - + while (true) { try { updateControllerInfo(); @@ -1952,7 +1777,7 @@ public class Controller implements IFloodlightProviderService, } } } - + // Startup load monitoring if (overload_drop) { this.loadmonitor.startMonitoring( @@ -1977,18 +1802,19 @@ public class Controller implements IFloodlightProviderService, log.debug("Provider type {} doesn't exist.", type); return; } - + providerMap.get(type).remove(provider); } - + + @Override public Map<String, Object> getControllerInfo(String type) { if (!providerMap.containsKey(type)) return null; - + Map<String, Object> result = new LinkedHashMap<String, Object>(); for (IInfoProvider provider : providerMap.get(type)) { result.putAll(provider.getInfo(type)); } - + return result; } @@ -2001,26 +1827,26 @@ public class Controller implements IFloodlightProviderService, public void removeHAListener(IHAListener listener) { this.haListeners.remove(listener); } - - + + /** - * Handle changes to the controller nodes IPs and dispatch update. + * Handle changes to the controller nodes IPs and dispatch update. */ @SuppressWarnings("unchecked") protected void handleControllerNodeIPChanges() { HashMap<String,String> curControllerNodeIPs = new HashMap<String,String>(); HashMap<String,String> addedControllerNodeIPs = new HashMap<String,String>(); HashMap<String,String> removedControllerNodeIPs =new HashMap<String,String>(); - String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID, - CONTROLLER_INTERFACE_TYPE, - CONTROLLER_INTERFACE_NUMBER, + String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID, + CONTROLLER_INTERFACE_TYPE, + CONTROLLER_INTERFACE_NUMBER, CONTROLLER_INTERFACE_DISCOVERED_IP }; synchronized(controllerNodeIPsCache) { // We currently assume that interface Ethernet0 is the relevant // controller interface. Might change. - // We could (should?) implement this using + // We could (should?) implement this using // predicates, but creating the individual and compound predicate - // seems more overhead then just checking every row. Particularly, + // seems more overhead then just checking every row. Particularly, // since the number of rows is small and changes infrequent IResultSet res = storageSource.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME, colNames,null, null); @@ -2030,14 +1856,14 @@ public class Controller implements IFloodlightProviderService, String controllerID = res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID); String discoveredIP = res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP); String curIP = controllerNodeIPsCache.get(controllerID); - + curControllerNodeIPs.put(controllerID, discoveredIP); if (curIP == null) { // new controller node IP addedControllerNodeIPs.put(controllerID, discoveredIP); - } + } else if (curIP != discoveredIP) { - // IP changed + // IP changed removedControllerNodeIPs.put(controllerID, curIP); addedControllerNodeIPs.put(controllerID, discoveredIP); } @@ -2064,7 +1890,7 @@ public class Controller implements IFloodlightProviderService, } } } - + @Override public Map<String, String> getControllerNodeIPs() { // We return a copy of the mapping so we can guarantee that @@ -2082,7 +1908,7 @@ public class Controller implements IFloodlightProviderService, if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) { handleControllerNodeIPChanges(); } - + } @Override @@ -2101,7 +1927,7 @@ public class Controller implements IFloodlightProviderService, public void setAlwaysClearFlowsOnSwAdd(boolean value) { this.alwaysClearFlowsOnSwAdd = value; } - + public boolean getAlwaysClearFlowsOnSwAdd() { return this.alwaysClearFlowsOnSwAdd; } @@ -2119,7 +1945,7 @@ public class Controller implements IFloodlightProviderService, // Sort so we match the longest string first int index = -1; for (String desc : switchDescSortedList) { - if (description.compareTo(desc) < 0) { + if (description.compareTo(desc) > 0) { index = switchDescSortedList.indexOf(desc); switchDescSortedList.add(index, description); break; diff --git a/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java b/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java deleted file mode 100644 index ddbb0ef37e45ee27f2b3a731483eb2676e9abb63..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.floodlightcontroller.core.internal; - -import org.openflow.protocol.statistics.OFDescriptionStatistics; - -import net.floodlightcontroller.core.IOFSwitch; - -public interface IOFSwitchFeatures { - public void setFromDescription(IOFSwitch sw, OFDescriptionStatistics description); -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java index 9a1f071f849569afb930b02d2728699878c489f3..2d281bb9243288bdab9f61b69255a9057a7b344d 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java @@ -17,7 +17,11 @@ package net.floodlightcontroller.core.internal; +import java.util.ArrayList; +import java.util.List; + import org.openflow.protocol.OFFeaturesReply; +import org.openflow.protocol.OFMessage; import org.openflow.protocol.statistics.OFDescriptionStatistics; /** @@ -60,12 +64,5 @@ class OFChannelState { protected OFFeaturesReply featuresReply = null; protected OFDescriptionStatistics description = null; - - // 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; + protected List<OFMessage> queuedOFMessages = new ArrayList<OFMessage>(); } \ 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 0909709c2c5460c51f20bd87a0246e760b655d3f..fd6339338ccee39470298b0253de7f4d18bf4311 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java @@ -17,850 +17,39 @@ package net.floodlightcontroller.core.internal; -import java.io.IOException; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -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; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import net.floodlightcontroller.core.FloodlightContext; -import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IOFMessageListener; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.annotations.LogMessageDoc; -import net.floodlightcontroller.core.annotations.LogMessageDocs; -import net.floodlightcontroller.core.web.serializers.DPIDSerializer; -import net.floodlightcontroller.threadpool.IThreadPoolService; -import net.floodlightcontroller.util.TimedCache; import org.codehaus.jackson.annotate.JsonIgnore; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.annotate.JsonSerialize; -import org.codehaus.jackson.map.ser.ToStringSerializer; -import org.jboss.netty.channel.Channel; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -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; 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; + +import net.floodlightcontroller.core.OFSwitchBase; /** * This is the internal representation of an openflow switch. */ -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); - - private static final String HA_CHECK_SWITCH = - "Check the health of the indicated switch. If the problem " + - "persists or occurs repeatedly, it likely indicates a defect " + - "in the switch HA implementation."; - - protected ConcurrentMap<Object, Object> attributes; - protected IFloodlightProviderService floodlightProvider; - protected IThreadPoolService threadPool; - protected Date connectedSince; - protected String stringId; - protected Channel channel; - protected AtomicInteger transactionIdSource; - // Lock to protect modification of the port maps. We only need to - // synchronize on modifications. For read operations we are fine since - // we rely on ConcurrentMaps which works for our use case. - private Object portLock; - // Map port numbers to the appropriate OFPhysicalPort - protected ConcurrentHashMap<Short, OFPhysicalPort> portsByNumber; - // Map port names to the appropriate OFPhyiscalPort - // XXX: The OF spec doesn't specify if port names need to be unique but - // according it's always the case in practice. - protected ConcurrentHashMap<String, OFPhysicalPort> portsByName; - protected Map<Integer,OFStatisticsFuture> statsFutureMap; - protected Map<Integer, IOFMessageListener> iofMsgListenersMap; - protected Map<Integer,OFFeaturesReplyFuture> featuresFutureMap; - protected volatile boolean connected; - protected Role role; - 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; - - /* Switch features from initial featuresReply */ - protected int capabilities; - protected int buffers; - protected int actions; - protected byte tables; - protected long datapathId; - - public static IOFSwitchFeatures switchFeatures; - protected static final ThreadLocal<Map<IOFSwitch,List<OFMessage>>> local_msg_buffer = - new ThreadLocal<Map<IOFSwitch,List<OFMessage>>>() { - @Override - protected Map<IOFSwitch,List<OFMessage>> initialValue() { - return new HashMap<IOFSwitch,List<OFMessage>>(); - } - }; - - // 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.stringId = null; - this.attributes = new ConcurrentHashMap<Object, Object>(); - this.connectedSince = new Date(); - this.transactionIdSource = new AtomicInteger(); - this.portLock = new Object(); - this.portsByNumber = new ConcurrentHashMap<Short, OFPhysicalPort>(); - this.portsByName = new ConcurrentHashMap<String, OFPhysicalPort>(); - this.connected = true; - this.statsFutureMap = new ConcurrentHashMap<Integer,OFStatisticsFuture>(); - this.featuresFutureMap = new ConcurrentHashMap<Integer,OFFeaturesReplyFuture>(); - this.iofMsgListenersMap = new ConcurrentHashMap<Integer,IOFMessageListener>(); - this.role = null; - 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, OFMatch.OFPFW_ALL); - this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, new Boolean(true)); - this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, new Boolean(true)); - } - - - @Override - public Object getAttribute(String name) { - if (this.attributes.containsKey(name)) { - return this.attributes.get(name); - } - return null; - } - - @Override - public void setAttribute(String name, Object value) { - this.attributes.put(name, value); - return; - } - - @Override - public Object removeAttribute(String name) { - return this.attributes.remove(name); - } - - @Override - public boolean hasAttribute(String name) { - return this.attributes.containsKey(name); - } - - @Override - @JsonIgnore - public Channel getChannel() { - return this.channel; - } - - @JsonIgnore - public void setChannel(Channel channel) { - this.channel = channel; - } - - @Override - public void write(OFMessage m, FloodlightContext bc) throws IOException { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - List<OFMessage> msg_buffer = msg_buffer_map.get(this); - if (msg_buffer == null) { - msg_buffer = new ArrayList<OFMessage>(); - msg_buffer_map.put(this, msg_buffer); - } - - this.floodlightProvider.handleOutgoingMessage(this, m, bc); - msg_buffer.add(m); - - if ((msg_buffer.size() >= Controller.BATCH_MAX_SIZE) || - ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) { - this.write(msg_buffer); - msg_buffer.clear(); - } - } - - @Override - @LogMessageDoc(level="WARN", - message="Sending OF message that modifies switch " + - "state while in the slave role: {switch}", - explanation="An application has sent a message to a switch " + - "that is not valid when the switch is in a slave role", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - public void write(List<OFMessage> msglist, - FloodlightContext bc) throws IOException { - for (OFMessage m : msglist) { - if (role == Role.SLAVE) { - switch (m.getType()) { - case PACKET_OUT: - case FLOW_MOD: - case PORT_MOD: - log.warn("Sending OF message that modifies switch " + - "state while in the slave role: {}", - m.getType().name()); - break; - default: - break; - } - } - this.floodlightProvider.handleOutgoingMessage(this, m, bc); - } - this.write(msglist); - } - - public void write(List<OFMessage> msglist) throws IOException { - this.channel.write(msglist); - } - - @Override - public void disconnectOutputStream() { - channel.close(); - } - - @Override - @JsonIgnore - public void setFeaturesReply(OFFeaturesReply featuresReply) { - synchronized(portLock) { - if (stringId == null) { - /* ports are updated via port status message, so we - * only fill in ports on initial connection. - */ - for (OFPhysicalPort port : featuresReply.getPorts()) { - setPort(port); - } - } - this.datapathId = featuresReply.getDatapathId(); - this.capabilities = featuresReply.getCapabilities(); - this.buffers = featuresReply.getBuffers(); - this.actions = featuresReply.getActions(); - this.tables = featuresReply.getTables(); - this.stringId = HexString.toHexString(this.datapathId); - } - } - - @Override - @JsonIgnore - public Collection<OFPhysicalPort> getEnabledPorts() { - List<OFPhysicalPort> result = new ArrayList<OFPhysicalPort>(); - for (OFPhysicalPort port : portsByNumber.values()) { - if (portEnabled(port)) { - result.add(port); - } - } - return result; - } - - @Override - @JsonIgnore - public Collection<Short> getEnabledPortNumbers() { - List<Short> result = new ArrayList<Short>(); - for (OFPhysicalPort port : portsByNumber.values()) { - if (portEnabled(port)) { - result.add(port.getPortNumber()); - } - } - return result; - } - - @Override - public OFPhysicalPort getPort(short portNumber) { - return portsByNumber.get(portNumber); - } - - @Override - public OFPhysicalPort getPort(String portName) { - return portsByName.get(portName); - } - - @Override - @JsonIgnore - public void setPort(OFPhysicalPort port) { - synchronized(portLock) { - portsByNumber.put(port.getPortNumber(), port); - portsByName.put(port.getName(), port); - } - } - - @Override - @JsonProperty("ports") - public Collection<OFPhysicalPort> getPorts() { - return Collections.unmodifiableCollection(portsByNumber.values()); - } - - @Override - public void deletePort(short portNumber) { - synchronized(portLock) { - portsByName.remove(portsByNumber.get(portNumber).getName()); - portsByNumber.remove(portNumber); - } - } - - @Override - public void deletePort(String portName) { - synchronized(portLock) { - portsByNumber.remove(portsByName.get(portName).getPortNumber()); - portsByName.remove(portName); - } - } - - @Override - public boolean portEnabled(short portNumber) { - if (portsByNumber.get(portNumber) == null) return false; - return portEnabled(portsByNumber.get(portNumber)); - } - - @Override - public boolean portEnabled(String portName) { - if (portsByName.get(portName) == null) return false; - return portEnabled(portsByName.get(portName)); - } - - @Override - public boolean portEnabled(OFPhysicalPort port) { - if (port == null) - return false; - if ((port.getConfig() & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0) - return false; - if ((port.getState() & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0) - return false; - // Port STP state doesn't work with multiple VLANs, so ignore it for now - //if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue()) - // return false; - return true; - } - - @Override - @JsonSerialize(using=DPIDSerializer.class) - @JsonProperty("dpid") - public long getId() { - if (this.stringId == null) - throw new RuntimeException("Features reply has not yet been set"); - return this.datapathId; - } - - @JsonIgnore - @Override - public String getStringId() { - return stringId; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "OFSwitchImpl [" + channel.getRemoteAddress() + " DPID[" + ((stringId != null) ? stringId : "?") + "]]"; - } - - @Override - public ConcurrentMap<Object, Object> getAttributes() { - return this.attributes; - } - - @Override - public Date getConnectedSince() { - return connectedSince; - } - - @JsonIgnore - @Override - public int getNextTransactionId() { - return this.transactionIdSource.incrementAndGet(); - } - - @Override - public void sendStatsQuery(OFStatisticsRequest request, int xid, - IOFMessageListener caller) throws IOException { - request.setXid(xid); - this.iofMsgListenersMap.put(xid, caller); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.channel.write(msglist); - return; - } - - @Override - public Future<List<OFStatistics>> getStatistics(OFStatisticsRequest request) throws IOException { - request.setXid(getNextTransactionId()); - OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this, request.getXid()); - this.statsFutureMap.put(request.getXid(), future); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.channel.write(msglist); - return future; - } - - @Override - public void deliverStatisticsReply(OFMessage reply) { - OFStatisticsFuture future = this.statsFutureMap.get(reply.getXid()); - if (future != null) { - future.deliverFuture(this, reply); - // The future will ultimately unregister itself and call - // cancelStatisticsReply - return; - } - /* Transaction id was not found in statsFutureMap.check the other map */ - IOFMessageListener caller = this.iofMsgListenersMap.get(reply.getXid()); - if (caller != null) { - caller.receive(this, reply, null); - } - } - - @Override - public void cancelStatisticsReply(int transactionId) { - if (null == this.statsFutureMap.remove(transactionId)) { - this.iofMsgListenersMap.remove(transactionId); - } - } - - @Override - public void cancelAllStatisticsReplies() { - /* we don't need to be synchronized here. Even if another thread - * modifies the map while we're cleaning up the future will eventuall - * timeout */ - for (OFStatisticsFuture f : statsFutureMap.values()) { - f.cancel(true); - } - statsFutureMap.clear(); - iofMsgListenersMap.clear(); - } - - - /** - * @param floodlightProvider the floodlightProvider to set - */ - @JsonIgnore - public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) { - this.floodlightProvider = floodlightProvider; - } - - @JsonIgnore - public void setThreadPoolService(IThreadPoolService tp) { - this.threadPool = tp; - } - - @JsonIgnore - @Override - public boolean isConnected() { - // No lock needed since we use volatile - return connected; - } - - @Override - @JsonIgnore - public void setConnected(boolean connected) { - // No lock needed since we use volatile - this.connected = connected; - } - - @Override - public Role getRole() { - return role; - } - - @JsonIgnore - @Override - public boolean isActive() { - return (role != Role.SLAVE); - } +public class OFSwitchImpl extends OFSwitchBase { @Override @JsonIgnore public void setSwitchProperties(OFDescriptionStatistics description) { - if (switchFeatures != null) { - switchFeatures.setFromDescription(this, description); - } - } - - @Override - @LogMessageDoc(level="ERROR", - message="Failed to clear all flows on switch {switch}", - explanation="An I/O error occured while trying to clear " + - "flows on the switch.", - recommendation=LogMessageDoc.CHECK_SWITCH) - public void clearAllFlowMods() { - // Delete all pre-existing flows - OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); - OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD)) - .setMatch(match) - .setCommand(OFFlowMod.OFPFC_DELETE) - .setOutPort(OFPort.OFPP_NONE) - .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); - try { - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(fm); - channel.write(msglist); - } catch (Exception e) { - log.error("Failed to clear all flows on switch " + this, e); - } - } - - @Override - public boolean updateBroadcastCache(Long entry, Short port) { - if (timedCache.update(entry)) { - Long count = portBroadcastCacheHitMap.putIfAbsent(port, new Long(1)); - if (count != null) { - count++; - } - return true; - } else { - return false; - } + // Nothing to do at the moment } @Override - @JsonIgnore - public Map<Short, Long> getPortBroadcastHits() { - return this.portBroadcastCacheHitMap; + public OFPortType getPortType(short port_num) { + return OFPortType.NORMAL; } - @Override - public void flush() { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - List<OFMessage> msglist = msg_buffer_map.get(this); - if ((msglist != null) && (msglist.size() > 0)) { - try { - this.write(msglist); - } catch (IOException e) { - // TODO: log exception - e.printStackTrace(); - } - msglist.clear(); - } - } - - public static void flush_all() { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - for (IOFSwitch sw : msg_buffer_map.keySet()) { - sw.flush(); - } - } - - /** - * Return a read lock that must be held while calling the listeners for - * messages from the switch. Holding the read lock prevents the active - * switch list from being modified out from under the listeners. - * @return - */ - @JsonIgnore - public Lock getListenerReadLock() { - return listenerLock.readLock(); - } - - /** - * Return a write lock that must be held when the controllers modifies the - * list of active switches. This is to ensure that the active switch list - * doesn't change out from under the listeners as they are handling a - * message from the switch. - * @return - */ @JsonIgnore - public Lock getListenerWriteLock() { - return listenerLock.writeLock(); - } - - /** - * Get the IP Address for the switch - * @return the inet address - */ - @JsonSerialize(using=ToStringSerializer.class) - public SocketAddress getInetAddress() { - return channel.getRemoteAddress(); - } - - /** - * 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 - */ - public 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 - */ - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="Switch {switch}: received unexpected role reply for " + - "Role {role}" + - " Disconnecting switch", - explanation="The switch sent an unexpected HA role reply", - recommendation=HA_CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Switch {switch}: expected role reply with " + - "Xid {xid}, got {xid}. Disconnecting switch", - explanation="The switch sent an unexpected HA role reply", - recommendation=HA_CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Switch {switch}: expected role reply with " + - "Role {role}, got {role}. Disconnecting switch", - explanation="The switch sent an unexpected HA role reply", - recommendation=HA_CHECK_SWITCH) - }) - public 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 - */ - public 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 - */ - public 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 - */ - public 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(); - } - } - } - - @Override - public Future<OFFeaturesReply> querySwitchFeaturesReply() - throws IOException { - OFMessage request = - floodlightProvider.getOFMessageFactory(). - getMessage(OFType.FEATURES_REQUEST); - request.setXid(getNextTransactionId()); - OFFeaturesReplyFuture future = - new OFFeaturesReplyFuture(threadPool, this, request.getXid()); - this.featuresFutureMap.put(request.getXid(), future); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.channel.write(msglist); - return future; - } - - @Override - public void deliverOFFeaturesReply(OFMessage reply) { - OFFeaturesReplyFuture future = this.featuresFutureMap.get(reply.getXid()); - if (future != null) { - future.deliverFuture(this, reply); - // The future will ultimately unregister itself and call - // cancelFeaturesReply - return; - } - log.error("Switch {}: received unexpected featureReply", this); - } - - @Override - public void cancelFeaturesReply(int transactionId) { - this.featuresFutureMap.remove(transactionId); - } - - - @Override - public int getBuffers() { - return buffers; - } - - - @Override - public int getActions() { - return actions; - } - - - @Override - public int getCapabilities() { - return capabilities; + public boolean isFastPort(short port_num) { + return false; } - @Override - public byte getTables() { - return tables; + public List<Short> getUplinkPorts() { + return null; } - - @Override - public void setFloodlightProvider(Controller controller) { - floodlightProvider = controller; - } + } diff --git a/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java b/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java index 008e54232cfbe0bf2dd47e20f3690b11d57ad42e..f9ec793b3ba9797ec06912dc5579772fccd15fb1 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java +++ b/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java @@ -4,16 +4,29 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFType; +import org.openflow.protocol.OFVendor; +import org.openflow.vendor.nicira.OFNiciraVendorData; +import org.openflow.vendor.nicira.OFRoleReplyVendorData; +import org.openflow.vendor.nicira.OFRoleRequestVendorData; +import org.openflow.vendor.nicira.OFRoleVendorData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.annotations.LogMessageDocs; /** * This class handles sending of RoleRequest messages to all connected switches. @@ -67,22 +80,19 @@ import net.floodlightcontroller.core.annotations.LogMessageDoc; * OFSwitchImpl's queue of pending requests) * - We handle requests and timeouts in the same thread. We use a priority queue * to schedule them so we are guaranteed that they are processed in - * the same order as they are submitted. If a request times out we drop - * the connection to this switch. + * the same order as they are submitted. If a request times out we assume + * the switch doesn't support HA role (the same as receiving an error reply). * - Since we decouple submission of role change requests and actually sending * them we cannot check a received role reply against the controller's current * role because the controller's current role could have changed again. - * - Receiving Role Reply messages is handled by OFChannelHandler and - * OFSwitchImpl directly. The OFSwitchImpl checks if the received request - * is as expected (xid and role match the head of the pending queue in - * OFSwitchImpl). If so - * the switch updates its role. Otherwise the connection is dropped. If this - * is the first reply, the SWITCH_SUPPORTS_NX_ROLE attribute is set. - * Next, we call addSwitch(), removeSwitch() to update the list of active - * switches if appropriate. + * - Receiving Role Reply messages is received by OFChannelHandler and + * delivered here. We call switch's setHARole() to mark the switch role and + * indicate that a reply was received. Next, we call addSwitch(), + * removeSwitch() to update the list of active switches if appropriate. * - If we receive an Error indicating that roles are not supported by the - * switch, we set the SWITCH_SUPPORTS_NX_ROLE to false. We keep the - * switch connection alive while in MASTER and EQUAL role. + * switch, we set the SWITCH_SUPPORTS_NX_ROLE to false. We call switch's + * setHARole(), indicating no reply was received. We keep the switch + * connection alive while in MASTER and EQUAL role. * (TODO: is this the right behavior for EQUAL??). If the role changes to * SLAVE the switch connection is dropped (remember: only if the switch * doesn't support role requests) @@ -94,13 +104,10 @@ import net.floodlightcontroller.core.annotations.LogMessageDoc; * New switch connection: * - Switch handshake is done without sending any role request messages. * - After handshake completes, switch is added to the list of connected switches - * and we send the first role request message if role - * requests are enabled. If roles are disabled automatically promote switch to - * active switch list and clear FlowTable. + * and we send the first role request message. If role is disabled, we assume + * the role is MASTER. * - When we receive the first reply we proceed as above. In addition, if - * the role request is for MASTER we wipe the flow table. We do not wipe - * the flow table if the switch connected while role supported was disabled - * on the controller. + * the role request is for MASTER we wipe the flow table. * */ public class RoleChanger { @@ -119,8 +126,13 @@ public class RoleChanger { protected long lastSubmitTime; protected Thread workerThread; protected long timeout; - protected static long DEFAULT_TIMEOUT = 15L*1000*1000*1000L; // 15s + protected ConcurrentHashMap<IOFSwitch, LinkedList<PendingRoleRequestEntry>> + pendingRequestMap; + private Controller controller; + + protected static long DEFAULT_TIMEOUT = 5L*1000*1000*1000L; // 5s protected static Logger log = LoggerFactory.getLogger(RoleChanger.class); + /** * A queued task to be handled by the Role changer thread. */ @@ -156,6 +168,22 @@ public class RoleChanger { } } + /** + * Per-switch list of pending HA role requests. + * @author shudongz + */ + 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; + } + } + @LogMessageDoc(level="ERROR", message="RoleRequestWorker task had an uncaught exception.", explanation="An unknown occured while processing an HA " + @@ -170,17 +198,25 @@ public class RoleChanger { try { while (true) { try { - t = pendingTasks.take(); + t = pendingTasks.poll(); + if (t == null) { + // Notify when there is no immediate tasks to run + // For the convenience of RoleChanger unit tests + synchronized (pendingTasks) { + pendingTasks.notifyAll(); + } + t = pendingTasks.take(); + } } catch (InterruptedException e) { // see http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html interrupted = true; continue; } if (t.type == RoleChangeTask.Type.REQUEST) { + t.deadline += timeout; sendRoleRequest(t.switches, t.role, t.deadline); // Queue the timeout t.type = RoleChangeTask.Type.TIMEOUT; - t.deadline += timeout; pendingTasks.put(t); } else { @@ -201,7 +237,16 @@ public class RoleChanger { } // end loop } - public RoleChanger() { + protected class HARoleUnsupportedException extends Exception { + + private static final long serialVersionUID = -6854500893864114158L; + + } + + public RoleChanger(Controller controller) { + this.controller = controller; + this.pendingRequestMap = new ConcurrentHashMap<IOFSwitch, + LinkedList<PendingRoleRequestEntry>>(); this.pendingTasks = new DelayQueue<RoleChangeTask>(); this.workerThread = new Thread(new RoleRequestWorker()); this.timeout = DEFAULT_TIMEOUT; @@ -223,12 +268,10 @@ public class RoleChanger { } /** - * Send a role request message to switches. 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 switches the collection of switches to send the request too + * Send a role request message to switches. The sw implementation throws + * HARoleUnsupportedException if HA is not supported. Otherwise, it + * returns the transaction id of the request message. + * @param switches the collection of switches to send the request too * @param role the role to request */ @LogMessageDoc(level="WARN", @@ -239,60 +282,42 @@ public class RoleChanger { recommendation=LogMessageDoc.CHECK_SWITCH) protected void sendRoleRequest(Collection<IOFSwitch> switches, Role role, long cookie) { - // 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. Iterator<IOFSwitch> iter = switches.iterator(); while(iter.hasNext()) { IOFSwitch sw = iter.next(); try { - Boolean supportsNxRole = (Boolean) - sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE); - if ((supportsNxRole == null) || supportsNxRole) { - // Handle cases #1 and #2 - sw.sendNxRoleRequest(role, cookie); - } else { - // Handle case #3 - if (role == Role.SLAVE) { - log.debug("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(); - iter.remove(); - } + LinkedList<PendingRoleRequestEntry> pendingList + = pendingRequestMap.get(sw); + if (pendingList == null) { + pendingList = new LinkedList<PendingRoleRequestEntry>(); + pendingRequestMap.put(sw, pendingList); + } + int xid = sendHARoleRequest(sw, role, cookie); + PendingRoleRequestEntry entry = + new PendingRoleRequestEntry(xid, role, cookie); + // Need to synchronize against removal from list + synchronized(pendingList) { + pendingList.add(entry); } } catch (IOException e) { log.warn("Failed to send role request message " + "to switch {}: {}. Disconnecting", sw, e); - sw.getChannel().close(); + sw.disconnectOutputStream(); iter.remove(); + } catch (HARoleUnsupportedException e) { + // Switch doesn't support HA role, remove if role is slave + if (role == Role.SLAVE) { + log.debug("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.disconnectOutputStream(); + iter.remove(); + } else { + sw.setHARole(role, false); + controller.addSwitch(sw, true); + } } } } @@ -311,11 +336,388 @@ public class RoleChanger { protected void verifyRoleReplyReceived(Collection<IOFSwitch> switches, long cookie) { for (IOFSwitch sw: switches) { - if (sw.checkFirstPendingRoleRequestCookie(cookie)) { - sw.getChannel().close(); - log.warn("Timeout while waiting for role reply from switch {}." - + " Disconnecting", sw); + PendingRoleRequestEntry entry = + checkFirstPendingRoleRequestCookie(sw, cookie); + if (entry != null){ + log.warn("Timeout while waiting for role reply from switch {}" + + " with datapath {}", sw, + sw.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA)); + setSwitchHARole(sw, entry.role, false); + } + } + } + + /** + * Deliver a RoleReply message for a 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. + * + * 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 + */ + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="Switch {switch}: received unexpected role reply for " + + "Role {role}" + + " Disconnecting switch", + explanation="The switch sent an unexpected HA role reply", + recommendation=LogMessageDoc.HA_CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Switch {switch}: expected role reply with " + + "Xid {xid}, got {xid}. Disconnecting switch", + explanation="The switch sent an unexpected HA role reply", + recommendation=LogMessageDoc.HA_CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Switch {switch}: expected role reply with " + + "Role {role}, got {role}. Disconnecting switch", + explanation="The switch sent an unexpected HA role reply", + recommendation=LogMessageDoc.HA_CHECK_SWITCH) + }) + + protected void deliverRoleReply(IOFSwitch sw, int xid, Role role) { + LinkedList<PendingRoleRequestEntry> pendingRoleRequests = + pendingRequestMap.get(sw); + if (pendingRoleRequests == null) { + log.debug("Switch {}: received unexpected role reply for Role {}" + + ", ignored", sw, role ); + return; + } + 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", sw, role ); + sw.disconnectOutputStream(); + } + 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 } ); + sw.disconnectOutputStream(); + } + else if (head.role != role) { + log.error("Switch {}: expected role reply with " + + "Role {}, got {}. Disconnecting switch", + new Object[] { this, head.role, role } ); + sw.disconnectOutputStream(); + } + else { + log.debug("Received role reply message from {}, setting role to {}", + this, role); + setSwitchHARole(sw, role, true); + } + } + } + + /** + * Checks whether the given xid matches the xid of the first pending + * role request. + * @param xid + * @return + */ + public boolean checkFirstPendingRoleRequestXid (IOFSwitch sw, int xid) { + LinkedList<PendingRoleRequestEntry> pendingRoleRequests = + pendingRequestMap.get(sw); + if (pendingRoleRequests == null) { + return false; + } + 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. If so, return the entry + * @param cookie + * @return + */ + protected PendingRoleRequestEntry checkFirstPendingRoleRequestCookie( + IOFSwitch sw, long cookie) + { + LinkedList<PendingRoleRequestEntry> pendingRoleRequests = + pendingRequestMap.get(sw); + if (pendingRoleRequests == null) { + return null; + } + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.peek(); + if (head == null) + return null; + if (head.cookie == cookie) { + return pendingRoleRequests.poll(); } } + return null; + } + + /** + * 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 + */ + private void deliverRoleRequestNotSupported(IOFSwitch sw, int xid) { + LinkedList<PendingRoleRequestEntry> pendingRoleRequests = + pendingRequestMap.get(sw); + if (pendingRoleRequests == null) { + log.warn("Switch {}: received unexpected error for xid {}" + + ", ignored", sw, xid); + return; + } + synchronized(pendingRoleRequests) { + PendingRoleRequestEntry head = pendingRoleRequests.poll(); + if (head != null && head.xid == xid) { + setSwitchHARole(sw, head.role, false); + } + } + } + + /** + * 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 + * @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 sendHARoleRequest(IOFSwitch sw, Role role, long cookie) + throws IOException, HARoleUnsupportedException { + // 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 simply throw an unsupported exception. + Boolean supportsNxRole = (Boolean) + sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE); + if ((supportsNxRole != null) && !supportsNxRole) { + throw new HARoleUnsupportedException(); + } + + int xid = sw.getNextTransactionId(); + // 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.", sw); + throw new HARoleUnsupportedException(); + } + + // Construct the role request message + OFVendor roleRequest = (OFVendor)controller. + getOFMessageFactory().getMessage(OFType.VENDOR); + 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); + sw.write(msgList, new FloodlightContext()); + + return xid; + } + + /* 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 + * + */ + @LogMessageDoc(level="ERROR", + message="Invalid role value in role reply message", + explanation="Was unable to set the HA role (master or slave) " + + "for the controller.", + recommendation=LogMessageDoc.CHECK_CONTROLLER) + protected void handleRoleReplyMessage(IOFSwitch sw, OFVendor vendorMessage, + OFRoleReplyVendorData roleReplyVendorData) { + // Map from the role code in the message to our role enum + int nxRole = roleReplyVendorData.getRole(); + Role role = null; + switch (nxRole) { + case OFRoleVendorData.NX_ROLE_OTHER: + role = Role.EQUAL; + break; + case OFRoleVendorData.NX_ROLE_MASTER: + role = Role.MASTER; + break; + case OFRoleVendorData.NX_ROLE_SLAVE: + role = Role.SLAVE; + break; + default: + log.error("Invalid role value in role reply message"); + sw.disconnectOutputStream(); + return; + } + + log.debug("Handling role reply for role {} from {}. " + + "Controller's role is {} ", + new Object[] { role, sw, controller.role} + ); + + deliverRoleReply(sw, vendorMessage.getXid(), role); + } + + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="Received ERROR from sw {switch} that indicates roles " + + "are not supported but we have received a valid role " + + "reply earlier", + explanation="Switch is responding to HA role request in an " + + "inconsistent manner.", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Unexpected error {error} from switch {switch} " + + "disconnecting", + explanation="Swith sent an error reply to request, but the error " + + "type is not OPET_BAD_REQUEST as required by the protocol", + recommendation=LogMessageDoc.CHECK_SWITCH) + }) + protected void deliverRoleRequestError(IOFSwitch sw, OFError error) { + // We expect to receive a bad request error when + // we're connected to a switch that doesn't support + // the Nicira vendor extensions (i.e. not OVS or + // derived from OVS). By protocol, it should also be + // BAD_VENDOR, but too many switch implementations + // get it wrong and we can already check the xid() + // so we can ignore the type with confidence that this + // is not a spurious error + boolean isBadRequestError = + (error.getErrorType() == OFError.OFErrorType. + OFPET_BAD_REQUEST.getValue()); + if (isBadRequestError) { + if (sw.getHARole() != null) { + log.warn("Received ERROR from sw {} that " + +"indicates roles are not supported " + +"but we have received a valid " + +"role reply earlier", sw); + } + // First, clean up pending requests + deliverRoleRequestNotSupported(sw, error.getXid()); + } 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 OFPET_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. + log.error("Unexpected error {} from switch {}, disconnecting", + error, sw); + sw.disconnectOutputStream(); + } + } + + /** + * Set switch's HA role and adjust switch's list membership + * based on the new role and switch's HA capability. This is + * a synchronized method to keep role handling consistent. + * @param sw + * @param role + * @param replyReceived + */ + private synchronized void setSwitchHARole(IOFSwitch sw, Role role, + boolean replyReceived) { + // Record previous role and set the current role + // We tell switch whether a correct reply was received. + // The switch may deduce if HA role request is supported + // (if it doesn't already know). + Role oldRole = sw.getHARole(); + sw.setHARole(role, replyReceived); + + // Adjust the active switch list based on the role + if (role != Role.SLAVE) { + // SLAVE->MASTER, need to add switch to active list. + // If this is the initial connection, clear flodmods. + boolean clearFlowMods = (oldRole == null) || + controller.getAlwaysClearFlowsOnSwAdd(); + controller.addSwitch(sw, clearFlowMods); + } else { + // Initial SLAVE setting. Nothing to do if role reply received. + // Else disconnect the switch. + if (oldRole == null && !replyReceived) { + sw.disconnectOutputStream(); + } else { + // MASTER->SLAVE transition. If switch supports HA roles, + // remove it from active list, but still leave it connected. + // Otherwise, disconnect it. Cleanup happens after channel + // handler receives the disconnect notification. + if (replyReceived) { + controller.removeSwitch(sw); + } else { + sw.disconnectOutputStream(); + } + } + } + } + + /** + * Cleanup pending requests associated witch switch. Called when + * switch disconnects. + * @param sw + */ + public void removePendingRequests(IOFSwitch sw) { + pendingRequestMap.remove(sw); } } diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java b/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java index 0d73f93f12130c1df48fc995b1ccd50ad53ef82e..cf7c6ad43bf64e71ccb93824cf691471cdbd3426 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java @@ -29,18 +29,18 @@ public class SwitchRoleResource extends ServerResource { if (switchId.equalsIgnoreCase("all")) { HashMap<String,RoleInfo> model = new HashMap<String,RoleInfo>(); for (IOFSwitch sw: floodlightProvider.getSwitches().values()) { - switchId = sw.getStringId(); - roleInfo = new RoleInfo(sw.getRole()); - model.put(switchId, roleInfo); + switchId = sw.getStringId(); + roleInfo = new RoleInfo(sw.getHARole()); + model.put(switchId, roleInfo); } return model; } - Long dpid = HexString.toLong(switchId); - IOFSwitch sw = floodlightProvider.getSwitches().get(dpid); - if (sw == null) - return null; - roleInfo = new RoleInfo(sw.getRole()); - return roleInfo; + Long dpid = HexString.toLong(switchId); + IOFSwitch sw = floodlightProvider.getSwitches().get(dpid); + if (sw == null) + return null; + roleInfo = new RoleInfo(sw.getHARole()); + return roleInfo; } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java index 645125e20f0797b4235fbb6a48a7fd191bf8e497..19cc0271bc38381ea4ff769e1f4a16200669f80a 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java @@ -60,6 +60,10 @@ public class Device implements IDevice { protected IEntityClass entityClass; protected String macAddressString; + // the vlan Ids from the entities of this device + protected Short[] vlanIds; + + /** * These are the old attachment points for the device that were @@ -110,6 +114,7 @@ entity.getLastSeenTimestamp().getTime()); this.attachmentPoints.add(ap); } } + computeVlandIds(); } /** @@ -142,23 +147,53 @@ entity.getLastSeenTimestamp().getTime()); HexString.toHexString(this.entities[0].getMacAddress(), 6); this.entityClass = entityClass; Arrays.sort(this.entities); + computeVlandIds(); } /** * Construct a new device consisting of the entities from the old device - * plus an additional entity + * plus an additional entity. + * The caller needs to ensure that the additional entity is not already + * present in the array * @param device the old device object * @param newEntity the entity to add. newEntity must be have the same * entity class as device + * @param if positive indicates the index in the entities array were the + * new entity should be inserted. If negative we will compute the + * correct insertion point */ public Device(Device device, - Entity newEntity) { + Entity newEntity, + int insertionpoint) { this.deviceManager = device.deviceManager; this.deviceKey = device.deviceKey; + + this.entities = new Entity[device.entities.length + 1]; + if (insertionpoint < 0) { + insertionpoint = -(Arrays.binarySearch(device.entities, + newEntity)+1); + } + if (insertionpoint > 0) { + // insertion point is not the beginning: + // copy up to insertion point + System.arraycopy(device.entities, 0, + this.entities, 0, + insertionpoint); + } + if (insertionpoint < device.entities.length) { + // insertion point is not the end + // copy from insertion point + System.arraycopy(device.entities, insertionpoint, + this.entities, insertionpoint+1, + device.entities.length-insertionpoint); + } + this.entities[insertionpoint] = newEntity; + /* this.entities = Arrays.<Entity>copyOf(device.entities, device.entities.length + 1); this.entities[this.entities.length - 1] = newEntity; Arrays.sort(this.entities); + */ this.oldAPs = null; if (device.oldAPs != null) { this.oldAPs = @@ -174,8 +209,29 @@ entity.getLastSeenTimestamp().getTime()); HexString.toHexString(this.entities[0].getMacAddress(), 6); this.entityClass = device.entityClass; + computeVlandIds(); + } + + private void computeVlandIds() { + if (entities.length == 1) { + if (entities[0].getVlan() != null) { + vlanIds = new Short[]{ entities[0].getVlan() }; + } else { + vlanIds = new Short[] { Short.valueOf((short)-1) }; + } + } + + TreeSet<Short> vals = new TreeSet<Short>(); + for (Entity e : entities) { + if (e.getVlan() == null) + vals.add((short)-1); + else + vals.add(e.getVlan()); + } + vlanIds = vals.toArray(new Short[vals.size()]); } + /** * Given a list of attachment points (apList), the procedure would return * a map of attachment points for each L2 domain. L2 domain id is the key. @@ -566,22 +622,7 @@ entity.getLastSeenTimestamp().getTime()); @Override public Short[] getVlanId() { - if (entities.length == 1) { - if (entities[0].getVlan() != null) { - return new Short[]{ entities[0].getVlan() }; - } else { - return new Short[] { Short.valueOf((short)-1) }; - } - } - - TreeSet<Short> vals = new TreeSet<Short>(); - for (Entity e : entities) { - if (e.getVlan() == null) - vals.add((short)-1); - else - vals.add(e.getVlan()); - } - return vals.toArray(new Short[vals.size()]); + return Arrays.copyOf(vlanIds, vlanIds.length); } static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4); diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java index 9f836b1c8c6d7b162d693da0dfc8093dbd3fd82c..f59d67c986c8a56982ea1448590c61a3ad0be859 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java @@ -1052,7 +1052,8 @@ IFlowReconcileListener, IInfoProvider, IHAListener { entityClass = entityClassifier.classifyEntity(entity); if (entityClass == null) { // could not classify entity. No device - return null; + device = null; + break; } ClassState classState = getClassState(entityClass); @@ -1066,17 +1067,36 @@ IFlowReconcileListener, IInfoProvider, IHAListener { // use resulting device key to look up the device in the // device map, and use the referenced Device below. device = deviceMap.get(deviceKey); - if (device == null) - throw new IllegalStateException("Corrupted device index"); + if (device == null) { + // This can happen due to concurrent modification + if (logger.isDebugEnabled()) { + logger.debug("No device for deviceKey {} while " + + "while processing entity {}", + deviceKey, entity); + } + } } else { // If the secondary index does not contain the entity, // create a new Device object containing the entity, and - // generate a new device ID. However, we first check if + // generate a new device ID if the the entity is on an + // attachment point port. Otherwise ignore. + if (entity.hasSwitchPort() && + !topology.isAttachmentPointPort(entity.getSwitchDPID(), + entity.getSwitchPort().shortValue())) { + if (logger.isDebugEnabled()) { + logger.debug("Not learning new device on internal" + + " link: {}", entity); + } + device = null; + break; + } + // Before we create the new device also check if // the entity is allowed (e.g., for spoofing protection) if (!isEntityAllowed(entity, entityClass)) { logger.info("PacketIn is not allowed {} {}", entityClass.getName(), entity); - return null; + device = null; + break; } synchronized (deviceKeyLock) { deviceKey = Long.valueOf(deviceKeyCounter++); @@ -1113,56 +1133,36 @@ IFlowReconcileListener, IInfoProvider, IHAListener { device.getEntityClass().getName(), entity); return null; } + // If this is an internal port we don't learn the new entity + // and don't update indexes. We only learn on attachment point + // ports. + if (entity.hasSwitchPort() && + !topology.isAttachmentPointPort(entity.getSwitchDPID(), + entity.getSwitchPort().shortValue())) { + break; + } int entityindex = -1; if ((entityindex = device.entityIndex(entity)) >= 0) { + // Entity already exists // update timestamp on the found entity Date lastSeen = entity.getLastSeenTimestamp(); - if (lastSeen == null) lastSeen = new Date(); - device.entities[entityindex].setLastSeenTimestamp(lastSeen); - if (device.entities[entityindex].getSwitchDPID() != null && - device.entities[entityindex].getSwitchPort() != null) { - long sw = device.entities[entityindex].getSwitchDPID(); - short port = device.entities[entityindex].getSwitchPort().shortValue(); - - boolean moved = - device.updateAttachmentPoint(sw, - port, - lastSeen.getTime()); - - if (moved) { - sendDeviceMovedNotification(device); - if (logger.isTraceEnabled()) { - logger.trace("Device moved: attachment points {}," + - "entities {}", device.attachmentPoints, - device.entities); - } - } else { - if (logger.isTraceEnabled()) { - logger.trace("Device attachment point NOT updated: " + - "attachment points {}," + - "entities {}", device.attachmentPoints, - device.entities); - } - } + if (lastSeen == null) { + lastSeen = new Date(); + entity.setLastSeenTimestamp(lastSeen); } - break; + device.entities[entityindex].setLastSeenTimestamp(lastSeen); + // we break the loop after the else block and after checking + // for new AP } else { - boolean moved = false; - Device newDevice = allocateDevice(device, entity); - if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) { - moved = newDevice.updateAttachmentPoint(entity.getSwitchDPID(), - entity.getSwitchPort().shortValue(), - entity.getLastSeenTimestamp().getTime()); - } - + // New entity for this devce + // compute the insertion point for the entity. + // see Arrays.binarySearch() + entityindex = -(entityindex + 1); + Device newDevice = allocateDevice(device, entity, entityindex); + // generate updates EnumSet<DeviceField> changedFields = findChangedFields(device, entity); - if (changedFields.size() > 0) - deviceUpdates = - updateUpdates(deviceUpdates, - new DeviceUpdate(newDevice, CHANGE, - changedFields)); // update the device map with a replace call boolean res = deviceMap.replace(deviceKey, device, newDevice); @@ -1173,7 +1173,6 @@ IFlowReconcileListener, IInfoProvider, IHAListener { continue; device = newDevice; - // update indices if (!updateIndices(device, deviceKey)) { continue; @@ -1181,36 +1180,45 @@ IFlowReconcileListener, IInfoProvider, IHAListener { updateSecondaryIndices(entity, device.getEntityClass(), deviceKey); - + + if (changedFields.size() > 0) { + deviceUpdates = + updateUpdates(deviceUpdates, + new DeviceUpdate(newDevice, CHANGE, + changedFields)); + } + // we break the loop after checking for changed AP + } + // Update attachment point (will only be hit if the device + // already existed and no concurrent modification + if (entity.hasSwitchPort()) { + boolean moved = + device.updateAttachmentPoint(entity.getSwitchDPID(), + entity.getSwitchPort().shortValue(), + entity.getLastSeenTimestamp().getTime()); if (moved) { sendDeviceMovedNotification(device); - if (logger.isDebugEnabled()) { - logger.debug("Device moved: attachment points {}," + + if (logger.isTraceEnabled()) { + logger.trace("Device moved: attachment points {}," + "entities {}", device.attachmentPoints, device.entities); } } else { - if (logger.isDebugEnabled()) { - logger.debug("Device attachment point updated: " + + if (logger.isTraceEnabled()) { + logger.trace("Device attachment point updated: " + "attachment points {}," + "entities {}", device.attachmentPoints, device.entities); } } - break; } + break; } if (deleteQueue != null) { for (Long l : deleteQueue) { Device dev = deviceMap.get(l); this.deleteDevice(dev); - - - // generate new device update - deviceUpdates = - updateUpdates(deviceUpdates, - new DeviceUpdate(dev, DELETE, null)); } } @@ -1479,9 +1487,9 @@ IFlowReconcileListener, IInfoProvider, IHAListener { for (Entity e : toRemove) { changedFields.addAll(findChangedFields(newDevice, e)); } + DeviceUpdate update = null; if (changedFields.size() > 0) - deviceUpdates.add(new DeviceUpdate(d, CHANGE, - changedFields)); + update = new DeviceUpdate(d, CHANGE, changedFields); if (!deviceMap.replace(newDevice.getDeviceKey(), d, @@ -1493,15 +1501,19 @@ IFlowReconcileListener, IInfoProvider, IHAListener { if (null != d) continue; } + if (update != null) + deviceUpdates.add(update); } else { - deviceUpdates.add(new DeviceUpdate(d, DELETE, null)); - if (!deviceMap.remove(d.getDeviceKey(), d)) + DeviceUpdate update = new DeviceUpdate(d, DELETE, null); + if (!deviceMap.remove(d.getDeviceKey(), d)) { // concurrent modification; try again // need to use device that is the map now for the next // iteration d = deviceMap.get(d.getDeviceKey()); if (null != d) continue; + } + deviceUpdates.add(update); } processUpdates(deviceUpdates); break; @@ -1591,8 +1603,9 @@ IFlowReconcileListener, IInfoProvider, IHAListener { } protected Device allocateDevice(Device device, - Entity entity) { - return new Device(device, entity); + Entity entity, + int insertionpoint) { + return new Device(device, entity, insertionpoint); } protected Device allocateDevice(Device device, Set <Entity> entities) { diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java index 36c5471fb7fb037b7c917a75c0f32e1875a8f638..5949a19e22d40542b83eef1660478a4655f8cce8 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java @@ -24,6 +24,7 @@ import net.floodlightcontroller.core.web.serializers.MACSerializer; import net.floodlightcontroller.core.web.serializers.DPIDSerializer; import net.floodlightcontroller.packet.IPv4; +import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.map.annotate.JsonSerialize; import org.openflow.util.HexString; @@ -145,6 +146,11 @@ public class Entity implements Comparable<Entity> { public Integer getSwitchPort() { return switchPort; } + + @JsonIgnore + public boolean hasSwitchPort() { + return (switchDPID != null && switchPort != null); + } public Date getLastSeenTimestamp() { return lastSeenTimestamp; diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index 45e02915901cb759c3dd6b76efb439e4e14e344d..7fde1cd72d66c592da299908097df4936ca4d57e 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -304,10 +304,6 @@ IFloodlightModule, IInfoProvider, IHAListener { return shuttingDown; } - public boolean isFastPort(long sw, short port) { - return false; - } - public boolean isTunnelPort(long sw, short port) { return false; } @@ -567,7 +563,7 @@ IFloodlightModule, IInfoProvider, IHAListener { } // For fast ports, do not send forward LLDPs or BDDPs. - if (!isReverse && autoPortFastFeature && isFastPort(sw, port)) + if (!isReverse && autoPortFastFeature && iofSwitch.isFastPort(port)) return; if (log.isTraceEnabled()) { @@ -692,7 +688,8 @@ IFloodlightModule, IInfoProvider, IHAListener { for (OFPhysicalPort ofp: iofSwitch.getEnabledPorts()) { if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber())) continue; - if (autoPortFastFeature && isFastPort(sw, ofp.getPortNumber())) + if (autoPortFastFeature && + iofSwitch.isFastPort(ofp.getPortNumber())) continue; // sends forward LLDP only non-fastports. @@ -1326,21 +1323,23 @@ IFloodlightModule, IInfoProvider, IHAListener { private void processNewPort(long sw, short p) { if (isLinkDiscoverySuppressed(sw, p)) { // Do nothing as link discovery is suppressed. + return; } - else if (autoPortFastFeature && isFastPort(sw, p)) { + + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (autoPortFastFeature && iofSwitch.isFastPort(p)) { // Do nothing as the port is a fast port. + return; } - else { - NodePortTuple npt = new NodePortTuple(sw, p); - discover(sw, p); - // if it is not a fast port, add it to quarantine. - if (!isFastPort(sw, p)) { - addToQuarantineQueue(npt); - } else { - // Add to maintenance queue to ensure that BDDP packets - // are sent out. - addToMaintenanceQueue(npt); - } + NodePortTuple npt = new NodePortTuple(sw, p); + discover(sw, p); + // if it is not a fast port, add it to quarantine. + if (!iofSwitch.isFastPort(p)) { + addToQuarantineQueue(npt); + } else { + // Add to maintenance queue to ensure that BDDP packets + // are sent out. + addToMaintenanceQueue(npt); } } @@ -2000,15 +1999,12 @@ IFloodlightModule, IInfoProvider, IHAListener { evTopoSwitch = new EventHistoryTopologySwitch(); } evTopoSwitch.dpid = sw.getId(); - if ((sw.getChannel() != null) && - (SocketAddress.class.isInstance( - sw.getChannel().getRemoteAddress()))) { - evTopoSwitch.ipv4Addr = - IPv4.toIPv4Address(((InetSocketAddress)(sw.getChannel(). - getRemoteAddress())).getAddress().getAddress()); + if ((SocketAddress.class.isInstance(sw.getInetAddress()))) { + evTopoSwitch.ipv4Addr = IPv4.toIPv4Address( + ((InetSocketAddress)(sw.getInetAddress())) + .getAddress().getAddress()); evTopoSwitch.l4Port = - ((InetSocketAddress)(sw.getChannel(). - getRemoteAddress())).getPort(); + ((InetSocketAddress)(sw.getInetAddress())).getPort(); } else { evTopoSwitch.ipv4Addr = 0; evTopoSwitch.l4Port = 0; diff --git a/src/main/java/org/openflow/protocol/OFMatch.java b/src/main/java/org/openflow/protocol/OFMatch.java index 0336d7dbdc0b681ff5e97f4720320221837e193f..391610b0c4bf4cdd9f68d1dd705ba1fc2093ce54 100644 --- a/src/main/java/org/openflow/protocol/OFMatch.java +++ b/src/main/java/org/openflow/protocol/OFMatch.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -21,7 +21,6 @@ import java.io.Serializable; import java.nio.ByteBuffer; import java.util.Arrays; - import org.codehaus.jackson.map.annotate.JsonSerialize; import org.jboss.netty.buffer.ChannelBuffer; import org.openflow.protocol.serializers.OFMatchJSONSerializer; @@ -29,17 +28,19 @@ import org.openflow.util.HexString; import org.openflow.util.U16; import org.openflow.util.U8; + /** * Represents an ofp_match structure - * + * * @author David Erickson (daviderickson@cs.stanford.edu) * @author Rob Sherwood (rob.sherwood@stanford.edu) - * + * */ @JsonSerialize(using=OFMatchJSONSerializer.class) public class OFMatch implements Cloneable, Serializable { + /** - * + * */ private static final long serialVersionUID = 1L; public static int MINIMUM_LENGTH = 40; @@ -80,6 +81,10 @@ public class OFMatch implements Cloneable, Serializable { * bits). */ + final public static int OFPFW_ALL_SANITIZED = + (((1 << 22) - 1) & ~OFPFW_NW_SRC_MASK & ~OFPFW_NW_DST_MASK) + | OFPFW_NW_SRC_ALL | OFPFW_NW_DST_ALL; + /* List of Strings for marshalling and unmarshalling to human readable forms */ final public static String STR_IN_PORT = "in_port"; final public static String STR_DL_DST = "dl_dst"; @@ -110,7 +115,7 @@ public class OFMatch implements Cloneable, Serializable { /** * By default, create a OFMatch that matches everything - * + * * (mostly because it's the least amount of work to make a valid OFMatch) */ public OFMatch() { @@ -131,7 +136,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get dl_dst - * + * * @return an arrays of bytes */ public byte[] getDataLayerDestination() { @@ -140,7 +145,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_dst - * + * * @param dataLayerDestination */ public OFMatch setDataLayerDestination(byte[] dataLayerDestination) { @@ -150,7 +155,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_dst, but first translate to byte[] using HexString - * + * * @param mac * A colon separated string of 6 pairs of octets, e..g., * "00:17:42:EF:CD:8D" @@ -166,7 +171,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get dl_src - * + * * @return an array of bytes */ public byte[] getDataLayerSource() { @@ -175,7 +180,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_src - * + * * @param dataLayerSource */ public OFMatch setDataLayerSource(byte[] dataLayerSource) { @@ -185,7 +190,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_src, but first translate to byte[] using HexString - * + * * @param mac * A colon separated string of 6 pairs of octets, e..g., * "00:17:42:EF:CD:8D" @@ -201,7 +206,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get dl_type - * + * * @return ether_type */ public short getDataLayerType() { @@ -210,7 +215,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_type - * + * * @param dataLayerType */ public OFMatch setDataLayerType(short dataLayerType) { @@ -220,7 +225,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get dl_vlan - * + * * @return vlan tag; VLAN_NONE == no tag */ public short getDataLayerVirtualLan() { @@ -229,7 +234,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_vlan - * + * * @param dataLayerVirtualLan */ public OFMatch setDataLayerVirtualLan(short dataLayerVirtualLan) { @@ -239,7 +244,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get dl_vlan_pcp - * + * * @return */ public byte getDataLayerVirtualLanPriorityCodePoint() { @@ -248,7 +253,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set dl_vlan_pcp - * + * * @param pcp */ public OFMatch setDataLayerVirtualLanPriorityCodePoint(byte pcp) { @@ -258,7 +263,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get in_port - * + * * @return */ public short getInputPort() { @@ -267,7 +272,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set in_port - * + * * @param inputPort */ public OFMatch setInputPort(short inputPort) { @@ -277,7 +282,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get nw_dst - * + * * @return */ public int getNetworkDestination() { @@ -286,7 +291,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set nw_dst - * + * * @param networkDestination */ public OFMatch setNetworkDestination(int networkDestination) { @@ -297,10 +302,10 @@ public class OFMatch implements Cloneable, Serializable { /** * Parse this match's wildcard fields and return the number of significant * bits in the IP destination field. - * + * * NOTE: this returns the number of bits that are fixed, i.e., like CIDR, * not the number of bits that are free like OpenFlow encodes. - * + * * @return a number between 0 (matches all IPs) and 63 ( 32>= implies exact * match) */ @@ -313,10 +318,10 @@ public class OFMatch implements Cloneable, Serializable { /** * Parse this match's wildcard fields and return the number of significant * bits in the IP destination field. - * + * * NOTE: this returns the number of bits that are fixed, i.e., like CIDR, * not the number of bits that are free like OpenFlow encodes. - * + * * @return a number between 0 (matches all IPs) and 32 (exact match) */ public int getNetworkSourceMaskLen() { @@ -327,7 +332,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get nw_proto - * + * * @return */ public byte getNetworkProtocol() { @@ -336,7 +341,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set nw_proto - * + * * @param networkProtocol */ public OFMatch setNetworkProtocol(byte networkProtocol) { @@ -346,7 +351,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get nw_src - * + * * @return */ public int getNetworkSource() { @@ -355,7 +360,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set nw_src - * + * * @param networkSource */ public OFMatch setNetworkSource(int networkSource) { @@ -367,7 +372,7 @@ public class OFMatch implements Cloneable, Serializable { * Get nw_tos * OFMatch stores the ToS bits as top 6-bits, so right shift by 2 bits * before returning the value - * + * * @return : 6-bit DSCP value (0-63) */ public byte getNetworkTypeOfService() { @@ -378,18 +383,18 @@ public class OFMatch implements Cloneable, Serializable { * Set nw_tos * OFMatch stores the ToS bits as top 6-bits, so left shift by 2 bits * before storing the value - * + * * @param networkTypeOfService : 6-bit DSCP value (0-63) */ public OFMatch setNetworkTypeOfService(byte networkTypeOfService) { this.networkTypeOfService = (byte)(networkTypeOfService << 2); return this; } - + /** * Get tp_dst - * + * * @return */ public short getTransportDestination() { @@ -398,7 +403,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set tp_dst - * + * * @param transportDestination */ public OFMatch setTransportDestination(short transportDestination) { @@ -408,7 +413,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Get tp_src - * + * * @return */ public short getTransportSource() { @@ -417,7 +422,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set tp_src - * + * * @param transportSource */ public OFMatch setTransportSource(short transportSource) { @@ -427,16 +432,25 @@ public class OFMatch implements Cloneable, Serializable { /** * Get wildcards - * + * * @return */ public int getWildcards() { return this.wildcards; } + /** + * Get wildcards + * + * @return + */ + public Wildcards getWildcardObj() { + return Wildcards.of(wildcards); + } + /** * Set wildcards - * + * * @param wildcards */ public OFMatch setWildcards(int wildcards) { @@ -444,16 +458,22 @@ public class OFMatch implements Cloneable, Serializable { return this; } + /** set the wildcard using the Wildcards convenience object */ + public OFMatch setWildcards(Wildcards wildcards) { + this.wildcards = wildcards.getInt(); + return this; + } + /** * Initializes this OFMatch structure with the corresponding data from the * specified packet. - * + * * Must specify the input port, to ensure that this.in_port is set * correctly. - * + * * Specify OFPort.NONE or OFPort.ANY if input port not applicable or * available - * + * * @param packetData * The packet's data * @param inputPort @@ -573,7 +593,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Read this message off the wire from the specified ByteBuffer - * + * * @param data */ public void readFrom(ChannelBuffer data) { @@ -599,7 +619,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Write this message's binary format to the specified ByteBuffer - * + * * @param data */ public void writeTo(ChannelBuffer data) { @@ -718,10 +738,10 @@ public class OFMatch implements Cloneable, Serializable { /** * Output a dpctl-styled string, i.e., only list the elements that are not * wildcarded - * + * * A match-everything OFMatch outputs "OFMatch[]" - * - * @return + * + * @return * "OFMatch[dl_src:00:20:01:11:22:33,nw_src:192.168.0.0/24,tp_dst:80]" */ @Override @@ -778,6 +798,56 @@ public class OFMatch implements Cloneable, Serializable { return "OFMatch[" + str + "]"; } + /** + * debug a set of wildcards + */ + public static String debugWildCards(int wildcards) { + String str = ""; + + // l1 + if ((wildcards & OFPFW_IN_PORT) != 0) + str += "|" + STR_IN_PORT; + + // l2 + if ((wildcards & OFPFW_DL_DST) != 0) + str += "|" + STR_DL_DST; + if ((wildcards & OFPFW_DL_SRC) != 0) + str += "|" + STR_DL_SRC; + if ((wildcards & OFPFW_DL_TYPE) != 0) + str += "|" + STR_DL_TYPE; + if ((wildcards & OFPFW_DL_VLAN) != 0) + str += "|" + STR_DL_VLAN; + if ((wildcards & OFPFW_DL_VLAN_PCP) != 0) + str += "|" + STR_DL_VLAN_PCP; + + int nwDstMask = Math.max(32 - ((wildcards & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT), + 0); + int nwSrcMask = Math.max(32 - ((wildcards & OFPFW_NW_SRC_MASK) >> OFPFW_NW_SRC_SHIFT), + 0); + + // l3 + if (nwDstMask < 32) + str += "|" + STR_NW_DST + "(/" +nwDstMask + ")"; + + if (nwSrcMask < 32) + str += "|" + STR_NW_SRC + "(/" + nwSrcMask + ")"; + + if ((wildcards & OFPFW_NW_PROTO) != 0) + str += "|" + STR_NW_PROTO; + if ((wildcards & OFPFW_NW_TOS) != 0) + str += "|" + STR_NW_TOS; + + // l4 + if ((wildcards & OFPFW_TP_DST) != 0) + str += "|" + STR_TP_DST; + if ((wildcards & OFPFW_TP_SRC) != 0) + str += "|" + STR_TP_SRC; + if ((str.length() > 0) && (str.charAt(0) == '|')) + str = str.substring(1); // trim the leading "," + // done + return str; + } + private String cidrToString(int ip, int prefix) { String str; if (prefix >= 32) { @@ -826,7 +896,7 @@ public class OFMatch implements Cloneable, Serializable { * <p> * The CIDR-style netmasks assume 32 netmask if none given, so: * "128.8.128.118/32" is the same as "128.8.128.118" - * + * * @param match * a key=value comma separated string, e.g. * "in_port=5,ip_dst=192.168.0.0/16,tp_src=80" @@ -904,7 +974,7 @@ public class OFMatch implements Cloneable, Serializable { /** * Set the networkSource or networkDestionation address and their wildcards * from the CIDR string - * + * * @param cidr * "192.168.0.0/16" or "172.16.1.5" * @param which diff --git a/src/main/java/org/openflow/protocol/OFMessage.java b/src/main/java/org/openflow/protocol/OFMessage.java index 7ea69a7ef921334a5bce1bd18ac53b28ed1d6384..38f4e477317e79682416b2145985bd4fa3948f5f 100644 --- a/src/main/java/org/openflow/protocol/OFMessage.java +++ b/src/main/java/org/openflow/protocol/OFMessage.java @@ -41,6 +41,7 @@ import org.openflow.util.U8; * @author Rob Sherwood (rob.sherwood@stanford.edu) - Feb 3, 2010 */ public class OFMessage { + public static final int MAXIMUM_LENGTH = (1 << 16) - 1; public static byte OFP_VERSION = 0x01; public static int MINIMUM_LENGTH = 8; @@ -48,14 +49,14 @@ public class OFMessage { protected OFType type; protected short length; protected int xid; - + private ConcurrentHashMap<String, Object> storage; - + public OFMessage() { storage = null; this.version = OFP_VERSION; } - + protected synchronized ConcurrentHashMap<String, Object> getMessageStore() { if (storage == null) { storage = new ConcurrentHashMap<String, Object>();; @@ -181,6 +182,7 @@ public class OFMessage { * Returns a summary of the message * @return "ofmsg=v=$version;t=$type:l=$len:xid=$xid" */ + @Override public String toString() { return "ofmsg" + ":v=" + U8.f(this.getVersion()) + @@ -230,7 +232,7 @@ public class OFMessage { } return true; } - + public static String getDataAsString(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { Ethernet eth; diff --git a/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java b/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java index 124a4013aaf3437db18041da630781d95bb3c9a6..9b3a6ca608aee1b37498e1b53f623ab28cb1aa10 100644 --- a/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java +++ b/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java @@ -41,7 +41,7 @@ public abstract class OFStatisticsMessageBase extends OFMessage implements // TODO: this should be List<? extends OFStatistics>, to // allow for type safe assignments of lists of specific message - protected List<OFStatistics> statistics; + protected List<? extends OFStatistics> statistics; /** * @return the statisticType @@ -74,7 +74,7 @@ public abstract class OFStatisticsMessageBase extends OFMessage implements /** * @return the statistics */ - public List<OFStatistics> getStatistics() { + public List<? extends OFStatistics> getStatistics() { return statistics; } @@ -84,7 +84,7 @@ public abstract class OFStatisticsMessageBase extends OFMessage implements * flow stats request, port statsrequest) * * @return the first and only element in the list of statistics - * @throw NoSuchElementException if the list does not contain exactly one + * @throw IllegalArgumentException if the list does not contain exactly one * element */ public OFStatistics getFirstStatistics() { @@ -98,7 +98,7 @@ public abstract class OFStatisticsMessageBase extends OFMessage implements /** * @param statistics the statistics to set */ - public void setStatistics(List<OFStatistics> statistics) { + public void setStatistics(List<? extends OFStatistics> statistics) { this.statistics = statistics; } diff --git a/src/main/java/org/openflow/protocol/Wildcards.java b/src/main/java/org/openflow/protocol/Wildcards.java new file mode 100644 index 0000000000000000000000000000000000000000..f982e5ace6f7165c91ff265ef5b78b8e2d50da91 --- /dev/null +++ b/src/main/java/org/openflow/protocol/Wildcards.java @@ -0,0 +1,587 @@ +package org.openflow.protocol; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; + +import com.google.common.base.Joiner; + +/** + * a more user friendly representation of the wildcards bits in an OpenFlow + * match. The Wildcards object is + * <ul> + * <li>immutable (i.e., threadsafe)</li> + * <li>instance managed (don't instantiate it yourself), instead call "of"</li> + * <ul> + * <p> + * You can construct a Wildcard object from either its integer representation + * </p> + * <code> + * Wildcard.of(0x3820e0); + * </code> + * <p> + * Or start with either an empty or full wildcard, and select/unselect foo. + * </p> + * <code> + * Wildcard w = Wildcards.NONE + * .set(Flag.DL_SRC, Flag. DL_DST, Flag.DL_VLAN_PCP) + * .setNwDstMask(8) + * .setNwSrcMask(8); + * </code> + * <p> + * <b>Remember:</b> Wildcards objects are immutable. set... operations have + * <b>NO EFFECT</b> on the current wildcard object. You HAVE to use the returned + * changed object. + * </p> + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class Wildcards { + + public final static Wildcards FULL = new Wildcards(OFMatch.OFPFW_ALL_SANITIZED); + private static final int FULL_INT = FULL.getInt(); + + public final static Wildcards EXACT = new Wildcards(0); + + // floodlight common case: matches on inport + l2 + public final static int INT_INPORT_L2_MATCH = 0x3820e0; + public final static Wildcards INPORT_L2_MATCH = new Wildcards( + INT_INPORT_L2_MATCH); + + /** + * enum type for the binary flags that can be set in the wildcards field of + * an OFMatch. Replaces the unwieldy c-ish int constants in OFMatch. + */ + public static enum Flag { + IN_PORT(OFMatch.OFPFW_IN_PORT), /* Switch input port. */ + DL_VLAN(OFMatch.OFPFW_DL_VLAN), /* VLAN id. */ + DL_SRC(OFMatch.OFPFW_DL_SRC), /* Ethernet source address. */ + DL_DST(OFMatch.OFPFW_DL_DST), /* Ethernet destination addr */ + DL_TYPE(OFMatch.OFPFW_DL_TYPE), /* Ethernet frame type. */ + NW_PROTO(OFMatch.OFPFW_NW_PROTO), /* IP protocol. */ + TP_SRC(OFMatch.OFPFW_TP_SRC), /* TCP/UDP source port. */ + TP_DST(OFMatch.OFPFW_TP_DST), /* TCP/UDP destination port. */ + DL_VLAN_PCP(OFMatch.OFPFW_DL_VLAN_PCP), /* VLAN priority. */ + NW_SRC(-1) { /* + * virtual NW_SRC flag => translates to the strange 6 bits + * in the header + */ + @Override + boolean isBolean() { + return false; + } + + @Override + int getInt(int flags) { + return ((flags & OFMatch.OFPFW_NW_SRC_MASK) >> OFMatch.OFPFW_NW_SRC_SHIFT); + } + + @Override + int setInt(int flags, int srcMask) { + return (flags & ~OFMatch.OFPFW_NW_SRC_MASK) | (srcMask << OFMatch.OFPFW_NW_SRC_SHIFT); + } + + @Override + int wildcard(int flags) { + return flags & ~OFMatch.OFPFW_NW_SRC_MASK; + } + + @Override + int matchOn(int flags) { + return flags | OFMatch.OFPFW_NW_SRC_ALL; + } + + @Override + boolean isPartiallyOn(int flags) { + int intValue = getInt(flags); + return intValue > 0 && intValue < 32; + } + + @Override + boolean isFullyOn(int flags) { + return getInt(flags) >= 32; + } + + }, + NW_DST(-1) { /* + * virtual NW_SRC flag => translates to the strange 6 bits + * in the header + */ + @Override + boolean isBolean() { + return false; + } + + @Override + int getInt(int flags) { + return ((flags & OFMatch.OFPFW_NW_DST_MASK) >> OFMatch.OFPFW_NW_DST_SHIFT); + } + + @Override + int setInt(int flags, int srcMask) { + return (flags & ~OFMatch.OFPFW_NW_DST_MASK) | (srcMask << OFMatch.OFPFW_NW_DST_SHIFT); + } + + @Override + int wildcard(int flags) { + return flags & ~OFMatch.OFPFW_NW_DST_MASK; + } + + @Override + int matchOn(int flags) { + return flags | OFMatch.OFPFW_NW_DST_ALL; + } + + @Override + boolean isFullyOn(int flags) { + return getInt(flags) >= 32; + } + }, + NW_TOS(OFMatch.OFPFW_NW_TOS); /* IP ToS (DSCP field, 6 bits). */ + + final int bitPosition; + + Flag(int bitPosition) { + this.bitPosition = bitPosition; + } + + /** + * @return a modified OF-1.0 flags field with this flag cleared (match + * on this field) + */ + int matchOn(int flags) { + return flags & ~this.bitPosition; + } + + /** + * @return a modified OF-1.0 flags field with this flag set (wildcard + * this field) + */ + int wildcard(int flags) { + return flags | this.bitPosition; + } + + /** + * @return true iff this is a true boolean flag that can either be off + * or on.True in OF-1.0 for all fields except NW_SRC and NW_DST + */ + boolean isBolean() { + return false; + } + + /** + * @return true iff this wildcard field is currently 'partially on'. + * Always false for true Boolean Flags. Can be true in OF-1.0 + * for NW_SRC, NW_DST. + */ + boolean isPartiallyOn(int flags) { + return false; + } + + /** + * @return true iff this wildcard field currently fully on (fully + * wildcarded). Equivalent to the boolean flag being set in the + * bitmask for bit flags, and to the wildcarded bit length set + * to >=32 for NW_SRC and NW_DST + * @param flags + * @return + */ + boolean isFullyOn(int flags) { + return (flags & this.bitPosition) != 0; + } + + /** + * set the integer representation of this flag. only for NW_SRC and + * NW_DST + */ + int setInt(int flags, int srcMask) { + throw new UnsupportedOperationException(); + } + + /** + * set the integer representation of this flag. only for NW_SRC and + * NW_DST + */ + int getInt(int flags) { + throw new UnsupportedOperationException(); + } + + + } + + private final int flags; + + /** private constructor. use Wildcard.of() instead */ + private Wildcards(int flags) { + this.flags = flags; + } + + /** + * return a wildcard object matching the given int flags. May reuse / cache + * frequently used wildcard instances. Don't rely on it though (use equals + * not ==). + * + * @param flags + * @return + */ + public static Wildcards of(int paramFlags) { + int flags = sanitizeInt(paramFlags); + switch(flags) { + case 0x0000: + return EXACT; + case OFMatch.OFPFW_ALL_SANITIZED: + return FULL; + case INT_INPORT_L2_MATCH: + return INPORT_L2_MATCH; + default: + return new Wildcards(flags); + } + } + + /** convience method return a wildcard for exactly one set flag */ + public static Wildcards of(Wildcards.Flag setFlag) { + return Wildcards.of(setFlag.wildcard(0)); + } + + /** convience method return a wildcard for exactly two set flags */ + public static Wildcards of(Wildcards.Flag setFlag, Wildcards.Flag setFlag2) { + return Wildcards.of(setFlag.wildcard(setFlag2.wildcard(0))); + } + + /** convience method return a wildcard for an arbitrary number of set flags */ + public static Wildcards of(Wildcards.Flag... setFlags) { + int flags = 0; + for (Wildcards.Flag flag : setFlags) + flags = flag.wildcard(0); + return Wildcards.of(flags); + } + + /** convience method return a wildcards for ofmatches that match on one flag */ + public static Wildcards ofMatches(Wildcards.Flag setFlag) { + return Wildcards.of(setFlag.matchOn(FULL_INT)); + } + + /** + * convience method return a wildcard for for an ofmatch that match on two + * flags + */ + public static Wildcards ofMatches(Wildcards.Flag setFlag, Wildcards.Flag setFlag2) { + return Wildcards.of(setFlag.matchOn(setFlag2.matchOn(FULL_INT))); + } + + /** + * convience method return a wildcard for an ofmatch that amtch on an + * arbitrary number of set flags + */ + public static Wildcards ofMatches(Wildcards.Flag... setFlags) { + int flags = FULL_INT; + for (Wildcards.Flag flag : setFlags) + flags = flag.matchOn(flags); + return Wildcards.of(flags); + } + + /** + * return a Wildcards object that has the given flags set + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards wildcard(Wildcards.Flag flag) { + int flags = flag.wildcard(this.flags); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcards object that has the given flags set + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards wildcard(Wildcards.Flag flag, Wildcards.Flag flag2) { + int flags = flag.wildcard(flag2.wildcard(this.flags)); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcards object that has the given flags wildcarded + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards wildcard(Wildcards.Flag... setFlags) { + int flags = this.flags; + for (Wildcards.Flag flag : setFlags) + flags = flag.wildcard(flags); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcards object that matches on exactly the given flag + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards matchOn(Wildcards.Flag flag) { + int flags = flag.matchOn(this.flags); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcards object that matches on exactly the given flags + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards matchOn(Wildcards.Flag flag, Wildcards.Flag flag2) { + int flags = flag.matchOn(flag2.matchOn(this.flags)); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcards object that matches on exactly the given flags + * <p> + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + */ + public Wildcards matchOn(Wildcards.Flag... setFlags) { + int flags = this.flags; + for (Wildcards.Flag flag : setFlags) + flags = flag.matchOn(flags); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return the nw src mask in normal CIDR style, e.g., 8 means x.x.x.x/8 + * means 8 bits wildcarded + */ + public int getNwSrcMask() { + return Math.max(0, 32 - Flag.NW_SRC.getInt(flags)); + } + + /** + * return the nw dst mask in normal CIDR style, e.g., 8 means x.x.x.x/8 + * means 8 bits wildcarded + */ + public int getNwDstMask() { + return Math.max(0, 32 - Flag.NW_DST.getInt(flags)); + } + + /** + * return a Wildcard object that has the given nwSrcCidrMask set. + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + * + * @param srcCidrMask + * source mask to set in <b>normal CIDR notation</b>, i.e., 8 + * means x.x.x.x/8 + * @return a modified object + */ + public Wildcards withNwSrcMask(int srcCidrMask) { + int flags = Flag.NW_SRC.setInt(this.flags, Math.max(0, 32 - srcCidrMask)); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcard object that has the given nwDstCidrMask set. + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + * + * @param dstCidrMask + * dest mask to set in <b>normal CIDR notation</b>, i.e., 8 means + * x.x.x.x/8 + * @return a modified object + */ + public Wildcards withNwDstMask(int dstCidrMask) { + int flags = Flag.NW_DST.setInt(this.flags, Math.max(0, 32 - dstCidrMask)); + if (flags == this.flags) + return this; + else + return new Wildcards(flags); + } + + /** + * return a Wildcard object that is inverted to this wildcard object. + * <b>NOTE:</b> NOT a mutator function. 'this' wildcard object stays + * unmodified. </b> + * @return a modified object + */ + public Wildcards inverted() { + return Wildcards.of(flags ^ OFMatch.OFPFW_ALL_SANITIZED); + } + + public boolean isWildcarded(Flag flag) { + return flag.isFullyOn(flags); + } + + /** + * return all wildcard flags that are fully wildcarded as an EnumSet. Do not + * modify. Note: some flags (like NW_SRC and NW_DST) that are partially + * wildcarded are not returned in this set. + * + * @return the EnumSet of wildcards + */ + public EnumSet<Wildcards.Flag> getWildcardedFlags() { + EnumSet<Wildcards.Flag> res = EnumSet.noneOf(Wildcards.Flag.class); + for (Wildcards.Flag flag : Flag.values()) { + if (flag.isFullyOn(flags)) { + res.add(flag); + } + } + return res; + } + + /** return the OpenFlow 'wire' integer representation of these wildcards */ + public int getInt() { + return flags; + } + + /** + * return the OpenFlow 'wire' integer representation of these wildcards. + * Sanitize nw_src and nw_dst to be max. 32 (values > 32 are technically + * possible, but don't make semantic sense) + */ + public static int sanitizeInt(int flags) { + if (((flags & OFMatch.OFPFW_NW_SRC_MASK) >> OFMatch.OFPFW_NW_SRC_SHIFT) > 32) { + flags = (flags & ~OFMatch.OFPFW_NW_SRC_MASK) | OFMatch.OFPFW_NW_SRC_ALL; + } + if (((flags & OFMatch.OFPFW_NW_DST_MASK) >> OFMatch.OFPFW_NW_DST_SHIFT) > 32) { + flags = (flags & ~OFMatch.OFPFW_NW_DST_MASK) | OFMatch.OFPFW_NW_DST_ALL; + } + return flags; + } + + /** + * is this a wildcard set that has all flags set + and full (/0) nw_src and + * nw_dst wildcarding ? + */ + public boolean isFull() { + return flags == OFMatch.OFPFW_ALL || flags == OFMatch.OFPFW_ALL_SANITIZED; + } + + /** is this a wildcard of an exact match */ + public boolean isExact() { + return flags == 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + flags; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Wildcards other = (Wildcards) obj; + if (flags != other.flags) + return false; + return true; + } + + private final static Joiner pipeJoiner = Joiner.on("|"); + + @Override + public String toString() { + List<String> res = new ArrayList<String>(); + for (Wildcards.Flag flag : Flag.values()) { + if (flag.isFullyOn(flags)) { + res.add(flag.name().toLowerCase()); + } + } + + if (Flag.NW_SRC.isPartiallyOn(flags)) { + res.add("nw_src(/" + getNwSrcMask() + ")"); + } + + if (Flag.NW_DST.isPartiallyOn(flags)) { + res.add("nw_dst(/" + getNwDstMask() + ")"); + } + + return pipeJoiner.join(res); + } + + private final static Joiner commaJoiner = Joiner.on(", "); + + /** a Java expression that constructs 'this' wildcards set */ + public String toJava() { + if(isFull()) { + return "Wildcards.FULL"; + } else if (isExact()){ + return "Wildcards.EXACT"; + } + + StringBuilder b = new StringBuilder(); + + EnumSet<Flag> myFlags = getWildcardedFlags(); + if (myFlags.size() < 3) { + // default to start with empty + b.append("Wildcards.of(" + + commaJoiner.join(prefix("Flag.", myFlags.iterator())) + ")"); + } else { + // too many - start with full + + EnumSet<Flag> invFlags = inverted().getWildcardedFlags(); + b.append("Wildcards.ofMatches(" + + commaJoiner.join(prefix("Flag.", invFlags.iterator())) + ")"); + } + if (Flag.NW_SRC.isPartiallyOn(flags)) { + b.append(".setNwSrcMask(" + getNwSrcMask() + ")"); + } + if (Flag.NW_DST.isPartiallyOn(flags)) { + b.append(".setNwDstMask(" + getNwDstMask() + ")"); + } + return b.toString(); + } + + private Iterator<String> prefix(final String prefix, final Iterator<?> i) { + return new Iterator<String>() { + + @Override + public boolean hasNext() { + return i.hasNext(); + } + + @Override + public String next() { + Object next = i.next(); + return next == null ? null : prefix + next.toString(); + } + + @Override + public void remove() { + i.remove(); + } + }; + } + + +} \ No newline at end of file diff --git a/src/main/java/org/openflow/protocol/factory/BasicFactory.java b/src/main/java/org/openflow/protocol/factory/BasicFactory.java index 7b15e82612326158f0eb5184518c20b11be7ae65..7b06f2c0c91a5a8a966be3430d34f4dbbb9f23cc 100644 --- a/src/main/java/org/openflow/protocol/factory/BasicFactory.java +++ b/src/main/java/org/openflow/protocol/factory/BasicFactory.java @@ -44,9 +44,20 @@ import org.openflow.protocol.vendor.OFVendorId; */ public class BasicFactory implements OFMessageFactory, OFActionFactory, OFStatisticsFactory, OFVendorDataFactory { + + /** + * create and return a new instance of a message for OFType t. Also injects + * factories for those message types that implement the *FactoryAware + * interfaces. + * + * @return a newly created instance that may be modified / used freely by + * the caller + */ @Override public OFMessage getMessage(OFType t) { - return t.newInstance(); + OFMessage message = t.newInstance(); + injectFactories(message); + return message; } @Override @@ -92,18 +103,7 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, if (ofm == null) return null; - if (ofm instanceof OFActionFactoryAware) { - ((OFActionFactoryAware)ofm).setActionFactory(this); - } - if (ofm instanceof OFMessageFactoryAware) { - ((OFMessageFactoryAware)ofm).setMessageFactory(this); - } - if (ofm instanceof OFStatisticsFactoryAware) { - ((OFStatisticsFactoryAware)ofm).setStatisticsFactory(this); - } - if (ofm instanceof OFVendorDataFactoryAware) { - ((OFVendorDataFactoryAware)ofm).setVendorDataFactory(this); - } + injectFactories(ofm); ofm.readFrom(data); if (OFMessage.class.equals(ofm.getClass())) { // advance the position for un-implemented messages @@ -124,6 +124,21 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, } } + private void injectFactories(OFMessage ofm) { + if (ofm instanceof OFActionFactoryAware) { + ((OFActionFactoryAware)ofm).setActionFactory(this); + } + if (ofm instanceof OFMessageFactoryAware) { + ((OFMessageFactoryAware)ofm).setMessageFactory(this); + } + if (ofm instanceof OFStatisticsFactoryAware) { + ((OFStatisticsFactoryAware)ofm).setStatisticsFactory(this); + } + if (ofm instanceof OFVendorDataFactoryAware) { + ((OFVendorDataFactoryAware)ofm).setVendorDataFactory(this); + } + } + @Override public OFAction getAction(OFActionType t) { return t.newInstance(); @@ -194,7 +209,7 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, * length of statistics * @param limit * number of statistics to grab; 0 == all - * + * * @return list of statistics */ @@ -230,7 +245,7 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, * though we have a full message. Found when NOX sent * agg_stats request with wrong agg statistics length (52 * instead of 56) - * + * * just throw the rest away, or we will break framing */ data.readerIndex(start + length); @@ -247,7 +262,7 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, OFVendorDataType vendorDataType) { if (vendorDataType == null) return null; - + return vendorDataType.newInstance(); } @@ -259,6 +274,7 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, * @param length the length to the end of the enclosing message. * @return an OFVendorData instance */ + @Override public OFVendorData parseVendorData(int vendor, ChannelBuffer data, int length) { OFVendorDataType vendorDataType = null; @@ -268,13 +284,13 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, vendorDataType = vendorId.parseVendorDataType(data, length); data.resetReaderIndex(); } - + OFVendorData vendorData = getVendorData(vendorId, vendorDataType); if (vendorData == null) vendorData = new OFByteArrayVendorData(); vendorData.readFrom(data, length); - + return vendorData; } diff --git a/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java b/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java index 6799fa3618e1f2303a84f4c9a8085db873261cd9..86ad782d8b56a4dff1be90e6f3af503b282adb87 100644 --- a/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java +++ b/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java @@ -213,4 +213,13 @@ public class OFDescriptionStatistics implements OFStatistics { } return true; } + + @Override + public String toString() { + return "Switch Desc - Vendor: " + manufacturerDescription + + " Model: " + hardwareDescription + + " Make: " + datapathDescription + + " Version: " + softwareDescription + + " S/N: " + serialNumber; + } } diff --git a/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorExtensions.java b/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..98f88b242f62a34a3ff4e45b9197f8da58ecc9ec --- /dev/null +++ b/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorExtensions.java @@ -0,0 +1,30 @@ +package org.openflow.vendor.nicira; + +import org.openflow.protocol.vendor.OFBasicVendorDataType; +import org.openflow.protocol.vendor.OFBasicVendorId; +import org.openflow.protocol.vendor.OFVendorId; + +public class OFNiciraVendorExtensions { + private static boolean initialized = false; + + public static synchronized void initialize() { + if (initialized) + return; + + // Configure openflowj to be able to parse the role request/reply + // vendor messages. + OFBasicVendorId niciraVendorId = + new OFBasicVendorId(OFNiciraVendorData.NX_VENDOR_ID, 4); + OFVendorId.registerVendorId(niciraVendorId); + OFBasicVendorDataType roleRequestVendorData = + new OFBasicVendorDataType(OFRoleRequestVendorData.NXT_ROLE_REQUEST, + OFRoleRequestVendorData.getInstantiable()); + niciraVendorId.registerVendorDataType(roleRequestVendorData); + OFBasicVendorDataType roleReplyVendorData = + new OFBasicVendorDataType(OFRoleReplyVendorData.NXT_ROLE_REPLY, + OFRoleReplyVendorData.getInstantiable()); + niciraVendorId.registerVendorDataType(roleReplyVendorData); + + initialized = true; + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java index 177c46dbb5bcd534d8f4426b4a24a0be012c5d44..195f1c042cfcf2f90878ae7ced4f773eb1fdb998 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -45,6 +46,7 @@ import net.floodlightcontroller.core.internal.Controller.IUpdate; import net.floodlightcontroller.core.internal.Controller.SwitchUpdate; import net.floodlightcontroller.core.internal.Controller.SwitchUpdateType; import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState; +import net.floodlightcontroller.core.internal.RoleChanger.PendingRoleRequestEntry; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.core.test.MockThreadPoolService; @@ -68,7 +70,6 @@ import org.easymock.EasyMock; import org.jboss.netty.channel.Channel; 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.OFPacketIn; @@ -101,6 +102,8 @@ public class ControllerTest extends FloodlightTestCase private Controller controller; private MockThreadPoolService tp; + private boolean test_bind_order = false; + private List<String> bind_order; @Override public void setUp() throws Exception { @@ -482,6 +485,7 @@ public class ControllerTest extends FloodlightTestCase Channel channel = createMock(Channel.class); oldsw.setChannel(channel); + expect(channel.getRemoteAddress()).andReturn(null); expect(channel.close()).andReturn(null); IOFSwitch newsw = createMock(IOFSwitch.class); @@ -509,6 +513,7 @@ public class ControllerTest extends FloodlightTestCase Channel channel = createMock(Channel.class); oldsw.setChannel(channel); expect(channel.close()).andReturn(null); + expect(channel.getRemoteAddress()).andReturn(null); IOFSwitch newsw = createMock(IOFSwitch.class); expect(newsw.getId()).andReturn(0L).anyTimes(); @@ -547,7 +552,7 @@ public class ControllerTest extends FloodlightTestCase return "dummy"; } @Override - public void switchPortChanged(Long switchId) { + public synchronized void switchPortChanged(Long switchId) { nPortChanged++; notifyAll(); } @@ -758,10 +763,11 @@ public class ControllerTest extends FloodlightTestCase assertTrue("Check that update is HARoleUpdate", upd instanceof Controller.HARoleUpdate); Controller.HARoleUpdate roleUpd = (Controller.HARoleUpdate)upd; - assertSame(null, roleUpd.oldRole); + assertSame(Role.MASTER, roleUpd.oldRole); assertSame(Role.SLAVE, roleUpd.newRole); } + @SuppressWarnings("unchecked") @Test public void testCheckSwitchReady() { OFChannelState state = new OFChannelState(); @@ -821,13 +827,13 @@ public class ControllerTest extends FloodlightTestCase // setupSwitchForAddSwitch(chdlr.sw, 0L); // chdlr.sw.clearAllFlowMods(); desc.setManufacturerDescription("test vendor"); + controller.roleChanger.submitRequest( + (List<IOFSwitch>)EasyMock.anyObject(), + (Role)EasyMock.anyObject()); replay(controller.roleChanger); chdlr.checkSwitchReady(); verify(controller.roleChanger); assertSame(OFChannelState.HandshakeState.READY, state.hsState); - assertSame(chdlr.sw, controller.activeSwitches.get(0L)); - assertTrue(controller.connectedSwitches.contains(chdlr.sw)); - assertTrue(state.firstRoleReplyReceived); reset(controller.roleChanger); controller.connectedSwitches.clear(); controller.activeSwitches.clear(); @@ -846,7 +852,6 @@ public class ControllerTest extends FloodlightTestCase assertSame(OFChannelState.HandshakeState.READY, state.hsState); assertTrue(controller.activeSwitches.isEmpty()); assertFalse(controller.connectedSwitches.isEmpty()); - assertTrue(state.firstRoleReplyReceived); Collection<IOFSwitch> swList = swListCapture.getValue(); assertEquals(1, swList.size()); } @@ -864,9 +869,6 @@ public class ControllerTest extends FloodlightTestCase OFChannelState state = new OFChannelState(); Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); - OFSwitchImpl sw = new OFSwitchImpl(); - sw.stringId = "1"; - chdlr.sw = sw; // Swith should be bound of OFSwitchImpl (default) state.hsState = OFChannelState.HandshakeState.HELLO; @@ -886,7 +888,6 @@ public class ControllerTest extends FloodlightTestCase assertTrue(!(chdlr.sw instanceof TestSwitchClass)); // Switch should be bound to TestSwitchImpl - chdlr.sw = sw; state.switchBindingDone = false; desc.setManufacturerDescription("test1 switch"); desc.setHardwareDescription("version 1.0"); @@ -897,7 +898,6 @@ public class ControllerTest extends FloodlightTestCase assertTrue(chdlr.sw instanceof TestSwitchClass); // Switch should be bound to Test11SwitchImpl - chdlr.sw = sw; state.switchBindingDone = false; desc.setManufacturerDescription("test11 switch"); desc.setHardwareDescription("version 1.1"); @@ -908,12 +908,50 @@ public class ControllerTest extends FloodlightTestCase assertTrue(chdlr.sw instanceof Test11SwitchClass); } + @Test + public void testBindSwitchOrder() { + List<String> order = new ArrayList<String>(3); + controller.addOFSwitchDriver("", this); + controller.addOFSwitchDriver("test switch", this); + controller.addOFSwitchDriver("test", this); + order.add("test switch"); + order.add("test"); + order.add(""); + test_bind_order = true; + + OFChannelState state = new OFChannelState(); + Controller.OFChannelHandler chdlr = + controller.new OFChannelHandler(state); + chdlr.sw = null; + + // Swith should be bound of OFSwitchImpl (default) + state.hsState = OFChannelState.HandshakeState.HELLO; + state.hasDescription = true; + state.hasGetConfigReply = true; + state.switchBindingDone = false; + OFDescriptionStatistics desc = new OFDescriptionStatistics(); + desc.setManufacturerDescription("test switch"); + desc.setHardwareDescription("version 0.9"); + state.description = desc; + OFFeaturesReply featuresReply = new OFFeaturesReply(); + featuresReply.setPorts(new ArrayList<OFPhysicalPort>()); + state.featuresReply = featuresReply; + + chdlr.bindSwitchToDriver(); + assertTrue(chdlr.sw instanceof OFSwitchImpl); + assertTrue(!(chdlr.sw instanceof TestSwitchClass)); + // Verify bind_order is called as expected + assertTrue(order.equals(bind_order)); + test_bind_order = false; + bind_order = null; + } + @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); + chdlr.sw = createMock(IOFSwitch.class); // Switch is active expect(chdlr.sw.getId()).andReturn(0L).anyTimes(); @@ -982,7 +1020,7 @@ public class ControllerTest extends FloodlightTestCase OFChannelState state = new OFChannelState(); state.hsState = HandshakeState.READY; Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); - chdlr.sw = createMock(OFSwitchImpl.class); + chdlr.sw = createMock(IOFSwitch.class); Channel ch = createMock(Channel.class); // the error returned when role request message is not supported by sw @@ -990,64 +1028,36 @@ public class ControllerTest extends FloodlightTestCase msg.setType(OFType.ERROR); 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(chdlr.sw.getRole()).andReturn(null).anyTimes(); - expect(ch.close()).andReturn(null); + setupPendingRoleRequest(chdlr.sw, xid, controller.role, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.SLAVE, false); + expect(chdlr.sw.getHARole()).andReturn(Role.SLAVE); + chdlr.sw.disconnectOutputStream(); replay(ch, chdlr.sw); chdlr.processOFMessage(msg); verify(ch, chdlr.sw); - assertTrue("state.firstRoleReplyReceived must be true", - state.firstRoleReplyReceived); assertTrue("activeSwitches must be empty", controller.activeSwitches.isEmpty()); reset(ch, chdlr.sw); - - // a different error message - should also reject role request - msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST); - msg.setErrorCode(OFBadRequestCode.OFPBRC_EPERM); - 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(chdlr.sw.getRole()).andReturn(null).anyTimes(); - expect(ch.close()).andReturn(null); - replay(ch, chdlr.sw); - - chdlr.processOFMessage(msg); - verify(ch, chdlr.sw); - assertTrue("state.firstRoleReplyReceived must be True even with EPERM", - 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); + setupPendingRoleRequest(chdlr.sw, xid, controller.role, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(controller.role, false); setupSwitchForAddSwitch(chdlr.sw, 0L); chdlr.sw.clearAllFlowMods(); - expect(chdlr.sw.getRole()).andReturn(null).anyTimes(); + expect(chdlr.sw.getHARole()).andReturn(null).anyTimes(); 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); @@ -1072,7 +1082,7 @@ public class ControllerTest extends FloodlightTestCase OFChannelState state = new OFChannelState(); state.hsState = HandshakeState.READY; Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state); - chdlr.sw = createMock(OFSwitchImpl.class); + chdlr.sw = createMock(IOFSwitch.class); return chdlr; } @@ -1087,7 +1097,18 @@ public class ControllerTest extends FloodlightTestCase roleReplyVendorData.setRole(nicira_role); return msg; } - + + // Helper function + protected void setupPendingRoleRequest(IOFSwitch sw, int xid, Role role, + long cookie) { + LinkedList<PendingRoleRequestEntry> pendingList = + new LinkedList<PendingRoleRequestEntry>(); + controller.roleChanger.pendingRequestMap.put(sw, pendingList); + PendingRoleRequestEntry entry = + new PendingRoleRequestEntry(xid, role, cookie); + pendingList.add(entry); + } + /** invalid role in role reply */ @Test public void testNiciraRoleReplyInvalidRole() @@ -1095,8 +1116,7 @@ public class ControllerTest extends FloodlightTestCase int xid = 424242; Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); Channel ch = createMock(Channel.class); - expect(chdlr.sw.getChannel()).andReturn(ch); - expect(ch.close()).andReturn(null); + chdlr.sw.disconnectOutputStream(); OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 232323); replay(chdlr.sw, ch); chdlr.processOFMessage(msg); @@ -1111,17 +1131,15 @@ public class ControllerTest extends FloodlightTestCase Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_MASTER); - - chdlr.sw.deliverRoleReply(xid, Role.MASTER); - expect(chdlr.sw.isActive()).andReturn(true); + + setupPendingRoleRequest(chdlr.sw, xid, Role.MASTER, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.MASTER, 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)); } @@ -1135,17 +1153,15 @@ public class ControllerTest extends FloodlightTestCase Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest(); OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_MASTER); - - chdlr.sw.deliverRoleReply(xid, Role.MASTER); - expect(chdlr.sw.isActive()).andReturn(true); + + setupPendingRoleRequest(chdlr.sw, xid, Role.MASTER, 123456); + expect(chdlr.sw.getHARole()).andReturn(Role.SLAVE); + chdlr.sw.setHARole(Role.MASTER, 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)); } @@ -1159,16 +1175,14 @@ public class ControllerTest extends FloodlightTestCase OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_OTHER); - chdlr.sw.deliverRoleReply(xid, Role.EQUAL); - expect(chdlr.sw.isActive()).andReturn(true); + setupPendingRoleRequest(chdlr.sw, xid, Role.EQUAL, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.EQUAL, 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)); }; @@ -1181,18 +1195,16 @@ public class ControllerTest extends FloodlightTestCase OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_SLAVE); - chdlr.sw.deliverRoleReply(xid, Role.SLAVE); + setupPendingRoleRequest(chdlr.sw, xid, Role.SLAVE, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.SLAVE, true); 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()); } @@ -1205,19 +1217,17 @@ public class ControllerTest extends FloodlightTestCase OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_MASTER); - chdlr.sw.deliverRoleReply(xid, Role.MASTER); + setupPendingRoleRequest(chdlr.sw, xid, Role.MASTER, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.MASTER, true); 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; // Must not clear flow mods 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)); } @@ -1230,20 +1240,19 @@ public class ControllerTest extends FloodlightTestCase OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, OFRoleReplyVendorData.NX_ROLE_SLAVE); - chdlr.sw.deliverRoleReply(xid, Role.SLAVE); + setupPendingRoleRequest(chdlr.sw, xid, Role.SLAVE, 123456); + expect(chdlr.sw.getHARole()).andReturn(null); + chdlr.sw.setHARole(Role.SLAVE, true); 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).anyTimes(); + expect(chdlr.sw.getHARole()).andReturn(Role.SLAVE).anyTimes(); 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()); } @@ -1321,7 +1330,16 @@ public class ControllerTest extends FloodlightTestCase } @Override - public IOFSwitch getOFSwitchImpl(OFDescriptionStatistics description) { + public IOFSwitch getOFSwitchImpl(String regis_desc, + OFDescriptionStatistics description) { + // If testing bind order, just record registered desc string + if (test_bind_order) { + if (bind_order == null) { + bind_order = new ArrayList<String>(); + } + bind_order.add(regis_desc); + return null; + } String hw_desc = description.getHardwareDescription(); if (hw_desc.equals("version 1.1")) { return new Test11SwitchClass(); @@ -1331,4 +1349,5 @@ public class ControllerTest extends FloodlightTestCase } return null; } + } diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java index 758cd0563588e7fa759c99ccaec95d2217fe3f21..685efdc4f4bfeeac548d695ee272cc3430c4ae83 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java @@ -1,27 +1,11 @@ package net.floodlightcontroller.core.internal; -import static org.easymock.EasyMock.*; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.List; - -import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.internal.OFSwitchImpl.PendingRoleRequestEntry; -import net.floodlightcontroller.core.test.MockFloodlightProvider; +import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.test.FloodlightTestCase; -import org.easymock.Capture; -import org.jboss.netty.channel.Channel; import org.junit.Before; import org.junit.Test; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; -import org.openflow.protocol.OFVendor; -import org.openflow.protocol.vendor.OFVendorData; -import org.openflow.vendor.nicira.OFNiciraVendorData; -import org.openflow.vendor.nicira.OFRoleRequestVendorData; -import org.openflow.vendor.nicira.OFRoleVendorData; public class OFSwitchImplTest extends FloodlightTestCase { protected OFSwitchImpl sw; @@ -30,207 +14,40 @@ public class OFSwitchImplTest extends FloodlightTestCase { @Before public void setUp() throws Exception { sw = new OFSwitchImpl(); - Channel ch = createMock(Channel.class); - SocketAddress sa = new InetSocketAddress(42); - expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); - sw.setChannel(ch); - MockFloodlightProvider floodlightProvider = new MockFloodlightProvider(); - sw.setFloodlightProvider(floodlightProvider); - } - - - public void doSendNxRoleRequest(Role role, int nx_role) throws Exception { - long cookie = System.nanoTime(); - - // verify that the correct OFMessage is sent - Capture<List<OFMessage>> msgCapture = new Capture<List<OFMessage>>(); - expect(sw.channel.write(capture(msgCapture))).andReturn(null); - replay(sw.channel); - int xid = sw.sendNxRoleRequest(role, cookie); - verify(sw.channel); - List<OFMessage> msgList = msgCapture.getValue(); - assertEquals(1, msgList.size()); - OFMessage msg = msgList.get(0); - assertEquals("Transaction Ids must match", xid, msg.getXid()); - assertTrue("Message must be an OFVendor type", msg instanceof OFVendor); - assertEquals(OFType.VENDOR, msg.getType()); - OFVendor vendorMsg = (OFVendor)msg; - assertEquals("Vendor message must be vendor Nicira", - OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor()); - OFVendorData vendorData = vendorMsg.getVendorData(); - assertTrue("Vendor Data must be an OFRoleRequestVendorData", - vendorData instanceof OFRoleRequestVendorData); - OFRoleRequestVendorData roleRequest = (OFRoleRequestVendorData)vendorData; - assertEquals(nx_role, roleRequest.getRole()); - - // Now verify that we've added the pending request correctly - // to the pending queue - assertEquals(1, sw.pendingRoleRequests.size()); - PendingRoleRequestEntry pendingRoleRequest = sw.pendingRoleRequests.poll(); - assertEquals(msg.getXid(), pendingRoleRequest.xid); - assertEquals(role, pendingRoleRequest.role); - assertEquals(cookie, pendingRoleRequest.cookie); - reset(sw.channel); - } - - @Test - public void testSendNxRoleRequest() throws Exception { - doSendNxRoleRequest(Role.MASTER, OFRoleVendorData.NX_ROLE_MASTER); - doSendNxRoleRequest(Role.SLAVE, OFRoleVendorData.NX_ROLE_SLAVE); - doSendNxRoleRequest(Role.EQUAL, OFRoleVendorData.NX_ROLE_OTHER); - } - + } @Test - public void testDeliverRoleReplyOk() { - // test normal case - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.pendingRoleRequests.add(pending); - replay(sw.channel); - sw.deliverRoleReply(pending.xid, pending.role); - verify(sw.channel); + public void testSetHARoleReplyReceived() { + assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + + sw.setHARole(Role.MASTER, true); + assertEquals(Role.MASTER, sw.getHARole()); assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); - assertEquals(pending.role, sw.role); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testDeliverRoleReplyOkRepeated() { - // test normal case. Not the first role reply - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - sw.pendingRoleRequests.add(pending); - replay(sw.channel); - sw.deliverRoleReply(pending.xid, pending.role); - verify(sw.channel); + + sw.setHARole(Role.EQUAL, true); + assertEquals(Role.EQUAL, sw.getHARole()); + assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + + sw.setHARole(Role.SLAVE, true); + assertEquals(Role.SLAVE, sw.getHARole()); assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); - assertEquals(pending.role, sw.role); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testDeliverRoleReplyNonePending() { - // nothing pending - expect(sw.channel.close()).andReturn(null); - replay(sw.channel); - sw.deliverRoleReply(1, Role.MASTER); - verify(sw.channel); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testDeliverRoleReplyWrongXid() { - // wrong xid received - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.pendingRoleRequests.add(pending); - expect(sw.channel.close()).andReturn(null); - replay(sw.channel); - sw.deliverRoleReply(pending.xid+1, pending.role); - verify(sw.channel); - assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); - assertEquals(0, sw.pendingRoleRequests.size()); } @Test - public void testDeliverRoleReplyWrongRole() { - // correct xid but incorrect role received - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.pendingRoleRequests.add(pending); - expect(sw.channel.close()).andReturn(null); - replay(sw.channel); - sw.deliverRoleReply(pending.xid, Role.SLAVE); - verify(sw.channel); + public void testSetHARoleNoReply() { assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testCheckFirstPendingRoleRequestXid() { - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - 54321, Role.MASTER, 232323); - replay(sw.channel); // we don't expect any invocations - sw.pendingRoleRequests.add(pending); - assertEquals(true, sw.checkFirstPendingRoleRequestXid(54321)); - assertEquals(false, sw.checkFirstPendingRoleRequestXid(0)); - sw.pendingRoleRequests.clear(); - assertEquals(false, sw.checkFirstPendingRoleRequestXid(54321)); - verify(sw.channel); - } - - @Test - public void testCheckFirstPendingRoleRequestCookie() { - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - 54321, Role.MASTER, 232323); - replay(sw.channel); // we don't expect any invocations - sw.pendingRoleRequests.add(pending); - assertEquals(true, sw.checkFirstPendingRoleRequestCookie(232323)); - assertEquals(false, sw.checkFirstPendingRoleRequestCookie(0)); - sw.pendingRoleRequests.clear(); - assertEquals(false, sw.checkFirstPendingRoleRequestCookie(232323)); - verify(sw.channel); - } - - @Test - public void testDeliverRoleRequestNotSupported () { - // normal case. xid is pending - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.role = Role.SLAVE; - sw.pendingRoleRequests.add(pending); - replay(sw.channel); - sw.deliverRoleRequestNotSupported(pending.xid); - verify(sw.channel); + + sw.setHARole(Role.MASTER, false); + assertEquals(Role.MASTER, sw.getHARole()); + assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + + sw.setHARole(Role.EQUAL, false); + assertEquals(Role.EQUAL, sw.getHARole()); + assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + + sw.setHARole(Role.SLAVE, false); + assertEquals(Role.SLAVE, sw.getHARole()); assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); - assertEquals(null, sw.role); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testDeliverRoleRequestNotSupportedNonePending() { - // nothing pending - sw.role = Role.SLAVE; - expect(sw.channel.close()).andReturn(null); - replay(sw.channel); - sw.deliverRoleRequestNotSupported(1); - verify(sw.channel); - assertEquals(null, sw.role); - assertEquals(0, sw.pendingRoleRequests.size()); - } - - @Test - public void testDeliverRoleRequestNotSupportedWrongXid() { - // wrong xid received - PendingRoleRequestEntry pending = new PendingRoleRequestEntry( - (int)System.currentTimeMillis(), // arbitrary xid - Role.MASTER, - System.nanoTime() // arbitrary cookie - ); - sw.role = Role.SLAVE; - sw.pendingRoleRequests.add(pending); - expect(sw.channel.close()).andReturn(null); - replay(sw.channel); - sw.deliverRoleRequestNotSupported(pending.xid+1); - verify(sw.channel); - assertEquals(null, sw.role); - assertEquals(0, sw.pendingRoleRequests.size()); } + } diff --git a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java index d7d981449d95d4e934d7610232f8977c5afa025a..cb446b2a9ec19cd46d59620e8b160e5e1ef3f4a7 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java @@ -1,29 +1,53 @@ package net.floodlightcontroller.core.internal; +import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; import static org.easymock.EasyMock.verify; import static org.junit.Assert.*; import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Collection; import java.util.LinkedList; +import java.util.List; + +import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.internal.RoleChanger.PendingRoleRequestEntry; import net.floodlightcontroller.core.internal.RoleChanger.RoleChangeTask; +import org.easymock.Capture; import org.easymock.EasyMock; import org.jboss.netty.channel.Channel; import org.junit.Before; import org.junit.Test; +import org.openflow.protocol.OFError; +import org.openflow.protocol.OFError.OFErrorType; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFType; +import org.openflow.protocol.OFVendor; +import org.openflow.protocol.factory.BasicFactory; +import org.openflow.protocol.vendor.OFVendorData; +import org.openflow.vendor.nicira.OFNiciraVendorData; +import org.openflow.vendor.nicira.OFRoleRequestVendorData; +import org.openflow.vendor.nicira.OFRoleVendorData; public class RoleChangerTest { public RoleChanger roleChanger; + Controller controller; @Before public void setUp() throws Exception { - roleChanger = new RoleChanger(); + controller = createMock(Controller.class); + roleChanger = new RoleChanger(controller); + BasicFactory factory = new BasicFactory(); + expect(controller.getOFMessageFactory()).andReturn(factory).anyTimes(); } /** @@ -31,22 +55,20 @@ public class RoleChangerTest { * The connection should be closed. */ @Test - public void testSendRoleRequestSlaveNotSupported() { + public void testSendRoleRequestSlaveNotSupported() throws Exception { LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>(); // a switch that doesn't support role requests - OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); - Channel channel1 = createMock(Channel.class); - expect(sw1.getChannel()).andReturn(channel1); + IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class); // No support for NX_ROLE expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(false); - expect(channel1.close()).andReturn(null); + .andReturn(false); + sw1.disconnectOutputStream(); switches.add(sw1); - replay(sw1, channel1); + replay(sw1); roleChanger.sendRoleRequest(switches, Role.SLAVE, 123456); - verify(sw1, channel1); + verify(sw1); // sendRoleRequest needs to remove the switch from the list since // it closed its connection @@ -55,17 +77,18 @@ public class RoleChangerTest { /** * Send a role request for MASTER to a switch that doesn't support it. - * The connection should be closed. + * The connection should stay open. */ @Test - public void testSendRoleRequestMasterNotSupported() { + public void testSendRoleRequestMasterNotSupported() throws Exception { LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>(); // a switch that doesn't support role requests - OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); + IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class); // No support for NX_ROLE expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(false); + .andReturn(false); + sw1.setHARole(Role.MASTER, false); switches.add(sw1); replay(sw1); @@ -76,81 +99,94 @@ public class RoleChangerTest { } /** - * Send a role request a switch that supports it and one that + * Check error handling * hasn't had a role request send to it yet */ + @SuppressWarnings("unchecked") @Test public void testSendRoleRequestErrorHandling () throws Exception { LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>(); // a switch that supports role requests - OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); + IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class); // No support for NX_ROLE expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(true); - expect(sw1.sendNxRoleRequest(Role.MASTER, 123456)) - .andThrow(new IOException()).once(); - Channel channel1 = createMock(Channel.class); - expect(sw1.getChannel()).andReturn(channel1); - expect(channel1.close()).andReturn(null); + .andReturn(true); + expect(sw1.getNextTransactionId()).andReturn(1); + sw1.write((List<OFMessage>)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); + expectLastCall().andThrow(new IOException()); + sw1.disconnectOutputStream(); switches.add(sw1); - replay(sw1); + replay(sw1, controller); roleChanger.sendRoleRequest(switches, Role.MASTER, 123456); - verify(sw1); + verify(sw1, controller); assertTrue(switches.isEmpty()); } /** - * Check error handling + * Send a role request a switch that supports it and one that * hasn't had a role request send to it yet */ + @SuppressWarnings("unchecked") @Test public void testSendRoleRequestSupported() throws Exception { LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>(); // a switch that supports role requests - OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); - // No support for NX_ROLE + IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class); + // Support for NX_ROLE expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(true); - expect(sw1.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once(); + .andReturn(true); + expect(sw1.getNextTransactionId()).andReturn(1); + sw1.write((List<OFMessage>)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); switches.add(sw1); - // a switch for which we don't have SUPPORTS_NX_ROLE yet - OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class); - // No support for NX_ROLE + // second switch + IOFSwitch sw2 = EasyMock.createMock(IOFSwitch.class); + // No role request yet expect(sw2.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(null); - expect(sw2.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once(); - switches.add(sw2); + .andReturn(null); + expect(sw2.getNextTransactionId()).andReturn(1); + sw2.write((List<OFMessage>)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); + switches.add(sw2); - - replay(sw1, sw2); + replay(sw1, sw2, controller); roleChanger.sendRoleRequest(switches, Role.MASTER, 123456); - verify(sw1, sw2); + verify(sw1, sw2, controller); assertEquals(2, switches.size()); } @Test - public void testVerifyRoleReplyReceived() { + public void testVerifyRoleReplyReceived() throws Exception { Collection<IOFSwitch> switches = new LinkedList<IOFSwitch>(); // Add a switch that has received a role reply - OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class); - expect(sw1.checkFirstPendingRoleRequestCookie(123456)) - .andReturn(false).once(); + IOFSwitch sw1 = EasyMock.createMock(IOFSwitch.class); + LinkedList<PendingRoleRequestEntry> pendingList1 = + new LinkedList<PendingRoleRequestEntry>(); + roleChanger.pendingRequestMap.put(sw1, pendingList1); switches.add(sw1); // Add a switch that has not yet received a role reply - OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class); - expect(sw2.checkFirstPendingRoleRequestCookie(123456)) - .andReturn(true).once(); - Channel channel2 = createMock(Channel.class); - expect(sw2.getChannel()).andReturn(channel2); - expect(channel2.close()).andReturn(null); + IOFSwitch sw2 = EasyMock.createMock(IOFSwitch.class); + LinkedList<PendingRoleRequestEntry> pendingList2 = + new LinkedList<PendingRoleRequestEntry>(); + roleChanger.pendingRequestMap.put(sw2, pendingList2); + PendingRoleRequestEntry entry = + new PendingRoleRequestEntry(1, Role.MASTER, 123456); + pendingList2.add(entry); + // Timed out switch should become active + expect(sw2.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA)) + .andReturn(null); + expect(sw2.getHARole()).andReturn(null); + sw2.setHARole(Role.MASTER, false); + EasyMock.expectLastCall(); switches.add(sw2); @@ -184,50 +220,306 @@ public class RoleChangerTest { assertTrue( t2.compareTo(t3) > 0 ); } + @SuppressWarnings("unchecked") @Test public void testSubmitRequest() throws Exception { LinkedList<IOFSwitch> switches = new LinkedList<IOFSwitch>(); - roleChanger.timeout = 500*1000*1000; // 500 ms + roleChanger.timeout = 100*1000*1000; // 100 ms // a switch that supports role requests - OFSwitchImpl sw1 = EasyMock.createStrictMock(OFSwitchImpl.class); - // No support for NX_ROLE + IOFSwitch sw1 = EasyMock.createStrictMock(IOFSwitch.class); + // Support for NX_ROLE expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(true); - expect(sw1.sendNxRoleRequest(EasyMock.same(Role.MASTER), EasyMock.anyLong())) - .andReturn(1); + .andReturn(true); + expect(sw1.getNextTransactionId()).andReturn(1); + sw1.write((List<OFMessage>)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); + // Second request expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(true); - expect(sw1.sendNxRoleRequest(EasyMock.same(Role.SLAVE), EasyMock.anyLong())) - .andReturn(1); - // The following calls happen for timeout handling: - expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong())) - .andReturn(false); - expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong())) - .andReturn(false); + .andReturn(true); + expect(sw1.getNextTransactionId()).andReturn(2); + sw1.write((List<OFMessage>)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); + expect(sw1.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA)) + .andReturn(null); + expect(sw1.getHARole()).andReturn(null); + sw1.setHARole(Role.MASTER, false); + expect(sw1.getAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA)) + .andReturn(null); + expect(sw1.getHARole()).andReturn(Role.MASTER); + sw1.setHARole(Role.SLAVE, false); + // Disconnect on timing out SLAVE request + sw1.disconnectOutputStream(); switches.add(sw1); + // Add to switch when timing out the MASTER request + controller.addSwitch(sw1, true); - replay(sw1); + + replay(sw1, controller); roleChanger.submitRequest(switches, Role.MASTER); roleChanger.submitRequest(switches, Role.SLAVE); - // Wait until role request has been sent. - // TODO: need to get rid of this sleep somehow - Thread.sleep(100); - // Now there should be exactly one timeout task pending + // Wait until role request has been sent. + synchronized (roleChanger.pendingTasks) { + while (RoleChanger.RoleChangeTask.Type.TIMEOUT != + roleChanger.pendingTasks.peek().type) { + roleChanger.pendingTasks.wait(); + } + } + // Now there should be exactly one timeout task pending for each request assertEquals(2, roleChanger.pendingTasks.size()); - // Make sure it's indeed a timeout task - assertSame(RoleChanger.RoleChangeTask.Type.TIMEOUT, - roleChanger.pendingTasks.peek().type); // Check that RoleChanger indeed made a copy of switches collection assertNotSame(switches, roleChanger.pendingTasks.peek().switches); // Wait until the timeout triggers - // TODO: get rid of this sleep too. - Thread.sleep(500); - assertEquals(0, roleChanger.pendingTasks.size()); - verify(sw1); + synchronized (roleChanger.pendingTasks) { + while (roleChanger.pendingTasks.size() != 0) { + roleChanger.pendingTasks.wait(); + } + } + verify(sw1, controller); } + // Helper function + protected void setupPendingRoleRequest(IOFSwitch sw, int xid, Role role, + long cookie) { + LinkedList<PendingRoleRequestEntry> pendingList = + new LinkedList<PendingRoleRequestEntry>(); + roleChanger.pendingRequestMap.put(sw, pendingList); + PendingRoleRequestEntry entry = + new PendingRoleRequestEntry(xid, role, cookie); + pendingList.add(entry); + } + + + @Test + public void testDeliverRoleReplyOk() { + // test normal case + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + roleChanger.deliverRoleReply(sw, xid, role); + assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(role, sw.getHARole()); + assertEquals(0, roleChanger.pendingRequestMap.get(sw).size()); + } + + @Test + public void testDeliverRoleReplyOkRepeated() { + // test normal case. Not the first role reply + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); + roleChanger.deliverRoleReply(sw, xid, role); + assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(role, sw.getHARole()); + assertEquals(0, roleChanger.pendingRequestMap.get(sw).size()); + } + + @Test + public void testDeliverRoleReplyNonePending() { + // nothing pending + OFSwitchImpl sw = new OFSwitchImpl(); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + sw.setChannel(ch); + roleChanger.deliverRoleReply(sw, 1, Role.MASTER); + assertEquals(null, sw.getHARole()); + } + + @Test + public void testDeliverRoleReplyWrongXid() { + // wrong xid received + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + sw.setChannel(ch); + expect(ch.close()).andReturn(null); + replay(ch); + roleChanger.deliverRoleReply(sw, xid+1, role); + verify(ch); + assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(0, roleChanger.pendingRequestMap.get(sw).size()); + } + + @Test + public void testDeliverRoleReplyWrongRole() { + // correct xid but incorrect role received + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + sw.setChannel(ch); + expect(ch.close()).andReturn(null); + replay(ch); + roleChanger.deliverRoleReply(sw, xid, Role.SLAVE); + verify(ch); + assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(0, roleChanger.pendingRequestMap.get(sw).size()); + } + + @Test + public void testCheckFirstPendingRoleRequestXid() { + int xid = 54321; + long cookie = 232323; + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + assertEquals(true, + roleChanger.checkFirstPendingRoleRequestXid(sw, xid)); + assertEquals(false, + roleChanger.checkFirstPendingRoleRequestXid(sw, 0)); + roleChanger.pendingRequestMap.get(sw).clear(); + assertEquals(false, + roleChanger.checkFirstPendingRoleRequestXid(sw, xid)); + } + + @Test + public void testCheckFirstPendingRoleRequestCookie() { + int xid = 54321; + long cookie = 232323; + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + assertNotSame(null, + roleChanger.checkFirstPendingRoleRequestCookie(sw, cookie)); + assertEquals(null, + roleChanger.checkFirstPendingRoleRequestCookie(sw, 0)); + roleChanger.pendingRequestMap.get(sw).clear(); + assertEquals(null, + roleChanger.checkFirstPendingRoleRequestCookie(sw, cookie)); + } + + @Test + public void testDeliverRoleRequestError() { + // normal case. xid is pending + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + sw.setChannel(ch); + setupPendingRoleRequest(sw, xid, role, cookie); + OFError error = new OFError(); + error.setErrorType(OFErrorType.OFPET_BAD_REQUEST); + error.setXid(xid); + replay(ch); + roleChanger.deliverRoleRequestError(sw, error); + verify(ch); + assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(role, sw.getHARole()); + assertEquals(0, roleChanger.pendingRequestMap.get(sw).size()); + } + + @Test + public void testDeliverRoleRequestErrorNonePending() { + // nothing pending + OFSwitchImpl sw = new OFSwitchImpl(); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + sw.setChannel(ch); + OFError error = new OFError(); + error.setErrorType(OFErrorType.OFPET_BAD_REQUEST); + error.setXid(1); + replay(ch); + roleChanger.deliverRoleRequestError(sw, error); + verify(ch); + assertEquals(null, sw.getHARole()); + } + + @Test + public void testDeliverRoleRequestErrorWrongXid() { + // wrong xid received + // wrong xid received + int xid = (int) System.currentTimeMillis(); + long cookie = System.nanoTime(); + Role role = Role.MASTER; + OFSwitchImpl sw = new OFSwitchImpl(); + setupPendingRoleRequest(sw, xid, role, cookie); + Channel ch = createMock(Channel.class); + SocketAddress sa = new InetSocketAddress(42); + expect(ch.getRemoteAddress()).andReturn(sa).anyTimes(); + expect(ch.close()).andReturn(null); + sw.setChannel(ch); + replay(ch); + OFError error = new OFError(); + error.setErrorCode(OFError.OFErrorType.OFPET_BAD_REQUEST.getValue()); + error.setXid(xid + 1); + roleChanger.deliverRoleRequestError(sw, error); + verify(ch); + assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)); + assertEquals(1, roleChanger.pendingRequestMap.get(sw).size()); + } + + public void doSendNxRoleRequest(Role role, int nx_role) throws Exception { + long cookie = System.nanoTime(); + OFSwitchImpl sw = new OFSwitchImpl(); + Channel ch = createMock(Channel.class); + sw.setChannel(ch); + sw.setFloodlightProvider(controller); + + // verify that the correct OFMessage is sent + Capture<List<OFMessage>> msgCapture = new Capture<List<OFMessage>>(); + // expect(sw.channel.getRemoteAddress()).andReturn(null); + controller.handleOutgoingMessage( + (IOFSwitch)EasyMock.anyObject(), + (OFMessage)EasyMock.anyObject(), + (FloodlightContext)EasyMock.anyObject()); + expect(ch.write(capture(msgCapture))).andReturn(null); + replay(ch, controller); + int xid = roleChanger.sendHARoleRequest(sw, role, cookie); + verify(ch, controller); + List<OFMessage> msgList = msgCapture.getValue(); + assertEquals(1, msgList.size()); + OFMessage msg = msgList.get(0); + assertEquals("Transaction Ids must match", xid, msg.getXid()); + assertTrue("Message must be an OFVendor type", msg instanceof OFVendor); + assertEquals(OFType.VENDOR, msg.getType()); + OFVendor vendorMsg = (OFVendor)msg; + assertEquals("Vendor message must be vendor Nicira", + OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor()); + OFVendorData vendorData = vendorMsg.getVendorData(); + assertTrue("Vendor Data must be an OFRoleRequestVendorData", + vendorData instanceof OFRoleRequestVendorData); + OFRoleRequestVendorData roleRequest = (OFRoleRequestVendorData)vendorData; + assertEquals(nx_role, roleRequest.getRole()); + + reset(ch); + } + + @Test + public void testSendNxRoleRequestMaster() throws Exception { + doSendNxRoleRequest(Role.MASTER, OFRoleVendorData.NX_ROLE_MASTER); + } + + @Test + public void testSendNxRoleRequestSlave() throws Exception { + doSendNxRoleRequest(Role.SLAVE, OFRoleVendorData.NX_ROLE_SLAVE); + } + + @Test + public void testSendNxRoleRequestEqual() throws Exception { + doSendNxRoleRequest(Role.EQUAL, OFRoleVendorData.NX_ROLE_OTHER); + } + + } diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java index 430a1bdfafbbbe4bcac775af9fb0ed1a99b6cabe..91e640d9e716dbac87c9c57db9ed8d90cdf7565d 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java @@ -18,7 +18,20 @@ package net.floodlightcontroller.devicemanager.internal; -import static org.easymock.EasyMock.*; +import static org.easymock.EasyMock.anyLong; +import static org.easymock.EasyMock.anyShort; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.isA; +import static org.easymock.EasyMock.or; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertArrayEquals; import java.util.ArrayList; import java.util.Arrays; @@ -31,18 +44,17 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static org.easymock.EasyMock.expectLastCall; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.core.test.MockThreadPoolService; -import net.floodlightcontroller.devicemanager.IDeviceListener; import net.floodlightcontroller.devicemanager.IDevice; +import net.floodlightcontroller.devicemanager.IDeviceListener; +import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.IEntityClassifierService; import net.floodlightcontroller.devicemanager.SwitchPort; -import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus; import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl.ClassState; import net.floodlightcontroller.devicemanager.test.MockEntityClassifier; @@ -61,15 +73,14 @@ import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.topology.ITopologyService; -import static org.junit.Assert.*; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; import org.openflow.protocol.OFPacketIn; +import org.openflow.protocol.OFPacketIn.OFPacketInReason; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFType; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; import org.openflow.util.HexString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,6 +113,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { return mockSwitch; } + @Override @Before public void setUp() throws Exception { super.setUp(); @@ -422,6 +434,48 @@ public class DeviceManagerImplTest extends FloodlightTestCase { verify(mockListener); } + + private void doTestEntityOrdering(boolean computeInsertionPoint) throws Exception { + Entity e = new Entity(10L, null, null, null, null, null); + IEntityClass ec = createNiceMock(IEntityClass.class); + Device d = new Device(deviceManager, 1L, e, ec); + + int expectedLength = 1; + Long[] macs = new Long[] { 5L, // new first element + 15L, // new last element + 7L, // insert in middle + 12L, // insert in middle + 6L, // insert at idx 1 + 14L, // insert at idx length-2 + 1L, + 20L + }; + + for (Long mac: macs) { + e = new Entity(mac, null, null, null, null, null); + int insertionPoint; + if (computeInsertionPoint) { + insertionPoint = -(Arrays.binarySearch(d.entities, e)+1); + } else { + insertionPoint = -1; + } + d = deviceManager.allocateDevice(d, e, insertionPoint); + expectedLength++; + assertEquals(expectedLength, d.entities.length); + for (int i = 0; i < d.entities.length-1; i++) + assertEquals(-1, d.entities[i].compareTo(d.entities[i+1])); + } + } + + @Test + public void testEntityOrderingExternal() throws Exception { + doTestEntityOrdering(true); + } + + @Test + public void testEntityOrderingInternal() throws Exception { + doTestEntityOrdering(false); + } @Test public void testAttachmentPointLearning() throws Exception { @@ -525,11 +579,131 @@ public class DeviceManagerImplTest extends FloodlightTestCase { assertArrayEquals(new Integer[] { 1 }, ips); verify(mockListener); } + + private void verifyEntityArray(Entity[] expected, Device d) { + Arrays.sort(expected); + assertArrayEquals(expected, d.entities); + } + + @Test + public void testNoLearningOnInternalPorts() throws Exception { + IDeviceListener mockListener = + createMock(IDeviceListener.class); + + deviceManager.addListener(mockListener); + ITopologyService mockTopology = createMock(ITopologyService.class); + expect(mockTopology.getL2DomainId(1L)). + andReturn(1L).anyTimes(); + expect(mockTopology.getL2DomainId(2L)). + andReturn(1L).anyTimes(); + expect(mockTopology.getL2DomainId(3L)). + andReturn(1L).anyTimes(); + expect(mockTopology.getL2DomainId(4L)). + andReturn(1L).anyTimes(); + expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())) + .andReturn(false).anyTimes(); + expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(), + anyLong(), anyShort())) + .andReturn(false).anyTimes(); + + expect(mockTopology.isAttachmentPointPort(or(eq(1L), eq(3L)), anyShort())) + .andReturn(true).anyTimes(); + // Switches 2 and 4 have only internal ports + expect(mockTopology.isAttachmentPointPort(or(eq(2L), eq(4L)), anyShort())) + .andReturn(false).anyTimes(); + + expect(mockTopology.isConsistent(1L, (short)1, 3L, (short)1)) + .andReturn(false).once(); + + Date topologyUpdateTime = new Date(); + expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime). + anyTimes(); + + replay(mockTopology); + + deviceManager.topology = mockTopology; + + Calendar c = Calendar.getInstance(); + Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime()); + c.add(Calendar.SECOND, 1); + Entity entity2 = new Entity(1L, null, 2, 2L, 1, c.getTime()); + c.add(Calendar.SECOND, 1); + Entity entity3 = new Entity(1L, null, 3, 3L, 1, c.getTime()); + c.add(Calendar.SECOND, 1); + Entity entity4 = new Entity(1L, null, 4, 4L, 1, c.getTime()); + + IDevice d; + SwitchPort[] aps; + Integer[] ips; + + mockListener.deviceAdded(isA(IDevice.class)); + expectLastCall().once(); + replay(mockListener); + + // cannot learn device internal ports + d = deviceManager.learnDeviceByEntity(entity2); + assertNull(d); + d = deviceManager.learnDeviceByEntity(entity4); + assertNull(d); + + d = deviceManager.learnDeviceByEntity(entity1); + assertEquals(1, deviceManager.getAllDevices().size()); + aps = d.getAttachmentPoints(); + assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps); + verifyEntityArray(new Entity[] { entity1 } , (Device)d); + ips = d.getIPv4Addresses(); + assertArrayEquals(new Integer[] { 1 }, ips); + verify(mockListener); + + reset(mockListener); + replay(mockListener); + + // don't learn + d = deviceManager.learnDeviceByEntity(entity2); + assertEquals(1, deviceManager.getAllDevices().size()); + aps = d.getAttachmentPoints(); + assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps); + verifyEntityArray(new Entity[] { entity1 } , (Device)d); + ips = d.getIPv4Addresses(); + assertArrayEquals(new Integer[] { 1 }, ips); + verify(mockListener); + + reset(mockListener); + mockListener.deviceMoved(isA(IDevice.class)); + mockListener.deviceIPV4AddrChanged(isA(IDevice.class)); + replay(mockListener); + + // learn + d = deviceManager.learnDeviceByEntity(entity3); + assertEquals(1, deviceManager.getAllDevices().size()); + aps = d.getAttachmentPoints(); + assertArrayEquals(new SwitchPort[] { new SwitchPort(3L, 1) }, aps); + verifyEntityArray(new Entity[] { entity1, entity3 } , (Device)d); + ips = d.getIPv4Addresses(); + Arrays.sort(ips); + assertArrayEquals(new Integer[] { 1, 3 }, ips); + verify(mockListener); + + reset(mockListener); + replay(mockListener); + + // don't learn + d = deviceManager.learnDeviceByEntity(entity4); + assertEquals(1, deviceManager.getAllDevices().size()); + aps = d.getAttachmentPoints(); + assertArrayEquals(new SwitchPort[] { new SwitchPort(3L, 1) }, aps); + verifyEntityArray(new Entity[] { entity1, entity3 } , (Device)d); + ips = d.getIPv4Addresses(); + Arrays.sort(ips); + assertArrayEquals(new Integer[] { 1, 3 }, ips); + verify(mockListener); + } + @Test public void testAttachmentPointSuppression() throws Exception { IDeviceListener mockListener = - createStrictMock(IDeviceListener.class); + createMock(IDeviceListener.class); deviceManager.addListener(mockListener); @@ -542,15 +716,16 @@ public class DeviceManagerImplTest extends FloodlightTestCase { andReturn(10L).anyTimes(); expect(mockTopology.getL2DomainId(50L)). andReturn(10L).anyTimes(); - expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())). - andReturn(false).anyTimes(); + expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())) + .andReturn(false).anyTimes(); expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(), - anyLong(), anyShort())).andReturn(false).anyTimes(); + anyLong(), anyShort())) + .andReturn(false).anyTimes(); - expect(mockTopology.isAttachmentPointPort(anyLong(), - anyShort())).andReturn(true).anyTimes(); - expect(mockTopology.isConsistent(5L, (short)1, 50L, (short)1)). - andReturn(false).anyTimes(); + expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort())) + .andReturn(true).anyTimes(); + expect(mockTopology.isConsistent(5L, (short)1, 50L, (short)1)) + .andReturn(false).anyTimes(); Date topologyUpdateTime = new Date(); expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime). @@ -564,10 +739,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase { deviceManager.addSuppressAPs(10L, (short)1); Calendar c = Calendar.getInstance(); - Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime()); Entity entity0 = new Entity(1L, null, null, null, null, c.getTime()); + Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime()); c.add(Calendar.SECOND, 1); - Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime()); + Entity entity2 = new Entity(1L, null, 1, 5L, 1, c.getTime()); c.add(Calendar.SECOND, 1); Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime()); c.add(Calendar.SECOND, 1); @@ -578,40 +753,49 @@ public class DeviceManagerImplTest extends FloodlightTestCase { Integer[] ips; mockListener.deviceAdded(isA(IDevice.class)); + mockListener.deviceIPV4AddrChanged((isA(IDevice.class))); replay(mockListener); + + // TODO: we currently do learn entities on suppressed APs + // // cannot learn device on suppressed AP + // d = deviceManager.learnDeviceByEntity(entity1); + // assertNull(d); - deviceManager.learnDeviceByEntity(entity1); - d = deviceManager.learnDeviceByEntity(entity0); + deviceManager.learnDeviceByEntity(entity0); + d = deviceManager.learnDeviceByEntity(entity1); assertEquals(1, deviceManager.getAllDevices().size()); aps = d.getAttachmentPoints(); assertEquals(aps.length, 0); + verifyEntityArray(new Entity[] { entity0, entity1} , (Device)d); ips = d.getIPv4Addresses(); assertArrayEquals(new Integer[] { 1 }, ips); verify(mockListener); reset(mockListener); mockListener.deviceMoved((isA(IDevice.class))); + //mockListener.deviceIPV4AddrChanged((isA(IDevice.class))); replay(mockListener); d = deviceManager.learnDeviceByEntity(entity2); assertEquals(1, deviceManager.getAllDevices().size()); aps = d.getAttachmentPoints(); assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps); + verifyEntityArray(new Entity[] { entity0, entity1, entity2 } , (Device)d); ips = d.getIPv4Addresses(); assertArrayEquals(new Integer[] { 1 }, ips); verify(mockListener); reset(mockListener); - mockListener.deviceMoved((isA(IDevice.class))); replay(mockListener); d = deviceManager.learnDeviceByEntity(entity3); assertEquals(1, deviceManager.getAllDevices().size()); aps = d.getAttachmentPoints(); assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps); + verifyEntityArray(new Entity[] { entity0, entity1, entity2, entity3 } , (Device)d); ips = d.getIPv4Addresses(); assertArrayEquals(new Integer[] { 1 }, ips); - //verify(mockListener); // There is no device movement here; no not needed. + verify(mockListener); reset(mockListener); mockListener.deviceMoved((isA(IDevice.class))); @@ -622,6 +806,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { aps = d.getAttachmentPoints(); assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1), new SwitchPort(50L, 1) }, aps); + verifyEntityArray(new Entity[] { entity0, entity1, entity2, entity3, entity4} , (Device)d); ips = d.getIPv4Addresses(); assertArrayEquals(new Integer[] { 1 }, ips); verify(mockListener); @@ -754,7 +939,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase { ipaddr, 5L, 2, - currentDate)); + currentDate), + -1); reset(mockTopology); expect(mockTopology.isAttachmentPointPort(anyLong(), @@ -837,7 +1023,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { null, null, 1, null, null); assertTrue(diter.hasNext()); assertEquals(d.getDeviceKey(), diter.next().getDeviceKey()); - diter = deviceManager.queryClassDevices(d.getEntityClass(), + diter = deviceManager.queryClassDevices(d.getEntityClass(), null, null, 2, null, null); assertTrue(diter.hasNext()); assertEquals(d.getDeviceKey(), diter.next().getDeviceKey()); @@ -924,12 +1110,12 @@ public class DeviceManagerImplTest extends FloodlightTestCase { } /* - * A ConcurrentHashMap for devices (deviceMap) that can be used to test + * A ConcurrentHashMap for devices (deviceMap) that can be used to test * code that specially handles concurrent modification situations. In * particular, we overwrite values() and will replace / remove all the - * elements returned by values. + * elements returned by values. * - * The remove flag in the constructor specifies if devices returned by + * The remove flag in the constructor specifies if devices returned by * values() should be removed or replaced. */ protected static class ConcurrentlyModifiedDeviceMap @@ -948,15 +1134,15 @@ public class DeviceManagerImplTest extends FloodlightTestCase { Collection<Device> devs = new ArrayList<Device>(super.values()); for (Device d: devs) { if (remove) { - // We remove the device from the underlying map + // We remove the device from the underlying map super.remove(d.getDeviceKey()); } else { super.remove(d.getDeviceKey()); // We add a different Device instance with the same // key to the map. We'll do some hackery so the device - // is different enough to compare differently in equals + // is different enough to compare differently in equals // but otherwise looks the same. - // It's ugly but it works. + // It's ugly but it works. Entity[] curEntities = new Entity[d.getEntities().length]; int i = 0; // clone entities @@ -967,19 +1153,19 @@ public class DeviceManagerImplTest extends FloodlightTestCase { e.switchDPID, e.switchPort, e.lastSeenTimestamp); - if (e.vlan == null) + if (e.vlan == null) curEntities[i].vlan = (short)1; - else + else curEntities[i].vlan = (short)((e.vlan + 1 % 4095)+1); i++; } - Device newDevice = new Device(d, curEntities[0]); + Device newDevice = new Device(d, curEntities[0], -1); newDevice.entities = curEntities; assertEquals(false, newDevice.equals(d)); super.put(newDevice.getDeviceKey(), newDevice); } } - return devs; + return devs; } } @@ -994,7 +1180,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { } /* Test correct entity cleanup behavior when a concurrent modification - * occurs. + * occurs. */ @Test public void testEntityExpirationConcurrentModification() throws Exception { @@ -1003,7 +1189,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { } /* Test correct entity cleanup behavior when a concurrent remove - * occurs. + * occurs. */ @Test public void testDeviceExpirationConcurrentRemove() throws Exception { @@ -1012,7 +1198,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { } /* Test correct entity cleanup behavior when a concurrent modification - * occurs. + * occurs. */ @Test public void testDeviceExpirationConcurrentModification() throws Exception { @@ -1415,31 +1601,31 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // Look up the device using findDevice() which uses only the primary // index - assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), + assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), entity1.getVlan(), entity1.getIpv4Address(), entity1.getSwitchDPID(), entity1.getSwitchPort())); // port changed. Device will be found through class index - assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), + assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), entity1.getVlan(), entity1.getIpv4Address(), entity1.getSwitchDPID(), entity1.getSwitchPort()+1)); // VLAN changed. No device matches - assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), + assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), (short)42, entity1.getIpv4Address(), entity1.getSwitchDPID(), entity1.getSwitchPort())); - assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), + assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), null, entity1.getIpv4Address(), entity1.getSwitchDPID(), entity1.getSwitchPort())); assertEquals(d2, deviceManager.findDeviceByEntity(entity2)); assertEquals(null, deviceManager.findDeviceByEntity(entity2b)); - assertEquals(d3, deviceManager.findDevice(entity3.getMacAddress(), + assertEquals(d3, deviceManager.findDevice(entity3.getMacAddress(), entity3.getVlan(), entity3.getIpv4Address(), entity3.getSwitchDPID(), @@ -1447,33 +1633,33 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // switch and port not set. throws exception exceptionCaught = false; try { - assertEquals(null, deviceManager.findDevice(entity3.getMacAddress(), + assertEquals(null, deviceManager.findDevice(entity3.getMacAddress(), entity3.getVlan(), entity3.getIpv4Address(), null, null)); - } + } catch (IllegalArgumentException e) { exceptionCaught = true; } if (!exceptionCaught) fail("findDevice() did not throw IllegalArgumentException"); assertEquals(d4, deviceManager.findDeviceByEntity(entity4)); - assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), + assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), entity5.getVlan(), entity5.getIpv4Address(), entity5.getSwitchDPID(), entity5.getSwitchPort())); - // switch and port not set. throws exception (swith/port are key + // switch and port not set. throws exception (swith/port are key // fields of IEntityClassifier but not d5.entityClass exceptionCaught = false; try { - assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), + assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), entity5.getVlan(), entity5.getIpv4Address(), null, null)); - } + } catch (IllegalArgumentException e) { exceptionCaught = true; } @@ -1486,16 +1672,16 @@ public class DeviceManagerImplTest extends FloodlightTestCase { // Now look up destination devices - assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(), - entity1.getMacAddress(), + assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(), + entity1.getMacAddress(), entity1.getVlan(), entity1.getIpv4Address())); - assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(), - entity1.getMacAddress(), + assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(), + entity1.getMacAddress(), entity1.getVlan(), null)); - assertEquals(null, deviceManager.findClassDevice(d2.getEntityClass(), - entity1.getMacAddress(), + assertEquals(null, deviceManager.findClassDevice(d2.getEntityClass(), + entity1.getMacAddress(), (short) -1, 0)); } @@ -1620,7 +1806,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date()); Entity entity3 = new Entity(1L, (short)3, null, 1L, 1, new Date()); Entity entity4 = new Entity(1L, (short)42, null, 1L, 1, new Date()); - Entity[] entities = new Entity[] { entity1, entity2, + Entity[] entities = new Entity[] { entity1, entity2, entity3, entity4 }; Device d = new Device(null,1L, null, null, Arrays.asList(entities), null); @@ -1628,9 +1814,9 @@ public class DeviceManagerImplTest extends FloodlightTestCase { SwitchPort swp1x2 = new SwitchPort(1L, 2); SwitchPort swp2x1 = new SwitchPort(2L, 1); SwitchPort swp10x1 = new SwitchPort(10L, 1); - assertArrayEquals(new Short[] { -1, 1}, + assertArrayEquals(new Short[] { -1, 1}, d.getSwitchPortVlanIds(swp10x1)); - assertArrayEquals(new Short[] { 3, 42}, + assertArrayEquals(new Short[] { 3, 42}, d.getSwitchPortVlanIds(swp1x1)); assertArrayEquals(new Short[0], d.getSwitchPortVlanIds(swp1x2)); @@ -1640,8 +1826,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase { @Test public void testReclassifyDevice() { - MockFlexEntityClassifier flexClassifier = - new MockFlexEntityClassifier(); + MockFlexEntityClassifier flexClassifier = + new MockFlexEntityClassifier(); deviceManager.entityClassifier= flexClassifier; deviceManager.startUp(null); @@ -1676,22 +1862,22 @@ public class DeviceManagerImplTest extends FloodlightTestCase { Device d1b = deviceManager.learnDeviceByEntity(entity1b); Device d2b = deviceManager.learnDeviceByEntity(entity2b); - d1 = deviceManager.getDeviceIteratorForQuery(entity1.getMacAddress(), - entity1.getVlan(), entity1.getIpv4Address(), - entity1.getSwitchDPID(), entity1.getSwitchPort()) - .next(); - d1b = deviceManager.getDeviceIteratorForQuery(entity1b.getMacAddress(), - entity1b.getVlan(), entity1b.getIpv4Address(), - entity1b.getSwitchDPID(), entity1b.getSwitchPort()).next(); + d1 = deviceManager.getDeviceIteratorForQuery(entity1.getMacAddress(), + entity1.getVlan(), entity1.getIpv4Address(), + entity1.getSwitchDPID(), entity1.getSwitchPort()) + .next(); + d1b = deviceManager.getDeviceIteratorForQuery(entity1b.getMacAddress(), + entity1b.getVlan(), entity1b.getIpv4Address(), + entity1b.getSwitchDPID(), entity1b.getSwitchPort()).next(); assertEquals(d1, d1b); - d2 = deviceManager.getDeviceIteratorForQuery(entity2.getMacAddress(), - entity2.getVlan(), entity2.getIpv4Address(), - entity2.getSwitchDPID(), entity2.getSwitchPort()).next(); - d2b = deviceManager.getDeviceIteratorForQuery(entity2b.getMacAddress(), - entity2b.getVlan(), entity2b.getIpv4Address(), - entity2b.getSwitchDPID(), entity2b.getSwitchPort()).next(); + d2 = deviceManager.getDeviceIteratorForQuery(entity2.getMacAddress(), + entity2.getVlan(), entity2.getIpv4Address(), + entity2.getSwitchDPID(), entity2.getSwitchPort()).next(); + d2b = deviceManager.getDeviceIteratorForQuery(entity2b.getMacAddress(), + entity2b.getVlan(), entity2b.getIpv4Address(), + entity2b.getSwitchDPID(), entity2b.getSwitchPort()).next(); assertEquals(d2, d2b); IEntityClass eC1 = flexClassifier.createTestEntityClass("C1"); @@ -1704,31 +1890,31 @@ public class DeviceManagerImplTest extends FloodlightTestCase { deviceManager.reclassifyDevice(d2); d1 = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity1)); + deviceManager.primaryIndex.findByEntity(entity1)); d1b = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity1b)); + deviceManager.primaryIndex.findByEntity(entity1b)); assertEquals(d1, d1b); d2 = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity2)); + deviceManager.primaryIndex.findByEntity(entity2)); d2b = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity2b)); + deviceManager.primaryIndex.findByEntity(entity2b)); assertEquals(d2, d2b); - + flexClassifier.addVlanEntities((short)1, eC2); deviceManager.reclassifyDevice(d1); deviceManager.reclassifyDevice(d2); d1 = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity1)); + deviceManager.primaryIndex.findByEntity(entity1)); d1b = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity1b)); + deviceManager.primaryIndex.findByEntity(entity1b)); d2 = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity2)); + deviceManager.primaryIndex.findByEntity(entity2)); d2b = deviceManager.deviceMap.get( - deviceManager.primaryIndex.findByEntity(entity2b)); + deviceManager.primaryIndex.findByEntity(entity2b)); assertNotSame(d1, d1b); @@ -1745,13 +1931,13 @@ public class DeviceManagerImplTest extends FloodlightTestCase { Long deviceKey2b = null; deviceKey1 = - classState.classIndex.findByEntity(entity1); + classState.classIndex.findByEntity(entity1); deviceKey1b = - classState.classIndex.findByEntity(entity1b); + classState.classIndex.findByEntity(entity1b); deviceKey2 = - classState.classIndex.findByEntity(entity2); + classState.classIndex.findByEntity(entity2); deviceKey2b = - classState.classIndex.findByEntity(entity2b); + classState.classIndex.findByEntity(entity2b); assertEquals(deviceKey1, deviceKey1b); diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java index 5460ea3a91f42c22065ecaca793ba8b23ece87f8..1c2f7d1b492558f1cff859e6a75e228eff891ce1 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java @@ -42,8 +42,8 @@ public class MockDevice extends Device { super(deviceManager, deviceKey, entity, entityClass); } - public MockDevice(Device device, Entity newEntity) { - super(device, newEntity); + public MockDevice(Device device, Entity newEntity, int insertionpoint) { + super(device, newEntity, insertionpoint); } public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey, diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java index 8b539f664f17e1da2cbc8e099be109fb78d10069..d99dda36e40fba33fa8dbd2fd2bc01b63336ac0c 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java @@ -97,7 +97,8 @@ public class MockDeviceManager extends DeviceManagerImpl { @Override protected Device allocateDevice(Device device, - Entity entity) { - return new MockDevice(device, entity); + Entity entity, + int insertionpoint) { + return new MockDevice(device, entity, insertionpoint); } } diff --git a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java index d1bcf2b16e89a55bff0469271a74e6c53894c0b2..e52829b60acaffedbf0780d8b0197956bf30674a 100644 --- a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java +++ b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java @@ -2,6 +2,7 @@ package net.floodlightcontroller.util; import static org.junit.Assert.*; import java.io.IOException; +import java.net.SocketAddress; import java.util.Collection; import java.util.Date; @@ -99,12 +100,6 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { assertTrue("Unexpected method call", false); } - @Override - public Channel getChannel() { - assertTrue("Unexpected method call", false); - return null; - } - @Override public void setFeaturesReply(OFFeaturesReply featuresReply) { assertTrue("Unexpected method call", false); @@ -228,17 +223,11 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { } @Override - public Role getRole() { + public Role getHARole() { assertTrue("Unexpected method call", false); return null; } - @Override - public boolean isActive() { - assertTrue("Unexpected method call", false); - return false; - } - @Override public void deliverStatisticsReply(OFMessage reply) { assertTrue("Unexpected method call", false); @@ -350,61 +339,61 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { } @Override - public int sendNxRoleRequest(Role role, long cookie) { + public void setChannel(Channel channel) { // TODO Auto-generated method stub - return 0; + } @Override - public boolean checkFirstPendingRoleRequestCookie(long cookie) { + public void setFloodlightProvider(Controller controller) { // TODO Auto-generated method stub - return false; + } @Override - public void setChannel(Channel channel) { + public void setThreadPoolService(IThreadPoolService threadPool) { // TODO Auto-generated method stub } @Override - public void setFloodlightProvider(Controller controller) { + public Lock getListenerReadLock() { // TODO Auto-generated method stub - + return null; } @Override - public void setThreadPoolService(IThreadPoolService threadPool) { + public Lock getListenerWriteLock() { // TODO Auto-generated method stub - + return null; } @Override - public void deliverRoleReply(int xid, Role role) { + public void setHARole(Role role, boolean haRoleReplyReceived) { // TODO Auto-generated method stub } @Override - public void deliverRoleRequestNotSupported(int xid) { + public SocketAddress getInetAddress() { // TODO Auto-generated method stub - + return null; } @Override - public Lock getListenerReadLock() { + public OFPortType getPortType(short port_num) { // TODO Auto-generated method stub return null; } @Override - public boolean checkFirstPendingRoleRequestXid(int xid) { + public boolean isFastPort(short port_num) { // TODO Auto-generated method stub return false; } @Override - public Lock getListenerWriteLock() { + public List<Short> getUplinkPorts() { // TODO Auto-generated method stub return null; } diff --git a/src/test/java/org/openflow/protocol/WildcardsTest.java b/src/test/java/org/openflow/protocol/WildcardsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5bf8d12314a070bc301a8ffda6a36ad02ff7d6a0 --- /dev/null +++ b/src/test/java/org/openflow/protocol/WildcardsTest.java @@ -0,0 +1,162 @@ +package org.openflow.protocol; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; + +import org.junit.Test; +import org.openflow.protocol.Wildcards.Flag; + +public class WildcardsTest { + + @Test + public void testBasic() { + int[] intMasks = { 0, 0x3820e0, OFMatch.OFPFW_ALL_SANITIZED }; + for (int i : intMasks) { + Wildcards w = Wildcards.of(i); + assertEquals(i, w.getInt()); + } + } + + @Test + public void testAllSanitize() { + Wildcards w = Wildcards.of(OFMatch.OFPFW_ALL); + assertEquals(OFMatch.OFPFW_ALL_SANITIZED, w.getInt()); + assertTrue(w.isFull()); + assertFalse(w.isExact()); + } + + @Test + public void testAll() { + Wildcards all = Wildcards.FULL; + assertTrue(all.isFull()); + assertFalse(all.isExact()); + assertEquals(0, all.getNwDstMask()); + assertEquals(0, all.getNwSrcMask()); + + // unsetting flags from NONE is a no-op + Wildcards stillAll = all.wildcard(Flag.IN_PORT); + assertTrue(stillAll.isFull()); + assertEquals(all, stillAll); + + // so is setting a >= 32 netmask + + stillAll = all.withNwSrcMask(0); + assertTrue(stillAll.isFull()); + assertEquals(all, stillAll); + + stillAll = all.withNwDstMask(0); + assertTrue(stillAll.isFull()); + assertEquals(all, stillAll); + } + + @Test + public void testNone() { + Wildcards none = Wildcards.EXACT; + assertTrue(none.isExact()); + assertEquals(32, none.getNwDstMask()); + assertEquals(32, none.getNwSrcMask()); + + // unsetting flags from NONE is a no-op + Wildcards stillNone = none.matchOn(Flag.IN_PORT); + assertTrue(stillNone.isExact()); + assertEquals(none, stillNone); + + // so is setting a >= 32 netmask + stillNone = none.withNwSrcMask(32); + assertTrue(stillNone.isExact()); + assertEquals(none, stillNone); + + stillNone = none.withNwDstMask(32); + assertTrue(stillNone.isExact()); + assertEquals(none, stillNone); + } + + @Test + public void testSetOneFlag() { + Wildcards none = Wildcards.EXACT; + assertTrue(none.isExact()); + assertFalse(none.isWildcarded(Flag.DL_SRC)); + Wildcards one = none.wildcard(Flag.DL_SRC); + assertFalse(one.isExact()); + assertTrue(one.isWildcarded(Flag.DL_SRC)); + assertEquals(OFMatch.OFPFW_DL_SRC, one.getInt()); + assertEquals(EnumSet.of(Flag.DL_SRC), one.getWildcardedFlags()); + } + + @Test + public void testSetTwoFlags() { + Wildcards none = Wildcards.EXACT; + + // set two flags + Wildcards two = none.wildcard(Flag.DL_SRC, Flag.DL_DST); + assertFalse(two.isExact()); + assertTrue(two.isWildcarded(Flag.DL_SRC)); + assertTrue(two.isWildcarded(Flag.DL_DST)); + assertEquals(OFMatch.OFPFW_DL_SRC | OFMatch.OFPFW_DL_DST, two.getInt()); + assertEquals(EnumSet.of(Flag.DL_SRC, Flag.DL_DST), two.getWildcardedFlags()); + + // unset dl_dst + Wildcards gone = two.matchOn(Flag.DL_DST); + assertFalse(gone.isExact()); + assertTrue(gone.isWildcarded(Flag.DL_SRC)); + assertFalse(gone.isWildcarded(Flag.DL_DST)); + assertEquals(OFMatch.OFPFW_DL_SRC, gone.getInt()); + assertEquals(EnumSet.of(Flag.DL_SRC), gone.getWildcardedFlags()); + } + + @Test + public void testSetNwSrc() { + Wildcards none = Wildcards.EXACT; + assertEquals(32, none.getNwSrcMask()); + + // unsetting flags from NONE is a no-op + Wildcards nwSet = none.withNwSrcMask(8); + assertFalse(nwSet.isExact()); + assertEquals(EnumSet.noneOf(Flag.class), nwSet.getWildcardedFlags()); + assertEquals(8, nwSet.getNwSrcMask()); + assertEquals((32 - 8) << OFMatch.OFPFW_NW_SRC_SHIFT, nwSet.getInt()); + } + + @Test + public void testSetNwDst() { + Wildcards none = Wildcards.EXACT; + assertEquals(32, none.getNwDstMask()); + + // unsetting flags from NONE is a no-op + Wildcards nwSet = none.withNwDstMask(8); + assertFalse(nwSet.isExact()); + assertEquals(EnumSet.noneOf(Flag.class), nwSet.getWildcardedFlags()); + assertEquals(8, nwSet.getNwDstMask()); + assertEquals((32 - 8) << OFMatch.OFPFW_NW_DST_SHIFT, nwSet.getInt()); + } + + @Test + public void testToString() { + String s = Wildcards.FULL.toString(); + assertNotNull(s); + assertTrue(s.length() > 0); + } + + @Test + public void testInvert() { + assertEquals(Wildcards.FULL, Wildcards.EXACT.inverted()); + + Wildcards some = Wildcards.of(Flag.DL_VLAN, Flag.DL_VLAN_PCP); + Wildcards inv = some.inverted(); + + for(Flag f : Flag.values()) { + boolean shouldBeSet = (f == Flag.DL_VLAN || f == Flag.DL_VLAN_PCP); + + assertEquals("Flag " + f + " " + + (shouldBeSet ? "should be set " : "should not be set"), + shouldBeSet, some.isWildcarded(f)); + assertEquals(!(f == Flag.DL_VLAN || f == Flag.DL_VLAN_PCP), inv.isWildcarded(f)); + } + assertEquals(0, inv.getNwDstMask()); + assertEquals(0, inv.getNwSrcMask()); + } +}