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 mr...@apache.org on 2014/10/29 23:21:13 UTC

svn commit: r1635315 - in /jackrabbit/oak/branches/1.0: ./ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/

Author: mreutegg
Date: Wed Oct 29 22:21:12 2014
New Revision: 1635315

URL: http://svn.apache.org/r1635315
Log:
OAK-2232: External events incomplete

Merged revisions 1634841,1635077,1635218 from trunk

Added:
    jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java
      - copied, changed from r1634841, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java
Modified:
    jackrabbit/oak/branches/1.0/   (props changed)
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java
    jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/AbstractClusterTest.java

Propchange: jackrabbit/oak/branches/1.0/
------------------------------------------------------------------------------
  Merged /jackrabbit/oak/trunk:r1634841,1635077,1635218

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java Wed Oct 29 22:21:12 2014
@@ -301,10 +301,7 @@ public class ClusterNodeInfo {
     private static ClusterNodeInfo createInstance(DocumentStore store, String machineId,
             String instanceId) {
         long now = getCurrentTime();
-        // keys between "0" and "a" includes all possible numbers
-        List<ClusterNodeInfoDocument> list = store.query(Collection.CLUSTER_NODES,
-                ClusterNodeInfoDocument.MIN_ID_VALUE, ClusterNodeInfoDocument.MAX_ID_VALUE,
-                Integer.MAX_VALUE);
+        List<ClusterNodeInfoDocument> list = ClusterNodeInfoDocument.all(store);
         int clusterNodeId = 0;
         int maxId = 0;
         ClusterNodeState state = ClusterNodeState.NONE;
@@ -365,11 +362,13 @@ public class ClusterNodeInfo {
      * to ensure the same cluster id is not re-used by a different instance.
      * The lease is only renewed when half of the lease time passed. That is,
      * with a lease time of 60 seconds, the lease is renewed every 30 seconds.
+     *
+     * @return {@code true} if the lease was renewed; {@code false} otherwise.
      */
-    public void renewLease() {
+    public boolean renewLease() {
         long now = getCurrentTime();
         if (now + leaseTime / 2 < leaseEndTime) {
-            return;
+            return false;
         }
         UpdateOp update = new UpdateOp("" + id, true);
         leaseEndTime = now + leaseTime;
@@ -381,6 +380,7 @@ public class ClusterNodeInfo {
             readWriteMode = mode;
             store.setReadWriteMode(mode);
         }
+        return true;
     }
 
     public void setLeaseTime(long leaseTime) {

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java Wed Oct 29 22:21:12 2014
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.oak.plugins.document;
 
+import java.util.List;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.jackrabbit.oak.plugins.document.ClusterNodeInfo.ClusterNodeState;
 import static org.apache.jackrabbit.oak.plugins.document.ClusterNodeInfo.RecoverLockState;
@@ -29,13 +31,13 @@ public class ClusterNodeInfoDocument ext
      * All ClusterNodeInfoDocument ID value would be greater than this value
      * It can be used as startKey in DocumentStore#query methods
      */
-    public static final String MIN_ID_VALUE = "0";
+    private static final String MIN_ID_VALUE = "0";
 
     /**
      * All ClusterNodeInfoDocument ID value would be less than this value
      * It can be used as endKey in DocumentStore#query methods
      */
-    public static final String MAX_ID_VALUE = "a";
+    private static final String MAX_ID_VALUE = "a";
 
     public long getLeaseEndTime(){
         return checkNotNull((Long) get(ClusterNodeInfo.LEASE_END_KEY), "Lease End Time not set");
@@ -49,6 +51,25 @@ public class ClusterNodeInfoDocument ext
         return getRecoveryState() == RecoverLockState.ACQUIRED;
     }
 
+    public int getClusterId() {
+        return Integer.parseInt(getId());
+    }
+
+    /**
+     * Returns all cluster node info documents currently available in the given
+     * document store.
+     *
+     * @param store the document store.
+     * @return list of cluster node info documents.
+     */
+    public static List<ClusterNodeInfoDocument> all(DocumentStore store) {
+        // keys between "0" and "a" includes all possible numbers
+        return store.query(Collection.CLUSTER_NODES,
+                MIN_ID_VALUE, MAX_ID_VALUE, Integer.MAX_VALUE);
+    }
+
+    //-----------------------< internal >---------------------------------------
+
     private ClusterNodeState getState(){
         return ClusterNodeState.fromString((String) get(ClusterNodeInfo.STATE));
     }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java Wed Oct 29 22:21:12 2014
@@ -30,6 +30,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,7 @@ import java.util.NavigableSet;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -175,6 +177,14 @@ public final class DocumentNodeStore
     private final int clusterId;
 
     /**
+     * Map of inactive cluster nodes and when the cluster node was last seen
+     * as inactive.
+     * Key: clusterId, value: timeInMillis
+     */
+    private final ConcurrentMap<Integer, Long> inactiveClusterNodes
+            = new ConcurrentHashMap<Integer, Long>();
+
+    /**
      * The comparator for revisions.
      */
     private final Revision.RevisionComparator revisionComparator;
@@ -1432,11 +1442,42 @@ public final class DocumentNodeStore
         }
     }
 
-    void renewClusterIdLease() {
-        if (clusterNodeInfo == null) {
-            return;
+    /**
+     * Renews the cluster lease if necessary.
+     *
+     * @return {@code true} if the lease was renewed; {@code false} otherwise.
+     */
+    boolean renewClusterIdLease() {
+        return clusterNodeInfo != null && clusterNodeInfo.renewLease();
+    }
+
+    /**
+     * Updates the info about inactive cluster nodes in
+     * {@link #inactiveClusterNodes}.
+     */
+    void updateClusterState() {
+        long now = clock.getTime();
+        Set<Integer> inactive = Sets.newHashSet();
+        for (ClusterNodeInfoDocument doc : ClusterNodeInfoDocument.all(store)) {
+            int cId = doc.getClusterId();
+            if (cId != this.clusterId && !doc.isActive()) {
+                inactive.add(cId);
+            }
+        }
+        inactiveClusterNodes.keySet().retainAll(inactive);
+        for (Integer clusterId : inactive) {
+            inactiveClusterNodes.putIfAbsent(clusterId, now);
         }
-        clusterNodeInfo.renewLease();
+    }
+
+    /**
+     * Returns the cluster nodes currently known to be inactive.
+     *
+     * @return a map with the cluster id as key and the time in millis when it
+     *          was first seen inactive.
+     */
+    Map<Integer, Long> getInactiveClusterNodes() {
+        return new HashMap<Integer, Long>(inactiveClusterNodes);
     }
 
     /**
@@ -1600,7 +1641,9 @@ public final class DocumentNodeStore
     }
 
     private void diffManyChildren(JsopWriter w, String path, Revision fromRev, Revision toRev) {
-        long minTimestamp = Math.min(fromRev.getTimestamp(), toRev.getTimestamp());
+        long minTimestamp = Math.min(
+                revisionComparator.getMinimumTimestamp(fromRev, inactiveClusterNodes),
+                revisionComparator.getMinimumTimestamp(toRev, inactiveClusterNodes));
         long minValue = NodeDocument.getModifiedInSecs(minTimestamp);
         String fromKey = Utils.getKeyLowerLimit(path);
         String toKey = Utils.getKeyUpperLimit(path);
@@ -1836,7 +1879,9 @@ public final class DocumentNodeStore
 
         @Override
         protected void execute(@Nonnull DocumentNodeStore nodeStore) {
-            nodeStore.renewClusterIdLease();
+            if (nodeStore.renewClusterIdLease()) {
+                nodeStore.updateClusterState();
+            }
         }
     }
 

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java Wed Oct 29 22:21:12 2014
@@ -45,8 +45,7 @@ public class MissingLastRevSeeker {
      * @return the clusters
      */
     public Iterable<ClusterNodeInfoDocument> getAllClusters() {
-        return store.query(Collection.CLUSTER_NODES, ClusterNodeInfoDocument.MIN_ID_VALUE,
-                ClusterNodeInfoDocument.MAX_ID_VALUE, Integer.MAX_VALUE);
+        return ClusterNodeInfoDocument.all(store);
     }
     
     /**

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java Wed Oct 29 22:21:12 2014
@@ -19,10 +19,13 @@ package org.apache.jackrabbit.oak.plugin
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import javax.annotation.Nonnull;
+
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.apache.jackrabbit.oak.stats.Clock;
 
@@ -534,6 +537,47 @@ public class Revision {
             }
         }
 
+        /**
+         * Returns the minimum timestamp of the most recent revisions from all
+         * active cluster nodes as seen from the given {@code revision}.
+         *
+         * @param revision a revision.
+         * @param inactive map of cluster nodes considered inactive.
+         * @return the minimum timestamp.
+         */
+        public long getMinimumTimestamp(@Nonnull Revision revision,
+                                        @Nonnull Map<Integer, Long> inactive) {
+            long timestamp = checkNotNull(revision).getTimestamp();
+            Revision seenAt = getRevisionSeen(revision);
+            if (seenAt == null) {
+                // already purged
+                return timestamp;
+            }
+            // go through all known cluster nodes
+            for (List<RevisionRange> list : map.values()) {
+                RevisionRange range;
+                for (int i = list.size() - 1; i >= 0; i--) {
+                    range = list.get(i);
+                    if (range.seenAt.compareRevisionTimeThenClusterId(seenAt) <= 0) {
+                        // found newest range older or equal the given seenAt
+                        // check if the cluster node is still active
+                        Long inactiveSince = inactive.get(range.revision.getClusterId());
+                        if (inactiveSince != null
+                                && revision.getTimestamp() > inactiveSince
+                                && range.revision.getTimestamp() < inactiveSince) {
+                            // ignore, because the revision is after the
+                            // cluster node became inactive and the most recent
+                            // range is before it became inactive
+                        } else {
+                            timestamp = Math.min(timestamp, range.revision.getTimestamp());
+                        }
+                        break;
+                    }
+                }
+            }
+            return timestamp;
+        }
+
         @Override
         public int compare(Revision o1, Revision o2) {
             if (o1.getClusterId() == o2.getClusterId()) {

Modified: jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java Wed Oct 29 22:21:12 2014
@@ -526,6 +526,98 @@ public class DocumentNodeStoreTest {
         assertEquals("value", state.getProperty("prop").getValue(Type.STRING));
     }
 
+    // OAK-2232
+    @Test
+    public void diffExternalChanges() throws Exception {
+        long modifiedResMillis = SECONDS.toMillis(MODIFIED_IN_SECS_RESOLUTION);
+        Clock clock = new Clock.Virtual();
+        clock.waitUntil(System.currentTimeMillis());
+        Revision.setClock(clock);
+
+        DocumentStore docStore = new MemoryDocumentStore();
+        DocumentNodeStore ns1 = new DocumentMK.Builder().setAsyncDelay(0)
+                .clock(clock).setDocumentStore(docStore).setClusterId(1)
+                .getNodeStore();
+        DocumentNodeStore ns2 = new DocumentMK.Builder().setAsyncDelay(0)
+                .clock(clock).setDocumentStore(docStore).setClusterId(2)
+                .getNodeStore();
+
+        NodeBuilder builder = ns1.getRoot().builder();
+        NodeBuilder test = builder.child("test");
+        for (int i = 0; i < DocumentMK.MANY_CHILDREN_THRESHOLD * 2; i++) {
+            test.child("node-" + i);
+        }
+        ns1.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        ns1.runBackgroundOperations();
+        ns2.runBackgroundOperations();
+
+        // make sure next change has a different _modified value
+        clock.waitUntil(clock.getTime() + modifiedResMillis * 2);
+
+        builder = ns2.getRoot().builder();
+        builder.child("test").child("foo");
+        ns2.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        // 'wait' again for a different _modified value
+        clock.waitUntil(clock.getTime() + modifiedResMillis * 2);
+
+        builder = ns1.getRoot().builder();
+        builder.child("test").child("bar");
+        ns1.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        // remember current root for diff
+        NodeState r1 = ns1.getRoot();
+
+        ns2.runBackgroundOperations();
+        ns1.runBackgroundOperations();
+
+        NodeState r2 = ns1.getRoot();
+
+        // are we able to see foo?
+        boolean found = false;
+        for (ChildNodeEntry entry : r2.getChildNode("test").getChildNodeEntries()) {
+            if (entry.getName().equals("foo")) {
+                found = true;
+                break;
+            }
+        }
+        assertTrue(found);
+
+        // diff must report '/test' modified and '/test/foo' added
+        ClusterTest.TrackingDiff diff = new ClusterTest.TrackingDiff();
+        r2.compareAgainstBaseState(r1, diff);
+        assertEquals(1, diff.modified.size());
+        assertTrue(diff.modified.contains("/test"));
+        assertEquals(1, diff.added.size());
+        assertTrue(diff.added.contains("/test/foo"));
+
+        ns1.dispose();
+        ns2.dispose();
+    }
+
+    @Test
+    public void updateClusterState() {
+        DocumentStore docStore = new MemoryDocumentStore();
+        DocumentNodeStore ns1 = new DocumentMK.Builder().setAsyncDelay(0)
+                .setDocumentStore(docStore).getNodeStore();
+        DocumentNodeStore ns2 = new DocumentMK.Builder().setAsyncDelay(0)
+                .setDocumentStore(docStore).getNodeStore();
+
+        assertEquals(0, ns1.getInactiveClusterNodes().size());
+        assertEquals(0, ns2.getInactiveClusterNodes().size());
+
+        ns1.dispose();
+
+        ns2.updateClusterState();
+
+        Map<Integer, Long> inactive = ns2.getInactiveClusterNodes();
+        assertEquals(1, inactive.size());
+        assertEquals(1, (int) inactive.keySet().iterator().next());
+
+        ns2.dispose();
+    }
+
     private static class TestHook extends EditorHook {
 
         TestHook(final String prefix) {

Modified: jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java Wed Oct 29 22:21:12 2014
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
@@ -33,6 +34,7 @@ import static org.junit.Assert.assertTru
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Uninterruptibles;
@@ -455,4 +457,43 @@ public class RevisionTest {
         assertTrue(String.format("Duplicate rev seen %s %n Seen %s", duplicates, seenRevs), duplicates.isEmpty());
     }
 
+    @Test
+    public void getMinimumTimestamp() {
+        Map<Integer, Long> inactive = Maps.newHashMap();
+        RevisionComparator comp = new RevisionComparator(1);
+
+        Revision r11 = new Revision(1, 0, 1);
+        comp.add(r11, new Revision(1, 0, 0));
+
+        assertEquals(1, comp.getMinimumTimestamp(r11, inactive));
+
+        Revision r21 = new Revision(1, 0, 2);
+        comp.add(r21, new Revision(2, 0, 0));
+
+        assertEquals(1, comp.getMinimumTimestamp(r21, inactive));
+
+        Revision r13 = new Revision(3, 0, 1);
+        comp.add(r13, new Revision(3, 0, 0));
+
+        assertEquals(1, comp.getMinimumTimestamp(r13, inactive));
+
+        Revision r24 = new Revision(4, 0, 2);
+        comp.add(r24, new Revision(4, 0, 0));
+
+        assertEquals(3, comp.getMinimumTimestamp(r24, inactive));
+
+        Revision r15 = new Revision(5, 0, 1);
+        comp.add(r15, new Revision(5, 0, 0));
+
+        assertEquals(4, comp.getMinimumTimestamp(r15, inactive));
+
+        // simulate cluster node 2 is stopped
+        inactive.put(2, 6L);
+
+        Revision r17 = new Revision(7, 0, 1);
+        comp.add(r17, new Revision(7, 0, 0));
+
+        assertEquals(7, comp.getMinimumTimestamp(r17, inactive));
+    }
+
 }

Modified: jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/AbstractClusterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/AbstractClusterTest.java?rev=1635315&r1=1635314&r2=1635315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/AbstractClusterTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/AbstractClusterTest.java Wed Oct 29 22:21:12 2014
@@ -68,6 +68,9 @@ public class AbstractClusterTest {
         }
     }
 
+    protected void prepareTestData(Session s) throws RepositoryException {
+    }
+
     @Before
     public void login() throws RepositoryException {
         ns1 = fixture.createNodeStore(1);
@@ -75,12 +78,13 @@ public class AbstractClusterTest {
             return;
         }
         r1  = new Jcr(ns1).createRepository();
+        s1 = r1.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        prepareTestData(s1);
         if (ns1 instanceof DocumentNodeStore) {
             // make sure initial repository data is visible to
             // other cluster nodes initialized later
             ((DocumentNodeStore) ns1).runBackgroundOperations();
         }
-        s1 = r1.login(new SimpleCredentials("admin", "admin".toCharArray()));
         ns2 = fixture.createNodeStore(2);
         r2  = new Jcr(ns2).createRepository();
         s2 = r2.login(new SimpleCredentials("admin", "admin".toCharArray()));

Copied: jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java (from r1634841, jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java)
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java?p2=jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java&p1=jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java&r1=1634841&r2=1635315&rev=1635315&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/cluster/ObservationTest.java Wed Oct 29 22:21:12 2014
@@ -16,7 +16,11 @@
  */
 package org.apache.jackrabbit.oak.jcr.cluster;
 
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -38,11 +42,16 @@ import org.junit.Ignore;
 import org.junit.Test;
 
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import static java.util.Collections.synchronizedSet;
+import static org.junit.Assert.assertTrue;
 
 /**
  * Test for external events from another cluster node.
  */
-@Ignore
+@Ignore("This is a rather long running test and therefore disabled. See " +
+        "DocumentNodeStoreTest.diffExternalChanges() in oak-core for a unit test")
 public class ObservationTest extends AbstractClusterTest {
 
     private ScheduledExecutorService executor;
@@ -68,6 +77,7 @@ public class ObservationTest extends Abs
 
     @Test
     public void externalEvents() throws Throwable {
+        final Set<String> externallyAdded = synchronizedSet(new LinkedHashSet<String>());
         final List<Throwable> exceptions = Lists.newArrayList();
         ObservationManager obsMgr = s1.getWorkspace().getObservationManager();
         final AtomicLong localEvents = new AtomicLong();
@@ -76,6 +86,7 @@ public class ObservationTest extends Abs
             @Override
             public void onEvent(EventIterator events) {
                 try {
+                    Set<String> paths = Sets.newHashSet();
                     while (events.hasNext()) {
                         Event event = events.nextEvent();
                         String external = "";
@@ -84,12 +95,19 @@ public class ObservationTest extends Abs
                            if (((JackrabbitEvent) event).isExternal()) {
                                external = " (external)";
                                counter = externalEvents;
+                               paths.add(event.getPath());
                            }
                         }
                         System.out.println(event.getPath() + external);
                         counter.incrementAndGet();
                     }
-                } catch (RepositoryException e) {
+                    while (!paths.isEmpty()) {
+                        Iterator<String> it = externallyAdded.iterator();
+                        String p = it.next();
+                        assertTrue("missing event for " + p, paths.remove(p));
+                        it.remove();
+                    }
+                } catch (Throwable e) {
                     exceptions.add(e);
                 }
             }
@@ -97,8 +115,8 @@ public class ObservationTest extends Abs
         obsMgr.addEventListener(listener, Event.NODE_ADDED, "/",
                 true, null, null, false);
 
-        Future f1 = executor.submit(new Worker(s1, exceptions));
-        Future f2 = executor.submit(new Worker(s2, exceptions));
+        Future f1 = executor.submit(new Worker(s1, exceptions, new HashSet<String>()));
+        Future f2 = executor.submit(new Worker(s2, exceptions, externallyAdded));
 
         f1.get();
         f2.get();
@@ -117,18 +135,21 @@ public class ObservationTest extends Abs
 
         private Session s;
         private List<Throwable> exceptions;
+        private Set<String> paths;
 
-        Worker(Session s, List<Throwable> exceptions) {
+        Worker(Session s, List<Throwable> exceptions, Set<String> paths) {
             this.s = s;
             this.exceptions = exceptions;
+            this.paths = paths;
         }
 
         @Override
         public void run() {
             try {
                 Node test = s.getNode("/test");
-                for (int i = 0; i < 500; i++) {
-                    test.addNode(UUID.randomUUID().toString());
+                for (int i = 0; i < 500 && exceptions.isEmpty(); i++) {
+                    Node n = test.addNode(UUID.randomUUID().toString());
+                    paths.add(n.getPath());
                     s.save();
                     Thread.sleep((long) (Math.random() * 100));
                 }