From: Tomas Cechvala Date: Wed, 15 Jul 2015 12:37:07 +0000 (+0200) Subject: Bug 3353 - added table offset to of-overlay-config X-Git-Tag: release/beryllium~117 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=5ba5f8bd1771dfd98eb7bce4e54948253584d216;p=groupbasedpolicy.git Bug 3353 - added table offset to of-overlay-config 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 --- diff --git a/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRenderer.java b/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRenderer.java index 7342dc564..139d09024 100644 --- a/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRenderer.java +++ b/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRenderer.java @@ -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 configIid = InstanceIdentifier.builder(OfOverlayConfig.class).build(); - private OfOverlayConfig config; ListenerRegistration 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 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, - DataObject> change) { - readConfig(); + public void onDataChanged(AsyncDataChangeEvent, DataObject> change) { + OfOverlayConfig config; + try { + for (Entry, 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, 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> dao = - dataBroker.newReadOnlyTransaction() - .read(LogicalDatastoreType.CONFIGURATION, configIid); - Futures.addCallback(dao, new FutureCallback>() { - @Override - public void onSuccess(final Optional 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 readConfig() { + final ReadOnlyTransaction rTx = dataBroker.newReadOnlyTransaction(); + Optional config = + DataStoreHelper.readFromDs(LogicalDatastoreType.CONFIGURATION, + configIid, + rTx); + rTx.close(); + return config; + } + + private ListenableFuture writeTableOffset(OfOverlayConfig ofc) { + WriteTransaction wTx = dataBroker.newWriteOnlyTransaction(); + wTx.merge(LogicalDatastoreType.CONFIGURATION, configIid, ofc, true); + return wTx.submit(); } - private void applyConfig() { + private ListenableFuture> applyConfig(@Nonnull OfOverlayConfig config) { + List> 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); } } diff --git a/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/PolicyManager.java b/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/PolicyManager.java index 60b7e9792..0e0a1d4e2 100755 --- a/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/PolicyManager.java +++ b/renderers/ofoverlay/src/main/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/PolicyManager.java @@ -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 flowPipeline; + private List 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 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 - to indicate that tables have been synced + */ + public ListenableFuture 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 tableIDs = getTableIDs(); + this.tableOffset = tableOffset; + return Futures.transform(removeUnusedTables(tableIDs), new Function() { + + @Override + public Void apply(Void tablesRemoved) { + flowPipeline = createFlowPipeline(); + scheduleUpdate(); + return null; + } + }); + } + + /** + * @param tableIDs - IDs of tables to delete + * @return ListenableFuture - which will be filled when clearing is done + */ + private ListenableFuture removeUnusedTables(final List tableIDs) { + List> checkList = new ArrayList<>(); + final ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction(); + for (Short tableId : tableIDs) { + for (NodeId nodeId : switchManager.getReadySwitches()) { + final InstanceIdentifier tablePath = FlowUtils.createTablePath(nodeId, tableId); + checkList.add(deteleTableIfExists(rwTx, tablePath)); + } + } + ListenableFuture> allAsListFuture = Futures.allAsList(checkList); + return Futures.transform(allAsListFuture, new AsyncFunction, Void>() { + + @Override + public ListenableFuture apply(List readyToSubmit) { + return rwTx.submit(); + } + }); + } + + private List getTableIDs() { + List 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 deteleTableIfExists(final ReadWriteTransaction rwTx, final InstanceIdentifier
tablePath){ + return Futures.transform(rwTx.read(LogicalDatastoreType.CONFIGURATION, tablePath), new Function, Void>() { + + @Override + public Void apply(Optional
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(); diff --git a/renderers/ofoverlay/src/main/yang/ofoverlay-provider-impl.yang b/renderers/ofoverlay/src/main/yang/ofoverlay-provider-impl.yang index cd31c806e..6d6baccf2 100644 --- a/renderers/ofoverlay/src/main/yang/ofoverlay-provider-impl.yang +++ b/renderers/ofoverlay/src/main/yang/ofoverlay-provider-impl.yang @@ -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; } } } diff --git a/renderers/ofoverlay/src/main/yang/ofoverlay.yang b/renderers/ofoverlay/src/main/yang/ofoverlay.yang index 47e1d578d..052120b00 100644 --- a/renderers/ofoverlay/src/main/yang/ofoverlay.yang +++ b/renderers/ofoverlay/src/main/yang/ofoverlay.yang @@ -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 { diff --git a/renderers/ofoverlay/src/test/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRendererTest.java b/renderers/ofoverlay/src/test/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRendererTest.java index 274bcc8a6..d22c727b0 100644 --- a/renderers/ofoverlay/src/test/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRendererTest.java +++ b/renderers/ofoverlay/src/test/java/org/opendaylight/groupbasedpolicy/renderer/ofoverlay/OFOverlayRendererTest.java @@ -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. 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();