/* * 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.netvirt.manager.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFType; import org.openflow.util.HexString; import org.sdnplatform.addressspace.BetterEntityClass; import org.sdnplatform.core.ListenerContext; import org.sdnplatform.core.IControllerService; import org.sdnplatform.core.IHAListener; import org.sdnplatform.core.IInfoProvider; import org.sdnplatform.core.IOFMessageListener; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.core.IOFSwitchListener; import org.sdnplatform.core.IControllerService.Role; import org.sdnplatform.core.annotations.LogMessageCategory; import org.sdnplatform.core.annotations.LogMessageDoc; import org.sdnplatform.core.annotations.LogMessageDocs; import org.sdnplatform.core.module.ModuleContext; import org.sdnplatform.core.module.ModuleException; import org.sdnplatform.core.module.IModule; import org.sdnplatform.core.module.IPlatformService; import org.sdnplatform.core.util.ListenerDispatcher; import org.sdnplatform.core.util.SingletonTask; import org.sdnplatform.devicegroup.DeviceGroupMatcher; import org.sdnplatform.devicegroup.IDeviceGroupMatcher; import org.sdnplatform.devicegroup.MembershipRule; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.devicemanager.IDeviceListener; import org.sdnplatform.devicemanager.IDeviceService; import org.sdnplatform.devicemanager.IEntityClass; import org.sdnplatform.devicemanager.SwitchPort; import org.sdnplatform.flowcache.FCQueryObj; import org.sdnplatform.flowcache.FlowCacheQueryResp; import org.sdnplatform.flowcache.IFlowCacheService; import org.sdnplatform.flowcache.IFlowQueryHandler; import org.sdnplatform.flowcache.IFlowReconcileListener; import org.sdnplatform.flowcache.IFlowReconcileService; import org.sdnplatform.flowcache.OFMatchReconcile; import org.sdnplatform.flowcache.PendingSwRespKey; import org.sdnplatform.flowcache.PendingSwitchResp; import org.sdnplatform.flowcache.IFlowCacheService.FCQueryEvType; import org.sdnplatform.forwarding.IRewriteService; import org.sdnplatform.netvirt.core.VNS; import org.sdnplatform.netvirt.core.VNSInterface; import org.sdnplatform.netvirt.core.VNS.ARPMode; import org.sdnplatform.netvirt.core.VNS.BroadcastMode; import org.sdnplatform.netvirt.core.VNS.DHCPMode; import org.sdnplatform.netvirt.manager.IVNSInterfaceClassifier; import org.sdnplatform.netvirt.manager.INetVirtListener; import org.sdnplatform.netvirt.manager.INetVirtManagerService; import org.sdnplatform.netvirt.virtualrouting.IVirtualRoutingService; import org.sdnplatform.netvirt.web.NetVirtWebRoutable; import org.sdnplatform.packet.Ethernet; import org.sdnplatform.packet.IPv4; import org.sdnplatform.restserver.IRestApiService; import org.sdnplatform.storage.IResultSet; import org.sdnplatform.storage.IStorageSourceListener; import org.sdnplatform.storage.IStorageSourceService; import org.sdnplatform.storage.StorageException; import org.sdnplatform.tagmanager.ITagListener; import org.sdnplatform.tagmanager.ITagManagerService; import org.sdnplatform.tagmanager.Tag; import org.sdnplatform.threadpool.IThreadPoolService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The NetVirt manager component is responsible for maintaining a mapping * between devices on the network and the NetVirts and NetVirt interfaces to * which they belong. NetVirt manager does not make any forwarding decision: * its responsibility is limited to NetVirt ID updates. * @author readams */ @LogMessageCategory("Network Virtualization") public class NetVirtManagerImpl implements IModule, IOFMessageListener, INetVirtManagerService, IStorageSourceListener, ITagListener, IFlowReconcileListener, IFlowQueryHandler, IOFSwitchListener, IInfoProvider, IHAListener { protected static Logger logger = LoggerFactory.getLogger(NetVirtManagerImpl.class); // ******************************* // Constants for accessing storage // ******************************* // Table names public static final String VNS_TABLE_NAME = "controller_vns"; public static final String VNS_INTERFACE_RULE_TABLE_NAME = "controller_vnsinterfacerule"; public static final String SWITCH_INTERFACE_CONFIG_TABLE_NAME = "controller_switchinterfaceconfig"; // Column names public static final String ID_COLUMN_NAME = "id"; public static final String ACTIVE_COLUMN_NAME = "active"; public static final String PRIORITY_COLUMN_NAME = "priority"; public static final String ORIGIN_COLUMN_NAME = "origin"; public static final String ADDRESS_SPACE_COLUMN_NAME = "vns_address_space_id"; public static final String DHCP_CONFIG_MODE_COLUMN_NAME = "dhcp_mode"; public static final String DHCP_IP_COLUMN_NAME = "dhcp_ip"; public static final String ARP_CONFIG_MODE_COLUMN_NAME = "arp_mode"; public static final String BROADCAST_CONFIG_MODE_COLUMN_NAME = "broadcast"; public static final String VNS_COLUMN_NAME = "vns_id"; public static final String DESCRIPTION_COLUMN_NAME = "description"; public static final String MULTIPLE_ALLOWED_COLUMN_NAME = "allow_multiple"; public static final String VLAN_TAG_ON_EGRESS_COLUMN_NAME = "vlan_tag_on_egress"; public static final String MAC_COLUMN_NAME = "mac"; public static final String IP_SUBNET_COLUMN_NAME = "ip_subnet"; public static final String SWITCH_COLUMN_NAME = "switch"; public static final String PORTS_COLUMN_NAME = "ports"; public static final String VLANS_COLUMN_NAME = "vlans"; public static final String TAGS_COLUMN_NAME = "tags"; public static final String SWITCH_BROADCAST_IFACE_COLUMN_NAME = "broadcast"; public static final String SWITCH_IFACE_NAME = "if_name"; public static final String SWITCH_DPID = "switch_id"; // Time constants protected static final int INTERFACE_AGE_TIME = 1000 * 60 * 60; // 1 hour protected static final int INTERFACE_CLEANUP_INTERVAL = 12; // hours protected static final int LAST_SEEN_UPDATE_INTERVAL = 1000 * 60 * 5; // 5 minutes protected static final int UPDATE_TASK_BATCH_DELAY_MS = 750; // ******************** // Service Dependencies // ******************** protected IControllerService controllerProvider; protected IDeviceService deviceManager; protected IStorageSourceService storageSource; protected ITagManagerService tagManager; protected IVirtualRoutingService virtualRouting; protected IRestApiService restApi; protected IFlowCacheService betterFlowCacheMgr; protected IFlowReconcileService flowReconcileMgr; protected IThreadPoolService threadPool; protected IRewriteService rewriteService; protected DeviceListenerImpl deviceListener; // ***************** // NetVirt configuration // ***************** /** * This is the switch dpid, iface name tuple of all configured * broadcast interfaces */ public class SwitchInterface { String dpid; String ifaceName; public String getDpid() { return dpid; } public String getIfaceName() { return ifaceName; } } /** * This class represents the configuration of NetVirt manager that is * created / updated / read from storage (NetVirt table and NetVirt interface * table). We use "double buffering" to increase performance. We have one * instance of ConfigState that contains the currently running config * and one that we use to build the configuration while reading from * storage. * @author gregor * */ public static class ConfigState { public ConfigState() { interfaceRuleMap = new HashMap<String, MembershipRule<VNS>>(); vnsMap = new ConcurrentHashMap<String, VNS>(); switchInterfaceRuleMap = new ConcurrentHashMap<Long, List<MembershipRule<VNS>>>(); interfaceMap = new ConcurrentHashMap<String, VNSInterface>(); deviceGroupMatchers = new HashMap<String, IDeviceGroupMatcher<VNS>>(); deviceInterfaceMap = new ConcurrentHashMap<Long, List<VNSInterface>>(); } public void clear() { interfaceRuleMap.clear(); vnsMap.clear(); switchInterfaceRuleMap.clear(); interfaceMap.clear(); deviceGroupMatchers.clear(); deviceInterfaceMap.clear(); } /** * This is the cache for mapping a device to its list of interfaces */ protected Map<Long, List<VNSInterface>> deviceInterfaceMap; /** * This is the set of VNS that exist in the system, mapping * ID to VNS object */ public ConcurrentHashMap<String, VNS> vnsMap; /** * This is the set of VNS interface rules that exist in the * system, mapping ID to VNS interface rule object */ public Map<String,MembershipRule<VNS>> interfaceRuleMap; /** * This is the set of VNS interface rules that exist in the * system, mapping a switch DPID to a VNS interface rule object */ public Map<Long, List<MembershipRule<VNS>>> switchInterfaceRuleMap; /** * This is the set of VNS interfaces that exist in the * system, mapping ID to VNS interface object */ public Map<String,VNSInterface> interfaceMap; /** * Matches devices to interfaces using the interface rules */ public HashMap<String, IDeviceGroupMatcher<VNS>> deviceGroupMatchers; } /** * The currently active config */ protected ConfigState curConfigState; /** * The config currently being read / created from storage */ protected ConfigState newConfigState; /** * This is the set of switch port tuples to which all broadcast * messages will be sent. */ protected List<SwitchInterface> confBroadcastIfaces; /** * This is the set of switch port tuples to which all broadcast * messages will be sent. */ protected List<SwitchPort> broadcastSwitchPorts; protected static final String CLASSIFIER = "Classifier"; private ListenerDispatcher<String,IVNSInterfaceClassifier> vnsInterfaceClassifiers; private IDeviceGroupMatcher<VNS> getDeviceGroupMatcher(IDevice device) { if (device.getEntityClass() == null) return null; String name = device.getEntityClass().getName(); return curConfigState.deviceGroupMatchers.get(name); } /** The number of times flow query resp handler method was called. */ protected int flowQueryRespHandlerCallCount; /** The last fc query resp. */ protected FlowCacheQueryResp lastFCQueryResp; /** * Data structure for pending switch query responses */ protected ConcurrentHashMap<PendingSwRespKey, PendingSwitchResp>pendSwRespMap; public FlowCacheQueryResp getLastFCQueryResp() { return lastFCQueryResp; } public int getFlowQueryRespHandlerCallCount() { return flowQueryRespHandlerCallCount; } public List<SwitchInterface> getConfBroadcastIfaces() { return confBroadcastIfaces; } // ***************** // NetVirt manager state // ***************** /** * Lock for reading from storage into newConfigState */ protected Object newConfigLock; /** * Lock on NetVirt configuration state. * readLock needs to be held while accessing curConfigState * writeLock needs to be held while switching curConfigState and * newConfigState * */ protected ReentrantReadWriteLock configLock; /** * Asynchronous task for responding to NetVirt configuration changes * notifications. */ SingletonTask configUpdateTask; // List of INetVirtListeners protected List<INetVirtListener> netVirtListeners; /* A config property. If this is set to true, we will enable special * treatment for VNS with a non-empty, non-null origin field. If such a * VNS is added/modified/deleted we will /only/ reconcile flows from * this NetVirt and not from all other NetVirt with the same or lower priority. * The assumption / contract is that such NetVirt have non-overlapping * membership rules */ private boolean nonNullOriginSimpleReconcile; public NetVirtManagerImpl() { } /* package private. Only to be used by test code */ void setNonNullOriginSimpleReconcile(boolean nonNullOriginSimpleReconcile) { this.nonNullOriginSimpleReconcile = nonNullOriginSimpleReconcile; } // ****************** // IPlatformService // ****************** @Override public Collection<Class<? extends IPlatformService>> getModuleServices() { Collection<Class<? extends IPlatformService>> l = new ArrayList<Class<? extends IPlatformService>>(); l.add(INetVirtManagerService.class); return l; } @Override public Map<Class<? extends IPlatformService>, IPlatformService> getServiceImpls() { Map<Class<? extends IPlatformService>, IPlatformService> m = new HashMap<Class<? extends IPlatformService>, IPlatformService>(); // We are the class that implements the service m.put(INetVirtManagerService.class, this); return m; } @Override public Collection<Class<? extends IPlatformService>> getModuleDependencies() { Collection<Class<? extends IPlatformService>> l = new ArrayList<Class<? extends IPlatformService>>(); l.add(IControllerService.class); l.add(IDeviceService.class); l.add(IStorageSourceService.class); l.add(ITagManagerService.class); l.add(IRestApiService.class); l.add(IVirtualRoutingService.class); l.add(IFlowCacheService.class); l.add(IFlowReconcileService.class); l.add(IThreadPoolService.class); l.add(IRewriteService.class); return l; } @Override public void init(ModuleContext context) throws ModuleException { Map<String, String> configOptions = context.getConfigParams(this); String option = configOptions.get("nonnulloriginsimplereconcile"); if (option != null && option.equalsIgnoreCase("false")) { nonNullOriginSimpleReconcile = false; logger.info("Disabled special treatmeant of non-null origin for " + "flow reconcile"); } else { nonNullOriginSimpleReconcile = true; logger.info("Enabled special treatmeant of non-null origin for " + "flow reconcile"); } controllerProvider = context.getServiceImpl(IControllerService.class); deviceManager = context.getServiceImpl(IDeviceService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); tagManager = context.getServiceImpl(ITagManagerService.class); restApi = context.getServiceImpl(IRestApiService.class); virtualRouting = context.getServiceImpl(IVirtualRoutingService.class); betterFlowCacheMgr = context.getServiceImpl(IFlowCacheService.class); flowReconcileMgr = context.getServiceImpl(IFlowReconcileService.class); threadPool = context.getServiceImpl(IThreadPoolService.class); rewriteService = context.getServiceImpl(IRewriteService.class); // Init internal data structures flowQueryRespHandlerCallCount = 0; configLock = new ReentrantReadWriteLock(); newConfigLock = new Object(); curConfigState = new ConfigState(); broadcastSwitchPorts = new ArrayList<SwitchPort>(); confBroadcastIfaces = new ArrayList<SwitchInterface>(); configLock = new ReentrantReadWriteLock(); vnsInterfaceClassifiers = new ListenerDispatcher<String, IVNSInterfaceClassifier>(); netVirtListeners = new ArrayList<INetVirtListener>(); deviceListener = new DeviceListenerImpl(); } @Override public void startUp(ModuleContext context) { restApi.addRestletRoutable(new NetVirtWebRoutable()); ScheduledExecutorService ses = threadPool.getScheduledExecutor(); configUpdateTask = new SingletonTask(ses, new Runnable() { @Override public void run() { readVNSConfigFromStorage(); } }); storageSource.createTable(VNS_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(VNS_TABLE_NAME, ID_COLUMN_NAME); storageSource.createTable(VNS_INTERFACE_RULE_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(VNS_INTERFACE_RULE_TABLE_NAME, ID_COLUMN_NAME); storageSource.createTable(SWITCH_INTERFACE_CONFIG_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(SWITCH_INTERFACE_CONFIG_TABLE_NAME, ID_COLUMN_NAME); storageSource.addListener(VNS_TABLE_NAME, this); storageSource.addListener(VNS_INTERFACE_RULE_TABLE_NAME, this); storageSource.addListener(SWITCH_INTERFACE_CONFIG_TABLE_NAME, this); controllerProvider.addOFMessageListener(OFType.PACKET_IN, this); controllerProvider.addOFMessageListener(OFType.STATS_REPLY, this); controllerProvider.addOFSwitchListener(this); controllerProvider.addHAListener(this); controllerProvider.addInfoProvider("summary", this); flowReconcileMgr.addFlowReconcileListener(this); tagManager.addListener(this); deviceManager.addListener(this.deviceListener); readVNSConfigFromStorage(); readSwitchInterfaceConfig(); } @Override public void addNetVirtListener(INetVirtListener listener) { if (listener != null) netVirtListeners.add(listener); } @Override public void removeNetVirtListener(INetVirtListener listener) { if (listener != null) netVirtListeners.remove(listener); } protected List<INetVirtListener> getNetVirtListener() { return Collections.unmodifiableList(netVirtListeners); } @Override public void addVNSInterfaceClassifier(IVNSInterfaceClassifier classifier) { vnsInterfaceClassifiers.addListener(CLASSIFIER, classifier); } // ****************** // IOFMessageListener // ****************** @Override public IOFMessageListener.Command receive(IOFSwitch sw, OFMessage msg, ListenerContext cntx) { switch (msg.getType()) { case PACKET_IN: return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx); default: break; } return Command.CONTINUE; } @Override public String getName() { return "netVirtmanager"; } @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return name.equals("devicemanager"); } @Override public boolean isCallbackOrderingPostreq(OFType type, String name) { return false; } // *********** // INetVirtManager // *********** @Override public Iterator<VNS> getAllVNS() { configLock.readLock().lock(); try { return curConfigState.vnsMap.values().iterator(); } finally { configLock.readLock().unlock(); } } @Override public VNS getVNS(String name) { configLock.readLock().lock(); try { return curConfigState.vnsMap.get(name); } finally { configLock.readLock().unlock(); } } @Override @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="Failed to assign a VNS to device {device} {exception}", explanation="Could not assign a VNS to the device.", recommendation=LogMessageDoc.GENERIC_ACTION) }) public List<VNSInterface> getInterfaces(IDevice d) { List<VNSInterface> ifaces = null; boolean cachemiss = false; configLock.readLock().lock(); try { // Always check with registered VNSInterfaceClassifiers and union the results. ifaces = classifyUnknownDevice(d); if (logger.isTraceEnabled()) { if (ifaces != null) { for (VNSInterface iface : ifaces) { logger.trace("Registered classifier results: {} matches interface {}", d.getMACAddressString(), iface.toString()); } } else { logger.trace("Registered classifier results: {} matches null interface", d.getMACAddressString()); } } // Check cached interfaces for the device List<VNSInterface> tmpIfaces = curConfigState .deviceInterfaceMap.get(d.getDeviceKey()); cachemiss = (tmpIfaces == null); if (cachemiss) { try { tmpIfaces = matchDevice(d); } catch (Exception e) { logger.error("Failed to assign a VNS to device {}: {}", d, e); } } // Merge the two sets of vnsInterfaces together. if (ifaces != null) { if (tmpIfaces != null && tmpIfaces.size() > 0) { ifaces.addAll(tmpIfaces); } } else { ifaces = tmpIfaces; } if (ifaces == null || ifaces.size() == 0) { // If there's no assignment, assign to the default interface on // the default VNS VNSInterface defIface = getIfaceFromName( new String[] {"default", d.getMACAddressString()}, null, d, null); tmpIfaces = new ArrayList<VNSInterface>(1); tmpIfaces.add(defIface); ifaces = tmpIfaces; } if (cachemiss && tmpIfaces != null) { // Cache result curConfigState.deviceInterfaceMap.put(d.getDeviceKey(), tmpIfaces); for (VNSInterface b : tmpIfaces) { b.getParentVNS().addDevice(d); } if (logger.isDebugEnabled()) { StringBuffer sb = new StringBuffer(); sb.append("VNS interface(s) assigned: "); sb.append(d.getEntityClass().getName()); sb.append("::"); sb.append(d.getMACAddressString() + " ["); for (VNSInterface b : tmpIfaces) { sb.append(b.getParentVNS().getName() + ":"); sb.append(b.getName() + ","); } sb.append("]"); logger.debug(sb.toString()); } } // Update timestamps on ifaces Date currentTime = new Date(); for (VNSInterface i : ifaces) { i.setLastSeen(currentTime); } } finally { configLock.readLock().unlock(); } if (logger.isTraceEnabled()) { logger.trace("All getInterface results for {} :", d.getMACAddressString()); for (VNSInterface iface : ifaces) { logger.trace("{} matches interface {}", d.getMACAddressString(), iface.toString()); } } return ifaces; } protected List<VNSInterface> classifyUnknownDevice(IDevice d) { if (d == null || d.getEntityClass() == null) return null; List<IVNSInterfaceClassifier> listeners = vnsInterfaceClassifiers.getOrderedListeners(); List<VNSInterface> vnsInterfaces = null; if (listeners != null) { vnsInterfaces = new ArrayList<VNSInterface>(); for (IVNSInterfaceClassifier listener : listeners) { List<VNSInterface> interfaces = listener.classifyDevice(d); if (interfaces != null && interfaces.size() > 0) { vnsInterfaces.addAll(interfaces); } } } if (vnsInterfaces == null || vnsInterfaces.size() == 0) { return null; } else { return vnsInterfaces; } } protected List<VNSInterface> classifyUnknownDevice(String addressSpace, Long deviceMac, Short deviceVlan, Integer deviceIpv4, SwitchPort switchPort) { List<IVNSInterfaceClassifier> listeners = vnsInterfaceClassifiers.getOrderedListeners(); List<VNSInterface> vnsInterfaces = null; if (listeners != null) { vnsInterfaces = new ArrayList<VNSInterface>(); for (IVNSInterfaceClassifier listener : listeners) { List<VNSInterface> interfaces = listener.classifyDevice(addressSpace, deviceMac, deviceVlan, deviceIpv4, switchPort); if (interfaces != null && interfaces.size() > 0) { vnsInterfaces.addAll(interfaces); } } } if (vnsInterfaces == null || vnsInterfaces.size() == 0) { return null; } else { return vnsInterfaces; } } @Override public VNSInterface getInterface(String name) { configLock.readLock().lock(); try { return curConfigState.interfaceMap.get(name); } finally { configLock.readLock().unlock(); } } @Override public Iterator<VNSInterface> getAllInterfaces() { configLock.readLock().lock(); try { return curConfigState.interfaceMap.values().iterator(); } finally { configLock.readLock().unlock(); } } @Override public List<SwitchPort> getBroadcastSwitchPorts() { return broadcastSwitchPorts; } // ******************* // IDeviceListener // ******************* class DeviceListenerImpl implements IDeviceListener { @Override public void deviceAdded(IDevice device) { // Don't care return; } @Override public void deviceRemoved(IDevice device) { clearCachedDeviceState(device.getDeviceKey()); } @Override public void deviceMoved(IDevice device) { // Remove cached device state if we have attachment point rules IDeviceGroupMatcher<VNS> deviceGroupMatcher = getDeviceGroupMatcher(device); if (deviceGroupMatcher == null) return; if (deviceGroupMatcher.hasSwitchPortRules() || deviceGroupMatcher.hasTagRules()) { clearCachedDeviceState(device.getDeviceKey()); } } @Override public void deviceIPV4AddrChanged(IDevice device) { // Remove cached device state if we have ip address rules IDeviceGroupMatcher<VNS> deviceGroupMatcher = getDeviceGroupMatcher(device); if (deviceGroupMatcher == null) return; if (deviceGroupMatcher.hasIpSubnetRules()) { clearCachedDeviceState(device.getDeviceKey()); } } @Override public void deviceVlanChanged(IDevice device) { clearCachedDeviceState(device.getDeviceKey()); } @Override public String getName() { return NetVirtManagerImpl.this.getName(); } @Override public boolean isCallbackOrderingPrereq(String type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(String type, String name) { return false; } } // *************** // IFlowReconcileListener // *************** @Override public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) { for (OFMatchReconcile ofm : ofmRcList) { IFlowCacheService.fcStore.put(ofm.cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME, "netVirt"); if (logger.isTraceEnabled()) { logger.trace("Reconciling flow: match={}", ofm.ofmWithSwDpid.getOfMatch()); } annotateDeviceVNSInterfaces (ofm.cntx, ofm.ofmWithSwDpid.getOfMatch()); } return Command.CONTINUE; } // ***************** // IFlowQueryHandler // ***************** @Override public void flowQueryRespHandler(FlowCacheQueryResp flowResp) { flowQueryRespHandlerCallCount++; lastFCQueryResp = flowResp; if (logger.isTraceEnabled()) { logger.trace("Executing flowQueryRespHandler {} flowCnt={}", flowResp.toString(), lastFCQueryResp.qrFlowCacheObjList.size()); } flowReconcileMgr.flowQueryGenericHandler(flowResp); } // ********************** // IStorageSourceListener // ********************** @Override public void rowsModified(String tableName, Set<Object> rowKeys) { if (tableName.equals(SWITCH_INTERFACE_CONFIG_TABLE_NAME)) readSwitchInterfaceConfig(); else queueConfigUpdate(); } @Override public void rowsDeleted(String tableName, Set<Object> rowKeys) { if (tableName.equals(SWITCH_INTERFACE_CONFIG_TABLE_NAME)) readSwitchInterfaceConfig(); else queueConfigUpdate(); } // ********************** // ITagListener // ********************** @Override public void tagAdded(Tag tag) { // Noop } @Override public void tagDeleted(Tag tag) { // Noop } /* (non-Javadoc) * @see org.sdnplatform.tagmanager.ITagListener# * tagDevicesReMapped(java.util.Iterator) */ @Override public void tagDevicesReMapped(Iterator<? extends IDevice> devices) { if (logger.isTraceEnabled()) logger.trace("Devices got remapped to tags"); while (devices.hasNext()) { IDevice d = devices.next(); IDeviceGroupMatcher<VNS> deviceGroupMatcher = getDeviceGroupMatcher(d); if (deviceGroupMatcher == null) continue; if (!deviceGroupMatcher.hasTagRules()) { continue; } if (logger.isTraceEnabled()) { logger.trace("This device got remapped - {}", d.getMACAddressString()); } clearCachedDeviceState(d.getDeviceKey()); flowReconcileMgr.updateFlowForDestinationDevice(d, this, FCQueryEvType.DEVICE_PROPERTY_CHANGED); flowReconcileMgr.updateFlowForSourceDevice(d, this, FCQueryEvType.DEVICE_PROPERTY_CHANGED); } } // ************* // IInfoProvider // ************* @Override public Map<String, Object> getInfo(String type) { if (!"summary".equals(type)) return null; Map<String, Object> info = new HashMap<String, Object>(); configLock.readLock().lock(); try { info.put("# VNSes", curConfigState.vnsMap.size()); info.put("# VNS Interfaces", curConfigState.interfaceMap.size()); } finally { configLock.readLock().unlock(); } return info; } // *************** // Private methods // *************** /** * Queue a task to update the configuration state */ protected void queueConfigUpdate() { configUpdateTask.reschedule(UPDATE_TASK_BATCH_DELAY_MS, TimeUnit.MILLISECONDS); } @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="VNS {vns}: Invalid DHCP server IP {ip}, " + "reverting to {mode}", explanation="An invalid IP DHCP server IP is present in the " + "configuration for this VNS", recommendation="Correct the invalid DHCP server IP"), @LogMessageDoc(level="INFO", message="VNS {vns}: DHCP Manager mode {mode}, server {ip}", explanation="This displays the DHCP Manager mode " + "configuration for this VNS") }) private void setDhcpConfig(VNS b, String mode, String ip) { int dhcpIp = 0; try { if (ip != null) dhcpIp = IPv4.toIPv4Address(ip); } catch (IllegalArgumentException e) { mode = "flood-if-unknown"; dhcpIp = 0; ip = null; logger.error("VNS {}: Invalid DHCP server IP {}, reverting to {}", ip, mode); } b.setDhcpIp(dhcpIp); b.setDhcpManagerMode(DHCPMode.fromString(mode)); logger.info("VNS {}: DHCP Manager mode {}, server {}", new Object[] {b.getName(), b.getDhcpManagerMode(), ip == null ? "not specified" : ip}); } @LogMessageDoc(level="INFO", message="VNS {vns}: ARP Manager setting is: {mode}", explanation="This displays the ARP Manager mode " + "configuration for this VNS") private void setArpConfig(VNS b, String setting) { b.setArpManagerMode(ARPMode.fromString(setting)); logger.info("VNS {}: ARP Manager setting is: {}", b.getName(), b.getArpManagerMode()); } @LogMessageDoc(level="INFO", message="VNS {vns}: Broadcast setting is: {mode}", explanation="This displays the general broadcast packet " + "mode for this VNS.") private void setBroadcastConfig(VNS b, String setting) { b.setBroadcastMode(BroadcastMode.fromString(setting)); logger.info("VNS {}: Broadcast setting is: {}", b.getName(), b.getBroadcastMode()); } /** * Read the broadcast interfaces from switch config. * @throws StorageException */ protected void readSwitchInterfaceConfig() throws StorageException { IResultSet swIfaceConfigResultSet; // try multiple times as work-around for BSC-2928; // see Jira BSC-2928 comment 09/Dec/12 1:01 PM int trial = 0; while (true) { trial++; try { swIfaceConfigResultSet = storageSource.executeQuery(SWITCH_INTERFACE_CONFIG_TABLE_NAME, new String[]{SWITCH_DPID, SWITCH_IFACE_NAME, SWITCH_BROADCAST_IFACE_COLUMN_NAME}, null, null); break; } catch (StorageException e) { if (trial > 15) { throw e; } logger.warn("retry " + trial + ": readSwitchInterfaceConfig encountered StorageExcption " + e.getMessage()); try { Thread.sleep((trial < 6) ? 1000 : 2000); } catch (InterruptedException e2) { logger.warn("sleep interrupted"); } } } List<SwitchInterface> lsi = new ArrayList<SwitchInterface>(); while (swIfaceConfigResultSet.next()) { boolean broadcast = swIfaceConfigResultSet.getBoolean(SWITCH_BROADCAST_IFACE_COLUMN_NAME); if (broadcast) { SwitchInterface si = new SwitchInterface(); si.dpid = swIfaceConfigResultSet.getString(SWITCH_DPID); si.ifaceName = swIfaceConfigResultSet.getString(SWITCH_IFACE_NAME); lsi.add(si); } } confBroadcastIfaces = lsi; updateBroadcastSwitchPorts(); } /** * Updates the broadcast switch ports whenever a switch joins, leaves, or the * config for broadcast interfaces is changed. */ protected void updateBroadcastSwitchPorts() { List<SwitchPort> lspt = new ArrayList<SwitchPort>(); for(SwitchInterface si : confBroadcastIfaces) { Long dpid = HexString.toLong(si.dpid); IOFSwitch sw = controllerProvider.getSwitches().get(dpid); if (sw != null) { OFPhysicalPort p = sw.getPort(si.getIfaceName()); if (p!=null && sw.portEnabled(p)) lspt.add(new SwitchPort(dpid, p.getPortNumber())); } } broadcastSwitchPorts = lspt; } private void triggerFlowReconciliation (String vnsName) { /* NetVirt Manager triggers the flow reconciliation by submitting * flow query to the flow cache. The flows are actually reconciled * in virtual routing. */ FCQueryObj fcQueryObj = new FCQueryObj(this, vnsName, null, // null vlan null, // null srcDevice null, // null destDevice getName(), FCQueryEvType.APP_CONFIG_CHANGED, null); betterFlowCacheMgr.submitFlowCacheQuery(fcQueryObj); } /* * netVirtProcessAddressSpaceConfig * * Process address-space configuration in netVirt. */ private void netVirtProcessAddressSpaceConfig (VNS oldVNS, VNS vns, IResultSet netVirtResultSet, Set<VNS> vnsModifiedSet) { String oldAddressSpace; String newAddressSpace; if (oldVNS == null) { oldAddressSpace = "default"; } else { oldAddressSpace = oldVNS.getAddressSpaceName(); if (oldAddressSpace == null || oldAddressSpace.isEmpty()) { oldAddressSpace = "default"; } } newAddressSpace = netVirtResultSet.getString(ADDRESS_SPACE_COLUMN_NAME); if (newAddressSpace == null || newAddressSpace.isEmpty()) { newAddressSpace = "default"; } vns.setAddressSpaceName(newAddressSpace); if (!oldAddressSpace.equals(newAddressSpace)) { // we need to add the old and new VNS to the change set // so we can correctly determine which address-spaces are affected vnsModifiedSet.add(vns); if (oldVNS != null) vnsModifiedSet.add(oldVNS); } if (!newConfigState.deviceGroupMatchers.containsKey(newAddressSpace)) { newConfigState.deviceGroupMatchers.put(newAddressSpace, new DeviceGroupMatcher<VNS>(tagManager, controllerProvider)); } return; } /** * Read the VNS configuration information from storage, merging with the * existing configuration. */ @LogMessageDocs({ @LogMessageDoc(level="ERROR", message="Error loading VNS {vns} from storage, entry ignored. ", explanation="Could not read the entry for this NetVirt from " + "the system database", recommendation="This can happen transiently under certain " + "circumstances without causing problems. If the " + "problem persists, it may indicate" + " corruption in the system database."), @LogMessageDoc(level="ERROR", message="Error loading VNS {vns} rule {rule} from " + "storage, entry ignored.", explanation="Could not read the entry for this VNS rule from " + "the system database", recommendation="This can happen transiently under certain " + "circumstances without causing problems. If the " + "problem persists, it may indicate" + " corruption in the system database.") }) protected void readVNSConfigFromStorage() throws StorageException { IResultSet vnsResultSet; // try multiple times as work-around for BSC-2928 int trial = 0; while (true) { trial++; try { vnsResultSet = storageSource.executeQuery(VNS_TABLE_NAME, null, null, null); break; } catch (StorageException e) { if (trial > 15) { throw e; } logger.warn("retry " + trial + ": readNetVirtConfigFromStorage encountered StorageExcption " + e.getMessage()); try { Thread.sleep((trial < 6) ? 1000 : 2000); } catch (InterruptedException e2) { logger.warn("sleep interrupted"); } } } IResultSet vnsIRuleResultSet = storageSource.executeQuery(VNS_INTERFACE_RULE_TABLE_NAME, new String[]{ID_COLUMN_NAME, VNS_COLUMN_NAME, DESCRIPTION_COLUMN_NAME, VLAN_TAG_ON_EGRESS_COLUMN_NAME, MULTIPLE_ALLOWED_COLUMN_NAME, ACTIVE_COLUMN_NAME, PRIORITY_COLUMN_NAME, MAC_COLUMN_NAME, IP_SUBNET_COLUMN_NAME, SWITCH_COLUMN_NAME, PORTS_COLUMN_NAME, VLANS_COLUMN_NAME, TAGS_COLUMN_NAME}, null, null); // We will maintain a set of NetVirtes that were deleted and a set new NetVirtes // that were created so that we can reconcile flows in a subset of NetVirtes // We add NetVirt to this set if they have been modified in a way // that would affect flow reconciliation. We will often add the // old and new NetVirt to this set so we can identify the all // relevant address-spaces and the highest priority NetVirt that // changed. // For this reason this must be a list and not a set // because old and new NetVirt will be considered equal Set<VNS> deletedSet = Collections.newSetFromMap(new IdentityHashMap<VNS, Boolean>()); Set<VNS> modifiedSet = Collections.newSetFromMap(new IdentityHashMap<VNS, Boolean>()); synchronized(newConfigLock) { newConfigState = new ConfigState(); while (vnsResultSet.next()) { String id = vnsResultSet.getString(ID_COLUMN_NAME); VNS oldvns = curConfigState.vnsMap.get(id); VNS vns = new VNS(id); if (oldvns == null) { // New VNS was created modifiedSet.add(vns); } try { // FIXME: We don't track changes to the ORIGIN column vns.setOrigin(vnsResultSet.getString(ORIGIN_COLUMN_NAME)); vns.setActive(vnsResultSet.getBoolean(ACTIVE_COLUMN_NAME)); if (oldvns != null && oldvns.isActive() != vns.isActive()) { if (oldvns.isActive()) modifiedSet.add(oldvns); else modifiedSet.add(vns); } vns.setPriority(vnsResultSet.getInt(PRIORITY_COLUMN_NAME)); if (oldvns != null && oldvns.getPriority() != vns.getPriority()) { // need to flag both VNS' as modified since we need // later identify the highest priority one to // reconcile modifiedSet.add(oldvns); modifiedSet.add(vns); } vns.setDescription( vnsResultSet.getString(DESCRIPTION_COLUMN_NAME)); netVirtProcessAddressSpaceConfig(oldvns, vns, vnsResultSet, modifiedSet); setArpConfig(vns, vnsResultSet.getString(ARP_CONFIG_MODE_COLUMN_NAME)); setDhcpConfig(vns, vnsResultSet.getString(DHCP_CONFIG_MODE_COLUMN_NAME), vnsResultSet.getString(DHCP_IP_COLUMN_NAME)); setBroadcastConfig(vns, vnsResultSet.getString(BROADCAST_CONFIG_MODE_COLUMN_NAME)); } catch (Exception e) { logger.warn("Error loading VNS " + id + " from storage, entry ignored. " + e); continue; } vns.setMarked(true); newConfigState.vnsMap.put(vns.getName(), vns); if (logger.isTraceEnabled()) { logger.trace("VNS {}: Configuration complete ", vns.getName()); } } // clear out state related to VNS that no longer exist for (VNS oldvns: curConfigState.vnsMap.values()) { if (! newConfigState.vnsMap.containsKey(oldvns.getName())) deletedSet.add(oldvns); } // Read interface rules from result set while (vnsIRuleResultSet.next()) { String id = vnsIRuleResultSet.getString(ID_COLUMN_NAME); String vnsid = vnsIRuleResultSet.getString(VNS_COLUMN_NAME); VNS vns = newConfigState.vnsMap.get(vnsid); MembershipRule<VNS> oldIRule = curConfigState.interfaceRuleMap.get(id); MembershipRule<VNS> irule; if (vns != null) { if (oldIRule == null) { modifiedSet.add(vns); } irule = new MembershipRule<VNS>(id, vns); try { /* For each of the possible VNS Interface rule match * options, we compare the match field with the old * value, and if the new value is differnet then we * add the netVirt to netVirtWithRuleChangeSet */ irule.setDescription(vnsIRuleResultSet.getString(DESCRIPTION_COLUMN_NAME)); irule.setActive(vnsIRuleResultSet.getBoolean(ACTIVE_COLUMN_NAME)); irule.setMultipleAllowed(vnsIRuleResultSet.getBoolean(MULTIPLE_ALLOWED_COLUMN_NAME)); irule.setVlanTagOnEgress(vnsIRuleResultSet.getBoolean(VLAN_TAG_ON_EGRESS_COLUMN_NAME)); irule.setPriority(vnsIRuleResultSet.getInt(PRIORITY_COLUMN_NAME)); irule.setMac(vnsIRuleResultSet.getString(MAC_COLUMN_NAME)); irule.setIpSubnet(vnsIRuleResultSet.getString(IP_SUBNET_COLUMN_NAME)); irule.setSwitchId(vnsIRuleResultSet.getString(SWITCH_COLUMN_NAME)); irule.setPorts(vnsIRuleResultSet.getString(PORTS_COLUMN_NAME)); irule.setVlans(vnsIRuleResultSet.getString(VLANS_COLUMN_NAME)); irule.setTags(vnsIRuleResultSet.getString(TAGS_COLUMN_NAME)); if (oldIRule != null && !oldIRule.matchingFieldsEquals(irule)) { // rules have changed modifiedSet.add(oldIRule.getParentDeviceGroup()); modifiedSet.add(irule.getParentDeviceGroup()); } } catch (Exception e) { logger.warn("Error loading VNS " + vnsid + " rule " + id + " from storage, entry ignored. " + e); continue; } irule.setMarked(true); newConfigState.interfaceRuleMap.put(irule.getName(), irule); createVNSInterfaces(irule); // Setup lookup data structures String addrSpaceName = vns.getAddressSpaceName(); IDeviceGroupMatcher<VNS> deviceGroupMatcher = newConfigState.deviceGroupMatchers.get(addrSpaceName); if (deviceGroupMatcher != null) { // deviceGroupMatcher should never be null deviceGroupMatcher.addRuleIfActive(irule); } if (logger.isTraceEnabled()) { logger.trace("Configured VNS Interface Rule {} ", irule); } } } // Flag NetVirt with deleted rules for (MembershipRule<VNS> oldIRule: curConfigState.interfaceRuleMap.values()) { if (! newConfigState.interfaceRuleMap .containsKey(oldIRule.getName())) { modifiedSet.add(oldIRule.getParentDeviceGroup()); } } // Get VNS that must be sent to flow reconciliation Set<String> vnsFlowQuerySet = getVNSFlowReconciliation(modifiedSet, deletedSet); // Swap config states configLock.writeLock().lock(); try { curConfigState = newConfigState; newConfigState = null; } finally { configLock.writeLock().unlock(); } for (String netVirtName : vnsFlowQuerySet) { triggerFlowReconciliation(netVirtName); } if (!vnsFlowQuerySet.isEmpty()) { // FIXME: temporary work-around to be able to reconcile // flow from VirtualRoutingService triggerFlowReconciliation( IVirtualRoutingService.VRS_FLOWCACHE_NAME); // Notify all netVirtListeners on changed netVirtes for (INetVirtListener listener : netVirtListeners) { listener.netVirtChanged(vnsFlowQuerySet); if (logger.isTraceEnabled()) { logger.trace("Notify listener {} of changed VNSes {}", listener.getClass().getName(), vnsFlowQuerySet); } } } } // end synchronized } protected Set<String> getVNSFlowReconciliation (Set<VNS> vnsModifiedSet, Set<VNS> vnsDeletedSet) { /* Reconcile flows for the VNStes that were deleted and check if any * flow would be moved to a new vns that was created with higher * priority. * <p> * * DELETED VNS * =========== * For the flows in VNSes that were deleted either those flows need to * be deleted or they need to be moved to some other NetVirt if the devices * were member of multiple vns. If the flow need to be moved to some * other NetVirt then the flow-mods in the switches need not be touched buy * the flow-cache should be updated so that these flows are moved to * other application instance name. Here we need to submit flow query * for all the VNSes that were deleted. * <p> * ADDED VNS * ========= * For the new vns that were created we need to check if any flow * from a lower priority NetVirt need to be migrated to a newly created * vns. To accomplish this we need to submit flow query for all the * existing vns that are in lower priority than the highest-priority * vns that was created. * * We will use the logic stated above to create a set of vns for which * flow queries need to be submitted for reconciliation. * * QUICK HACK * ========== * see nonNullOriginSimpleReconcile's comment * */ HashSet<String> vnsFlowQuerySet = new HashSet<String>(); if (vnsDeletedSet.isEmpty() && vnsModifiedSet.isEmpty()) return Collections.emptySet(); /* * TODO: we can do this a lot more efficently: * a) only query for NetVirt' that are in the affected address-spaces * (instead of quering all NetVirt' with a priority lower than max) * b) rely on the NetVirt ordering (which includes NetVirt name to break a * tie) instead of using just the priority. */ /* Find the NetVirt with highest priority in netVirtModifiedSet */ int maxPriority = -Integer.MIN_VALUE; for (VNS netVirtIdx : vnsModifiedSet) { vnsFlowQuerySet.add(netVirtIdx.getName()); if (nonNullOriginSimpleReconcile && netVirtIdx.getOrigin() != null && !netVirtIdx.getOrigin().isEmpty() ) { continue; } if (netVirtIdx.getPriority() > maxPriority) { maxPriority = netVirtIdx.getPriority(); } } /* Get all NetVirtes with priority less than or equal to * highestPriorityNetVirtAddedAndRuleChanged */ for (String vnsNameIdx : newConfigState.vnsMap.keySet() ) { VNS vnsCur = newConfigState.vnsMap.get(vnsNameIdx); if (vnsCur.getPriority() <= maxPriority) { vnsFlowQuerySet.add(vnsNameIdx); } } /* Now add all the NetVirtes that were deleted as we need to reconcile * flows in those NetVirtes */ for (VNS vnsIdx : vnsDeletedSet) { vnsFlowQuerySet.add(vnsIdx.getName()); } /* Now submit flow query to get the flows in each of these NetVirtes */ if (logger.isTraceEnabled()) { logger.trace("Set of NetVirtes to query for flow reconciliation: {}", vnsFlowQuerySet); } // We want to always add default NetVirt to reconcile set given that it // will match everything that was otherwise unmatched vnsFlowQuerySet.add("default|default"); return vnsFlowQuerySet; } /** * Pre-create NetVirt interface based on the rule */ private void createVNSInterfaces(MembershipRule<VNS> vnsIRule) { String ifname = vnsIRule.getFixedInterfaceName(); VNS vns = vnsIRule.getParentDeviceGroup(); VNSInterface iface = newConfigState.interfaceMap.get(vns.getName() + "|" + ifname); if (iface == null) { iface = new VNSInterface(ifname, vns, vnsIRule, null); newConfigState.interfaceMap.put(vns.getName() + "|" + ifname, iface); } /** * Update the switchInterfaceRuleMap to include vnsirule if * it has any switch-specific sub-interface to be created. */ if (vnsIRule.getSwitchId() == null) return; long switchDPID = HexString.toLong(vnsIRule.getSwitchId()); List<MembershipRule<VNS>> memList; memList = newConfigState.switchInterfaceRuleMap.get(switchDPID); if (memList == null) { memList = new ArrayList<MembershipRule<VNS>>(); newConfigState.switchInterfaceRuleMap.put(switchDPID, memList); } memList.add(vnsIRule); /** * If the switch with switchDPID is already connected, then * create the sub-interface rule immediately. */ IOFSwitch sw = controllerProvider.getSwitches().get(switchDPID); if (sw == null) return; createVNSSubInterfaces(newConfigState, sw, vnsIRule); } /** * When a switch connects to the controller, this method will create * NetVirt sub-interfaces for any switch/switch-port specific configurations. * This method is called from addedSwitch() and therefore must operate * on curConfigState * @param switchDPID */ private void createVNSSubInterfacesForSwitch(long switchDPID) { /* * We can perform this operation while only holding the read lock * since we won't be swapping configState pointers. * TODO: verify that this is indeed the case */ configLock.readLock().lock(); try { List<MembershipRule<VNS>> memList; memList = curConfigState.switchInterfaceRuleMap.get(switchDPID); if (memList == null) return; IOFSwitch sw = controllerProvider.getSwitches().get(switchDPID); if (sw == null) return; for(MembershipRule<VNS> irule: memList) createVNSSubInterfaces(curConfigState, sw, irule); } finally { configLock.readLock().unlock(); } } /** * Pre-create NetVirt sub-interfaces based on rule (if applicable) * Only switch-port rules are relevant in the current implementation */ private void createVNSSubInterfaces(ConfigState configState, IOFSwitch sw, MembershipRule<VNS> irule) { String ifname = irule.getFixedInterfaceName(); VNS vns = irule.getParentDeviceGroup(); VNSInterface iface = configState.interfaceMap.get(vns.getName() + "|" + ifname); if (iface == null) { logger.debug("Fixed interface not created for {}", irule); return; } List<String> subifnames = irule.getFixedSubInterfaceNames(sw); if (subifnames == null) return; for (String name : subifnames) { VNSInterface subiface = configState.interfaceMap.get(vns.getName() + "|" + name); if (subiface == null) { subiface = new VNSInterface(name, vns, irule, iface); configState.interfaceMap.put(vns.getName() + "|" + name, subiface); } } } /** * Annotate the message context for a packet in the NetVirt interfaces associated * with the source and destination addresses for the flow. * * @param sw the switch for the packet-in * @param pi the packet-in message * @param cntx * @return @ref */ private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, ListenerContext cntx) { IFlowCacheService.fcStore.put(cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME, "netVirt"); return annotateDeviceVNSInterfaces (cntx, null); } /** * Annotate NetVirt interfaces for src and dst devices * @param cntx * @return */ @LogMessageDoc(level="WARN", message="Source device {device}'s entity class is not " + " a BetterEntityClass", explanation="This message indicates a misconfiguration of the " + "packet processing pipeline.", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) private Command annotateDeviceVNSInterfaces (ListenerContext cntx, OFMatch match) { IDevice src = IDeviceService.fcStore. get(cntx, IDeviceService.CONTEXT_SRC_DEVICE); IDevice dst = IDeviceService.fcStore. get(cntx, IDeviceService.CONTEXT_DST_DEVICE); if (logger.isTraceEnabled()) { logger.trace("srcDevice: {}, dstDevice: {}", src, dst); } // Retrieve interfaces for source and destination List<VNSInterface> srcIfaces = null; IEntityClass srcEc = null; if (src != null) { srcEc = src.getEntityClass(); if (srcEc instanceof BetterEntityClass) { BetterEntityClass addrSpace = (BetterEntityClass)srcEc; if (addrSpace.getVlan() != null) { rewriteService.setTransportVlan(addrSpace.getVlan(), cntx); } } else { logger.warn("Source device {}'s entity class is not " + " a BetterEntityClass", src); } srcIfaces = getInterfaces(src); if (srcIfaces != null && srcIfaces.size() > 0) { if (logger.isTraceEnabled()) { logger.trace("srcIface: {}", srcIfaces.get(0)); } bcStore.put(cntx, INetVirtManagerService.CONTEXT_SRC_IFACES, srcIfaces); } } List<VNSInterface> dstIfaces = null; if (dst != null) { dstIfaces = getInterfaces(dst); } else { // shortcut out of having to do work before calling registered classifiers // if no classifiers have registered if (srcEc != null && vnsInterfaceClassifiers.getOrderedListeners() != null) { dstIfaces = tryClassifyingUnknownDevice(cntx, match, srcEc); } } if (dstIfaces != null && dstIfaces.size() > 0) { if (logger.isTraceEnabled()) { logger.trace("dstIface: {}", dstIfaces.get(0)); } bcStore.put(cntx, INetVirtManagerService.CONTEXT_DST_IFACES, dstIfaces); } return Command.CONTINUE; } /** * Even without a device, this method will try to get the relevant information * from the packet, in order to classify the unknown device's NetVirtInterfaces * using registered INetVirtIntefaceClassifiers (like SINetVirtInterfaceClassifier) */ private List<VNSInterface> tryClassifyingUnknownDevice(ListenerContext cntx, OFMatch match, IEntityClass srcEc) { Integer ipAddress = null; Short vlan = null; long dstMac = 0; Ethernet eth = IControllerService.bcStore. get(cntx,IControllerService.CONTEXT_PI_PAYLOAD); if (eth != null) { // If packetIn is an unicast IPV4 packet, get destination ipaddress. if (eth.getPayload() instanceof IPv4 && !eth.isBroadcast() && !eth.isMulticast()) { IPv4 ipv4 = (IPv4) eth.getPayload(); ipAddress = ipv4.getDestinationAddress(); } vlan = eth.getVlanID(); dstMac = eth.getDestinationMAC().toLong(); } else if (match != null) { ipAddress = match.getNetworkDestination(); vlan = match.getDataLayerVirtualLan(); dstMac = Ethernet.toLong(match.getDataLayerDestination()); } /** * There are two options on when to query registered NetVirtInterfaceClassifiers: * 1. If the device doesn't match any NetVirtInterface, * Check the registered NetVirtInterfaceClassifiers for device * classification. This is what is required for ServiceInsertion feature and, * hence, the implementation. * 2. Always check with registered NetVirtInterfaceClassifiers and union the results. * * Option 2 is more flexible, but no use case yet. */ return classifyUnknownDevice(srcEc.getName(), dstMac, vlan, ipAddress, null); } /** * Match a device against the interface rules and return the list of interfaces. * Must be called with a config read lock held. * * @param d * @return */ protected List<VNSInterface> matchDevice(IDevice d) throws Exception { IDeviceGroupMatcher<VNS> deviceGroupMatcher = getDeviceGroupMatcher(d); if (deviceGroupMatcher == null) return null; List<MembershipRule<VNS>> matches = deviceGroupMatcher.matchDevice(d); if (matches == null) return null; ArrayList<VNSInterface> deviceInterfaces = new ArrayList<VNSInterface>(); for (MembershipRule<VNS> netVirtIRule: matches) { deviceInterfaces.add(getIfaceFromMatchingRule(netVirtIRule, d)); } return deviceInterfaces; } /** * createDefaultNetVirt * Create default NetVirt under the entity's address-space. * for tenant default */ protected VNS createDefaultNetVirt (IEntityClass entityClass) { /* * Use 'default' by default. */ String netVirtName = "default|default"; String addressSpaceName = "default"; /* * If this device is in non-default address-space, name the netVirt * accordingly. */ if (entityClass != null && !entityClass.getName().isEmpty() && !entityClass.getName().equals("default")) { addressSpaceName = entityClass.getName(); netVirtName = "default|" + addressSpaceName + "-default"; } /* * Create a new NetVirt and set its address-space. */ VNS netVirt = new VNS(netVirtName); netVirt.setActive(true); netVirt.setAddressSpaceName(addressSpaceName); VNS oldNetVirt = curConfigState.vnsMap.putIfAbsent(netVirt.getName(), netVirt); if (oldNetVirt != null) return oldNetVirt; else return netVirt; } /** * Get an interface from an interface name, creating it if needed * @param iname the components of the interface name * @param netVirt the netVirt for the interface (may be null, in which case * use the default NetVirt) * @param rule the NetVirt rule for the interface (may be null) * @return */ @Override public VNSInterface getIfaceFromName(String[] iname, VNS netVirt, IDevice device, MembershipRule<VNS> rule) { String[] names = new String[iname.length]; StringBuffer sb = new StringBuffer(); for (int i = 0; i < iname.length; i++) { if (i > 0) sb.append("/"); sb.append(iname[i]); names[i] = sb.toString(); } // use default NetVirt, creating if needed, if NetVirt is null if (netVirt == null) { IEntityClass entityClass = null; if (device != null) { entityClass = device.getEntityClass(); } netVirt = createDefaultNetVirt(entityClass); } VNSInterface iface = curConfigState.interfaceMap.get(netVirt.getName() + "|" + names[names.length-1]); if (iface == null) { // Allocate new interface boolean created = false; for (String name : names) { VNSInterface last = iface; iface = curConfigState.interfaceMap.get(netVirt.getName() + "|" + name); if (iface == null) { iface = new VNSInterface(name, netVirt, rule, last); curConfigState.interfaceMap.put(netVirt.getName() + "|" + name, iface); if (created) { // catching missed pre-creation logger.debug( "Parent interface not pre-created {}", last); } created = true; } } } return iface; } /** * Get the NetVirt interface for a matching rule and its device * @param rule the rule * @param d the device * @return the interface */ protected VNSInterface getIfaceFromMatchingRule(MembershipRule<VNS> rule, IDevice d) { String[] iname = rule.getInterfaceNameForDevice(d, controllerProvider); return getIfaceFromName(iname, rule.getParentDeviceGroup(), d, rule); } @Override public void clearCachedDeviceState(long deviceKey) { if (logger.isDebugEnabled()) { logger.debug("Clearing cached NetVirt interface mapping for {}", deviceKey); } configLock.writeLock().lock(); try { List<VNSInterface> ifaces = curConfigState.deviceInterfaceMap.get(deviceKey); if (ifaces == null) return; for (VNSInterface iface : ifaces) { iface.getParentVNS().removeDevice(deviceKey); curConfigState.interfaceMap.remove(iface.getParentVNS().getName() + "|" + iface.getName()); } curConfigState.deviceInterfaceMap.remove(deviceKey); } finally { configLock.writeLock().unlock(); } } // *************** // IOFSwitchListener // *************** @Override public void addedSwitch(IOFSwitch sw) { configLock.writeLock().lock(); try { createVNSSubInterfacesForSwitch(sw.getId()); } finally { configLock.writeLock().unlock(); } updateBroadcastSwitchPorts(); } @Override public void removedSwitch(IOFSwitch sw) { updateBroadcastSwitchPorts(); } @Override public void switchPortChanged(Long switchId) { // no-op } @Override public void controllerNodeIPsChanged( Map<String, String> curControllerNodeIPs, Map<String, String> addedControllerNodeIPs, Map<String, String> removedControllerNodeIPs) { // ignore } // *************** // IHARoleListener // *************** @Override public void roleChanged(Role oldRole, Role newRole) { switch(newRole) { case MASTER: if (oldRole == Role.SLAVE) { reloadConfigState(); } break; case SLAVE: logger.debug("Clearing config state due to HA " + "role change to SLAVE"); clearConfigState(); clearCachedState(); break; default: break; } } /** * Clears all the internal config state. */ protected void clearConfigState() { configLock.writeLock().lock(); try { curConfigState.clear(); confBroadcastIfaces.clear(); broadcastSwitchPorts.clear(); } finally { configLock.writeLock().unlock(); } } protected void clearCachedState() { flowQueryRespHandlerCallCount = 0; lastFCQueryResp = null; } /** * Reloads the internal config state. */ protected void reloadConfigState() { readVNSConfigFromStorage(); readSwitchInterfaceConfig(); } }