BUG 2970 : Recovery fails with SchemaValidationException when removing modules 05/22505/1
authorMoiz Raja <moraja@cisco.com>
Fri, 12 Jun 2015 01:29:07 +0000 (18:29 -0700)
committerTom Pantelis <tpanteli@brocade.com>
Fri, 12 Jun 2015 21:28:56 +0000 (21:28 +0000)
My prior fixes for bug 2970 did not address the recovery problem when a module is
removed but it's data is still present in the persisted files. This patch attempts
to address that.

There seems to be no schema validation when you write a normalized node at / so
for when writing or merging at / the pruning is done first before attempting a
write on InMemoryDataTreeModification

Also removed all unneccessary code.

Change-Id: Id2793330441606c62dfd903cdd698c2f86b14c1b
Signed-off-by: Moiz Raja <moraja@cisco.com>
(cherry picked from commit 9a06d5de97157fdd497946f7c467c928344e0e67)

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/PruningShardDataTreeSnapshot.java [deleted file]
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/ShardRecoveryCoordinator.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/PruningDataTreeModification.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/PruningShardDataTreeSnapshotTest.java [deleted file]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardDataTreeTest.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinatorTest.java [new file with mode: 0644]

index 2c5bdf62df84468d728918902f313b66712e245b..6877a54a8a83a0d47f4375f529f689a1dc954ed5 100644 (file)
@@ -32,6 +32,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
  */
 public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
 
+    public static final URI BASE_NAMESPACE = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
     private final SimpleStack<NormalizedNodeBuilderWrapper> stack = new SimpleStack<>();
     private NormalizedNode<?,?> normalizedNode;
     private final Set<URI> validNamespaces;
