Skip to content

Commit

Permalink
Enable container and Kubernetes awareness to improve telemetry. (#1235)
Browse files Browse the repository at this point in the history
  • Loading branch information
vbabanin authored Oct 30, 2023
1 parent 52f623c commit fd8352f
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 17 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
* <p>This class is not part of the public API and may be removed or changed at any time</p>
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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"),
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand All @@ -235,29 +321,29 @@ private static Integer getEnvInteger(final String name) {
}
}

static Environment getEnvironment() {
List<Environment> result = new ArrayList<>();
static FaasEnvironment getFaasEnvironment() {
List<FaasEnvironment> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -42,6 +48,9 @@

/**
* See <a href="https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#test-plan">spec</a>
*
* <p>
* NOTE: This class also contains tests that aren't categorized as Prose tests.
*/
public class ClientMetadataHelperProseTest {
private static final String APP_NAME = "app name";
Expand Down Expand Up @@ -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<Files> 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<Files> 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
Expand Down

0 comments on commit fd8352f

Please sign in to comment.