Skip to content

Commit fd8352f

Browse files
authored
Enable container and Kubernetes awareness to improve telemetry. (#1235)
JAVA-5072
1 parent 52f623c commit fd8352f

File tree

3 files changed

+168
-17
lines changed

3 files changed

+168
-17
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ configure(javaCodeCheckedProjects) {
257257
testImplementation 'org.spockframework:spock-core'
258258
testImplementation 'org.spockframework:spock-junit4'
259259
testImplementation("org.mockito:mockito-core:3.8.0")
260+
testImplementation("org.mockito:mockito-inline:3.8.0")
260261
testImplementation 'cglib:cglib-nodep:2.2.2'
261262
testImplementation 'org.objenesis:objenesis:1.3'
262263
testImplementation 'org.hamcrest:hamcrest-all:1.3'

driver-core/src/main/com/mongodb/internal/connection/ClientMetadataHelper.java

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import org.bson.codecs.EncoderContext;
3030
import org.bson.io.BasicOutputBuffer;
3131

32+
import java.io.File;
3233
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Files;
3335
import java.util.ArrayList;
3436
import java.util.Arrays;
3537
import java.util.List;
@@ -38,6 +40,7 @@
3840
import static com.mongodb.assertions.Assertions.isTrueArgument;
3941
import static java.lang.String.format;
4042
import static java.lang.System.getProperty;
43+
import static java.nio.file.Paths.get;
4144

4245
/**
4346
* <p>This class is not part of the public API and may be removed or changed at any time</p>
@@ -98,17 +101,26 @@ public static BsonDocument createClientMetadataDocument(@Nullable final String a
98101
putAtPath(d, "driver.name", listToString(fullDriverInfo.getDriverNames()));
99102
putAtPath(d, "driver.version", listToString(fullDriverInfo.getDriverVersions()));
100103
});
104+
101105
// optional fields:
102-
Environment environment = getEnvironment();
106+
FaasEnvironment faasEnvironment = getFaasEnvironment();
107+
ContainerRuntime containerRuntime = ContainerRuntime.determineExecutionContainer();
108+
Orchestrator orchestrator = Orchestrator.determineExecutionOrchestrator();
109+
103110
tryWithLimit(client, d -> putAtPath(d, "platform", listToString(baseDriverInfor.getDriverPlatforms())));
104111
tryWithLimit(client, d -> putAtPath(d, "platform", listToString(fullDriverInfo.getDriverPlatforms())));
105-
tryWithLimit(client, d -> putAtPath(d, "env.name", environment.getName()));
106112
tryWithLimit(client, d -> putAtPath(d, "os.name", getOperatingSystemName()));
107113
tryWithLimit(client, d -> putAtPath(d, "os.architecture", getProperty("os.arch", "unknown")));
108114
tryWithLimit(client, d -> putAtPath(d, "os.version", getProperty("os.version", "unknown")));
109-
tryWithLimit(client, d -> putAtPath(d, "env.timeout_sec", environment.getTimeoutSec()));
110-
tryWithLimit(client, d -> putAtPath(d, "env.memory_mb", environment.getMemoryMb()));
111-
tryWithLimit(client, d -> putAtPath(d, "env.region", environment.getRegion()));
115+
116+
tryWithLimit(client, d -> putAtPath(d, "env.name", faasEnvironment.getName()));
117+
tryWithLimit(client, d -> putAtPath(d, "env.timeout_sec", faasEnvironment.getTimeoutSec()));
118+
tryWithLimit(client, d -> putAtPath(d, "env.memory_mb", faasEnvironment.getMemoryMb()));
119+
tryWithLimit(client, d -> putAtPath(d, "env.region", faasEnvironment.getRegion()));
120+
121+
tryWithLimit(client, d -> putAtPath(d, "env.container.runtime", containerRuntime.getName()));
122+
tryWithLimit(client, d -> putAtPath(d, "env.container.orchestrator", orchestrator.getName()));
123+
112124
return client;
113125
}
114126

@@ -168,8 +180,7 @@ static boolean clientMetadataDocumentTooLarge(final BsonDocument document) {
168180
new BsonDocumentCodec().encode(new BsonBinaryWriter(buffer), document, EncoderContext.builder().build());
169181
return buffer.getPosition() > MAXIMUM_CLIENT_METADATA_ENCODED_SIZE;
170182
}
171-
172-
private enum Environment {
183+
private enum FaasEnvironment {
173184
AWS_LAMBDA("aws.lambda"),
174185
AZURE_FUNC("azure.func"),
175186
GCP_FUNC("gcp.func"),
@@ -179,7 +190,7 @@ private enum Environment {
179190
@Nullable
180191
private final String name;
181192

182-
Environment(@Nullable final String name) {
193+
FaasEnvironment(@Nullable final String name) {
183194
this.name = name;
184195
}
185196

@@ -225,6 +236,81 @@ public String getRegion() {
225236
}
226237
}
227238

239+
public enum ContainerRuntime {
240+
DOCKER("docker") {
241+
@Override
242+
boolean isCurrentRuntimeContainer() {
243+
try {
244+
return Files.exists(get(File.separator + ".dockerenv"));
245+
} catch (Exception e) {
246+
return false;
247+
// NOOP. This could be a SecurityException.
248+
}
249+
}
250+
},
251+
UNKNOWN(null);
252+
253+
@Nullable
254+
private final String name;
255+
256+
ContainerRuntime(@Nullable final String name) {
257+
this.name = name;
258+
}
259+
260+
@Nullable
261+
public String getName() {
262+
return name;
263+
}
264+
265+
boolean isCurrentRuntimeContainer() {
266+
return false;
267+
}
268+
269+
static ContainerRuntime determineExecutionContainer() {
270+
for (ContainerRuntime allegedContainer : ContainerRuntime.values()) {
271+
if (allegedContainer.isCurrentRuntimeContainer()) {
272+
return allegedContainer;
273+
}
274+
}
275+
return UNKNOWN;
276+
}
277+
}
278+
279+
private enum Orchestrator {
280+
K8S("kubernetes") {
281+
@Override
282+
boolean isCurrentOrchestrator() {
283+
return System.getenv("KUBERNETES_SERVICE_HOST") != null;
284+
}
285+
},
286+
UNKNOWN(null);
287+
288+
@Nullable
289+
private final String name;
290+
291+
Orchestrator(@Nullable final String name) {
292+
this.name = name;
293+
}
294+
295+
@Nullable
296+
public String getName() {
297+
return name;
298+
}
299+
300+
boolean isCurrentOrchestrator() {
301+
return false;
302+
}
303+
304+
static Orchestrator determineExecutionOrchestrator() {
305+
for (Orchestrator alledgedOrchestrator : Orchestrator.values()) {
306+
if (alledgedOrchestrator.isCurrentOrchestrator()) {
307+
return alledgedOrchestrator;
308+
}
309+
}
310+
return UNKNOWN;
311+
}
312+
}
313+
228314
@Nullable
229315
private static Integer getEnvInteger(final String name) {
230316
try {
@@ -235,29 +321,29 @@ private static Integer getEnvInteger(final String name) {
235321
}
236322
}
237323

238-
static Environment getEnvironment() {
239-
List<Environment> result = new ArrayList<>();
324+
static FaasEnvironment getFaasEnvironment() {
325+
List<FaasEnvironment> result = new ArrayList<>();
240326
String awsExecutionEnv = System.getenv("AWS_EXECUTION_ENV");
241327

242328
if (System.getenv("VERCEL") != null) {
243-
result.add(Environment.VERCEL);
329+
result.add(FaasEnvironment.VERCEL);
244330
}
245331
if ((awsExecutionEnv != null && awsExecutionEnv.startsWith("AWS_Lambda_"))
246332
|| System.getenv("AWS_LAMBDA_RUNTIME_API") != null) {
247-
result.add(Environment.AWS_LAMBDA);
333+
result.add(FaasEnvironment.AWS_LAMBDA);
248334
}
249335
if (System.getenv("FUNCTIONS_WORKER_RUNTIME") != null) {
250-
result.add(Environment.AZURE_FUNC);
336+
result.add(FaasEnvironment.AZURE_FUNC);
251337
}
252338
if (System.getenv("K_SERVICE") != null || System.getenv("FUNCTION_NAME") != null) {
253-
result.add(Environment.GCP_FUNC);
339+
result.add(FaasEnvironment.GCP_FUNC);
254340
}
255341
// vercel takes precedence over aws.lambda
256-
if (result.equals(Arrays.asList(Environment.VERCEL, Environment.AWS_LAMBDA))) {
257-
return Environment.VERCEL;
342+
if (result.equals(Arrays.asList(FaasEnvironment.VERCEL, FaasEnvironment.AWS_LAMBDA))) {
343+
return FaasEnvironment.VERCEL;
258344
}
259345
if (result.size() != 1) {
260-
return Environment.UNKNOWN;
346+
return FaasEnvironment.UNKNOWN;
261347
}
262348
return result.get(0);
263349
}

driver-core/src/test/functional/com/mongodb/internal/connection/ClientMetadataHelperProseTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@
2929
import org.junit.jupiter.api.Test;
3030
import org.junit.jupiter.params.ParameterizedTest;
3131
import org.junit.jupiter.params.provider.CsvSource;
32+
import org.mockito.MockedStatic;
33+
import org.mockito.Mockito;
3234

35+
import java.io.File;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.nio.file.Paths;
3339
import java.util.ArrayList;
3440
import java.util.List;
3541

@@ -42,6 +48,9 @@
4248

4349
/**
4450
* See <a href="https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#test-plan">spec</a>
51+
*
52+
* <p>
53+
* NOTE: This class also contains tests that aren't categorized as Prose tests.
4554
*/
4655
public class ClientMetadataHelperProseTest {
4756
private static final String APP_NAME = "app name";
@@ -168,6 +177,61 @@ public void test08NotLambda() {
168177

169178
// Additional tests, not specified as prose tests:
170179

180+
@Test
181+
void testKubernetesMetadataIncluded() {
182+
withWrapper()
183+
.withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8")
184+
.withEnvironmentVariable("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local")
185+
.run(() -> {
186+
BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME);
187+
expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'orchestrator': 'kubernetes'}}"));
188+
BsonDocument actual = createActualClientMetadataDocument();
189+
assertEquals(expected, actual);
190+
191+
performHello();
192+
});
193+
}
194+
195+
@Test
196+
void testDockerMetadataIncluded() {
197+
try (MockedStatic<Files> pathsMockedStatic = Mockito.mockStatic(Files.class)) {
198+
Path path = Paths.get(File.separator + ".dockerenv");
199+
pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true);
200+
201+
withWrapper()
202+
.withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8")
203+
.run(() -> {
204+
BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME);
205+
expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'runtime': 'docker'}}"));
206+
BsonDocument actual = createActualClientMetadataDocument();
207+
assertEquals(expected, actual);
208+
209+
performHello();
210+
});
211+
}
212+
}
213+
214+
@Test
215+
void testDockerAndKubernetesMetadataIncluded() {
216+
try (MockedStatic<Files> pathsMockedStatic = Mockito.mockStatic(Files.class)) {
217+
Path path = Paths.get(File.separator + "/.dockerenv");
218+
pathsMockedStatic.when(() -> Files.exists(path)).thenReturn(true);
219+
220+
withWrapper()
221+
.withEnvironmentVariable("AWS_EXECUTION_ENV", "AWS_Lambda_java8")
222+
.withEnvironmentVariable("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc.cluster.local")
223+
.run(() -> {
224+
BsonDocument expected = createExpectedClientMetadataDocument(APP_NAME);
225+
expected.put("env", BsonDocument.parse("{'name': 'aws.lambda', 'container': {'runtime': 'docker', "
226+
+ "'orchestrator': 'kubernetes'}}"));
227+
BsonDocument actual = createActualClientMetadataDocument();
228+
assertEquals(expected, actual);
229+
230+
performHello();
231+
});
232+
}
233+
}
234+
171235
@Test
172236
public void testLimitForDriverVersion() {
173237
// should create client metadata document and exclude the extra driver info if its too verbose

0 commit comments

Comments
 (0)