/** * 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. * * <p>This method acquires the readlock and is thread-safe by itself. Most callers will need to * acquire the write lock before calling this method though (if the caller wants to update the * ports stored by this switch) * * @param newPort the new or modified port. * @return the list of changes */ public OrderedCollection<PortChangeEvent> getSinglePortChanges(OFPortDesc newPort) { lock.readLock().lock(); try { OrderedCollection<PortChangeEvent> events = new LinkedHashSetWrapper<PortChangeEvent>(); // Check if we have a port by the same number in our // old map. OFPortDesc prevPort = portsByNumber.get(newPort.getPortNo()); if (newPort.equals(prevPort)) { // nothing has changed return events; } if (prevPort != null && prevPort.getName().equals(newPort.getName())) { // A simple modify of a exiting port // A previous port with this number exists and it's name // also matches the new port. Find the differences if ((!prevPort.getState().contains(OFPortState.LINK_DOWN) && !prevPort.getConfig().contains(OFPortConfig.PORT_DOWN)) && (newPort.getState().contains(OFPortState.LINK_DOWN) || newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) { events.add(new PortChangeEvent(newPort, PortChangeType.DOWN)); } else if ((prevPort.getState().contains(OFPortState.LINK_DOWN) || prevPort.getConfig().contains(OFPortConfig.PORT_DOWN)) && (!newPort.getState().contains(OFPortState.LINK_DOWN) && !newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) { events.add(new PortChangeEvent(newPort, PortChangeType.UP)); } else { events.add(new PortChangeEvent(newPort, PortChangeType.OTHER_UPDATE)); } return events; } if (prevPort != null) { // There exists a previous port with the same port // number but the port name is different (otherwise we would // never have gotten here) // Remove the port. Name-number mapping(s) have changed events.add(new PortChangeEvent(prevPort, PortChangeType.DELETE)); } // We now need to check if there exists a previous port sharing // the same name as the new/updated port. prevPort = portsByName.get(newPort.getName().toLowerCase()); if (prevPort != null) { // There exists a previous port with the same port // name but the port number is different (otherwise we // never have gotten here). // Remove the port. Name-number mapping(s) have changed events.add(new PortChangeEvent(prevPort, PortChangeType.DELETE)); } // We always need to add the new port. Either no previous port // existed or we just deleted previous ports with inconsistent // name-number mappings events.add(new PortChangeEvent(newPort, PortChangeType.ADD)); return events; } finally { lock.readLock().unlock(); } }
/** * Handle a OFPortStatus message, update the internal data structures that store ports and * return the list of OFChangeEvents. * * <p>This method will increment error/warn counters and log * * @param ps * @return */ @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) { if (ps == null) { throw new NullPointerException("OFPortStatus message must " + "not be null"); } lock.writeLock().lock(); try { OFPortDesc port = ps.getDesc(); OFPortReason reason = ps.getReason(); if (reason == null) { throw new IllegalArgumentException( "Unknown PortStatus " + "reason code " + ps.getReason()); } if (log.isDebugEnabled()) { log.debug( "Handling OFPortStatus: {} for {}", reason, String.format("%s (%d)", port.getName(), port.getPortNo().getPortNumber())); } if (reason == OFPortReason.DELETE) return handlePortStatusDelete(port); // We handle ADD and MODIFY the same way. Since OpenFlow // doesn't specify what uniquely identifies a port the // notion of ADD vs. MODIFY can also be hazy. So we just // compare the new port to the existing ones. Map<OFPort, OFPortDesc> newPortByNumber = new HashMap<OFPort, OFPortDesc>(portsByNumber); OrderedCollection<PortChangeEvent> events = getSinglePortChanges(port); for (PortChangeEvent e : events) { switch (e.type) { case DELETE: newPortByNumber.remove(e.port.getPortNo()); break; case ADD: if (reason != OFPortReason.ADD) { // weird case } // fall through case DOWN: case OTHER_UPDATE: case UP: // update or add the port in the map newPortByNumber.put(e.port.getPortNo(), e.port); break; } } updatePortsWithNewPortsByNumber(newPortByNumber); return events; } finally { lock.writeLock().unlock(); } }
/** * 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. * * <p>This method will increment error/warn counters and log * * @param delPort the port from the port status message that should be deleted. * @return ordered collection of port changes applied to this switch */ private OrderedCollection<PortChangeEvent> handlePortStatusDelete(OFPortDesc delPort) { OrderedCollection<PortChangeEvent> events = new LinkedHashSetWrapper<PortChangeEvent>(); lock.writeLock().lock(); try { Map<OFPort, OFPortDesc> newPortByNumber = new HashMap<OFPort, OFPortDesc>(portsByNumber); OFPortDesc prevPort = portsByNumber.get(delPort.getPortNo()); if (prevPort == null) { // so such port. Do we have a port with the name? prevPort = portsByName.get(delPort.getName()); if (prevPort != null) { newPortByNumber.remove(prevPort.getPortNo()); events.add(new PortChangeEvent(prevPort, PortChangeType.DELETE)); } } else if (prevPort.getName().equals(delPort.getName())) { // port exists with consistent name-number mapping newPortByNumber.remove(delPort.getPortNo()); events.add(new PortChangeEvent(delPort, PortChangeType.DELETE)); } else { // port with same number exists but its name differs. This // is weird. The best we can do is to delete the existing // port(s) that have delPort's name and number. newPortByNumber.remove(delPort.getPortNo()); events.add(new PortChangeEvent(prevPort, PortChangeType.DELETE)); // is there another port that has delPort's name? prevPort = portsByName.get(delPort.getName().toLowerCase()); if (prevPort != null) { newPortByNumber.remove(prevPort.getPortNo()); events.add(new PortChangeEvent(prevPort, PortChangeType.DELETE)); } } updatePortsWithNewPortsByNumber(newPortByNumber); return events; } finally { lock.writeLock().unlock(); } }
/** * Set the internal data structure storing this switch's port to the ports specified by * newPortsByNumber * * <p>CALLER MUST HOLD WRITELOCK * * @param newPortsByNumber * @throws IllegaalStateException if called without holding the writelock */ private void updatePortsWithNewPortsByNumber(Map<OFPort, OFPortDesc> newPortsByNumber) { if (!lock.writeLock().isHeldByCurrentThread()) { throw new IllegalStateException("Method called without " + "holding writeLock"); } Map<String, OFPortDesc> newPortsByName = new HashMap<String, OFPortDesc>(); List<OFPortDesc> newPortList = new ArrayList<OFPortDesc>(); List<OFPortDesc> newEnabledPortList = new ArrayList<OFPortDesc>(); List<OFPort> newEnabledPortNumbers = new ArrayList<OFPort>(); for (OFPortDesc p : newPortsByNumber.values()) { newPortList.add(p); newPortsByName.put(p.getName().toLowerCase(), p); if (!p.getState().contains(OFPortState.LINK_DOWN) && !p.getConfig().contains(OFPortConfig.PORT_DOWN)) { newEnabledPortList.add(p); newEnabledPortNumbers.add(p.getPortNo()); } } portsByName = Collections.unmodifiableMap(newPortsByName); portsByNumber = Collections.unmodifiableMap(newPortsByNumber); enabledPortList = Collections.unmodifiableList(newEnabledPortList); enabledPortNumbers = Collections.unmodifiableList(newEnabledPortNumbers); portList = Collections.unmodifiableList(newPortList); }
/** * 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) * * <p>Implementation note: Since this method can optionally modify the current ports and since * it's not possible to upgrade a read-lock to a write-lock we need to hold the write-lock for * the entire operation. If this becomes a problem and if compares() are common we can consider * splitting in two methods but this requires lots of code duplication * * @param newPorts the list of new ports. * @param doUpdate If true the newPortList will replace the current port list for this switch. * If false this switch will not be changed. * @return The list of differences between the current ports and newPorts * @throws NullPointerException if newPortsList is null * @throws IllegalArgumentException if either port names or port numbers are duplicated in the * newPortsList. */ private OrderedCollection<PortChangeEvent> compareAndUpdatePorts( Collection<OFPortDesc> newPorts, boolean doUpdate) { if (newPorts == null) { throw new NullPointerException("newPortsList must not be null"); } lock.writeLock().lock(); try { OrderedCollection<PortChangeEvent> events = new LinkedHashSetWrapper<PortChangeEvent>(); Map<OFPort, OFPortDesc> newPortsByNumber = new HashMap<OFPort, OFPortDesc>(); Map<String, OFPortDesc> newPortsByName = new HashMap<String, OFPortDesc>(); List<OFPortDesc> newEnabledPortList = new ArrayList<OFPortDesc>(); List<OFPort> newEnabledPortNumbers = new ArrayList<OFPort>(); List<OFPortDesc> newPortsList = new ArrayList<OFPortDesc>(newPorts); for (OFPortDesc p : newPortsList) { if (p == null) { throw new NullPointerException("portList must not " + "contain null values"); } // Add the port to the new maps and lists and check // that every port is unique OFPortDesc duplicatePort; duplicatePort = newPortsByNumber.put(p.getPortNo(), p); if (duplicatePort != null) { String msg = String.format( "Cannot have two ports " + "with the same number: %s <-> %s", String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()), String.format( "%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber())); throw new IllegalArgumentException(msg); } duplicatePort = newPortsByName.put(p.getName().toLowerCase(), p); if (duplicatePort != null) { String msg = String.format( "Cannot have two ports " + "with the same name: %s <-> %s", String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()), String.format( "%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber())); throw new IllegalArgumentException(msg); } // Enabled = not down admin (config) or phys (state) if (!p.getConfig().contains(OFPortConfig.PORT_DOWN) && !p.getState().contains(OFPortState.LINK_DOWN)) { newEnabledPortList.add(p); newEnabledPortNumbers.add(p.getPortNo()); } // get changes events.addAll(getSinglePortChanges(p)); } // find deleted ports // We need to do this after looping through all the new ports // to we can handle changed name<->number mappings correctly // We could pull it into the loop of we address this but // it's probably not worth it for (OFPortDesc oldPort : this.portList) { if (!newPortsByNumber.containsKey(oldPort.getPortNo())) { PortChangeEvent ev = new PortChangeEvent(oldPort, PortChangeType.DELETE); events.add(ev); } } if (doUpdate) { portsByName = Collections.unmodifiableMap(newPortsByName); portsByNumber = Collections.unmodifiableMap(newPortsByNumber); enabledPortList = Collections.unmodifiableList(newEnabledPortList); enabledPortNumbers = Collections.unmodifiableList(newEnabledPortNumbers); portList = Collections.unmodifiableList(newPortsList); } return events; } finally { lock.writeLock().unlock(); } }