X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fforwardingrulesmanager%2Fimplementation%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fforwardingrulesmanager%2Finternal%2FForwardingRulesManager.java;h=c2d1f46737683b4a0eb8a7339c7677ee7f0ca961;hp=19b045b217a3a5877a4d3e00cb58840e23f13f7e;hb=dfec2688d4213adc433187295d6667cea746d68c;hpb=36737d75a800d7646f140b3b58b1a47aa8d8c993 diff --git a/opendaylight/forwardingrulesmanager/implementation/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManager.java b/opendaylight/forwardingrulesmanager/implementation/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManager.java index 19b045b217..c2d1f46737 100644 --- a/opendaylight/forwardingrulesmanager/implementation/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManager.java +++ b/opendaylight/forwardingrulesmanager/implementation/src/main/java/org/opendaylight/controller/forwardingrulesmanager/internal/ForwardingRulesManager.java @@ -22,17 +22,24 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import org.eclipse.osgi.framework.console.CommandInterpreter; import org.eclipse.osgi.framework.console.CommandProvider; import org.opendaylight.controller.clustering.services.CacheConfigException; import org.opendaylight.controller.clustering.services.CacheExistException; +import org.opendaylight.controller.clustering.services.ICacheUpdateAware; import org.opendaylight.controller.clustering.services.IClusterContainerServices; import org.opendaylight.controller.clustering.services.IClusterServices; import org.opendaylight.controller.configuration.IConfigurationContainerAware; +import org.opendaylight.controller.sal.connection.ConnectionLocality; +import org.opendaylight.controller.connectionmanager.IConnectionManager; import org.opendaylight.controller.forwardingrulesmanager.FlowConfig; import org.opendaylight.controller.forwardingrulesmanager.FlowEntry; import org.opendaylight.controller.forwardingrulesmanager.FlowEntryInstall; @@ -42,12 +49,14 @@ import org.opendaylight.controller.forwardingrulesmanager.PortGroup; import org.opendaylight.controller.forwardingrulesmanager.PortGroupChangeListener; import org.opendaylight.controller.forwardingrulesmanager.PortGroupConfig; import org.opendaylight.controller.forwardingrulesmanager.PortGroupProvider; +import org.opendaylight.controller.forwardingrulesmanager.implementation.data.FlowEntryDistributionOrder; import org.opendaylight.controller.sal.action.Action; import org.opendaylight.controller.sal.action.ActionType; import org.opendaylight.controller.sal.action.Controller; import org.opendaylight.controller.sal.action.Flood; import org.opendaylight.controller.sal.action.Output; import org.opendaylight.controller.sal.action.PopVlan; +import org.opendaylight.controller.sal.core.Config; import org.opendaylight.controller.sal.core.ContainerFlow; import org.opendaylight.controller.sal.core.IContainer; import org.opendaylight.controller.sal.core.IContainerListener; @@ -85,13 +94,22 @@ import org.slf4j.LoggerFactory; * the network. It also maintains the central repository of all the forwarding * rules installed on the network nodes. */ -public class ForwardingRulesManager implements IForwardingRulesManager, PortGroupChangeListener, - IContainerListener, ISwitchManagerAware, IConfigurationContainerAware, IInventoryListener, IObjectReader, - CommandProvider, IFlowProgrammerListener { +public class ForwardingRulesManager implements + IForwardingRulesManager, + PortGroupChangeListener, + IContainerListener, + ISwitchManagerAware, + IConfigurationContainerAware, + IInventoryListener, + IObjectReader, + ICacheUpdateAware, + CommandProvider, + IFlowProgrammerListener { private static final String NODEDOWN = "Node is Down"; private static final String SUCCESS = StatusCode.SUCCESS.toString(); private static final Logger log = LoggerFactory.getLogger(ForwardingRulesManager.class); private static final String PORTREMOVED = "Port removed"; + private static final Logger logsync = LoggerFactory.getLogger("FRMsync"); private String frmFileName; private String portGroupFileName; private ConcurrentMap staticFlows; @@ -136,6 +154,98 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou private Thread frmEventHandler; protected BlockingQueue pendingEvents; + // Distributes FRM programming in the cluster + private IConnectionManager connectionManager; + + /* + * Name clustered caches used to support FRM entry distribution these are by + * necessity non-transactional as long as need to be able to synchronize + * states also while a transaction is in progress + */ + static final String WORKORDERCACHE = "frm.workOrder"; + static final String WORKSTATUSCACHE = "frm.workStatus"; + + /* + * Data structure responsible for distributing the FlowEntryInstall requests + * in the cluster. The key value is entry that is being either Installed or + * Updated or Delete. The value field is the same of the key value in case + * of Installation or Deletion, it's the new entry in case of Modification, + * this because the clustering caches don't allow null values. + * + * The logic behind this data structure is that the controller that initiate + * the request will place the order here, someone will pick it and then will + * remove from this data structure because is being served. + * + * TODO: We need to have a way to cleanup this data structure if entries are + * not picked by anyone, which is always a case can happen especially on + * Node disconnect cases. + */ + private ConcurrentMap workOrder; + + /* + * Data structure responsible for retrieving the results of the workOrder + * submitted to the cluster. + * + * The logic behind this data structure is that the controller that has + * executed the order will then place the result in workStatus signaling + * that there was a success or a failure. + * + * TODO: The workStatus entries need to have a lifetime associated in case + * of requestor controller leaving the cluster. + */ + private ConcurrentMap workStatus; + + /* + * Local Map used to hold the Future which a caller can use to monitor for + * completion + */ + private ConcurrentMap workMonitor = + new ConcurrentHashMap(); + + /** + * @param e + * Entry being installed/updated/removed + * @param u + * New entry will be placed after the update operation. Valid + * only for UpdateType.CHANGED, null for all the other cases + * @param t + * Type of update + * @return a Future object for monitoring the progress of the result, or + * null in case the processing should take place locally + */ + private FlowEntryDistributionOrderFutureTask distributeWorkOrder(FlowEntryInstall e, FlowEntryInstall u, + UpdateType t) { + // A null entry it's an unexpected condition, anyway it's safe to keep + // the handling local + if (e == null) { + return null; + } + + Node n = e.getNode(); + if (connectionManager.getLocalityStatus(n) == ConnectionLocality.NOT_LOCAL) { + // Create the work order and distribute it + FlowEntryDistributionOrder fe = + new FlowEntryDistributionOrder(e, t, clusterContainerService.getMyAddress()); + // First create the monitor job + FlowEntryDistributionOrderFutureTask ret = new FlowEntryDistributionOrderFutureTask(fe); + logsync.trace("Node {} not local so sending fe {}", n, fe); + workMonitor.put(fe, ret); + if (t.equals(UpdateType.CHANGED)) { + // Then distribute the work + workOrder.put(fe, u); + } else { + // Then distribute the work + workOrder.put(fe, e); + } + logsync.trace("WorkOrder requested"); + // Now create an Handle to monitor the execution of the operation + return ret; + } + + logsync.trace("Node {} could be local. so processing Entry:{} UpdateType:{}", n, e, t); + return null; + } + /** * Adds a flow entry onto the network node It runs various validity checks * and derive the final container flows merged entries that will be @@ -434,25 +544,46 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou * contain the unique id assigned to this request */ private Status modifyEntryInternal(FlowEntryInstall currentEntries, FlowEntryInstall newEntries, boolean async) { - // Modify the flow on the network node - Status status = (async) ? programmer.modifyFlowAsync(currentEntries.getNode(), currentEntries.getInstall() - .getFlow(), newEntries.getInstall().getFlow()) : programmer.modifyFlow(currentEntries.getNode(), - currentEntries.getInstall().getFlow(), newEntries.getInstall().getFlow()); + FlowEntryDistributionOrderFutureTask futureStatus = + distributeWorkOrder(currentEntries, newEntries, UpdateType.CHANGED); + if (futureStatus != null) { + Status retStatus = new Status(StatusCode.UNDEFINED); + try { + retStatus = futureStatus.get(); + if (retStatus.getCode() + .equals(StatusCode.TIMEOUT)) { + // A timeout happened, lets cleanup the workMonitor + workMonitor.remove(futureStatus.getOrder()); + } + } catch (InterruptedException e) { + log.error("", e); + } catch (ExecutionException e) { + log.error("", e); + } + return retStatus; + } else { + // Modify the flow on the network node + Status status = async ? programmer.modifyFlowAsync(currentEntries.getNode(), currentEntries.getInstall() + .getFlow(), newEntries.getInstall() + .getFlow()) : programmer.modifyFlow(currentEntries.getNode(), currentEntries.getInstall() + .getFlow(), newEntries.getInstall() + .getFlow()); - if (!status.isSuccess()) { - log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", newEntries.getInstall(), - status.getDescription()); - return status; - } + if (!status.isSuccess()) { + log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", newEntries.getInstall(), + status.getDescription()); + return status; + } - log.trace("Modified {} => {}", currentEntries.getInstall(), newEntries.getInstall()); + log.trace("Modified {} => {}", currentEntries.getInstall(), newEntries.getInstall()); - // Update DB - newEntries.setRequestId(status.getRequestId()); - updateLocalDatabase(currentEntries, false); - updateLocalDatabase(newEntries, true); + // Update DB + newEntries.setRequestId(status.getRequestId()); + updateLocalDatabase(currentEntries, false); + updateLocalDatabase(newEntries, true); - return status; + return status; + } } /** @@ -531,24 +662,43 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou * contain the unique id assigned to this request */ private Status removeEntryInternal(FlowEntryInstall entry, boolean async) { - // Mark the entry to be deleted (for CC just in case we fail) - entry.toBeDeleted(); + FlowEntryDistributionOrderFutureTask futureStatus = distributeWorkOrder(entry, null, UpdateType.REMOVED); + if (futureStatus != null) { + Status retStatus = new Status(StatusCode.UNDEFINED); + try { + retStatus = futureStatus.get(); + if (retStatus.getCode() + .equals(StatusCode.TIMEOUT)) { + // A timeout happened, lets cleanup the workMonitor + workMonitor.remove(futureStatus.getOrder()); + } + } catch (InterruptedException e) { + log.error("", e); + } catch (ExecutionException e) { + log.error("", e); + } + return retStatus; + } else { + // Mark the entry to be deleted (for CC just in case we fail) + entry.toBeDeleted(); - // Remove from node - Status status = (async) ? programmer.removeFlowAsync(entry.getNode(), entry.getInstall().getFlow()) - : programmer.removeFlow(entry.getNode(), entry.getInstall().getFlow()); + // Remove from node + Status status = async ? programmer.removeFlowAsync(entry.getNode(), entry.getInstall() + .getFlow()) : programmer.removeFlow(entry.getNode(), entry.getInstall() + .getFlow()); - if (!status.isSuccess()) { - log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(), - status.getDescription()); - return status; - } - log.trace("Removed {}", entry.getInstall()); + if (!status.isSuccess()) { + log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(), + status.getDescription()); + return status; + } + log.trace("Removed {}", entry.getInstall()); - // Update DB - updateLocalDatabase(entry, false); + // Update DB + updateLocalDatabase(entry, false); - return status; + return status; + } } /** @@ -565,23 +715,42 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou * contain the unique id assigned to this request */ private Status addEntriesInternal(FlowEntryInstall entry, boolean async) { - // Install the flow on the network node - Status status = (async) ? programmer.addFlowAsync(entry.getNode(), entry.getInstall().getFlow()) : programmer - .addFlow(entry.getNode(), entry.getInstall().getFlow()); + FlowEntryDistributionOrderFutureTask futureStatus = distributeWorkOrder(entry, null, UpdateType.ADDED); + if (futureStatus != null) { + Status retStatus = new Status(StatusCode.UNDEFINED); + try { + retStatus = futureStatus.get(); + if (retStatus.getCode() + .equals(StatusCode.TIMEOUT)) { + // A timeout happened, lets cleanup the workMonitor + workMonitor.remove(futureStatus.getOrder()); + } + } catch (InterruptedException e) { + log.error("", e); + } catch (ExecutionException e) { + log.error("", e); + } + return retStatus; + } else { + // Install the flow on the network node + Status status = async ? programmer.addFlowAsync(entry.getNode(), entry.getInstall() + .getFlow()) : programmer.addFlow(entry.getNode(), entry.getInstall() + .getFlow()); - if (!status.isSuccess()) { - log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(), - status.getDescription()); - return status; - } + if (!status.isSuccess()) { + log.warn("SDN Plugin failed to program the flow: {}. The failure is: {}", entry.getInstall(), + status.getDescription()); + return status; + } - log.trace("Added {}", entry.getInstall()); + log.trace("Added {}", entry.getInstall()); - // Update DB - entry.setRequestId(status.getRequestId()); - updateLocalDatabase(entry, true); + // Update DB + entry.setRequestId(status.getRequestId()); + updateLocalDatabase(entry, true); - return status; + return status; + } } /** @@ -613,6 +782,16 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou return true; } + private ConcurrentMap.Entry getStaticFlowEntry(String name, Node node) { + for (ConcurrentMap.Entry flowEntry : staticFlows.entrySet()) { + FlowConfig flowConfig = flowEntry.getValue(); + if (flowConfig.isByNameAndNodeIdEqual(name, node)) { + return flowEntry; + } + } + return null; + } + private void updateLocalDatabase(FlowEntryInstall entry, boolean add) { // Update the software view updateSwViewes(entry, add); @@ -1160,7 +1339,6 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou retrieveCaches(); } - @SuppressWarnings("deprecation") private void allocateCaches() { if (this.clusterContainerService == null) { log.warn("Un-initialized clusterContainerService, can't create cache"); @@ -1203,6 +1381,12 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou clusterContainerService.createCache("frm.TSPolicies", EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL)); + clusterContainerService.createCache(WORKSTATUSCACHE, + EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL)); + + clusterContainerService.createCache(WORKORDERCACHE, + EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL)); + } catch (CacheConfigException cce) { log.error("CacheConfigException"); } catch (CacheExistException cce) { @@ -1210,7 +1394,7 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } } - @SuppressWarnings({ "unchecked", "deprecation" }) + @SuppressWarnings({ "unchecked" }) private void retrieveCaches() { ConcurrentMap map; @@ -1292,6 +1476,19 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou log.error("Retrieval of frm.TSPolicies cache failed for Container {}", container.getName()); } + map = clusterContainerService.getCache(WORKORDERCACHE); + if (map != null) { + workOrder = (ConcurrentMap) map; + } else { + log.error("Retrieval of " + WORKORDERCACHE + " cache failed for Container {}", container.getName()); + } + + map = clusterContainerService.getCache(WORKSTATUSCACHE); + if (map != null) { + workStatus = (ConcurrentMap) map; + } else { + log.error("Retrieval of " + WORKSTATUSCACHE + " cache failed for Container {}", container.getName()); + } } private boolean flowConfigExists(FlowConfig config) { @@ -1646,9 +1843,13 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } } if (target != null) { - // Program the network node - Status status = (target.installInHw()) ? this.uninstallFlowEntry(target.getFlowEntry()) : this - .installFlowEntry(target.getFlowEntry()); + Status status = target.validate(container); + if (!status.isSuccess()) { + log.warn(status.getDescription()); + return status; + } + status = (target.installInHw()) ? this.uninstallFlowEntry(target.getFlowEntry()) : this + .installFlowEntry(target.getFlowEntry()); if (status.isSuccess()) { // Update Configuration database target.setStatus(SUCCESS); @@ -1681,27 +1882,38 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou /** * Uninstall all the non-internal Flow Entries present in the software view. - * A copy of each entry is stored in the inactive list so that it can be - * re-applied when needed. This function is called on the global instance of - * FRM only, when the first container is created + * If requested, a copy of each original flow entry will be stored in the + * inactive list so that it can be re-applied when needed (This is typically + * the case when running in the default container and controller moved to + * container mode) + * + * @param preserveFlowEntries + * if true, a copy of each original entry is stored in the + * inactive list */ - private void uninstallAllFlowEntries() { + private void uninstallAllFlowEntries(boolean preserveFlowEntries) { log.info("Uninstalling all non-internal flows"); + List toRemove = new ArrayList(); + // Store entries / create target list for (ConcurrentMap.Entry mapEntry : installedSwView.entrySet()) { FlowEntryInstall flowEntries = mapEntry.getValue(); // Skip internal generated static flows if (!flowEntries.isInternal()) { - inactiveFlows.put(flowEntries.getOriginal(), flowEntries.getOriginal()); + toRemove.add(flowEntries); + // Store the original entries if requested + if (preserveFlowEntries) { + inactiveFlows.put(flowEntries.getOriginal(), flowEntries.getOriginal()); + } } } // Now remove the entries - for (FlowEntry flowEntry : inactiveFlows.keySet()) { - Status status = this.removeEntry(flowEntry, false); + for (FlowEntryInstall flowEntryHw : toRemove) { + Status status = this.removeEntryInternal(flowEntryHw, false); if (!status.isSuccess()) { - log.warn("Failed to remove entry: {}. The failure is: {}", flowEntry, status.getDescription()); + log.warn("Failed to remove entry: {}. The failure is: {}", flowEntryHw, status.getDescription()); } } } @@ -1744,10 +1956,9 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou @Override public FlowConfig getStaticFlow(String name, Node node) { - for (ConcurrentMap.Entry entry : staticFlows.entrySet()) { - if (entry.getValue().isByNameAndNodeIdEqual(name, node)) { - return entry.getValue(); - } + ConcurrentMap.Entry entry = getStaticFlowEntry(name, node); + if(entry != null) { + return entry.getValue(); } return null; } @@ -1864,51 +2075,79 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou addStaticFlowInternal(allowARP, true); // skip validation on internal static flow name } + /** + * (non-Javadoc) + * + * @see org.opendaylight.controller.switchmanager.ISwitchManagerAware#modeChangeNotify(org.opendaylight.controller.sal.core.Node, + * boolean) + * + * This method can be called from within the OSGi framework context, + * given the programming operation can take sometime, it not good + * pratice to have in it's context operations that can take time, + * hence moving off to a different thread for async processing. + */ + private ExecutorService executor; @Override - public void modeChangeNotify(Node node, boolean proactive) { - List defaultConfigs = new ArrayList(); - - List puntAction = new ArrayList(); - puntAction.add(ActionType.CONTROLLER.toString()); - - FlowConfig allowARP = new FlowConfig(); - allowARP.setInstallInHw(true); - allowARP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt ARP" + FlowConfig.INTERNALSTATICFLOWEND); - allowARP.setPriority("1"); - allowARP.setNode(node); - allowARP.setEtherType("0x" + Integer.toHexString(EtherTypes.ARP.intValue()).toUpperCase()); - allowARP.setActions(puntAction); - defaultConfigs.add(allowARP); - - FlowConfig allowLLDP = new FlowConfig(); - allowLLDP.setInstallInHw(true); - allowLLDP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt LLDP" + FlowConfig.INTERNALSTATICFLOWEND); - allowLLDP.setPriority("1"); - allowLLDP.setNode(node); - allowLLDP.setEtherType("0x" + Integer.toHexString(EtherTypes.LLDP.intValue()).toUpperCase()); - allowLLDP.setActions(puntAction); - defaultConfigs.add(allowLLDP); - - List dropAction = new ArrayList(); - dropAction.add(ActionType.DROP.toString()); - - FlowConfig dropAllConfig = new FlowConfig(); - dropAllConfig.setInstallInHw(true); - dropAllConfig.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Catch-All Drop" + FlowConfig.INTERNALSTATICFLOWEND); - dropAllConfig.setPriority("0"); - dropAllConfig.setNode(node); - dropAllConfig.setActions(dropAction); - defaultConfigs.add(dropAllConfig); - - log.info("Forwarding mode for node {} set to {}", node, (proactive ? "proactive" : "reactive")); - for (FlowConfig fc : defaultConfigs) { - Status status = (proactive) ? addStaticFlowInternal(fc, false) : removeStaticFlow(fc); - if (status.isSuccess()) { - log.info("{} Proactive Static flow: {}", (proactive ? "Installed" : "Removed"), fc.getName()); - } else { - log.warn("Failed to {} Proactive Static flow: {}", (proactive ? "install" : "remove"), fc.getName()); + public void modeChangeNotify(final Node node, final boolean proactive) { + Callable modeChangeCallable = new Callable() { + @Override + public Status call() throws Exception { + List defaultConfigs = new ArrayList(); + + List puntAction = new ArrayList(); + puntAction.add(ActionType.CONTROLLER.toString()); + + FlowConfig allowARP = new FlowConfig(); + allowARP.setInstallInHw(true); + allowARP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt ARP" + FlowConfig.INTERNALSTATICFLOWEND); + allowARP.setPriority("1"); + allowARP.setNode(node); + allowARP.setEtherType("0x" + Integer.toHexString(EtherTypes.ARP.intValue()) + .toUpperCase()); + allowARP.setActions(puntAction); + defaultConfigs.add(allowARP); + + FlowConfig allowLLDP = new FlowConfig(); + allowLLDP.setInstallInHw(true); + allowLLDP.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Punt LLDP" + FlowConfig.INTERNALSTATICFLOWEND); + allowLLDP.setPriority("1"); + allowLLDP.setNode(node); + allowLLDP.setEtherType("0x" + Integer.toHexString(EtherTypes.LLDP.intValue()) + .toUpperCase()); + allowLLDP.setActions(puntAction); + defaultConfigs.add(allowLLDP); + + List dropAction = new ArrayList(); + dropAction.add(ActionType.DROP.toString()); + + FlowConfig dropAllConfig = new FlowConfig(); + dropAllConfig.setInstallInHw(true); + dropAllConfig.setName(FlowConfig.INTERNALSTATICFLOWBEGIN + "Catch-All Drop" + + FlowConfig.INTERNALSTATICFLOWEND); + dropAllConfig.setPriority("0"); + dropAllConfig.setNode(node); + dropAllConfig.setActions(dropAction); + defaultConfigs.add(dropAllConfig); + + log.info("Forwarding mode for node {} set to {}", node, (proactive ? "proactive" : "reactive")); + for (FlowConfig fc : defaultConfigs) { + Status status = (proactive) ? addStaticFlowInternal(fc, false) : removeStaticFlow(fc); + if (status.isSuccess()) { + log.info("{} Proactive Static flow: {}", (proactive ? "Installed" : "Removed"), fc.getName()); + } else { + log.warn("Failed to {} Proactive Static flow: {}", (proactive ? "install" : "remove"), + fc.getName()); + } + } + return new Status(StatusCode.SUCCESS); } - } + }; + + /* + * Execute the work outside the caller context, this could be an + * expensive operation and we don't want to block the caller for it. + */ + this.executor.submit(modeChangeCallable); } /** @@ -1960,7 +2199,99 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou @Override public void notifyNodeConnector(NodeConnector nodeConnector, UpdateType type, Map propMap) { + boolean updateStaticFlowCluster = false; + + switch (type) { + case ADDED: + break; + case CHANGED: + Config config = (propMap == null) ? null : (Config) propMap.get(Config.ConfigPropName); + if (config != null) { + switch (config.getValue()) { + case Config.ADMIN_DOWN: + log.trace("Port {} is administratively down: uninstalling interested flows", nodeConnector); + updateStaticFlowCluster = removeFlowsOnNodeConnectorDown(nodeConnector); + break; + case Config.ADMIN_UP: + log.trace("Port {} is administratively up: installing interested flows", nodeConnector); + updateStaticFlowCluster = installFlowsOnNodeConnectorUp(nodeConnector); + break; + case Config.ADMIN_UNDEF: + break; + default: + } + } + break; + case REMOVED: + // This is the case where a switch port is removed from the SDN agent space + log.trace("Port {} was removed from our control: uninstalling interested flows", nodeConnector); + updateStaticFlowCluster = removeFlowsOnNodeConnectorDown(nodeConnector); + break; + default: + + } + + if (updateStaticFlowCluster) { + refreshClusterStaticFlowsStatus(nodeConnector.getNode()); + } + } + + /* + * It goes through the static flows configuration, it identifies the ones + * which have the specified node connector as input or output port and + * install them on the network node if they are marked to be installed in + * hardware and their status shows they were not installed yet + */ + private boolean installFlowsOnNodeConnectorUp(NodeConnector nodeConnector) { + boolean updated = false; + List flowConfigForNode = getStaticFlows(nodeConnector.getNode()); + for (FlowConfig flowConfig : flowConfigForNode) { + if (doesFlowContainNodeConnector(flowConfig.getFlow(), nodeConnector)) { + if (flowConfig.installInHw() && !flowConfig.getStatus().equals(SUCCESS)) { + Status status = this.installFlowEntry(flowConfig.getFlowEntry()); + if (!status.isSuccess()) { + flowConfig.setStatus(status.getDescription()); + } else { + flowConfig.setStatus(SUCCESS); + } + updated = true; + } + } + } + return updated; + } + /* + * Remove from the network node all the flows which have the specified node + * connector as input or output port. If any of the flow entry is a static + * flow, it updates the correspondent configuration. + */ + private boolean removeFlowsOnNodeConnectorDown(NodeConnector nodeConnector) { + boolean updated = false; + List nodeFlowEntries = nodeFlows.get(nodeConnector.getNode()); + if (nodeFlowEntries == null) { + return updated; + } + for (FlowEntryInstall fei : new ArrayList(nodeFlowEntries)) { + if (doesFlowContainNodeConnector(fei.getInstall().getFlow(), nodeConnector)) { + Status status = this.removeEntryInternal(fei, true); + if (!status.isSuccess()) { + continue; + } + /* + * If the flow entry is a static flow, then update its + * configuration + */ + if (fei.getGroupName().equals(FlowConfig.STATICFLOWGROUP)) { + FlowConfig flowConfig = getStaticFlow(fei.getFlowName(), fei.getNode()); + if (flowConfig != null) { + flowConfig.setStatus(PORTREMOVED); + updated = true; + } + } + } + } + return updated; } private FlowConfig getDerivedFlowConfig(FlowConfig original, String configName, Short port) { @@ -2201,11 +2532,68 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } else if (event instanceof ErrorReportedEvent) { ErrorReportedEvent errEvent = (ErrorReportedEvent) event; processErrorEvent(errEvent); + } else if (event instanceof WorkOrderEvent) { + /* + * Take care of handling the remote Work request + */ + WorkOrderEvent work = (WorkOrderEvent) event; + FlowEntryDistributionOrder fe = work.getFe(); + if (fe != null) { + logsync.trace("Executing the workOrder {}", fe); + Status gotStatus = null; + FlowEntryInstall feiCurrent = fe.getEntry(); + FlowEntryInstall feiNew = workOrder.get(fe.getEntry()); + switch (fe.getUpType()) { + case ADDED: + /* + * TODO: Not still sure how to handle the + * sync entries + */ + gotStatus = addEntriesInternal(feiCurrent, true); + break; + case CHANGED: + gotStatus = modifyEntryInternal(feiCurrent, feiNew, true); + break; + case REMOVED: + gotStatus = removeEntryInternal(feiCurrent, true); + break; + } + // Remove the Order + workOrder.remove(fe); + logsync.trace( + "The workOrder has been executed and now the status is being returned {}", fe); + // Place the status + workStatus.put(fe, gotStatus); + } else { + log.warn("Not expected null WorkOrder", work); + } + } else if (event instanceof WorkStatusCleanup) { + /* + * Take care of handling the remote Work request + */ + WorkStatusCleanup work = (WorkStatusCleanup) event; + FlowEntryDistributionOrder fe = work.getFe(); + if (fe != null) { + logsync.trace("The workStatus {} is being removed", fe); + workStatus.remove(fe); + } else { + log.warn("Not expected null WorkStatus", work); + } + } else if (event instanceof ContainerFlowChangeEvent) { + /* + * Whether it is an addition or removal, we have to + * recompute the merged flows entries taking into + * account all the current container flows because + * flow merging is not an injective function + */ + updateFlowsContainerFlow(); } else { - log.warn("Dequeued unknown event {}", event.getClass().getSimpleName()); + log.warn("Dequeued unknown event {}", event.getClass() + .getSimpleName()); } } catch (InterruptedException e) { - log.warn("FRM EventHandler thread interrupted", e); + // clear pending events + pendingEvents.clear(); } } } @@ -2219,7 +2607,12 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou * */ void destroy() { + // Interrupt the thread + frmEventHandler.interrupt(); + // Clear the pendingEvents queue + pendingEvents.clear(); frmAware.clear(); + workMonitor.clear(); } /** @@ -2231,6 +2624,9 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou // Initialize graceful stop flag stopping = false; + // Allocate the executor service + this.executor = Executors.newSingleThreadExecutor(); + // Start event handler thread frmEventHandler.start(); @@ -2250,7 +2646,9 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou */ void stop() { stopping = true; - uninstallAllFlowEntries(); + uninstallAllFlowEntries(false); + // Shutdown executor + this.executor.shutdownNow(); } public void setFlowProgrammerService(IFlowProgrammerService service) { @@ -2287,12 +2685,8 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } log.trace("Container {}: Updating installed flows because of container flow change: {} {}", container.getName(), t, current); - /* - * Whether it is an addition or removal, we have to recompute the merged - * flows entries taking into account all the current container flows - * because flow merging is not an injective function - */ - updateFlowsContainerFlow(); + ContainerFlowChangeEvent ev = new ContainerFlowChangeEvent(previous, current, t); + pendingEvents.offer(ev); } @Override @@ -2305,57 +2699,22 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou switch (t) { case REMOVED: - - List nodeFlowEntries = nodeFlows.get(nc.getNode()); - if (nodeFlowEntries == null) { - return; - } - for (FlowEntryInstall fei : new ArrayList(nodeFlowEntries)) { - if (doesFlowContainNodeConnector(fei.getInstall().getFlow(), nc)) { - Status status = this.removeEntryInternal(fei, true); - if (!status.isSuccess()) { - continue; - } - /* - * If the flow entry is a static flow, then update its - * configuration - */ - if (fei.getGroupName().equals(FlowConfig.STATICFLOWGROUP)) { - FlowConfig flowConfig = getStaticFlow(fei.getFlowName(), fei.getNode()); - if (flowConfig != null) { - flowConfig.setStatus(PORTREMOVED); - updateStaticFlowCluster = true; - } - } - } - } - if (updateStaticFlowCluster) { - refreshClusterStaticFlowsStatus(nc.getNode()); - } + log.trace("Port {} was removed from container: uninstalling interested flows", nc); + updateStaticFlowCluster = removeFlowsOnNodeConnectorDown(nc); break; case ADDED: - List flowConfigForNode = getStaticFlows(nc.getNode()); - for (FlowConfig flowConfig : flowConfigForNode) { - if (doesFlowContainNodeConnector(flowConfig.getFlow(), nc)) { - if (flowConfig.installInHw()) { - Status status = this.installFlowEntry(flowConfig.getFlowEntry()); - if (!status.isSuccess()) { - flowConfig.setStatus(status.getDescription()); - } else { - flowConfig.setStatus(SUCCESS); - } - updateStaticFlowCluster = true; - } - } - } - if (updateStaticFlowCluster) { - refreshClusterStaticFlowsStatus(nc.getNode()); - } + log.trace("Port {} was added to container: reinstall interested flows", nc); + updateStaticFlowCluster = installFlowsOnNodeConnectorUp(nc); + break; case CHANGED: break; default: } + + if (updateStaticFlowCluster) { + refreshClusterStaticFlowsStatus(nc.getNode()); + } } @Override @@ -2366,8 +2725,15 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } switch (update) { case ADDED: + /* + * Controller is moving to container mode. We are in the default + * container context, we need to remove all our non-internal flows + * to prevent any container isolation breakage. We also need to + * preserve our flow so that they can be re-installed if we move + * back to non container mode (no containers). + */ this.inContainerMode = true; - this.uninstallAllFlowEntries(); + this.uninstallAllFlowEntries(true); break; case REMOVED: this.inContainerMode = false; @@ -2426,6 +2792,76 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } } + private class WorkOrderEvent extends FRMEvent { + private FlowEntryDistributionOrder fe; + private FlowEntryInstall newEntry; + + /** + * @param fe + * @param newEntry + */ + WorkOrderEvent(FlowEntryDistributionOrder fe, FlowEntryInstall newEntry) { + this.fe = fe; + this.newEntry = newEntry; + } + + /** + * @return the fe + */ + public FlowEntryDistributionOrder getFe() { + return fe; + } + + /** + * @return the newEntry + */ + public FlowEntryInstall getNewEntry() { + return newEntry; + } + } + private class ContainerFlowChangeEvent extends FRMEvent { + private final ContainerFlow previous; + private final ContainerFlow current; + private final UpdateType type; + + public ContainerFlowChangeEvent(ContainerFlow previous, ContainerFlow current, UpdateType type) { + this.previous = previous; + this.current = current; + this.type = type; + } + + public ContainerFlow getPrevious() { + return this.previous; + } + + public ContainerFlow getCurrent() { + return this.current; + } + + public UpdateType getType() { + return this.type; + } + } + + + private class WorkStatusCleanup extends FRMEvent { + private FlowEntryDistributionOrder fe; + + /** + * @param fe + */ + WorkStatusCleanup(FlowEntryDistributionOrder fe) { + this.fe = fe; + } + + /** + * @return the fe + */ + public FlowEntryDistributionOrder getFe() { + return fe; + } + } + /* * OSGI COMMANDS */ @@ -2604,6 +3040,36 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou } } + public void _frmProcessErrorEvent(CommandInterpreter ci) throws UnknownHostException { + Node node = null; + long reqId = 0L; + String nodeId = ci.nextArgument(); + if (nodeId == null) { + ci.print("Node id not specified"); + return; + } + String requestId = ci.nextArgument(); + if (requestId == null) { + ci.print("Request id not specified"); + return; + } + try { + node = NodeCreator.createOFNode(Long.valueOf(nodeId)); + } catch (NumberFormatException e) { + ci.print("Node id not a number"); + return; + } + try { + reqId = Long.parseLong(requestId); + } catch (NumberFormatException e) { + ci.print("Request id not a number"); + return; + } + // null for error object is good enough for now + ErrorReportedEvent event = new ErrorReportedEvent(reqId, node, null); + this.processErrorEvent(event); + } + @Override public void flowRemoved(Node node, Flow flow) { log.trace("Received flow removed notification on {} for {}", node, flow); @@ -2656,16 +3122,32 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou * mapping will have to be added in future */ FlowEntryInstall target = null; - for (FlowEntryInstall index : nodeFlows.get(node)) { - FlowEntryInstall entry = installedSwView.get(index); - if (entry.getRequestId() == rid) { - target = entry; - break; + List flowEntryInstallList = nodeFlows.get(node); + // flowEntryInstallList could be null. + // so check for it. + if(flowEntryInstallList != null) { + for (FlowEntryInstall index : flowEntryInstallList) { + FlowEntryInstall entry = installedSwView.get(index); + if(entry != null) { + if (entry.getRequestId() == rid) { + target = entry; + break; + } + } } } if (target != null) { // This was a flow install, update database this.updateLocalDatabase(target, false); + // also update the config + if(FlowConfig.STATICFLOWGROUP.equals(target.getGroupName())) { + ConcurrentMap.Entry staticFlowEntry = getStaticFlowEntry(target.getFlowName(),target.getNode()); + // staticFlowEntry should never be null. + // the null check is just an extra defensive check. + if(staticFlowEntry != null) { + staticFlows.remove(staticFlowEntry.getKey()); + } + } } // Notify listeners @@ -2696,4 +3178,78 @@ public class ForwardingRulesManager implements IForwardingRulesManager, PortGrou return rv; } + + public void unsetIConnectionManager(IConnectionManager s) { + if (s == this.connectionManager) { + this.connectionManager = null; + } + } + + public void setIConnectionManager(IConnectionManager s) { + this.connectionManager = s; + } + + @Override + public void entryCreated(Object key, String cacheName, boolean originLocal) { + /* + * Do nothing + */ + } + + @Override + public void entryUpdated(Object key, Object new_value, String cacheName, boolean originLocal) { + if (originLocal) { + /* + * Local updates are of no interest + */ + return; + } + if (cacheName.equals(WORKORDERCACHE)) { + logsync.trace("Got a WorkOrderCacheUpdate for {}", key); + /* + * This is the case of one workOrder becoming available, so we need + * to dispatch the work to the appropriate handler + */ + FlowEntryDistributionOrder fe = (FlowEntryDistributionOrder) key; + FlowEntryInstall fei = fe.getEntry(); + if (fei == null) { + return; + } + Node n = fei.getNode(); + if (connectionManager.getLocalityStatus(n) == ConnectionLocality.LOCAL) { + logsync.trace("workOrder for fe {} processed locally", fe); + // I'm the controller in charge for the request, queue it for + // processing + pendingEvents.offer(new WorkOrderEvent(fe, (FlowEntryInstall) new_value)); + } + } else if (cacheName.equals(WORKSTATUSCACHE)) { + logsync.trace("Got a WorkStatusCacheUpdate for {}", key); + /* + * This is the case of one workOrder being completed and a status + * returned + */ + FlowEntryDistributionOrder fe = (FlowEntryDistributionOrder) key; + /* + * Check if the order was initiated by this controller in that case + * we need to actually look at the status returned + */ + if (fe.getRequestorController() + .equals(clusterContainerService.getMyAddress())) { + FlowEntryDistributionOrderFutureTask fet = workMonitor.remove(fe); + if (fet != null) { + logsync.trace("workStatus response is for us {}", fe); + // Signal we got the status + fet.gotStatus(fe, workStatus.get(fe)); + pendingEvents.offer(new WorkStatusCleanup(fe)); + } + } + } + } + + @Override + public void entryDeleted(Object key, String cacheName, boolean originLocal) { + /* + * Do nothing + */ + } }