Skip to content

Commit fc6a68a

Browse files
authored
Add server detection based on host names (#1214)
1 parent e1b4b90 commit fc6a68a

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java

+68
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.mongodb.MongoCompressor;
2121
import com.mongodb.MongoCredential;
2222
import com.mongodb.MongoDriverInformation;
23+
import com.mongodb.ServerAddress;
2324
import com.mongodb.ServerApi;
2425
import com.mongodb.connection.ClusterConnectionMode;
2526
import com.mongodb.connection.ClusterId;
@@ -31,18 +32,23 @@
3132
import com.mongodb.event.CommandListener;
3233
import com.mongodb.event.ServerListener;
3334
import com.mongodb.event.ServerMonitorListener;
35+
import com.mongodb.internal.VisibleForTesting;
36+
import com.mongodb.internal.diagnostics.logging.Logger;
37+
import com.mongodb.internal.diagnostics.logging.Loggers;
3438
import com.mongodb.lang.Nullable;
3539
import com.mongodb.spi.dns.DnsClient;
3640
import com.mongodb.spi.dns.InetAddressResolver;
3741

3842
import java.util.List;
3943

44+
import static com.mongodb.internal.connection.DefaultClusterFactory.ClusterEnvironment.detectCluster;
4045
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_CLUSTER_LISTENER;
4146
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_SERVER_LISTENER;
4247
import static com.mongodb.internal.event.EventListenerHelper.NO_OP_SERVER_MONITOR_LISTENER;
4348
import static com.mongodb.internal.event.EventListenerHelper.clusterListenerMulticaster;
4449
import static com.mongodb.internal.event.EventListenerHelper.serverListenerMulticaster;
4550
import static com.mongodb.internal.event.EventListenerHelper.serverMonitorListenerMulticaster;
51+
import static java.lang.String.format;
4652
import static java.util.Collections.singletonList;
4753

4854
/**
@@ -52,6 +58,7 @@
5258
*/
5359
@SuppressWarnings("deprecation")
5460
public final class DefaultClusterFactory {
61+
private static final Logger LOGGER = Loggers.getLogger("DefaultClusterFactory");
5562

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

75+
detectAndLogClusterEnvironment(originalClusterSettings);
76+
6877
ClusterId clusterId = new ClusterId(applicationName);
6978
ClusterSettings clusterSettings;
7079
ServerSettings serverSettings;
@@ -143,4 +152,63 @@ private static ServerMonitorListener getServerMonitorListener(final ServerSettin
143152
? NO_OP_SERVER_MONITOR_LISTENER
144153
: serverMonitorListenerMulticaster(serverSettings.getServerMonitorListeners());
145154
}
155+
156+
@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
157+
public void detectAndLogClusterEnvironment(final ClusterSettings clusterSettings) {
158+
String srvHost = clusterSettings.getSrvHost();
159+
ClusterEnvironment clusterEnvironment;
160+
if (srvHost != null) {
161+
clusterEnvironment = detectCluster(srvHost);
162+
} else {
163+
clusterEnvironment = detectCluster(clusterSettings.getHosts()
164+
.stream()
165+
.map(ServerAddress::getHost)
166+
.toArray(String[]::new));
167+
}
168+
169+
if (clusterEnvironment != null) {
170+
LOGGER.info(format("You appear to be connected to a %s cluster. For more information regarding feature compatibility"
171+
+ " and support please visit %s", clusterEnvironment.clusterProductName, clusterEnvironment.documentationUrl));
172+
}
173+
}
174+
175+
enum ClusterEnvironment {
176+
AZURE("https://www.mongodb.com/supportability/cosmosdb",
177+
"CosmosDB",
178+
".cosmos.azure.com"),
179+
AWS("https://www.mongodb.com/supportability/documentdb",
180+
"DocumentDB",
181+
".docdb.amazonaws.com", ".docdb-elastic.amazonaws.com");
182+
183+
private final String documentationUrl;
184+
private final String clusterProductName;
185+
private final String[] hostSuffixes;
186+
187+
ClusterEnvironment(final String url, final String name, final String... hostSuffixes) {
188+
this.hostSuffixes = hostSuffixes;
189+
this.documentationUrl = url;
190+
this.clusterProductName = name;
191+
}
192+
@Nullable
193+
public static ClusterEnvironment detectCluster(final String... hosts) {
194+
for (String host : hosts) {
195+
for (ClusterEnvironment clusterEnvironment : values()) {
196+
if (clusterEnvironment.isExternalClusterProvider(host)) {
197+
return clusterEnvironment;
198+
}
199+
}
200+
}
201+
return null;
202+
}
203+
204+
private boolean isExternalClusterProvider(final String host) {
205+
for (String hostSuffix : hostSuffixes) {
206+
String lowerCaseHost = host.toLowerCase();
207+
if (lowerCaseHost.endsWith(hostSuffix)) {
208+
return true;
209+
}
210+
}
211+
return false;
212+
}
213+
}
146214
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.internal.connection;
17+
18+
import ch.qos.logback.classic.Level;
19+
import ch.qos.logback.classic.Logger;
20+
import ch.qos.logback.classic.LoggerContext;
21+
import ch.qos.logback.classic.spi.ILoggingEvent;
22+
import ch.qos.logback.core.read.ListAppender;
23+
import com.mongodb.ConnectionString;
24+
import com.mongodb.connection.ClusterSettings;
25+
import org.junit.jupiter.api.AfterAll;
26+
import org.junit.jupiter.api.AfterEach;
27+
import org.junit.jupiter.api.Assertions;
28+
import org.junit.jupiter.api.BeforeAll;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.Arguments;
31+
import org.junit.jupiter.params.provider.MethodSource;
32+
import org.slf4j.LoggerFactory;
33+
34+
import java.util.List;
35+
import java.util.stream.Collectors;
36+
import java.util.stream.Stream;
37+
38+
class DefaultClusterFactoryTest {
39+
private static final String EXPECTED_COSMOS_DB_MESSAGE =
40+
"You appear to be connected to a CosmosDB cluster. For more information regarding "
41+
+ "feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb";
42+
43+
private static final String EXPECTED_DOCUMENT_DB_MESSAGE =
44+
"You appear to be connected to a DocumentDB cluster. For more information regarding "
45+
+ "feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb";
46+
47+
private static final Logger LOGGER = (Logger) LoggerFactory.getLogger("org.mongodb.driver.DefaultClusterFactory");
48+
private static final MemoryAppender MEMORY_APPENDER = new MemoryAppender();
49+
50+
@BeforeAll
51+
public static void setUp() {
52+
MEMORY_APPENDER.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
53+
LOGGER.setLevel(Level.DEBUG);
54+
LOGGER.addAppender(MEMORY_APPENDER);
55+
MEMORY_APPENDER.start();
56+
}
57+
58+
@AfterAll
59+
public static void cleanUp() {
60+
LOGGER.detachAppender(MEMORY_APPENDER);
61+
}
62+
63+
@AfterEach
64+
public void reset() {
65+
MEMORY_APPENDER.reset();
66+
}
67+
68+
static Stream<Arguments> shouldLogAllegedClusterEnvironmentWhenNonGenuineHostsSpecified() {
69+
return Stream.of(
70+
Arguments.of("mongodb://a.MONGO.COSMOS.AZURE.COM:19555", EXPECTED_COSMOS_DB_MESSAGE),
71+
Arguments.of("mongodb://a.mongo.cosmos.azure.com:19555", EXPECTED_COSMOS_DB_MESSAGE),
72+
Arguments.of("mongodb://a.DOCDB-ELASTIC.AMAZONAWS.COM:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
73+
Arguments.of("mongodb://a.docdb-elastic.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
74+
Arguments.of("mongodb://a.DOCDB.AMAZONAWS.COM", EXPECTED_DOCUMENT_DB_MESSAGE),
75+
Arguments.of("mongodb://a.docdb.amazonaws.com", EXPECTED_DOCUMENT_DB_MESSAGE),
76+
77+
/* SRV matching */
78+
Arguments.of("mongodb+srv://A.MONGO.COSMOS.AZURE.COM", EXPECTED_COSMOS_DB_MESSAGE),
79+
Arguments.of("mongodb+srv://a.mongo.cosmos.azure.com", EXPECTED_COSMOS_DB_MESSAGE),
80+
Arguments.of("mongodb+srv://a.DOCDB.AMAZONAWS.COM/", EXPECTED_DOCUMENT_DB_MESSAGE),
81+
Arguments.of("mongodb+srv://a.docdb.amazonaws.com/", EXPECTED_DOCUMENT_DB_MESSAGE),
82+
Arguments.of("mongodb+srv://a.DOCDB-ELASTIC.AMAZONAWS.COM/", EXPECTED_DOCUMENT_DB_MESSAGE),
83+
Arguments.of("mongodb+srv://a.docdb-elastic.amazonaws.com/", EXPECTED_DOCUMENT_DB_MESSAGE),
84+
85+
/* Mixing genuine and nongenuine hosts (unlikely in practice) */
86+
Arguments.of("mongodb://a.example.com:27017,b.mongo.cosmos.azure.com:19555/", EXPECTED_COSMOS_DB_MESSAGE),
87+
Arguments.of("mongodb://a.example.com:27017,b.docdb.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE),
88+
Arguments.of("mongodb://a.example.com:27017,b.docdb-elastic.amazonaws.com:27017/", EXPECTED_DOCUMENT_DB_MESSAGE)
89+
);
90+
}
91+
92+
@ParameterizedTest
93+
@MethodSource
94+
void shouldLogAllegedClusterEnvironmentWhenNonGenuineHostsSpecified(final String connectionString, final String expectedLogMessage) {
95+
//when
96+
ClusterSettings clusterSettings = toClusterSettings(new ConnectionString(connectionString));
97+
new DefaultClusterFactory().detectAndLogClusterEnvironment(clusterSettings);
98+
99+
//then
100+
List<ILoggingEvent> loggedEvents = MEMORY_APPENDER.search(expectedLogMessage);
101+
102+
Assertions.assertEquals(1, loggedEvents.size());
103+
Assertions.assertEquals(Level.INFO, loggedEvents.get(0).getLevel());
104+
105+
}
106+
107+
static Stream<String> shouldNotLogClusterEnvironmentWhenGenuineHostsSpecified() {
108+
return Stream.of(
109+
"mongodb://a.mongo.cosmos.azure.com.tld:19555",
110+
"mongodb://a.docdb-elastic.amazonaws.com.t",
111+
"mongodb+srv://a.example.com",
112+
"mongodb+srv://a.mongodb.net/",
113+
"mongodb+srv://a.mongo.cosmos.azure.com.tld/",
114+
"mongodb+srv://a.docdb-elastic.amazonaws.com.tld/"
115+
);
116+
}
117+
118+
@ParameterizedTest
119+
@MethodSource
120+
void shouldNotLogClusterEnvironmentWhenGenuineHostsSpecified(final String connectionUrl) {
121+
//when
122+
ClusterSettings clusterSettings = toClusterSettings(new ConnectionString(connectionUrl));
123+
new DefaultClusterFactory().detectAndLogClusterEnvironment(clusterSettings);
124+
125+
//then
126+
Assertions.assertEquals(0, MEMORY_APPENDER.search(EXPECTED_COSMOS_DB_MESSAGE).size());
127+
Assertions.assertEquals(0, MEMORY_APPENDER.search(EXPECTED_DOCUMENT_DB_MESSAGE).size());
128+
}
129+
130+
private static ClusterSettings toClusterSettings(final ConnectionString connectionUrl) {
131+
return ClusterSettings.builder().applyConnectionString(connectionUrl).build();
132+
}
133+
134+
public static class MemoryAppender extends ListAppender<ILoggingEvent> {
135+
public void reset() {
136+
this.list.clear();
137+
}
138+
139+
public List<ILoggingEvent> search(final String message) {
140+
return this.list.stream()
141+
.filter(event -> event.getFormattedMessage().contains(message))
142+
.collect(Collectors.toList());
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)