You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by re...@apache.org on 2023/01/04 10:18:28 UTC

[jackrabbit-oak] 01/03: OAK-9908: Recovery may revert committed changes

This is an automated email from the ASF dual-hosted git repository.

reschke pushed a commit to branch 1.22
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit aee3c2026f4d1b445b7203238c99c9548d7e96ef
Author: Marcel Reutegger <ma...@gmail.com>
AuthorDate: Mon Aug 22 14:54:52 2022 +0200

    OAK-9908: Recovery may revert committed changes
    
    Add ignored test
---
 .../plugins/document/NodeDocumentSweeperIT.java    | 140 +++++++++++++++++++++
 1 file changed, 140 insertions(+)

diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperIT.java
new file mode 100644
index 0000000000..8048f19ea1
--- /dev/null
+++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperIT.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoMissingLastRevSeeker;
+import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.rdb.RDBMissingLastRevSeeker;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.oak.plugins.document.TestUtils.disposeQuietly;
+import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge;
+import static org.junit.Assert.assertTrue;
+
+public class NodeDocumentSweeperIT extends AbstractTwoNodeTest {
+
+    private static final Path TEST_PATH = Path.fromString("/foo/bar/baz");
+
+    private FailingDocumentStore fds1;
+
+    private LastRevRecoveryAgent agent2;
+
+    public NodeDocumentSweeperIT(DocumentStoreFixture fixture) {
+        super(fixture);
+    }
+
+    @Override
+    protected DocumentStore customize(DocumentStore store) {
+        if (fds1 == null) {
+            fds1 = new FailingDocumentStore(store);
+            return fds1;
+        } else {
+            return store;
+        }
+    }
+
+    @Before
+    public void prepareAgent() {
+        // first setup seeker according to underlying document store implementation
+        MissingLastRevSeeker seeker;
+        if (store2 instanceof MongoDocumentStore) {
+            seeker = new MongoMissingLastRevSeeker((MongoDocumentStore) store2, clock);
+        } else if (store2 instanceof RDBDocumentStore) {
+            seeker = new RDBMissingLastRevSeeker((RDBDocumentStore) store2, clock) {
+                @Override
+                public @NotNull Iterable<NodeDocument> getCandidates(long startTime) {
+                    List<NodeDocument> docs = new ArrayList<>();
+                    super.getCandidates(startTime).forEach(docs::add);
+                    docs.sort((o1, o2) -> NodeDocumentIdComparator.INSTANCE.compare(o1.getId(), o2.getId()));
+                    return docs;
+                }
+            };
+        } else {
+            // use default implementation
+            seeker = new MissingLastRevSeeker(store2, clock);
+        }
+        // then customize seeker to return documents in a defined order
+        // return docs sorted by decreasing depth
+        MissingLastRevSeeker testSeeker = new MissingLastRevSeeker(store2, clock) {
+            @Override
+            public @NotNull Iterable<NodeDocument> getCandidates(long startTime) {
+                List<NodeDocument> docs = new ArrayList<>();
+                seeker.getCandidates(startTime).forEach(docs::add);
+                docs.sort((o1, o2) -> NodeDocumentIdComparator.INSTANCE.compare(o1.getId(), o2.getId()));
+                return docs;
+            }
+        };
+        agent2 = new LastRevRecoveryAgent(ds2.getDocumentStore(), ds2, testSeeker, v -> {});
+    }
+
+    @Ignore("OAK-9908")
+    @Test
+    public void recoveryWithSweep() throws Exception {
+        // create some test data
+        NodeBuilder builder = ds2.getRoot().builder();
+        getOrCreate(builder, TEST_PATH);
+        merge(ds2, builder);
+        ds2.runBackgroundOperations();
+        ds1.runBackgroundOperations();
+        // now these nodes are visible on ds1
+        assertExists(ds1, TEST_PATH);
+        // wait a bit
+        clock.waitUntil(clock.getTime() + TimeUnit.SECONDS.toMillis(10));
+        // add a child
+        builder = ds1.getRoot().builder();
+        getOrCreate(builder, TEST_PATH).child("test");
+        merge(ds1, builder);
+        // simulate a crash
+        fds1.fail().after(0).eternally();
+        disposeQuietly(ds1);
+        // wait and run recovery for ds1
+        clock.waitUntil(clock.getTime() + TimeUnit.MINUTES.toMillis(3));
+        ds2.renewClusterIdLease();
+
+        assertTrue(agent2.isRecoveryNeeded());
+        agent2.recover(1);
+        ds2.runBackgroundOperations();
+        assertExists(ds2, new Path(TEST_PATH, "test"));
+    }
+
+    private void assertExists(NodeStore ns, Path path) {
+        NodeState state = ns.getRoot();
+        for (String name : path.elements()) {
+            state = state.getChildNode(name);
+            assertTrue(state.exists());
+        }
+    }
+
+    private NodeBuilder getOrCreate(NodeBuilder builder, Path path) {
+        for (String name : path.elements()) {
+            builder = builder.child(name);
+        }
+        return builder;
+    }
+
+}