diff --git a/buildSrc/src/main/kotlin/conventions/testing-base.gradle.kts b/buildSrc/src/main/kotlin/conventions/testing-base.gradle.kts index 8aa6d25a5fd..da4a9b10866 100644 --- a/buildSrc/src/main/kotlin/conventions/testing-base.gradle.kts +++ b/buildSrc/src/main/kotlin/conventions/testing-base.gradle.kts @@ -105,3 +105,7 @@ testlogger { showFailedStandardStreams = true logLevel = LogLevel.LIFECYCLE } + +dependencies { + testImplementation(libs.assertj) +} diff --git a/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java b/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java index 137a2f266e3..35a01e39e18 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java @@ -77,8 +77,11 @@ private ServerTuple(final ClusterableServer server, final ServerDescription desc } } - AbstractMultiServerCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory) { - super(clusterId, settings, serverFactory); + AbstractMultiServerCluster(final ClusterId clusterId, + final ClusterSettings settings, + final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata) { + super(clusterId, settings, serverFactory, clientMetadata); isTrue("connection mode is multiple", settings.getMode() == MULTIPLE); clusterType = settings.getRequiredClusterType(); replicaSetName = settings.getRequiredReplicaSetName(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java index df3e4d1c1fe..63d52ca70c0 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java @@ -55,11 +55,11 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.stream.Stream; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Stream; import static com.mongodb.assertions.Assertions.assertNotNull; import static com.mongodb.assertions.Assertions.isTrue; @@ -101,19 +101,24 @@ abstract class BaseCluster implements Cluster { private final ClusterListener clusterListener; private final Deque waitQueue = new ConcurrentLinkedDeque<>(); private final ClusterClock clusterClock = new ClusterClock(); + private final ClientMetadata clientMetadata; private Thread waitQueueHandler; private volatile boolean isClosed; private volatile ClusterDescription description; - BaseCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory) { + BaseCluster(final ClusterId clusterId, + final ClusterSettings settings, + final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata) { this.clusterId = notNull("clusterId", clusterId); this.settings = notNull("settings", settings); this.serverFactory = notNull("serverFactory", serverFactory); this.clusterListener = singleClusterListener(settings); - clusterListener.clusterOpening(new ClusterOpeningEvent(clusterId)); - description = new ClusterDescription(settings.getMode(), ClusterType.UNKNOWN, Collections.emptyList(), + this.clusterListener.clusterOpening(new ClusterOpeningEvent(clusterId)); + this.description = new ClusterDescription(settings.getMode(), ClusterType.UNKNOWN, Collections.emptyList(), settings, serverFactory.getSettings()); + this.clientMetadata = clientMetadata; } @Override @@ -121,6 +126,11 @@ public ClusterClock getClock() { return clusterClock; } + @Override + public ClientMetadata getClientMetadata() { + return clientMetadata; + } + @Override public ServerTuple selectServer(final ServerSelector serverSelector, final OperationContext operationContext) { isTrue("open", !isClosed()); diff --git a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java new file mode 100644 index 00000000000..e48327cd5a3 --- /dev/null +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java @@ -0,0 +1,57 @@ +/* + * 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 com.mongodb.MongoDriverInformation; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static com.mongodb.internal.Locks.withLock; +import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; +import static com.mongodb.internal.connection.ClientMetadataHelper.updateClientMetadataDocument; + +/** + * Represents metadata of the current MongoClient. + * + *

This class is not part of the public API and may be removed or changed at any time

+ */ +public class ClientMetadata { + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private BsonDocument clientMetadataBsonDocument; + + public ClientMetadata(@Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation) { + withLock(readWriteLock.writeLock(), () -> { + this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, mongoDriverInformation); + }); + } + + /** + * Returns mutable BsonDocument that represents the client metadata. + */ + public BsonDocument getBsonDocument() { + return withLock(readWriteLock.readLock(), () -> clientMetadataBsonDocument); + } + + public void append(final MongoDriverInformation mongoDriverInformation) { + withLock(readWriteLock.writeLock(), () -> + this.clientMetadataBsonDocument = updateClientMetadataDocument(clientMetadataBsonDocument.clone(), mongoDriverInformation) + ); + } +} + diff --git a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java index 825af685c10..2675e0f8efd 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -32,6 +32,7 @@ import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -180,6 +181,44 @@ static boolean clientMetadataDocumentTooLarge(final BsonDocument document) { return buffer.getPosition() > MAXIMUM_CLIENT_METADATA_ENCODED_SIZE; } + /** + * Modifies the given client metadata document by appending the driver information. + * Driver name and version are appended atomically to the existing driver name and version if they do not exceed + * {@value MAXIMUM_CLIENT_METADATA_ENCODED_SIZE} bytes. + *

+ * Platform is appended separately to the existing platform if it does not exceed {@value MAXIMUM_CLIENT_METADATA_ENCODED_SIZE} bytes. + */ + public static BsonDocument updateClientMetadataDocument(final BsonDocument clientMetadataDocument, + final MongoDriverInformation driverInformationToAppend) { + BsonDocument currentDriverInformation = clientMetadataDocument.getDocument("driver"); + + List driverNamesToAppend = driverInformationToAppend.getDriverNames(); + List driverVersionsToAppend = driverInformationToAppend.getDriverVersions(); + List driverPlatformsToAppend = driverInformationToAppend.getDriverPlatforms(); + + List updatedDriverNames = new ArrayList<>(driverNamesToAppend.size() + 1); + List updatedDriverVersions = new ArrayList<>(driverVersionsToAppend.size() + 1); + List updateDriverPlatforms = new ArrayList<>(driverPlatformsToAppend.size() + 1); + + updatedDriverNames.add(currentDriverInformation.getString("name").getValue()); + updatedDriverNames.addAll(driverNamesToAppend); + + updatedDriverVersions.add(currentDriverInformation.getString("version").getValue()); + updatedDriverVersions.addAll(driverVersionsToAppend); + + updateDriverPlatforms.add(clientMetadataDocument.getString("platform").getValue()); + updateDriverPlatforms.addAll(driverPlatformsToAppend); + + tryWithLimit(clientMetadataDocument, d -> { + putAtPath(d, "driver.name", listToString(updatedDriverNames)); + putAtPath(d, "driver.version", listToString(updatedDriverVersions)); + }); + tryWithLimit(clientMetadataDocument, d -> { + putAtPath(d, "platform", listToString(updateDriverPlatforms)); + }); + return clientMetadataDocument; + } + public enum ContainerRuntime { DOCKER("docker") { @Override diff --git a/driver-core/src/main/com/mongodb/internal/connection/Cluster.java b/driver-core/src/main/com/mongodb/internal/connection/Cluster.java index 87fa73c8536..ba154b48308 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/Cluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/Cluster.java @@ -57,6 +57,8 @@ public interface Cluster extends Closeable { */ ClusterClock getClock(); + ClientMetadata getClientMetadata(); + ServerTuple selectServer(ServerSelector serverSelector, OperationContext operationContext); void selectServerAsync(ServerSelector serverSelector, OperationContext operationContext, diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java index 5fb6de6f69a..ac853cb002e 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterFactory.java @@ -107,27 +107,29 @@ public Cluster createCluster(final ClusterSettings originalClusterSettings, fina InternalOperationContextFactory heartBeatOperationContextFactory = new InternalOperationContextFactory(heartbeatTimeoutSettings, serverApi); + ClientMetadata clientMetadata = new ClientMetadata( + applicationName, + mongoDriverInformation != null ? mongoDriverInformation : MongoDriverInformation.builder().build()); + if (clusterSettings.getMode() == ClusterConnectionMode.LOAD_BALANCED) { ClusterableServerFactory serverFactory = new LoadBalancedClusterableServerFactory(serverSettings, connectionPoolSettings, internalConnectionPoolSettings, streamFactory, credential, loggerSettings, commandListener, - applicationName, mongoDriverInformation != null ? mongoDriverInformation : MongoDriverInformation.builder().build(), compressorList, serverApi, clusterOperationContextFactory); - return new LoadBalancedCluster(clusterId, clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + return new LoadBalancedCluster(clusterId, clusterSettings, serverFactory, clientMetadata, dnsSrvRecordMonitorFactory); } else { ClusterableServerFactory serverFactory = new DefaultClusterableServerFactory(serverSettings, connectionPoolSettings, internalConnectionPoolSettings, clusterOperationContextFactory, streamFactory, heartBeatOperationContextFactory, heartbeatStreamFactory, credential, - loggerSettings, commandListener, applicationName, - mongoDriverInformation != null ? mongoDriverInformation : MongoDriverInformation.builder().build(), compressorList, + loggerSettings, commandListener, compressorList, serverApi, FaasEnvironment.getFaasEnvironment() != FaasEnvironment.UNKNOWN); if (clusterSettings.getMode() == ClusterConnectionMode.SINGLE) { - return new SingleServerCluster(clusterId, clusterSettings, serverFactory); + return new SingleServerCluster(clusterId, clusterSettings, serverFactory, clientMetadata); } else if (clusterSettings.getMode() == ClusterConnectionMode.MULTIPLE) { if (clusterSettings.getSrvHost() == null) { - return new MultiServerCluster(clusterId, clusterSettings, serverFactory); + return new MultiServerCluster(clusterId, clusterSettings, serverFactory, clientMetadata); } else { - return new DnsMultiServerCluster(clusterId, clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + return new DnsMultiServerCluster(clusterId, clusterSettings, serverFactory, clientMetadata, dnsSrvRecordMonitorFactory); } } else { throw new UnsupportedOperationException("Unsupported cluster mode: " + clusterSettings.getMode()); diff --git a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java index aa8973ec092..cb9830c4017 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DefaultClusterableServerFactory.java @@ -19,7 +19,6 @@ import com.mongodb.LoggerSettings; 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; @@ -50,8 +49,6 @@ public class DefaultClusterableServerFactory implements ClusterableServerFactory private final MongoCredentialWithCache credential; private final LoggerSettings loggerSettings; private final CommandListener commandListener; - private final String applicationName; - private final MongoDriverInformation mongoDriverInformation; private final List compressorList; @Nullable private final ServerApi serverApi; @@ -63,8 +60,7 @@ public DefaultClusterableServerFactory( final InternalOperationContextFactory clusterOperationContextFactory, final StreamFactory streamFactory, final InternalOperationContextFactory heartbeatOperationContextFactory, final StreamFactory heartbeatStreamFactory, @Nullable final MongoCredential credential, final LoggerSettings loggerSettings, - @Nullable final CommandListener commandListener, @Nullable final String applicationName, - @Nullable final MongoDriverInformation mongoDriverInformation, + @Nullable final CommandListener commandListener, final List compressorList, @Nullable final ServerApi serverApi, final boolean isFunctionAsAServiceEnvironment) { this.serverSettings = serverSettings; this.connectionPoolSettings = connectionPoolSettings; @@ -76,8 +72,6 @@ public DefaultClusterableServerFactory( this.credential = credential == null ? null : new MongoCredentialWithCache(credential); this.loggerSettings = loggerSettings; this.commandListener = commandListener; - this.applicationName = applicationName; - this.mongoDriverInformation = mongoDriverInformation; this.compressorList = compressorList; this.serverApi = serverApi; this.isFunctionAsAServiceEnvironment = isFunctionAsAServiceEnvironment; @@ -88,15 +82,17 @@ public ClusterableServer create(final Cluster cluster, final ServerAddress serve ServerId serverId = new ServerId(cluster.getClusterId(), serverAddress); ClusterConnectionMode clusterMode = cluster.getSettings().getMode(); SameObjectProvider sdamProvider = SameObjectProvider.uninitialized(); + ClientMetadata clientMetadata = cluster.getClientMetadata(); + ServerMonitor serverMonitor = new DefaultServerMonitor(serverId, serverSettings, // no credentials, compressor list, or command listener for the server monitor factory - new InternalStreamConnectionFactory(clusterMode, true, heartbeatStreamFactory, null, applicationName, - mongoDriverInformation, emptyList(), loggerSettings, null, serverApi), + new InternalStreamConnectionFactory(clusterMode, true, heartbeatStreamFactory, null, clientMetadata, + emptyList(), loggerSettings, null, serverApi), clusterMode, serverApi, isFunctionAsAServiceEnvironment, sdamProvider, heartbeatOperationContextFactory); ConnectionPool connectionPool = new DefaultConnectionPool(serverId, - new InternalStreamConnectionFactory(clusterMode, streamFactory, credential, applicationName, - mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi), + new InternalStreamConnectionFactory(clusterMode, streamFactory, credential, clientMetadata, + compressorList, loggerSettings, commandListener, serverApi), connectionPoolSettings, internalConnectionPoolSettings, sdamProvider, clusterOperationContextFactory); ServerListener serverListener = singleServerListener(serverSettings); SdamServerDescriptionManager sdam = new DefaultSdamServerDescriptionManager(cluster, serverId, serverListener, serverMonitor, diff --git a/driver-core/src/main/com/mongodb/internal/connection/DnsMultiServerCluster.java b/driver-core/src/main/com/mongodb/internal/connection/DnsMultiServerCluster.java index 51e28ee5c84..e165146dd29 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/DnsMultiServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/DnsMultiServerCluster.java @@ -40,9 +40,11 @@ public final class DnsMultiServerCluster extends AbstractMultiServerCluster { private final DnsSrvRecordMonitor dnsSrvRecordMonitor; private volatile MongoException srvResolutionException; - public DnsMultiServerCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory, + public DnsMultiServerCluster(final ClusterId clusterId, final ClusterSettings settings, + final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata, final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory) { - super(clusterId, settings, serverFactory); + super(clusterId, settings, serverFactory, clientMetadata); dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(assertNotNull(settings.getSrvHost()), settings.getSrvServiceName(), new DnsSrvRecordInitializer() { private volatile boolean initialized; diff --git a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java index 8b5c840c501..252d62c35f8 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnectionFactory.java @@ -19,24 +19,21 @@ import com.mongodb.AuthenticationMechanism; import com.mongodb.LoggerSettings; import com.mongodb.MongoCompressor; -import com.mongodb.MongoDriverInformation; import com.mongodb.ServerApi; import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.connection.ServerId; import com.mongodb.event.CommandListener; import com.mongodb.lang.Nullable; -import org.bson.BsonDocument; import java.util.List; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; class InternalStreamConnectionFactory implements InternalConnectionFactory { private final ClusterConnectionMode clusterConnectionMode; private final boolean isMonitoringConnection; private final StreamFactory streamFactory; - private final BsonDocument clientMetadataDocument; + private final ClientMetadata clientMetadata; private final List compressorList; private final LoggerSettings loggerSettings; private final CommandListener commandListener; @@ -45,19 +42,20 @@ class InternalStreamConnectionFactory implements InternalConnectionFactory { private final MongoCredentialWithCache credential; InternalStreamConnectionFactory(final ClusterConnectionMode clusterConnectionMode, - final StreamFactory streamFactory, - @Nullable final MongoCredentialWithCache credential, - @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, - final List compressorList, - final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi) { - this(clusterConnectionMode, false, streamFactory, credential, applicationName, mongoDriverInformation, compressorList, + final StreamFactory streamFactory, + @Nullable final MongoCredentialWithCache credential, + final ClientMetadata clientMetadata, + final List compressorList, + final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, + @Nullable final ServerApi serverApi) { + this(clusterConnectionMode, false, streamFactory, credential, clientMetadata, compressorList, loggerSettings, commandListener, serverApi); } InternalStreamConnectionFactory(final ClusterConnectionMode clusterConnectionMode, final boolean isMonitoringConnection, - final StreamFactory streamFactory, - @Nullable final MongoCredentialWithCache credential, - @Nullable final String applicationName, @Nullable final MongoDriverInformation mongoDriverInformation, + final StreamFactory streamFactory, + @Nullable final MongoCredentialWithCache credential, + final ClientMetadata clientMetadata, final List compressorList, final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, @Nullable final ServerApi serverApi) { this.clusterConnectionMode = clusterConnectionMode; @@ -67,7 +65,7 @@ class InternalStreamConnectionFactory implements InternalConnectionFactory { this.loggerSettings = loggerSettings; this.commandListener = commandListener; this.serverApi = serverApi; - this.clientMetadataDocument = createClientMetadataDocument(applicationName, mongoDriverInformation); + this.clientMetadata = clientMetadata; this.credential = credential; } @@ -75,7 +73,7 @@ class InternalStreamConnectionFactory implements InternalConnectionFactory { public InternalConnection create(final ServerId serverId, final ConnectionGenerationSupplier connectionGenerationSupplier) { Authenticator authenticator = credential == null ? null : createAuthenticator(credential); InternalStreamConnectionInitializer connectionInitializer = new InternalStreamConnectionInitializer( - clusterConnectionMode, authenticator, clientMetadataDocument, compressorList, serverApi); + clusterConnectionMode, authenticator, clientMetadata.getBsonDocument(), compressorList, serverApi); return new InternalStreamConnection( clusterConnectionMode, authenticator, isMonitoringConnection, serverId, connectionGenerationSupplier, diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java index ba47236cf4f..2b9a0e8ddd4 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java @@ -76,6 +76,7 @@ final class LoadBalancedCluster implements Cluster { private final ClusterId clusterId; private final ClusterSettings settings; private final ClusterClock clusterClock = new ClusterClock(); + private final ClientMetadata clientMetadata; private final ClusterListener clusterListener; private ClusterDescription description; @Nullable @@ -91,6 +92,7 @@ final class LoadBalancedCluster implements Cluster { private final Condition condition = lock.newCondition(); LoadBalancedCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata, final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory) { assertTrue(settings.getMode() == ClusterConnectionMode.LOAD_BALANCED); LOGGER.info(format("Cluster created with id %s and settings %s", clusterId, settings.getShortDescription())); @@ -100,6 +102,7 @@ final class LoadBalancedCluster implements Cluster { this.clusterListener = singleClusterListener(settings); this.description = new ClusterDescription(settings.getMode(), ClusterType.UNKNOWN, emptyList(), settings, serverFactory.getSettings()); + this.clientMetadata = clientMetadata; if (settings.getSrvHost() == null) { dnsSrvRecordMonitor = null; @@ -204,6 +207,11 @@ public ClusterClock getClock() { return clusterClock; } + @Override + public ClientMetadata getClientMetadata() { + return clientMetadata; + } + @Override public ServerTuple selectServer(final ServerSelector serverSelector, final OperationContext operationContext) { isTrue("open", !isClosed()); diff --git a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java index bcd86fa5205..296240cf39f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java +++ b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedClusterableServerFactory.java @@ -19,7 +19,6 @@ import com.mongodb.LoggerSettings; import com.mongodb.MongoCompressor; import com.mongodb.MongoCredential; -import com.mongodb.MongoDriverInformation; import com.mongodb.ServerAddress; import com.mongodb.ServerApi; import com.mongodb.annotations.ThreadSafe; @@ -47,8 +46,6 @@ public class LoadBalancedClusterableServerFactory implements ClusterableServerFa private final MongoCredentialWithCache credential; private final LoggerSettings loggerSettings; private final CommandListener commandListener; - private final String applicationName; - private final MongoDriverInformation mongoDriverInformation; private final List compressorList; private final ServerApi serverApi; private final InternalOperationContextFactory operationContextFactory; @@ -59,7 +56,6 @@ public LoadBalancedClusterableServerFactory(final ServerSettings serverSettings, final StreamFactory streamFactory, @Nullable final MongoCredential credential, final LoggerSettings loggerSettings, @Nullable final CommandListener commandListener, - @Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation, final List compressorList, @Nullable final ServerApi serverApi, final InternalOperationContextFactory operationContextFactory) { this.serverSettings = serverSettings; @@ -69,8 +65,6 @@ public LoadBalancedClusterableServerFactory(final ServerSettings serverSettings, this.credential = credential == null ? null : new MongoCredentialWithCache(credential); this.loggerSettings = loggerSettings; this.commandListener = commandListener; - this.applicationName = applicationName; - this.mongoDriverInformation = mongoDriverInformation; this.compressorList = compressorList; this.serverApi = serverApi; this.operationContextFactory = operationContextFactory; @@ -79,8 +73,8 @@ public LoadBalancedClusterableServerFactory(final ServerSettings serverSettings, @Override public ClusterableServer create(final Cluster cluster, final ServerAddress serverAddress) { ConnectionPool connectionPool = new DefaultConnectionPool(new ServerId(cluster.getClusterId(), serverAddress), - new InternalStreamConnectionFactory(ClusterConnectionMode.LOAD_BALANCED, streamFactory, credential, applicationName, - mongoDriverInformation, compressorList, loggerSettings, commandListener, serverApi), + new InternalStreamConnectionFactory(ClusterConnectionMode.LOAD_BALANCED, streamFactory, credential, cluster.getClientMetadata(), + compressorList, loggerSettings, commandListener, serverApi), connectionPoolSettings, internalConnectionPoolSettings, EmptyProvider.instance(), operationContextFactory); connectionPool.ready(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/MultiServerCluster.java b/driver-core/src/main/com/mongodb/internal/connection/MultiServerCluster.java index 186fe12dd61..55a11a10228 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/MultiServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/MultiServerCluster.java @@ -26,8 +26,9 @@ */ public final class MultiServerCluster extends AbstractMultiServerCluster { public MultiServerCluster(final ClusterId clusterId, final ClusterSettings settings, - final ClusterableServerFactory serverFactory) { - super(clusterId, settings, serverFactory); + final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata) { + super(clusterId, settings, serverFactory, clientMetadata); isTrue("srvHost is null", settings.getSrvHost() == null); initialize(settings.getHosts()); } diff --git a/driver-core/src/main/com/mongodb/internal/connection/SingleServerCluster.java b/driver-core/src/main/com/mongodb/internal/connection/SingleServerCluster.java index daeb67be54d..c21205559ee 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SingleServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SingleServerCluster.java @@ -49,8 +49,9 @@ public final class SingleServerCluster extends BaseCluster { private final AtomicReference server; - public SingleServerCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory) { - super(clusterId, settings, serverFactory); + public SingleServerCluster(final ClusterId clusterId, final ClusterSettings settings, final ClusterableServerFactory serverFactory, + final ClientMetadata clientMetadata) { + super(clusterId, settings, serverFactory, clientMetadata); isTrue("one server in a direct cluster", settings.getHosts().size() == 1); isTrue("connection mode is single", settings.getMode() == ClusterConnectionMode.SINGLE); diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index f0004cd9e03..b61e2b2f117 100644 --- a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java +++ b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java @@ -50,6 +50,7 @@ import com.mongodb.internal.binding.SingleConnectionBinding; import com.mongodb.internal.connection.AsyncConnection; import com.mongodb.internal.connection.AsynchronousSocketChannelStreamFactory; +import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.connection.DefaultClusterFactory; import com.mongodb.internal.connection.DefaultInetAddressResolver; @@ -127,6 +128,7 @@ public final class ClusterFixture { private static final int COMMAND_NOT_FOUND_ERROR_CODE = 59; public static final long TIMEOUT = 120L; public static final Duration TIMEOUT_DURATION = Duration.ofSeconds(TIMEOUT); + public static final ClientMetadata CLIENT_METADATA = new ClientMetadata("test", MongoDriverInformation.builder().build()); public static final TimeoutSettings TIMEOUT_SETTINGS = new TimeoutSettings(30_000, 10_000, 0, null, SECONDS.toMillis(5)); public static final TimeoutSettings TIMEOUT_SETTINGS_WITH_TIMEOUT = TIMEOUT_SETTINGS.withTimeout(TIMEOUT, SECONDS); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java index 3adafc3a945..ae7bc9e5e7c 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java @@ -28,7 +28,9 @@ import org.bson.codecs.DocumentCodec; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -38,12 +40,16 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import static com.mongodb.client.CrudTestHelper.repeat; import static com.mongodb.client.WithWrapper.withWrapper; import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; import static com.mongodb.internal.connection.ClientMetadataHelper.getOperatingSystemType; +import static com.mongodb.internal.connection.ClientMetadataHelper.updateClientMetadataDocument; +import static java.util.Optional.ofNullable; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -318,6 +324,58 @@ public void testCreateClientMetadataDocument(@Nullable final String appName, fin createClientMetadataDocument(appName, driverInformation)); } + public static java.util.stream.Stream provideDriverInformation() { + return Stream.of( + Arguments.of("1.0", "Framework", "Framework Platform"), + Arguments.of("1.0", "Framework", null), + Arguments.of(null, "Framework", "Framework Platform"), + Arguments.of(null, null, "Framework Platform"), + Arguments.of(null, "Framework", null) + ); + } + + + @ParameterizedTest + @MethodSource("provideDriverInformation") + void testUpdateClientMetadataDocument(@Nullable final String driverVersion, + @Nullable final String driverName, + @Nullable final String driverPlatform) { + //given + MongoDriverInformation initialDriverInformation = MongoDriverInformation.builder() + .driverName("mongo-spark") + .driverVersion("2.0.0") + .driverPlatform("Scala 2.10 / Spark 2.0.0") + .build(); + + BsonDocument initialClientMetadataDocument = createClientMetadataDocument(null, initialDriverInformation); + assertEquals( + createExpectedClientMetadataDocument(null, initialDriverInformation), + initialClientMetadataDocument); + + MongoDriverInformation.Builder builder; + builder = MongoDriverInformation.builder(); + ofNullable(driverName).ifPresent(builder::driverName); + ofNullable(driverVersion).ifPresent(builder::driverVersion); + ofNullable(driverPlatform).ifPresent(builder::driverPlatform); + MongoDriverInformation metadataToAppend = builder.build(); + + //We pass metadataToAppend to a builder and prepend with initial driver information. + MongoDriverInformation expectedUpdatedMetadata = MongoDriverInformation.builder(metadataToAppend) + .driverName("mongo-spark") + .driverVersion("2.0.0") + .driverPlatform("Scala 2.10 / Spark 2.0.0") + .build(); + + //when + BsonDocument updatedClientMetadata = updateClientMetadataDocument(initialClientMetadataDocument.clone(), metadataToAppend); + + //then + assertEquals( + createExpectedClientMetadataDocument(null, expectedUpdatedMetadata), + updatedClientMetadata); + assertNotEquals(updatedClientMetadata, initialClientMetadataDocument); + } + @ParameterizedTest @CsvSource({ "unknown, unknown", diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy index 085a5100198..83ce94f7075 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/CommandHelperSpecification.groovy @@ -29,6 +29,7 @@ import spock.lang.Specification import java.util.concurrent.CountDownLatch +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.LEGACY_HELLO import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.getClusterConnectionMode @@ -44,7 +45,7 @@ class CommandHelperSpecification extends Specification { def setup() { connection = new InternalStreamConnectionFactory(ClusterConnectionMode.SINGLE, new NettyStreamFactory(SocketSettings.builder().build(), getSslSettings()), - getCredentialWithCache(), null, null, [], LoggerSettings.builder().build(), null, getServerApi()) + getCredentialWithCache(), CLIENT_METADATA, [], LoggerSettings.builder().build(), null, getServerApi()) .create(new ServerId(new ClusterId(), getPrimary())) connection.open(OPERATION_CONTEXT) } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java index 6ab01fdfc8a..b95b9c96894 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/PlainAuthenticatorTest.java @@ -32,6 +32,7 @@ import java.util.Collections; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.getClusterConnectionMode; import static com.mongodb.ClusterFixture.getServerApi; @@ -52,8 +53,8 @@ public void setUp() { userName = System.getProperty("org.mongodb.test.userName"); source = System.getProperty("org.mongod.test.source"); password = System.getProperty("org.mongodb.test.password"); - internalConnection = new InternalStreamConnectionFactory(ClusterConnectionMode.SINGLE, streamFactory, null, null, - null, Collections.emptyList(), LoggerSettings.builder().build(), null, getServerApi() + internalConnection = new InternalStreamConnectionFactory(ClusterConnectionMode.SINGLE, streamFactory, null, CLIENT_METADATA, + Collections.emptyList(), LoggerSettings.builder().build(), null, getServerApi() ).create(new ServerId(new ClusterId(), new ServerAddress(host))); connectionDescription = new ConnectionDescription(new ServerId(new ClusterId(), new ServerAddress())); diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy index 266f4e88996..4cd36909238 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ServerMonitorSpecification.groovy @@ -34,6 +34,7 @@ import org.bson.types.ObjectId import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY import static com.mongodb.ClusterFixture.getClusterConnectionMode import static com.mongodb.ClusterFixture.getCredentialWithCache @@ -223,7 +224,7 @@ class ServerMonitorSpecification extends OperationFunctionalSpecification { serverMonitor = new DefaultServerMonitor(new ServerId(new ClusterId(), address), ServerSettings.builder().build(), new InternalStreamConnectionFactory(SINGLE, new SocketStreamFactory(new DefaultInetAddressResolver(), SocketSettings.builder().connectTimeout(500, TimeUnit.MILLISECONDS).build(), getSslSettings()), - getCredentialWithCache(), null, null, [], LoggerSettings.builder().build(), null, + getCredentialWithCache(), CLIENT_METADATA, [], LoggerSettings.builder().build(), null, getServerApi()), getClusterConnectionMode(), getServerApi(), false, SameObjectProvider.initialized(sdam), OPERATION_CONTEXT_FACTORY) diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java index d66bcff46e3..62fa6c27032 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/SingleServerClusterTest.java @@ -36,6 +36,7 @@ import java.util.Collections; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT_FACTORY; import static com.mongodb.ClusterFixture.getCredential; @@ -67,8 +68,8 @@ private void setUpCluster(final ServerAddress serverAddress) { new DefaultClusterableServerFactory(ServerSettings.builder().build(), ConnectionPoolSettings.builder().maxSize(1).build(), InternalConnectionPoolSettings.builder().build(), OPERATION_CONTEXT_FACTORY, streamFactory, OPERATION_CONTEXT_FACTORY, streamFactory, getCredential(), - LoggerSettings.builder().build(), null, null, null, - Collections.emptyList(), getServerApi(), false)); + LoggerSettings.builder().build(), null, + Collections.emptyList(), getServerApi(), false), CLIENT_METADATA); } @After diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java index 0cf8deb479d..92e224df835 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractConnectionPoolTest.java @@ -188,10 +188,11 @@ public void setUp() { pool = new ConnectionIdAdjustingConnectionPool(new DefaultConnectionPool(serverId, new InternalStreamConnectionFactory( connectionMode, - createStreamFactory(SocketSettings.builder().build(), ClusterFixture.getSslSettings()), + createStreamFactory(SocketSettings.builder().build(), + ClusterFixture.getSslSettings()), ClusterFixture.getCredentialWithCache(), - poolOptions.getString("appName", new BsonString(fileName + ": " + description)).getValue(), - MongoDriverInformation.builder().build(), + new ClientMetadata(poolOptions.getString("appName", new BsonString(fileName + ": " + description)).getValue(), + MongoDriverInformation.builder().build()), Collections.emptyList(), LoggerSettings.builder().build(), new TestCommandListener(), diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java index 514f5bde383..fa16462dabb 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/AbstractServerDiscoveryAndMonitoringTest.java @@ -42,6 +42,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.connection.ServerConnectionState.CONNECTING; @@ -187,11 +188,11 @@ protected void init(final ServerListenerFactory serverListenerFactory, final Clu : ClusterSettings.builder(settings).addClusterListener(clusterListener).build(); if (settings.getMode() == ClusterConnectionMode.SINGLE) { - cluster = new SingleServerCluster(clusterId, clusterSettings, factory); + cluster = new SingleServerCluster(clusterId, clusterSettings, factory, CLIENT_METADATA); } else if (settings.getMode() == ClusterConnectionMode.MULTIPLE) { - cluster = new MultiServerCluster(clusterId, clusterSettings, factory); + cluster = new MultiServerCluster(clusterId, clusterSettings, factory, CLIENT_METADATA); } else { - cluster = new LoadBalancedCluster(clusterId, clusterSettings, factory, null); + cluster = new LoadBalancedCluster(clusterId, clusterSettings, factory, CLIENT_METADATA, null); } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy index a509779d09f..56c500c6183 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/BaseClusterSpecification.groovy @@ -36,11 +36,12 @@ import com.mongodb.internal.selector.ReadPreferenceServerSelector import com.mongodb.internal.selector.ServerAddressSelector import com.mongodb.internal.selector.WritableServerSelector import com.mongodb.internal.time.Timeout -import spock.lang.Specification import com.mongodb.spock.Slow +import spock.lang.Specification import java.util.concurrent.CountDownLatch +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS import static com.mongodb.ClusterFixture.createOperationContext @@ -68,7 +69,7 @@ class BaseClusterSpecification extends Specification { .hosts([firstServer, secondServer, thirdServer]) .serverSelector(new ServerAddressSelector(firstServer)) .build() - def cluster = new BaseCluster(new ClusterId(), clusterSettings, factory) { + def cluster = new BaseCluster(new ClusterId(), clusterSettings, factory, CLIENT_METADATA) { @Override protected void connect() { } @@ -114,7 +115,7 @@ class BaseClusterSpecification extends Specification { .serverSelectionTimeout(1, SECONDS) .serverSelector(new ServerAddressSelector(firstServer)) .build() - def cluster = new MultiServerCluster(new ClusterId(), clusterSettings, factory) + def cluster = new MultiServerCluster(new ClusterId(), clusterSettings, factory, CLIENT_METADATA) expect: cluster.getSettings() == clusterSettings @@ -128,7 +129,7 @@ class BaseClusterSpecification extends Specification { .serverSelectionTimeout(1, SECONDS) .serverSelector(new ServerAddressSelector(firstServer)) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(secondServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(thirdServer, REPLICA_SET_PRIMARY, allServers) @@ -144,7 +145,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(secondServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(thirdServer, REPLICA_SET_PRIMARY, allServers) @@ -164,7 +165,7 @@ class BaseClusterSpecification extends Specification { .serverSelector(new ReadPreferenceServerSelector(ReadPreference.secondary())) .localThreshold(5, MILLISECONDS) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, 1, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(secondServer, 7, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) @@ -182,7 +183,7 @@ class BaseClusterSpecification extends Specification { .hosts([firstServer, secondServer, thirdServer]) .localThreshold(5, MILLISECONDS) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, 1, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(secondServer, 7, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(thirdServer, 1, REPLICA_SET_PRIMARY, allServers) @@ -198,7 +199,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer]) .build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(firstServer, ServerDescription.builder().type(ServerType.UNKNOWN) @@ -229,7 +230,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(secondServer, REPLICA_SET_SECONDARY, allServers) factory.sendNotification(thirdServer, REPLICA_SET_PRIMARY, allServers) @@ -253,7 +254,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) when: def latch = new CountDownLatch(1) @@ -283,7 +284,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, allServers) when: @@ -305,7 +306,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) when: def secondServerLatch = selectServerAsync(cluster, secondServer, serverSelectionTimeoutMS) @@ -330,7 +331,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) when: def serverLatch = selectServerAsync(cluster, firstServer) @@ -350,7 +351,7 @@ class BaseClusterSpecification extends Specification { builder().mode(MULTIPLE) .hosts([firstServer, secondServer, thirdServer]) .build(), - factory) + factory, CLIENT_METADATA) when: selectServerAsyncAndGet(cluster, firstServer, serverSelectionTimeoutMS) diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy index 21f03260818..ed18cb8ae86 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DefaultServerSpecification.groovy @@ -49,6 +49,7 @@ import spock.lang.Specification import java.util.concurrent.CountDownLatch +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.MongoCredential.createCredential import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE @@ -386,7 +387,7 @@ class DefaultServerSpecification extends Specification { } private Cluster mockCluster() { - new BaseCluster(new ClusterId(), ClusterSettings.builder().build(), Mock(ClusterableServerFactory)) { + new BaseCluster(new ClusterId(), ClusterSettings.builder().build(), Mock(ClusterableServerFactory), CLIENT_METADATA) { @Override protected void connect() { } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/DnsMultiServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/DnsMultiServerClusterSpecification.groovy index 2c381165acd..930e30b2c7b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/DnsMultiServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/DnsMultiServerClusterSpecification.groovy @@ -16,6 +16,7 @@ package com.mongodb.internal.connection +import com.mongodb.ClusterFixture import com.mongodb.MongoConfigurationException import com.mongodb.ServerAddress import com.mongodb.connection.ClusterId @@ -67,7 +68,7 @@ class DnsMultiServerClusterSpecification extends Specification { .srvHost(srvHost) .mode(MULTIPLE) .build(), - factory, dnsSrvRecordMonitorFactory) + factory, ClusterFixture.CLIENT_METADATA, dnsSrvRecordMonitorFactory) then: 'the monitor is created and started' initializer != null diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java index 27ed86e7b63..d49f67a1e38 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InitialDnsSeedListDiscoveryProseTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -117,6 +118,7 @@ private void doTest(final String srvHost, final String resolvedHost, final boole cluster = new DnsMultiServerCluster(clusterId, settingsBuilder.build(), serverFactory, + CLIENT_METADATA, dnsSrvRecordMonitorFactory); ClusterFixture.sleep(100); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java index ad447f3da65..7366a03b584 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/LoadBalancedClusterTest.java @@ -51,6 +51,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static com.mongodb.ClusterFixture.OPERATION_CONTEXT; import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; import static com.mongodb.ClusterFixture.createOperationContext; @@ -91,7 +92,8 @@ public void shouldSelectServerWhenThereIsNoSRVLookup() { .build(); ClusterableServerFactory serverFactory = mockServerFactory(serverAddress, expectedServer); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, mock(DnsSrvRecordMonitorFactory.class)); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, + mock(DnsSrvRecordMonitorFactory.class)); // when ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); @@ -126,7 +128,7 @@ public void shouldSelectServerWhenThereIsSRVLookup() { when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); // when ServerTuple serverTuple = cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT); @@ -153,7 +155,7 @@ public void shouldSelectServerAsynchronouslyWhenThereIsSRVLookup() { when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); // when FutureResultCallback callback = new FutureResultCallback<>(); @@ -180,7 +182,7 @@ public void shouldFailSelectServerWhenThereIsSRVMisconfiguration() { invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)) .hosts(Arrays.asList(new ServerAddress("host1"), new ServerAddress("host2")))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); MongoClientException exception = assertThrows(MongoClientException.class, () -> cluster.selectServer(mock(ServerSelector.class), OPERATION_CONTEXT)); @@ -204,7 +206,7 @@ public void shouldFailSelectServerAsynchronouslyWhenThereIsSRVMisconfiguration() invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)) .hosts(Arrays.asList(new ServerAddress("host1"), new ServerAddress("host2")))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); FutureResultCallback callback = new FutureResultCallback<>(); cluster.selectServerAsync(mock(ServerSelector.class), OPERATION_CONTEXT, callback); @@ -232,7 +234,7 @@ public void shouldTimeoutSelectServerWhenThereIsSRVLookup() { when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)).sleepTime(Duration.ofHours(1))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); MongoTimeoutException exception = assertThrows(MongoTimeoutException.class, () -> cluster.selectServer(mock(ServerSelector.class), createOperationContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(5)))); @@ -257,7 +259,7 @@ public void shouldTimeoutSelectServerWhenThereIsSRVLookupAndTimeoutMsIsSet() { when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)).sleepTime(Duration.ofHours(1))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); //when & then MongoOperationTimeoutException exception = assertThrows(MongoOperationTimeoutException.class, () -> cluster.selectServer(mock(ServerSelector.class), @@ -284,7 +286,7 @@ public void shouldTimeoutSelectServerWhenThereIsSRVLookupException() { invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)) .sleepTime(Duration.ofMillis(1)) .exception(new MongoConfigurationException("Unable to resolve SRV record"))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); MongoTimeoutException exception = assertThrows(MongoTimeoutException.class, () -> cluster.selectServer(mock(ServerSelector.class), createOperationContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(10)))); @@ -312,7 +314,7 @@ public void shouldTimeoutSelectServerAsynchronouslyWhenThereIsSRVLookup() { when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)).sleepTime(Duration.ofHours(1))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); FutureResultCallback callback = new FutureResultCallback<>(); cluster.selectServerAsync(mock(ServerSelector.class), @@ -341,7 +343,7 @@ public void shouldTimeoutSelectServerAsynchronouslyWhenThereIsSRVLookupException invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)) .sleepTime(Duration.ofMillis(1)) .exception(new MongoConfigurationException("Unable to resolve SRV record"))); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); FutureResultCallback callback = new FutureResultCallback<>(); cluster.selectServerAsync(mock(ServerSelector.class), @@ -362,7 +364,7 @@ void shouldNotInitServerAfterClosing() { when(srvRecordMonitorFactory.create(any(), eq(clusterSettings.getSrvServiceName()), any(DnsSrvRecordInitializer.class))).thenReturn(mock(DnsSrvRecordMonitor.class)); ArgumentCaptor serverInitializerCaptor = ArgumentCaptor.forClass(DnsSrvRecordInitializer.class); // create `cluster` and capture its `DnsSrvRecordInitializer` (server initializer) - LoadBalancedCluster cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, srvRecordMonitorFactory); + LoadBalancedCluster cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, srvRecordMonitorFactory); verify(srvRecordMonitorFactory, times(1)).create(any(), eq(clusterSettings.getSrvServiceName()), serverInitializerCaptor.capture()); // close `cluster`, call `DnsSrvRecordInitializer.initialize` and check that it does not result in creating a `ClusterableServer` cluster.close(); @@ -379,7 +381,7 @@ void shouldCloseServerWhenClosing() { when(serverFactory.create(any(), any())).thenReturn(server); // create `cluster` and check that it creates a `ClusterableServer` LoadBalancedCluster cluster = new LoadBalancedCluster(new ClusterId(), - ClusterSettings.builder().mode(ClusterConnectionMode.LOAD_BALANCED).build(), serverFactory, + ClusterSettings.builder().mode(ClusterConnectionMode.LOAD_BALANCED).build(), serverFactory, CLIENT_METADATA, mock(DnsSrvRecordMonitorFactory.class)); verify(serverFactory, times(1)).create(any(), any()); // close `cluster` and check that it closes `server` @@ -405,7 +407,7 @@ public void synchronousConcurrentTest() throws InterruptedException, ExecutionEx DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory = mock(DnsSrvRecordMonitorFactory.class); when(dnsSrvRecordMonitorFactory.create(eq(srvHostName), eq(clusterSettings.getSrvServiceName()), any())).thenAnswer( invocation -> new TestDnsSrvRecordMonitor(invocation.getArgument(2)).sleepTime(srvResolutionTime)); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); int numThreads = 100; ExecutorService executorService = Executors.newFixedThreadPool(numThreads); @@ -461,7 +463,7 @@ public void asynchronousConcurrentTest() throws InterruptedException, ExecutionE dnsSrvRecordMonitorReference.set(dnsSrvRecordMonitor); return dnsSrvRecordMonitor; }); - cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, dnsSrvRecordMonitorFactory); + cluster = new LoadBalancedCluster(new ClusterId(), clusterSettings, serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); int numThreads = 10; List>> callbacksList = new ArrayList<>(numThreads); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy index e0f932f4963..48d44a7ed87 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy @@ -28,6 +28,7 @@ import com.mongodb.internal.selector.WritableServerSelector import org.bson.types.ObjectId import spock.lang.Specification +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.connection.ClusterConnectionMode.MULTIPLE import static com.mongodb.connection.ClusterType.REPLICA_SET @@ -66,7 +67,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE) .serverSelectionTimeout(1, MILLISECONDS) - .hosts([firstServer]).build(), factory) + .hosts([firstServer]).build(), factory, CLIENT_METADATA) sendNotification(firstServer, REPLICA_SET_PRIMARY) expect: @@ -77,7 +78,7 @@ class MultiServerClusterSpecification extends Specification { def 'should correct report description when connected to a primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, REPLICA_SET_PRIMARY) @@ -90,7 +91,7 @@ class MultiServerClusterSpecification extends Specification { def 'should not get servers snapshot when closed'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts(Arrays.asList(firstServer)).mode(MULTIPLE).build(), - factory) + factory, CLIENT_METADATA) cluster.close() when: @@ -105,7 +106,7 @@ class MultiServerClusterSpecification extends Specification { def 'should discover all hosts in the cluster when notified by the primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer]) @@ -117,7 +118,7 @@ class MultiServerClusterSpecification extends Specification { def 'should discover all hosts in the cluster when notified by a secondary and there is no primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, [firstServer, secondServer, thirdServer]) @@ -129,7 +130,7 @@ class MultiServerClusterSpecification extends Specification { def 'should discover all passives in the cluster'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer], [secondServer, thirdServer]) @@ -142,7 +143,7 @@ class MultiServerClusterSpecification extends Specification { given: def seedListAddress = new ServerAddress('127.0.0.1:27017') def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([seedListAddress]).mode(MULTIPLE).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(seedListAddress, REPLICA_SET_SECONDARY, [firstServer, secondServer], firstServer) @@ -155,7 +156,7 @@ class MultiServerClusterSpecification extends Specification { given: def seedListAddress = new ServerAddress('127.0.0.1:27017') def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().hosts([seedListAddress]).mode(MULTIPLE).build(), factory) + ClusterSettings.builder().hosts([seedListAddress]).mode(MULTIPLE).build(), factory, CLIENT_METADATA) when: factory.sendNotification(seedListAddress, REPLICA_SET_PRIMARY, [firstServer, secondServer], firstServer) @@ -167,7 +168,7 @@ class MultiServerClusterSpecification extends Specification { def 'should remove a server when it no longer appears in hosts reported by the primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory) + ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory, CLIENT_METADATA) sendNotification(firstServer, REPLICA_SET_PRIMARY) sendNotification(secondServer, REPLICA_SET_SECONDARY) sendNotification(thirdServer, REPLICA_SET_SECONDARY) @@ -184,7 +185,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster( CLUSTER_ID, ClusterSettings.builder().requiredClusterType(REPLICA_SET).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(secondServer, SHARD_ROUTER) @@ -198,7 +199,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster( CLUSTER_ID, ClusterSettings.builder().requiredClusterType(REPLICA_SET).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(secondServer, REPLICA_SET_GHOST, []) @@ -213,7 +214,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster( CLUSTER_ID, ClusterSettings.builder().requiredClusterType(REPLICA_SET).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(secondServer, REPLICA_SET_GHOST, [firstServer, secondServer], (String) null) // null replica set name @@ -228,7 +229,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster( CLUSTER_ID, ClusterSettings.builder().requiredClusterType(SHARDED).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) sendNotification(firstServer, SHARD_ROUTER) when: @@ -242,7 +243,7 @@ class MultiServerClusterSpecification extends Specification { def 'should remove a server of wrong type from discovered replica set'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer, secondServer]).build(), factory) + ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer, secondServer]).build(), factory, CLIENT_METADATA) sendNotification(firstServer, REPLICA_SET_PRIMARY) when: @@ -259,7 +260,7 @@ class MultiServerClusterSpecification extends Specification { ClusterSettings.builder() .serverSelectionTimeout(1, MILLISECONDS) .mode(MULTIPLE).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, STANDALONE) @@ -274,7 +275,7 @@ class MultiServerClusterSpecification extends Specification { ClusterSettings.builder() .serverSelectionTimeout(1, MILLISECONDS) .mode(MULTIPLE).hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, REPLICA_SET_GHOST) @@ -293,7 +294,7 @@ class MultiServerClusterSpecification extends Specification { def 'should invalidate existing primary when a new primary notifies'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) sendNotification(firstServer, REPLICA_SET_PRIMARY) when: @@ -307,7 +308,7 @@ class MultiServerClusterSpecification extends Specification { def 'should invalidate new primary if its electionId is less than the previously reported electionId'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer], new ObjectId(new Date(1000))) when: @@ -323,7 +324,7 @@ class MultiServerClusterSpecification extends Specification { given: def serverAddressAlias = new ServerAddress('alternate') def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().mode(MULTIPLE).hosts([serverAddressAlias]).build(), factory) + ClusterSettings.builder().mode(MULTIPLE).hosts([serverAddressAlias]).build(), factory, CLIENT_METADATA) when: sendNotification(serverAddressAlias, REPLICA_SET_PRIMARY) @@ -335,7 +336,7 @@ class MultiServerClusterSpecification extends Specification { def 'should retain a Standalone server given a hosts list of size 1'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, STANDALONE) @@ -348,7 +349,7 @@ class MultiServerClusterSpecification extends Specification { def 'should remove any Standalone server given a hosts list of size greater than one'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, STANDALONE) @@ -364,7 +365,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster( CLUSTER_ID, ClusterSettings.builder().hosts([secondServer]).mode(MULTIPLE).requiredReplicaSetName('test1').build(), - factory) + factory, CLIENT_METADATA) when: factory.sendNotification(secondServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer], 'test2') @@ -377,7 +378,7 @@ class MultiServerClusterSpecification extends Specification { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().serverSelectionTimeout(100, MILLISECONDS).hosts([firstServer]).mode(MULTIPLE).build(), - factory) + factory, CLIENT_METADATA) cluster.close() when: @@ -390,7 +391,7 @@ class MultiServerClusterSpecification extends Specification { def 'should ignore a notification from a server that has been removed'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, thirdServer]) when: @@ -403,7 +404,7 @@ class MultiServerClusterSpecification extends Specification { def 'should add servers from a secondary host list when there is no primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory) + ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_SECONDARY, [firstServer, secondServer]) when: @@ -416,7 +417,7 @@ class MultiServerClusterSpecification extends Specification { def 'should add and removes servers from a primary host list when there is a primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory) + ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer]) when: @@ -435,7 +436,7 @@ class MultiServerClusterSpecification extends Specification { def 'should ignore a secondary host list when there is a primary'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, - ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory) + ClusterSettings.builder().hosts([firstServer, secondServer, thirdServer]).build(), factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer]) when: @@ -448,7 +449,7 @@ class MultiServerClusterSpecification extends Specification { def 'should ignore a notification from a server that is not ok'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer]) when: @@ -473,7 +474,7 @@ class MultiServerClusterSpecification extends Specification { when: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(MULTIPLE).hosts([firstServer]) - .addClusterListener(clusterListener).build(), factory) + .addClusterListener(clusterListener).build(), factory, CLIENT_METADATA) then: 1 * clusterListener.clusterOpening { it.clusterId == CLUSTER_ID } @@ -506,7 +507,7 @@ class MultiServerClusterSpecification extends Specification { def 'should connect to all servers'() { given: def cluster = new MultiServerCluster(CLUSTER_ID, ClusterSettings.builder().hosts([firstServer, secondServer]).build(), - factory) + factory, CLIENT_METADATA) when: cluster.connect() diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy index 3ebd5c4eb0f..faa04a188f9 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/SingleServerClusterSpecification.groovy @@ -28,6 +28,7 @@ import com.mongodb.event.ClusterListener import com.mongodb.internal.selector.WritableServerSelector import spock.lang.Specification +import static com.mongodb.ClusterFixture.CLIENT_METADATA import static com.mongodb.ClusterFixture.OPERATION_CONTEXT import static com.mongodb.connection.ClusterConnectionMode.SINGLE import static com.mongodb.connection.ClusterType.REPLICA_SET @@ -54,7 +55,7 @@ class SingleServerClusterSpecification extends Specification { def 'should update description when the server connects'() { given: def cluster = new SingleServerCluster(CLUSTER_ID, - ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory) + ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory, CLIENT_METADATA) when: sendNotification(firstServer, STANDALONE) @@ -71,7 +72,7 @@ class SingleServerClusterSpecification extends Specification { def 'should get server when open'() { given: def cluster = new SingleServerCluster(CLUSTER_ID, - ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory) + ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory, CLIENT_METADATA) when: sendNotification(firstServer, STANDALONE) @@ -90,7 +91,7 @@ class SingleServerClusterSpecification extends Specification { def 'should not get servers snapshot when closed'() { given: def cluster = new SingleServerCluster(CLUSTER_ID, - ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory) + ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)).build(), factory, CLIENT_METADATA) cluster.close() when: @@ -108,7 +109,7 @@ class SingleServerClusterSpecification extends Specification { given: def cluster = new SingleServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(SINGLE).requiredClusterType(ClusterType.SHARDED).hosts(Arrays.asList(firstServer)).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, ServerType.REPLICA_SET_PRIMARY) @@ -125,7 +126,7 @@ class SingleServerClusterSpecification extends Specification { given: def cluster = new SingleServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(SINGLE).requiredReplicaSetName('test1').hosts(Arrays.asList(firstServer)).build(), - factory) + factory, CLIENT_METADATA) when: sendNotification(firstServer, ServerType.REPLICA_SET_PRIMARY, 'test1') @@ -141,7 +142,7 @@ class SingleServerClusterSpecification extends Specification { def 'getServer should throw when cluster is incompatible'() { given: def cluster = new SingleServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(SINGLE).hosts(Arrays.asList(firstServer)) - .serverSelectionTimeout(1, SECONDS).build(), factory) + .serverSelectionTimeout(1, SECONDS).build(), factory, CLIENT_METADATA) sendNotification(firstServer, getBuilder(firstServer).minWireVersion(1000).maxWireVersion(1000).build()) when: @@ -157,7 +158,7 @@ class SingleServerClusterSpecification extends Specification { def 'should connect to server'() { given: def cluster = new SingleServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(SINGLE).hosts([firstServer]).build(), - factory) + factory, CLIENT_METADATA) when: cluster.connect() @@ -181,7 +182,7 @@ class SingleServerClusterSpecification extends Specification { when: def cluster = new SingleServerCluster(CLUSTER_ID, ClusterSettings.builder().mode(SINGLE).hosts([firstServer]) .addClusterListener(listener).build(), - factory) + factory, CLIENT_METADATA) then: 1 * listener.clusterOpening { it.clusterId == CLUSTER_ID } diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/SrvPollingProseTests.java b/driver-core/src/test/unit/com/mongodb/internal/connection/SrvPollingProseTests.java index a0f08a82360..51cc4884f02 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/SrvPollingProseTests.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/SrvPollingProseTests.java @@ -36,6 +36,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static com.mongodb.ClusterFixture.CLIENT_METADATA; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -198,7 +199,7 @@ private void initCluster(final TestDnsResolver dnsResolver, @Nullable final Inte invocation.getArgument(2), clusterId, dnsResolver); return dnsSrvRecordMonitor; }); - cluster = new DnsMultiServerCluster(clusterId, settingsBuilder.srvMaxHosts(srvMaxHosts).build(), serverFactory, + cluster = new DnsMultiServerCluster(clusterId, settingsBuilder.srvMaxHosts(srvMaxHosts).build(), serverFactory, CLIENT_METADATA, dnsSrvRecordMonitorFactory); try { Thread.sleep(100); // racy diff --git a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt index bfa48ef1e1c..4a97557d14a 100644 --- a/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt +++ b/driver-kotlin-coroutine/src/integrationTest/kotlin/com/mongodb/kotlin/client/coroutine/syncadapter/SyncMongoClient.kt @@ -15,6 +15,7 @@ */ package com.mongodb.kotlin.client.coroutine.syncadapter +import com.mongodb.MongoDriverInformation import com.mongodb.client.MongoClient as JMongoClient import com.mongodb.connection.ClusterDescription import com.mongodb.kotlin.client.coroutine.MongoClient @@ -23,4 +24,7 @@ internal class SyncMongoClient(override val wrapped: MongoClient) : SyncMongoClu override fun close(): Unit = wrapped.close() override fun getClusterDescription(): ClusterDescription = wrapped.getClusterDescription() + + override fun appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } diff --git a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt index 68b937588d9..64832903b40 100644 --- a/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt +++ b/driver-kotlin-coroutine/src/main/kotlin/com/mongodb/kotlin/client/coroutine/MongoClient.kt @@ -110,6 +110,21 @@ public class MongoClient(private val wrapped: JMongoClient) : MongoCluster(wrapp * @see com.mongodb.MongoClientSettings.Builder.applyToClusterSettings */ public fun getClusterDescription(): ClusterDescription = wrapped.clusterDescription + + /** + * Appends the provided [MongoDriverInformation] to the existing metadata. + * + * This enables frameworks and libraries to include identifying metadata (e.g., name, version, platform) which might + * be visible in the MongoD/MongoS logs. This can assist with diagnostics by making client identity visible to the + * server. + * + * **Note:** Metadata is limited to 512 bytes; any excess will be truncated. + * + * @param mongoDriverInformation the driver information to append to the existing metadata + * @since 5.6 + */ + public fun appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } /** diff --git a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt index fd66e4de31b..b1dc72e6a81 100644 --- a/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt +++ b/driver-kotlin-coroutine/src/test/kotlin/com/mongodb/kotlin/client/coroutine/MongoClientTest.kt @@ -16,6 +16,7 @@ package com.mongodb.kotlin.client.coroutine import com.mongodb.ClientSessionOptions +import com.mongodb.MongoDriverInformation import com.mongodb.MongoNamespace import com.mongodb.client.model.bulk.ClientBulkWriteOptions import com.mongodb.client.model.bulk.ClientNamespacedWriteModel @@ -70,6 +71,22 @@ class MongoClientTest { verifyNoMoreInteractions(wrapped) } + @Test + fun shouldCallTheUnderlyingAppendMetadata() { + val mongoClient = MongoClient(wrapped) + + val mongoDriverInformation = + MongoDriverInformation.builder() + .driverName("kotlin") + .driverPlatform("kotlin/${KotlinVersion.CURRENT}") + .build() + + mongoClient.appendMetadata(mongoDriverInformation) + + verify(wrapped).appendMetadata(mongoDriverInformation) + verifyNoMoreInteractions(wrapped) + } + @Test fun shouldCallTheUnderlyingGetDatabase() { val mongoClient = MongoClient(wrapped) diff --git a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt index 16660562a33..02c58833df5 100644 --- a/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt +++ b/driver-kotlin-sync/src/integrationTest/kotlin/com/mongodb/kotlin/client/syncadapter/SyncMongoClient.kt @@ -15,6 +15,7 @@ */ package com.mongodb.kotlin.client.syncadapter +import com.mongodb.MongoDriverInformation import com.mongodb.client.MongoClient as JMongoClient import com.mongodb.connection.ClusterDescription import com.mongodb.kotlin.client.MongoClient @@ -23,4 +24,6 @@ internal class SyncMongoClient(override val wrapped: MongoClient) : SyncMongoClu override fun close(): Unit = wrapped.close() override fun getClusterDescription(): ClusterDescription = wrapped.clusterDescription + override fun appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } diff --git a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt index 4d8d2f26cc0..c71e59520b6 100644 --- a/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt +++ b/driver-kotlin-sync/src/main/kotlin/com/mongodb/kotlin/client/MongoClient.kt @@ -109,6 +109,21 @@ public class MongoClient(private val wrapped: JMongoClient) : MongoCluster(wrapp */ public val clusterDescription: ClusterDescription get() = wrapped.clusterDescription + + /** + * Appends the provided [MongoDriverInformation] to the existing metadata. + * + * This enables frameworks and libraries to include identifying metadata (e.g., name, version, platform) which might + * be visible in the MongoD/MongoS logs. This can assist with diagnostics by making client identity visible to the + * server. + * + * **Note:** Metadata is limited to 512 bytes; any excess will be truncated. + * + * @param mongoDriverInformation the driver information to append to the existing metadata + * @since 5.6 + */ + public fun appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } /** diff --git a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt index 0aa0c582ff4..a6f67b22ce7 100644 --- a/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt +++ b/driver-kotlin-sync/src/test/kotlin/com/mongodb/kotlin/client/MongoClientTest.kt @@ -16,6 +16,7 @@ package com.mongodb.kotlin.client import com.mongodb.ClientSessionOptions +import com.mongodb.MongoDriverInformation import com.mongodb.MongoNamespace import com.mongodb.client.MongoClient as JMongoClient import com.mongodb.client.model.bulk.ClientBulkWriteOptions @@ -44,6 +45,7 @@ class MongoClientTest { @Test fun shouldHaveTheSameMethods() { val jMongoClientFunctions = JMongoClient::class.declaredFunctions.map { it.name }.toSet() + val kMongoClientFunctions = MongoClient::class.declaredFunctions.map { it.name }.toSet() + MongoClient::class @@ -74,6 +76,22 @@ class MongoClientTest { verifyNoMoreInteractions(wrapped) } + @Test + fun shouldCallTheUnderlyingAppendMetadata() { + val mongoClient = MongoClient(wrapped) + + val mongoDriverInformation = + MongoDriverInformation.builder() + .driverName("kotlin") + .driverPlatform("kotlin/${KotlinVersion.CURRENT}") + .build() + + mongoClient.appendMetadata(mongoDriverInformation) + + verify(wrapped).appendMetadata(mongoDriverInformation) + verifyNoMoreInteractions(wrapped) + } + @Test fun shouldCallTheUnderlyingGetDatabase() { val mongoClient = MongoClient(wrapped) diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClient.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClient.java index 061fd3c8bed..87a3148b8b2 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClient.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/MongoClient.java @@ -16,6 +16,7 @@ package com.mongodb.reactivestreams.client; +import com.mongodb.MongoDriverInformation; import com.mongodb.annotations.Immutable; import com.mongodb.connection.ClusterDescription; import com.mongodb.connection.ClusterSettings; @@ -58,4 +59,18 @@ public interface MongoClient extends MongoCluster, Closeable { * @since 4.1 */ ClusterDescription getClusterDescription(); + + /** + * Appends the provided {@link MongoDriverInformation} to the existing metadata. + * + *

+ * This enables frameworks and libraries to include identifying metadata (e.g., name, version, platform) which might be visible in + * the MongoD/MongoS logs. This can assist with diagnostics by making client identity visible to the server. + *

+ * Note: Metadata is limited to 512 bytes; any excess will be truncated. + * + * @param mongoDriverInformation the driver information to append to the existing metadata + * @since 5.6 + */ + void appendMetadata(MongoDriverInformation mongoDriverInformation); } diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java index 3d4822eb7e3..473ec0915d0 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/MongoClientImpl.java @@ -325,4 +325,9 @@ public MongoDatabase getDatabase(final String name) { public ClusterDescription getClusterDescription() { return getCluster().getCurrentDescription(); } + + @Override + public void appendMetadata(final MongoDriverInformation mongoDriverInformation) { + getCluster().getClientMetadata().append(mongoDriverInformation); + } } diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/AbstractClientMetadataProseTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/AbstractClientMetadataProseTest.java new file mode 100644 index 00000000000..60343711ba9 --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/AbstractClientMetadataProseTest.java @@ -0,0 +1,34 @@ +/* + * 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.reactivestreams.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoDriverInformation; +import com.mongodb.client.AbstractClientMetadataProseTest; +import com.mongodb.client.MongoClient; +import com.mongodb.lang.Nullable; +import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient; + +/** + * See spec + */ +class ClientMetadataProseTest extends AbstractClientMetadataProseTest { + + protected MongoClient createMongoClient(@Nullable final MongoDriverInformation mongoDriverInformation, final MongoClientSettings mongoClientSettings) { + return new SyncMongoClient(MongoClients.create(mongoClientSettings, mongoDriverInformation)); + } +} diff --git a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoClient.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoClient.java index 3f2265cb795..3c67440c675 100644 --- a/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoClient.java +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/syncadapter/SyncMongoClient.java @@ -18,6 +18,7 @@ import com.mongodb.ClientBulkWriteException; import com.mongodb.ClientSessionOptions; +import com.mongodb.MongoDriverInformation; import com.mongodb.ReadConcern; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; @@ -29,8 +30,8 @@ import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoIterable; import com.mongodb.client.model.bulk.ClientBulkWriteOptions; -import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.client.model.bulk.ClientBulkWriteResult; +import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.connection.ClusterDescription; import com.mongodb.reactivestreams.client.internal.BatchCursor; import org.bson.Document; @@ -311,4 +312,8 @@ public ClusterDescription getClusterDescription() { return wrapped.getClusterDescription(); } + @Override + public void appendMetadata(final MongoDriverInformation mongoDriverInformation) { + wrapped.appendMetadata(mongoDriverInformation); + } } diff --git a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncMongoClient.scala b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncMongoClient.scala index 4daa6d94ef1..b0617e95fd7 100644 --- a/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncMongoClient.scala +++ b/driver-scala/src/integrationTest/scala/org/mongodb/scala/syncadapter/SyncMongoClient.scala @@ -1,15 +1,8 @@ package org.mongodb.scala.syncadapter -import com.mongodb.ClientSessionOptions -import com.mongodb.client.{ ClientSession, MongoClient => JMongoClient, MongoDatabase => JMongoDatabase } -import org.bson.Document -import org.bson.conversions.Bson +import com.mongodb.MongoDriverInformation +import com.mongodb.client.{ MongoClient => JMongoClient } import org.mongodb.scala.MongoClient -import org.mongodb.scala.bson.DefaultHelper.DefaultsTo - -import scala.collection.JavaConverters._ -import scala.concurrent.Await -import scala.reflect.ClassTag case class SyncMongoClient(wrapped: MongoClient) extends SyncMongoCluster(wrapped) with JMongoClient { @@ -17,4 +10,6 @@ case class SyncMongoClient(wrapped: MongoClient) extends SyncMongoCluster(wrappe override def getClusterDescription = throw new UnsupportedOperationException + override def appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } diff --git a/driver-scala/src/main/scala/org/mongodb/scala/MongoClient.scala b/driver-scala/src/main/scala/org/mongodb/scala/MongoClient.scala index c6849c550c1..ba4510d308d 100644 --- a/driver-scala/src/main/scala/org/mongodb/scala/MongoClient.scala +++ b/driver-scala/src/main/scala/org/mongodb/scala/MongoClient.scala @@ -132,4 +132,20 @@ case class MongoClient(private val wrapped: JMongoClient) extends MongoCluster(w */ def getClusterDescription: ClusterDescription = wrapped.getClusterDescription + + /** + * Appends the provided [[MongoDriverInformation]] to the existing metadata. + * + * + * This enables frameworks and libraries to include identifying metadata (e.g., name, version, platform) which might be visible in + * the MongoD/MongoS logs. This can assist with diagnostics by making client identity visible to the server. + * + * + * **Note:** Metadata is limited to 512 bytes; any excess will be truncated. + * + * @param mongoDriverInformation the driver information to append to the existing metadata + * @since 5.6 + */ + def appendMetadata(mongoDriverInformation: MongoDriverInformation): Unit = + wrapped.appendMetadata(mongoDriverInformation) } diff --git a/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala b/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala index a888e33ae7f..ca5b4f8734e 100644 --- a/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala +++ b/driver-scala/src/test/scala/org/mongodb/scala/MongoClientSpec.scala @@ -19,7 +19,7 @@ package org.mongodb.scala import com.mongodb.reactivestreams.client.{ MongoClient => JMongoClient } import org.bson.BsonDocument import org.mockito.Mockito.verify -import org.mongodb.scala.model.bulk.{ ClientBulkWriteOptions, ClientBulkWriteResult, ClientNamespacedWriteModel } +import org.mongodb.scala.model.bulk.{ ClientBulkWriteOptions, ClientNamespacedWriteModel } import org.scalatestplus.mockito.MockitoSugar import scala.collection.JavaConverters._ @@ -136,4 +136,10 @@ class MongoClientSpec extends BaseSpec with MockitoSugar { mongoClient.getClusterDescription verify(wrapped).getClusterDescription } + + it should "call the underlying appendMetadata" in { + val driverInformation = MongoDriverInformation.builder().build() + mongoClient.appendMetadata(driverInformation) + verify(wrapped).appendMetadata(driverInformation) + } } diff --git a/driver-sync/src/main/com/mongodb/client/MongoClient.java b/driver-sync/src/main/com/mongodb/client/MongoClient.java index 14519e2413a..e61ebf92566 100644 --- a/driver-sync/src/main/com/mongodb/client/MongoClient.java +++ b/driver-sync/src/main/com/mongodb/client/MongoClient.java @@ -16,6 +16,7 @@ package com.mongodb.client; +import com.mongodb.MongoDriverInformation; import com.mongodb.annotations.Immutable; import com.mongodb.connection.ClusterDescription; import com.mongodb.connection.ClusterSettings; @@ -61,4 +62,17 @@ public interface MongoClient extends MongoCluster, Closeable { * @since 3.11 */ ClusterDescription getClusterDescription(); + + /** + * Appends the provided {@link MongoDriverInformation} to the existing metadata. + *

+ * This enables frameworks and libraries to include identifying metadata (e.g., name, version, platform) which might be visible in + * the MongoD/MongoS logs. This can assist with diagnostics by making client identity visible to the server. + *

+ * Note: Metadata is limited to 512 bytes; any excess will be truncated. + * + * @param mongoDriverInformation the driver information to append to the existing metadata + * @since 5.6 + */ + void appendMetadata(MongoDriverInformation mongoDriverInformation); } diff --git a/driver-sync/src/main/com/mongodb/client/MongoCluster.java b/driver-sync/src/main/com/mongodb/client/MongoCluster.java index f097f71288f..8ffc54612b0 100644 --- a/driver-sync/src/main/com/mongodb/client/MongoCluster.java +++ b/driver-sync/src/main/com/mongodb/client/MongoCluster.java @@ -28,10 +28,10 @@ import com.mongodb.annotations.Immutable; import com.mongodb.annotations.Reason; import com.mongodb.client.model.bulk.ClientBulkWriteOptions; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; import com.mongodb.client.model.bulk.ClientNamespacedDeleteManyModel; import com.mongodb.client.model.bulk.ClientNamespacedUpdateManyModel; import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; -import com.mongodb.client.model.bulk.ClientBulkWriteResult; import com.mongodb.lang.Nullable; import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java index cf9ca2a3b7d..47e3266cbf1 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -135,6 +135,11 @@ public ClusterDescription getClusterDescription() { return delegate.getCluster().getCurrentDescription(); } + @Override + public void appendMetadata(final MongoDriverInformation mongoDriverInformation) { + delegate.getCluster().getClientMetadata().append(mongoDriverInformation); + } + @Override public CodecRegistry getCodecRegistry() { return delegate.getCodecRegistry(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java new file mode 100644 index 00000000000..e798e0ea87d --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java @@ -0,0 +1,266 @@ +/* + * 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.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoDriverInformation; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.ConnectionClosedEvent; +import com.mongodb.internal.connection.InternalStreamConnection; +import com.mongodb.internal.connection.TestCommandListener; +import com.mongodb.internal.connection.TestConnectionPoolListener; +import com.mongodb.lang.Nullable; +import org.bson.BsonDocument; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import static com.mongodb.ClusterFixture.isAuthenticated; +import static com.mongodb.ClusterFixture.isLoadBalanced; +import static com.mongodb.ClusterFixture.sleep; +import static java.util.Optional.ofNullable; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +/** + * See spec + */ +public abstract class AbstractClientMetadataProseTest { + + private TestCommandListener commandListener; + private TestConnectionPoolListener connectionPoolListener; + + protected abstract MongoClient createMongoClient(@Nullable MongoDriverInformation driverInformation, + MongoClientSettings mongoClientSettings); + + @BeforeEach + public void setUp() { + assumeFalse(isLoadBalanced()); + assumeFalse(isAuthenticated()); + + commandListener = new TestCommandListener(); + connectionPoolListener = new TestConnectionPoolListener(); + InternalStreamConnection.setRecordEverything(true); + } + + @AfterEach + public void tearDown() { + InternalStreamConnection.setRecordEverything(false); + } + + public static Stream provideDriverInformation() { + return Stream.of( + Arguments.of("1.0", "Framework", "Framework Platform"), + Arguments.of("1.0", "Framework", null), + Arguments.of(null, "Framework", "Framework Platform"), + Arguments.of(null, "Framework", null) + ); + } + + @ParameterizedTest + @MethodSource("provideDriverInformation") + void shouldAppendToPreviousMetadataWhenUpdatedAfterInitialization(@Nullable final String driverVersion, + @Nullable final String driverName, + @Nullable final String driverPlatform) { + //given + MongoDriverInformation initialWrappingLibraryDriverInformation = MongoDriverInformation.builder() + .driverName("library") + .driverVersion("1.2") + .driverPlatform("Library Platform") + .build(); + + try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> + builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS)) + .build())) { + + //TODO change get() to orElseThrow + BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + BsonDocument driverInformation = initialClientMetadata.getDocument("driver"); + String generatedDriverName = driverInformation.get("name").asString().getValue(); + String generatedVersionName = driverInformation.get("version").asString().getValue(); + String generatedPlatformName = initialClientMetadata.get("platform").asString().getValue(); + + //when + sleep(5); // wait for connection to become idle + updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient); + + //then + //TODO change get() to orElseThrow + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + BsonDocument updatedDriverInformation = updatedClientMetadata.getDocument("driver"); + + String expectedDriverName = driverName == null ? generatedDriverName : generatedDriverName + "|" + driverName; + String expectedDriverVersion = driverVersion == null ? generatedVersionName : generatedVersionName + "|" + driverVersion; + String expectedDriverPlatform = driverPlatform == null ? generatedPlatformName : generatedPlatformName + "|" + driverPlatform; + + assertThat(updatedDriverInformation.getString("name").getValue()).isEqualTo(expectedDriverName); + assertThat(updatedDriverInformation.getString("version").getValue()).endsWith(expectedDriverVersion); + assertThat(updatedClientMetadata.getString("platform").getValue()).endsWith(expectedDriverPlatform); + assertThat(withRemovedKeys(updatedClientMetadata, "driver", "platform")) + .usingRecursiveAssertion() + .isEqualTo(withRemovedKeys(initialClientMetadata, "driver", "platform")); + } + } + + @ParameterizedTest + @MethodSource("provideDriverInformation") + void shouldAppendToDefaultClientMetadataWhenUpdatedAfterInitialization(@Nullable final String driverVersion, + @Nullable final String driverName, + @Nullable final String driverPlatform) { + //given + try (MongoClient mongoClient = createMongoClient(null, getMongoClientSettingsBuilder() + .applyToConnectionPoolSettings(builder -> + builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS)) + .build())) { + + //TODO change get() to orElseThrow + BsonDocument initialClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + + BsonDocument generatedDriverInformation = initialClientMetadata.getDocument("driver"); + String generatedDriverName = generatedDriverInformation.get("name").asString().getValue(); + String generatedVersionName = generatedDriverInformation.get("version").asString().getValue(); + String generatedPlatformName = initialClientMetadata.get("platform").asString().getValue(); + + //when + sleep(5); // wait for connection to become idle + updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient); + + //then + //TODO change get() to orElseThrow + BsonDocument updatedClientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + BsonDocument updatedDriverInformation = updatedClientMetadata.getDocument("driver"); + + String expectedDriverName = driverName == null ? generatedDriverName : generatedDriverName + "|" + driverName; + String expectedDriverVersion = driverVersion == null ? generatedVersionName : generatedVersionName + "|" + driverVersion; + String expectedDriverPlatform = driverPlatform == null ? generatedPlatformName : generatedPlatformName + "|" + driverPlatform; + + assertThat(updatedDriverInformation.getString("name").getValue()).isEqualTo(expectedDriverName); + assertThat(updatedDriverInformation.getString("version").getValue()).endsWith(expectedDriverVersion); + assertThat(updatedClientMetadata.getString("platform").getValue()).endsWith(expectedDriverPlatform); + assertThat(withRemovedKeys(updatedClientMetadata, "driver", "platform")) + .usingRecursiveAssertion() + .isEqualTo(withRemovedKeys(initialClientMetadata, "driver", "platform")); + } + } + + @Test + void shouldNotCloseExistingConnectionsToAppendMetadata() { + //given + MongoDriverInformation initialWrappingLibraryDriverInformation = MongoDriverInformation.builder() + .driverName("library") + .driverVersion("1.2") + .driverPlatform("Library Platform") + .build(); + + try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettingsBuilder() + .build())) { + + //TODO change get() to orElseThrow + BsonDocument clientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + BsonDocument driverInformation = clientMetadata.getDocument("driver"); + assertNotNull(driverInformation); + + //when + mongoClient.appendMetadata(initialWrappingLibraryDriverInformation); + + //then + assertThat(executePingAndCaptureMetadataHandshake(mongoClient)).isEmpty(); + assertFalse(connectionPoolListener.getEvents().stream().anyMatch(ConnectionClosedEvent.class::isInstance), + "Expected no connection closed events"); + } + } + + // Not a prose test. Additional test for better coverage. + @Test + void shouldAppendProvidedMetadatDuringInitialization() { + //given + MongoDriverInformation initialWrappingLibraryDriverInformation = MongoDriverInformation.builder() + .driverName("library") + .driverVersion("1.2") + .driverPlatform("Library Platform") + .build(); + + try (MongoClient mongoClient = createMongoClient(initialWrappingLibraryDriverInformation, getMongoClientSettingsBuilder() + .build())) { + + //when + //TODO change get() to orElseThrow + BsonDocument clientMetadata = executePingAndCaptureMetadataHandshake(mongoClient).get(); + BsonDocument driverInformation = clientMetadata.getDocument("driver"); + + //then + assertThat(driverInformation.get("name").asString().getValue()).endsWith("|library"); + assertThat(driverInformation.get("version").asString().getValue()).endsWith("|1.2"); + assertThat(clientMetadata.get("platform").asString().getValue()).endsWith("|Library Platform"); + } + } + + private Optional executePingAndCaptureMetadataHandshake(final MongoClient mongoClient) { + commandListener.reset(); + mongoClient.getDatabase("admin") + .runCommand(BsonDocument.parse("{ping: 1}")); + + List commandStartedEvents = commandListener.getCommandStartedEvents("isMaster"); + + if (commandStartedEvents.isEmpty()) { + return Optional.empty(); + } + CommandStartedEvent event = commandStartedEvents.get(0); + BsonDocument helloCommand = event.getCommand(); + return Optional.of(helloCommand.getDocument("client")); + } + + protected MongoClientSettings.Builder getMongoClientSettingsBuilder() { + return Fixture.getMongoClientSettingsBuilder() + .addCommandListener(commandListener) + .applyToConnectionPoolSettings(builder -> + builder.addConnectionPoolListener(connectionPoolListener)); + } + + private static BsonDocument withRemovedKeys(final BsonDocument updatedClientMetadata, + final String... keysToFilter) { + BsonDocument clone = updatedClientMetadata.clone(); + for (String keyToRemove : keysToFilter) { + clone.remove(keyToRemove); + } + return clone; + } + + private static void updateClientMetadata(@Nullable final String driverVersion, + @Nullable final String driverName, + @Nullable final String driverPlatform, + final MongoClient mongoClient) { + MongoDriverInformation.Builder builder; + builder = MongoDriverInformation.builder(); + ofNullable(driverName).ifPresent(builder::driverName); + ofNullable(driverVersion).ifPresent(builder::driverVersion); + ofNullable(driverPlatform).ifPresent(builder::driverPlatform); + mongoClient.appendMetadata(builder.build()); + } +} + diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataProseTest.java new file mode 100644 index 00000000000..f457eb350fe --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataProseTest.java @@ -0,0 +1,30 @@ +/* + * 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.client; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoDriverInformation; +import com.mongodb.lang.Nullable; + +public class ClientMetadataProseTest extends AbstractClientMetadataProseTest { + + @Override + protected MongoClient createMongoClient(@Nullable final MongoDriverInformation mongoDriverInformation, + final MongoClientSettings mongoClientSettings) { + return MongoClients.create(mongoClientSettings, mongoDriverInformation); + } +}