From 3cc50b465f404fcaa11318b43fd55855b1be7b47 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 16 Jan 2023 03:50:16 +0530 Subject: [PATCH 01/18] Code changes for Resource Detection for container properties (e.g.`container.id`) Fixes #1372 --- .github/workflows/test.yml | 2 +- docs/conf.py | 8 +- docs/index.rst | 11 +- docs/resource/container/container.rst | 7 + .../LICENSE | 201 ++++++++++++++++++ .../MANITEST.rst | 9 + .../README.rst | 43 ++++ .../pyproject.toml | 50 +++++ .../resource/detector/container/__init__.py | 84 ++++++++ .../resource/detector/container/version.py | 15 ++ .../tests/__init__.py | 0 .../tests/test_container.py | 113 ++++++++++ tox.ini | 8 + 13 files changed, 548 insertions(+), 3 deletions(-) create mode 100644 docs/resource/container/container.rst create mode 100644 resource/opentelemetry-resource-detector-container/LICENSE create mode 100644 resource/opentelemetry-resource-detector-container/MANITEST.rst create mode 100644 resource/opentelemetry-resource-detector-container/README.rst create mode 100644 resource/opentelemetry-resource-detector-container/pyproject.toml create mode 100644 resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py create mode 100644 resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py create mode 100644 resource/opentelemetry-resource-detector-container/tests/__init__.py create mode 100644 resource/opentelemetry-resource-detector-container/tests/test_container.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa4244bc66..6e4843ff45 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false # ensures the entire test matrix is run, even if one permutation fails matrix: python-version: [ py37, py38, py39, py310, py311, pypy3 ] - package: ["instrumentation", "distro", "exporter", "sdkextension", "propagator"] + package: ["instrumentation", "distro", "exporter", "sdkextension", "propagator", "resource"] os: [ ubuntu-20.04 ] steps: - name: Checkout Contrib Repo @ SHA - ${{ github.sha }} diff --git a/docs/conf.py b/docs/conf.py index 93d5b7cdca..40a37f7dd3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,13 @@ if isdir(join(sdk_ext, f)) ] -sys.path[:0] = exp_dirs + instr_dirs + sdk_ext_dirs + prop_dirs +resource = "../resource" +resource_dirs = [ + os.path.abspath("/".join(["../resource", f, "src"])) + for f in listdir(resource) + if isdir(join(resource, f)) +] +sys.path[:0] = exp_dirs + instr_dirs + sdk_ext_dirs + prop_dirs + resource_dirs # -- Project information ----------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 44fbfc1188..8a7fb7254c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,7 +33,7 @@ Extensions Visit `OpenTelemetry Registry `_ to find a lot of related projects like exporters, instrumentation libraries, tracer -implementations, etc. +implementations, resource etc. Installing Cutting Edge Packages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -51,6 +51,7 @@ install pip install -e ./instrumentation/opentelemetry-instrumentation-flask pip install -e ./instrumentation/opentelemetry-instrumentation-botocore pip install -e ./sdk-extension/opentelemetry-sdk-extension-aws + pip install -e ./resource/opentelemetry-resource-detector-container .. toctree:: @@ -85,6 +86,14 @@ install sdk-extension/** +.. toctree:: + :maxdepth: 2 + :caption: OpenTelemetry Resource Detectors + :name: Resource Detectors + :glob: + + resource/** + Indices and tables ------------------ diff --git a/docs/resource/container/container.rst b/docs/resource/container/container.rst new file mode 100644 index 0000000000..9c9b7d4247 --- /dev/null +++ b/docs/resource/container/container.rst @@ -0,0 +1,7 @@ +OpenTelemetry Python - Resource Detector for Containers +======================================== + +.. automodule:: opentelemetry.resource.detector.container + :members: + :undoc-members: + :show-inheritance: diff --git a/resource/opentelemetry-resource-detector-container/LICENSE b/resource/opentelemetry-resource-detector-container/LICENSE new file mode 100644 index 0000000000..1ef7dad2c5 --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + 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. diff --git a/resource/opentelemetry-resource-detector-container/MANITEST.rst b/resource/opentelemetry-resource-detector-container/MANITEST.rst new file mode 100644 index 0000000000..2906eeef0f --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/MANITEST.rst @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE \ No newline at end of file diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst new file mode 100644 index 0000000000..92b9f11930 --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -0,0 +1,43 @@ +OpenTelemetry Resource detectors for containers +========================================================== + +|pypi| + +.. |pypi| image:: TODO + :target: TODO + + +This library provides container property (container.id) detection features which can help +in identifying the problems in app - infra correlation for apps running on containers i.e. docker + +Installation +------------ + +:: + + pip install opentelemetry-resource-detector-containerß + +--------------------------- + +Below is the give example for `opentelemetry-resource-detector-kubernetes` + +.. code-block:: python + import opentelemetry.trace as trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.resource.detector.container import ( + ContainerResourceDetector, + ) + from opentelemetry.sdk.resources import get_aggregated_resources + trace.set_tracer_provider( + TracerProvider( + resource=get_aggregated_resources( + [ + ContainerResourceDetector(), + ] + ), + ) + ) +References +---------- + +* `OpenTelemetry Project `_ \ No newline at end of file diff --git a/resource/opentelemetry-resource-detector-container/pyproject.toml b/resource/opentelemetry-resource-detector-container/pyproject.toml new file mode 100644 index 0000000000..87fa4c83a8 --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "opentelemetry-resource-detector-container" +dynamic = ["version"] +description = "Container Resource Detector for OpenTelemetry" +readme = "README.rst" +license = "Apache-2.0" +requires-python = ">=3.7" +authors = [ + { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +dependencies = [ + "opentelemetry-sdk ~= 1.12", +] + +[project.optional-dependencies] +test = [] + +[project.entry-points.opentelemetry_resource_detector] +container = "opentelemetry.resource.detector.container:ContainerResourceDetector" + +[project.urls] +Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/resource/opentelemetry-resource-detector-container" + +[tool.hatch.version] +path = "src/opentelemetry/resource/detector/container/version.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/src", + "/tests", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/opentelemetry"] diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py new file mode 100644 index 0000000000..49010820e7 --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -0,0 +1,84 @@ +# 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. + +import logging + +from opentelemetry.sdk.resources import Resource, ResourceDetector +from opentelemetry.semconv.resource import ResourceAttributes + +logger = logging.getLogger(__name__) +DEFAULT_CGROUP_V1_PATH = "/proc/self/cgroup" +DEFAULT_CGROUP_V2_PATH = "/proc/self/mountinfo" +CONTAINER_ID_LENGTH = 64 + + +class ContainerResourceDetector(ResourceDetector): + """Detects container.id only available when app is running inside the + docker container and return it in a Resource + """ + + def detect(self) -> "Resource": + try: + container_id = self._get_container_id_v1() or self._get_container_id_v2() + resource = Resource.get_empty() + if container_id: + resource = resource.merge(Resource( + { + ResourceAttributes.CONTAINER_ID: container_id + } + )) + return resource + + except Exception as exception: + logger.warning("%s Resource Detection failed silently: %s", self.__class__.__name__, exception) + if self.raise_on_error: + raise exception + return Resource.get_empty() + + def _get_container_id_v1(self): + container_id = None + try: + with open( + DEFAULT_CGROUP_V1_PATH, encoding="utf8" + ) as container_info_file: + for raw_line in container_info_file.readlines(): + line = raw_line.strip() + if len(line) > CONTAINER_ID_LENGTH: + container_id = line[-CONTAINER_ID_LENGTH:] + break + except FileNotFoundError as exception: + logger.warning( + f"Failed to get container id. Exception: {exception}" + ) + return container_id + + def _get_container_id_v2(self): + container_id = None + try: + with open( + DEFAULT_CGROUP_V2_PATH, encoding="utf8" + ) as container_info_file: + for raw_line in container_info_file.readlines(): + line = raw_line.strip() + if "hostname" in line: + container_id_list = [id for id in line.split("/") if len(id) == CONTAINER_ID_LENGTH] + if len(container_id_list) > 0: + container_id = container_id_list[0] + break + + except FileNotFoundError as exception: + logger.warning( + f"Failed to get container id. Exception: {exception}" + ) + return container_id diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py new file mode 100644 index 0000000000..7b82da2b85 --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py @@ -0,0 +1,15 @@ +# 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. + +__version__ = "1.0.0" diff --git a/resource/opentelemetry-resource-detector-container/tests/__init__.py b/resource/opentelemetry-resource-detector-container/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py new file mode 100644 index 0000000000..507244d44f --- /dev/null +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -0,0 +1,113 @@ +# 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. + +from unittest.mock import mock_open, patch + +from opentelemetry import trace as trace_api +from opentelemetry.resource.detector.container import ContainerResourceDetector +from opentelemetry.sdk.resources import get_aggregated_resources +from opentelemetry.semconv.resource import ResourceAttributes +from opentelemetry.test.wsgitestutil import WsgiTestBase + +MockContainerResourceAttributes = { + ResourceAttributes.CONTAINER_ID: "7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605", +} + + +def simple_wsgi(environ, start_response): + start_response("200 OK", [("Content-Type", "text/plain")]) + + def response(): + yield b"*" + return response() + + +class ContainerResourceDetectorTest(WsgiTestBase): + @patch( + "builtins.open", + new_callable=mock_open, + read_data=f"""14:name=systemd:/docker/{MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID]} + 13:rdma:/ + 12:pids:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 11:hugetlb:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 10:net_prio:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 9:perf_event:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 8:net_cls:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 7:freezer:/docker/ + 6:devices:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 5:memory:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 4:blkio:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 3:cpuacct:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 2:cpu:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + 1:cpuset:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked + """, + ) + def test_container_id_detect_from_cgroup_file(self, mock_cgroup_file): + actual = ContainerResourceDetector().detect() + self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) + + @patch( + "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + ) + def test_container_id_as_span_attribute(self, mock_cgroup_file): + tracer_provider, exporter = self.create_tracer_provider( + resource=get_aggregated_resources( + [ + ContainerResourceDetector() + ] + ) + ) + tracer = tracer_provider.get_tracer(__name__) + + with tracer.start_as_current_span( + "test", kind=trace_api.SpanKind.SERVER + ) as _: + response = simple_wsgi(self.environ, self.start_response) + while True: + try: + value = next(response) + self.assertEqual(value, b"*") + except StopIteration: + break + + span_list = exporter.get_finished_spans() + self.assertEqual( + span_list[0].resource.attributes["container.id"], + MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID] + ) + + @patch( + "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + ) + def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): + actual = ContainerResourceDetector().detect() + self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) + + @patch( + "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + return_value=None, + ) + @patch( + "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v2", + return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + ) + def test_container_id_detect_from_mount_info( + self, + mock_get_container_id_v1, + mock_get_container_id_v2 + ): + actual = ContainerResourceDetector().detect() + self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) diff --git a/tox.ini b/tox.ini index 4cd6fa02f5..b6fe80d8fc 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,10 @@ envlist = ; Environments are organized by individual package, allowing ; for specifying supported Python versions per package. + ; opentelemetry-resource-detector-container + py3{7,8,9,10,11}-test-resource-detector-container + pypy3-test-resource-detector-container + ; opentelemetry-sdk-extension-aws py3{7,8,9,10,11}-test-sdkextension-aws pypy3-test-sdkextension-aws @@ -311,6 +315,7 @@ changedir = test-instrumentation-httpx{18,21}: instrumentation/opentelemetry-instrumentation-httpx/tests test-util-http: util/opentelemetry-util-http/tests test-sdkextension-aws: sdk-extension/opentelemetry-sdk-extension-aws/tests + test-resource-detector-container: resource/opentelemetry-resource-detector-container/tests test-propagator-aws: propagator/opentelemetry-propagator-aws-xray/tests test-propagator-ot-trace: propagator/opentelemetry-propagator-ot-trace/tests test-exporter-richconsole: exporter/opentelemetry-exporter-richconsole/tests @@ -418,6 +423,8 @@ commands_pre = sdkextension-aws: pip install {toxinidir}/sdk-extension/opentelemetry-sdk-extension-aws[test] + resource-detector-container: pip install {toxinidir}/resource/opentelemetry-resource-detector-container[test] + http: pip install {toxinidir}/util/opentelemetry-util-http[test] ; In order to get a health coverage report, propagator-ot-trace: pip install {toxinidir}/propagator/opentelemetry-propagator-ot-trace[test] @@ -523,6 +530,7 @@ commands_pre = python -m pip install -e {toxinidir}/exporter/opentelemetry-exporter-richconsole[test] python -m pip install -e {toxinidir}/exporter/opentelemetry-exporter-prometheus-remote-write[test] python -m pip install -e {toxinidir}/sdk-extension/opentelemetry-sdk-extension-aws[test] + python -m pip install -e {toxinidir}/resource/opentelemetry-resource-detector-container[test] python -m pip install -e {toxinidir}/propagator/opentelemetry-propagator-aws-xray[test] python -m pip install -e {toxinidir}/propagator/opentelemetry-propagator-ot-trace[test] python -m pip install -e {toxinidir}/opentelemetry-distro[test] From b4cc1705d79330185eee735191e04cd1da6dde4a Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 16 Jan 2023 04:08:14 +0530 Subject: [PATCH 02/18] adding entry to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb80fc7c4b..6be1bfdc28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `opentelemetry-resource-detector-container` Add support resource detection of container properties. + ([#1584](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1584)) - `opentelemetry-instrumentation-pymysql` Add tests for commit() and rollback(). ([#1424](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1424)) - `opentelemetry-instrumentation-fastapi` Add support for regular expression matching and sanitization of HTTP headers. From 2a68ebb95f5f25b78b17316f8bedbb1bbefbe6b0 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 16 Jan 2023 19:10:43 +0530 Subject: [PATCH 03/18] resolving docs build --- docs/resource/container/container.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resource/container/container.rst b/docs/resource/container/container.rst index 9c9b7d4247..e69a2f6a0f 100644 --- a/docs/resource/container/container.rst +++ b/docs/resource/container/container.rst @@ -1,5 +1,5 @@ OpenTelemetry Python - Resource Detector for Containers -======================================== +======================================================= .. automodule:: opentelemetry.resource.detector.container :members: From 112a655e0b29300f41faf7e5de43b8ddfecb3228 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 16 Jan 2023 19:39:47 +0530 Subject: [PATCH 04/18] Resolving Lint errors --- .../resource/detector/container/__init__.py | 24 +++++++++----- .../tests/test_container.py | 33 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 49010820e7..90d1891774 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -30,18 +30,22 @@ class ContainerResourceDetector(ResourceDetector): def detect(self) -> "Resource": try: - container_id = self._get_container_id_v1() or self._get_container_id_v2() + container_id = ( + self._get_container_id_v1() or self._get_container_id_v2() + ) resource = Resource.get_empty() if container_id: - resource = resource.merge(Resource( - { - ResourceAttributes.CONTAINER_ID: container_id - } - )) + resource = resource.merge( + Resource({ResourceAttributes.CONTAINER_ID: container_id}) + ) return resource except Exception as exception: - logger.warning("%s Resource Detection failed silently: %s", self.__class__.__name__, exception) + logger.warning( + "%s Resource Detection failed silently: %s", + self.__class__.__name__, + exception + ) if self.raise_on_error: raise exception return Resource.get_empty() @@ -72,7 +76,11 @@ def _get_container_id_v2(self): for raw_line in container_info_file.readlines(): line = raw_line.strip() if "hostname" in line: - container_id_list = [id for id in line.split("/") if len(id) == CONTAINER_ID_LENGTH] + container_id_list = [ + id + for id in line.split("/") + if len(id) == CONTAINER_ID_LENGTH + ] if len(container_id_list) > 0: container_id = container_id_list[0] break diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py index 507244d44f..bc5721e349 100644 --- a/resource/opentelemetry-resource-detector-container/tests/test_container.py +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -30,6 +30,7 @@ def simple_wsgi(environ, start_response): def response(): yield b"*" + return response() @@ -55,19 +56,19 @@ class ContainerResourceDetectorTest(WsgiTestBase): ) def test_container_id_detect_from_cgroup_file(self, mock_cgroup_file): actual = ContainerResourceDetector().detect() - self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) + self.assertDictEqual( + actual.attributes.copy(), MockContainerResourceAttributes + ) @patch( "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", - return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + return_value=MockContainerResourceAttributes[ + ResourceAttributes.CONTAINER_ID + ], ) def test_container_id_as_span_attribute(self, mock_cgroup_file): tracer_provider, exporter = self.create_tracer_provider( - resource=get_aggregated_resources( - [ - ContainerResourceDetector() - ] - ) + resource=get_aggregated_resources([ContainerResourceDetector()]) ) tracer = tracer_provider.get_tracer(__name__) @@ -85,16 +86,20 @@ def test_container_id_as_span_attribute(self, mock_cgroup_file): span_list = exporter.get_finished_spans() self.assertEqual( span_list[0].resource.attributes["container.id"], - MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID] + MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], ) @patch( "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", - return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + return_value=MockContainerResourceAttributes[ + ResourceAttributes.CONTAINER_ID + ], ) def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): actual = ContainerResourceDetector().detect() - self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) + self.assertDictEqual( + actual.attributes.copy(), MockContainerResourceAttributes + ) @patch( "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", @@ -102,7 +107,9 @@ def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): ) @patch( "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v2", - return_value=MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID], + return_value=MockContainerResourceAttributes[ + ResourceAttributes.CONTAINER_ID + ], ) def test_container_id_detect_from_mount_info( self, @@ -110,4 +117,6 @@ def test_container_id_detect_from_mount_info( mock_get_container_id_v2 ): actual = ContainerResourceDetector().detect() - self.assertDictEqual(actual.attributes.copy(), MockContainerResourceAttributes) + self.assertDictEqual( + actual.attributes.copy(), MockContainerResourceAttributes + ) From d14f7cb11a1645c77d9a5832bbaab916391b6cc4 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 16 Jan 2023 20:51:30 +0530 Subject: [PATCH 05/18] Resolving linting errors --- .../src/opentelemetry/resource/detector/container/__init__.py | 2 +- .../tests/test_container.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 90d1891774..f079bad01e 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -44,7 +44,7 @@ def detect(self) -> "Resource": logger.warning( "%s Resource Detection failed silently: %s", self.__class__.__name__, - exception + exception, ) if self.raise_on_error: raise exception diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py index bc5721e349..92232f671b 100644 --- a/resource/opentelemetry-resource-detector-container/tests/test_container.py +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -112,9 +112,7 @@ def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): ], ) def test_container_id_detect_from_mount_info( - self, - mock_get_container_id_v1, - mock_get_container_id_v2 + self, mock_get_container_id_v1, mock_get_container_id_v2 ): actual = ContainerResourceDetector().detect() self.assertDictEqual( From e8e2971e07498f7856947c595efd3ba71fc06288 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Wed, 18 Jan 2023 16:28:38 +0530 Subject: [PATCH 06/18] Resolving few lint errors --- .../resource/detector/container/__init__.py | 86 ++++++++++--------- .../tests/test_container.py | 8 +- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index f079bad01e..f7350d21ab 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -23,6 +23,49 @@ CONTAINER_ID_LENGTH = 64 +def _get_container_id_v1(): + container_id = None + try: + with open( + DEFAULT_CGROUP_V1_PATH, encoding="utf8" + ) as container_info_file: + for raw_line in container_info_file.readlines(): + line = raw_line.strip() + if len(line) > CONTAINER_ID_LENGTH: + container_id = line[-CONTAINER_ID_LENGTH:] + break + except FileNotFoundError as exception: + logger.warning( + f"Failed to get container id. Exception: {exception}" + ) + return container_id + + +def _get_container_id_v2(): + container_id = None + try: + with open( + DEFAULT_CGROUP_V2_PATH, encoding="utf8" + ) as container_info_file: + for raw_line in container_info_file.readlines(): + line = raw_line.strip() + if "hostname" in line: + container_id_list = [ + id + for id in line.split("/") + if len(id) == CONTAINER_ID_LENGTH + ] + if len(container_id_list) > 0: + container_id = container_id_list[0] + break + + except FileNotFoundError as exception: + logger.warning( + f"Failed to get container id. Exception: {exception}" + ) + return container_id + + class ContainerResourceDetector(ResourceDetector): """Detects container.id only available when app is running inside the docker container and return it in a Resource @@ -31,7 +74,7 @@ class ContainerResourceDetector(ResourceDetector): def detect(self) -> "Resource": try: container_id = ( - self._get_container_id_v1() or self._get_container_id_v2() + _get_container_id_v1() or _get_container_id_v2() ) resource = Resource.get_empty() if container_id: @@ -49,44 +92,3 @@ def detect(self) -> "Resource": if self.raise_on_error: raise exception return Resource.get_empty() - - def _get_container_id_v1(self): - container_id = None - try: - with open( - DEFAULT_CGROUP_V1_PATH, encoding="utf8" - ) as container_info_file: - for raw_line in container_info_file.readlines(): - line = raw_line.strip() - if len(line) > CONTAINER_ID_LENGTH: - container_id = line[-CONTAINER_ID_LENGTH:] - break - except FileNotFoundError as exception: - logger.warning( - f"Failed to get container id. Exception: {exception}" - ) - return container_id - - def _get_container_id_v2(self): - container_id = None - try: - with open( - DEFAULT_CGROUP_V2_PATH, encoding="utf8" - ) as container_info_file: - for raw_line in container_info_file.readlines(): - line = raw_line.strip() - if "hostname" in line: - container_id_list = [ - id - for id in line.split("/") - if len(id) == CONTAINER_ID_LENGTH - ] - if len(container_id_list) > 0: - container_id = container_id_list[0] - break - - except FileNotFoundError as exception: - logger.warning( - f"Failed to get container id. Exception: {exception}" - ) - return container_id diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py index 92232f671b..be34411ecb 100644 --- a/resource/opentelemetry-resource-detector-container/tests/test_container.py +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -61,7 +61,7 @@ def test_container_id_detect_from_cgroup_file(self, mock_cgroup_file): ) @patch( - "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + "opentelemetry.resource.detector.container._get_container_id_v1", return_value=MockContainerResourceAttributes[ ResourceAttributes.CONTAINER_ID ], @@ -90,7 +90,7 @@ def test_container_id_as_span_attribute(self, mock_cgroup_file): ) @patch( - "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + "opentelemetry.resource.detector.container._get_container_id_v1", return_value=MockContainerResourceAttributes[ ResourceAttributes.CONTAINER_ID ], @@ -102,11 +102,11 @@ def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): ) @patch( - "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v1", + "opentelemetry.resource.detector.container._get_container_id_v1", return_value=None, ) @patch( - "opentelemetry.resource.detector.container.ContainerResourceDetector._get_container_id_v2", + "opentelemetry.resource.detector.container._get_container_id_v2", return_value=MockContainerResourceAttributes[ ResourceAttributes.CONTAINER_ID ], From 35ffeccd5980380fd5bdc2a54e6aa6fe87c1de50 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Wed, 18 Jan 2023 16:41:10 +0530 Subject: [PATCH 07/18] resolving lint error --- .../resource/detector/container/__init__.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index f7350d21ab..332eaf05bf 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -24,21 +24,21 @@ def _get_container_id_v1(): - container_id = None - try: - with open( - DEFAULT_CGROUP_V1_PATH, encoding="utf8" - ) as container_info_file: - for raw_line in container_info_file.readlines(): - line = raw_line.strip() - if len(line) > CONTAINER_ID_LENGTH: - container_id = line[-CONTAINER_ID_LENGTH:] - break - except FileNotFoundError as exception: - logger.warning( - f"Failed to get container id. Exception: {exception}" - ) - return container_id + container_id = None + try: + with open( + DEFAULT_CGROUP_V1_PATH, encoding="utf8" + ) as container_info_file: + for raw_line in container_info_file.readlines(): + line = raw_line.strip() + if len(line) > CONTAINER_ID_LENGTH: + container_id = line[-CONTAINER_ID_LENGTH:] + break + except FileNotFoundError as exception: + logger.warning( + f"Failed to get container id. Exception: {exception}" + ) + return container_id def _get_container_id_v2(): @@ -73,9 +73,7 @@ class ContainerResourceDetector(ResourceDetector): def detect(self) -> "Resource": try: - container_id = ( - _get_container_id_v1() or _get_container_id_v2() - ) + container_id = _get_container_id_v1() or _get_container_id_v2() resource = Resource.get_empty() if container_id: resource = resource.merge( From 03d3ebe76b56e21b050948180f77c203ee41448e Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Wed, 18 Jan 2023 18:07:44 +0530 Subject: [PATCH 08/18] resolving lint errors --- .../opentelemetry/resource/detector/container/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 332eaf05bf..54fd991537 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -35,9 +35,7 @@ def _get_container_id_v1(): container_id = line[-CONTAINER_ID_LENGTH:] break except FileNotFoundError as exception: - logger.warning( - f"Failed to get container id. Exception: {exception}" - ) + logger.warning(f"Failed to get container id. Exception: {exception}") return container_id @@ -60,9 +58,7 @@ def _get_container_id_v2(): break except FileNotFoundError as exception: - logger.warning( - f"Failed to get container id. Exception: {exception}" - ) + logger.warning(f"Failed to get container id. Exception: {exception}") return container_id From f26961900892f812fd7b0fda3671ac0a487033eb Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Thu, 19 Jan 2023 01:16:48 +0530 Subject: [PATCH 09/18] Resolving linting errors --- .../opentelemetry/resource/detector/container/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 54fd991537..ddb400fd3c 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -35,7 +35,7 @@ def _get_container_id_v1(): container_id = line[-CONTAINER_ID_LENGTH:] break except FileNotFoundError as exception: - logger.warning(f"Failed to get container id. Exception: {exception}") + logger.warning("Failed to get container id. Exception: %s", exception) return container_id @@ -58,7 +58,7 @@ def _get_container_id_v2(): break except FileNotFoundError as exception: - logger.warning(f"Failed to get container id. Exception: {exception}") + logger.warning("Failed to get container id. Exception: %s", exception) return container_id @@ -77,11 +77,12 @@ def detect(self) -> "Resource": ) return resource + # pylint: disable=broad-except except Exception as exception: logger.warning( "%s Resource Detection failed silently: %s", self.__class__.__name__, - exception, + exception ) if self.raise_on_error: raise exception From 14c42bc85d5d8feb760566e91a5c196229588408 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Thu, 19 Jan 2023 01:31:15 +0530 Subject: [PATCH 10/18] resolving linting error --- .../src/opentelemetry/resource/detector/container/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index ddb400fd3c..a667178f4b 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -82,7 +82,7 @@ def detect(self) -> "Resource": logger.warning( "%s Resource Detection failed silently: %s", self.__class__.__name__, - exception + exception, ) if self.raise_on_error: raise exception From d9d40395d88ef7c637179c2237a39ab18552ae50 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Sun, 22 Jan 2023 18:06:37 +0530 Subject: [PATCH 11/18] resolving readme file syntax errors --- .../opentelemetry-resource-detector-container/README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst index 92b9f11930..fb1b4a069f 100644 --- a/resource/opentelemetry-resource-detector-container/README.rst +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -22,12 +22,15 @@ Installation Below is the give example for `opentelemetry-resource-detector-kubernetes` .. code-block:: python + import opentelemetry.trace as trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.resource.detector.container import ( ContainerResourceDetector, ) from opentelemetry.sdk.resources import get_aggregated_resources + + trace.set_tracer_provider( TracerProvider( resource=get_aggregated_resources( @@ -37,7 +40,8 @@ Below is the give example for `opentelemetry-resource-detector-kubernetes` ), ) ) + References ---------- -* `OpenTelemetry Project `_ \ No newline at end of file +* `OpenTelemetry Project `_ From a57d1da51b291d7eccf81f70e0ed073459fdd373 Mon Sep 17 00:00:00 2001 From: Sanket Mehta Date: Thu, 16 Feb 2023 23:36:36 +0530 Subject: [PATCH 12/18] Update resource/opentelemetry-resource-detector-container/README.rst Co-authored-by: Diego Hurtado --- resource/opentelemetry-resource-detector-container/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst index fb1b4a069f..a1b5464f08 100644 --- a/resource/opentelemetry-resource-detector-container/README.rst +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -15,7 +15,7 @@ Installation :: - pip install opentelemetry-resource-detector-containerß + pip install opentelemetry-resource-detector-container --------------------------- From e0123ac74f93facec17610a8ae210ec2183ee9e4 Mon Sep 17 00:00:00 2001 From: Sanket Mehta Date: Fri, 3 Mar 2023 00:52:49 +0530 Subject: [PATCH 13/18] Update resource/opentelemetry-resource-detector-container/README.rst Co-authored-by: Srikanth Chekuri --- resource/opentelemetry-resource-detector-container/README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst index a1b5464f08..4c3eb5f2d0 100644 --- a/resource/opentelemetry-resource-detector-container/README.rst +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -7,8 +7,7 @@ OpenTelemetry Resource detectors for containers :target: TODO -This library provides container property (container.id) detection features which can help -in identifying the problems in app - infra correlation for apps running on containers i.e. docker +This library provides custom resource detector for container platforms Installation ------------ From bb62c3aa0596eb2ac2e9a8e0f7697344bd57f5d5 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Fri, 3 Mar 2023 01:34:06 +0530 Subject: [PATCH 14/18] resolving review comments --- .../README.rst | 2 +- .../resource/detector/container/__init__.py | 26 +++++++++++-------- .../tests/test_container.py | 6 ++--- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst index 4c3eb5f2d0..e6f2165f14 100644 --- a/resource/opentelemetry-resource-detector-container/README.rst +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -18,7 +18,7 @@ Installation --------------------------- -Below is the give example for `opentelemetry-resource-detector-kubernetes` +Usage example for `opentelemetry-resource-detector-container` .. code-block:: python diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index a667178f4b..41290a057b 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -18,21 +18,21 @@ from opentelemetry.semconv.resource import ResourceAttributes logger = logging.getLogger(__name__) -DEFAULT_CGROUP_V1_PATH = "/proc/self/cgroup" -DEFAULT_CGROUP_V2_PATH = "/proc/self/mountinfo" -CONTAINER_ID_LENGTH = 64 +_DEFAULT_CGROUP_V1_PATH = "/proc/self/cgroup" +_DEFAULT_CGROUP_V2_PATH = "/proc/self/mountinfo" +_CONTAINER_ID_LENGTH = 64 def _get_container_id_v1(): container_id = None try: with open( - DEFAULT_CGROUP_V1_PATH, encoding="utf8" + _DEFAULT_CGROUP_V1_PATH, encoding="utf8" ) as container_info_file: for raw_line in container_info_file.readlines(): line = raw_line.strip() - if len(line) > CONTAINER_ID_LENGTH: - container_id = line[-CONTAINER_ID_LENGTH:] + if len(line) > _CONTAINER_ID_LENGTH: + container_id = line[-_CONTAINER_ID_LENGTH:] break except FileNotFoundError as exception: logger.warning("Failed to get container id. Exception: %s", exception) @@ -43,15 +43,15 @@ def _get_container_id_v2(): container_id = None try: with open( - DEFAULT_CGROUP_V2_PATH, encoding="utf8" + _DEFAULT_CGROUP_V2_PATH, encoding="utf8" ) as container_info_file: for raw_line in container_info_file.readlines(): line = raw_line.strip() if "hostname" in line: container_id_list = [ - id - for id in line.split("/") - if len(id) == CONTAINER_ID_LENGTH + id_ + for id_ in line.split("/") + if len(id_) == _CONTAINER_ID_LENGTH ] if len(container_id_list) > 0: container_id = container_id_list[0] @@ -62,6 +62,10 @@ def _get_container_id_v2(): return container_id +def _get_container_id(): + return _get_container_id_v1() or _get_container_id_v2() + + class ContainerResourceDetector(ResourceDetector): """Detects container.id only available when app is running inside the docker container and return it in a Resource @@ -69,7 +73,7 @@ class ContainerResourceDetector(ResourceDetector): def detect(self) -> "Resource": try: - container_id = _get_container_id_v1() or _get_container_id_v2() + container_id = _get_container_id() resource = Resource.get_empty() if container_id: resource = resource.merge( diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py index be34411ecb..f94e1355cc 100644 --- a/resource/opentelemetry-resource-detector-container/tests/test_container.py +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -61,7 +61,7 @@ def test_container_id_detect_from_cgroup_file(self, mock_cgroup_file): ) @patch( - "opentelemetry.resource.detector.container._get_container_id_v1", + "opentelemetry.resource.detector.container._get_container_id", return_value=MockContainerResourceAttributes[ ResourceAttributes.CONTAINER_ID ], @@ -90,12 +90,12 @@ def test_container_id_as_span_attribute(self, mock_cgroup_file): ) @patch( - "opentelemetry.resource.detector.container._get_container_id_v1", + "opentelemetry.resource.detector.container._get_container_id", return_value=MockContainerResourceAttributes[ ResourceAttributes.CONTAINER_ID ], ) - def test_container_id_detect_from_cgroup(self, mock_get_container_id_v1): + def test_container_id_detect_from_cgroup(self, mock_get_container_id): actual = ContainerResourceDetector().detect() self.assertDictEqual( actual.attributes.copy(), MockContainerResourceAttributes From 96b9b71775b4dd827a77865ddc650099e9410672 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Mon, 8 May 2023 02:03:48 +0530 Subject: [PATCH 15/18] adding testcase for mountinfo file. Removed unnecessary wsgi_app from testcases. --- .../resource/detector/container/__init__.py | 2 +- .../tests/test_container.py | 62 +++++++++++++------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 41290a057b..4eae00b760 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -47,7 +47,7 @@ def _get_container_id_v2(): ) as container_info_file: for raw_line in container_info_file.readlines(): line = raw_line.strip() - if "hostname" in line: + if "containers" or "hostname" in line: container_id_list = [ id_ for id_ in line.split("/") diff --git a/resource/opentelemetry-resource-detector-container/tests/test_container.py b/resource/opentelemetry-resource-detector-container/tests/test_container.py index f94e1355cc..ac55afa291 100644 --- a/resource/opentelemetry-resource-detector-container/tests/test_container.py +++ b/resource/opentelemetry-resource-detector-container/tests/test_container.py @@ -18,23 +18,14 @@ from opentelemetry.resource.detector.container import ContainerResourceDetector from opentelemetry.sdk.resources import get_aggregated_resources from opentelemetry.semconv.resource import ResourceAttributes -from opentelemetry.test.wsgitestutil import WsgiTestBase +from opentelemetry.test.test_base import TestBase MockContainerResourceAttributes = { ResourceAttributes.CONTAINER_ID: "7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605", } -def simple_wsgi(environ, start_response): - start_response("200 OK", [("Content-Type", "text/plain")]) - - def response(): - yield b"*" - - return response() - - -class ContainerResourceDetectorTest(WsgiTestBase): +class ContainerResourceDetectorTest(TestBase): @patch( "builtins.open", new_callable=mock_open, @@ -60,6 +51,47 @@ def test_container_id_detect_from_cgroup_file(self, mock_cgroup_file): actual.attributes.copy(), MockContainerResourceAttributes ) + @patch( + "opentelemetry.resource.detector.container._get_container_id_v1", + return_value=None, + ) + @patch( + "builtins.open", + new_callable=mock_open, + read_data=f""" + 608 607 0:183 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw + 609 607 0:184 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 + 610 609 0:185 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 + 611 607 0:186 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro + 612 611 0:29 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw + 613 609 0:182 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw + 614 609 0:187 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k + 615 607 254:1 /docker/containers/{MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID]}/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw + 616 607 254:1 /docker/containers/{MockContainerResourceAttributes[ResourceAttributes.CONTAINER_ID]}/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw + 617 607 254:1 /docker/containers/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw + 618 607 0:131 /Users/sankmeht/development/otel/opentelemetry-python /development/otel/opentelemetry-python rw,nosuid,nodev,relatime - fuse.grpcfuse grpcfuse rw,user_id=0,group_id=0,allow_other,max_read=1048576 + 619 607 0:131 /Users/sankmeht/development/otel/opentelemetry-python-contrib /development/otel/opentelemetry-python-contrib rw,nosuid,nodev,relatime - fuse.grpcfuse grpcfuse rw,user_id=0,group_id=0,allow_other,max_read=1048576 + 519 609 0:185 /0 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 + 520 608 0:183 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw + 521 608 0:183 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw + 522 608 0:183 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw + 523 608 0:183 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw + 524 608 0:183 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw + 525 608 0:212 / /proc/acpi ro,relatime - tmpfs tmpfs ro + 526 608 0:184 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 + 527 608 0:184 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 + 528 608 0:184 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 + 529 611 0:213 / /sys/firmware ro,relatime - tmpfs tmpfs ro + """, + ) + def test_container_id_detect_from_mountinfo_file( + self, mock_get_container_id_v1, mock_cgroup_file + ): + actual = ContainerResourceDetector().detect() + self.assertDictEqual( + actual.attributes.copy(), MockContainerResourceAttributes + ) + @patch( "opentelemetry.resource.detector.container._get_container_id", return_value=MockContainerResourceAttributes[ @@ -75,13 +107,7 @@ def test_container_id_as_span_attribute(self, mock_cgroup_file): with tracer.start_as_current_span( "test", kind=trace_api.SpanKind.SERVER ) as _: - response = simple_wsgi(self.environ, self.start_response) - while True: - try: - value = next(response) - self.assertEqual(value, b"*") - except StopIteration: - break + pass span_list = exporter.get_finished_spans() self.assertEqual( From 93a432497fad6b16b6232679f04ea3481037015e Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Thu, 11 May 2023 19:12:15 +0530 Subject: [PATCH 16/18] adding proper check for containers or hostname string in each line of mountinfo file --- .../src/opentelemetry/resource/detector/container/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index 4eae00b760..b1f0ae4d1b 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -47,7 +47,9 @@ def _get_container_id_v2(): ) as container_info_file: for raw_line in container_info_file.readlines(): line = raw_line.strip() - if "containers" or "hostname" in line: + if any( + key_word in line for key_word in ["containers", "hostname"] + ): container_id_list = [ id_ for id_ in line.split("/") From 3228dc791a7072c78d70d2b4e48a4f450abef088 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Fri, 12 May 2023 12:59:07 +0530 Subject: [PATCH 17/18] resolving review comments --- docs/index.rst | 2 +- resource/opentelemetry-resource-detector-container/README.rst | 2 +- .../src/opentelemetry/resource/detector/container/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 8a7fb7254c..5203c377e4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,7 +33,7 @@ Extensions Visit `OpenTelemetry Registry `_ to find a lot of related projects like exporters, instrumentation libraries, tracer -implementations, resource etc. +implementations, resource, etc. Installing Cutting Edge Packages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/resource/opentelemetry-resource-detector-container/README.rst b/resource/opentelemetry-resource-detector-container/README.rst index e6f2165f14..8fadd67951 100644 --- a/resource/opentelemetry-resource-detector-container/README.rst +++ b/resource/opentelemetry-resource-detector-container/README.rst @@ -22,7 +22,7 @@ Usage example for `opentelemetry-resource-detector-container` .. code-block:: python - import opentelemetry.trace as trace + from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.resource.detector.container import ( ContainerResourceDetector, diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py index 7b82da2b85..8778b43b17 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.0.0" +__version__ = "0.38b0.dev" From 817465045689a39d729c93a8266272aa94aa1700 Mon Sep 17 00:00:00 2001 From: sanket Mehta Date: Fri, 12 May 2023 13:01:00 +0530 Subject: [PATCH 18/18] resolving review comment for importing logger module --- .../src/opentelemetry/resource/detector/container/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py index b1f0ae4d1b..8e7db6a7a8 100644 --- a/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py +++ b/resource/opentelemetry-resource-detector-container/src/opentelemetry/resource/detector/container/__init__.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging +from logging import getLogger from opentelemetry.sdk.resources import Resource, ResourceDetector from opentelemetry.semconv.resource import ResourceAttributes -logger = logging.getLogger(__name__) +logger = getLogger(__name__) _DEFAULT_CGROUP_V1_PATH = "/proc/self/cgroup" _DEFAULT_CGROUP_V2_PATH = "/proc/self/mountinfo" _CONTAINER_ID_LENGTH = 64