blob: 236ed065cf0d6eb30d0645f9cd7a779a5056cb03 [file] [log] [blame]
package net.floodlightcontroller.core.internal;
/**
* 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.
**/
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.Set;
import java.util.WeakHashMap;
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.IFloodlightProviderService.Role;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted;
import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
import net.floodlightcontroller.debugcounter.IDebugCounter;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException;
import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType;
import net.floodlightcontroller.debugcounter.NullDebugCounter;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.util.LinkedHashSetWrapper;
import net.floodlightcontroller.util.OrderedCollection;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jboss.netty.channel.Channel;
import org.projectfloodlight.openflow.protocol.OFActionType;
import org.projectfloodlight.openflow.protocol.OFBarrierReply;
import org.projectfloodlight.openflow.protocol.OFCapabilities;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
import org.projectfloodlight.openflow.protocol.OFFlowDelete.Builder;
import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
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.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsRequest;
import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFAuxId;
import org.projectfloodlight.openflow.types.OFGroup;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the internal representation of an openflow switch.
*/
public class OFSwitchImplBase implements IOFSwitch {
// TODO: should we really do logging in the class or should we throw
// exception that can then be handled by callers?
protected final static Logger log = LoggerFactory.getLogger(OFSwitchImplBase.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;
// transaction id used for messages sent out to this switch from
// this controller instance. This xid has significance only between this
// controller<->switch pair.
protected AtomicInteger transactionIdSource;
// generation id used for roleRequest messages sent to switches (see section
// 6.3.5 of the OF1.3.4 spec). This generationId has significance between
// all the controllers that this switch is connected to; and only for role
// request messages with role MASTER or SLAVE. The set of Controllers that
// this switch is connected to should coordinate the next generation id,
// via transactional semantics.
protected long generationIdSource;
// 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; XXX S remove this
// Map port numbers to the appropriate OFPortDesc
protected ConcurrentHashMap<Integer, OFPortDesc> 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, OFPortDesc> portsByName;
protected Map<Integer, OFStatisticsFuture> statsFutureMap;
// XXX Consider removing the following 2 maps - not used anymore
protected Map<Integer, IOFMessageListener> iofMsgListenersMap;
protected Map<Integer, OFFeaturesReplyFuture> featuresFutureMap;
protected Map<Long, OFBarrierReplyFuture> barrierFutureMap;
protected boolean connected;
protected Role role;
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.
* <p/>
* Need to synchronize on this instance if a request is sent, received,
* checked.
*/
protected LinkedList<PendingRoleRequestEntry> pendingRoleRequests;
/** OpenFlow version for this switch */
protected OFVersion ofversion;
// Description stats reply describing this switch
private OFDescStatsReply switchDescription;
// Switch features from initial featuresReply
protected Set<OFCapabilities> capabilities;
protected int buffers;
protected Set<OFActionType> actions;
protected byte tables;
protected DatapathId datapathId;
private OFAuxId auxId;
private IDebugCounterService debugCounters;
private boolean debugCountersRegistered;
@SuppressWarnings("unused")
private IDebugCounter ctrSwitch, ctrSwitchPktin, ctrSwitchWrite,
ctrSwitchPktinDrops, ctrSwitchWriteDrops;
protected boolean startDriverHandshakeCalled = false;
private boolean flowTableFull = false;
private final PortManager portManager;
protected static final ThreadLocal<Map<OFSwitchImplBase, List<OFMessage>>> local_msg_buffer =
new ThreadLocal<Map<OFSwitchImplBase, List<OFMessage>>>() {
@Override
protected Map<OFSwitchImplBase, List<OFMessage>> initialValue() {
return new WeakHashMap<OFSwitchImplBase, List<OFMessage>>();
}
};
private static final String BASE = "switchbase";
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 OFSwitchImplBase() {
this.stringId = null;
this.attributes = new ConcurrentHashMap<Object, Object>();
this.connectedSince = new Date();
this.transactionIdSource = new AtomicInteger();
this.generationIdSource = 0; // XXX S this is wrong; should be
// negotiated
// XXX S no need this.portLock = new Object();
this.portsByNumber = new ConcurrentHashMap<Integer, OFPortDesc>();
this.portsByName = new ConcurrentHashMap<String, OFPortDesc>();
this.connected = true;
this.statsFutureMap = new ConcurrentHashMap<Integer, OFStatisticsFuture>();
this.featuresFutureMap = new ConcurrentHashMap<Integer, OFFeaturesReplyFuture>();
this.iofMsgListenersMap = new ConcurrentHashMap<Integer, IOFMessageListener>();
this.barrierFutureMap = new ConcurrentHashMap<Long, OFBarrierReplyFuture>();
this.role = null;
this.listenerLock = new ReentrantReadWriteLock();
this.pendingRoleRequests = new LinkedList<OFSwitchImplBase.PendingRoleRequestEntry>();
this.portManager = new PortManager();
// by default the base impl declares no support for Nx_role_requests.
// OF1.0 switches like OVS that do support these messages should set the
// attribute in the associated switch driver.
setAttribute(SWITCH_SUPPORTS_NX_ROLE, false);
}
// *******************************************
// Setters and Getters
// *******************************************
@Override
public Object getAttribute(String name) {
if (this.attributes.containsKey(name)) {
return this.attributes.get(name);
}
return null;
}
@Override
public ConcurrentMap<Object, Object> getAttributes() {
return this.attributes;
}
@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
@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.getLong();
}
@JsonIgnore
@Override
public String getStringId() {
return stringId;
}
@Override
public OFFactory getFactory() {
return OFFactories.getFactory(ofversion);
}
@Override
public OFVersion getOFVersion() {
return ofversion;
}
@Override
public void setOFVersion(OFVersion ofv) {
ofversion = ofv;
}
/**
* @param floodlightProvider the floodlightProvider to set
*/
@JsonIgnore
@Override
public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
this.floodlightProvider = floodlightProvider;
}
@JsonIgnore
@Override
public void setThreadPoolService(IThreadPoolService tp) {
this.threadPool = tp;
}
@Override
@JsonIgnore
public void setDebugCounterService(IDebugCounterService debugCounters)
throws CounterException {
this.debugCounters = debugCounters;
registerOverloadCounters();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "OFSwitchImpl [" + ((channel != null) ? channel.getRemoteAddress() : "?")
+ " DPID[" + ((stringId != null) ? stringId : "?") + "]]";
}
// *******************************************
// Channel related methods
// *******************************************
@JsonIgnore
@Override
public void setChannel(Channel channel) {
this.channel = channel;
}
@Override
public SocketAddress getChannelSocketAddress(){
return channel.getRemoteAddress();
}
@Override
public void write(OFMessage m, FloodlightContext bc) throws IOException {
Map<OFSwitchImplBase, 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);
}
// XXX S will change when iFloodlight provider changes
// 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;
}
}
// XXX S again
// this.floodlightProvider.handleOutgoingMessage(this, m, bc);
}
this.write(msglist);
}
protected void write(List<OFMessage> msglist) throws IOException {
this.channel.write(msglist);
}
@Override
public void disconnectSwitch() {
channel.close();
}
@Override
public Date getConnectedSince() {
return connectedSince;
}
@JsonIgnore
@Override
public int getNextTransactionId() {
return this.transactionIdSource.incrementAndGet();
}
@JsonIgnore
@Override
public synchronized boolean isConnected() {
return connected;
}
@Override
@JsonIgnore
public synchronized void setConnected(boolean connected) {
this.connected = connected;
}
// *******************************************
// Switch features related methods
// *******************************************
/**
* Set the features reply for this switch from the handshake
*/
protected void setFeaturesReply(OFFeaturesReply featuresReply) {
if (featuresReply == null) {
log.error("Error setting featuresReply for switch: {}", getStringId());
return;
}
this.datapathId = featuresReply.getDatapathId();
this.capabilities = featuresReply.getCapabilities();
this.buffers = (int) featuresReply.getNBuffers();
this.tables = (byte) featuresReply.getNTables();
this.stringId = this.datapathId.toString();
if (ofversion == OFVersion.OF_13) {
auxId = featuresReply.getAuxiliaryId();
if (!auxId.equals(OFAuxId.MAIN)) {
log.warn("This controller does not handle auxiliary connections. "
+ "Aux connection id {} received from switch {}",
auxId, getStringId());
}
}
if (ofversion == OFVersion.OF_10) {
this.actions = featuresReply.getActions();
portManager.compareAndUpdatePorts(featuresReply.getPorts(), true);
}
}
/**
* Set the port descriptions for this switch from the handshake for an OF1.3
* switch.
*/
protected void setPortDescReply(OFPortDescStatsReply pdrep) {
if (ofversion != OFVersion.OF_13)
return;
if (pdrep == null) {
log.error("Error setting ports description for switch: {}", getStringId());
return;
}
portManager.updatePorts(pdrep.getEntries());
}
@Override
public int getNumBuffers() {
return buffers;
}
@Override
public Set<OFActionType> getActions() {
return actions;
}
@Override
public Set<OFCapabilities> getCapabilities() {
return capabilities;
}
@Override
public byte getNumTables() {
return tables;
}
// public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
// throws IOException {
// // XXX S fix this later
// OFMessage request = floodlightProvider.getOFMessageFactory_13()
// .buildFeaturesRequest()
// .setXid(getNextTransactionId())
// .build();
// OFFeaturesReplyFuture future =
// new OFFeaturesReplyFuture(threadPool, this, (int) request.getXid());
// this.featuresFutureMap.put((int) request.getXid(), future);
// this.channel.write(Collections.singletonList(request));
// return future;
//
// }
//
// 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);
}
@JsonIgnore
public void setSwitchDescription(OFDescStatsReply desc) {
switchDescription = desc;
}
@Override
@JsonIgnore
public OFDescStatsReply getSwitchDescription() {
return switchDescription;
}
// *******************************************
// Switch port handling
// *******************************************
@Override
@JsonIgnore
public Collection<OFPortDesc> getEnabledPorts() {
return portManager.getEnabledPorts();
}
@Override
@JsonIgnore
public Collection<Integer> getEnabledPortNumbers() {
return portManager.getEnabledPortNumbers();
}
@Override
public OFPortDesc getPort(int portNumber) {
return portManager.getPort(portNumber);
}
@Override
public OFPortDesc getPort(String portName) {
return portManager.getPort(portName);
}
@Override
@JsonIgnore
public OrderedCollection<PortChangeEvent> processOFPortStatus(OFPortStatus ps) {
return portManager.handlePortStatusMessage(ps);
}
@Override
@JsonProperty("ports")
public Collection<OFPortDesc> getPorts() {
return portManager.getPorts();
}
@Override
public boolean portEnabled(int portNumber) {
return isEnabled(portManager.getPort(portNumber));
}
@Override
public boolean portEnabled(String portName) {
return isEnabled(portManager.getPort(portName));
}
private boolean isEnabled(OFPortDesc p) {
return (p != null &&
!p.getState().contains(OFPortState.LINK_DOWN) &&
!p.getState().contains(OFPortState.BLOCKED) && !p.getConfig().contains(
OFPortConfig.PORT_DOWN));
}
@Override
public OrderedCollection<PortChangeEvent> comparePorts(Collection<OFPortDesc> ports) {
return portManager.comparePorts(ports);
}
@Override
@JsonIgnore
public OrderedCollection<PortChangeEvent> setPorts(Collection<OFPortDesc> ports) {
return portManager.updatePorts(ports);
}
/**
* 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. -
* All port numbers have int representation (no more shorts)
*/
protected class PortManager {
private final ReentrantReadWriteLock lock;
private List<OFPortDesc> portList;
private List<OFPortDesc> enabledPortList;
private List<Integer> enabledPortNumbers;
private Map<Integer, 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<Integer, 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<Integer> newEnabledPortNumbers = new ArrayList<Integer>();
for (OFPortDesc p : newPortsByNumber.values()) {
newPortList.add(p);
newPortsByName.put(p.getName().toLowerCase(), p);
if (isEnabled(p)) {
newEnabledPortList.add(p);
newEnabledPortNumbers.add(p.getPortNo().getPortNumber());
}
}
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) {
lock.writeLock().lock();
OrderedCollection<PortChangeEvent> events =
new LinkedHashSetWrapper<PortChangeEvent>();
try {
Map<Integer, OFPortDesc> newPortByNumber =
new HashMap<Integer, OFPortDesc>(portsByNumber);
OFPortDesc prevPort =
portsByNumber.get(delPort.getPortNo().getPortNumber());
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().getPortNumber());
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().getPortNumber());
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().getPortNumber());
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().getPortNumber());
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
*/
public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) {
if (ps == null) {
throw new NullPointerException("OFPortStatus message must " +
"not be null");
}
lock.writeLock().lock();
try {
OFPortReason reason = ps.getReason();
if (reason == null) {
throw new IllegalArgumentException("Unknown PortStatus " +
"reason code " + ps.getReason());
}
if (log.isDebugEnabled()) {
log.debug("Handling OFPortStatus: {} for {} in sw {}",
reason, ps, getStringId());
}
if (reason == OFPortReason.DELETE)
return handlePortStatusDelete(ps.getDesc());
// 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<Integer, OFPortDesc> newPortByNumber =
new HashMap<Integer, OFPortDesc>(portsByNumber);
OrderedCollection<PortChangeEvent> events =
getSinglePortChanges(ps.getDesc());
for (PortChangeEvent e : events) {
switch (e.type) {
case DELETE:
newPortByNumber.remove(e.port.getPortNo().getPortNumber());
break;
case ADD:
if (reason != OFPortReason.ADD) {
// weird case
}
newPortByNumber.put(e.port.getPortNo().getPortNumber(),
e.port);
break;
case DOWN:
newPortByNumber.put(e.port.getPortNo().getPortNumber(),
e.port);
break;
case OTHER_UPDATE:
newPortByNumber.put(e.port.getPortNo().getPortNumber(),
e.port);
break;
case UP:
newPortByNumber.put(e.port.getPortNo().getPortNumber(),
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().getPortNumber());
if (newPort.equals(prevPort)) {
// nothing has changed
return events;
}
if (prevPort != null &&
prevPort.getName().equals(newPort.getName())) {
// A simple modify of a existing port
// A previous port with this number exists and it's name
// also matches the new port. Find the differences
if (isEnabled(prevPort) && !isEnabled(newPort)) {
events.add(new PortChangeEvent(newPort,
PortChangeType.DOWN));
} else if (!isEnabled(prevPort) && isEnabled(newPort)) {
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 transfort 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<Integer, OFPortDesc> newPortsByNumber =
new HashMap<Integer, OFPortDesc>();
Map<String, OFPortDesc> newPortsByName =
new HashMap<String, OFPortDesc>();
List<OFPortDesc> newEnabledPortList =
new ArrayList<OFPortDesc>();
List<Integer> newEnabledPortNumbers =
new ArrayList<Integer>();
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().getPortNumber(), p);
if (duplicatePort != null) {
String msg = String.format("Cannot have two ports " +
"with the same number: %s <-> %s",
p, duplicatePort);
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",
p.toString().substring(0, 80),
duplicatePort.toString().substring(0, 80));
throw new IllegalArgumentException(msg);
}
if (isEnabled(p)) {
newEnabledPortList.add(p);
newEnabledPortNumbers.add(p.getPortNo().getPortNumber());
}
// 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().getPortNumber())) {
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(int 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<Integer> getEnabledPortNumbers() {
lock.readLock().lock();
try {
return enabledPortNumbers;
} finally {
lock.readLock().unlock();
}
}
}
// *******************************************
// Switch stats handling
// *******************************************
@Override
public Future<List<OFStatsReply>> getStatistics(OFStatsRequest<?> request)
throws IOException {
OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this,
(int) request.getXid());
log.info("description STAT REQUEST XID {}", request.getXid());
this.statsFutureMap.put((int) request.getXid(), future);
this.channel.write(Collections.singletonList(request));
return future;
}
@SuppressWarnings("unused")
private void analyzeStatsReply(OFMessage reply) {
log.info("recieved stats reply (xid = {} type: {}) from sw {} ",
reply.getXid(), reply.getType(), getStringId());
if (reply.getType() == OFType.STATS_REPLY) {
OFStatsReply sr = (OFStatsReply) reply;
if (sr.getStatsType() == OFStatsType.FLOW) {
OFFlowStatsReply fsr = (OFFlowStatsReply) sr;
log.info("received flow stats sw {} --> {}", getStringId(), fsr);
// fsr.getEntries().get(0).getMatch().getMatchFields()
for (OFFlowStatsEntry e : fsr.getEntries()) {
for (MatchField<?> mf : e.getMatch().getMatchFields()) {
log.info("mf is exact: {} for {}: {}",
e.getMatch().isExact(mf),
mf.id,
e.getMatch().get(mf));
}
}
}
}
}
@Override
public void deliverStatisticsReply(OFMessage reply) {
OFStatisticsFuture future = this.statsFutureMap.get((int) 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();
}
// *******************************************
// Switch role handling
// *******************************************
// XXX S this is completely wrong. The generation id should be obtained
// after coordinating with all controllers connected to this switch.
// ie. should be part of registry service (account for 1.3 vs 1.0)
// For now we are just generating this locally and keeping it constant.
public U64 getNextGenerationId() {
// TODO: Pankaj, fix nextGenerationId as part of Registry interface
return U64.of(generationIdSource);
}
@Override
public Role getRole() {
return role;
}
@JsonIgnore
@Override
public void setRole(Role role) {
this.role = role;
}
// *******************************************
// Switch utility methods
// *******************************************
private void registerOverloadCounters() throws CounterException {
if (debugCountersRegistered) {
return;
}
if (debugCounters == null) {
log.error("Debug Counter Service not found");
debugCounters = new NullDebugCounter();
debugCountersRegistered = true;
}
// every level of the hierarchical counter has to be registered
// even if they are not used
ctrSwitch = debugCounters.registerCounter(
BASE, stringId,
"Counter for this switch",
CounterType.ALWAYS_COUNT);
ctrSwitchPktin = debugCounters.registerCounter(
BASE, stringId + "/pktin",
"Packet in counter for this switch",
CounterType.ALWAYS_COUNT);
ctrSwitchWrite = debugCounters.registerCounter(
BASE, stringId + "/write",
"Write counter for this switch",
CounterType.ALWAYS_COUNT);
ctrSwitchPktinDrops = debugCounters.registerCounter(
BASE, stringId + "/pktin/drops",
"Packet in throttle drop count",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
ctrSwitchWriteDrops = debugCounters.registerCounter(
BASE, stringId + "/write/drops",
"Switch write throttle drop count",
CounterType.ALWAYS_COUNT,
IDebugCounterService.CTR_MDATA_WARN);
}
@Override
public void startDriverHandshake() throws IOException {
log.debug("Starting driver handshake for sw {}", getStringId());
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
@JsonIgnore
@LogMessageDoc(level = "WARN",
message = "Switch {switch} flow table is full",
explanation = "The controller received flow table full " +
"message from the switch, could be caused by increased " +
"traffic pattern",
recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG)
public void setTableFull(boolean isFull) {
if (isFull && !flowTableFull) {
floodlightProvider.addSwitchEvent(this.datapathId.getLong(),
"SWITCH_FLOW_TABLE_FULL " +
"Table full error from switch", false);
log.warn("Switch {} flow table is full", stringId);
}
flowTableFull = isFull;
}
@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
// by default if match is not specified, then an empty list of matches
// is sent, resulting in a wildcard-all flows
Builder builder = getFactory()
.buildFlowDelete()
.setOutPort(OFPort.ANY);
if (ofversion.wireVersion >= OFVersion.OF_13.wireVersion) {
builder.setOutGroup(OFGroup.ANY)
.setTableId(TableId.ALL);
}
OFMessage fm = builder.build();
try {
channel.write(Collections.singletonList(fm));
} catch (Exception e) {
log.error("Failed to clear all flows on switch " + this, e);
}
}
@Override
public void flush() {
Map<OFSwitchImplBase, 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) {
log.error("Failed flushing messages", e);
}
msglist.clear();
}
}
public static void flush_all() {
Map<OFSwitchImplBase, List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
for (OFSwitchImplBase 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 listener read lock
*/
@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 listener write lock
*/
@JsonIgnore
public Lock getListenerWriteLock() {
return listenerLock.writeLock();
}
public String getSwitchDriverState() {
return "";
}
public OFBarrierReplyFuture sendBarrier() throws IOException {
long xid = getNextTransactionId();
OFMessage br = getFactory()
.buildBarrierRequest()
.setXid(xid)
.build();
write(Collections.singletonList(br));
OFBarrierReplyFuture future = new OFBarrierReplyFuture(threadPool, this,
(int) xid);
barrierFutureMap.put(xid, future);
return future;
}
public void deliverBarrierReply(OFBarrierReply br) {
OFBarrierReplyFuture f = barrierFutureMap.get(br.getXid());
if (f != null) {
f.deliverFuture(this, br);
barrierFutureMap.remove(br.getXid());
}
}
}