diff --git a/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs b/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs
index fb6d25f452..a589117d37 100644
--- a/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs
+++ b/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs
@@ -17,6 +17,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Text.RegularExpressions;
using OpenTelemetry.Extensions.Docker.Utils;
using OpenTelemetry.Resources;
@@ -28,6 +29,24 @@ namespace OpenTelemetry.Extensions.Docker.Resources;
public class DockerResourceDetector : IResourceDetector
{
private const string FILEPATH = "/proc/self/cgroup";
+ private const string FILEPATHV2 = "/proc/self/mountinfo";
+ private const string HOSTNAME = "hostname";
+
+ ///
+ /// CGroup Parse Versions.
+ ///
+ internal enum ParseMode
+ {
+ ///
+ /// Represents CGroupV1.
+ ///
+ V1,
+
+ ///
+ /// Represents CGroupV2.
+ ///
+ V2,
+ }
///
/// Detects the resource attributes from Docker.
@@ -35,17 +54,24 @@ public class DockerResourceDetector : IResourceDetector
/// Resource with key-value pairs of resource attributes.
public Resource Detect()
{
- return this.BuildResource(FILEPATH);
+ var cGroupBuild = this.BuildResource(FILEPATH, ParseMode.V1);
+ if (cGroupBuild == Resource.Empty)
+ {
+ cGroupBuild = this.BuildResource(FILEPATHV2, ParseMode.V2);
+ }
+
+ return cGroupBuild;
}
///
/// Builds the resource attributes from Container Id in file path.
///
/// File path where container id exists.
+ /// CGroup Version of file to parse from.
/// Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource.
- internal Resource BuildResource(string path)
+ internal Resource BuildResource(string path, ParseMode cgroupVersion)
{
- var containerId = this.ExtractContainerId(path);
+ var containerId = this.ExtractContainerId(path, cgroupVersion);
if (string.IsNullOrEmpty(containerId))
{
@@ -58,11 +84,12 @@ internal Resource BuildResource(string path)
}
///
- /// Extracts Container Id from path.
+ /// Extracts Container Id from path using the cgroupv1 format.
///
/// cgroup path.
+ /// CGroup Version of file to parse from.
/// Container Id, Null if not found or exception being thrown.
- private string ExtractContainerId(string path)
+ private string ExtractContainerId(string path, ParseMode cgroupVersion)
{
try
{
@@ -73,7 +100,19 @@ private string ExtractContainerId(string path)
foreach (string line in File.ReadLines(path))
{
- string containerId = (!string.IsNullOrEmpty(line)) ? this.GetIdFromLine(line) : null;
+ string containerId = null;
+ if (!string.IsNullOrEmpty(line))
+ {
+ if (cgroupVersion == ParseMode.V1)
+ {
+ containerId = this.GetIdFromLineV1(line);
+ }
+ else if (cgroupVersion == ParseMode.V2 && line.Contains(HOSTNAME))
+ {
+ containerId = this.GetIdFromLineV2(line);
+ }
+ }
+
if (!string.IsNullOrEmpty(containerId))
{
return containerId;
@@ -89,11 +128,11 @@ private string ExtractContainerId(string path)
}
///
- /// Gets the Container Id from the line after removing the prefix and suffix.
+ /// Gets the Container Id from the line after removing the prefix and suffix from the cgroupv1 format.
///
/// line read from cgroup file.
- /// Container Id.
- private string GetIdFromLine(string line)
+ /// Container Id, Null if not found.
+ private string GetIdFromLineV1(string line)
{
// This cgroup output line should have the container id in it
int lastSlashIndex = line.LastIndexOf('/');
@@ -116,6 +155,28 @@ private string GetIdFromLine(string line)
return containerId;
}
+ ///
+ /// Gets the Container Id from the line of the cgroupv2 format.
+ ///
+ /// line read from cgroup file.
+ /// Container Id, Null if not found.
+ private string GetIdFromLineV2(string line)
+ {
+ string containerId = null;
+ var match = Regex.Match(line, @".*/.+/([\w+-.]{64})/.*$");
+ if (match.Success)
+ {
+ containerId = match.Groups[1].Value;
+ }
+
+ if (string.IsNullOrEmpty(containerId) || !EncodingUtils.IsValidHexString(containerId))
+ {
+ return null;
+ }
+
+ return containerId;
+ }
+
private string RemovePrefixAndSuffixIfneeded(string input, int startIndex, int endIndex)
{
startIndex = (startIndex == -1) ? 0 : startIndex + 1;
diff --git a/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs
index 004410766e..5c50045727 100644
--- a/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs
+++ b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs
@@ -14,6 +14,7 @@
// limitations under the License.
//
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenTelemetry.Extensions.Docker.Resources;
@@ -24,83 +25,152 @@ namespace OpenTelemetry.Extensions.Docker.Tests;
public class DockerResourceDetectorTests
{
- // Invalid cgroup line
- private const string INVALIDCGROUPLINE =
- "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23zzzz";
-
- // cgroup line with prefix
- private const string CGROUPLINEWITHPREFIX =
- "13:name=systemd:/podruntime/docker/kubepods/crio-e2cc29debdf85dde404998aa128997a819ff";
-
- // Expected Container Id with prefix removed
- private const string CONTAINERIDWITHPREFIXREMOVED = "e2cc29debdf85dde404998aa128997a819ff";
-
- // cgroup line with suffix
- private const string CGROUPLINEWITHSUFFIX =
- "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa";
-
- // Expected Container Id with suffix removed
- private const string CONTAINERIDWITHSUFFIXREMOVED = "ac679f8a8319c8cf7d38e1adf263bc08d23";
-
- // cgroup line with prefix and suffix
- private const string CGROUPLINEWITHPREFIXandSUFFIX =
- "13:name=systemd:/podruntime/docker/kubepods/crio-dc679f8a8319c8cf7d38e1adf263bc08d23.stuff";
-
- // Expected Container Id with both prefix and suffix removed
- private const string CONTAINERIDWITHPREFIXANDSUFFIXREMOVED = "dc679f8a8319c8cf7d38e1adf263bc08d23";
-
- // cgroup line with container Id
- private const string CGROUPLINE =
- "13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356";
-
- // Expected Container Id
- private const string CONTAINERID =
- "d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356";
+ private readonly List testValidCasesV1 = new()
+ {
+ new TestCase()
+ {
+ Name = "cgroupv1 with prefix",
+ Line = "13:name=systemd:/podruntime/docker/kubepods/crio-e2cc29debdf85dde404998aa128997a819ff",
+ ExpectedContainerId = "e2cc29debdf85dde404998aa128997a819ff",
+ CgroupVersion = DockerResourceDetector.ParseMode.V1,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv1 with suffix",
+ Line = "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa",
+ ExpectedContainerId = "ac679f8a8319c8cf7d38e1adf263bc08d23",
+ CgroupVersion = DockerResourceDetector.ParseMode.V1,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv1 with prefix and suffix",
+ Line = "13:name=systemd:/podruntime/docker/kubepods/crio-dc679f8a8319c8cf7d38e1adf263bc08d23.stuff",
+ ExpectedContainerId = "dc679f8a8319c8cf7d38e1adf263bc08d23",
+ CgroupVersion = DockerResourceDetector.ParseMode.V1,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv1 with container Id",
+ Line = "13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356",
+ ExpectedContainerId = "d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356",
+ CgroupVersion = DockerResourceDetector.ParseMode.V1,
+ },
+ };
+
+ private readonly List testValidCasesV2 = new()
+ {
+ new TestCase()
+ {
+ Name = "cgroupv2 with container Id",
+ Line = "13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356/hostname",
+ ExpectedContainerId = "d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv2 with full line",
+ Line = "473 456 254:1 /docker/containers/dc64b5743252dbaef6e30521c34d6bbd1620c8ce65bdb7bf9e7143b61bb5b183/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw",
+ ExpectedContainerId = "dc64b5743252dbaef6e30521c34d6bbd1620c8ce65bdb7bf9e7143b61bb5b183",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv2 with minikube containerd mountinfo",
+ Line = "1537 1517 8:1 /var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3ff9320f4402ae6/hostname /etc/hostname rw,relatime - ext4 /dev/sda1 rw",
+ ExpectedContainerId = "fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3ff9320f4402ae6",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv2 with minikube docker mountinfo",
+ Line = "2327 2307 8:1 /var/lib/docker/containers/a1551a1d7e1881d6c18d2c9ec462cab6ad3666825f0adb2098e9d5b198fd7e19/hostname /etc/hostname rw,relatime - ext4 /dev/sda1 rw",
+ ExpectedContainerId = "a1551a1d7e1881d6c18d2c9ec462cab6ad3666825f0adb2098e9d5b198fd7e19",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv2 with minikube docker mountinfo2",
+ Line = "929 920 254:1 /docker/volumes/minikube/_data/lib/docker/containers/0eaa6718003210b6520f7e82d14b4c8d4743057a958a503626240f8d1900bc33/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw",
+ ExpectedContainerId = "0eaa6718003210b6520f7e82d14b4c8d4743057a958a503626240f8d1900bc33",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ new TestCase()
+ {
+ Name = "cgroupv2 with podman mountinfo",
+ Line = "1096 1088 0:104 /containers/overlay-containers/1a2de27e7157106568f7e081e42a8c14858c02bd9df30d6e352b298178b46809/userdata/hostname /etc/hostname rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=813800k,nr_inodes=203450,mode=700,uid=1000,gid=1000",
+ ExpectedContainerId = "1a2de27e7157106568f7e081e42a8c14858c02bd9df30d6e352b298178b46809",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ };
+
+ private readonly List testInvalidCases = new()
+ {
+ new TestCase()
+ {
+ Name = "Invalid cgroupv1 line",
+ Line = "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23zzzz",
+ CgroupVersion = DockerResourceDetector.ParseMode.V1,
+ },
+ new TestCase()
+ {
+ Name = "Invalid hex cgroupv2 line (contains a z)",
+ Line = "13:name=systemd:/var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3fz9320f4402ae6/hostname",
+ CgroupVersion = DockerResourceDetector.ParseMode.V2,
+ },
+ };
[Fact]
public void TestValidContainer()
{
var dockerResourceDetector = new DockerResourceDetector();
+ var allValidTestCases = this.testValidCasesV1.Concat(this.testValidCasesV2);
- using (TempFile tempFile = new TempFile())
+ foreach (var testCase in allValidTestCases)
{
- tempFile.Write(CGROUPLINEWITHPREFIX);
- Assert.Equal(CONTAINERIDWITHPREFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath)));
+ using TempFile tempFile = new TempFile();
+ tempFile.Write(testCase.Line);
+ Assert.Equal(
+ testCase.ExpectedContainerId,
+ this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath, testCase.CgroupVersion)));
}
+ }
- using (TempFile tempFile = new TempFile())
- {
- tempFile.Write(CGROUPLINEWITHSUFFIX);
- Assert.Equal(CONTAINERIDWITHSUFFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath)));
- }
+ [Fact]
+ public void TestInvalidContainer()
+ {
+ var dockerResourceDetector = new DockerResourceDetector();
- using (TempFile tempFile = new TempFile())
+ // Valid in cgroupv1 is not valid in cgroupv2
+ foreach (var testCase in this.testValidCasesV1)
{
- tempFile.Write(CGROUPLINEWITHPREFIXandSUFFIX);
- Assert.Equal(CONTAINERIDWITHPREFIXANDSUFFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath)));
+ using TempFile tempFile = new TempFile();
+ tempFile.Write(testCase.Line);
+ Assert.Equal(
+ dockerResourceDetector.BuildResource(tempFile.FilePath, DockerResourceDetector.ParseMode.V2),
+ Resource.Empty);
}
- using (TempFile tempFile = new TempFile())
+ // Valid in cgroupv1 is not valid in cgroupv1
+ foreach (var testCase in this.testValidCasesV2)
{
- tempFile.Write(CGROUPLINE);
- Assert.Equal(CONTAINERID, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath)));
+ using TempFile tempFile = new TempFile();
+ tempFile.Write(testCase.Line);
+ Assert.Equal(
+ dockerResourceDetector.BuildResource(tempFile.FilePath, DockerResourceDetector.ParseMode.V1),
+ Resource.Empty);
}
- }
- [Fact]
- public void TestInvalidContainer()
- {
- var dockerResourceDetector = new DockerResourceDetector();
-
- // test invalid containerId (non-hex)
- using (TempFile tempFile = new TempFile())
+ // test invalid cases
+ foreach (var testCase in this.testInvalidCases)
{
- tempFile.Write(INVALIDCGROUPLINE);
- Assert.Equal(dockerResourceDetector.BuildResource(tempFile.FilePath), Resource.Empty);
+ using TempFile tempFile = new TempFile();
+ tempFile.Write(testCase.Line);
+ Assert.Equal(dockerResourceDetector.BuildResource(tempFile.FilePath, testCase.CgroupVersion), Resource.Empty);
}
// test invalid file
- Assert.Equal(dockerResourceDetector.BuildResource(Path.GetTempPath()), Resource.Empty);
+ Assert.Equal(dockerResourceDetector.BuildResource(Path.GetTempPath(), DockerResourceDetector.ParseMode.V1), Resource.Empty);
+ Assert.Equal(dockerResourceDetector.BuildResource(Path.GetTempPath(), DockerResourceDetector.ParseMode.V2), Resource.Empty);
}
private string GetContainerId(Resource resource)
@@ -108,4 +178,15 @@ private string GetContainerId(Resource resource)
var resourceAttributes = resource.Attributes.ToDictionary(x => x.Key, x => x.Value);
return resourceAttributes[DockerSemanticConventions.AttributeContainerID]?.ToString();
}
+
+ private class TestCase
+ {
+ public string Name { get; set; }
+
+ public string Line { get; set; }
+
+ public string ExpectedContainerId { get; set; }
+
+ public DockerResourceDetector.ParseMode CgroupVersion { get; set; }
+ }
}