Skip to content

Commit

Permalink
#3308 Support adding container.id to resource metadata (#3321)
Browse files Browse the repository at this point in the history
* Support adding container.id to resource metadta

* Address review comments

* Address review feedback

* Change containerResource to cached Resource factory

* Make method private

* Change to Paths and Files

* Fix bad merge and change regex

* Remove debug code, remove regex

* Add nullable annotation

Co-authored-by: Anuraag Agrawal <[email protected]>
  • Loading branch information
lo-jason and Anuraag Agrawal authored Jul 21, 2021
1 parent 0d2b7b0 commit 2cb461d
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
Comparing source compatibility of against
No changes.
+++ NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.extension.resources.ContainerResource (not serializable)
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+++ NEW SUPERCLASS: java.lang.Object
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.resources.Resource get()
+++ NEW CLASS: PUBLIC(+) io.opentelemetry.sdk.extension.resources.ContainerResourceProvider (not serializable)
+++ CLASS FILE FORMAT VERSION: 52.0 <- n.a.
+++ NEW SUPERCLASS: java.lang.Object
+++ NEW CONSTRUCTOR: PUBLIC(+) ContainerResourceProvider()
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.Resource createResource(io.opentelemetry.sdk.autoconfigure.ConfigProperties)
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.resources;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.internal.OtelEncodingUtils;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;

