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 67d925dbac7..acaf1a40e14 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/AbstractMultiServerCluster.java @@ -78,8 +78,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 8cdc9951293..eda2fddbec4 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java @@ -53,11 +53,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; @@ -106,21 +106,26 @@ 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); ClusterOpeningEvent clusterOpeningEvent = new ClusterOpeningEvent(clusterId); - clusterListener.clusterOpening(clusterOpeningEvent); + this.clusterListener.clusterOpening(clusterOpeningEvent); logTopologyOpening(clusterId, clusterOpeningEvent); - description = new ClusterDescription(settings.getMode(), UNKNOWN, emptyList(), + this.description = new ClusterDescription(settings.getMode(), UNKNOWN, emptyList(), settings, serverFactory.getSettings()); + this.clientMetadata = clientMetadata; } @Override @@ -128,6 +133,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/ClientMetadataHelper.java b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java similarity index 60% rename from driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java rename to driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java index 825af685c10..c83a32ce4d4 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadata.java @@ -17,6 +17,7 @@ package com.mongodb.internal.connection; import com.mongodb.MongoDriverInformation; +import com.mongodb.annotations.ThreadSafe; import com.mongodb.internal.VisibleForTesting; import com.mongodb.internal.build.MongoDriverVersion; import com.mongodb.lang.Nullable; @@ -32,53 +33,64 @@ 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.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import static com.mongodb.assertions.Assertions.isTrueArgument; +import static com.mongodb.internal.Locks.withLock; import static com.mongodb.internal.connection.FaasEnvironment.getFaasEnvironment; import static java.lang.String.format; import static java.lang.System.getProperty; import static java.nio.file.Paths.get; /** + * Represents metadata of the current MongoClient. + * + * Metadata is used to identify the client in the server logs and metrics. + * *

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

*/ -public final class ClientMetadataHelper { +@ThreadSafe +public class ClientMetadata { private static final String SEPARATOR = "|"; - private static final int MAXIMUM_CLIENT_METADATA_ENCODED_SIZE = 512; - - @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) - static String getOperatingSystemType(final String operatingSystemName) { - if (nameStartsWith(operatingSystemName, "linux")) { - return "Linux"; - } else if (nameStartsWith(operatingSystemName, "mac")) { - return "Darwin"; - } else if (nameStartsWith(operatingSystemName, "windows")) { - return "Windows"; - } else if (nameStartsWith(operatingSystemName, "hp-ux", "aix", "irix", "solaris", "sunos")) { - return "Unix"; - } else { - return "unknown"; - } + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final String applicationName; + private BsonDocument clientMetadataBsonDocument; + private DriverInformation driverInformation; + + public ClientMetadata(@Nullable final String applicationName, final MongoDriverInformation mongoDriverInformation) { + this.applicationName = applicationName; + withLock(readWriteLock.writeLock(), () -> { + this.driverInformation = DriverInformation.from( + mongoDriverInformation.getDriverNames(), + mongoDriverInformation.getDriverVersions(), + mongoDriverInformation.getDriverPlatforms()); + this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformation); + }); } - private static String getOperatingSystemName() { - return getProperty("os.name", "unknown"); + /** + * Returns mutable BsonDocument that represents the client metadata. + */ + public BsonDocument getBsonDocument() { + return withLock(readWriteLock.readLock(), () -> clientMetadataBsonDocument); } - private static boolean nameStartsWith(final String name, final String... prefixes) { - for (String prefix : prefixes) { - if (name.toLowerCase().startsWith(prefix.toLowerCase())) { - return true; - } - } - return false; + public void append(final MongoDriverInformation mongoDriverInformationToAppend) { + withLock(readWriteLock.writeLock(), () -> { + this.driverInformation.append( + mongoDriverInformationToAppend.getDriverNames(), + mongoDriverInformationToAppend.getDriverVersions(), + mongoDriverInformationToAppend.getDriverPlatforms()); + this.clientMetadataBsonDocument = createClientMetadataDocument(applicationName, driverInformation); + }); } - public static BsonDocument createClientMetadataDocument(@Nullable final String applicationName, - @Nullable final MongoDriverInformation mongoDriverInformation) { + private static BsonDocument createClientMetadataDocument(@Nullable final String applicationName, + final DriverInformation driverInformation) { if (applicationName != null) { isTrueArgument("applicationName UTF-8 encoding length <= 128", applicationName.getBytes(StandardCharsets.UTF_8).length <= 128); @@ -87,27 +99,26 @@ public static BsonDocument createClientMetadataDocument(@Nullable final String a // client fields are added in "preservation" order: BsonDocument client = new BsonDocument(); tryWithLimit(client, d -> putAtPath(d, "application.name", applicationName)); - MongoDriverInformation baseDriverInfor = getDriverInformation(null); + // required fields: tryWithLimit(client, d -> { - putAtPath(d, "driver.name", listToString(baseDriverInfor.getDriverNames())); - putAtPath(d, "driver.version", listToString(baseDriverInfor.getDriverVersions())); + putAtPath(d, "driver.name", driverInformation.getInitialDriverName()); + putAtPath(d, "driver.version", driverInformation.getInitialDriverVersion()); }); tryWithLimit(client, d -> putAtPath(d, "os.type", getOperatingSystemType(getOperatingSystemName()))); // full driver information: - MongoDriverInformation fullDriverInfo = getDriverInformation(mongoDriverInformation); tryWithLimit(client, d -> { - putAtPath(d, "driver.name", listToString(fullDriverInfo.getDriverNames())); - putAtPath(d, "driver.version", listToString(fullDriverInfo.getDriverVersions())); + putAtPath(d, "driver.name", listToString(driverInformation.getAllDriverNames())); + putAtPath(d, "driver.version", listToString(driverInformation.getAllDriverVersions())); }); // optional fields: FaasEnvironment faasEnvironment = getFaasEnvironment(); - ContainerRuntime containerRuntime = ContainerRuntime.determineExecutionContainer(); - Orchestrator orchestrator = Orchestrator.determineExecutionOrchestrator(); + ClientMetadata.ContainerRuntime containerRuntime = ClientMetadata.ContainerRuntime.determineExecutionContainer(); + ClientMetadata.Orchestrator orchestrator = ClientMetadata.Orchestrator.determineExecutionOrchestrator(); - tryWithLimit(client, d -> putAtPath(d, "platform", listToString(baseDriverInfor.getDriverPlatforms()))); - tryWithLimit(client, d -> putAtPath(d, "platform", listToString(fullDriverInfo.getDriverPlatforms()))); + tryWithLimit(client, d -> putAtPath(d, "platform", driverInformation.getInitialDriverPlatform())); + tryWithLimit(client, d -> putAtPath(d, "platform", listToString(driverInformation.getAllDriverPlatforms()))); tryWithLimit(client, d -> putAtPath(d, "os.name", getOperatingSystemName())); tryWithLimit(client, d -> putAtPath(d, "os.architecture", getProperty("os.arch", "unknown"))); tryWithLimit(client, d -> putAtPath(d, "os.version", getProperty("os.version", "unknown"))); @@ -123,7 +134,6 @@ public static BsonDocument createClientMetadataDocument(@Nullable final String a return client; } - private static void putAtPath(final BsonDocument d, final String path, @Nullable final String value) { if (value == null) { return; @@ -180,7 +190,7 @@ static boolean clientMetadataDocumentTooLarge(final BsonDocument document) { return buffer.getPosition() > MAXIMUM_CLIENT_METADATA_ENCODED_SIZE; } - public enum ContainerRuntime { + private enum ContainerRuntime { DOCKER("docker") { @Override boolean isCurrentRuntimeContainer() { @@ -210,8 +220,8 @@ boolean isCurrentRuntimeContainer() { return false; } - static ContainerRuntime determineExecutionContainer() { - for (ContainerRuntime allegedContainer : ContainerRuntime.values()) { + static ClientMetadata.ContainerRuntime determineExecutionContainer() { + for (ClientMetadata.ContainerRuntime allegedContainer : ClientMetadata.ContainerRuntime.values()) { if (allegedContainer.isCurrentRuntimeContainer()) { return allegedContainer; } @@ -245,8 +255,8 @@ boolean isCurrentOrchestrator() { return false; } - static Orchestrator determineExecutionOrchestrator() { - for (Orchestrator alledgedOrchestrator : Orchestrator.values()) { + static ClientMetadata.Orchestrator determineExecutionOrchestrator() { + for (ClientMetadata.Orchestrator alledgedOrchestrator : ClientMetadata.Orchestrator.values()) { if (alledgedOrchestrator.isCurrentOrchestrator()) { return alledgedOrchestrator; } @@ -255,17 +265,6 @@ static Orchestrator determineExecutionOrchestrator() { } } - static MongoDriverInformation getDriverInformation(@Nullable final MongoDriverInformation mongoDriverInformation) { - MongoDriverInformation.Builder builder = mongoDriverInformation != null ? MongoDriverInformation.builder(mongoDriverInformation) - : MongoDriverInformation.builder(); - return builder - .driverName(MongoDriverVersion.NAME) - .driverVersion(MongoDriverVersion.VERSION) - .driverPlatform(format("Java/%s/%s", getProperty("java.vendor", "unknown-vendor"), - getProperty("java.runtime.version", "unknown-version"))) - .build(); - } - private static String listToString(final List listOfStrings) { StringBuilder stringBuilder = new StringBuilder(); int i = 0; @@ -279,6 +278,95 @@ private static String listToString(final List listOfStrings) { return stringBuilder.toString(); } - private ClientMetadataHelper() { + @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) + public static String getOperatingSystemType(final String operatingSystemName) { + if (nameStartsWith(operatingSystemName, "linux")) { + return "Linux"; + } else if (nameStartsWith(operatingSystemName, "mac")) { + return "Darwin"; + } else if (nameStartsWith(operatingSystemName, "windows")) { + return "Windows"; + } else if (nameStartsWith(operatingSystemName, "hp-ux", "aix", "irix", "solaris", "sunos")) { + return "Unix"; + } else { + return "unknown"; + } + } + + private static String getOperatingSystemName() { + return getProperty("os.name", "unknown"); + } + + private static boolean nameStartsWith(final String name, final String... prefixes) { + for (String prefix : prefixes) { + if (name.toLowerCase().startsWith(prefix.toLowerCase())) { + return true; + } + } + return false; + } + + /** + * Holds driver information of client.driver field + * in {@link ClientMetadata#clientMetadataBsonDocument}. + */ + private static class DriverInformation { + private final List driverNames; + private final List driverVersions; + private final List driverPlatforms; + private final String initialPlatform; + + DriverInformation() { + this.driverNames = new ArrayList<>(); + driverNames.add(MongoDriverVersion.NAME); + + this.driverVersions = new ArrayList<>(); + driverVersions.add(MongoDriverVersion.VERSION); + + this.initialPlatform = format("Java/%s/%s", getProperty("java.vendor", "unknown-vendor"), + getProperty("java.runtime.version", "unknown-version")); + this.driverPlatforms = new ArrayList<>(); + driverPlatforms.add(initialPlatform); + } + + static DriverInformation from(final List driverNames, + final List driverVersions, + final List driverPlatforms) { + DriverInformation driverInformation = new DriverInformation(); + return driverInformation.append(driverNames, driverVersions, driverPlatforms); + } + + DriverInformation append(final List driverNames, + final List driverVersions, + final List driverPlatforms) { + this.driverNames.addAll(driverNames); + this.driverVersions.addAll(driverVersions); + this.driverPlatforms.addAll(driverPlatforms); + return this; + } + + public String getInitialDriverPlatform() { + return initialPlatform; + } + + public String getInitialDriverName() { + return MongoDriverVersion.NAME; + } + + public String getInitialDriverVersion() { + return MongoDriverVersion.VERSION; + } + + public List getAllDriverNames() { + return driverNames; + } + + public List getAllDriverVersions() { + return driverVersions; + } + + public List getAllDriverPlatforms() { + return driverPlatforms; + } } } 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 9eac751943c..b177bcb12d6 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java +++ b/driver-core/src/main/com/mongodb/internal/connection/LoadBalancedCluster.java @@ -77,6 +77,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 @@ -92,6 +93,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())); @@ -101,6 +103,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; @@ -205,6 +208,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/main/resources/META-INF/native-image/native-image.properties b/driver-core/src/main/resources/META-INF/native-image/native-image.properties index 49541a06e0e..6de9c4d8765 100644 --- a/driver-core/src/main/resources/META-INF/native-image/native-image.properties +++ b/driver-core/src/main/resources/META-INF/native-image/native-image.properties @@ -17,6 +17,6 @@ Args =\ --initialize-at-run-time=\ com.mongodb.UnixServerAddress,\ com.mongodb.internal.connection.SnappyCompressor,\ - com.mongodb.internal.connection.ClientMetadataHelper,\ + com.mongodb.internal.connection.ClientMetadata,\ com.mongodb.internal.connection.ServerAddressHelper,\ com.mongodb.internal.dns.DefaultDnsResolver diff --git a/driver-core/src/test/functional/com/mongodb/ClusterFixture.java b/driver-core/src/test/functional/com/mongodb/ClusterFixture.java index 57f7fac825d..09976e363d6 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; @@ -126,6 +127,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/ClientMetadataTest.java similarity index 81% rename from driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java rename to driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.java index 3adafc3a945..bb2e5dc7351 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataTest.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,14 @@ 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.ClientMetadata.getOperatingSystemType; +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; /** @@ -52,8 +56,9 @@ *

* NOTE: This class also contains tests that aren't categorized as Prose tests. */ -public class ClientMetadataHelperProseTest { +public class ClientMetadataTest { private static final String APP_NAME = "app name"; + private static final MongoDriverInformation EMPTY_MONGO_DRIVER_INFORMATION = MongoDriverInformation.builder().build(); @Test public void test01ValidAws() { @@ -258,7 +263,7 @@ public void testLimitForDriverVersion() { BsonDocument expectedBase = createExpectedClientMetadataDocument(APP_NAME); expected.put("driver", expectedBase.get("driver")); - BsonDocument actual = createClientMetadataDocument(APP_NAME, driverInfo); + BsonDocument actual = new ClientMetadata(APP_NAME, driverInfo).getBsonDocument(); assertEquals(expected, actual); } @@ -274,7 +279,7 @@ public void testLimitForPlatform() { BsonDocument expectedBase = createExpectedClientMetadataDocument(APP_NAME); expected.put("platform", expectedBase.get("platform")); - BsonDocument actual = createClientMetadataDocument(APP_NAME, driverInfo); + BsonDocument actual = new ClientMetadata(APP_NAME, driverInfo).getBsonDocument(); assertEquals(expected, actual); } @@ -294,14 +299,14 @@ public void testLimitForOsName() { @Test public void testApplicationNameUnderLimit() { String applicationName = repeat(126, "a") + "\u00A0"; - BsonDocument client = createClientMetadataDocument(applicationName, null); + BsonDocument client = new ClientMetadata(applicationName, EMPTY_MONGO_DRIVER_INFORMATION).getBsonDocument(); assertEquals(applicationName, client.getDocument("application").getString("name").getValue()); } @Test public void testApplicationNameOverLimit() { String applicationName = repeat(127, "a") + "\u00A0"; - assertThrows(IllegalArgumentException.class, () -> createClientMetadataDocument(applicationName, null)); + assertThrows(IllegalArgumentException.class, () -> new ClientMetadata(applicationName, EMPTY_MONGO_DRIVER_INFORMATION)); } @ParameterizedTest @@ -312,10 +317,65 @@ public void testApplicationNameOverLimit() { ", " + false, }) public void testCreateClientMetadataDocument(@Nullable final String appName, final boolean hasDriverInfo) { - MongoDriverInformation driverInformation = hasDriverInfo ? createDriverInformation() : null; + MongoDriverInformation driverInformation = hasDriverInfo ? createDriverInformation() : EMPTY_MONGO_DRIVER_INFORMATION; + ClientMetadata clientMetadata = new ClientMetadata(appName, driverInformation); assertEquals( createExpectedClientMetadataDocument(appName, driverInformation), - createClientMetadataDocument(appName, driverInformation)); + clientMetadata.getBsonDocument()); + } + + 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(); + + ClientMetadata clientMetadata = new ClientMetadata(null, initialDriverInformation); + BsonDocument initialClientMetadataDocument = clientMetadata.getBsonDocument(); + 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 + clientMetadata.append(metadataToAppend); + BsonDocument updatedClientMetadata = clientMetadata.getBsonDocument(); + + //then + assertEquals( + createExpectedClientMetadataDocument(null, expectedUpdatedMetadata), + updatedClientMetadata); + assertNotEquals(updatedClientMetadata, initialClientMetadataDocument); } @ParameterizedTest @@ -343,7 +403,7 @@ private void performHello() { } private BsonDocument createActualClientMetadataDocument() { - return createClientMetadataDocument(APP_NAME, null); + return new ClientMetadata(APP_NAME, EMPTY_MONGO_DRIVER_INFORMATION).getBsonDocument(); } private static MongoDriverInformation createDriverInformation() { 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 ebde0d9c593..092f74ef96a 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 @@ -230,7 +231,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/resources/specifications b/driver-core/src/test/resources/specifications index 4e5d6245655..d5adadb2f59 160000 --- a/driver-core/src/test/resources/specifications +++ b/driver-core/src/test/resources/specifications @@ -1 +1 @@ -Subproject commit 4e5d6245655f30f13e42a15bd340f57f6729bb27 +Subproject commit d5adadb2f59ba5c598bc46bc93b0f1edbea9381c 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 c11e4136aa7..e187e94da7b 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 f8ef0eddc01..6552a69a70d 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 @@ -54,6 +54,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 @@ -443,7 +444,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/InternalStreamConnectionInitializerSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy index 156499797c2..1d44f8dde46 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/InternalStreamConnectionInitializerSpecification.groovy @@ -44,7 +44,6 @@ import static com.mongodb.MongoCredential.createPlainCredential import static com.mongodb.MongoCredential.createScramSha1Credential import static com.mongodb.MongoCredential.createScramSha256Credential import static com.mongodb.connection.ClusterConnectionMode.SINGLE -import static com.mongodb.internal.connection.ClientMetadataHelperProseTest.createExpectedClientMetadataDocument import static com.mongodb.internal.connection.MessageHelper.LEGACY_HELLO import static com.mongodb.internal.connection.MessageHelper.buildSuccessfulReply import static com.mongodb.internal.connection.MessageHelper.decodeCommand @@ -225,7 +224,7 @@ class InternalStreamConnectionInitializerSpecification extends Specification { decodeCommand(internalConnection.getSent()[0]) == expectedHelloCommandDocument where: - [clientMetadataDocument, async] << [[createExpectedClientMetadataDocument('appName'), null], + [clientMetadataDocument, async] << [[ClientMetadataTest.createExpectedClientMetadataDocument('appName'), null], [true, false]].combinations() } 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 3e0cfcb849a..a3cf8104fd3 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,12 +308,11 @@ 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) def electionId = new ObjectId(new Date(1000)) factory.sendNotification(firstServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer], electionId) - when: def outdatedElectionId = new ObjectId(new Date(999)) factory.sendNotification(secondServer, REPLICA_SET_PRIMARY, [firstServer, secondServer, thirdServer], outdatedElectionId) @@ -328,7 +328,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) @@ -340,7 +340,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) @@ -353,7 +353,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) @@ -369,7 +369,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') @@ -382,7 +382,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: @@ -395,7 +395,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: @@ -408,7 +408,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: @@ -421,7 +421,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: @@ -440,7 +440,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: @@ -453,7 +453,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: @@ -478,7 +478,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 } @@ -511,7 +511,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-legacy/src/main/com/mongodb/MongoClient.java b/driver-legacy/src/main/com/mongodb/MongoClient.java index 21323a40604..31da4c1b9ef 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClient.java +++ b/driver-legacy/src/main/com/mongodb/MongoClient.java @@ -66,7 +66,6 @@ import java.util.stream.Collectors; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; import static com.mongodb.internal.connection.ServerAddressHelper.createServerAddress; import static com.mongodb.internal.connection.ServerAddressHelper.getInetAddressResolver; import static com.mongodb.internal.connection.StreamFactoryHelper.getSyncStreamFactoryFactory; @@ -266,7 +265,8 @@ private MongoClient(final MongoClientSettings settings, this.options = options != null ? options : MongoClientOptions.builder(settings).build(); cursorCleaningService = this.options.isCursorFinalizerEnabled() ? createCursorCleaningService() : null; this.closed = new AtomicBoolean(); - BsonDocument clientMetadataDocument = createClientMetadataDocument(settings.getApplicationName(), mongoDriverInformation); + + BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument(); LOGGER.info(format("MongoClient with metadata %s created with settings %s", clientMetadataDocument.toJson(), settings)); } diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy index 0816dc83a87..1389a41c760 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientSpecification.groovy @@ -21,6 +21,7 @@ import com.mongodb.client.internal.MongoDatabaseImpl import com.mongodb.client.internal.TestOperationExecutor import com.mongodb.client.model.geojson.MultiPolygon import com.mongodb.connection.ClusterSettings +import com.mongodb.internal.connection.ClientMetadata import com.mongodb.internal.connection.Cluster import org.bson.BsonDocument import org.bson.Document @@ -309,7 +310,11 @@ class MongoClientSpecification extends Specification { def 'should validate the ChangeStreamIterable pipeline data correctly'() { given: def executor = new TestOperationExecutor([]) - def client = new MongoClientImpl(Stub(Cluster), null, MongoClientSettings.builder().build(), null, executor) + + def clusterStub = Stub(Cluster) + clusterStub.getClientMetadata() >> new ClientMetadata("test", MongoDriverInformation.builder().build()) + + def client = new MongoClientImpl(clusterStub, null, MongoClientSettings.builder().build(), null, executor) when: client.watch((Class) null) 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..07a17badcd7 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 @@ -29,6 +29,7 @@ import com.mongodb.client.model.bulk.ClientNamespacedWriteModel; import com.mongodb.connection.ClusterDescription; import com.mongodb.internal.TimeoutSettings; +import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.diagnostics.logging.Logger; import com.mongodb.internal.diagnostics.logging.Loggers; @@ -54,7 +55,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.mongodb.assertions.Assertions.notNull; -import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; import static java.lang.String.format; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -117,7 +117,8 @@ private MongoClientImpl(final MongoClientSettings settings, final MongoDriverInf this.externalResourceCloser = externalResourceCloser; this.settings = settings; this.closed = new AtomicBoolean(); - BsonDocument clientMetadataDocument = createClientMetadataDocument(settings.getApplicationName(), mongoDriverInformation); + + BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument(); LOGGER.info(format("MongoClient with metadata %s created with settings %s", clientMetadataDocument.toJson(), settings)); } @@ -325,4 +326,11 @@ public MongoDatabase getDatabase(final String name) { public ClusterDescription getClusterDescription() { return getCluster().getCurrentDescription(); } + + @Override + public void appendMetadata(final MongoDriverInformation mongoDriverInformation) { + ClientMetadata clientMetadata = getCluster().getClientMetadata(); + clientMetadata.append(mongoDriverInformation); + LOGGER.info(format("MongoClient metadata has been updated to %s", clientMetadata.getBsonDocument())); + } } 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-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientMetadataTest.java b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientMetadataTest.java new file mode 100644 index 00000000000..6b0caf615bc --- /dev/null +++ b/driver-reactive-streams/src/test/functional/com/mongodb/reactivestreams/client/unified/ClientMetadataTest.java @@ -0,0 +1,28 @@ +/* + * 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.unified; + +import org.junit.jupiter.params.provider.Arguments; + +import java.util.Collection; + +public class ClientMetadataTest extends UnifiedReactiveStreamsTest { + + private static Collection data() { + return getTestData("mongodb-handshake/tests/unified"); + } +} diff --git a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java index 1eb42c647d7..c192ae17896 100644 --- a/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java +++ b/driver-reactive-streams/src/test/unit/com/mongodb/reactivestreams/client/internal/MongoClientImplTest.java @@ -20,12 +20,11 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoDriverInformation; import com.mongodb.ReadConcern; -import com.mongodb.ServerAddress; import com.mongodb.TransactionOptions; -import com.mongodb.connection.ServerConnectionState; -import com.mongodb.connection.ServerDescription; import com.mongodb.internal.client.model.changestream.ChangeStreamLevel; +import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; +import com.mongodb.internal.mockito.MongoMockito; import com.mongodb.internal.session.ServerSessionPool; import com.mongodb.reactivestreams.client.ChangeStreamPublisher; import com.mongodb.reactivestreams.client.ClientSession; @@ -44,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MongoClientImplTest extends TestHelper { @@ -178,13 +178,6 @@ void testWatch() { @Test void testStartSession() { - ServerDescription serverDescription = ServerDescription.builder() - .address(new ServerAddress()) - .state(ServerConnectionState.CONNECTED) - .maxWireVersion(8) - .build(); - - MongoClientImpl mongoClient = createMongoClient(); ServerSessionPool serverSessionPool = mock(ServerSessionPool.class); ClientSessionHelper clientSessionHelper = new ClientSessionHelper(mongoClient, serverSessionPool); @@ -209,7 +202,12 @@ void testStartSession() { } private MongoClientImpl createMongoClient() { + MongoDriverInformation mongoDriverInformation = MongoDriverInformation.builder().driverName("reactive-streams").build(); + Cluster mock = MongoMockito.mock(Cluster.class, cluster -> { + when(cluster.getClientMetadata()) + .thenReturn(new ClientMetadata("test", mongoDriverInformation)); + }); return new MongoClientImpl(MongoClientSettings.builder().build(), - MongoDriverInformation.builder().driverName("reactive-streams").build(), mock(Cluster.class), OPERATION_EXECUTOR); + mongoDriverInformation, mock, OPERATION_EXECUTOR); } } 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/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java index cf9ca2a3b7d..6870277b1c6 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -38,6 +38,8 @@ import com.mongodb.connection.ClusterDescription; import com.mongodb.connection.SocketSettings; import com.mongodb.internal.TimeoutSettings; +import com.mongodb.internal.VisibleForTesting; +import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.connection.DefaultClusterFactory; import com.mongodb.internal.connection.InternalConnectionPoolSettings; @@ -58,7 +60,6 @@ import static com.mongodb.assertions.Assertions.notNull; import static com.mongodb.client.internal.Crypts.createCrypt; -import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument; import static com.mongodb.internal.event.EventListenerHelper.getCommandListener; import static java.lang.String.format; import static org.bson.codecs.configuration.CodecRegistries.withUuidRepresentation; @@ -82,9 +83,10 @@ public MongoClientImpl(final Cluster cluster, this(cluster, mongoDriverInformation, settings, externalResourceCloser, null); } - private MongoClientImpl(final Cluster cluster, + @VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE) + public MongoClientImpl(final Cluster cluster, final MongoDriverInformation mongoDriverInformation, - final MongoClientSettings settings, + final MongoClientSettings settings, @Nullable final AutoCloseable externalResourceCloser, @Nullable final OperationExecutor operationExecutor) { @@ -106,8 +108,8 @@ private MongoClientImpl(final Cluster cluster, new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()), TimeoutSettings.create(settings), settings.getUuidRepresentation(), settings.getWriteConcern()); this.closed = new AtomicBoolean(); - BsonDocument clientMetadataDocument = createClientMetadataDocument(settings.getApplicationName(), mongoDriverInformation); + BsonDocument clientMetadataDocument = delegate.getCluster().getClientMetadata().getBsonDocument(); LOGGER.info(format("MongoClient with metadata %s created with settings %s", clientMetadataDocument.toJson(), settings)); } @@ -135,6 +137,13 @@ public ClusterDescription getClusterDescription() { return delegate.getCluster().getCurrentDescription(); } + @Override + public void appendMetadata(final MongoDriverInformation mongoDriverInformation) { + ClientMetadata clientMetadata = getCluster().getClientMetadata(); + clientMetadata.append(mongoDriverInformation); + LOGGER.info(format("MongoClient metadata has been updated to %s", clientMetadata.getBsonDocument())); + } + @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..28465633016 --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractClientMetadataProseTest.java @@ -0,0 +1,238 @@ +/* + * 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.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 com.mongodb.assertions.Assertions.assertTrue; +import static java.util.Optional.ofNullable; +import static org.junit.jupiter.api.Assertions.assertEquals; +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; + + assertEquals(updatedDriverInformation.getString("name").getValue(), expectedDriverName); + assertTrue(updatedDriverInformation.getString("version").getValue().endsWith(expectedDriverVersion)); + assertTrue(updatedClientMetadata.getString("platform").getValue().endsWith(expectedDriverPlatform)); + + assertEquals( + withRemovedKeys(updatedClientMetadata, "driver", "platform"), + 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; + + assertEquals(updatedDriverInformation.getString("name").getValue(), expectedDriverName); + assertTrue(updatedDriverInformation.getString("version").getValue().endsWith(expectedDriverVersion)); + assertTrue(updatedClientMetadata.getString("platform").getValue().endsWith(expectedDriverPlatform)); + + assertEquals( + withRemovedKeys(updatedClientMetadata, "driver", "platform"), + withRemovedKeys(initialClientMetadata, "driver", "platform")); + } + } + + // 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 + assertTrue(driverInformation.get("name").asString().getValue().endsWith("|library")); + assertTrue(driverInformation.get("version").asString().getValue().endsWith("|1.2")); + assertTrue(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); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataTest.java b/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataTest.java new file mode 100644 index 00000000000..652d5a4059d --- /dev/null +++ b/driver-sync/src/test/functional/com/mongodb/client/ClientMetadataTest.java @@ -0,0 +1,29 @@ +/* + * 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.client.unified.UnifiedSyncTest; +import org.junit.jupiter.params.provider.Arguments; + +import java.util.Collection; + +public class ClientMetadataTest extends UnifiedSyncTest { + + private static Collection data() { + return getTestData("mongodb-handshake/tests/unified"); + } +} diff --git a/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java b/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java index fb8db8c2ceb..6d3413f032a 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/MongoClientTest.java @@ -23,6 +23,7 @@ import com.mongodb.connection.ClusterId; import com.mongodb.event.ClusterListener; import com.mongodb.event.ClusterOpeningEvent; +import com.mongodb.internal.connection.ClientMetadata; import com.mongodb.internal.connection.Cluster; import com.mongodb.internal.mockito.MongoMockito; import org.junit.jupiter.api.Test; @@ -36,6 +37,7 @@ import static com.mongodb.client.Fixture.getMongoClientSettingsBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; class MongoClientTest { @@ -64,10 +66,13 @@ public void clusterOpening(final ClusterOpeningEvent event) { void shouldCloseExternalResources() throws Exception { //given + MongoDriverInformation mongoDriverInformation = MongoDriverInformation.builder().build(); Cluster cluster = MongoMockito.mock( Cluster.class, mockedCluster -> { doNothing().when(mockedCluster).close(); + when(mockedCluster.getClientMetadata()) + .thenReturn(new ClientMetadata("test", mongoDriverInformation)); }); AutoCloseable externalResource = MongoMockito.mock( AutoCloseable.class, @@ -82,7 +87,7 @@ void shouldCloseExternalResources() throws Exception { MongoClientImpl mongoClient = new MongoClientImpl( cluster, MongoClientSettings.builder().build(), - MongoDriverInformation.builder().build(), + mongoDriverInformation, externalResource); //when diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 2c03bbba051..d3945221e14 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -17,6 +17,7 @@ package com.mongodb.client.unified; import com.mongodb.CursorType; +import com.mongodb.MongoDriverInformation; import com.mongodb.MongoNamespace; import com.mongodb.ReadConcern; import com.mongodb.ReadConcernLevel; @@ -39,6 +40,7 @@ import com.mongodb.client.ListIndexesIterable; import com.mongodb.client.ListSearchIndexesIterable; import com.mongodb.client.MongoChangeStreamCursor; +import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCluster; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; @@ -2217,6 +2219,24 @@ public OperationResult executeEstimatedDocumentCount(final BsonDocument operatio new BsonInt64(collection.estimatedDocumentCount(options))); } + public OperationResult executeUpdateClientMetadata(final BsonDocument operation) { + BsonDocument arguments = operation.getDocument("arguments", new BsonDocument()); + BsonDocument driverInfo = arguments.getDocument("driverInfoOptions"); + + MongoDriverInformation mongoDriverInformation = MongoDriverInformation.builder() + .driverVersion(driverInfo.getString("version").getValue()) + .driverName(driverInfo.getString("name").getValue()) + .driverPlatform(driverInfo.getString("platform").getValue()) + .build(); + + String clientId = operation.getString("object").getValue(); + MongoClient client = entities.getClient(clientId); + return resultOf(() -> { + client.appendMetadata(mongoDriverInformation); + return null; + }); + } + @NonNull private String createRandomEntityId() { return "random-entity-id" + uniqueIdGenerator.getAndIncrement(); diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java index b47f396f535..008d49a3146 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTest.java @@ -723,6 +723,8 @@ private OperationResult executeOperation(final UnifiedTestContext context, final return clientEncryptionHelper.executeEncrypt(operation); case "decrypt": return clientEncryptionHelper.executeDecrypt(operation); + case "appendMetadata": + return crudHelper.executeUpdateClientMetadata(operation); default: throw new UnsupportedOperationException("Unsupported test operation: " + name); } diff --git a/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy index 95004ddedf8..ade491b6a6b 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/MongoClientSpecification.groovy @@ -17,6 +17,7 @@ package com.mongodb.client import com.mongodb.MongoClientSettings +import com.mongodb.MongoDriverInformation import com.mongodb.MongoNamespace import com.mongodb.ReadConcern import com.mongodb.ServerAddress @@ -34,6 +35,7 @@ import com.mongodb.connection.ServerDescription import com.mongodb.connection.ServerType import com.mongodb.internal.TimeoutSettings import com.mongodb.internal.client.model.changestream.ChangeStreamLevel +import com.mongodb.internal.connection.ClientMetadata import com.mongodb.internal.connection.Cluster import org.bson.BsonDocument import org.bson.Document @@ -191,13 +193,15 @@ class MongoClientSpecification extends Specification { .type(ServerType.UNKNOWN) .state(ServerConnectionState.CONNECTING) .build()]) + def driverInformation = MongoDriverInformation.builder().build() def cluster = Mock(Cluster) { 1 * getCurrentDescription() >> { clusterDescription } + 1 * getClientMetadata() >> new ClientMetadata("test", driverInformation) } def settings = MongoClientSettings.builder().build() - def client = new MongoClientImpl(cluster, null, settings, null, new TestOperationExecutor([])) + def client = new MongoClientImpl(cluster, driverInformation, settings, null, new TestOperationExecutor([])) expect: client.getClusterDescription() == clusterDescription