Skip to content

Add Client Metadata Update Support. #1708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open

Conversation

vbabanin
Copy link
Member

@vbabanin vbabanin commented May 6, 2025

Description

This PR updates the Java sync and reactive-streams driver to support dynamic client metadata updates, enabling wrapping libraries and integrations to append metadata about themselves when they do not instantiate the MongoClient directly.

JAVA-5870

NOTE BEFORE MERGING:

Remove a submodule link to a fork: #1708 (comment)

@vbabanin vbabanin self-assigned this May 8, 2025
Comment on lines 109 to 111
dependencies {
testImplementation(libs.assertj)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AssertJ has been used in this PR and added to test-base as it is a useful library that could be shared across all modules.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A decision to use a new assertion library seems like something that should be debated a bit more widely, rather than being brought in as part of a PR like this one. How important do you think it is to the assertions required for this PR?

Copy link
Member Author

@vbabanin vbabanin Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that adopting a new assertion or testing approach should generally be a broader team decision. This isn’t a critical for this PR, so I can remove it from this PR. AssertJ has already been used in the Kotlin coroutine module (reference) and is present in the toml file, though it’s not widely used in the codebase. This PR made the library visible to all modules, based on its usefulness in the Hibernate repository, which was the motivation for extending its usage further.

For now, I will revert this change and we can revisit the discussion as a team.

@vbabanin vbabanin requested review from a team and nhachicha and removed request for a team May 8, 2025 05:55
@vbabanin vbabanin requested a review from jyemin May 20, 2025 20:58
Copy link
Collaborator

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial feedback

@vbabanin vbabanin requested a review from jyemin May 21, 2025 19:19
vbabanin added 4 commits June 16, 2025 19:47
# Conflicts:
#	driver-core/src/test/resources/specifications
#	driver-core/src/test/unit/com/mongodb/internal/connection/MultiServerClusterSpecification.groovy
# Conflicts:
#	driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java
@vbabanin vbabanin marked this pull request as ready for review June 17, 2025 03:02
@vbabanin vbabanin requested a review from a team as a code owner June 17, 2025 03:02
Copy link
Contributor

@nhachicha nhachicha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why the changes of SDAM/Connection/Monitoring is part of this PR? The client metadata aspect LGTM but didn't review the SDAM changes

@@ -269,7 +270,7 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip
}

