From 085b076786d299c235ab5561c9fa678fd6b8d726 Mon Sep 17 00:00:00 2001 From: Moiz Raja Date: Fri, 22 May 2015 17:22:02 -0700 Subject: [PATCH] BUG 2970 : Create a PruningDataTreeModification The PruningDataTreeModification automatically tries to prune the passed in NormalizedNode if an IllegalArgumentException is thrown when doing a write or a merge on the DataTreeModification. *I know* that doing the pruning on IllegalArgumentException is flaky but that is the best I can do unless we can change the implementation in yangtools to throw a more specific exception when the data node does not map to a the schema context Change-Id: Iea0cea82eda8bd51c1f3f07b063404458798c348 Signed-off-by: Moiz Raja (cherry picked from commit 759e3c83955458b56d5d3e5a64521e0662b41004) --- .../transformer/NormalizedNodePruner.java | 18 ++- .../controller/cluster/datastore/Shard.java | 1 + .../cluster/datastore/ShardDataTree.java | 65 ++++++++- .../datastore/ShardDataTreeSnapshot.java | 41 ++++++ .../datastore/SimpleShardDataTreeCohort.java | 13 +- .../utils/PruningDataTreeModification.java | 136 +++++++++++++++++ .../datastore/ShardDataTreeSnapshotTest.java | 48 ++++++ .../cluster/datastore/ShardDataTreeTest.java | 100 +++++++++++++ .../PruningDataTreeModificationTest.java | 138 ++++++++++++++++++ .../datastore/model/SchemaContextHelper.java | 24 ++- 10 files changed, 563 insertions(+), 21 deletions(-) create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshot.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshotTest.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModificationTest.java diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java index 861f11305e..2c5bdf62df 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java @@ -38,11 +38,13 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter { private boolean sealed = false; public NormalizedNodePruner(SchemaContext schemaContext) { - validNamespaces = new HashSet<>(schemaContext.getModules().size()); - for(org.opendaylight.yangtools.yang.model.api.Module module : schemaContext.getModules()){ - validNamespaces.add(module.getNamespace()); - } + this(NormalizedNodePruner.namespaces(schemaContext)); } + + public NormalizedNodePruner(Set validNamespaces) { + this.validNamespaces = validNamespaces; + } + @Override public void leafNode(YangInstanceIdentifier.NodeIdentifier nodeIdentifier, Object o) throws IOException, IllegalArgumentException { @@ -267,5 +269,11 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter { return stack; } - + public static Set namespaces(SchemaContext schemaContext){ + Set namespaces = new HashSet<>(schemaContext.getModules().size()); + for(org.opendaylight.yangtools.yang.model.api.Module module : schemaContext.getModules()){ + namespaces.add(module.getNamespace()); + } + return namespaces; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java index 6da9fe947a..ab3120432f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java @@ -600,6 +600,7 @@ public class Shard extends RaftActor { @Override protected void onRecoveryComplete() { + store.recoveryDone(); //notify shard manager getContext().parent().tell(new ActorInitialized(), getSelf()); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java index 56c5eb65bf..7a7eceb876 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java @@ -9,12 +9,16 @@ package org.opendaylight.controller.cluster.datastore; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import java.net.URI; import java.util.AbstractMap.SimpleEntry; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import javax.annotation.concurrent.NotThreadSafe; +import org.opendaylight.controller.cluster.datastore.node.utils.transformer.NormalizedNodePruner; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener; @@ -42,7 +46,6 @@ import org.slf4j.LoggerFactory; * This class is not part of the API contract and is subject to change at any time. */ @NotThreadSafe -@VisibleForTesting public final class ShardDataTree extends ShardDataTreeTransactionParent { private static final Logger LOG = LoggerFactory.getLogger(ShardDataTree.class); private static final ShardDataTreeNotificationManager MANAGER = new ShardDataTreeNotificationManager(); @@ -50,12 +53,13 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent { private final ShardDataTreeChangePublisher treeChangePublisher = new ShardDataTreeChangePublisher(); private final ListenerTree listenerTree = ListenerTree.create(); private final TipProducingDataTree dataTree; + private Set validNamespaces; + private ShardDataTreeTransactionFactory transactionFactory = new RecoveryShardDataTreeTransactionFactory(); ShardDataTree(final SchemaContext schemaContext) { dataTree = InMemoryDataTreeFactory.getInstance().create(); - if (schemaContext != null) { - dataTree.setSchemaContext(schemaContext); - } + updateSchemaContext(schemaContext); + } TipProducingDataTree getDataTree() { @@ -63,7 +67,9 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent { } void updateSchemaContext(final SchemaContext schemaContext) { + Preconditions.checkNotNull(schemaContext); dataTree.setSchemaContext(schemaContext); + validNamespaces = NormalizedNodePruner.namespaces(schemaContext); } private ShardDataTreeTransactionChain ensureTransactionChain(final String chainId) { @@ -78,7 +84,7 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent { ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(final String txId, final String chainId) { if (Strings.isNullOrEmpty(chainId)) { - return new ReadOnlyShardDataTreeTransaction(txId, dataTree.takeSnapshot()); + return transactionFactory.newReadOnlyTransaction(txId, chainId); } return ensureTransactionChain(chainId).newReadOnlyTransaction(txId); @@ -86,7 +92,7 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent { ReadWriteShardDataTreeTransaction newReadWriteTransaction(final String txId, final String chainId) { if (Strings.isNullOrEmpty(chainId)) { - return new ReadWriteShardDataTreeTransaction(this, txId, dataTree.takeSnapshot().newModification()); + return transactionFactory.newReadWriteTransaction(txId, chainId); } return ensureTransactionChain(chainId).newReadWriteTransaction(txId); @@ -177,4 +183,51 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent { return new SimpleShardDataTreeCohort(this, snapshot); } + void recoveryDone(){ + transactionFactory = new RegularShardDataTreeTransactionFactory(); + } + + @VisibleForTesting + ShardDataTreeTransactionFactory getTransactionFactory(){ + return transactionFactory; + } + + @VisibleForTesting + static interface ShardDataTreeTransactionFactory { + ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(final String txId, final String chainId); + + ReadWriteShardDataTreeTransaction newReadWriteTransaction(final String txId, final String chainId); + } + + @VisibleForTesting + class RecoveryShardDataTreeTransactionFactory implements ShardDataTreeTransactionFactory { + + @Override + public ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(String txId, String chainId) { + return new ReadOnlyShardDataTreeTransaction(txId, + new ShardDataTreeSnapshot(dataTree.takeSnapshot(), validNamespaces)); + } + + @Override + public ReadWriteShardDataTreeTransaction newReadWriteTransaction(String txId, String chainId) { + return new ReadWriteShardDataTreeTransaction(ShardDataTree.this, txId, + new ShardDataTreeSnapshot(dataTree.takeSnapshot(), validNamespaces).newModification()); + } + } + + @VisibleForTesting + class RegularShardDataTreeTransactionFactory implements ShardDataTreeTransactionFactory { + + @Override + public ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(String txId, String chainId) { + return new ReadOnlyShardDataTreeTransaction(txId, dataTree.takeSnapshot()); + + } + + @Override + public ReadWriteShardDataTreeTransaction newReadWriteTransaction(String txId, String chainId) { + return new ReadWriteShardDataTreeTransaction(ShardDataTree.this, txId, dataTree.takeSnapshot() + .newModification()); + } + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshot.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshot.java new file mode 100644 index 0000000000..b7ec3a712e --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshot.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.cluster.datastore; + +import com.google.common.base.Optional; +import java.net.URI; +import java.util.Set; +import org.opendaylight.controller.cluster.datastore.utils.PruningDataTreeModification; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; + +class ShardDataTreeSnapshot implements DataTreeSnapshot { + + private final DataTreeSnapshot dataTreeSnapshot; + private final Set validNamespaces; + + public ShardDataTreeSnapshot(DataTreeSnapshot dataTreeSnapshot, Set validNamespaces) { + this.dataTreeSnapshot = dataTreeSnapshot; + this.validNamespaces = validNamespaces; + } + + @Override + public Optional> readNode(YangInstanceIdentifier yangInstanceIdentifier) { + return this.dataTreeSnapshot.readNode(yangInstanceIdentifier); + } + + @Override + public DataTreeModification newModification() { + return new PruningDataTreeModification(this.dataTreeSnapshot.newModification(), validNamespaces); + } + + +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/SimpleShardDataTreeCohort.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/SimpleShardDataTreeCohort.java index 9f22ce8a73..bfbfb138a1 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/SimpleShardDataTreeCohort.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/SimpleShardDataTreeCohort.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.datastore; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.controller.cluster.datastore.utils.PruningDataTreeModification; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; import org.slf4j.Logger; @@ -36,7 +37,7 @@ final class SimpleShardDataTreeCohort extends ShardDataTreeCohort { @Override public ListenableFuture canCommit() { try { - dataTree.getDataTree().validate(transaction); + dataTree.getDataTree().validate(dataTreeModification()); LOG.debug("Transaction {} validated", transaction); return TRUE_FUTURE; } catch (Exception e) { @@ -47,7 +48,7 @@ final class SimpleShardDataTreeCohort extends ShardDataTreeCohort { @Override public ListenableFuture preCommit() { try { - candidate = dataTree.getDataTree().prepare(transaction); + candidate = dataTree.getDataTree().prepare(dataTreeModification()); /* * FIXME: this is the place where we should be interacting with persistence, specifically by invoking * persist on the candidate (which gives us a Future). @@ -60,6 +61,14 @@ final class SimpleShardDataTreeCohort extends ShardDataTreeCohort { } } + private DataTreeModification dataTreeModification() { + DataTreeModification dataTreeModification = transaction; + if(transaction instanceof PruningDataTreeModification){ + dataTreeModification = ((PruningDataTreeModification) transaction).getDelegate(); + } + return dataTreeModification; + } + @Override public ListenableFuture abort() { // No-op, really diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java new file mode 100644 index 0000000000..2427223293 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.cluster.datastore.utils; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import java.io.IOException; +import java.net.URI; +import java.util.Set; +import org.opendaylight.controller.cluster.datastore.node.utils.transformer.NormalizedNodePruner; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The PruningDataTreeModification first removes all entries from the data which do not belong in the schemaContext + * before delegating it to the actual DataTreeModification + */ +public class PruningDataTreeModification implements DataTreeModification { + + private static final Logger LOG = LoggerFactory.getLogger(PruningDataTreeModification.class); + private final DataTreeModification delegate; + private final Set validNamespaces; + + public PruningDataTreeModification(DataTreeModification delegate, Set validNamespaces) { + this.delegate = delegate; + this.validNamespaces = validNamespaces; + } + + @Override + public void delete(YangInstanceIdentifier yangInstanceIdentifier) { + try { + delegate.delete(yangInstanceIdentifier); + } catch(IllegalArgumentException e){ + LOG.warn("Node at path : {} does not exist ignoring delete", yangInstanceIdentifier); + } + } + + @Override + public void merge(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { + try { + delegate.merge(yangInstanceIdentifier, normalizedNode); + } catch (IllegalArgumentException e){ + if(!isValidYangInstanceIdentifier(yangInstanceIdentifier)){ + LOG.warn("Invalid node identifier {} ignoring merge", yangInstanceIdentifier); + return; + } + + LOG.warn("Node at path : {} was pruned during merge", yangInstanceIdentifier); + + NormalizedNode pruned = pruneNormalizedNode(normalizedNode); + + if(pruned != null) { + delegate.merge(yangInstanceIdentifier, pruned); + } + } + + } + + @Override + public void write(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode normalizedNode) { + try { + delegate.write(yangInstanceIdentifier, normalizedNode); + } catch (IllegalArgumentException e){ + if(!isValidYangInstanceIdentifier(yangInstanceIdentifier)){ + LOG.warn("Invalid node identifier {} ignoring write", yangInstanceIdentifier); + return; + } + + LOG.warn("Node at path : {} was pruned during write", yangInstanceIdentifier); + + NormalizedNode pruned = pruneNormalizedNode(normalizedNode); + + if(pruned != null) { + delegate.write(yangInstanceIdentifier, pruned); + } + } + } + + @Override + public void ready() { + delegate.ready(); + } + + @Override + public void applyToCursor(DataTreeModificationCursor dataTreeModificationCursor) { + delegate.applyToCursor(dataTreeModificationCursor); + } + + @Override + public Optional> readNode(YangInstanceIdentifier yangInstanceIdentifier) { + return delegate.readNode(yangInstanceIdentifier); + } + + @Override + public DataTreeModification newModification() { + return new PruningDataTreeModification(delegate.newModification(), validNamespaces); + } + + @VisibleForTesting + NormalizedNode pruneNormalizedNode(NormalizedNode input){ + NormalizedNodePruner pruner = new NormalizedNodePruner(validNamespaces); + try { + NormalizedNodeWriter.forStreamWriter(pruner).write(input); + } catch (IOException ioe) { + LOG.error("Unexpected IOException when pruning normalizedNode", ioe); + } + + return pruner.normalizedNode(); + } + + public DataTreeModification getDelegate(){ + return delegate; + } + + private boolean isValidYangInstanceIdentifier(YangInstanceIdentifier instanceIdentifier){ + for(YangInstanceIdentifier.PathArgument pathArgument : instanceIdentifier.getPathArguments()){ + if(!validNamespaces.contains(pathArgument.getNodeType().getNamespace())){ + return false; + } + } + + return true; + } + +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshotTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshotTest.java new file mode 100644 index 0000000000..dc07265d7f --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshotTest.java @@ -0,0 +1,48 @@ +package org.opendaylight.controller.cluster.datastore; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import java.net.URI; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.cluster.datastore.utils.PruningDataTreeModification; +import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; + +public class ShardDataTreeSnapshotTest { + + @Mock + DataTreeSnapshot dataTreeSnapshot; + + @Mock + Set validNamespaces; + + @Before + public void setUp(){ + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNewModification(){ + ShardDataTreeSnapshot snapshot1 + = new ShardDataTreeSnapshot(dataTreeSnapshot, validNamespaces); + + DataTreeModification dataTreeModification1 = snapshot1.newModification(); + + assertTrue(dataTreeModification1 instanceof PruningDataTreeModification); + } + + @Test + public void testReadNode(){ + ShardDataTreeSnapshot snapshot + = new ShardDataTreeSnapshot(dataTreeSnapshot, validNamespaces); + + snapshot.readNode(CarsModel.BASE_PATH); + + verify(dataTreeSnapshot).readNode(CarsModel.BASE_PATH); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java new file mode 100644 index 0000000000..ae3ae6f792 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java @@ -0,0 +1,100 @@ +package org.opendaylight.controller.cluster.datastore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import com.google.common.base.Optional; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; +import org.opendaylight.controller.md.cluster.datastore.model.PeopleModel; +import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class ShardDataTreeTest { + + @Test + public void testWrite() throws ExecutionException, InterruptedException { + + SchemaContext schemaContext = SchemaContextHelper.full(); + + modify(new ShardDataTree(schemaContext), false, true, true); + } + + @Test + public void testWriteWithMissingSchema() throws ExecutionException, InterruptedException { + + SchemaContext schemaContext = SchemaContextHelper.select(SchemaContextHelper.ODL_DATASTORE_TEST_YANG, SchemaContextHelper.PEOPLE_YANG); + + modify(new ShardDataTree(schemaContext), false, false, true); + } + + @Test + public void testMerge() throws ExecutionException, InterruptedException { + + SchemaContext schemaContext = SchemaContextHelper.full(); + + modify(new ShardDataTree(schemaContext), true, true, true); + } + + @Test + public void testMergeWithMissingSchema() throws ExecutionException, InterruptedException { + + SchemaContext schemaContext = SchemaContextHelper.select(SchemaContextHelper.ODL_DATASTORE_TEST_YANG, SchemaContextHelper.PEOPLE_YANG); + + modify(new ShardDataTree(schemaContext), true, false, true); + } + + private void modify(ShardDataTree shardDataTree, boolean merge, boolean expectedCarsPresent, boolean expectedPeoplePresent) throws ExecutionException, InterruptedException { + ReadWriteShardDataTreeTransaction transaction = shardDataTree.newReadWriteTransaction("txn-1", null); + + DataTreeModification snapshot = transaction.getSnapshot(); + + assertNotNull(snapshot); + + if(merge){ + snapshot.merge(CarsModel.BASE_PATH, CarsModel.create()); + snapshot.merge(PeopleModel.BASE_PATH, PeopleModel.create()); + } else { + snapshot.write(CarsModel.BASE_PATH, CarsModel.create()); + snapshot.write(PeopleModel.BASE_PATH, PeopleModel.create()); + } + + ShardDataTreeCohort cohort = shardDataTree.finishTransaction(transaction); + + cohort.preCommit().get(); + cohort.commit().get(); + + + ReadOnlyShardDataTreeTransaction readOnlyShardDataTreeTransaction = shardDataTree.newReadOnlyTransaction("txn-2", null); + + DataTreeSnapshot snapshot1 = readOnlyShardDataTreeTransaction.getSnapshot(); + + Optional> optional = snapshot1.readNode(CarsModel.BASE_PATH); + + assertEquals(expectedCarsPresent, optional.isPresent()); + + Optional> optional1 = snapshot1.readNode(PeopleModel.BASE_PATH); + + assertEquals(expectedPeoplePresent, optional1.isPresent()); + + } + + @Test(expected = IllegalArgumentException.class) + public void testAfterRecoveryDone() throws ExecutionException, InterruptedException { + SchemaContext schemaContext = SchemaContextHelper.select(SchemaContextHelper.ODL_DATASTORE_TEST_YANG, SchemaContextHelper.PEOPLE_YANG); + ShardDataTree shardDataTree = new ShardDataTree(schemaContext); + assertTrue("transaction factory must be the recovery transaction factory", + shardDataTree.getTransactionFactory() instanceof ShardDataTree.RecoveryShardDataTreeTransactionFactory); + shardDataTree.recoveryDone(); + assertTrue("transaction factory must be the regular transaction factory", + shardDataTree.getTransactionFactory() instanceof ShardDataTree.RegularShardDataTreeTransactionFactory); + + modify(shardDataTree, true, false, true); + + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModificationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModificationTest.java new file mode 100644 index 0000000000..b5e6caee9a --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModificationTest.java @@ -0,0 +1,138 @@ +package org.opendaylight.controller.cluster.datastore.utils; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import java.net.URI; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModificationCursor; + +public class PruningDataTreeModificationTest { + + @Mock + DataTreeModification delegate; + + @Mock + Set validNamespaces; + + @Mock + NormalizedNode prunedNormalizedNode; + + PruningDataTreeModification pruningDataTreeModification; + + @Before + public void setUp(){ + MockitoAnnotations.initMocks(this); + pruningDataTreeModification = new PruningDataTreeModification(delegate, validNamespaces) { + @Override + NormalizedNode pruneNormalizedNode(NormalizedNode input) { + return prunedNormalizedNode; + } + }; + } + + @Test + public void testDelete(){ + pruningDataTreeModification.delete(CarsModel.BASE_PATH); + + verify(delegate).delete(CarsModel.BASE_PATH); + } + + @Test + public void testDeleteOnException(){ + YangInstanceIdentifier path = CarsModel.BASE_PATH; + doThrow(IllegalArgumentException.class).when(delegate).delete(path); + + pruningDataTreeModification.delete(path); + + verify(delegate, times(1)).delete(path); + } + + + @Test + public void testMerge(){ + NormalizedNode normalizedNode = CarsModel.create(); + YangInstanceIdentifier path = CarsModel.BASE_PATH; + pruningDataTreeModification.merge(path, normalizedNode); + + verify(delegate, times(1)).merge(path, normalizedNode); + } + + @Test + public void testMergeOnException(){ + NormalizedNode normalizedNode = CarsModel.create(); + YangInstanceIdentifier path = CarsModel.BASE_PATH; + + doThrow(IllegalArgumentException.class).when(delegate).merge(path, normalizedNode); + doReturn(true).when(validNamespaces).contains(any(YangInstanceIdentifier.PathArgument.class)); + + pruningDataTreeModification.merge(path, normalizedNode); + + verify(delegate, times(1)).merge(path, normalizedNode); + verify(delegate, times(1)).merge(path, prunedNormalizedNode); + } + + @Test + public void testWrite(){ + NormalizedNode normalizedNode = CarsModel.create(); + YangInstanceIdentifier path = CarsModel.BASE_PATH; + pruningDataTreeModification.write(path, normalizedNode); + + verify(delegate, times(1)).write(path, normalizedNode); + } + + @Test + public void testWriteOnException(){ + NormalizedNode normalizedNode = CarsModel.create(); + YangInstanceIdentifier path = CarsModel.BASE_PATH; + + doThrow(IllegalArgumentException.class).when(delegate).write(path, normalizedNode); + doReturn(true).when(validNamespaces).contains(any(YangInstanceIdentifier.PathArgument.class)); + + pruningDataTreeModification.write(path, normalizedNode); + + verify(delegate, times(1)).write(path, normalizedNode); + verify(delegate, times(1)).write(path, prunedNormalizedNode); + } + + @Test + public void testReady(){ + pruningDataTreeModification.ready(); + + verify(delegate).ready(); + } + + @Test + public void testApplyToCursor(){ + DataTreeModificationCursor dataTreeModificationCursor = mock(DataTreeModificationCursor.class); + pruningDataTreeModification.applyToCursor(dataTreeModificationCursor); + + verify(delegate).applyToCursor(dataTreeModificationCursor); + } + + @Test + public void testReadNode(){ + pruningDataTreeModification.readNode(CarsModel.BASE_PATH); + + verify(delegate).readNode(CarsModel.BASE_PATH); + } + + @Test + public void testNewModification(){ + DataTreeModification dataTreeModification = pruningDataTreeModification.newModification(); + + assertTrue("new modification not of type PruningDataTreeModification", dataTreeModification instanceof PruningDataTreeModification); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SchemaContextHelper.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SchemaContextHelper.java index 3395738846..d09e4b9690 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SchemaContextHelper.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SchemaContextHelper.java @@ -8,30 +8,38 @@ package org.opendaylight.controller.md.cluster.datastore.model; -import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; - import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; public class SchemaContextHelper { + public static final String ODL_DATASTORE_TEST_YANG = "/odl-datastore-test.yang"; + public static final String PEOPLE_YANG = "/people.yang"; + public static final String CARS_YANG = "/cars.yang"; + public static InputStream getInputStream(final String yangFileName) { return TestModel.class.getResourceAsStream(yangFileName); } public static SchemaContext full(){ + return select(ODL_DATASTORE_TEST_YANG, PEOPLE_YANG, CARS_YANG); + } + + public static SchemaContext select(String... schemaFiles){ YangParserImpl parser = new YangParserImpl(); List streams = new ArrayList<>(); - streams.add(getInputStream("/odl-datastore-test.yang")); - streams.add(getInputStream("/people.yang")); - streams.add(getInputStream("/cars.yang")); + + for(String schemaFile : schemaFiles){ + streams.add(getInputStream(schemaFile)); + } Set modules = parser.parseYangModelsFromStreams(streams); return parser.resolveSchemaContext(modules); - } + } -- 2.36.6