/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.sdnplatform.devicemanager.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.openflow.protocol.OFPhysicalPort; import org.sdnplatform.core.IControllerService; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.core.IOFSwitchListener; import org.sdnplatform.core.annotations.LogMessageCategory; import org.sdnplatform.core.annotations.LogMessageDoc; import org.sdnplatform.devicemanager.SwitchPort; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @LogMessageCategory("Device Management") public class SwitchInterfaceRegexMatcher implements IOFSwitchListener { protected class SwitchInterfaceRegex { @Override public String toString() { return "SwitchInterfaceRegex [dpid=" + dpid + ", pattern=" + pattern + "]"; } public SwitchInterfaceRegex(Long dpid, Pattern pattern) { super(); this.dpid = dpid; this.pattern = pattern; } Long dpid; Pattern pattern; } protected static Logger logger = LoggerFactory.getLogger(BetterDeviceManagerImpl.class); /** * Map keys (tags) to the appropriate regular expression */ protected ConcurrentHashMap<String, SwitchInterfaceRegex> regexMap; /** * Map a key (tag) to the collection of switch/ports that matc */ protected ConcurrentHashMap<String, Collection<SwitchPort>> matchKey2Interfaces; protected Object configWriteLock; protected IControllerService controllerProvider; public SwitchInterfaceRegexMatcher( IControllerService controllerProvider) { super(); regexMap = new ConcurrentHashMap<String, SwitchInterfaceRegex>(); matchKey2Interfaces = new ConcurrentHashMap<String, Collection<SwitchPort>>(); this.controllerProvider = controllerProvider; configWriteLock = new Object(); } /** * Add or update an entry for a given key (tag) * @param key the key to add or update * @param dpid the dpid of the switch for which we add an entry. Null means * "all switches" * @param regex the regular expression matching interfaces names. */ @LogMessageDoc(level="ERROR", message="Invalid regular expression {expr} at index {index}", explanation="A transient error occurred while notifying tog changes", recommendation=LogMessageDoc.GENERIC_ACTION) public void addOrUpdate(String key, Long dpid, String regex) { synchronized(configWriteLock) { // we check if this add or update involves only a single switch. SwitchInterfaceRegex oldEntry = regexMap.get(key); Pattern pattern = null; try { pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException e) { logger.error("Invalid regular expression '{}' at index {}: {} ", new Object[] { e.getPattern(), e.getIndex(), e.getDescription() }); return; } SwitchInterfaceRegex newEntry = new SwitchInterfaceRegex(dpid, pattern); if (logger.isDebugEnabled()) { logger.debug("Old entry {}, new entry {}", new Object[] { oldEntry, newEntry } ); } regexMap.put(key, new SwitchInterfaceRegex(dpid, pattern)); ArrayList<SwitchPort> interfaces = new ArrayList<SwitchPort>(); if (dpid != null) { // a specific DPID, only need to check this switch IOFSwitch sw = controllerProvider.getSwitches().get(dpid); if (sw != null) { addMatchingInterfaces(interfaces, sw, pattern); } } else { for (IOFSwitch sw: controllerProvider.getSwitches().values()) addMatchingInterfaces(interfaces, sw, pattern); } if (!interfaces.isEmpty()) matchKey2Interfaces.put(key, interfaces); } } /** * Remove the given key (tag) from matching * @param key */ public void remove(String key) { synchronized(configWriteLock) { SwitchInterfaceRegex oldEntry = regexMap.remove(key); if (oldEntry == null) { logger.debug("Trying to remove interface regex for {} which " + "doesn't exist", key); return; } matchKey2Interfaces.remove(key); } } /** * Get the collection of SwitchPorts that match the given key (tag). * Returns null if the key doesn't exist or no interfaces match. * @param key * @return */ public Collection<SwitchPort> getInterfacesByKey(String key) { Collection<SwitchPort> interfaces = matchKey2Interfaces.get(key); if (interfaces == null) return null; return Collections.unmodifiableCollection(interfaces); } /** * Add all interfaces matching pattern on switch sw to the collection * of interfaces * * @param interfaces * @param sw * @param pattern */ protected void addMatchingInterfaces(Collection<SwitchPort> interfaces, IOFSwitch sw, Pattern pattern) { for (OFPhysicalPort port: sw.getEnabledPorts()) { Matcher m = pattern.matcher(port.getName()); if (m.matches()) { interfaces.add(new SwitchPort(sw.getId(), port.getPortNumber())); } } } /** * Update match maps when a switch changes (added, deleted, ports changed) * * NOTE: Caller needs to hold configWriteLock when calling this method * @param sw */ protected void updateForSwitchChange(IOFSwitch sw, boolean deleted) { for (Entry<String, SwitchInterfaceRegex> entry: regexMap.entrySet()) { SwitchInterfaceRegex interfaceSpec = entry.getValue(); if (interfaceSpec.dpid != null && !interfaceSpec.dpid.equals(sw.getId())) continue; // won't match for this switch Collection<SwitchPort> interfaces = matchKey2Interfaces.get(entry.getKey()); if (interfaces == null) interfaces = new ArrayList<SwitchPort>(); else { // create copy of list interfaces = new ArrayList<SwitchPort>(interfaces); // remove all interfaces from this switch. We need to do // this on a copy otherwise so we don't have a time window // were some ports that should be matching are missing Iterator<SwitchPort> it = interfaces.iterator(); while (it.hasNext()) { SwitchPort swp = it.next(); if (swp.getSwitchDPID() == sw.getId()) it.remove(); } } if (!deleted) { // now add all interfaces that match addMatchingInterfaces(interfaces, sw, interfaceSpec.pattern); } // add back to matching structures if (interfaces.isEmpty()) matchKey2Interfaces.remove(entry.getKey()); else matchKey2Interfaces.put(entry.getKey(), interfaces); } } // IOFSwitchListener @Override public void addedSwitch(IOFSwitch sw) { synchronized(configWriteLock) { updateForSwitchChange(sw, false); } } @Override public void removedSwitch(IOFSwitch sw) { synchronized(configWriteLock) { updateForSwitchChange(sw, true); } } @Override public void switchPortChanged(Long switchId) { synchronized(configWriteLock) { IOFSwitch sw = controllerProvider.getSwitches().get(switchId); if (sw != null) updateForSwitchChange(sw, false); } } @Override public String getName() { return getClass().getName(); } }