Skip to content

Commit

Permalink
Add server detection based on host names (#1214)
Browse files Browse the repository at this point in the history
  • Loading branch information
vbabanin authored Oct 7, 2023
1 parent e1b4b90 commit fc6a68a
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.mongodb.MongoCompressor;
import com.mongodb.MongoCredential;
import com.mongodb.MongoDriverInformation;
import com.mongodb.ServerAddress;
import com.mongodb.ServerApi;
import com.mongodb.connection.ClusterConnectionMode;
import com.mongodb.connection.ClusterId;
Expand All @@ -31,18 +32,23 @@
import com.mongodb.event.CommandListener;
import com.mongodb.event.ServerListener;
import com.mongodb.event.ServerMonitorListener;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.DnsClient;
import com.mongodb.spi.dns.InetAddressResolver;

import java.util.List;

import static com.mongodb.internal.connection.DefaultClusterFactory.ClusterEnvironment.detectCluster;
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_CLUSTER_LISTENER;
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_SERVER_LISTENER;
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_SERVER_MONITOR_LISTENER;
import static com.mongodb.internal.event.EventListenerHelper.clusterListenerMulticaster;
import static com.mongodb.internal.event.EventListenerHelper.serverListenerMulticaster;
import static com.mongodb.internal.event.EventListenerHelper.serverMonitorListenerMulticaster;
import static java.lang.String.format;
import static java.util.Collections.singletonList;

/**
Expand All @@ -52,6 +58,7 @@
*/
@SuppressWarnings("deprecation")
public final class DefaultClusterFactory {
private static final Logger LOGGER = Loggers.getLogger("DefaultClusterFactory");

public Cluster createCluster(final ClusterSettings originalClusterSettings, final ServerSettings originalServerSettings,
final ConnectionPoolSettings connectionPoolSettings,
Expand All @@ -65,6 +72,8 @@ public Cluster createCluster(final ClusterSettings originalClusterSettings, fina
final List<MongoCompressor> compressorList, @Nullable final ServerApi serverApi,
@Nullable final DnsClient dnsClient, @Nullable final InetAddressResolver inetAddressResolver) {

detectAndLogClusterEnvironment(originalClusterSettings);

ClusterId clusterId = new ClusterId(applicationName);
ClusterSettings clusterSettings;
ServerSettings serverSettings;
Expand Down Expand Up @@ -143,4 +152,63 @@ private static ServerMonitorListener getServerMonitorListener(final ServerSettin
? NO_OP_SERVER_MONITOR_LISTENER
: serverMonitorListenerMulticaster(serverSettings.getServerMonitorListeners());
}

@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
public void detectAndLogClusterEnvironment(final ClusterSettings clusterSettings) {
String srvHost = clusterSettings.getSrvHost();
ClusterEnvironment clusterEnvironment;
if (srvHost != null) {
clusterEnvironment = detectCluster(srvHost);
} else {
clusterEnvironment = detectCluster(clusterSettings.getHosts()
.stream()
.map(ServerAddress::getHost)
.toArray(String[]::new));
}

if (clusterEnvironment != null) {
LOGGER.info(format("You appear to be connected to a %s cluster. For more information regarding feature compatibility"
+ " and support please visit %s", clusterEnvironment.clusterProductName, clusterEnvironment.documentationUrl));
}
}

enum ClusterEnvironment {
AZURE("https://www.mongodb.com/supportability/cosmosdb",
"CosmosDB",
".cosmos.azure.com"),
AWS("https://www.mongodb.com/supportability/documentdb",
"DocumentDB",
".docdb.amazonaws.com", ".docdb-elastic.amazonaws.com");

private final String documentationUrl;
private final String clusterProductName;
private final String[] hostSuffixes;

ClusterEnvironment(final String url, final String name, final String... hostSuffixes) {
this.hostSuffixes = hostSuffixes;
this.documentationUrl = url;
this.clusterProductName = name;
}
@Nullable
public static ClusterEnvironment detectCluster(final String... hosts) {
for (String host : hosts) {
for (ClusterEnvironment clusterEnvironment : values()) {
if (clusterEnvironment.isExternalClusterProvider(host)) {
return clusterEnvironment;
}
}
}
return null;
}

private boolean isExternalClusterProvider(final String host) {
for (String hostSuffix : hostSuffixes) {
String lowerCaseHost = host.toLowerCase();
if (lowerCaseHost.endsWith(hostSuffix)) {
return true;
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2008-present 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.internal.connection;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.mongodb.ConnectionString;
import com.mongodb.connection.ClusterSettings;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class DefaultClusterFactoryTest {
private static final String EXPECTED_COSMOS_DB_MESSAGE =
"You appear to be connected to a CosmosDB cluster. For more information regarding "
+ "feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb";

private static final String EXPECTED_DOCUMENT_DB_MESSAGE =
"You appear to be connected to a DocumentDB cluster. For more information regarding "
+ "feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb";

private static final Logger LOGGER = (Logger) LoggerFactory.getLogger("org.mongodb.driver.DefaultClusterFactory");
private static final MemoryAppender MEMORY_APPENDER = new MemoryAppender();

@BeforeAll
public static void setUp() {
MEMORY_APPENDER.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
LOGGER.setLevel(Level.DEBUG);
LOGGER.addAppender(MEMORY_APPENDER);
MEMORY_APPENDER.start();
}

@AfterAll
public static void cleanUp() {
LOGGER.detachAppender(MEMORY_APPENDER);
}

@AfterEach
public void reset() {
MEMORY_APPENDER.reset();
}

static Stream<Arguments> shouldLogAllegedClusterEnvironmentWhenNonGenuineHostsSpecified() {
return Stream.of(
Arguments.of("mongodb://a.MONGO.COSMOS.AZURE.COM:19555", EXPECTED_COSMOS_DB_MESSAGE),
Arguments.of("mongodb://a.mongo.cosmos.azure.com:19555", EXPECTED_COSMOS_DB_MESSAGE),
Arguments.of("mongodb://a.DOCDB-ELASTIC.AMAZONAWS.COM:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb://a.docdb-elastic.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb://a.DOCDB.AMAZONAWS.COM", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb://a.docdb.amazonaws.com", EXPECTED_DOCUMENT_DB_MESSAGE),

/* SRV matching */
Arguments.of("mongodb+srv://A.MONGO.COSMOS.AZURE.COM", EXPECTED_COSMOS_DB_MESSAGE),
Arguments.of("mongodb+srv://a.mongo.cosmos.azure.com", EXPECTED_COSMOS_DB_MESSAGE),
Arguments.of("mongodb+srv://a.DOCDB.AMAZONAWS.COM/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb+srv://a.docdb.amazonaws.com/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb+srv://a.DOCDB-ELASTIC.AMAZONAWS.COM/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb+srv://a.docdb-elastic.amazonaws.com/", EXPECTED_DOCUMENT_DB_MESSAGE),

/* Mixing genuine and nongenuine hosts (unlikely in practice) */
Arguments.of("mongodb://a.example.com:27017,b.mongo.cosmos.azure.com:19555/", EXPECTED_COSMOS_DB_MESSAGE),
Arguments.of("mongodb://a.example.com:27017,b.docdb.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
Arguments.of("mongodb://a.example.com:27017,b.docdb-elastic.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE)
);
}

@ParameterizedTest
@MethodSource
void shouldLogAllegedClusterEnvironmentWhenNonGenuineHostsSpecified(final String connectionString, final String expectedLogMessage) {
//when
ClusterSettings clusterSettings = toClusterSettings(new ConnectionString(connectionString));
new DefaultClusterFactory().detectAndLogClusterEnvironment(clusterSettings);

//then
List<ILoggingEvent> loggedEvents = MEMORY_APPENDER.search(expectedLogMessage);

Assertions.assertEquals(1, loggedEvents.size());
Assertions.assertEquals(Level.INFO, loggedEvents.get(0).getLevel());

}

static Stream<String> shouldNotLogClusterEnvironmentWhenGenuineHostsSpecified() {
return Stream.of(
"mongodb://a.mongo.cosmos.azure.com.tld:19555",
"mongodb://a.docdb-elastic.amazonaws.com.t",
"mongodb+srv://a.example.com",
"mongodb+srv://a.mongodb.net/",
"mongodb+srv://a.mongo.cosmos.azure.com.tld/",
"mongodb+srv://a.docdb-elastic.amazonaws.com.tld/"
);
}

@ParameterizedTest
@MethodSource
void shouldNotLogClusterEnvironmentWhenGenuineHostsSpecified(final String connectionUrl) {
//when
ClusterSettings clusterSettings = toClusterSettings(new ConnectionString(connectionUrl));
new DefaultClusterFactory().detectAndLogClusterEnvironment(clusterSettings);

//then
Assertions.assertEquals(0, MEMORY_APPENDER.search(EXPECTED_COSMOS_DB_MESSAGE).size());
Assertions.assertEquals(0, MEMORY_APPENDER.search(EXPECTED_DOCUMENT_DB_MESSAGE).size());
}

private static ClusterSettings toClusterSettings(final ConnectionString connectionUrl) {
return ClusterSettings.builder().applyConnectionString(connectionUrl).build();
}

public static class MemoryAppender extends ListAppender<ILoggingEvent> {
public void reset() {
this.list.clear();
}

public List<ILoggingEvent> search(final String message) {
return this.list.stream()
.filter(event -> event.getFormattedMessage().contains(message))
.collect(Collectors.toList());
}
}
}

0 comments on commit fc6a68a

Please sign in to comment.