diff --git a/build.gradle b/build.gradle index 73352795017..5d75c92139b 100644 --- a/build.gradle +++ b/build.gradle @@ -257,6 +257,7 @@ configure(javaCodeCheckedProjects) { testImplementation 'org.spockframework:spock-core' testImplementation 'org.spockframework:spock-junit4' testImplementation("org.mockito:mockito-core:3.8.0") + testImplementation("org.mockito:mockito-inline:3.8.0") testImplementation 'cglib:cglib-nodep:2.2.2' testImplementation 'org.objenesis:objenesis:1.3' testImplementation 'org.hamcrest:hamcrest-all:1.3' diff --git a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java index 500e610889f..5ca1e53497f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -29,7 +29,9 @@ import org.bson.codecs.EncoderContext; import org.bson.io.BasicOutputBuffer; +import java.io.File; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -38,6 +40,7 @@ import static com.mongodb.assertions.Assertions.isTrueArgument; import static java.lang.String.format; import static java.lang.System.getProperty; +import static java.nio.file.Paths.get; /** *

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

@@ -98,17 +101,26 @@ public static BsonDocument createClientMetadataDocument(@Nullable final String a putAtPath(d, "driver.name", listToString(fullDriverInfo.getDriverNames())); putAtPath(d, "driver.version", listToString(fullDriverInfo.getDriverVersions())); }); + // optional fields: - Environment environment = getEnvironment(); + FaasEnvironment faasEnvironment = getFaasEnvironment(); + ContainerRuntime containerRuntime = ContainerRuntime.determineExecutionContainer(); + Orchestrator orchestrator = 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, "env.name", environment.getName())); 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"))); - tryWithLimit(client, d -> putAtPath(d, "env.timeout_sec", environment.getTimeoutSec())); - tryWithLimit(client, d -> putAtPath(d, "env.memory_mb", environment.getMemoryMb())); - tryWithLimit(client, d -> putAtPath(d, "env.region", environment.getRegion())); + + tryWithLimit(client, d -> putAtPath(d, "env.name", faasEnvironment.getName())); + tryWithLimit(client, d -> putAtPath(d, "env.timeout_sec", faasEnvironment.getTimeoutSec())); + tryWithLimit(client, d -> putAtPath(d, "env.memory_mb", faasEnvironment.getMemoryMb())); + tryWithLimit(client, d -> putAtPath(d, "env.region", faasEnvironment.getRegion())); + + tryWithLimit(client, d -> putAtPath(d, "env.container.runtime", containerRuntime.getName())); + tryWithLimit(client, d -> putAtPath(d, "env.container.orchestrator", orchestrator.getName())); + return client; } @@ -168,8 +180,7 @@ static boolean clientMetadataDocumentTooLarge(final BsonDocument document) { new BsonDocumentCodec().encode(new BsonBinaryWriter(buffer), document, EncoderContext.builder().build()); return buffer.getPosition() > MAXIMUM_CLIENT_METADATA_ENCODED_SIZE; } - - private enum Environment { + private enum FaasEnvironment { AWS_LAMBDA("aws.lambda"), AZURE_FUNC("azure.func"), GCP_FUNC("gcp.func"), @@ -179,7 +190,7 @@ private enum Environment { @Nullable private final String name; - Environment(@Nullable final String name) { + FaasEnvironment(@Nullable final String name) { this.name = name; } @@ -225,6 +236,81 @@ public String getRegion() { } } + public enum ContainerRuntime { + DOCKER("docker") { + @Override + boolean isCurrentRuntimeContainer() { + try { + return Files.exists(get(File.separator + ".dockerenv")); + } catch (Exception e) { + return false; + // NOOP. This could be a SecurityException. + } + } + }, + UNKNOWN(null); + + @Nullable + private final String name; + + ContainerRuntime(@Nullable final String name) { + this.name = name; + } + + @Nullable + public String getName() { + return name; + } + + boolean isCurrentRuntimeContainer() { + return false; + } + + static ContainerRuntime determineExecutionContainer() { + for (ContainerRuntime allegedContainer : ContainerRuntime.values()) { + if (allegedContainer.isCurrentRuntimeContainer()) { + return allegedContainer; + } + } + return UNKNOWN; + } + } + + private enum Orchestrator { + K8S("kubernetes") { + @Override + boolean isCurrentOrchestrator() { + return System.getenv("KUBERNETES_SERVICE_HOST") != null; + } + }, + UNKNOWN(null); + + @Nullable + private final String name; + + Orchestrator(@Nullable final String name) { + this.name = name; + } + + @Nullable + public String getName() { + return name; + } + + boolean isCurrentOrchestrator() { + return false; + } + + static Orchestrator determineExecutionOrchestrator() { + for (Orchestrator alledgedOrchestrator : Orchestrator.values()) { + if (alledgedOrchestrator.isCurrentOrchestrator()) { + return alledgedOrchestrator; + } + } + return UNKNOWN; + } + } + @Nullable private static Integer getEnvInteger(final String name) { try { @@ -235,29 +321,29 @@ private static Integer getEnvInteger(final String name) { } } - static Environment getEnvironment() { - List result = new ArrayList<>(); + static FaasEnvironment getFaasEnvironment() { + List result = new ArrayList<>(); String awsExecutionEnv = System.getenv("AWS_EXECUTION_ENV"); if (System.getenv("VERCEL") != null) { - result.add(Environment.VERCEL); + result.add(FaasEnvironment.VERCEL); } if ((awsExecutionEnv != null && awsExecutionEnv.startsWith("AWS_Lambda_")) || System.getenv("AWS_LAMBDA_RUNTIME_API") != null) { - result.add(Environment.AWS_LAMBDA); + result.add(FaasEnvironment.AWS_LAMBDA); } if (System.getenv("FUNCTIONS_WORKER_RUNTIME") != null) { - result.add(Environment.AZURE_FUNC); + result.add(FaasEnvironment.AZURE_FUNC); } if (System.getenv("K_SERVICE") != null || System.getenv("FUNCTION_NAME") != null) { - result.add(Environment.GCP_FUNC); + result.add(FaasEnvironment.GCP_FUNC); } // vercel takes precedence over aws.lambda - if (result.equals(Arrays.asList(Environment.VERCEL, Environment.AWS_LAMBDA))) { - return Environment.VERCEL; + if (result.equals(Arrays.asList(FaasEnvironment.VERCEL, FaasEnvironment.AWS_LAMBDA))) { + return FaasEnvironment.VERCEL; } if (result.size() != 1) { - return Environment.UNKNOWN; + return FaasEnvironment.UNKNOWN; } return result.get(0); } diff --git a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java index b52806deeab..c4069ab0660 100644 --- a/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java +++ b/driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java @@ -29,7 +29,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -42,6 +48,9 @@ /** * See spec + * + *

+ * NOTE: This class also contains tests that aren't categorized as Prose tests. */ public class ClientMetadataHelperProseTest { private static final String APP_NAME = "app name"; @@ -168,6 +177,61 @@ public void test08NotLambda() { // Additional tests, not specified as prose tests: + @Test + void testKubernetesMetadataIncluded() { + withWrapper() + .withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8") + .withEnvironmentVariable("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local") + .run(() -> { + BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME); + expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'orchestrator': 'kubernetes'}}")); + BsonDocument actual = createActualClientMetadataDocument(); + assertEquals(expected, actual); + + performHello(); + }); + } + + @Test + void testDockerMetadataIncluded() { + try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { + Path path = Paths.get(File.separator + ".dockerenv"); + pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); + + withWrapper() + .withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8") + .run(() -> { + BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME); + expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'runtime': 'docker'}}")); + BsonDocument actual = createActualClientMetadataDocument(); + assertEquals(expected, actual); + + performHello(); + }); + } + } + + @Test + void testDockerAndKubernetesMetadataIncluded() { + try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { + Path path = Paths.get(File.separator + "/.dockerenv"); + pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); + + withWrapper() + .withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8") + .withEnvironmentVariable("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local") + .run(() -> { + BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME); + expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'runtime': 'docker', " + + "'orchestrator': 'kubernetes'}}")); + BsonDocument actual = createActualClientMetadataDocument(); + assertEquals(expected, actual); + + performHello(); + }); + } + } + @Test public void testLimitForDriverVersion() { // should create client metadata document and exclude the extra driver info if its too verbose