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>
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;
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;
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.
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());
+ }
}
// *************
// ******************
@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);
}
}
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
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
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();
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)
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);
}
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();
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" {
}
}
- 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;
}
}
}
}
}
}
+
+ 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 {
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;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
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);
}
@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();