| 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.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.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.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.OFType; |
| import org.projectfloodlight.openflow.protocol.OFVersion; |
| 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 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.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 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); |
| } |
| |
| public 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 {}", |
| reason, ps); |
| } |
| |
| 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; |
| } |
| |
| @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 |
| // XXX fix this later to be sure it works for both 1.0 and 1.3 |
| OFMessage fm = getFactory() |
| .buildFlowDelete() |
| .setOutPort(OFPort.ANY) |
| .setOutGroup(OFGroup.ANY) |
| .setTableId(TableId.ALL) |
| .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(); |
| } |
| } |