/** Factory for {@link Resource} retrieving Container ID information. */
public final class ContainerResource {

private static final Logger logger = Logger.getLogger(ContainerResource.class.getName());
private static final String UNIQUE_HOST_NAME_FILE_NAME = "/proc/self/cgroup";
private static final Resource INSTANCE = buildSingleton(UNIQUE_HOST_NAME_FILE_NAME);

@IgnoreJRERequirement
private static Resource buildSingleton(String uniqueHostNameFileName) {
// can't initialize this statically without running afoul of animalSniffer on paths
return buildResource(Paths.get(uniqueHostNameFileName));
}

// package private for testing
static Resource buildResource(Path path) {
String containerId = extractContainerId(path);

if (containerId == null || containerId.isEmpty()) {
return Resource.empty();
} else {
return Resource.create(Attributes.of(ResourceAttributes.CONTAINER_ID, containerId));
}
}

/** Returns resource with container information. */
public static Resource get() {
return INSTANCE;
}

/**
* Each line of cgroup file looks like "14:name=systemd:/docker/.../... A hex string is expected
* inside the last section separated by '/' Each segment of the '/' can contain metadata separated
* by either '.' (at beginning) or '-' (at end)
*
* @return containerId
*/
@IgnoreJRERequirement
@Nullable
private static String extractContainerId(Path cgroupFilePath) {
if (!Files.exists(cgroupFilePath) || !Files.isReadable(cgroupFilePath)) {
return null;
}
try (Stream<String> lines = Files.lines(cgroupFilePath)) {
Optional<String> value =
lines
.filter(line -> !line.isEmpty())
.map(line -> getIdFromLine(line))
.filter(Objects::nonNull)
.findFirst();
if (value.isPresent()) {
return value.get();
}
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to read file: " + e.getMessage());
}
return null;
}

@Nullable
private static String getIdFromLine(String line) {
// This cgroup output line should have the container id in it
int lastSlashIdx = line.lastIndexOf("/");
if (lastSlashIdx < 0) {
return null;
}

String lastSection = line.substring(lastSlashIdx + 1);
int startIdx = lastSection.indexOf("-");
int endIdx = lastSection.lastIndexOf(".");

String containerId =
lastSection.substring(
startIdx == -1 ? 0 : startIdx + 1, endIdx == -1 ? lastSection.length() : endIdx);
if (OtelEncodingUtils.isValidBase16String(containerId) && !containerId.isEmpty()) {
return containerId;
} else {
return null;
}
}

private ContainerResource() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.resources;

import io.opentelemetry.sdk.autoconfigure.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;

/** {@link ResourceProvider} for automatically configuring {@link ResourceProvider}. */
public class ContainerResourceProvider implements ResourceProvider {
@Override
public Resource createResource(ConfigProperties config) {
return ContainerResource.get();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
io.opentelemetry.sdk.extension.resources.ContainerResourceProvider
io.opentelemetry.sdk.extension.resources.HostResourceProvider
io.opentelemetry.sdk.extension.resources.OsResourceProvider
io.opentelemetry.sdk.extension.resources.ProcessResourceProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.resources;

import static io.opentelemetry.sdk.extension.resources.ContainerResource.buildResource;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

public class ContainerResourceTest {

// Invalid because ID is not a hex string
private static final String INVALID_CGROUP_LINE_1 =
"13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23zzzz";

// with suffix
private static final String CGROUP_LINE_1 =
"13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa";
private static final String EXPECTED_CGROUP_1 = "ac679f8a8319c8cf7d38e1adf263bc08d23";

// with prefix and suffix
private static final String CGROUP_LINE_2 =
"13:name=systemd:/podruntime/docker/kubepods/crio-dc679f8a8319c8cf7d38e1adf263bc08d23.stuff";
private static final String EXPECTED_CGROUP_2 = "dc679f8a8319c8cf7d38e1adf263bc08d23";

// just container id
private static final String CGROUP_LINE_3 =
"13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356";
private static final String EXPECTED_CGROUP_3 =
"d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356";

// with prefix
private static final String CGROUP_LINE_4 =
"//\n"
+ "1:name=systemd:/podruntime/docker/kubepods/docker-dc579f8a8319c8cf7d38e1adf263bc08d23"
+ "2:name=systemd:/podruntime/docker/kubepods/docker-dc579f8a8319c8cf7d38e1adf263bc08d23"
+ "3:name=systemd:/podruntime/docker/kubepods/docker-dc579f8a8319c8cf7d38e1adf263bc08d23";

private static final String EXPECTED_CGROUP_4 = "dc579f8a8319c8cf7d38e1adf263bc08d23";

@Test
public void testNegativeCases(@TempDir Path tempFolder) throws IOException {
// invalid containerId (non-hex)
Path cgroup = createCGroup(tempFolder.resolve("cgroup1"), INVALID_CGROUP_LINE_1);
assertThat(buildResource(cgroup)).isEqualTo(Resource.empty());

// test invalid file
cgroup = tempFolder.resolve("DoesNotExist");
assertThat(buildResource(cgroup)).isEqualTo(Resource.empty());
}

@Test
public void testContainer(@TempDir Path tempFolder) throws IOException {
Path cgroup = createCGroup(tempFolder.resolve("cgroup1"), CGROUP_LINE_1);
assertThat(getContainerId(buildResource(cgroup))).isEqualTo(EXPECTED_CGROUP_1);

Path cgroup2 = createCGroup(tempFolder.resolve("cgroup2"), CGROUP_LINE_2);
assertThat(getContainerId(buildResource(cgroup2))).isEqualTo(EXPECTED_CGROUP_2);

Path cgroup3 = createCGroup(tempFolder.resolve("cgroup3"), CGROUP_LINE_3);
assertThat(getContainerId(buildResource(cgroup3))).isEqualTo(EXPECTED_CGROUP_3);

Path cgroup4 = createCGroup(tempFolder.resolve("cgroup4"), CGROUP_LINE_4);
assertThat(getContainerId(buildResource(cgroup4))).isEqualTo(EXPECTED_CGROUP_4);
}

private static String getContainerId(Resource resource) {
return resource.getAttributes().get(ResourceAttributes.CONTAINER_ID);
}

private static Path createCGroup(Path path, String line) throws IOException {
return Files.write(path, line.getBytes(StandardCharsets.UTF_8));
}
}

0 comments on commit 2cb461d

Please sign in to comment.