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()); } /** * Set the port descriptions for this switch from the handshake for an OF1.3 * switch. */ protected void setPortDescReplies(List<OFPortDescStatsReply> portDescList) { if (ofversion != OFVersion.OF_13) return; if (portDescList == null) { log.error("Error setting ports description for switch: {}", getStringId()); return; } /* for (OFPortDescStatsReply portDesc: portDescList) { for (OFPortDesc port: portDesc.getEntries()) { boolean up = false; for (OFPortConfig state: port.getConfig()) { if (state != OFPortConfig.PORT_DOWN) { up = true; break; } } if (up) { portManager.updatePorts(portDesc.getEntries()); } } } */ for (OFPortDescStatsReply portDesc: portDescList) { portManager.updatePorts(portDesc.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) { if (enabledPortList.isEmpty() ) { //portsByName = new HashMap<String, OFPortDesc>(newPortsByName); portsByName = newPortsByName; portsByNumber = newPortsByNumber; enabledPortList = newEnabledPortList; enabledPortNumbers = newEnabledPortNumbers; portList = newPortsList; /* portsByNumber = Collections Collections.unmodifiableMap(newPortsByNumber); enabledPortList = Collections.unmodifiableList(newEnabledPortList); enabledPortNumbers = Collections.unmodifiableList(newEnabledPortNumbers); portList = Collections.unmodifiableList(newPortsList); */ } else { portsByName.putAll(newPortsByName); portsByNumber.putAll(newPortsByNumber); enabledPortList.addAll(newEnabledPortList); enabledPortNumbers.addAll(newEnabledPortNumbers); portList.addAll(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(); OFBarrierReplyFuture future = new OFBarrierReplyFuture(threadPool, this, (int) xid); barrierFutureMap.put(xid, future); write(Collections.singletonList(br)); return future; } public void deliverBarrierReply(OFBarrierReply br) { OFBarrierReplyFuture f = barrierFutureMap.get(br.getXid()); if (f != null) { f.deliverFuture(this, br); barrierFutureMap.remove(br.getXid()); } else { log.warn("Rcvd unknown barrier reply xid: {} from sw: {}", br.getXid(), getStringId()); } } }