@@ -271,6 +272,7 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
 
     public static Set<URI> namespaces(SchemaContext schemaContext){
         Set<URI> namespaces = new HashSet<>(schemaContext.getModules().size());
+        namespaces.add(BASE_NAMESPACE);
         for(org.opendaylight.yangtools.yang.model.api.Module module : schemaContext.getModules()){
             namespaces.add(module.getNamespace());
         }
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/PruningShardDataTreeSnapshot.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/PruningShardDataTreeSnapshot.java
deleted file mode 100644 (file)
index 2a0b976..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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;
-
-/**
- * The PruningShardDataTreeSnapshot returns a PruningDataTreeModification when a newModification is created
- */
-class PruningShardDataTreeSnapshot implements DataTreeSnapshot {
-
-    private final DataTreeSnapshot dataTreeSnapshot;
-    private final Set<URI> validNamespaces;
-
-    public PruningShardDataTreeSnapshot(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 0b4abe98a10a9ecac5be58a036f0653bcece5e8c..7ca9ca99284348be0f99b775076c6313788ebcf4 100644 (file)
@@ -156,6 +156,8 @@ public class Shard extends RaftActor {
                         Dispatchers.DispatcherType.Transaction), self(), getContext(), shardMBean);
 
         snapshotCohort = new ShardSnapshotCohort(transactionActorFactory, store, LOG, this.name);
+
+
     }
 
     private void setTransactionCommitTimeout() {
@@ -598,12 +600,11 @@ public class Shard extends RaftActor {
     @Override
     @Nonnull
     protected RaftActorRecoveryCohort getRaftActorRecoveryCohort() {
-        return new ShardRecoveryCoordinator(store, persistenceId(), LOG);
+        return new ShardRecoveryCoordinator(store, store.getSchemaContext(), persistenceId(), LOG);
     }
 
     @Override
     protected void onRecoveryComplete() {
-        store.recoveryDone();
         //notify shard manager
         getContext().parent().tell(new ActorInitialized(), getSelf());
 
index e852cfe420416bbfa38049f0938bc954472b8584..fd42740784a3334a0670bec9fcedf90951d4f4ad 100644 (file)
@@ -7,18 +7,14 @@
  */
 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;
@@ -53,8 +49,7 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
     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();
+    private SchemaContext schemaContext;
 
     ShardDataTree(final SchemaContext schemaContext) {
         dataTree = InMemoryDataTreeFactory.getInstance().create();
@@ -66,10 +61,14 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
         return dataTree;
     }
 
+    SchemaContext getSchemaContext() {
+        return schemaContext;
+    }
+
     void updateSchemaContext(final SchemaContext schemaContext) {
         Preconditions.checkNotNull(schemaContext);
+        this.schemaContext = schemaContext;
         dataTree.setSchemaContext(schemaContext);
-        validNamespaces = NormalizedNodePruner.namespaces(schemaContext);
     }
 
     private ShardDataTreeTransactionChain ensureTransactionChain(final String chainId) {
@@ -84,7 +83,7 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
 
     ReadOnlyShardDataTreeTransaction newReadOnlyTransaction(final String txId, final String chainId) {
         if (Strings.isNullOrEmpty(chainId)) {
-            return transactionFactory.newReadOnlyTransaction(txId, chainId);
+            return new ReadOnlyShardDataTreeTransaction(txId, dataTree.takeSnapshot());
         }
 
         return ensureTransactionChain(chainId).newReadOnlyTransaction(txId);
@@ -92,7 +91,8 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
 
     ReadWriteShardDataTreeTransaction newReadWriteTransaction(final String txId, final String chainId) {
         if (Strings.isNullOrEmpty(chainId)) {
-            return transactionFactory.newReadWriteTransaction(txId, chainId);
+            return new ReadWriteShardDataTreeTransaction(ShardDataTree.this, txId, dataTree.takeSnapshot()
+                    .newModification());
         }
 
         return ensureTransactionChain(chainId).newReadWriteTransaction(txId);
@@ -182,52 +182,4 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
         snapshot.ready();
         return new SimpleShardDataTreeCohort(this, snapshot, transaction.getId());
     }
-
-    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 PruningShardDataTreeSnapshot(dataTree.takeSnapshot(), validNamespaces));
-        }
-
-        @Override
-        public ReadWriteShardDataTreeTransaction newReadWriteTransaction(String txId, String chainId) {
-            return new ReadWriteShardDataTreeTransaction(ShardDataTree.this, txId,
-                    new PruningShardDataTreeSnapshot(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());
-        }
-    }
 }
index 797641978d2cd47cc7eed57c12e77e2334cb943c..5c9f0d11c6584378e4c3a9ff4d46fabe597a80ef 100644 (file)
@@ -7,9 +7,14 @@
  */
 package org.opendaylight.controller.cluster.datastore;
 
+import com.google.common.base.Preconditions;
 import java.io.IOException;
+import java.net.URI;
+import java.util.Set;
 import org.opendaylight.controller.cluster.datastore.modification.ModificationPayload;
 import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification;
+import org.opendaylight.controller.cluster.datastore.node.utils.transformer.NormalizedNodePruner;
+import org.opendaylight.controller.cluster.datastore.utils.PruningDataTreeModification;
 import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils;
 import org.opendaylight.controller.cluster.raft.RaftActorRecoveryCohort;
 import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationByteStringPayload;
@@ -21,6 +26,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 
 /**
@@ -37,24 +43,28 @@ class ShardRecoveryCoordinator implements RaftActorRecoveryCohort {
     private final DataTree store;
     private final String shardName;
     private final Logger log;
-    private DataTreeModification transaction;
+    private final Set<URI> validNamespaces;
+    private PruningDataTreeModification transaction;
     private int size;
 
-    ShardRecoveryCoordinator(ShardDataTree store, String shardName, Logger log) {
+    ShardRecoveryCoordinator(ShardDataTree store, SchemaContext schemaContext, String shardName, Logger log) {
         this.store = store.getDataTree();
         this.shardName = shardName;
         this.log = log;
+        this.validNamespaces = NormalizedNodePruner.namespaces(schemaContext);
     }
 
     @Override
     public void startLogRecoveryBatch(int maxBatchSize) {
         log.debug("{}: starting log recovery batch with max size {}", shardName, maxBatchSize);
-        transaction = store.takeSnapshot().newModification();
+        transaction = new PruningDataTreeModification(store.takeSnapshot().newModification(), validNamespaces);
         size = 0;
     }
 
     @Override
     public void appendRecoveredLogEntry(Payload payload) {
+        Preconditions.checkState(transaction != null, "call startLogRecovery before calling appendRecoveredLogEntry");
+
         try {
             if (payload instanceof DataTreeCandidatePayload) {
                 DataTreeCandidates.applyToModification(transaction, ((DataTreeCandidatePayload)payload).getCandidate());
@@ -79,10 +89,11 @@ class ShardRecoveryCoordinator implements RaftActorRecoveryCohort {
         }
     }
 
-    private void commitTransaction(DataTreeModification tx) throws DataValidationFailedException {
-        tx.ready();
-        store.validate(tx);
-        store.commit(store.prepare(tx));
+    private void commitTransaction(PruningDataTreeModification tx) throws DataValidationFailedException {
+        DataTreeModification delegate = tx.getDelegate();
+        delegate.ready();
+        store.validate(delegate);
+        store.commit(store.prepare(delegate));
     }
 
     /**
@@ -90,6 +101,8 @@ class ShardRecoveryCoordinator implements RaftActorRecoveryCohort {
      */
     @Override
     public void applyCurrentLogRecoveryBatch() {
+        Preconditions.checkState(transaction != null, "call startLogRecovery before calling applyCurrentLogRecoveryBatch");
+
         log.debug("{}: Applying current log recovery batch with size {}", shardName, size);
         try {
             commitTransaction(transaction);
@@ -109,7 +122,7 @@ class ShardRecoveryCoordinator implements RaftActorRecoveryCohort {
         log.debug("{}: Applying recovered snapshot", shardName);
 
         final NormalizedNode<?, ?> node = SerializationUtils.deserializeNormalizedNode(snapshotBytes);
-        final DataTreeModification tx = store.takeSnapshot().newModification();
+        final PruningDataTreeModification tx = new PruningDataTreeModification(store.takeSnapshot().newModification(), validNamespaces);
         tx.write(ROOT, node);
         try {
             commitTransaction(tx);
index cc976b381f98b7c76b9811f28aa6dbf5e1cfbe04..d59e5643458b5184ced5ff35eab2baa4dbca4fb7 100644 (file)
@@ -50,41 +50,57 @@ public class PruningDataTreeModification implements DataTreeModification {
     @Override
     public void merge(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode<?, ?> normalizedNode) {
         try {
-            delegate.merge(yangInstanceIdentifier, normalizedNode);
+            if(YangInstanceIdentifier.EMPTY.equals(yangInstanceIdentifier)){
+                pruneAndMergeNode(yangInstanceIdentifier, normalizedNode);
+            } else {
+                delegate.merge(yangInstanceIdentifier, normalizedNode);
+            }
         } catch (SchemaValidationFailedException e){
             if(!isValidYangInstanceIdentifier(yangInstanceIdentifier)){
                 LOG.warn("Invalid node identifier {} ignoring merge", yangInstanceIdentifier);
                 return;
             }
 
-            LOG.warn("Node at path : {} was pruned during merge", yangInstanceIdentifier);
+            pruneAndMergeNode(yangInstanceIdentifier, normalizedNode);
+        }
+
+    }
 
-            NormalizedNode<?,?> pruned = pruneNormalizedNode(normalizedNode);
+    private void pruneAndMergeNode(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode<?, ?> normalizedNode) {
+        LOG.warn("Node at path : {} was pruned during merge", yangInstanceIdentifier);
 
-            if(pruned != null) {
-                delegate.merge(yangInstanceIdentifier, pruned);
-            }
-        }
+        NormalizedNode<?,?> pruned = pruneNormalizedNode(normalizedNode);
 
+        if(pruned != null) {
+            delegate.merge(yangInstanceIdentifier, pruned);
+        }
     }
 
     @Override
     public void write(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode<?, ?> normalizedNode) {
         try {
-            delegate.write(yangInstanceIdentifier, normalizedNode);
+            if(YangInstanceIdentifier.EMPTY.equals(yangInstanceIdentifier)){
+                pruneAndWriteNode(yangInstanceIdentifier, normalizedNode);
+            } else {
+                delegate.write(yangInstanceIdentifier, normalizedNode);
+            }
         } catch (SchemaValidationFailedException e){
             if(!isValidYangInstanceIdentifier(yangInstanceIdentifier)){
                 LOG.warn("Invalid node identifier {} ignoring write", yangInstanceIdentifier);
                 return;
             }
 
-            LOG.warn("Node at path : {} was pruned during write", yangInstanceIdentifier);
+            pruneAndWriteNode(yangInstanceIdentifier, normalizedNode);
+        }
+    }
+
+    private void pruneAndWriteNode(YangInstanceIdentifier yangInstanceIdentifier, NormalizedNode<?, ?> normalizedNode) {
+        LOG.warn("Node at path : {} was pruned during write", yangInstanceIdentifier);
 
-            NormalizedNode<?,?> pruned = pruneNormalizedNode(normalizedNode);
+        NormalizedNode<?,?> pruned = pruneNormalizedNode(normalizedNode);
 
-            if(pruned != null) {
-                delegate.write(yangInstanceIdentifier, pruned);
-            }
+        if(pruned != null) {
+            delegate.write(yangInstanceIdentifier, pruned);
         }
     }
 
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/PruningShardDataTreeSnapshotTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/PruningShardDataTreeSnapshotTest.java
deleted file mode 100644 (file)
index 2d48e7a..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-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 PruningShardDataTreeSnapshotTest {
-
-    @Mock
-    DataTreeSnapshot dataTreeSnapshot;
-
-    @Mock
-    Set<URI> validNamespaces;
-
-    @Before
-    public void setUp(){
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testNewModification(){
-        PruningShardDataTreeSnapshot snapshot1
-                = new PruningShardDataTreeSnapshot(dataTreeSnapshot, validNamespaces);
-
-        DataTreeModification dataTreeModification1 = snapshot1.newModification();
-
-        assertTrue(dataTreeModification1 instanceof PruningDataTreeModification);
-    }
-
-    @Test
-    public void testReadNode(){
-        PruningShardDataTreeSnapshot snapshot
-                = new PruningShardDataTreeSnapshot(dataTreeSnapshot, validNamespaces);
-
-        snapshot.readNode(CarsModel.BASE_PATH);
-
-        verify(dataTreeSnapshot).readNode(CarsModel.BASE_PATH);
-    }
-}
\ No newline at end of file
index ae3ae6f7920d5c845f228bdf833004b6357626ff..f001bc372e25d461fdfa44cdc67ba9b67ebbf1fe 100644 (file)
@@ -2,9 +2,9 @@ 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.Before;
 import org.junit.Test;
 import org.opendaylight.controller.md.cluster.datastore.model.CarsModel;
 import org.opendaylight.controller.md.cluster.datastore.model.PeopleModel;
@@ -16,39 +16,28 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public class ShardDataTreeTest {
 
-    @Test
-    public void testWrite() throws ExecutionException, InterruptedException {
+    SchemaContext fullSchema;
 
-        SchemaContext schemaContext = SchemaContextHelper.full();
-
-        modify(new ShardDataTree(schemaContext), false, true, true);
+    @Before
+    public void setUp(){
+        fullSchema = SchemaContextHelper.full();
     }
 
     @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);
+    public void testWrite() throws ExecutionException, InterruptedException {
+        modify(new ShardDataTree(fullSchema), false, true, true);
     }
 
     @Test
     public void testMerge() throws ExecutionException, InterruptedException {
-
-        SchemaContext schemaContext = SchemaContextHelper.full();
-
-        modify(new ShardDataTree(schemaContext), true, true, true);
+        modify(new ShardDataTree(fullSchema), true, true, true);
     }
 
-    @Test
-    public void testMergeWithMissingSchema() throws ExecutionException, InterruptedException {
 
-        SchemaContext schemaContext = SchemaContextHelper.select(SchemaContextHelper.ODL_DATASTORE_TEST_YANG, SchemaContextHelper.PEOPLE_YANG);
+    private void modify(ShardDataTree shardDataTree, boolean merge, boolean expectedCarsPresent, boolean expectedPeoplePresent) throws ExecutionException, InterruptedException {
 
-        modify(new ShardDataTree(schemaContext), true, false, true);
-    }
+        assertEquals(fullSchema, shardDataTree.getSchemaContext());
 
-    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();
@@ -83,18 +72,4 @@ public class ShardDataTreeTest {
 
     }
 
-    @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/ShardRecoveryCoordinatorTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinatorTest.java
new file mode 100644 (file)
index 0000000..f14bb3d
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import com.google.common.base.Optional;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.modification.ModificationPayload;
+import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification;
+import org.opendaylight.controller.cluster.datastore.modification.WriteModification;
+import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils;
+import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationByteStringPayload;
+import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationPayload;
+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.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+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.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.SchemaValidationFailedException;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.LoggerFactory;
+
+public class ShardRecoveryCoordinatorTest {
+
+    private ShardDataTree peopleDataTree;
+    private SchemaContext peopleSchemaContext;
+    private SchemaContext carsSchemaContext;
+
+    @Before
+    public void setUp(){
+        peopleSchemaContext = SchemaContextHelper.select(SchemaContextHelper.PEOPLE_YANG);
+        carsSchemaContext = SchemaContextHelper.select(SchemaContextHelper.CARS_YANG);
+
+        peopleDataTree = new ShardDataTree(peopleSchemaContext);
+    }
+
+    @Test
+    public void testAppendRecoveredLogEntryDataTreeCandidatePayload(){
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree, peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+        try {
+            coordinator.appendRecoveredLogEntry(DataTreeCandidatePayload.create(createCar()));
+        } catch(SchemaValidationFailedException e){
+            fail("SchemaValidationFailedException should not happen if pruning is done");
+        }
+
+        coordinator.applyCurrentLogRecoveryBatch();
+    }
+
+    @Test
+    public void testAppendRecoveredLogEntryModificationPayload() throws IOException {
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree, peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+        try {
+            MutableCompositeModification modification  = new MutableCompositeModification((short) 1);
+            modification.addModification(new WriteModification(CarsModel.BASE_PATH, CarsModel.create()));
+            coordinator.appendRecoveredLogEntry(new ModificationPayload(modification));
+        } catch(SchemaValidationFailedException e){
+            fail("SchemaValidationFailedException should not happen if pruning is done");
+        }
+    }
+
+    @Test
+    public void testAppendRecoveredLogEntryCompositeModificationPayload() throws IOException {
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree, peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+        try {
+            MutableCompositeModification modification  = new MutableCompositeModification((short) 1);
+            modification.addModification(new WriteModification(CarsModel.BASE_PATH, CarsModel.create()));
+            coordinator.appendRecoveredLogEntry(new CompositeModificationPayload(modification.toSerializable()));
+        } catch(SchemaValidationFailedException e){
+            fail("SchemaValidationFailedException should not happen if pruning is done");
+        }
+    }
+
+    @Test
+    public void testAppendRecoveredLogEntryCompositeModificationByteStringPayload() throws IOException {
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree, peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+        try {
+            MutableCompositeModification modification  = new MutableCompositeModification((short) 1);
+            modification.addModification(new WriteModification(CarsModel.BASE_PATH, CarsModel.create()));
+            coordinator.appendRecoveredLogEntry(new CompositeModificationByteStringPayload(modification.toSerializable()));
+        } catch(SchemaValidationFailedException e){
+            fail("SchemaValidationFailedException should not happen if pruning is done");
+        }
+
+        assertEquals(false, readCars(peopleDataTree).isPresent());
+    }
+
+    @Test
+    public void testApplyRecoverySnapshot(){
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree , peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+
+        coordinator.applyRecoverySnapshot(createSnapshot());
+
+        assertEquals(false, readCars(peopleDataTree).isPresent());
+        assertEquals(true, readPeople(peopleDataTree).isPresent());
+    }
+
+
+    @Test
+    public void testApplyCurrentLogRecoveryBatch(){
+        ShardRecoveryCoordinator coordinator = new ShardRecoveryCoordinator(peopleDataTree, peopleSchemaContext, "foobar", LoggerFactory.getLogger("foo"));
+        coordinator.startLogRecoveryBatch(10);
+
+        try {
+            coordinator.applyCurrentLogRecoveryBatch();
+        } catch(IllegalArgumentException e){
+            fail("IllegalArgumentException should not happen - if the pruning modification delegate is passed");
+        }
+    }
+
+    private DataTreeCandidateTip createCar(){
+        TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create();
+        dataTree.setSchemaContext(carsSchemaContext);
+
+        DataTreeSnapshot snapshot = dataTree.takeSnapshot();
+
+        DataTreeModification modification = snapshot.newModification();
+
+        modification.merge(CarsModel.BASE_PATH, CarsModel.create());
+
+        return dataTree.prepare(modification);
+    }
+
+    private Optional<NormalizedNode<?,?>> readCars(ShardDataTree shardDataTree){
+        TipProducingDataTree dataTree = shardDataTree.getDataTree();
+        dataTree.setSchemaContext(peopleSchemaContext);
+
+        DataTreeSnapshot snapshot = dataTree.takeSnapshot();
+
+        DataTreeModification modification = snapshot.newModification();
+
+        return modification.readNode(CarsModel.BASE_PATH);
+    }
+
+    private Optional<NormalizedNode<?,?>> readPeople(ShardDataTree shardDataTree){
+        TipProducingDataTree dataTree = shardDataTree.getDataTree();
+        dataTree.setSchemaContext(peopleSchemaContext);
+
+        DataTreeSnapshot snapshot = dataTree.takeSnapshot();
+
+        DataTreeModification modification = snapshot.newModification();
+
+        return modification.readNode(PeopleModel.BASE_PATH);
+    }
+
+
+
+    private byte[] createSnapshot(){
+        TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create();
+        dataTree.setSchemaContext(SchemaContextHelper.select(SchemaContextHelper.CARS_YANG, SchemaContextHelper.PEOPLE_YANG));
+
+        DataTreeSnapshot snapshot = dataTree.takeSnapshot();
+
+        DataTreeModification modification = snapshot.newModification();
+
+        modification.merge(CarsModel.BASE_PATH, CarsModel.create());
+        modification.merge(PeopleModel.BASE_PATH, PeopleModel.create());
+
+        DataTreeCandidateTip prepare = dataTree.prepare(modification);
+
+        dataTree.commit(prepare);
+
+        snapshot = dataTree.takeSnapshot();
+
+        modification = snapshot.newModification();
+
+        Optional<NormalizedNode<?, ?>> optional = modification.readNode(YangInstanceIdentifier.EMPTY);
+
+        byte[] bytes = SerializationUtils.serializeNormalizedNode(optional.get());
+
+        return bytes;
+
+
+    }
+}
\ No newline at end of file