From b530a2fc140b09e7431f47923ae9231ef2311aef Mon Sep 17 00:00:00 2001 From: Swetha Ravichandran Date: Wed, 8 Jun 2022 16:06:11 -0700 Subject: [PATCH] #172 Resource detectors for Docker (#206) * #172 Resource detectors for Docker --- .../ISSUE_TEMPLATE/comp_extensions_docker.md | 41 ++++++ .github/component_owners.yml | 4 + .../workflows/package-Extensions.Docker.yml | 49 +++++++ opentelemetry-dotnet-contrib.sln | 14 ++ .../AssemblyInfo.cs | 23 +++ .../CHANGELOG.md | 12 ++ .../DockerExtensionsEventSource.cs | 63 +++++++++ .../OpenTelemetry.Extensions.Docker.csproj | 10 ++ src/OpenTelemetry.Extensions.Docker/README.md | 38 +++++ .../Resources/DockerResourceDetector.cs | 131 ++++++++++++++++++ .../Resources/DockerSemanticConventions.cs | 23 +++ .../Utils/EncodingUtils.cs | 37 +++++ ...enTelemetry.Extensions.Docker.Tests.csproj | 23 +++ .../Resources/DockerResourceDetectorTests.cs | 112 +++++++++++++++ .../Resources/TempFile.cs | 67 +++++++++ 15 files changed, 647 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/comp_extensions_docker.md create mode 100644 .github/workflows/package-Extensions.Docker.yml create mode 100644 src/OpenTelemetry.Extensions.Docker/AssemblyInfo.cs create mode 100644 src/OpenTelemetry.Extensions.Docker/CHANGELOG.md create mode 100644 src/OpenTelemetry.Extensions.Docker/DockerExtensionsEventSource.cs create mode 100644 src/OpenTelemetry.Extensions.Docker/OpenTelemetry.Extensions.Docker.csproj create mode 100644 src/OpenTelemetry.Extensions.Docker/README.md create mode 100644 src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs create mode 100644 src/OpenTelemetry.Extensions.Docker/Resources/DockerSemanticConventions.cs create mode 100644 src/OpenTelemetry.Extensions.Docker/Utils/EncodingUtils.cs create mode 100644 test/OpenTelemetry.Extensions.Docker.Tests/OpenTelemetry.Extensions.Docker.Tests.csproj create mode 100644 test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs create mode 100644 test/OpenTelemetry.Extensions.Docker.Tests/Resources/TempFile.cs diff --git a/.github/ISSUE_TEMPLATE/comp_extensions_docker.md b/.github/ISSUE_TEMPLATE/comp_extensions_docker.md new file mode 100644 index 0000000000..0f6b510858 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/comp_extensions_docker.md @@ -0,0 +1,41 @@ +--- +name: OpenTelemetry.Extensions.Docker +about: Issue with OpenTelemetry.Extensions.Docker +labels: comp:extensions.docker +--- + +# Issue with OpenTelemetry.Extensions.Docker + +List of [all OpenTelemetry NuGet +packages](https://www.nuget.org/profiles/OpenTelemetry) and version that you are +using (e.g. `OpenTelemetry 1.0.2`): + +* TBD + +Runtime version (e.g. `net462`, `net48`, `netcoreapp3.1`, `net6.0` etc. You can +find this information from the `*.csproj` file): + +* TBD + +**Is this a feature request or a bug?** + +* [ ] Feature Request +* [ ] Bug + +**What is the expected behavior?** + +What do you expect to see? + +**What is the actual behavior?** + +What did you see instead? If you are reporting a bug, create a self-contained +project using the template of your choice and apply the minimum required code to +result in the issue you're observing. We will close this issue if: + +* The repro project you share with us is complex. We can't investigate custom + projects, so don't point us to such, please. +* If we can not reproduce the behavior you're reporting. + +## Additional Context + +Add any other context about the feature request here. diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 144badb8c4..e42885e953 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -2,6 +2,8 @@ # Each component identified by its path prefix has a list of users components: + src/OpenTelemetry.Extensions.Docker/: + - swetharavichandrancisco src/OpenTelemetry.Contrib.Extensions.AWSXRay/: - srprash - lupengamzn @@ -66,6 +68,8 @@ components: - reyang - utpilla - Yun-Ting + test/OpenTelemetry.Extensions.Docker.Tests/: + - swetharavichandrancisco test/OpenTelemetry.Exporter.Instana.Tests/: - zivaninstana test/OpenTelemetry.Exporter.Stackdriver.Tests/: diff --git a/.github/workflows/package-Extensions.Docker.yml b/.github/workflows/package-Extensions.Docker.yml new file mode 100644 index 0000000000..bb1bf1680f --- /dev/null +++ b/.github/workflows/package-Extensions.Docker.yml @@ -0,0 +1,49 @@ +name: Pack OpenTelemetry.Extensions.Docker + +on: + workflow_dispatch: + inputs: + logLevel: + description: 'Log level' + required: true + default: 'warning' + push: + tags: + - 'Extensions.Docker-*' # trigger when we create a tag with prefix "Extensions.Docker-" + +jobs: + build-test-pack: + runs-on: ${{ matrix.os }} + env: + PROJECT: OpenTelemetry.Extensions.Docker + + strategy: + matrix: + os: [windows-latest] + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # fetching all + + - name: Install dependencies + run: dotnet restore + + - name: dotnet build ${{env.PROJECT}} + run: dotnet build src/${{env.PROJECT}} --configuration Release --no-restore -p:Deterministic=true + + - name: dotnet test ${{env.PROJECT}} + run: dotnet test test/${{env.PROJECT}}.Tests + + - name: dotnet pack ${{env.PROJECT}} + run: dotnet pack src/${{env.PROJECT}} --configuration Release --no-build + + - name: Publish Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{env.PROJECT}}-packages + path: '**/${{env.PROJECT}}/bin/**/*.*nupkg' + + - name: Publish Nuget + run: | + nuget push **/${{env.PROJECT}}/bin/**/*.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_TOKEN }} -SymbolApiKey ${{ secrets.NUGET_TOKEN }} \ No newline at end of file diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index 631728d407..969a91d702 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -177,6 +177,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Tests", "test\OpenTelemetry.Extensions.Tests\OpenTelemetry.Extensions.Tests.csproj", "{2117F4E3-6612-4E4D-A757-27271EEB7783}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Docker", "src\OpenTelemetry.Extensions.Docker\OpenTelemetry.Extensions.Docker.csproj", "{498A6808-C0DF-441F-A764-51A3BC4B8FC5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Docker.Tests", "test\OpenTelemetry.Extensions.Docker.Tests\OpenTelemetry.Extensions.Docker.Tests.csproj", "{FB41E19E-2682-4D07-BA59-FD5205AFA71E}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Geneva", "src\OpenTelemetry.Exporter.Geneva\OpenTelemetry.Exporter.Geneva.csproj", "{1105C814-31DA-4214-BEA8-6DB5FC12C808}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Geneva.Benchmark", "test\OpenTelemetry.Exporter.Geneva.Benchmark\OpenTelemetry.Exporter.Geneva.Benchmark.csproj", "{F53FD7F5-DBC0-4FA5-83BA-B4C07A5BD248}" @@ -369,6 +373,14 @@ Global {2117F4E3-6612-4E4D-A757-27271EEB7783}.Debug|Any CPU.Build.0 = Debug|Any CPU {2117F4E3-6612-4E4D-A757-27271EEB7783}.Release|Any CPU.ActiveCfg = Release|Any CPU {2117F4E3-6612-4E4D-A757-27271EEB7783}.Release|Any CPU.Build.0 = Release|Any CPU + {498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {498A6808-C0DF-441F-A764-51A3BC4B8FC5}.Release|Any CPU.Build.0 = Release|Any CPU + {FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB41E19E-2682-4D07-BA59-FD5205AFA71E}.Release|Any CPU.Build.0 = Release|Any CPU {1105C814-31DA-4214-BEA8-6DB5FC12C808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1105C814-31DA-4214-BEA8-6DB5FC12C808}.Debug|Any CPU.Build.0 = Debug|Any CPU {1105C814-31DA-4214-BEA8-6DB5FC12C808}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -468,6 +480,8 @@ Global {6AE92AAD-CF08-4E60-98EF-A7F762DAAB4D} = {2097345F-4DD3-477D-BC54-A922F9B2B402} {42B3FB71-BB42-46E3-9CEC-56620CB76BD9} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} {2117F4E3-6612-4E4D-A757-27271EEB7783} = {2097345F-4DD3-477D-BC54-A922F9B2B402} + {498A6808-C0DF-441F-A764-51A3BC4B8FC5} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} + {FB41E19E-2682-4D07-BA59-FD5205AFA71E} = {2097345F-4DD3-477D-BC54-A922F9B2B402} {1105C814-31DA-4214-BEA8-6DB5FC12C808} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} {F53FD7F5-DBC0-4FA5-83BA-B4C07A5BD248} = {2097345F-4DD3-477D-BC54-A922F9B2B402} {F632DFB6-38AD-4356-8997-8CCC0492619C} = {2097345F-4DD3-477D-BC54-A922F9B2B402} diff --git a/src/OpenTelemetry.Extensions.Docker/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Docker/AssemblyInfo.cs new file mode 100644 index 0000000000..a6a0089eba --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/AssemblyInfo.cs @@ -0,0 +1,23 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Runtime.CompilerServices; + +#if SIGNED +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Docker.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] +#else +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Docker.Tests")] +#endif diff --git a/src/OpenTelemetry.Extensions.Docker/CHANGELOG.md b/src/OpenTelemetry.Extensions.Docker/CHANGELOG.md new file mode 100644 index 0000000000..7bb1594dee --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog - OpenTelemetry.Extensions.Docker + +This is the first release for the `OpenTelemetry.Extensions.Docker` project. +The release targets +[OpenTelemetry.Extensions.Docker](https://www.nuget.org/packages/OpenTelemetry.Extensions.Docker/). +The project targets 1.2.0 of the [OpenTelemetry +SDK](https://www.nuget.org/packages/OpenTelemetry/). + +The Docker extensions include plugin to extract resource detectors +from docker environment (container id). For more details, +please refer to the +[README](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Extensions.Docker/README.md) diff --git a/src/OpenTelemetry.Extensions.Docker/DockerExtensionsEventSource.cs b/src/OpenTelemetry.Extensions.Docker/DockerExtensionsEventSource.cs new file mode 100644 index 0000000000..ab522cea8b --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/DockerExtensionsEventSource.cs @@ -0,0 +1,63 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.Threading; + +namespace OpenTelemetry.Extensions.Docker +{ + [EventSource(Name = "OpenTelemetry-Extensions-Docker")] + internal class DockerExtensionsEventSource : EventSource + { + public static DockerExtensionsEventSource Log = new DockerExtensionsEventSource(); + + [NonEvent] + public void ExtractResourceAttributesException(string format, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) + { + this.FailedToExtractResourceAttributes(format, ToInvariantString(ex)); + } + } + + [Event(1, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Error)] + public void FailedToExtractResourceAttributes(string format, string exception) + { + this.WriteEvent(1, format, exception); + } + + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + private static string ToInvariantString(Exception exception) + { + var originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/OpenTelemetry.Extensions.Docker/OpenTelemetry.Extensions.Docker.csproj b/src/OpenTelemetry.Extensions.Docker/OpenTelemetry.Extensions.Docker.csproj new file mode 100644 index 0000000000..d70c0d06c6 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/OpenTelemetry.Extensions.Docker.csproj @@ -0,0 +1,10 @@ + + + ;net461;netstandard2.0 + OpenTelemetry Extensions - Container Resource Detector from Docker environment. + Extensions.Docker- + + + + + diff --git a/src/OpenTelemetry.Extensions.Docker/README.md b/src/OpenTelemetry.Extensions.Docker/README.md new file mode 100644 index 0000000000..63ed574581 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/README.md @@ -0,0 +1,38 @@ +# Docker Resource Detectors + +## Getting Started + +You need to install the +`OpenTelemetry.Extensions.Docker` to be able to use the +Docker Resource Detectors. It detects container.id from +Docker environment. + +```shell +dotnet add package OpenTelemetry.Extensions.Docker +``` + +## Usage + +You can configure Docker resource detector to +the `TracerProvider` with the following example below. + +```csharp +using OpenTelemetry; +using OpenTelemetry.Extensions.Docker.Resources; + +var tracerProvider = Sdk.CreateTracerProviderBuilder() + // other configurations + .SetResourceBuilder(ResourceBuilder + .CreateEmpty() + .AddDetector(new DockerResourceDetector())) + .Build(); +``` + +The resource detectors will record the following metadata based on where +your application is running: + +- **DockerResourceDetector**: container id. + +## References + +- [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs b/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs new file mode 100644 index 0000000000..bb6ab6a13f --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/Resources/DockerResourceDetector.cs @@ -0,0 +1,131 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.IO; +using OpenTelemetry.Extensions.Docker.Utils; +using OpenTelemetry.Resources; + +namespace OpenTelemetry.Extensions.Docker.Resources +{ + /// + /// Resource detector for application running in Docker environment. + /// + public class DockerResourceDetector : IResourceDetector + { + private const string FILEPATH = "/proc/self/cgroup"; + + /// + /// Detects the resource attributes from Docker. + /// + /// Resource with key-value pairs of resource attributes. + public Resource Detect() + { + return this.BuildResource(FILEPATH); + } + + /// + /// Builds the resource attributes from Container Id in file path. + /// + /// File path where container id exists. + /// Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource. + internal Resource BuildResource(string path) + { + var containerId = this.ExtractContainerId(path); + + if (string.IsNullOrEmpty(containerId)) + { + return Resource.Empty; + } + else + { + return new Resource(new List>() { new KeyValuePair(DockerSemanticConventions.AttributeContainerID, containerId), }); + } + } + + /// + /// Extracts Container Id from path. + /// + /// cgroup path. + /// Container Id, Null if not found or exception being thrown. + private string ExtractContainerId(string path) + { + try + { + if (!File.Exists(path)) + { + return null; + } + + foreach (string line in File.ReadLines(path)) + { + string containerId = (!string.IsNullOrEmpty(line)) ? this.GetIdFromLine(line) : null; + if (!string.IsNullOrEmpty(containerId)) + { + return containerId; + } + } + } + catch (Exception ex) + { + DockerExtensionsEventSource.Log.ExtractResourceAttributesException($"{nameof(DockerResourceDetector)} : Failed to extract Container id from path", ex); + } + + return null; + } + + /// + /// Gets the Container Id from the line after removing the prefix and suffix. + /// + /// line read from cgroup file. + /// Container Id. + private string GetIdFromLine(string line) + { + // This cgroup output line should have the container id in it + int lastSlashIndex = line.LastIndexOf('/'); + if (lastSlashIndex < 0) + { + return null; + } + + string lastSection = line.Substring(lastSlashIndex + 1); + int startIndex = lastSection.LastIndexOf('-'); + int endIndex = lastSection.LastIndexOf('.'); + + string containerId = this.RemovePrefixAndSuffixIfneeded(lastSection, startIndex, endIndex); + + 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; + + if (endIndex == -1) + { + endIndex = input.Length; + } + + return input.Substring(startIndex, endIndex - startIndex); + } + } +} diff --git a/src/OpenTelemetry.Extensions.Docker/Resources/DockerSemanticConventions.cs b/src/OpenTelemetry.Extensions.Docker/Resources/DockerSemanticConventions.cs new file mode 100644 index 0000000000..b41d7404af --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/Resources/DockerSemanticConventions.cs @@ -0,0 +1,23 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace OpenTelemetry.Extensions.Docker.Resources +{ + internal static class DockerSemanticConventions + { + public const string AttributeContainerID = "container.id"; + } +} diff --git a/src/OpenTelemetry.Extensions.Docker/Utils/EncodingUtils.cs b/src/OpenTelemetry.Extensions.Docker/Utils/EncodingUtils.cs new file mode 100644 index 0000000000..5963f7ecb2 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Docker/Utils/EncodingUtils.cs @@ -0,0 +1,37 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Linq; + +namespace OpenTelemetry.Extensions.Docker.Utils +{ + internal class EncodingUtils + { + /// + /// Checks if the string is valid hex. + /// + /// string. + /// true if valid else false. + public static bool IsValidHexString(IEnumerable hexString) + { + return hexString.Select(currentCharacter => + (currentCharacter >= '0' && currentCharacter <= '9') || + (currentCharacter >= 'a' && currentCharacter <= 'f') || + (currentCharacter >= 'A' && currentCharacter <= 'F')).All(isHexCharacter => isHexCharacter); + } + } +} diff --git a/test/OpenTelemetry.Extensions.Docker.Tests/OpenTelemetry.Extensions.Docker.Tests.csproj b/test/OpenTelemetry.Extensions.Docker.Tests/OpenTelemetry.Extensions.Docker.Tests.csproj new file mode 100644 index 0000000000..5596e02af2 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Docker.Tests/OpenTelemetry.Extensions.Docker.Tests.csproj @@ -0,0 +1,23 @@ + + + + Unit test project for Docker Detector for OpenTelemetry + netcoreapp3.1 + $(TargetFrameworks);net461 + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + diff --git a/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs new file mode 100644 index 0000000000..bebb93d3e8 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/DockerResourceDetectorTests.cs @@ -0,0 +1,112 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.IO; +using System.Linq; +using OpenTelemetry.Extensions.Docker.Resources; +using OpenTelemetry.Resources; +using Xunit; + +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"; + + [Fact] + public void TestValidContainer() + { + var dockerResourceDetector = new DockerResourceDetector(); + + using (TempFile tempFile = new TempFile()) + { + tempFile.Write(CGROUPLINEWITHPREFIX); + Assert.Equal(CONTAINERIDWITHPREFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath))); + } + + using (TempFile tempFile = new TempFile()) + { + tempFile.Write(CGROUPLINEWITHSUFFIX); + Assert.Equal(CONTAINERIDWITHSUFFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath))); + } + + using (TempFile tempFile = new TempFile()) + { + tempFile.Write(CGROUPLINEWITHPREFIXandSUFFIX); + Assert.Equal(CONTAINERIDWITHPREFIXANDSUFFIXREMOVED, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath))); + } + + using (TempFile tempFile = new TempFile()) + { + tempFile.Write(CGROUPLINE); + Assert.Equal(CONTAINERID, this.GetContainerId(dockerResourceDetector.BuildResource(tempFile.FilePath))); + } + } + + [Fact] + public void TestInvalidContainer() + { + var dockerResourceDetector = new DockerResourceDetector(); + + // test invalid containerId (non-hex) + using (TempFile tempFile = new TempFile()) + { + tempFile.Write(INVALIDCGROUPLINE); + Assert.Equal(dockerResourceDetector.BuildResource(tempFile.FilePath), Resource.Empty); + } + + // test invalid file + Assert.Equal(dockerResourceDetector.BuildResource(Path.GetTempPath()), Resource.Empty); + } + + private string GetContainerId(Resource resource) + { + var resourceAttributes = resource.Attributes.ToDictionary(x => x.Key, x => x.Value); + return resourceAttributes[DockerSemanticConventions.AttributeContainerID]?.ToString(); + } + } +} diff --git a/test/OpenTelemetry.Extensions.Docker.Tests/Resources/TempFile.cs b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/TempFile.cs new file mode 100644 index 0000000000..592c128152 --- /dev/null +++ b/test/OpenTelemetry.Extensions.Docker.Tests/Resources/TempFile.cs @@ -0,0 +1,67 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.IO; +using System.Threading; + +namespace OpenTelemetry.Extensions.Docker.Tests +{ + internal class TempFile : IDisposable + { + private string filePath; + + public TempFile() + { + this.filePath = Path.GetTempFileName(); + } + + public string FilePath + { + get { return this.filePath; } + set { this.filePath = value; } + } + + public void Write(string data) + { + using (FileStream stream = new FileStream(this.filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)) + { + using (StreamWriter sw = new StreamWriter(stream)) + { + sw.Write(data); + } + } + } + + public void Dispose() + { + for (int tries = 0; ; tries++) + { + try + { + File.Delete(this.filePath); + return; + } + catch (IOException) when (tries < 3) + { + // the file is unavailable because it is: still being written to or being processed by another thread + // sleep for sometime before deleting + Thread.Sleep(1000); + } + } + } + } +}