Bug 3353 - added table offset to of-overlay-config 57/24157/14
authorTomas Cechvala <tcechval@cisco.com>
Wed, 15 Jul 2015 12:37:07 +0000 (14:37 +0200)
committerTomas Cechvala <tcechval@cisco.com>
Fri, 28 Aug 2015 13:20:34 +0000 (13:20 +0000)
Table offset added to ofoverlay model and might be changed in runtime.
 - offset leaf moved from overlay-provider-impl.yang to ofoverlay.yang
 - offset value changed from uint16 to uint8 according to spec,
 - offset value validation.
 - OfOverlayRendererTest adapted

Change-Id: I1479f36b0ef34be8fce97b562e59b723489a7482
Signed-off-by: Tomas Cechvala <tcechval@cisco.com>
renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRenderer.java
renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/PolicyManager.java
renderers/ofoverlay/src/main/yang/ofoverlay-provider-impl.yang
renderers/ofoverlay/src/main/yang/ofoverlay.yang
renderers/ofoverlay/src/test/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRendererTest.java

index 7342dc56437612f1f67180bd7503dc37b324191c..139d09024c80c37a8d39aeff2bd25d75d85805e2 100644 (file)
@@ -8,19 +8,29 @@
 
 package org.opendaylight.groupbasedpolicy.renderer.ofoverlay;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
+import javax.annotation.Nonnull;
+
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
 import org.opendaylight.controller.md.sal.binding.api.NotificationService;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchManager;
 import org.opendaylight.groupbasedpolicy.resolver.PolicyResolver;
+import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfigBuilder;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@@ -28,7 +38,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Optional;
-import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -47,23 +56,19 @@ public class OFOverlayRenderer implements AutoCloseable, DataChangeListener {
     private final EndpointManager endpointManager;
     private final PolicyManager policyManager;
 
-    private final short tableOffset;
-
     private final ScheduledExecutorService executor;
 
     private static final InstanceIdentifier<OfOverlayConfig> configIid =
             InstanceIdentifier.builder(OfOverlayConfig.class).build();
 
-    private OfOverlayConfig config;
     ListenerRegistration<DataChangeListener> configReg;
 
-    public OFOverlayRenderer(DataBroker dataProvider,
+    public OFOverlayRenderer(final DataBroker dataProvider,
                              RpcProviderRegistry rpcRegistry,
                              NotificationService notificationService,
-                             short tableOffset) {
+                             final short tableOffset) {
         super();
         this.dataBroker = dataProvider;
-        this.tableOffset=tableOffset;
 
         int numCPU = Runtime.getRuntime().availableProcessors();
         //TODO: Consider moving to groupbasedpolicy-ofoverlay-config so as to be user configurable in distribution.
@@ -81,15 +86,16 @@ public class OFOverlayRenderer implements AutoCloseable, DataChangeListener {
                                           rpcRegistry,
                                           executor,
                                           tableOffset);
-
-        configReg =
-                dataProvider.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
-                                                        configIid,
-                                                        this,
-                                                        DataChangeScope.SUBTREE);
-        readConfig();
-        LOG.info("Initialized OFOverlay renderer");
-
+            Optional<OfOverlayConfig> config = readConfig();
+            OfOverlayConfigBuilder configBuilder = new OfOverlayConfigBuilder();
+            if (config.isPresent()) {
+                configBuilder = new OfOverlayConfigBuilder(config.get());
+            }
+            registerConfigListener(dataProvider);
+            if (configBuilder.getGbpOfoverlayTableOffset() == null) {
+                configBuilder.setGbpOfoverlayTableOffset(tableOffset).build();
+                writeTableOffset(configBuilder.build());
+            }
     }
 
     // *************
@@ -110,39 +116,65 @@ public class OFOverlayRenderer implements AutoCloseable, DataChangeListener {
     // ******************
 
     @Override
-    public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>,
-                                                   DataObject> change) {
-        readConfig();
+    public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
+        OfOverlayConfig config;
+        try {
+            for (Entry<InstanceIdentifier<?>, DataObject> entry : change.getCreatedData().entrySet()) {
+                if (entry.getValue() instanceof OfOverlayConfig) {
+                    config = (OfOverlayConfig) entry.getValue();
+                    applyConfig(config).get();
+                    LOG.info("OF-Overlay config created: {}", config);
+                }
+            }
+            for (Entry<InstanceIdentifier<?>, DataObject> entry : change.getUpdatedData().entrySet()) {
+                if (entry.getValue() instanceof OfOverlayConfig) {
+                    config = (OfOverlayConfig) entry.getValue();
+                    applyConfig(config).get();
+                    LOG.info("OF-Overlay config updated: {}", config);
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Error occured while updating config for OF-Overlay Renderer.\n{}", e);
+        }
     }
 
     // **************
     // Implementation
     // **************
 
-    private void readConfig() {
-        ListenableFuture<Optional<OfOverlayConfig>> dao =
-                dataBroker.newReadOnlyTransaction()
-                    .read(LogicalDatastoreType.CONFIGURATION, configIid);
-        Futures.addCallback(dao, new FutureCallback<Optional<OfOverlayConfig>>() {
-            @Override
-            public void onSuccess(final Optional<OfOverlayConfig> result) {
-                if (!result.isPresent()) return;
-                if (result.get() instanceof OfOverlayConfig) {
-                    config = result.get();
-                    applyConfig();
-                }
-            }
+    private void registerConfigListener(DataBroker dataProvider) {
+        configReg =
+                dataProvider.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
+                                                        configIid,
+                                                        this,
+                                                        DataChangeScope.SUBTREE);
+    }
 
-            @Override
-            public void onFailure(Throwable t) {
-                LOG.error("Failed to read configuration", t);
-            }
-        }, executor);
+    private Optional<OfOverlayConfig> readConfig() {
+        final ReadOnlyTransaction rTx = dataBroker.newReadOnlyTransaction();
+        Optional<OfOverlayConfig> config =
+                DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION,
+                                           configIid,
+                                           rTx);
+        rTx.close();
+        return config;
+    }
+
+    private ListenableFuture<Void> writeTableOffset(OfOverlayConfig ofc) {
+        WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
+        wTx.merge(LogicalDatastoreType.CONFIGURATION, configIid, ofc, true);
+        return wTx.submit();
     }
 
-    private void applyConfig() {
+    private ListenableFuture<List<Void>> applyConfig(@Nonnull OfOverlayConfig config) {
+        List<ListenableFuture<Void>> configFutures = new ArrayList<>();
+        // TODO add to list when implemented
         switchManager.setEncapsulationFormat(config.getEncapsulationFormat());
         endpointManager.setLearningMode(config.getLearningMode());
         policyManager.setLearningMode(config.getLearningMode());
+        if (config.getGbpOfoverlayTableOffset() != null) {
+            configFutures.add(policyManager.changeOpenFlowTableOffset(config.getGbpOfoverlayTableOffset().shortValue()));
+        }
+        return Futures.allAsList(configFutures);
     }
 }
index 60b7e97927728c3920646586fbbb5ffdbb8808f5..0e0a1d4e236ea7a99991a03f1314a006d0a33a1e 100755 (executable)
@@ -57,19 +57,23 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.LearningMode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.SubjectFeatureDefinitions;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.TableId;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Equivalence;
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.AsyncFunction;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 
 /**
  * Manage policies on switches by subscribing to updates from the
@@ -98,11 +102,11 @@ public class PolicyManager
     private final ScheduledExecutorService executor;
     private final SingletonTask flowUpdateTask;
     private final DataBroker dataBroker;
-
+    private final OfContext ofCtx;
     /**
      * The flow tables that make up the processing pipeline
      */
-    private final List<? extends OfTable> flowPipeline;
+    private List<? extends OfTable> flowPipeline;
 
     /**
      * The delay before triggering the flow update task in response to an
@@ -123,7 +127,13 @@ public class PolicyManager
         this.policyResolver = policyResolver;
         this.dataBroker = dataBroker;
         this.tableOffset = tableOffset;
-
+        try {
+            // to validate against model
+            verifyMaxTableId(tableOffset);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException("Failed to start OF-Overlay renderer\n."
+                    + "Max. table ID would be out of range. Check config-subsystem.\n{}", e);
+        }
 
         if (dataBroker != null) {
             WriteTransaction t = dataBroker.newWriteOnlyTransaction();
@@ -139,19 +149,11 @@ public class PolicyManager
             policyResolver.registerActionDefinitions(entry.getKey(), entry.getValue());
         }
 
-        OfContext ctx = new OfContext(dataBroker, rpcRegistry,
+        ofCtx = new OfContext(dataBroker, rpcRegistry,
                                         this, policyResolver, switchManager,
                                         endpointManager, executor);
 
-        flowPipeline = ImmutableList.of(new PortSecurity(ctx, (short)(tableOffset+TABLEID_PORTSECURITY)),
-                                        new GroupTable(ctx),
-                                        new IngressNatMapper(ctx, (short)(tableOffset+TABLEID_INGRESS_NAT)),
-                                        new SourceMapper(ctx, (short)(tableOffset+TABLEID_SOURCE_MAPPER)),
-                                        new DestinationMapper(ctx, (short)(tableOffset+TABLEID_DESTINATION_MAPPER)),
-                                        new PolicyEnforcer(ctx, (short)(tableOffset+TABLEID_POLICY_ENFORCER)),
-                                        new EgressNatMapper(ctx, (short)(tableOffset+TABLEID_EGRESS_NAT)),
-                                        new ExternalMapper(ctx, (short)(tableOffset+TABLEID_EXTERNAL_MAPPER))
-                                        );
+        flowPipeline = createFlowPipeline();
 
         policyScope = policyResolver.registerListener(this);
         if (switchManager != null)
@@ -164,11 +166,100 @@ public class PolicyManager
         LOG.debug("Initialized OFOverlay policy manager");
     }
 
+    private List<? extends OfTable> createFlowPipeline() {
+        // TODO - PORTSECURITY is kept in table 0.
+        // According to openflow spec,processing on vSwitch always starts from table 0.
+        // Packets will be droped if table 0 is empty.
+        // Alternative workaround - table-miss flow entries in table 0.
+        return ImmutableList.of(new PortSecurity(ofCtx, (short) 0),
+                                        new GroupTable(ofCtx),
+                                        new IngressNatMapper(ofCtx, getTABLEID_INGRESS_NAT()),
+                                        new SourceMapper(ofCtx, getTABLEID_SOURCE_MAPPER()),
+                                        new DestinationMapper(ofCtx, getTABLEID_DESTINATION_MAPPER()),
+                                        new PolicyEnforcer(ofCtx, getTABLEID_POLICY_ENFORCER()),
+                                        new EgressNatMapper(ofCtx, getTABLEID_EGRESS_NAT()),
+                                        new ExternalMapper(ofCtx, getTABLEID_EXTERNAL_MAPPER())
+                                        );
+    }
+
+    /**
+     * @param tableOffset - new offset value
+     * @return ListenableFuture<List> - to indicate that tables have been synced
+     */
+    public ListenableFuture<Void> changeOpenFlowTableOffset(final short tableOffset) {
+        try {
+            verifyMaxTableId(tableOffset);
+        } catch (IllegalArgumentException e) {
+            LOG.error("Cannot update table offset. Max. table ID would be out of range.\n{}", e);
+            // TODO - invalid offset value remains in conf DS
+            // It's not possible to validate offset value by using constrains in model,
+            // because number of tables in pipeline varies.
+            return Futures.immediateFuture(null);
+        }
+        List<Short> tableIDs = getTableIDs();
+        this.tableOffset = tableOffset;
+        return Futures.transform(removeUnusedTables(tableIDs), new Function<Void, Void>() {
+
+            @Override
+            public Void apply(Void tablesRemoved) {
+                flowPipeline = createFlowPipeline();
+                scheduleUpdate();
+                return null;
+            }
+        });
+    }
+
+    /**
+     * @param  tableIDs - IDs of tables to delete
+     * @return ListenableFuture<Void> - which will be filled when clearing is done
+     */
+    private ListenableFuture<Void> removeUnusedTables(final List<Short> tableIDs) {
+        List<ListenableFuture<Void>> checkList = new ArrayList<>();
+        final ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
+        for (Short tableId : tableIDs) {
+            for (NodeId nodeId : switchManager.getReadySwitches()) {
+                final InstanceIdentifier<Table> tablePath = FlowUtils.createTablePath(nodeId, tableId);
+                checkList.add(deteleTableIfExists(rwTx, tablePath));
+            }
+        }
+        ListenableFuture<List<Void>> allAsListFuture = Futures.allAsList(checkList);
+        return Futures.transform(allAsListFuture, new AsyncFunction<List<Void>, Void>() {
+
+            @Override
+            public ListenableFuture<Void> apply(List<Void> readyToSubmit) {
+                return rwTx.submit();
+            }
+        });
+    }
+
+    private List<Short> getTableIDs() {
+        List<Short> tableIds = new ArrayList<>();
+        tableIds.add(getTABLEID_PORTSECURITY());
+        tableIds.add(getTABLEID_INGRESS_NAT());
+        tableIds.add(getTABLEID_SOURCE_MAPPER());
+        tableIds.add(getTABLEID_DESTINATION_MAPPER());
+        tableIds.add(getTABLEID_POLICY_ENFORCER());
+        tableIds.add(getTABLEID_EGRESS_NAT());
+        tableIds.add(getTABLEID_EXTERNAL_MAPPER());
+        return tableIds;
+    }
+
+    private ListenableFuture<Void> deteleTableIfExists(final ReadWriteTransaction rwTx, final InstanceIdentifier<Table> tablePath){
+    return Futures.transform(rwTx.read(LogicalDatastoreType.CONFIGURATION, tablePath), new Function<Optional<Table>, Void>() {
+
+        @Override
+        public Void apply(Optional<Table> optTable) {
+            if(optTable.isPresent()){
+                rwTx.delete(LogicalDatastoreType.CONFIGURATION, tablePath);
+            }
+            return null;
+        }});
+    }
+
     // **************
     // SwitchListener
     // **************
 
-
     public short getTABLEID_PORTSECURITY() {
         return (short)(tableOffset+TABLEID_PORTSECURITY);
     }
@@ -203,6 +294,11 @@ public class PolicyManager
         return (short)(tableOffset+TABLEID_EXTERNAL_MAPPER);
     }
 
+
+    public TableId verifyMaxTableId(short tableOffset) {
+        return new TableId((short)(tableOffset+TABLEID_EXTERNAL_MAPPER));
+    }
+
     @Override
     public void switchReady(final NodeId nodeId) {
         scheduleUpdate();
index cd31c806e0cf0c50c2870a4dc0b56304ce06d9fd..6d6baccf2e5d90d4fe30a8191267febb09c6f3f7 100644 (file)
@@ -15,9 +15,10 @@ module ofoverlay-provider-impl {
     import opendaylight-md-sal-binding { prefix mdsal; revision-date 2013-10-28; }
     import ietf-yang-types { prefix "yang"; revision-date 2010-09-24; }
     import opendaylight-sal-binding-broker-impl { prefix sal-broker; revision-date 2013-10-28;}
+    import ofoverlay { prefix ofoverlay; revision-date 2014-05-28; }
 
     description
-        "This module contains the base YANG definitions for 
+        "This module contains the base YANG definitions for
           ofoverlay-provider impl implementation.";
 
     revision "2014-06-11" {
@@ -64,12 +65,7 @@ module ofoverlay-provider-impl {
                 }
             }
 
-            leaf gbp-ofoverlay-table-offset {
-                description 
-                    "Used to offset pipeline to start at offset+1. Table0 is required.
-                    This is to allow the enduser to configure where the GBP pipeline starts.";
-                type uint16;
-            }
+            uses ofoverlay:initial-values;
         }
     }
 }
