/* * Copyright 2015 MongoDB, Inc. * * Licensed 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 com.mongodb.connection; import com.mongodb.MongoConfigurationException; import com.mongodb.ReadPreference; import com.mongodb.ServerAddress; import com.mongodb.Tag; import com.mongodb.TagSet; import com.mongodb.selector.CompositeServerSelector; import com.mongodb.selector.LatencyMinimizingServerSelector; import com.mongodb.selector.ReadPreferenceServerSelector; import com.mongodb.selector.ServerSelector; import com.mongodb.selector.WritableServerSelector; import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonDocument; import org.bson.BsonInt64; import org.bson.BsonString; import org.bson.BsonValue; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import util.JsonPoweredTestHelper; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; // See https://github.com/mongodb/specifications/tree/master/source/server-selection/tests @RunWith(Parameterized.class) public class ServerSelectionSelectionTest { private final String description; private final BsonDocument definition; private final ClusterDescription clusterDescription; private final long heartbeatFrequencyMS; private final boolean error; public ServerSelectionSelectionTest(final String description, final BsonDocument definition) { this.description = description; this.definition = definition; this.heartbeatFrequencyMS = definition.getNumber("heartbeatFrequencyMS", new BsonInt64(10000)).longValue(); this.error = definition.getBoolean("error", BsonBoolean.FALSE).getValue(); this.clusterDescription = buildClusterDescription(definition.getDocument("topology_description")); } @Test public void shouldPassAllOutcomes() { // skip this test because the driver prohibits maxStaleness or tagSets with mode of primary at a much lower level assumeTrue(!description.equals("max-staleness/server_selection/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.json")); ServerSelector serverSelector = null; List<ServerDescription> suitableServers = buildServerDescriptions(definition.getArray("suitable_servers", new BsonArray())); List<ServerDescription> selectedServers = null; try { serverSelector = getServerSelector(); selectedServers = serverSelector.select(clusterDescription); if (error) { fail("Should have thrown exception"); } } catch (MongoConfigurationException e) { if (!error) { fail("Should not have thrown exception: " + e); } return; } assertServers(selectedServers, suitableServers); ServerSelector latencyBasedServerSelector = new CompositeServerSelector(asList(serverSelector, new LatencyMinimizingServerSelector(15, TimeUnit.MILLISECONDS))); List<ServerDescription> inLatencyWindowServers = buildServerDescriptions(definition.getArray("in_latency_window")); List<ServerDescription> latencyBasedSelectedServers = latencyBasedServerSelector.select(clusterDescription); assertServers(latencyBasedSelectedServers, inLatencyWindowServers); } @Parameterized.Parameters(name = "{0}") public static Collection<Object[]> data() throws URISyntaxException, IOException { List<Object[]> data = new ArrayList<Object[]>(); for (File file : JsonPoweredTestHelper.getTestFiles("/server-selection/server_selection")) { data.add(new Object[]{getDescription("server-selection/server_selection", file), JsonPoweredTestHelper.getTestDocument(file)}); } for (File file : JsonPoweredTestHelper.getTestFiles("/max-staleness/server_selection")) { data.add(new Object[]{getDescription("max-staleness/server_selection", file), JsonPoweredTestHelper.getTestDocument(file)}); } return data; } private static String getDescription(final String root, final File file) { return root + "/" + file.getParentFile().getName() + "/" + file.getName(); } private ClusterDescription buildClusterDescription(final BsonDocument topologyDescription) { ClusterType clusterType = getClusterType(topologyDescription.getString("type").getValue()); ClusterConnectionMode connectionMode = ClusterConnectionMode.MULTIPLE; List<ServerDescription> servers = buildServerDescriptions(topologyDescription.getArray("servers")); return new ClusterDescription(connectionMode, clusterType, servers, null, ServerSettings.builder() .heartbeatFrequency(heartbeatFrequencyMS, TimeUnit.MILLISECONDS) .build()); } private ClusterType getClusterType(final String type) { if (type.equals("Single")) { return ClusterType.STANDALONE; } else if (type.startsWith("ReplicaSet")) { return ClusterType.REPLICA_SET; } else if (type.equals("Sharded")) { return ClusterType.SHARDED; } else if (type.equals("Unknown")) { return ClusterType.UNKNOWN; } throw new UnsupportedOperationException("Unknown topology type: " + type); } private List<ServerDescription> buildServerDescriptions(final BsonArray serverDescriptions) { List<ServerDescription> descriptions = new ArrayList<ServerDescription>(); for (BsonValue document : serverDescriptions) { descriptions.add(buildServerDescription(document.asDocument())); } return descriptions; } private ServerDescription buildServerDescription(final BsonDocument serverDescription) { ServerDescription.Builder builder = ServerDescription.builder(); builder.address(new ServerAddress(serverDescription.getString("address").getValue())); ServerType serverType = getServerType(serverDescription.getString("type").getValue()); builder.ok(serverType != ServerType.UNKNOWN); builder.type(serverType); if (serverDescription.containsKey("tags")) { builder.tagSet(buildTagSet(serverDescription.getDocument("tags"))); } if (serverDescription.containsKey("avg_rtt_ms")) { builder.roundTripTime(serverDescription.getNumber("avg_rtt_ms").asInt32().getValue(), TimeUnit.MILLISECONDS); } builder.state(ServerConnectionState.CONNECTED); if (serverDescription.containsKey("lastWrite")) { builder.lastWriteDate(new Date(serverDescription.getDocument("lastWrite").getNumber("lastWriteDate").longValue())); } if (serverDescription.containsKey("lastUpdateTime")) { builder.lastUpdateTimeNanos(serverDescription.getNumber("lastUpdateTime").longValue() * 1000000); // convert to nanos } else { builder.lastUpdateTimeNanos(42L); } if (serverDescription.containsKey("maxWireVersion")) { builder.maxWireVersion(serverDescription.getNumber("maxWireVersion").intValue()); } return builder.build(); } private ServerType getServerType(final String serverTypeString) { ServerType serverType; if (serverTypeString.equals("RSPrimary")) { serverType = ServerType.REPLICA_SET_PRIMARY; } else if (serverTypeString.equals("RSSecondary")) { serverType = ServerType.REPLICA_SET_SECONDARY; } else if (serverTypeString.equals("RSArbiter")) { serverType = ServerType.REPLICA_SET_ARBITER; } else if (serverTypeString.equals("RSGhost")) { serverType = ServerType.REPLICA_SET_GHOST; } else if (serverTypeString.equals("RSOther")) { serverType = ServerType.REPLICA_SET_OTHER; } else if (serverTypeString.equals("Mongos")) { serverType = ServerType.SHARD_ROUTER; } else if (serverTypeString.equals("Standalone")) { serverType = ServerType.STANDALONE; } else if (serverTypeString.equals("PossiblePrimary")) { serverType = ServerType.UNKNOWN; } else if (serverTypeString.equals("Unknown")) { serverType = ServerType.UNKNOWN; } else { throw new UnsupportedOperationException("No handler for server type " + serverTypeString); } return serverType; } private List<TagSet> buildTagSets(final BsonArray tags) { List<TagSet> tagSets = new ArrayList<TagSet>(); for (BsonValue tag : tags) { tagSets.add(buildTagSet(tag.asDocument())); } return tagSets; } private TagSet buildTagSet(final BsonDocument tags) { List<Tag> tagsSetTags = new ArrayList<Tag>(); for (String key : tags.keySet()) { tagsSetTags.add(new Tag(key, tags.getString(key).getValue())); } return new TagSet(tagsSetTags); } private ServerSelector getServerSelector() { if (definition.getString("operation", new BsonString("read")).getValue().equals("write")) { return new WritableServerSelector(); } else { BsonDocument readPreferenceDefinition = definition.getDocument("read_preference"); ReadPreference readPreference; if (readPreferenceDefinition.getString("mode").getValue().equals("Primary")) { readPreference = ReadPreference.valueOf("Primary"); } else if (readPreferenceDefinition.containsKey("maxStalenessSeconds")) { readPreference = ReadPreference.valueOf(readPreferenceDefinition.getString("mode", new BsonString("Primary")).getValue(), buildTagSets(readPreferenceDefinition.getArray("tag_sets", new BsonArray())), Math.round(readPreferenceDefinition.getNumber("maxStalenessSeconds").doubleValue() * 1000), TimeUnit.MILLISECONDS); } else { readPreference = ReadPreference.valueOf(readPreferenceDefinition.getString("mode", new BsonString("Primary")).getValue(), buildTagSets(readPreferenceDefinition.getArray("tag_sets", new BsonArray()))); } return new ReadPreferenceServerSelector(readPreference); } } private void assertServers(final List<ServerDescription> actual, final List<ServerDescription> expected) { assertEquals(expected.size(), actual.size()); assertTrue(actual.containsAll(expected)); } }