if (isStalePrimary(newDescription)) {
invalidatePotentialPrimary(newDescription);
invalidatePotentialPrimary(newDescription, new MongoStalePrimaryException("Primary marked stale due to electionId/setVersion mismatch"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test asserting this exception?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in #1708 (comment).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why it show up empty in the diff?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in #1708 (comment).

Comment on lines 109 to 111
dependencies {
testImplementation(libs.assertj)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A decision to use a new assertion library seems like something that should be debated a bit more widely, rather than being brought in as part of a PR like this one. How important do you think it is to the assertions required for this PR?

import java.util.concurrent.locks.ReentrantReadWriteLock;

import static com.mongodb.internal.Locks.withLock;
import static com.mongodb.internal.connection.ClientMetadataHelper.createClientMetadataDocument;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have a proper ClientMetadata class that is meant to be instantiated, it seems like we no longer need a separate ClientMetadataHelper class, instead moving the methods of that class into this one.

* <p>
* Platform is appended separately to the existing platform if it does not exceed {@value MAXIMUM_CLIENT_METADATA_ENCODED_SIZE} bytes.
*/
public static BsonDocument updateClientMetadataDocument(final BsonDocument clientMetadataDocument,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's consider a slightly different algorithm for updating client metadata that shared more logic with createClientMetadataDocument.

Now that we have a ClientMetadata class that is meant to be instantiated, we can store more than just the resulting BsonDocument representing the metadata. We can also store the data from which it was created: the application name and the MongoDriverInformation. If we have that, we can share all the create logic by first merging the original MongoDriverInformation with the appended one, and then calling createClientMetadataDocument. We might need a new MongoDriverInformation#merge method, or it could be added to the MongoDriverInformation.Builder, but otherwise it reduces the code complexity a bit.

What do you think?

Copy link
Member Author

@vbabanin vbabanin Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After our offline discussion, I checked the spec and see that we’re actually allowed to omit any values outside of client.driver if the appended driver name, version, and platform take up the full space.

I agree that removing updateClientMetadata makes code easier to reason about.

I’ve updated the code to reuse createClientMetadataDocument, but I think we should avoid adding public API elements purely for internal purposes. To address that, I added an internal class to encapsulate the metadata construction logic - similar to what we did with SearchIndexRequest as an internal representation of SearchIndexModel.

I pushed a commit to a separate branch vbabanin@a41ad61. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the approach.

Copy link
Member Author

@vbabanin vbabanin Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the changes to this PR in this commit: 66a5913. Thank you! Let me know if you have any further suggestions.

@vbabanin
Copy link
Member Author

vbabanin commented Jun 19, 2025

Not sure why the changes of SDAM/Connection/Monitoring is part of this PR? The client metadata aspect LGTM but didn't review the SDAM changes

After merging main into this branch, some diffs (SDAM-related changes) are appearing, even though they’re already present on main and the total number of line additions hasn’t changed. I’m looking into why this is happening.

@Override
boolean isCurrentRuntimeContainer() {
try {
return Files.exists(get(File.separator + ".dockerenv"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is not always guaranteed to be created. we can add other heuristics based on cgroup and environment variables (set by Kubernetes for example)

boolean isRunningInContainer() {
    // Check for /.dockerenv
    if (Files.exists(Paths.get("/.dockerenv"))) return true;

    // Check cgroup for Docker/container patterns
    try {
        List<String> lines = Files.readAllLines(Paths.get("/proc/1/cgroup"));
        for (String line : lines) {
            if (line.contains("docker") || line.contains("kubepods") || line.contains("containerd")) {
                return true;
            }
        }
    } catch (IOException ignored) {}

    // Check for common container environment variable
    if (System.getenv("container") != null) return true;

    return false;
}

Copy link
Member Author

@vbabanin vbabanin Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are valid points and I agree. However, these changes were just moved from ClientMetadataHelper to consolidate them in the ClientMetadata instance; they were already present in the codebase and were not introduced in this PR.

You can see the diff here:
https://github.com/mongodb/mongo-java-driver/pull/1708/files#diff-ad857628989f20f23a2f410c3a2c9b49d1fc664d06a2998a489393adb5bd8e8dR161

The methods getOperatingSystemType, getOperatingSystemName, and nameStartsWith were also moved from ClientMetadataHelper. They show up as new additions because I relocated them to the bottom of the class for better organization - separating utility methods from the core logic.

}
}
},
UNKNOWN(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we interested to track other containers like Podman?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in #1708 (comment).

K8S("kubernetes") {
@Override
boolean isCurrentOrchestrator() {
return FaasEnvironment.getEnv("KUBERNETES_SERVICE_HOST") != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also check for /etc/kubernetes per node if available?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in #1708 (comment).

.build();
}

private static String listToString(final List<String> listOfStrings) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use

String result = listOfStrings.stream()
    .collect(Collectors.joining(SEPARATOR));

Instead of this method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in #1708 (comment).

@vbabanin vbabanin requested review from jyemin and nhachicha June 25, 2025 20:51
@vbabanin vbabanin requested a review from jyemin June 26, 2025 20:38
Copy link
Collaborator

@jyemin jyemin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@alexbevi
Copy link
Collaborator

Adding an additional (unnecessary) LGTM to show excitement for this feature 😃

Copy link
Contributor

@nhachicha nhachicha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 🎉 minor comments

builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS))
.build())) {

//TODO change get() to orElseThrow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still relevant?

updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient);

//then
//TODO change get() to orElseThrow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

builder.maxConnectionIdleTime(1, TimeUnit.MILLISECONDS))
.build())) {

//TODO change get() to orElseThrow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

updateClientMetadata(driverVersion, driverName, driverPlatform, mongoClient);

//then
//TODO change get() to orElseThrow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

.build())) {

//when
//TODO change get() to orElseThrow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, other drivers can't update their submodule deps on the sepc until they update their runner? or are they excluding the new client metadata tests?

Copy link
Member Author

@vbabanin vbabanin Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other drivers can update their submodule dependencies, however they will have to skip/ignore the new client metadata tests in their test runner until after they implemented the relevant changes from the epic.

vbabanin added 3 commits June 30, 2025 12:00
# Conflicts:
#	driver-core/src/test/resources/specifications
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants