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 to...@apache.org on 2016/12/14 10:01:16 UTC

svn commit: r1774159 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/docume...

Author: tomekr
Date: Wed Dec 14 10:01:16 2016
New Revision: 1774159

URL: http://svn.apache.org/viewvc?rev=1774159&view=rev
Log:
OAK-4069: Use read concern majority when connected to a replica set

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatus.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatusTest.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MongoConnection.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoConnectionTest.java
    jackrabbit/oak/trunk/oak-parent/pom.xml

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java?rev=1774159&r1=1774158&r2=1774159&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java Wed Dec 14 10:01:16 2016
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.plugins.document.util.MongoConnection.readConcernLevel;
 
 import java.io.InputStream;
 import java.net.UnknownHostException;
@@ -45,6 +46,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.mongodb.DB;
+import com.mongodb.ReadConcernLevel;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.cache.CacheLIRS;
@@ -79,6 +81,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBVersionGCSupport;
 import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.apache.jackrabbit.oak.plugins.document.mongo.MongoStatus;
 import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey;
 import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
 import org.apache.jackrabbit.oak.spi.blob.AbstractBlobStore;
@@ -544,6 +547,7 @@ public class DocumentMK {
         private DocumentNodeStore nodeStore;
         private DocumentStore documentStore;
         private String mongoUri;
+        private MongoStatus mongoStatus;
         private DiffCache diffCache;
         private BlobStore blobStore;
         private int clusterId  = Integer.getInteger("oak.documentMK.clusterId", 0);
@@ -606,10 +610,14 @@ public class DocumentMK {
             this.mongoUri = uri;
 
             DB db = new MongoConnection(uri).getDB(name);
+            MongoStatus status = new MongoStatus(db);
             if (!MongoConnection.hasWriteConcern(uri)) {
                 db.setWriteConcern(MongoConnection.getDefaultWriteConcern(db));
             }
-            setMongoDB(db, blobCacheSizeMB);
+            if (status.isMajorityReadConcernSupported() && status.isMajorityReadConcernEnabled() && !MongoConnection.hasReadConcern(uri)) {
+                db.setReadConcern(MongoConnection.getDefaultReadConcern(db));
+            }
+            setMongoDB(db, status, blobCacheSizeMB);
             return this;
         }
 
@@ -621,10 +629,29 @@ public class DocumentMK {
          */
         public Builder setMongoDB(@Nonnull DB db,
                                   int blobCacheSizeMB) {
+            return setMongoDB(db, new MongoStatus(db), blobCacheSizeMB);
+        }
+
+        private Builder setMongoDB(@Nonnull DB db,
+                                   MongoStatus status,
+                                   int blobCacheSizeMB) {
             if (!MongoConnection.hasSufficientWriteConcern(db)) {
                 LOG.warn("Insufficient write concern: " + db.getWriteConcern()
                         + " At least " + MongoConnection.getDefaultWriteConcern(db) + " is recommended.");
             }
+            if (status.isMajorityReadConcernSupported() && !status.isMajorityReadConcernEnabled()) {
+                LOG.warn("The read concern should be enabled on mongod using --enableMajorityReadConcern");
+            } else if (status.isMajorityReadConcernSupported() && !MongoConnection.hasSufficientReadConcern(db)) {
+                ReadConcernLevel currentLevel = readConcernLevel(db.getReadConcern());
+                ReadConcernLevel recommendedLevel = readConcernLevel(MongoConnection.getDefaultReadConcern(db));
+                if (currentLevel == null) {
+                    LOG.warn("Read concern hasn't been set. At least " + recommendedLevel + " is recommended.");
+                } else {
+                    LOG.warn("Insufficient read concern: " + currentLevel + ". At least " + recommendedLevel + " is recommended.");
+                }
+            }
+
+            this.mongoStatus = status;
             if (this.documentStore == null) {
                 this.documentStore = new MongoDocumentStore(db, this);
             }
@@ -662,6 +689,16 @@ public class DocumentMK {
         }
 
         /**
+         * Returns the status of the Mongo server configured in the {@link #setMongoDB(String, String, int)} method.
+         *
+         * @return the status or null if the {@link #setMongoDB(String, String, int)} method hasn't
+         * been called.
+         */
+        public MongoStatus getMongoStatus() {
+            return mongoStatus;
+        }
+
+        /**
          * Sets a {@link DataSource} to use for the RDB document and blob
          * stores.
          *

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java?rev=1774159&r1=1774158&r2=1774159&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java Wed Dec 14 10:01:16 2016
@@ -34,8 +34,6 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
@@ -43,7 +41,6 @@ import javax.annotation.Nullable;
 
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Lists;
@@ -135,11 +132,6 @@ public class MongoDocumentStore implemen
 
     public static final int IN_CLAUSE_BATCH_SIZE = 500;
 
-    private static final ImmutableSet<String> SERVER_DETAIL_FIELD_NAMES
-            = ImmutableSet.<String>builder()
-            .add("host", "process", "connections", "repl", "storageEngine", "mem")
-            .build();
-
     private final DBCollection nodes;
     private final DBCollection clusterNodes;
     private final DBCollection settings;
@@ -233,11 +225,14 @@ public class MongoDocumentStore implemen
     private boolean hasModifiedIdCompoundIndex = true;
 
     public MongoDocumentStore(DB db, DocumentMK.Builder builder) {
-        CommandResult serverStatus = db.command("serverStatus");
-        String version = checkVersion(db, serverStatus);
+        MongoStatus mongoStatus = builder.getMongoStatus();
+        if (mongoStatus == null) {
+            mongoStatus = new MongoStatus(db);
+        }
+        mongoStatus.checkVersion();
         metadata = ImmutableMap.<String,String>builder()
                 .put("type", "mongo")
-                .put("version", version)
+                .put("version", mongoStatus.getVersion())
                 .build();
 
         this.db = db;
@@ -295,47 +290,9 @@ public class MongoDocumentStore implemen
         LOG.info("Connected to MongoDB {} with maxReplicationLagMillis {}, " +
                 "maxDeltaForModTimeIdxSecs {}, disableIndexHint {}, " +
                 "{}, serverStatus {}",
-                version, maxReplicationLagMillis, maxDeltaForModTimeIdxSecs,
+                mongoStatus.getVersion(), maxReplicationLagMillis, maxDeltaForModTimeIdxSecs,
                 disableIndexHint, db.getWriteConcern(),
-                serverDetails(serverStatus));
-    }
-
-    @Nonnull
-    private static String checkVersion(DB db, CommandResult serverStatus) {
-        String version = serverStatus.getString("version");
-        if (version == null) {
-            // OAK-4841: serverStatus was probably unauthorized,
-            // use buildInfo command to get version
-            version = db.command("buildInfo").getString("version");
-        }
-        Matcher m = Pattern.compile("^(\\d+)\\.(\\d+)\\..*").matcher(version);
-        if (!m.matches()) {
-            throw new IllegalArgumentException("Malformed MongoDB version: " + version);
-        }
-        int major = Integer.parseInt(m.group(1));
-        int minor = Integer.parseInt(m.group(2));
-        if (major > 2) {
-            return version;
-        }
-        if (minor < 6) {
-            String msg = "MongoDB version 2.6.0 or higher required. " +
-                    "Currently connected to a MongoDB with version: " + version;
-            throw new RuntimeException(msg);
-        }
-
-        return version;
-    }
-
-    @Nonnull
-    private static String serverDetails(CommandResult serverStatus) {
-        Map<String, Object> details = Maps.newHashMap();
-        for (String key : SERVER_DETAIL_FIELD_NAMES) {
-            Object value = serverStatus.get(key);
-            if (value != null) {
-                details.put(key, value);
-            }
-        }
-        return details.toString();
+                mongoStatus.getServerDetails());
     }
 
     @Override

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatus.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatus.java?rev=1774159&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatus.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatus.java Wed Dec 14 10:01:16 2016
@@ -0,0 +1,185 @@
+/*
+ * 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.mongo;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.MongoQueryException;
+import com.mongodb.ReadConcern;
+import com.mongodb.client.model.DBCollectionFindOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MongoStatus {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MongoStatus.class);
+
+    private static final ImmutableSet<String> SERVER_DETAIL_FIELD_NAMES
+            = ImmutableSet.<String>builder()
+            .add("host", "process", "connections", "repl", "storageEngine", "mem")
+            .build();
+
+    private final DB db;
+
+    private BasicDBObject serverStatus;
+
+    private BasicDBObject buildInfo;
+
+    private String version;
+
+    private Boolean majorityReadConcernSupported;
+
+    private Boolean majorityReadConcernEnabled;
+
+    public MongoStatus(@Nonnull DB db) {
+        this.db = db;
+    }
+
+    public void checkVersion() {
+        if (!isVersion(2, 6)) {
+            String msg = "MongoDB version 2.6.0 or higher required. " +
+                    "Currently connected to a MongoDB with version: " + version;
+            throw new RuntimeException(msg);
+        }
+    }
+
+    /**
+     * Check if the majority read concern is supported by this storage engine.
+     * The fact that read concern is supported doesn't it can be used - it also
+     * has to be enabled.
+     *
+     * @return true if the majority read concern is supported
+     */
+    public boolean isMajorityReadConcernSupported() {
+        if (majorityReadConcernSupported == null) {
+            BasicDBObject stat = getServerStatus();
+            if (stat.isEmpty()) {
+                LOG.debug("User doesn't have privileges to get server status; falling back to the isMajorityReadConcernEnabled()");
+                return isMajorityReadConcernEnabled();
+            } else {
+                if (stat.containsField("storageEngine")) {
+                    BasicDBObject storageEngine = (BasicDBObject) stat.get("storageEngine");
+                    majorityReadConcernSupported = storageEngine.getBoolean("supportsCommittedReads");
+                } else {
+                    majorityReadConcernSupported = false;
+                }
+            }
+        }
+        return majorityReadConcernSupported;
+    }
+
+    /**
+     * Check if the majority read concern is enabled and can be used for queries.
+     *
+     * @return true if the majority read concern is enabled
+     */
+    public boolean isMajorityReadConcernEnabled() {
+        if (majorityReadConcernEnabled == null) {
+            // Mongo API doesn't seem to provide an option to check whether the
+            // majority read concern has been enabled, so we have to try to use
+            // it and optionally catch the exception.
+            DBCollection emptyCollection = db.getCollection("emptyCollection-" + System.currentTimeMillis());
+            DBCursor cursor = emptyCollection.find(new BasicDBObject(), new DBCollectionFindOptions().readConcern(ReadConcern.MAJORITY));
+            try {
+                cursor.hasNext();
+                majorityReadConcernEnabled = true;
+            } catch (MongoQueryException e) {
+                majorityReadConcernEnabled = false;
+            } finally {
+                cursor.close();
+            }
+        }
+        return majorityReadConcernEnabled;
+    }
+
+    @Nonnull
+    public String getServerDetails() {
+        Map<String, Object> details = Maps.newHashMap();
+        for (String key : SERVER_DETAIL_FIELD_NAMES) {
+            Object value = getServerStatus().get(key);
+            if (value != null) {
+                details.put(key, value);
+            }
+        }
+        return details.toString();
+    }
+
+    @Nonnull
+    public String getVersion() {
+        if (version == null) {
+            String v = getServerStatus().getString("version");
+            if (v == null) {
+                // OAK-4841: serverStatus was probably unauthorized,
+                // use buildInfo command to get version
+                v = getBuildInfo().getString("version");
+            }
+            version = v;
+        }
+        return version;
+    }
+
+    private boolean isVersion(int requiredMajor, int requiredMinor) {
+        String v = getVersion();
+        Matcher m = Pattern.compile("^(\\d+)\\.(\\d+)\\..*").matcher(v);
+        if (!m.matches()) {
+            throw new IllegalArgumentException("Malformed MongoDB version: " + v);
+        }
+        int major = Integer.parseInt(m.group(1));
+        int minor = Integer.parseInt(m.group(2));
+
+        if (major > requiredMajor) {
+            return true;
+        } else if (major == requiredMajor) {
+            return minor >= requiredMinor;
+        } else {
+            return false;
+        }
+    }
+
+    private BasicDBObject getServerStatus() {
+        if (serverStatus == null) {
+            serverStatus = db.command("serverStatus");
+        }
+        return serverStatus;
+    }
+
+    private BasicDBObject getBuildInfo() {
+        if (buildInfo == null) {
+            buildInfo = db.command("buildInfo");
+        }
+        return buildInfo;
+    }
+
+    // for testing purposes
+    void setVersion(String version) {
+        this.version = version;
+    }
+
+    void setServerStatus(BasicDBObject serverStatus) {
+        this.majorityReadConcernSupported = null;
+        this.serverStatus = serverStatus;
+    }
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MongoConnection.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MongoConnection.java?rev=1774159&r1=1774158&r2=1774159&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MongoConnection.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MongoConnection.java Wed Dec 14 10:01:16 2016
@@ -17,15 +17,19 @@
 package org.apache.jackrabbit.oak.plugins.document.util;
 
 import java.net.UnknownHostException;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nonnull;
 
 import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
 import com.mongodb.DB;
 import com.mongodb.MongoClient;
 import com.mongodb.MongoClientOptions;
 import com.mongodb.MongoClientURI;
+import com.mongodb.ReadConcern;
+import com.mongodb.ReadConcernLevel;
 import com.mongodb.WriteConcern;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -37,6 +41,7 @@ public class MongoConnection {
 
     private static final int DEFAULT_MAX_WAIT_TIME = (int) TimeUnit.MINUTES.toMillis(1);
     private static final WriteConcern WC_UNKNOWN = new WriteConcern("unknown");
+    private static final Set<ReadConcernLevel> REPLICA_RC = ImmutableSet.of(ReadConcernLevel.MAJORITY, ReadConcernLevel.LINEARIZABLE);
     private final MongoClientURI mongoURI;
     private final MongoClient mongo;
 
@@ -133,6 +138,18 @@ public class MongoConnection {
     }
 
     /**
+     * Returns {@code true} if the given {@code uri} has a read concern set.
+     * @param uri the URI to check.
+     * @return {@code true} if the URI has a read concern set, {@code false}
+     *      otherwise.
+     */
+    public static boolean hasReadConcern(@Nonnull String uri) {
+        ReadConcern rc = new MongoClientURI(checkNotNull(uri))
+                .getOptions().getReadConcern();
+        return readConcernLevel(rc) != null;
+    }
+
+    /**
      * Returns the default write concern depending on MongoDB deployment.
      * <ul>
      *     <li>{@link WriteConcern#MAJORITY}: for a MongoDB replica set</li>
@@ -153,6 +170,36 @@ public class MongoConnection {
     }
 
     /**
+     * Returns the default read concern depending on MongoDB deployment.
+     * <ul>
+     *     <li>{@link ReadConcern#MAJORITY}: for a MongoDB replica set with w=majority</li>
+     *     <li>{@link ReadConcern#LOCAL}: for other cases</li>
+     * </ul>
+     *
+     * @param db the connection to MongoDB.
+     * @return the default write concern to use for Oak.
+     */
+    public static ReadConcern getDefaultReadConcern(@Nonnull DB db) {
+        ReadConcern r;
+        if (checkNotNull(db).getMongo().getReplicaSetStatus() != null && isMajorityWriteConcern(db)) {
+            r = ReadConcern.MAJORITY;
+        } else {
+            r = ReadConcern.LOCAL;
+        }
+        return r;
+    }
+
+    /**
+     * Returns true if the majority write concern is used for the given DB.
+     *
+     * @param db the connection to MongoDB.
+     * @return true if the majority write concern has been configured; false otherwise
+     */
+    public static boolean isMajorityWriteConcern(@Nonnull DB db) {
+        return "majority".equals(db.getWriteConcern().getWObject());
+    }
+
+    /**
      * Returns {@code true} if the default write concern on the {@code db} is
      * sufficient for Oak. On a replica set Oak expects at least w=2. For
      * a single MongoDB node deployment w=1 is sufficient.
@@ -181,4 +228,29 @@ public class MongoConnection {
             return w >= 1;
         }
     }
+
+    /**
+     * Returns {@code true} if the default read concern on the {@code db} is
+     * sufficient for Oak. On a replica set Oak expects majority or linear. For
+     * a single MongoDB node deployment local is sufficient.
+     *
+     * @param db the database.
+     * @return whether the read concern is sufficient.
+     */
+    public static boolean hasSufficientReadConcern(@Nonnull DB db) {
+        ReadConcernLevel r = readConcernLevel(checkNotNull(db).getReadConcern());
+        if (db.getMongo().getReplicaSetStatus() == null) {
+            return true;
+        } else {
+            return REPLICA_RC.contains(r);
+        }
+    }
+
+    public static ReadConcernLevel readConcernLevel(ReadConcern readConcern) {
+        if (readConcern.isServerDefault()) {
+            return null;
+        } else {
+            return ReadConcernLevel.fromString(readConcern.asDocument().getString("level").getValue());
+        }
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoConnectionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoConnectionTest.java?rev=1774159&r1=1774158&r2=1774159&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoConnectionTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoConnectionTest.java Wed Dec 14 10:01:16 2016
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import com.mongodb.DB;
 import com.mongodb.Mongo;
+import com.mongodb.ReadConcern;
 import com.mongodb.ReplicaSetStatus;
 import com.mongodb.WriteConcern;
 
@@ -39,6 +40,12 @@ public class MongoConnectionTest {
     }
 
     @Test
+    public void hasReadConcern() throws Exception {
+        assertFalse(MongoConnection.hasReadConcern("mongodb://localhost:27017/foo"));
+        assertTrue(MongoConnection.hasReadConcern("mongodb://localhost:27017/foo?readconcernlevel=majority"));
+    }
+
+    @Test
     public void sufficientWriteConcern() throws Exception {
         sufficientWriteConcernReplicaSet(WriteConcern.ACKNOWLEDGED, false);
         sufficientWriteConcernReplicaSet(WriteConcern.JOURNALED, false);
@@ -65,6 +72,17 @@ public class MongoConnectionTest {
         sufficientWriteConcernSingleNode(WriteConcern.UNACKNOWLEDGED, false);
     }
 
+    @Test
+    public void sufficientReadConcern() throws Exception {
+        sufficientReadConcernReplicaSet(ReadConcern.DEFAULT, false);
+        sufficientReadConcernReplicaSet(ReadConcern.LOCAL, false);
+        sufficientReadConcernReplicaSet(ReadConcern.MAJORITY, true);
+
+        sufficientReadConcernSingleNode(ReadConcern.DEFAULT, true);
+        sufficientReadConcernSingleNode(ReadConcern.LOCAL, true);
+        sufficientReadConcernSingleNode(ReadConcern.MAJORITY, true);
+    }
+
     private void sufficientWriteConcernReplicaSet(WriteConcern w,
                                                   boolean sufficient) {
         sufficientWriteConcern(w, true, sufficient);
@@ -78,6 +96,29 @@ public class MongoConnectionTest {
     private void sufficientWriteConcern(WriteConcern w,
                                         boolean replicaSet,
                                         boolean sufficient) {
+        DB db = mockDB(ReadConcern.DEFAULT, w, replicaSet);
+        assertEquals(sufficient, MongoConnection.hasSufficientWriteConcern(db));
+    }
+
+    private void sufficientReadConcernReplicaSet(ReadConcern r,
+                                                 boolean sufficient) {
+        sufficientReadConcern(r, true, sufficient);
+    }
+
+    private void sufficientReadConcernSingleNode(ReadConcern r,
+                                                 boolean sufficient) {
+        sufficientReadConcern(r, false, sufficient);
+    }
+    private void sufficientReadConcern(ReadConcern r,
+                                       boolean replicaSet,
+                                       boolean sufficient) {
+        DB db = mockDB(r, replicaSet ? WriteConcern.MAJORITY : WriteConcern.W1, replicaSet);
+        assertEquals(sufficient, MongoConnection.hasSufficientReadConcern(db));
+    }
+
+    private DB mockDB(ReadConcern r,
+                      WriteConcern w,
+                      boolean replicaSet) {
         ReplicaSetStatus status;
         if (replicaSet) {
             status = mock(ReplicaSetStatus.class);
@@ -88,7 +129,8 @@ public class MongoConnectionTest {
         Mongo mongo = mock(Mongo.class);
         when(db.getMongo()).thenReturn(mongo);
         when(db.getWriteConcern()).thenReturn(w);
+        when(db.getReadConcern()).thenReturn(r);
         when(mongo.getReplicaSetStatus()).thenReturn(status);
-        assertEquals(sufficient, MongoConnection.hasSufficientWriteConcern(db));
+        return db;
     }
 }

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatusTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatusTest.java?rev=1774159&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatusTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoStatusTest.java Wed Dec 14 10:01:16 2016
@@ -0,0 +1,107 @@
+/*
+ * 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.mongo;
+
+import com.mongodb.BasicDBObject;
+import org.apache.jackrabbit.oak.plugins.document.MongoConnectionFactory;
+import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.apache.jackrabbit.oak.plugins.document.MongoUtils.isAvailable;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+public class MongoStatusTest {
+
+    @Rule
+    public MongoConnectionFactory connectionFactory = new MongoConnectionFactory();
+
+    private MongoStatus status;
+
+    @BeforeClass
+    public static void mongoAvailable() {
+        assumeTrue(isAvailable());
+    }
+
+    @Before
+    public void createStatus() {
+        MongoConnection c = connectionFactory.getConnection();
+        status = new MongoStatus(c.getDB());
+    }
+
+    @Test
+    public void testDetails() {
+        String details = status.getServerDetails();
+        assertNotNull(details);
+        assertFalse(details.isEmpty());
+        assertTrue(details.startsWith("{"));
+        assertTrue(details.endsWith("}"));
+        assertTrue(details.contains("host="));
+    }
+
+    @Test
+    public void testReadConcern() {
+        BasicDBObject mockServerStatus = new BasicDBObject();
+        BasicDBObject storageEngine = new BasicDBObject();
+        status.setServerStatus(mockServerStatus);
+
+        assertFalse(status.isMajorityReadConcernSupported());
+
+        mockServerStatus.put("storageEngine", storageEngine);
+        status.setServerStatus(mockServerStatus);
+        assertFalse(status.isMajorityReadConcernSupported());
+
+        storageEngine.put("supportsCommittedReads", false);
+        status.setServerStatus(mockServerStatus);
+        assertFalse(status.isMajorityReadConcernSupported());
+
+        storageEngine.put("supportsCommittedReads", true);
+        status.setServerStatus(mockServerStatus);
+        assertTrue(status.isMajorityReadConcernSupported());
+    }
+
+    @Test
+    public void testGetVersion() {
+        assertTrue(status.getVersion().matches("^\\d+\\.\\d+\\.\\d+$"));
+    }
+
+    @Test
+    public void testCheckVersionValid() {
+        for (String v : new String[] { "2.6.0", "2.7.0", "3.0.0"}) {
+            status.setVersion(v);
+            status.checkVersion();
+        }
+    }
+
+    @Test
+    public void testCheckVersionInvalid() {
+        for (String v : new String[] { "1.0.0", "2.0.0", "2.5.0"}) {
+            status.setVersion(v);
+            try {
+                status.checkVersion();
+                fail("Version " + v + " shouldn't be allowed");
+            } catch (Exception e) {
+            }
+        }
+    }
+}

Modified: jackrabbit/oak/trunk/oak-parent/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-parent/pom.xml?rev=1774159&r1=1774158&r2=1774159&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-parent/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-parent/pom.xml Wed Dec 14 10:01:16 2016
@@ -49,7 +49,7 @@
     <mongo.db2>MongoMKDB2</mongo.db2>
     <segment.db>SegmentMK</segment.db>
     <lucene.version>4.7.1</lucene.version>
-    <mongo.driver.version>3.2.2</mongo.driver.version>
+    <mongo.driver.version>3.4.0</mongo.driver.version>
     <!-- Note that we're using SLF4J API version 1.7 when compiling     -->
     <!-- core Oak components but more recent SLF4J and Logback versions -->
     <!-- when compiling and running test cases and the oak-run jar.     -->