Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CGroupv2 support into Docker Extensions #839

Merged
merged 18 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
15b1b51
Initial changes
anoopbabu29 Dec 6, 2022
4cb146d
Adding cgroupv2 to test and updating the approach such that it checks…
anoopbabu29 Dec 15, 2022
cb8f573
Merge branch 'main' of https://github.com/anoopbabu29/opentelemetry-d…
anoopbabu29 Dec 16, 2022
7ff1941
Merge branch 'open-telemetry:main' into cgroupv2-sup
anoopbabu29 Dec 16, 2022
aad5630
Merge branch 'cgroupv2-sup' of https://github.com/anoopbabu29/opentel…
anoopbabu29 Dec 19, 2022
94be6e9
Respoding to Comments and build checks
anoopbabu29 Dec 19, 2022
856fed7
Merge branch 'main' into cgroupv2-sup
anoopbabu29 Dec 19, 2022
fd0c8e4
Adjusted build such that Detect checks both versions, and BuildResour…
anoopbabu29 Dec 22, 2022
5ba7994
Merge branch 'cgroupv2-sup' of https://github.com/anoopbabu29/opentel…
anoopbabu29 Dec 22, 2022
78a5a62
Merge branch 'main' into cgroupv2-sup
anoopbabu29 Dec 22, 2022
14ce9b1
Adjusting BuildResource at suggestion
anoopbabu29 Dec 23, 2022
492552d
Merge branch 'cgroupv2-sup' of https://github.com/anoopbabu29/opentel…
anoopbabu29 Dec 23, 2022
a3647b3
Added same tests as the go version as well as their regex expression
anoopbabu29 Jan 4, 2023
9e05aba
Merge branch 'main' into cgroupv2-sup
anoopbabu29 Jan 4, 2023
b5857f0
Addressing Comments by adding Enums and cleaning up tests
anoopbabu29 Jan 5, 2023
7b7e580
Merge branch 'main' into cgroupv2-sup
anoopbabu29 Jan 6, 2023
ba6b6ab
Fix parse mode version in test
anoopbabu29 Jan 6, 2023
d043c33
Merge branch 'main' into cgroupv2-sup
utpilla Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -28,24 +29,49 @@ namespace OpenTelemetry.Extensions.Docker.Resources;
public class DockerResourceDetector : IResourceDetector
{
private const string FILEPATH = "/proc/self/cgroup";
private const string FILEPATHV2 = "/proc/self/mountinfo";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any usage of FILEPATHV2 in detector. I'd guess, that ExtractContainerIdV2 should use it, instead of FILEPATH. Proper fix for it will also solve problem mentioned by @alexeypukhov , as that functions should check different file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change has been made to check both versions in the Detect method and assuming the version being used in the BuildResource method. This way, the problem mentioned by @alexeypukhov would be solved as you mentioned since no container id would be able to be found when checking for cgroupv1 if cgroupv2 is being used.

private const string HOSTNAME = "hostname";

/// <summary>
/// CGroup Parse Versions.
/// </summary>
internal enum ParseMode
{
/// <summary>
/// Represents CGroupV1.
/// </summary>
V1,

/// <summary>
/// Represents CGroupV2.
/// </summary>
V2,
}

/// <summary>
/// Detects the resource attributes from Docker.
/// </summary>
/// <returns>Resource with key-value pairs of resource attributes.</returns>
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;
}

/// <summary>
/// Builds the resource attributes from Container Id in file path.
/// </summary>
/// <param name="path">File path where container id exists.</param>
/// <param name="cgroupVersion">CGroup Version of file to parse from.</param>
/// <returns>Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource.</returns>
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))
{
Expand All @@ -58,11 +84,12 @@ internal Resource BuildResource(string path)
}

/// <summary>
/// Extracts Container Id from path.
/// Extracts Container Id from path using the cgroupv1 format.
/// </summary>
/// <param name="path">cgroup path.</param>
/// <param name="cgroupVersion">CGroup Version of file to parse from.</param>
/// <returns>Container Id, Null if not found or exception being thrown.</returns>
private string ExtractContainerId(string path)
private string ExtractContainerId(string path, ParseMode cgroupVersion)
{
try
{
Expand All @@ -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;
Expand All @@ -89,11 +128,11 @@ private string ExtractContainerId(string path)
}

/// <summary>
/// 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.
/// </summary>
/// <param name="line">line read from cgroup file.</param>
/// <returns>Container Id.</returns>
private string GetIdFromLine(string line)
/// <returns>Container Id, Null if not found.</returns>
private string GetIdFromLineV1(string line)
{
// This cgroup output line should have the container id in it
int lastSlashIndex = line.LastIndexOf('/');
Expand All @@ -116,6 +155,28 @@ private string GetIdFromLine(string line)
return containerId;
}

/// <summary>
/// Gets the Container Id from the line of the cgroupv2 format.
/// </summary>
/// <param name="line">line read from cgroup file.</param>
/// <returns>Container Id, Null if not found.</returns>
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenTelemetry.Extensions.Docker.Resources;
Expand All @@ -24,88 +25,168 @@ 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<TestCase> 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<TestCase> 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<TestCase> 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)
{
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; }
}
}