From fd081725a4629adc5f2533bdd287e77c4b860779 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 26 Oct 2023 17:22:34 -0700 Subject: [PATCH 1/6] Enable container and Kubernetes awareness for improved telemetry. JAVA-5072 --- .../connection/ClientMetadataHelper.java | 119 +++++++++++++++--- .../ClientMetadataHelperProseTest.java | 18 +++ 2 files changed, 120 insertions(+), 17 deletions(-) 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..be96f80f3a4 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -30,6 +30,8 @@ import org.bson.io.BasicOutputBuffer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -98,17 +100,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 +179,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 +189,7 @@ private enum Environment { @Nullable private final String name; - Environment(@Nullable final String name) { + FaasEnvironment(@Nullable final String name) { this.name = name; } @@ -225,6 +235,81 @@ public String getRegion() { } } + public enum ContainerRuntime { + DOCKER("docker") { + @Override + boolean isCurrentRuntimeContainer() { + try { + return Files.exists(Paths.get("/.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 +320,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..8cd9dca591c 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 @@ -42,6 +42,9 @@ /** * See spec + * + *

+ * NOTE: This class also contains tests which are not specified as Prose ones. */ public class ClientMetadataHelperProseTest { private static final String APP_NAME = "app name"; @@ -168,6 +171,21 @@ 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 public void testLimitForDriverVersion() { // should create client metadata document and exclude the extra driver info if its too verbose From d5e7441963f13e4bd6bfc547b3d10a04e5218c9d Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 26 Oct 2023 20:05:59 -0700 Subject: [PATCH 2/6] Add tests. JAVA-5210 --- build.gradle | 1 + config/spotbugs/exclude.xml | 5 ++++ .../connection/ClientMetadataHelper.java | 5 ++-- .../ClientMetadataHelperProseTest.java | 29 ++++++++++++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) 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/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index d35f0a81c8a..b05d83bec43 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -277,4 +277,9 @@ + + + + + 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 be96f80f3a4..f47f609e7d7 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -31,7 +31,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -40,6 +39,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

@@ -240,7 +240,7 @@ public enum ContainerRuntime { @Override boolean isCurrentRuntimeContainer() { try { - return Files.exists(Paths.get("/.dockerenv")); + return Files.exists(get("/.dockerenv")); } catch (Exception e) { return false; // NOOP. This could be a SecurityException. @@ -248,7 +248,6 @@ boolean isCurrentRuntimeContainer() { } }, UNKNOWN(null); - @Nullable private final String name; 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 8cd9dca591c..7aca9a44cf3 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,12 @@ 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.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -44,7 +49,7 @@ * See spec * *

- * NOTE: This class also contains tests which are not specified as Prose ones. + * NOTE: This class also contains tests that aren't categorized as Prose tests. */ public class ClientMetadataHelperProseTest { private static final String APP_NAME = "app name"; @@ -186,6 +191,28 @@ void testKubernetesMetadataIncluded() { }); } + + @Test + void testDockerMetadataIncluded() { + try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { + Path path = Paths.get("/.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 From 735541f94841368ce2fdae4b55b33b065fa4f8fb Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 27 Oct 2023 11:16:39 -0700 Subject: [PATCH 3/6] Add file separator. JAVA-5210 --- config/spotbugs/exclude.xml | 5 ----- .../mongodb/internal/connection/ClientMetadataHelper.java | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index b05d83bec43..d35f0a81c8a 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -277,9 +277,4 @@ - - - - - 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 f47f609e7d7..7023f94021b 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -29,6 +29,7 @@ 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; @@ -240,7 +241,7 @@ public enum ContainerRuntime { @Override boolean isCurrentRuntimeContainer() { try { - return Files.exists(get("/.dockerenv")); + return Files.exists(get(File.separator + ".dockerenv")); } catch (Exception e) { return false; // NOOP. This could be a SecurityException. From 19923a0ff554c9adf3b2e93c89c602d3242f0c97 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 27 Oct 2023 16:26:26 -0700 Subject: [PATCH 4/6] Add additional test case. JAVA-5210 --- .../ClientMetadataHelperProseTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 7aca9a44cf3..21a2e8bb7be 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 @@ -191,9 +191,27 @@ void testKubernetesMetadataIncluded() { }); } - @Test void testDockerMetadataIncluded() { + try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { + Path path = Paths.get("/.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("/.dockerenv"); pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); From b5221ccf66d298dbdbdf0c78fc116cc19051ea66 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 27 Oct 2023 17:06:21 -0700 Subject: [PATCH 5/6] Add whitespace. JAVA-5210 --- .../com/mongodb/internal/connection/ClientMetadataHelper.java | 1 + 1 file changed, 1 insertion(+) 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 7023f94021b..5ca1e53497f 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java @@ -249,6 +249,7 @@ boolean isCurrentRuntimeContainer() { } }, UNKNOWN(null); + @Nullable private final String name; From 6158afb2119550c13e1b337c5a8f276db0c073f9 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Fri, 27 Oct 2023 17:48:12 -0700 Subject: [PATCH 6/6] Add file separator to tests. JAVA-5210 --- .../internal/connection/ClientMetadataHelperProseTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 21a2e8bb7be..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 @@ -32,6 +32,7 @@ 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; @@ -194,7 +195,7 @@ void testKubernetesMetadataIncluded() { @Test void testDockerMetadataIncluded() { try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { - Path path = Paths.get("/.dockerenv"); + Path path = Paths.get(File.separator + ".dockerenv"); pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); withWrapper() @@ -213,7 +214,7 @@ void testDockerMetadataIncluded() { @Test void testDockerAndKubernetesMetadataIncluded() { try (MockedStatic pathsMockedStatic = Mockito.mockStatic(Files.class)) { - Path path = Paths.get("/.dockerenv"); + Path path = Paths.get(File.separator + "/.dockerenv"); pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true); withWrapper()