BUG 2970 : Create a PruningDataTreeModification 84/21384/2
authorMoiz Raja <moraja@cisco.com>
Sat, 23 May 2015 00:22:02 +0000 (17:22 -0700)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 29 May 2015 06:07:11 +0000 (06:07 +0000)
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 <moraja@cisco.com>
(cherry picked from commit 759e3c83955458b56d5d3e5a64521e0662b41004)

opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshot.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/SimpleShardDataTreeCohort.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeSnapshotTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModificationTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SchemaContextHelper.java

index 861f11305ef4798e766b0f5518ea6e5cc69f19a5..2c5bdf62df84468d728918902f313b66712e245b 100644 (file)
@@ -38,11 +38,13 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
     private boolean sealed = false;
 
     public NormalizedNodePruner(SchemaContext schemaContext) {
     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<URI> validNamespaces) {
+        this.validNamespaces = validNamespaces;
+    }
+
     @Override
     public void leafNode(YangInstanceIdentifier.NodeIdentifier nodeIdentifier, Object o) throws IOException, IllegalArgumentException {
 
     @Override
     public void leafNode(YangInstanceIdentifier.NodeIdentifier nodeIdentifier, Object o) throws IOException, IllegalArgumentException {
 
@@ -267,5 +269,11 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
         return stack;
     }
 
         return stack;
     }
 
-
+    public static Set<URI> namespaces(SchemaContext schemaContext){
+        Set<URI> namespaces = new HashSet<>(schemaContext.getModules().size());
+        for(org.opendaylight.yangtools.yang.model.api.Module module : schemaContext.getModules()){
+            namespaces.add(module.getNamespace());
+        }
+        return namespaces;
+    }
 }
 }
index 6da9fe947a0651e5d96981b87a274bde814f902a..ab3120432f591827839a4c44c0e7c2254a6c7b63 100644 (file)
@@ -600,6 +600,7 @@ public class Shard extends RaftActor {
 
     @Override
     protected void onRecoveryComplete() {
 
     @Override
     protected void onRecoveryComplete() {
+        store.recoveryDone();
         //notify shard manager
         getContext().parent().tell(new ActorInitialized(), getSelf());
 
         //notify shard manager
         getContext().parent().tell(new ActorInitialized(), getSelf());
 
index 56c5eb65bf087c3a0d979de2ecaff7b0a65467a2..7a7eceb87628b7c8636f26917f8cefc8ecb5abe2 100644 (file)
@@ -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.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 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.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 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;
 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
  * 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();
 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 final ShardDataTreeChangePublisher treeChangePublisher = new ShardDataTreeChangePublisher();
     private final ListenerTree listenerTree = ListenerTree.create();
     private final TipProducingDataTree dataTree;
+    private Set<URI> validNamespaces;
+    private ShardDataTreeTransactionFactory transactionFactory = new RecoveryShardDataTreeTransactionFactory();
 
     ShardDataTree(final SchemaContext schemaContext) {
         dataTree = InMemoryDataTreeFactory.getInstance().create();
 
     ShardDataTree(final SchemaContext schemaContext) {
         dataTree = InMemoryDataTreeFactory.getInstance().create();
-        if (schemaContext != null) {
-            dataTree.setSchemaContext(schemaContext);
-        }
+        updateSchemaContext(schemaContext);
+
     }
 
     TipProducingDataTree getDataTree() {
     }
 
     TipProducingDataTree getDataTree() {
@@ -63,7 +67,9 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent {
     }
 
     void updateSchemaContext(final SchemaContext schemaContext) {
     }
 
     void updateSchemaContext(final SchemaContext schemaContext) {
+        Preconditions.checkNotNull(schemaContext);
         dataTree.setSchemaContext(schemaContext);
         dataTree.setSchemaContext(schemaContext);
+        validNamespaces = NormalizedNodePruner.namespaces(schemaContext);
     }
 
     private ShardDataTreeTransactionChain ensureTransactionChain(final String chainId) {
     }
 
     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)) {
 
     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);
         }
 
         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)) {
 
     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);
         }
 
         return ensureTransactionChain(chainId).newReadWriteTransaction(txId);
@@ -177,4 +183,51 @@ public final class ShardDataTree extends ShardDataTreeTransactionParent {
         return new SimpleShardDataTreeCohort(this, snapshot);
     }
 
         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 (file)
index 0000000..b7ec3a7
--- /dev/null
@@ -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<URI> validNamespaces;
+
+    public ShardDataTreeSnapshot(DataTreeSnapshot dataTreeSnapshot, Set<URI> validNamespaces) {
+        this.dataTreeSnapshot = dataTreeSnapshot;
+        this.validNamespaces = validNamespaces;
+    }
+
+    @Override
+    public Optional<NormalizedNode<?, ?>> readNode(YangInstanceIdentifier yangInstanceIdentifier) {
+        return this.dataTreeSnapshot.readNode(yangInstanceIdentifier);
+    }
+
+    @Override
+    public DataTreeModification newModification() {
+        return new PruningDataTreeModification(this.dataTreeSnapshot.newModification(), validNamespaces);
+    }
+
+
+}
index 9f22ce8a73e2380625f2f03c0e1ab5e7b35a2c29..bfbfb138a1791ea8dc577e095b0dd38fa5164d14 100644 (file)
@@ -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 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;
 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<Boolean> canCommit() {
         try {
     @Override
     public ListenableFuture<Boolean> canCommit() {
         try {
-            dataTree.getDataTree().validate(transaction);
+            dataTree.getDataTree().validate(dataTreeModification());
             LOG.debug("Transaction {} validated", transaction);
             return TRUE_FUTURE;
         } catch (Exception e) {
             LOG.debug("Transaction {} validated", transaction);
             return TRUE_FUTURE;
         } catch (Exception e) {
@@ -47,7 +48,7 @@ final class SimpleShardDataTreeCohort extends ShardDataTreeCohort {
     @Override
     public ListenableFuture<Void> preCommit() {
         try {
     @Override
     public ListenableFuture<Void> 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).
             /*
              * 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<Void> abort() {
         // No-op, really
     @Override
     public ListenableFuture<Void> 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 (file)
index 0000000..2427223
--- /dev/null
@@ -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<URI> validNamespaces;
+
+    public PruningDataTreeModification(DataTreeModification delegate, Set<URI> 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<NormalizedNode<?, ?>> 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 (file)
index 0000000..dc07265
--- /dev/null
@@ -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<URI> 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 (file)
index 0000000..ae3ae6f
--- /dev/null
@@ -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<NormalizedNode<?, ?>> optional = snapshot1.readNode(CarsModel.BASE_PATH);
+
+        assertEquals(expectedCarsPresent, optional.isPresent());
+
+        Optional<NormalizedNode<?, ?>> 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 (file)
index 0000000..b5e6cae
--- /dev/null
@@ -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<URI> 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
index 33957388463fd6225feb57ce1135364ee90d2551..d09e4b96909e176ad9afd18613a012e2536d8d6c 100644 (file)
@@ -8,30 +8,38 @@
 
 package org.opendaylight.controller.md.cluster.datastore.model;
 
 
 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 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 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(){
     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<InputStream> streams = new ArrayList<>();
         YangParserImpl parser = new YangParserImpl();
         List<InputStream> 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<Module> modules = parser.parseYangModelsFromStreams(streams);
         return parser.resolveSchemaContext(modules);
 
         Set<Module> modules = parser.parseYangModelsFromStreams(streams);
         return parser.resolveSchemaContext(modules);
-
     }
     }
+
 }
 }