/**
* 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.internal;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnull;
import net.floodlightcontroller.core.IOFConnection;
import net.floodlightcontroller.core.IOFConnectionBackend;
import net.floodlightcontroller.core.IOFSwitchBackend;
import net.floodlightcontroller.core.LogicalOFMessageCategory;
import net.floodlightcontroller.core.PortChangeEvent;
import net.floodlightcontroller.core.PortChangeType;
import net.floodlightcontroller.core.SwitchDescription;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted;
import net.floodlightcontroller.core.util.AppCookie;
import net.floodlightcontroller.core.util.URIUtil;
import org.projectfloodlight.openflow.protocol.OFActionType;
import org.projectfloodlight.openflow.protocol.OFBsnControllerConnection;
import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionState;
import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply;
import org.projectfloodlight.openflow.protocol.OFCapabilities;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
import org.projectfloodlight.openflow.protocol.OFFlowWildcards;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPortConfig;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFPortReason;
import org.projectfloodlight.openflow.protocol.OFPortState;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
import org.projectfloodlight.openflow.protocol.OFRequest;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsRequest;
import org.projectfloodlight.openflow.protocol.OFTableFeatures;
import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsReply;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFAuxId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U64;
import net.floodlightcontroller.util.IterableUtils;
import net.floodlightcontroller.util.LinkedHashSetWrapper;
import net.floodlightcontroller.util.OrderedCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* This is the internal representation of an openflow switch.
*/
public class OFSwitch implements IOFSwitchBackend {
protected static final Logger log =
LoggerFactory.getLogger(OFSwitch.class);
protected final ConcurrentMap<Object, Object> attributes;
protected final IOFSwitchManager switchManager;
/* Switch features from initial featuresReply */
protected Set<OFCapabilities> capabilities;
protected long buffers;
protected Set<OFActionType> actions;
protected Collection<TableId> tables;
protected short nTables;
protected final DatapathId datapathId;
private Map<TableId, TableFeatures> tableFeaturesByTableId;
private boolean startDriverHandshakeCalled = false;
private final Map<OFAuxId, IOFConnectionBackend> connections;
private volatile Map<URI, Map<OFAuxId, OFBsnControllerConnection>> controllerConnections;
protected OFFactory factory;
/**
* Members hidden from subclasses
*/
private final PortManager portManager;
private volatile boolean connected;
private volatile OFControllerRole role;
private boolean flowTableFull = false;
protected SwitchDescription description;
private SwitchStatus status;
public static final int OFSWITCH_APP_ID = ident(5);
private TableId maxTableToGetTableMissFlow = TableId.of(4); /* this should cover most HW switches that have a couple SW flow tables */
static {
AppCookie.registerApp(OFSwitch.OFSWITCH_APP_ID, "switch");
}
public OFSwitch(IOFConnectionBackend connection, @Nonnull OFFactory factory, @Nonnull IOFSwitchManager switchManager,
@Nonnull DatapathId datapathId) {
if(connection == null)
throw new NullPointerException("connection must not be null");
if(!connection.getAuxId().equals(OFAuxId.MAIN))
throw new IllegalArgumentException("connection must be the main connection");
if(factory == null)
throw new NullPointerException("factory must not be null");
if(switchManager == null)
throw new NullPointerException("switchManager must not be null");
this.connected = true;
this.factory = factory;
this.switchManager = switchManager;
this.datapathId = datapathId;
this.attributes = new ConcurrentHashMap<Object, Object>();
this.role = null;
this.description = new SwitchDescription();
this.portManager = new PortManager();
this.status = SwitchStatus.HANDSHAKE;
// Connections
this.connections = new ConcurrentHashMap<OFAuxId, IOFConnectionBackend>();
this.connections.put(connection.getAuxId(), connection);
// Switch's controller connection
this.controllerConnections = ImmutableMap.of();
// Defaults properties for an ideal switch
this.setAttribute(PROP_FASTWILDCARDS, EnumSet.allOf(OFFlowWildcards.class));
this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, Boolean.TRUE);
this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, Boolean.TRUE);
this.tableFeaturesByTableId = new HashMap<TableId, TableFeatures>();
this.tables = new ArrayList<TableId>();
}
private static int ident(int i) {
return i;
}
@Override
public OFFactory getOFFactory() {
return factory;
}
/**
* Manages the ports of this switch.
*
* Provides methods to query and update the stored ports. The class ensures
* that every port name and port number is unique. When updating ports
* the class checks if port number <-> port name mappings have change due
* to the update. If a new port P has number and port that are inconsistent
* with the previous mapping(s) the class will delete all previous ports
* with name or number of the new port and then add the new port.
*
* Port names are stored as-is but they are compared case-insensitive
*
* The methods that change the stored ports return a list of
* PortChangeEvents that represent the changes that have been applied
* to the port list so that IOFSwitchListeners can be notified about the
* changes.
*
* Implementation notes:
* - We keep several different representations of the ports to allow for
* fast lookups
* - Ports are stored in unchangeable lists. When a port is modified new
* data structures are allocated.
* - We use a read-write-lock for synchronization, so multiple readers are
* allowed.
*/
protected static class PortManager {
private final ReentrantReadWriteLock lock;
private List<OFPortDesc> portList;
private List<OFPortDesc> enabledPortList;
private List<OFPort> enabledPortNumbers;
private Map<OFPort,OFPortDesc> portsByNumber;
private Map<String,OFPortDesc> portsByName;
public PortManager() {
this.lock = new ReentrantReadWriteLock();
this.portList = Collections.emptyList();
this.enabledPortList = Collections.emptyList();
this.enabledPortNumbers = Collections.emptyList();
this.portsByName = Collections.emptyMap();
this.portsByNumber = Collections.emptyMap();
}
/**
* Set the internal data structure storing this switch's port
* to the ports specified by newPortsByNumber
*
* CALLER MUST HOLD WRITELOCK
*
* @param newPortsByNumber
* @throws IllegaalStateException if called without holding the
* writelock
*/
private void updatePortsWithNewPortsByNumber(
Map<OFPort,OFPortDesc> newPortsByNumber) {
if (!lock.writeLock().isHeldByCurrentThread()) {
throw new IllegalStateException("Method called without " +
"holding writeLock");
}
Map<String,OFPortDesc> newPortsByName =
new HashMap<String, OFPortDesc>();
List<OFPortDesc> newPortList =
new ArrayList<OFPortDesc>();
List<OFPortDesc> newEnabledPortList =
new ArrayList<OFPortDesc>();
List<OFPort> newEnabledPortNumbers = new ArrayList<OFPort>();
for(OFPortDesc p: newPortsByNumber.values()) {
newPortList.add(p);
newPortsByName.put(p.getName().toLowerCase(), p);
if (!p.getState().contains(OFPortState.LINK_DOWN)
&& !p.getConfig().contains(OFPortConfig.PORT_DOWN)) {
if (!newEnabledPortList.contains(p)) {
newEnabledPortList.add(p);
}
if (!newEnabledPortNumbers.contains(p.getPortNo())) {
newEnabledPortNumbers.add(p.getPortNo());
}
}
}
portsByName = Collections.unmodifiableMap(newPortsByName);
portsByNumber =
Collections.unmodifiableMap(newPortsByNumber);
enabledPortList =
Collections.unmodifiableList(newEnabledPortList);
enabledPortNumbers =
Collections.unmodifiableList(newEnabledPortNumbers);
portList = Collections.unmodifiableList(newPortList);
}
/**
* Handle a OFPortStatus delete message for the given port.
* Updates the internal port maps/lists of this switch and returns
* the PortChangeEvents caused by the delete. If the given port
* exists as it, it will be deleted. If the name<->number for the
* given port is inconsistent with the ports stored by this switch
* the method will delete all ports with the number or name of the
* given port.
*
* This method will increment error/warn counters and log
*
* @param delPort the port from the port status message that should
* be deleted.
* @return ordered collection of port changes applied to this switch
*/
private OrderedCollection<PortChangeEvent>
handlePortStatusDelete(OFPortDesc delPort) {
OrderedCollection<PortChangeEvent> events =
new LinkedHashSetWrapper<PortChangeEvent>();
lock.writeLock().lock();
try {
Map<OFPort,OFPortDesc> newPortByNumber =
new HashMap<OFPort, OFPortDesc>(portsByNumber);
OFPortDesc prevPort =
portsByNumber.get(delPort.getPortNo());
if (prevPort == null) {
// so such port. Do we have a port with the name?
prevPort = portsByName.get(delPort.getName());
if (prevPort != null) {
newPortByNumber.remove(prevPort.getPortNo());
events.add(new PortChangeEvent(prevPort,
PortChangeType.DELETE));
}
} else if (prevPort.getName().equals(delPort.getName())) {
// port exists with consistent name-number mapping
newPortByNumber.remove(delPort.getPortNo());
events.add(new PortChangeEvent(delPort,
PortChangeType.DELETE));
} else {
// port with same number exists but its name differs. This
// is weird. The best we can do is to delete the existing
// port(s) that have delPort's name and number.
newPortByNumber.remove(delPort.getPortNo());
events.add(new PortChangeEvent(prevPort,
PortChangeType.DELETE));
// is there another port that has delPort's name?
prevPort = portsByName.get(delPort.getName().toLowerCase());
if (prevPort != null) {
newPortByNumber.remove(prevPort.getPortNo());
events.add(new PortChangeEvent(prevPort,
PortChangeType.DELETE));
}
}
updatePortsWithNewPortsByNumber(newPortByNumber);
return events;
} finally {
lock.writeLock().unlock();
}
}
/**
* Handle a OFPortStatus message, update the internal data structures
* that store ports and return the list of OFChangeEvents.
*
* This method will increment error/warn counters and log
*
* @param ps
* @return
*/
@SuppressFBWarnings(value="SF_SWITCH_FALLTHROUGH")
public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) {
if (ps == null) {
throw new NullPointerException("OFPortStatus message must " +
"not be null");
}
lock.writeLock().lock();
try {
OFPortDesc port = ps.getDesc();
OFPortReason reason = ps.getReason();
if (reason == null) {
throw new IllegalArgumentException("Unknown PortStatus " +
"reason code " + ps.getReason());
}
if (log.isDebugEnabled()) {
log.debug("Handling OFPortStatus: {} for {}",
reason, String.format("%s (%d)", port.getName(), port.getPortNo().getPortNumber()));
}
if (reason == OFPortReason.DELETE)
return handlePortStatusDelete(port);
// We handle ADD and MODIFY the same way. Since OpenFlow
// doesn't specify what uniquely identifies a port the
// notion of ADD vs. MODIFY can also be hazy. So we just
// compare the new port to the existing ones.
Map<OFPort,OFPortDesc> newPortByNumber =
new HashMap<OFPort, OFPortDesc>(portsByNumber);
OrderedCollection<PortChangeEvent> events = getSinglePortChanges(port);
for (PortChangeEvent e: events) {
switch(e.type) {
case DELETE:
newPortByNumber.remove(e.port.getPortNo());
break;
case ADD:
if (reason != OFPortReason.ADD) {
// weird case
}
// fall through
case DOWN:
case OTHER_UPDATE:
case UP:
// update or add the port in the map
newPortByNumber.put(e.port.getPortNo(), e.port);
break;
}
}
updatePortsWithNewPortsByNumber(newPortByNumber);
return events;
} finally {
lock.writeLock().unlock();
}
}
/**
* Given a new or modified port newPort, returns the list of
* PortChangeEvents to "transform" the current ports stored by
* this switch to include / represent the new port. The ports stored
* by this switch are <b>NOT</b> updated.
*
* This method acquires the readlock and is thread-safe by itself.
* Most callers will need to acquire the write lock before calling
* this method though (if the caller wants to update the ports stored
* by this switch)
*
* @param newPort the new or modified port.
* @return the list of changes
*/
public OrderedCollection<PortChangeEvent>
getSinglePortChanges(OFPortDesc newPort) {
lock.readLock().lock();
try {
OrderedCollection<PortChangeEvent> events =
new LinkedHashSetWrapper<PortChangeEvent>();
// Check if we have a port by the same number in our
// old map.
OFPortDesc prevPort =
portsByNumber.get(newPort.getPortNo());
if (newPort.equals(prevPort)) {
// nothing has changed
return events;
}
if (prevPort != null &&
prevPort.getName().equals(newPort.getName())) {
// A simple modify of a exiting port
// A previous port with this number exists and it's name
// also matches the new port. Find the differences
if ((!prevPort.getState().contains(OFPortState.LINK_DOWN)
&& !prevPort.getConfig().contains(OFPortConfig.PORT_DOWN))
&& (newPort.getState().contains(OFPortState.LINK_DOWN)
|| newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) {
events.add(new PortChangeEvent(newPort,
PortChangeType.DOWN));
} else if ((prevPort.getState().contains(OFPortState.LINK_DOWN)
|| prevPort.getConfig().contains(OFPortConfig.PORT_DOWN))
&& (!newPort.getState().contains(OFPortState.LINK_DOWN)
&& !newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) {
events.add(new PortChangeEvent(newPort,
PortChangeType.UP));
} else {
events.add(new PortChangeEvent(newPort,
PortChangeType.OTHER_UPDATE));
}
return events;
}
if (prevPort != null) {
// There exists a previous port with the same port
// number but the port name is different (otherwise we would
// never have gotten here)
// Remove the port. Name-number mapping(s) have changed
events.add(new PortChangeEvent(prevPort,
PortChangeType.DELETE));
}
// We now need to check if there exists a previous port sharing
// the same name as the new/updated port.
prevPort = portsByName.get(newPort.getName().toLowerCase());
if (prevPort != null) {
// There exists a previous port with the same port
// name but the port number is different (otherwise we
// never have gotten here).
// Remove the port. Name-number mapping(s) have changed
events.add(new PortChangeEvent(prevPort,
PortChangeType.DELETE));
}
// We always need to add the new port. Either no previous port
// existed or we just deleted previous ports with inconsistent
// name-number mappings
events.add(new PortChangeEvent(newPort, PortChangeType.ADD));
return events;
} finally {
lock.readLock().unlock();
}
}
/**
* Compare the current ports of this switch to the newPorts list and
* return the changes that would be applied to transform the current
* ports to the new ports. No internal data structures are updated
* see {@link #compareAndUpdatePorts(List, boolean)}
*
* @param newPorts the list of new ports
* @return The list of differences between the current ports and
* newPortList
*/
public OrderedCollection<PortChangeEvent>
comparePorts(Collection<OFPortDesc> newPorts) {
return compareAndUpdatePorts(newPorts, false);
}
/**
* Compare the current ports of this switch to the newPorts list and
* return the changes that would be applied to transform the current
* ports to the new ports. No internal data structures are updated
* see {@link #compareAndUpdatePorts(List, boolean)}
*
* @param newPorts the list of new ports
* @return The list of differences between the current ports and
* newPortList
*/
public OrderedCollection<PortChangeEvent>
updatePorts(Collection<OFPortDesc> newPorts) {
return compareAndUpdatePorts(newPorts, true);
}
/**
* Compare the current ports stored in this switch instance with the
* new port list given and return the differences in the form of
* PortChangeEvents. If the doUpdate flag is true, newPortList will
* replace the current list of this switch (and update the port maps)
*
* Implementation note:
* Since this method can optionally modify the current ports and
* since it's not possible to upgrade a read-lock to a write-lock
* we need to hold the write-lock for the entire operation. If this
* becomes a problem and if compares() are common we can consider
* splitting in two methods but this requires lots of code duplication
*
* @param newPorts the list of new ports.
* @param doUpdate If true the newPortList will replace the current
* port list for this switch. If false this switch will not be changed.
* @return The list of differences between the current ports and
* newPorts
* @throws NullPointerException if newPortsList is null
* @throws IllegalArgumentException if either port names or port numbers
* are duplicated in the newPortsList.
*/
private OrderedCollection<PortChangeEvent> compareAndUpdatePorts(
Collection<OFPortDesc> newPorts,
boolean doUpdate) {
if (newPorts == null) {
throw new NullPointerException("newPortsList must not be null");
}
lock.writeLock().lock();
try {
OrderedCollection<PortChangeEvent> events =
new LinkedHashSetWrapper<PortChangeEvent>();
Map<OFPort,OFPortDesc> newPortsByNumber =
new HashMap<OFPort, OFPortDesc>();
Map<String,OFPortDesc> newPortsByName =
new HashMap<String, OFPortDesc>();
List<OFPortDesc> newEnabledPortList =
new ArrayList<OFPortDesc>();
List<OFPort> newEnabledPortNumbers =
new ArrayList<OFPort>();
List<OFPortDesc> newPortsList =
new ArrayList<OFPortDesc>(newPorts);
for (OFPortDesc p: newPortsList) {
if (p == null) {
throw new NullPointerException("portList must not " +
"contain null values");
}
// Add the port to the new maps and lists and check
// that every port is unique
OFPortDesc duplicatePort;
duplicatePort = newPortsByNumber.put(p.getPortNo(), p);
if (duplicatePort != null) {
String msg = String.format("Cannot have two ports " +
"with the same number: %s <-> %s",
String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()),
String.format("%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber()));
throw new IllegalArgumentException(msg);
}
duplicatePort =
newPortsByName.put(p.getName().toLowerCase(), p);
if (duplicatePort != null) {
String msg = String.format("Cannot have two ports " +
"with the same name: %s <-> %s",
String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()),
String.format("%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber()));
throw new IllegalArgumentException(msg);
}
// Enabled = not down admin (config) or phys (state)
if (!p.getConfig().contains(OFPortConfig.PORT_DOWN)
&& !p.getState().contains(OFPortState.LINK_DOWN)) {
if (!newEnabledPortList.contains(p)) {
newEnabledPortList.add(p);
}
if (!newEnabledPortNumbers.contains(p.getPortNo())) {
newEnabledPortNumbers.add(p.getPortNo());
}
}
// get changes
events.addAll(getSinglePortChanges(p));
}
// find deleted ports
// We need to do this after looping through all the new ports
// to we can handle changed name<->number mappings correctly
// We could pull it into the loop of we address this but
// it's probably not worth it
for (OFPortDesc oldPort: this.portList) {
if (!newPortsByNumber.containsKey(oldPort.getPortNo())) {
PortChangeEvent ev =
new PortChangeEvent(oldPort,
PortChangeType.DELETE);
events.add(ev);
}
}
if (doUpdate) {
portsByName = Collections.unmodifiableMap(newPortsByName);
portsByNumber =
Collections.unmodifiableMap(newPortsByNumber);
enabledPortList =
Collections.unmodifiableList(newEnabledPortList);
enabledPortNumbers =
Collections.unmodifiableList(newEnabledPortNumbers);
portList = Collections.unmodifiableList(newPortsList);
}
return events;
} finally {
lock.writeLock().unlock();
}
}
public OFPortDesc getPort(String name) {
if (name == null) {
throw new NullPointerException("Port name must not be null");
}
lock.readLock().lock();
try {
return portsByName.get(name.toLowerCase());
} finally {
lock.readLock().unlock();
}
}
public OFPortDesc getPort(OFPort portNumber) {
lock.readLock().lock();
try {
return portsByNumber.get(portNumber);
} finally {
lock.readLock().unlock();
}
}
public List<OFPortDesc> getPorts() {
lock.readLock().lock();
try {
return portList;
} finally {
lock.readLock().unlock();
}
}
public List<OFPortDesc> getEnabledPorts() {
lock.readLock().lock();
try {
return enabledPortList;
} finally {
lock.readLock().unlock();
}
}
public List<OFPort> getEnabledPortNumbers() {
lock.readLock().lock();
try {
return enabledPortNumbers;
} finally {
lock.readLock().unlock();
}
}
}
protected static class SwitchRoleMessageValidator {
private static final Map<OFVersion, Set<OFType>> invalidSlaveMsgsByOFVersion;
static {
Map<OFVersion, Set<OFType>> m = new HashMap<OFVersion, Set<OFType>>();
Set<OFType> s = new HashSet<OFType>();
s.add(OFType.PACKET_OUT);
s.add(OFType.FLOW_MOD);
s.add(OFType.PORT_MOD);
s.add(OFType.TABLE_MOD);
s.add(OFType.BARRIER_REQUEST);
m.put(OFVersion.OF_10, Collections.unmodifiableSet(s));
s = new HashSet<OFType>();
s.addAll(m.get(OFVersion.OF_10));
s.add(OFType.GROUP_MOD);
s.add(OFType.TABLE_MOD);
m.put(OFVersion.OF_11, Collections.unmodifiableSet(s));
s = new HashSet<OFType>();
s.addAll(m.get(OFVersion.OF_11));
m.put(OFVersion.OF_12, Collections.unmodifiableSet(s));
s = new HashSet<OFType>();
s.addAll(m.get(OFVersion.OF_12));
s.add(OFType.METER_MOD);
m.put(OFVersion.OF_13, Collections.unmodifiableSet(s));
s = new HashSet<OFType>();
s.addAll(m.get(OFVersion.OF_13));
s.add(OFType.BUNDLE_ADD_MESSAGE);
s.add(OFType.BUNDLE_CONTROL);
m.put(OFVersion.OF_14, Collections.unmodifiableSet(s));
invalidSlaveMsgsByOFVersion = Collections.unmodifiableMap(m);
}
/**
* Sorts any invalid messages by moving them from the msgList. The net result
* is a new list returned containing the invalid messages and a pruned msgList
* containing only those messages that are valid for the given role of the controller
* and OpenFlow version of the switch.
*
* @param msgList the list of messages to sort
* @param valid the list of valid messages (caller must allocate)
* @param swVersion the OFVersion of the switch
* @param isSlave true if controller is slave; false otherwise
* @return list of messages that are not valid, removed from input parameter msgList
*/
protected static Collection<OFMessage> pruneInvalidMessages(Iterable<OFMessage> msgList, Collection<OFMessage> valid, OFVersion swVersion, boolean isActive) {
if (isActive) { /* master or equal/other support all */
valid.addAll(IterableUtils.toCollection(msgList));
return Collections.emptyList();
} else { /* slave */
Set<OFType> invalidSlaveMsgs = invalidSlaveMsgsByOFVersion.get(swVersion);
List<OFMessage> invalid = new ArrayList<OFMessage>();
Iterator<OFMessage> itr = msgList.iterator();
while (itr.hasNext()) {
OFMessage m = itr.next();
if (invalidSlaveMsgs.contains(m.getType())) {
invalid.add(m);
} else {
valid.add(m);
}
}
return invalid;
}
}
}
@Override
public boolean attributeEquals(String name, Object other) {
Object attr = this.attributes.get(name);
if (attr == null)
return false;
return attr.equals(other);
}
@Override
public Object getAttribute(String name) {
// returns null if key doesn't exist
return this.attributes.get(name);
}
@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
public void registerConnection(IOFConnectionBackend connection) {
this.connections.put(connection.getAuxId(), connection);
}
@Override
public ImmutableList<IOFConnection> getConnections() {
return ImmutableList.<IOFConnection> copyOf(this.connections.values());
}
@Override
public void removeConnections() {
this.connections.clear();
}
@Override
public void removeConnection(IOFConnectionBackend connection) {
this.connections.remove(connection.getAuxId());
}
/**
* Gets a connection specified by aux Id.
* @param auxId the specified aux id for the connection desired.
* @return the aux connection specified by the auxId
*/
public IOFConnection getConnection(OFAuxId auxId) {
IOFConnection connection = this.connections.get(auxId);
if (connection == null) {
throw new IllegalArgumentException("OF Connection for " + this + " with " + auxId + " does not exist.");
}
return connection;
}
public IOFConnection getConnection(LogicalOFMessageCategory category) {
if (switchManager.isCategoryRegistered(category)) {
return getConnection(category.getAuxId());
}
else{
throw new IllegalArgumentException(category + " is not registered with the floodlight provider service.");
}
}
/**
* Write a single message to the switch
*
* @param m the message to write
* @return true upon success; false upon failure;
* failure can occur either from sending a message not supported in the current role, or
* from the channel being disconnected
*/
@Override
public boolean write(OFMessage m) {
return this.write(Collections.singletonList(m)).isEmpty();
}
/**
* Write a list of messages to the switch
*
* @param msglist list of messages to write
* @return list of failed messages; messages can fail if sending the messages is not supported
* in the current role, or from the channel becoming disconnected
*/
@Override
public Collection<OFMessage> write(Iterable<OFMessage> msglist) {
return this.write(msglist, LogicalOFMessageCategory.MAIN);
}
@Override
public boolean write(OFMessage m, LogicalOFMessageCategory category) {
return this.write(Collections.singletonList(m), category).isEmpty();
}
@Override
public Collection<OFMessage> write(Iterable<OFMessage> msgList, LogicalOFMessageCategory category) {
IOFConnection conn = this.getConnection(category); /* do first to check for supported category */
Collection<OFMessage> validMsgs = new ArrayList<OFMessage>();
Collection<OFMessage> invalidMsgs = SwitchRoleMessageValidator.pruneInvalidMessages(
msgList, validMsgs, this.getOFFactory().getVersion(), this.isActive());
if (log.isDebugEnabled()) {
log.debug("MESSAGES: {}, VALID: {}, INVALID: {}", new Object[] { msgList, validMsgs, invalidMsgs});
}
/* Try to write all valid messages */
Collection<OFMessage> unsent = conn.write(validMsgs);
for (OFMessage m : validMsgs) {
if (!unsent.contains(m)) {
switchManager.handleOutgoingMessage(this, m);
}
}
/* Collect invalid and unsent messages */
Collection<OFMessage> ret = null;
if (!unsent.isEmpty()) {
log.warn("Could not send messages {} due to channel disconnection on switch {}", unsent, this.getId());
ret = IterableUtils.toCollection(unsent);
}
if (!invalidMsgs.isEmpty()) {
log.warn("Could not send messages {} while in SLAVE role on switch {}", invalidMsgs, this.getId());
if (ret == null) {
ret = IterableUtils.toCollection(invalidMsgs);
} else {
ret.addAll(IterableUtils.toCollection(invalidMsgs));
}
}
if (ret == null) {
return Collections.emptyList();
} else {
return ret;
}
}
@Override
public OFConnection getConnectionByCategory(LogicalOFMessageCategory category){
return (OFConnection) this.getConnection(category);
}
@Override
public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request, LogicalOFMessageCategory category) {
return getConnection(category).writeRequest(request);
}
@Override
public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) {
return writeRequest(request, LogicalOFMessageCategory.MAIN);
}
@Override
public void disconnect() {
// Iterate through connections and perform cleanup
for (Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()) {
entry.getValue().disconnect();
this.connections.remove(entry.getKey());
}
log.debug("~~~~~~~SWITCH DISCONNECTED~~~~~~");
// Remove all counters from the store
connected = false;
}
@Override
public void setFeaturesReply(OFFeaturesReply featuresReply) {
if (portManager.getPorts().isEmpty() && featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) {
/* ports are updated via port status message, so we
* only fill in ports on initial connection.
*/
List<OFPortDesc> OFPortDescs = featuresReply.getPorts();
portManager.updatePorts(OFPortDescs);
}
this.capabilities = featuresReply.getCapabilities();
this.buffers = featuresReply.getNBuffers();
if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0 ) {
/* OF1.3+ Per-table actions are set later in the OFTableFeaturesRequest/Reply */
this.actions = featuresReply.getActions();
}
this.nTables = featuresReply.getNTables();
}
@Override
public void setPortDescStats(OFPortDescStatsReply reply) {
/* ports are updated via port status message, so we
* only fill in ports on initial connection.
*/
List<OFPortDesc> OFPortDescs = reply.getEntries();
portManager.updatePorts(OFPortDescs);
}
@Override
public Collection<OFPortDesc> getEnabledPorts() {
return portManager.getEnabledPorts();
}
@Override
public Collection<OFPort> getEnabledPortNumbers() {
return portManager.getEnabledPortNumbers();
}
@Override
public OFPortDesc getPort(OFPort portNumber) {
return portManager.getPort(portNumber);
}
@Override
public OFPortDesc getPort(String portName) {
return portManager.getPort(portName);
}
@Override
public OrderedCollection<PortChangeEvent>
processOFPortStatus(OFPortStatus ps) {
return portManager.handlePortStatusMessage(ps);
}
@Override
public void processOFTableFeatures(List<OFTableFeaturesStatsReply> replies) {
/*
* Parse out all the individual replies for each table.
*/
for (OFTableFeaturesStatsReply reply : replies) {
/*
* Add or update the features for a particular table.
*/
List<OFTableFeatures> tfs = reply.getEntries();
for (OFTableFeatures tf : tfs) {
tableFeaturesByTableId.put(tf.getTableId(), TableFeatures.of(tf));
tables.add(tf.getTableId());
log.trace("Received TableFeatures for TableId {}, TableName {}", tf.getTableId().toString(), tf.getName());
}
}
}
@Override
public Collection<OFPortDesc> getSortedPorts() {
List<OFPortDesc> sortedPorts =
new ArrayList<OFPortDesc>(portManager.getPorts());
Collections.sort(sortedPorts, new Comparator<OFPortDesc>() {
@Override
public int compare(OFPortDesc o1, OFPortDesc o2) {
String name1 = o1.getName();
String name2 = o2.getName();
return name1.compareToIgnoreCase(name2);
}
});
return sortedPorts;
}
@Override
public Collection<OFPortDesc> getPorts() {
return portManager.getPorts();
}
@Override
public OrderedCollection<PortChangeEvent>
comparePorts(Collection<OFPortDesc> ports) {
return portManager.comparePorts(ports);
}
@Override
public OrderedCollection<PortChangeEvent>
setPorts(Collection<OFPortDesc> ports) {
return portManager.updatePorts(ports);
}
@Override
public boolean portEnabled(OFPort portNumber) {
OFPortDesc p = portManager.getPort(portNumber);
if (p == null) return false;
return (!p.getState().contains(OFPortState.BLOCKED) && !p.getState().contains(OFPortState.LINK_DOWN) && !p.getState().contains(OFPortState.STP_BLOCK));
}
@Override
public boolean portEnabled(String portName) {
OFPortDesc p = portManager.getPort(portName);
if (p == null) return false;
return (!p.getState().contains(OFPortState.BLOCKED) && !p.getState().contains(OFPortState.LINK_DOWN) && !p.getState().contains(OFPortState.STP_BLOCK));
}
@Override
public DatapathId getId() {
if (datapathId == null)
throw new RuntimeException("Features reply has not yet been set");
return datapathId;
}
@Override
public String toString() {
return "OFSwitch DPID[" + ((datapathId != null) ? datapathId.toString() : "?") + "]";
}
@Override
public ConcurrentMap<Object, Object> getAttributes() {
return this.attributes;
}
@Override
public Date getConnectedSince() {
return this.connections.get(OFAuxId.MAIN).getConnectedSince();
}
@Override
public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request) {
return addInternalStatsReplyListener(connections.get(OFAuxId.MAIN).writeStatsRequest(request), request);
}
@Override
public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request, LogicalOFMessageCategory category) {
return addInternalStatsReplyListener(getConnection(category).writeStatsRequest(request), request);
}
/**
* Append a listener to receive an OFStatsReply and update the
* internal OFSwitch data structures.
*
* This presently taps into the following stats request
* messages to listen for the corresponding reply:
* -- OFTableFeaturesStatsRequest
*
* Extend this to tap into and update other OFStatsType messages.
*
* @param future
* @param request
* @return
*/
private <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> addInternalStatsReplyListener(final ListenableFuture<List<REPLY>> future, OFStatsRequest<REPLY> request) {
switch (request.getStatsType()) {
case TABLE_FEATURES:
/* case YOUR_CASE_HERE */
future.addListener(new Runnable() {
/*
* We know the reply will be a list of OFStatsReply.
*/
@SuppressWarnings("unchecked")
@Override
public void run() {
/*
* The OFConnection handles REPLY_MORE for us in the case there
* are multiple OFStatsReply messages with the same XID.
*/
try {
List<? extends OFStatsReply> replies = future.get();
if (!replies.isEmpty()) {
/*
* By checking only the 0th element, we assume all others are the same type.
* TODO If not, what then?
*/
switch (replies.get(0).getStatsType()) {
case TABLE_FEATURES:
processOFTableFeatures((List<OFTableFeaturesStatsReply>) future.get());
break;
/* case YOUR_CASE_HERE */
default:
throw new Exception("Received an invalid OFStatsReply of "
+ replies.get(0).getStatsType().toString() + ". Expected TABLE_FEATURES.");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, MoreExecutors.sameThreadExecutor()); /* No need for another thread. */
default:
break;
}
return future; /* either unmodified or with an additional listener */
}
@Override
public void cancelAllPendingRequests() {
for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){
entry.getValue().cancelAllPendingRequests();
}
}
// If any connections are down consider a switch disconnected
@Override
public boolean isConnected() {
return connected;
}
@Override
public boolean isActive() {
// no lock needed since we use volatile
return isConnected() && (this.role == OFControllerRole.ROLE_MASTER || this.role == OFControllerRole.ROLE_EQUAL);
}
@Override
public OFControllerRole getControllerRole() {
return role;
}
@Override
public void setControllerRole(OFControllerRole role) {
this.role = role;
}
/**
* Get the IP Address for the switch
* @return the inet address
*/
@Override
public SocketAddress getInetAddress() {
return connections.get(OFAuxId.MAIN).getRemoteInetAddress();
}
@Override
public long getBuffers() {
return buffers;
}
@Override
public Set<OFActionType> getActions() {
return actions;
}
@Override
public Set<OFCapabilities> getCapabilities() {
return capabilities;
}
/**
* This performs a copy on each 'get'.
* Use sparingly for good performance.
*/
@Override
public Collection<TableId> getTables() {
return new ArrayList<TableId>(tables);
}
@Override
public short getNumTables() {
return this.nTables;
}
@Override
public SwitchDescription getSwitchDescription() {
return description;
}
@Override
public void setTableFull(boolean isFull) {
if (isFull && !flowTableFull) {
switchManager.addSwitchEvent(this.datapathId,
"SWITCH_FLOW_TABLE_FULL " +
"Table full error from switch", false);
log.warn("Switch {} flow table is full", datapathId.toString());
}
flowTableFull = isFull;
}
@Override
public void startDriverHandshake() {
if (startDriverHandshakeCalled)
throw new SwitchDriverSubHandshakeAlreadyStarted();
startDriverHandshakeCalled = true;
}
@Override
public boolean isDriverHandshakeComplete() {
if (!startDriverHandshakeCalled)
throw new SwitchDriverSubHandshakeNotStarted();
return true;
}
@Override
public void processDriverHandshakeMessage(OFMessage m) {
if (startDriverHandshakeCalled)
throw new SwitchDriverSubHandshakeCompleted(m);
else
throw new SwitchDriverSubHandshakeNotStarted();
}
@Override
public void setSwitchProperties(SwitchDescription description) {
this.description = description;
}
@Override
public SwitchStatus getStatus() {
return status;
}
@Override
public void setStatus(SwitchStatus switchStatus) {
this.status = switchStatus;
}
@Override
public void updateControllerConnections(OFBsnControllerConnectionsReply controllerCxnsReply) {
// Instantiate clean map, can't use a builder here since we need to call temp.get()
Map<URI,Map<OFAuxId, OFBsnControllerConnection>> temp = new ConcurrentHashMap<URI,Map<OFAuxId, OFBsnControllerConnection>>();
List<OFBsnControllerConnection> controllerCxnUpdates = controllerCxnsReply.getConnections();
for(OFBsnControllerConnection update : controllerCxnUpdates) {
URI uri = URI.create(update.getUri());
Map<OFAuxId, OFBsnControllerConnection> cxns = temp.get(uri);
// Add to nested map
if(cxns != null){
cxns.put(update.getAuxiliaryId(), update);
} else{
cxns = new ConcurrentHashMap<OFAuxId, OFBsnControllerConnection>();
cxns.put(update.getAuxiliaryId(), update);
temp.put(uri, cxns);
}
}
this.controllerConnections = ImmutableMap.<URI,Map<OFAuxId, OFBsnControllerConnection>>copyOf(temp);
}
@Override
public boolean hasAnotherMaster() {
//TODO: refactor get connection to not throw illegal arg exceptions
IOFConnection mainCxn = this.getConnection(OFAuxId.MAIN);
if(mainCxn != null) {
// Determine the local URI
InetSocketAddress address = (InetSocketAddress) mainCxn.getLocalInetAddress();
URI localURI = URIUtil.createURI(address.getHostName(), address.getPort());
for(Entry<URI,Map<OFAuxId, OFBsnControllerConnection>> entry : this.controllerConnections.entrySet()) {
// Don't check our own controller connections
URI uri = entry.getKey();
if(!localURI.equals(uri)){
// We only care for the MAIN connection
Map<OFAuxId, OFBsnControllerConnection> cxns = this.controllerConnections.get(uri);
OFBsnControllerConnection controllerCxn = cxns.get(OFAuxId.MAIN);
if(controllerCxn != null) {
// If the controller id disconnected or not master we know it is not connected
if(controllerCxn.getState() == OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED
&& controllerCxn.getRole() == OFControllerRole.ROLE_MASTER){
return true;
}
} else {
log.warn("Unable to find controller connection with aux id "
+ "MAIN for switch {} on controller with URI {}.",
this, uri);
}
}
}
}
return false;
}
@Override
public TableFeatures getTableFeatures(TableId table) {
return tableFeaturesByTableId.get(table);
}
@Override
public TableId getMaxTableForTableMissFlow() {
return maxTableToGetTableMissFlow;
}
@Override
public TableId setMaxTableForTableMissFlow(TableId max) {
if (max.getValue() >= nTables) {
maxTableToGetTableMissFlow = TableId.of(nTables - 1 < 0 ? 0 : nTables - 1);
} else {
maxTableToGetTableMissFlow = max;
}
return maxTableToGetTableMissFlow;
}
@Override
public U64 getLatency() {
return this.connections.get(OFAuxId.MAIN).getLatency();
}
}