From e74fdf8766a0ad3b6397723b4ad2c52d80b26f86 Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Fri, 26 Apr 2024 15:32:14 -0700 Subject: [PATCH 1/7] Adds docker id check for ECS Fargate containers to GetDockerVendorInfo --- .../Agent/Core/Utilization/VendorInfo.cs | 69 +++++++++++++------ .../Utilization/VendorInfoTests.cs | 22 +++++- 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index 5326f19e54..71c28f2a2f 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -26,6 +26,7 @@ public class VendorInfo #if NETSTANDARD2_0 private const string ContainerIdV1Regex = @".*cpu.*([0-9a-f]{64})"; private const string ContainerIdV2Regex = ".*/docker/containers/([0-9a-f]{64})/.*"; + private const string AwsEcsMetadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"; #endif private const string AwsName = @"aws"; @@ -34,6 +35,7 @@ public class VendorInfo private const string PcfName = @"pcf"; private const string DockerName = @"docker"; private const string KubernetesName = @"kubernetes"; + private const string EcsFargateName = @"ecs-fargate"; private readonly string AwsTokenUri = @"http://169.254.169.254/latest/api/token"; private readonly string AwsMetadataUri = @"http://169.254.169.254/latest/dynamic/instance-identity/document"; @@ -279,37 +281,51 @@ private string GetProcessEnvironmentVariable(string variableName) #if NETSTANDARD2_0 public IVendorModel GetDockerVendorInfo(IFileReaderWrapper fileReaderWrapper) { - IVendorModel vendorModel = null; + IVendorModel vendorModel = null; + try + { + var fileContent = fileReaderWrapper.ReadAllText("/proc/self/mountinfo"); + vendorModel = TryGetDockerCGroupV2(fileContent); + if (vendorModel == null) + Log.Finest("Found /proc/self/mountinfo but failed to parse Docker container id."); + + } + catch (Exception ex) + { + Log.Finest(ex, "Failed to parse Docker container id from /proc/self/mountinfo."); + } + + if (vendorModel == null) // fall back to the v1 check if v2 wasn't successful + { try { - var fileContent = fileReaderWrapper.ReadAllText("/proc/self/mountinfo"); - vendorModel = TryGetDockerCGroupV2(fileContent); + var fileContent = fileReaderWrapper.ReadAllText("/proc/self/cgroup"); + vendorModel = TryGetDockerCGroupV1(fileContent); if (vendorModel == null) - Log.Finest("Found /proc/self/mountinfo but failed to parse Docker container id."); - + Log.Finest("Found /proc/self/cgroup but failed to parse Docker container id."); } catch (Exception ex) { - Log.Finest(ex, "Failed to parse Docker container id from /proc/self/mountinfo."); + Log.Finest(ex, "Failed to parse Docker container id from /proc/self/cgroup."); } + } - if (vendorModel == null) // fall back to the v1 check if v2 wasn't successful + if (vendorModel == null) + { + try { - try - { - var fileContent = fileReaderWrapper.ReadAllText("/proc/self/cgroup"); - vendorModel = TryGetDockerCGroupV1(fileContent); - if (vendorModel == null) - Log.Finest("Found /proc/self/cgroup but failed to parse Docker container id."); - } - catch (Exception ex) - { - Log.Finest(ex, "Failed to parse Docker container id from /proc/self/cgroup."); - return null; - } + vendorModel = TryGetEcsFargateDockerId(); + if (vendorModel == null) + Log.Finest("Found ECS_CONTAINER_METADATA_URI_V4 but failed to parse Docker container id."); + } + catch (Exception ex) + { + Log.Finest(ex, "Failed to parse Docker container id from ECS_CONTAINER_METADATA_URI_V4."); + return null; } + } - return vendorModel; + return vendorModel; } private IVendorModel TryGetDockerCGroupV1(string fileContent) @@ -353,6 +369,19 @@ private IVendorModel TryGetDockerCGroupV2(string fileContent) return id == null ? null : new DockerVendorModel(id); } + + private IVendorModel TryGetEcsFargateDockerId() + { + var metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV4EnvVar); + if (string.IsNullOrWhiteSpace(metadataUri)) + { + return null; + } + + var id = new Uri(metadataUri).Segments.LastOrDefault(); + return id == null ? null : new DockerVendorModel(id); + } + #endif public IVendorModel GetKubernetesInfo() diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs index 0cf2dd82ee..991b17e79a 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs @@ -4,9 +4,8 @@ using System; using System.IO; using System.Linq; -using NewRelic.Agent.Core.AgentHealth; using NewRelic.Agent.Configuration; -using NewRelic.Agent.TestUtilities; +using NewRelic.Agent.Core.AgentHealth; using NewRelic.SystemInterfaces; using NUnit.Framework; using Telerik.JustMock; @@ -27,6 +26,7 @@ public class VendorInfoTests private const string PcfInstanceIp = @"CF_INSTANCE_IP"; private const string PcfMemoryLimit = @"MEMORY_LIMIT"; private const string KubernetesServiceHost = @"KUBERNETES_SERVICE_HOST"; + private const string AwsEcsMetadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"; [SetUp] public void Setup() @@ -468,7 +468,23 @@ public void GetVendors_GetDockerVendorInfo_ParsesV1_IfMountinfoDoesNotExist() } [Test] - public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2() + public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_IfUnableToParseV1OrV2() + { + var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; + SetEnvironmentVariable(AwsEcsMetadataV4EnvVar, $"http://169.254.170.2/v4/{dockerId}", EnvironmentVariableTarget.Process); + + var vendorInfo = new VendorInfo(_configuration, _agentHealthReporter, _environment, _vendorHttpApiRequestor); + var mockFileReaderWrapper = Mock.Create(); + Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); + Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/cgroup")).Returns("foo bar baz"); + + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + Assert.That(model, Is.Not.Null); + Assert.That(model.Id, Is.EqualTo(dockerId)); + } + + [Test] + public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEcsFargate() { var vendorInfo = new VendorInfo(_configuration, _agentHealthReporter, _environment, _vendorHttpApiRequestor); var mockFileReaderWrapper = Mock.Create(); From 16c59cf653ced91d148c8257b3ba2e854574225d Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Mon, 29 Apr 2024 10:29:12 -0700 Subject: [PATCH 2/7] Use the Json returned from metadata URI --- .../Agent/Core/Utilization/VendorInfo.cs | 5 +- .../Utilization/VendorInfoTests.cs | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index 71c28f2a2f..892ac62b62 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -378,7 +378,10 @@ private IVendorModel TryGetEcsFargateDockerId() return null; } - var id = new Uri(metadataUri).Segments.LastOrDefault(); + var responseJson = _vendorHttpApiRequestor.CallVendorApi(new Uri(metadataUri), GetMethod, EcsFargateName); + var jObject = JObject.Parse(responseJson); + var idToken = jObject.SelectToken("DockerId"); + var id = NormalizeAndValidateMetadata((string)idToken, "DockerId", EcsFargateName); return id == null ? null : new DockerVendorModel(id); } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs index 991b17e79a..79d4a65955 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using Google.Protobuf.WellKnownTypes; using NewRelic.Agent.Configuration; using NewRelic.Agent.Core.AgentHealth; using NewRelic.SystemInterfaces; @@ -472,6 +474,58 @@ public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_IfUnableToParseV1OrV { var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; SetEnvironmentVariable(AwsEcsMetadataV4EnvVar, $"http://169.254.170.2/v4/{dockerId}", EnvironmentVariableTarget.Process); + Mock.Arrange(() => _vendorHttpApiRequestor.CallVendorApi(Arg.IsAny(), Arg.AnyString, Arg.AnyString, Arg.IsNull>())).Returns(""" +{ + "DockerId": "1e1698469422439ea356071e581e8545-2769485393", + "Name": "fargateapp", + "DockerName": "fargateapp", + "Image": "123456789012.dkr.ecr.us-west-2.amazonaws.com/fargatetest:latest", + "ImageID": "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:123456789012:cluster/testcluster", + "com.amazonaws.ecs.container-name": "fargateapp", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:123456789012:task/testcluster/1e1698469422439ea356071e581e8545", + "com.amazonaws.ecs.task-definition-family": "fargatetestapp", + "com.amazonaws.ecs.task-definition-version": "7" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 2 + }, + "CreatedAt": "2024-04-25T17:38:31.073208914Z", + "StartedAt": "2024-04-25T17:38:31.073208914Z", + "Type": "NORMAL", + "LogDriver": "awslogs", + "LogOptions": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/fargatetestapp", + "awslogs-region": "us-west-2", + "awslogs-stream": "ecs/fargateapp/1e1698469422439ea356071e581e8545" + }, + "ContainerARN": "arn:aws:ecs:us-west-2:123456789012:container/testcluster/1e1698469422439ea356071e581e8545/050256a5-a7f3-461c-a16f-aca4eae37b01", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "10.10.10.10" + ], + "AttachmentIndex": 0, + "MACAddress": "06:d7:3f:49:1d:a7", + "IPv4SubnetCIDRBlock": "10.10.10.0/20", + "DomainNameServers": [ + "10.10.10.2" + ], + "DomainNameSearchList": [ + "us-west-2.compute.internal" + ], + "PrivateDNSName": "ip-10-10-10-10.us-west-2.compute.internal", + "SubnetGatewayIpv4Address": "10.10.10.1/20" + } + ], + "Snapshotter": "overlayfs" +} +"""); var vendorInfo = new VendorInfo(_configuration, _agentHealthReporter, _environment, _vendorHttpApiRequestor); var mockFileReaderWrapper = Mock.Create(); @@ -486,6 +540,8 @@ public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_IfUnableToParseV1OrV [Test] public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEcsFargate() { + // Not setting the ECS_CONTAINER_METADATA_URI_V4 env var will cause the fargate check to be skipped. + var vendorInfo = new VendorInfo(_configuration, _agentHealthReporter, _environment, _vendorHttpApiRequestor); var mockFileReaderWrapper = Mock.Create(); Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); From 9ecd3e6fed1eea0839fece20ecf91e4c0ea0deeb Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Tue, 14 May 2024 14:30:03 -0700 Subject: [PATCH 3/7] Adds support for checking metadata V3 as a fallback to V4. --- .../Agent/Core/Utilization/VendorInfo.cs | 6 +++ .../Utilization/VendorInfoTests.cs | 51 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index 892ac62b62..73551eb34c 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -26,6 +26,7 @@ public class VendorInfo #if NETSTANDARD2_0 private const string ContainerIdV1Regex = @".*cpu.*([0-9a-f]{64})"; private const string ContainerIdV2Regex = ".*/docker/containers/([0-9a-f]{64})/.*"; + private const string AwsEcsMetadataV3EnvVar = "ECS_CONTAINER_METADATA_URI"; private const string AwsEcsMetadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"; #endif @@ -373,6 +374,11 @@ private IVendorModel TryGetDockerCGroupV2(string fileContent) private IVendorModel TryGetEcsFargateDockerId() { var metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV4EnvVar); + if (string.IsNullOrWhiteSpace(metadataUri)) + { + metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV3EnvVar); + } +; if (string.IsNullOrWhiteSpace(metadataUri)) { return null; diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs index 79d4a65955..6af1791f15 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs @@ -28,6 +28,7 @@ public class VendorInfoTests private const string PcfInstanceIp = @"CF_INSTANCE_IP"; private const string PcfMemoryLimit = @"MEMORY_LIMIT"; private const string KubernetesServiceHost = @"KUBERNETES_SERVICE_HOST"; + private const string AwsEcsMetadataV3EnvVar = "ECS_CONTAINER_METADATA_URI"; private const string AwsEcsMetadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"; [SetUp] @@ -470,7 +471,7 @@ public void GetVendors_GetDockerVendorInfo_ParsesV1_IfMountinfoDoesNotExist() } [Test] - public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_IfUnableToParseV1OrV2() + public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV4_IfUnableToParseV1OrV2() { var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; SetEnvironmentVariable(AwsEcsMetadataV4EnvVar, $"http://169.254.170.2/v4/{dockerId}", EnvironmentVariableTarget.Process); @@ -537,6 +538,54 @@ public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_IfUnableToParseV1OrV Assert.That(model.Id, Is.EqualTo(dockerId)); } + [Test] + public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV3_IfUnableToParseV1OrV2() + { + var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; + SetEnvironmentVariable(AwsEcsMetadataV3EnvVar, $"http://169.254.170.2/v3/{dockerId}", EnvironmentVariableTarget.Process); + Mock.Arrange(() => _vendorHttpApiRequestor.CallVendorApi(Arg.IsAny(), Arg.AnyString, Arg.AnyString, Arg.IsNull>())).Returns(""" +{ + "DockerId": "1e1698469422439ea356071e581e8545-2769485393", + "Name": "fargateapp", + "DockerName": "fargateapp", + "Image": "123456789012.dkr.ecr.us-west-2.amazonaws.com/fargatetest:latest", + "ImageID": "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd", + "Labels": { + "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:123456789012:cluster/testcluster", + "com.amazonaws.ecs.container-name": "fargateapp", + "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:123456789012:task/testcluster/1e1698469422439ea356071e581e8545", + "com.amazonaws.ecs.task-definition-family": "fargatetestapp", + "com.amazonaws.ecs.task-definition-version": "7" + }, + "DesiredStatus": "RUNNING", + "KnownStatus": "RUNNING", + "Limits": { + "CPU": 2 + }, + "CreatedAt": "2024-04-25T17:38:31.073208914Z", + "StartedAt": "2024-04-25T17:38:31.073208914Z", + "Type": "NORMAL", + "Networks": [ + { + "NetworkMode": "awsvpc", + "IPv4Addresses": [ + "10.10.10.10" + ] + } + ] +} +"""); + + var vendorInfo = new VendorInfo(_configuration, _agentHealthReporter, _environment, _vendorHttpApiRequestor); + var mockFileReaderWrapper = Mock.Create(); + Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); + Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/cgroup")).Returns("foo bar baz"); + + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + Assert.That(model, Is.Not.Null); + Assert.That(model.Id, Is.EqualTo(dockerId)); + } + [Test] public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEcsFargate() { From dfd365648242384b29984de0fb325d386def0d66 Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Tue, 14 May 2024 14:37:20 -0700 Subject: [PATCH 4/7] Random using got added. --- .../Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs index 6af1791f15..15cbf603f0 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Google.Protobuf.WellKnownTypes; using NewRelic.Agent.Configuration; using NewRelic.Agent.Core.AgentHealth; using NewRelic.SystemInterfaces; From 9a42a00cefc30fa050e54995ccf3da1824e6b8c8 Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Tue, 14 May 2024 17:19:12 -0700 Subject: [PATCH 5/7] Fix ecs fargate logic and match up with existing code. --- .../Agent/Core/Utilization/VendorInfo.cs | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index 73551eb34c..bb248b8425 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -315,14 +315,35 @@ public IVendorModel GetDockerVendorInfo(IFileReaderWrapper fileReaderWrapper) { try { - vendorModel = TryGetEcsFargateDockerId(); - if (vendorModel == null) - Log.Finest("Found ECS_CONTAINER_METADATA_URI_V4 but failed to parse Docker container id."); + var metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV4EnvVar); + if (!string.IsNullOrWhiteSpace(metadataUri)) + { + vendorModel = TryGetEcsFargateDockerId(metadataUri); + if (vendorModel == null) + Log.Finest($"Found {AwsEcsMetadataV4EnvVar} but failed to parse Docker container id."); + } } catch (Exception ex) { - Log.Finest(ex, "Failed to parse Docker container id from ECS_CONTAINER_METADATA_URI_V4."); - return null; + Log.Finest(ex, $"Failed to parse Docker container id from {AwsEcsMetadataV4EnvVar}."); + } + } + + if (vendorModel == null) + { + try + { + var metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV3EnvVar); + if (!string.IsNullOrWhiteSpace(metadataUri)) + { + vendorModel = TryGetEcsFargateDockerId(metadataUri); + if (vendorModel == null) + Log.Finest($"Found {AwsEcsMetadataV3EnvVar} but failed to parse Docker container id."); + } + } + catch (Exception ex) + { + Log.Finest(ex, $"Failed to parse Docker container id from {AwsEcsMetadataV3EnvVar}."); } } @@ -371,19 +392,8 @@ private IVendorModel TryGetDockerCGroupV2(string fileContent) return id == null ? null : new DockerVendorModel(id); } - private IVendorModel TryGetEcsFargateDockerId() + private IVendorModel TryGetEcsFargateDockerId(string metadataUri) { - var metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV4EnvVar); - if (string.IsNullOrWhiteSpace(metadataUri)) - { - metadataUri = GetProcessEnvironmentVariable(AwsEcsMetadataV3EnvVar); - } -; - if (string.IsNullOrWhiteSpace(metadataUri)) - { - return null; - } - var responseJson = _vendorHttpApiRequestor.CallVendorApi(new Uri(metadataUri), GetMethod, EcsFargateName); var jObject = JObject.Parse(responseJson); var idToken = jObject.SelectToken("DockerId"); From c67e41638690818fab82509d7c4b4d915ac52c49 Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Fri, 31 May 2024 11:11:47 -0700 Subject: [PATCH 6/7] Add support for Windows backed ECS clusters. --- .../Agent/Core/Utilization/VendorInfo.cs | 50 +++++++++---------- .../Utilization/VendorInfoTests.cs | 31 +++++++----- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index bb248b8425..fb5982645e 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -100,13 +100,10 @@ public IDictionary GetVendors() if (_configuration.UtilizationDetectDocker) { #if NETSTANDARD2_0 - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + var dockerVendorInfo = GetDockerVendorInfo(new FileReaderWrapper(), RuntimeInformation.IsOSPlatform(OSPlatform.Linux)); + if (dockerVendorInfo != null) { - var dockerVendorInfo = GetDockerVendorInfo(new FileReaderWrapper()); - if (dockerVendorInfo != null) - { - vendors.Add(dockerVendorInfo.VendorName, dockerVendorInfo); - } + vendors.Add(dockerVendorInfo.VendorName, dockerVendorInfo); } #endif } @@ -280,34 +277,37 @@ private string GetProcessEnvironmentVariable(string variableName) } #if NETSTANDARD2_0 - public IVendorModel GetDockerVendorInfo(IFileReaderWrapper fileReaderWrapper) + public IVendorModel GetDockerVendorInfo(IFileReaderWrapper fileReaderWrapper, bool isLinux) { IVendorModel vendorModel = null; - try - { - var fileContent = fileReaderWrapper.ReadAllText("/proc/self/mountinfo"); - vendorModel = TryGetDockerCGroupV2(fileContent); - if (vendorModel == null) - Log.Finest("Found /proc/self/mountinfo but failed to parse Docker container id."); - - } - catch (Exception ex) - { - Log.Finest(ex, "Failed to parse Docker container id from /proc/self/mountinfo."); - } - - if (vendorModel == null) // fall back to the v1 check if v2 wasn't successful + if (isLinux) { try { - var fileContent = fileReaderWrapper.ReadAllText("/proc/self/cgroup"); - vendorModel = TryGetDockerCGroupV1(fileContent); + var fileContent = fileReaderWrapper.ReadAllText("/proc/self/mountinfo"); + vendorModel = TryGetDockerCGroupV2(fileContent); if (vendorModel == null) - Log.Finest("Found /proc/self/cgroup but failed to parse Docker container id."); + Log.Finest("Found /proc/self/mountinfo but failed to parse Docker container id."); + } catch (Exception ex) { - Log.Finest(ex, "Failed to parse Docker container id from /proc/self/cgroup."); + Log.Finest(ex, "Failed to parse Docker container id from /proc/self/mountinfo."); + } + + if (vendorModel == null) // fall back to the v1 check if v2 wasn't successful + { + try + { + var fileContent = fileReaderWrapper.ReadAllText("/proc/self/cgroup"); + vendorModel = TryGetDockerCGroupV1(fileContent); + if (vendorModel == null) + Log.Finest("Found /proc/self/cgroup but failed to parse Docker container id."); + } + catch (Exception ex) + { + Log.Finest(ex, "Failed to parse Docker container id from /proc/self/cgroup."); + } } } diff --git a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs index 15cbf603f0..894883d3f3 100644 --- a/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs +++ b/tests/Agent/UnitTests/Core.UnitTest/Utilization/VendorInfoTests.cs @@ -340,7 +340,7 @@ public void GetVendors_GetDockerVendorInfo_ParsesV2() 1342 1429 0:300 / /sys/firmware ro,relatime - tmpfs tmpfs ro "); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, true); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo("adf04870aa0a9f01fb712e283765ee5d7c7b1c1c0ad8ebfdea20a8bb3ae382fb")); } @@ -369,7 +369,7 @@ public void GetVendors_GetDockerVendorInfo_ParsesV1_IfV2LookupFailsToParseFile() 1:cpuset:/docker/b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043 0::/docker/b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043"); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, true); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo("b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043")); } @@ -434,7 +434,7 @@ public void GetVendors_GetDockerVendorInfo_ParsesV1_ForCustomerIssue() 1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod04f9c4b4_5e71_4a0a_aa3a_f62f089e3f73.slice/cri-containerd-b10c13eeeea82c495c9e2fbb07ab448024715fdd55218e22cce6cd815c84bd58.scope "); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, true); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo("b10c13eeeea82c495c9e2fbb07ab448024715fdd55218e22cce6cd815c84bd58")); } @@ -464,14 +464,16 @@ public void GetVendors_GetDockerVendorInfo_ParsesV1_IfMountinfoDoesNotExist() 1:cpuset:/docker/b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043 0::/docker/b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043"); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, true); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo("b9d734e13dc5f508571d975edade94a05dfc637e73a83e11077a39bc11681043")); } - [Test] - public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV4_IfUnableToParseV1OrV2() + [TestCase(true)] + [TestCase(false)] + public void GetVendors_GetDockerVendorInfo_ParsesEcs_VarV4_IfUnableToParseV1OrV2(bool isLinux) { + // This docker ID is in the Fargate format, but the test is still valid for non-Fargate ECS hosts. var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; SetEnvironmentVariable(AwsEcsMetadataV4EnvVar, $"http://169.254.170.2/v4/{dockerId}", EnvironmentVariableTarget.Process); Mock.Arrange(() => _vendorHttpApiRequestor.CallVendorApi(Arg.IsAny(), Arg.AnyString, Arg.AnyString, Arg.IsNull>())).Returns(""" @@ -532,14 +534,16 @@ public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV4_IfUnableToPars Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/cgroup")).Returns("foo bar baz"); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, isLinux); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo(dockerId)); } - [Test] - public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV3_IfUnableToParseV1OrV2() + [TestCase(true)] + [TestCase(false)] + public void GetVendors_GetDockerVendorInfo_ParsesEcs_VarV3_IfUnableToParseV1OrV2(bool isLinux) { + // This docker ID is in the Fargate format, but the test is still valid for non-Fargate ECS hosts. var dockerId = "1e1698469422439ea356071e581e8545-2769485393"; SetEnvironmentVariable(AwsEcsMetadataV3EnvVar, $"http://169.254.170.2/v3/{dockerId}", EnvironmentVariableTarget.Process); Mock.Arrange(() => _vendorHttpApiRequestor.CallVendorApi(Arg.IsAny(), Arg.AnyString, Arg.AnyString, Arg.IsNull>())).Returns(""" @@ -580,13 +584,14 @@ public void GetVendors_GetDockerVendorInfo_ParsesEcsFargate_VarV3_IfUnableToPars Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/cgroup")).Returns("foo bar baz"); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, isLinux); Assert.That(model, Is.Not.Null); Assert.That(model.Id, Is.EqualTo(dockerId)); } - [Test] - public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEcsFargate() + [TestCase(true)] + [TestCase(false)] + public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEcs(bool isLinux) { // Not setting the ECS_CONTAINER_METADATA_URI_V4 env var will cause the fargate check to be skipped. @@ -595,7 +600,7 @@ public void GetVendors_GetDockerVendorInfo_ReturnsNull_IfUnableToParseV1OrV2OrEc Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/mountinfo")).Returns("blah blah blah"); Mock.Arrange(() => mockFileReaderWrapper.ReadAllText("/proc/self/cgroup")).Returns("foo bar baz"); - var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper); + var model = (DockerVendorModel)vendorInfo.GetDockerVendorInfo(mockFileReaderWrapper, isLinux); Assert.That(model, Is.Null); } #endif From fe2a7d1abbb19dfd3b18af6f558900dba5b70b17 Mon Sep 17 00:00:00 2001 From: Jacob Affinito Date: Fri, 31 May 2024 13:39:11 -0700 Subject: [PATCH 7/7] Adds support for checking ECS on .NET Framework --- .../Agent/Core/Utilization/VendorInfo.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs index fb5982645e..efeb9bb9f9 100644 --- a/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs +++ b/src/Agent/NewRelic/Agent/Core/Utilization/VendorInfo.cs @@ -12,23 +12,17 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; - -#if NETSTANDARD2_0 using System.IO; -using System.Runtime.InteropServices; -#endif namespace NewRelic.Agent.Core.Utilization { public class VendorInfo { private const string ValidateMetadataRegex = @"^[a-zA-Z0-9-_. /]*$"; -#if NETSTANDARD2_0 private const string ContainerIdV1Regex = @".*cpu.*([0-9a-f]{64})"; private const string ContainerIdV2Regex = ".*/docker/containers/([0-9a-f]{64})/.*"; private const string AwsEcsMetadataV3EnvVar = "ECS_CONTAINER_METADATA_URI"; private const string AwsEcsMetadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4"; -#endif private const string AwsName = @"aws"; private const string AzureName = @"azure"; @@ -99,13 +93,11 @@ public IDictionary GetVendors() // If Docker info is set to be checked, it must be checked for all vendors. if (_configuration.UtilizationDetectDocker) { -#if NETSTANDARD2_0 - var dockerVendorInfo = GetDockerVendorInfo(new FileReaderWrapper(), RuntimeInformation.IsOSPlatform(OSPlatform.Linux)); + var dockerVendorInfo = GetDockerVendorInfo(new FileReaderWrapper(), IsLinux()); if (dockerVendorInfo != null) { vendors.Add(dockerVendorInfo.VendorName, dockerVendorInfo); } -#endif } if (_configuration.UtilizationDetectKubernetes) @@ -276,7 +268,6 @@ private string GetProcessEnvironmentVariable(string variableName) } } -#if NETSTANDARD2_0 public IVendorModel GetDockerVendorInfo(IFileReaderWrapper fileReaderWrapper, bool isLinux) { IVendorModel vendorModel = null; @@ -401,8 +392,6 @@ private IVendorModel TryGetEcsFargateDockerId(string metadataUri) return id == null ? null : new DockerVendorModel(id); } -#endif - public IVendorModel GetKubernetesInfo() { var envVar = _environment.GetEnvironmentVariable("KUBERNETES_SERVICE_HOST"); @@ -461,8 +450,17 @@ public bool IsValidMetadata(string data) { return Regex.IsMatch(data, ValidateMetadataRegex); } - } + + private static bool IsLinux() + { #if NETSTANDARD2_0 + return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); +#else + return false; // No Linux on .NET Framework +#endif + } + } + // needed for unit testing only public interface IFileReaderWrapper { @@ -476,5 +474,4 @@ public string ReadAllText(string fileName) return File.ReadAllText(fileName); } } -#endif }