index 47e1d578d26377d37f8b65eb521bb42b0f19a3a5..052120b0038e029e40bf6b493dd44d94c712f565 100644 (file)
@@ -59,6 +59,19 @@ module ofoverlay {
                 }
             }
         }
+
+        uses initial-values;
+    }
+
+    grouping initial-values {
+        description
+            "Initial value of table offset is set in config subsystem";
+        leaf gbp-ofoverlay-table-offset {
+            description
+                "Used to offset pipeline to start at offset+1. Table0 is required.
+                This is to allow the enduser to configure where the GBP pipeline starts.";
+            type uint8;
+        }
     }
 
     grouping endpoint-location {
index 274bcc8a6b1ae9ab02f6071cf2ce561d3103b73d..d22c727b004cca4c947d432966292184c266708f 100644 (file)
@@ -14,7 +14,6 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
 import org.junit.Before;
@@ -35,6 +34,7 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
 import com.google.common.base.Optional;
 import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
 
 public class OFOverlayRendererTest {
 
@@ -67,9 +67,8 @@ public class OFOverlayRendererTest {
 
         readTransaction = mock(ReadOnlyTransaction.class);
         when(dataProvider.newReadOnlyTransaction()).thenReturn(readTransaction);
-        future = mock(CheckedFuture.class);
+        future = Futures.immediateCheckedFuture(Optional.<OfOverlayConfig> absent());
         when(readTransaction.read(any(LogicalDatastoreType.class), any(InstanceIdentifier.class))).thenReturn(future);
-
         renderer = new OFOverlayRenderer(dataProvider, rpcRegistry, notificationService, tableOffset);
     }
 
@@ -82,13 +81,10 @@ public class OFOverlayRendererTest {
     @Test
     public void applyConfigTest() throws Exception {
         OfOverlayConfig config = mock(OfOverlayConfig.class);
-        Field field = OFOverlayRenderer.class.getDeclaredField("config");
-        field.setAccessible(true);
-        field.set(renderer, config);
-
-        Method method = OFOverlayRenderer.class.getDeclaredMethod("applyConfig");
+        when(config.getGbpOfoverlayTableOffset()).thenReturn(null);
+        Method method = OFOverlayRenderer.class.getDeclaredMethod("applyConfig",OfOverlayConfig.class);
         method.setAccessible(true);
-        method.invoke(renderer);
+        method.invoke(renderer, config);
 
         verify(config).getEncapsulationFormat();
         verify(config, times(2)).getLearningMode();