Skip to content

Commit 6320522

Browse files
authored
Apply patched only when dependency detected. (#122)
*Issue #, if available:* We face `botocore` version conflict and python module miss localization when both sample app and aws distro install `botocore` pkg with their own version. *Description of changes:* Move `botocore` to `[project.optional-dependencies]` as additional packages. The user will need to manually install botocore themselves, otherwise, it won’t be automatically installed when users install `aws-opentelemetry-distro`. This has the same behaviour as upstream [opentelemetry-instrumentation-botocore](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/b84d779104b8c511ae7b876f5a9f0db3f21654e6/instrumentation/opentelemetry-instrumentation-botocore). Note: 1. To check whether `botocore` is installed in the system, we don't read `pyproject.toml` file directly since it is only used when building the pkg, and won't be copied into the container. Instead, we define a `patch_libraries` dict. 2. we move `import apply_instrumentation_patches ` into conditional check since the `botocore` only available if condition pass. Otherwise, if we put it on the top of the file, the instrumentation will throw error when `botocore` not installed. Test: Tested on ECS and locally. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent be3bdcb commit 6320522

File tree

8 files changed

+131
-57
lines changed

8 files changed

+131
-57
lines changed

aws-opentelemetry-distro/pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ dependencies = [
6868
"opentelemetry-instrumentation-urllib3 == 0.43b0",
6969
"opentelemetry-instrumentation-wsgi == 0.43b0",
7070
"opentelemetry-instrumentation-cassandra == 0.43b0",
71-
72-
# Patch dependencies
73-
"botocore ~= 1.0",
7471
]
7572

7673
[project.optional-dependencies]
74+
# The 'patch' optional dependency is used for applying patches to specific libraries.
75+
# If a new patch is added into the list, it must also be added into tox.ini, dev-requirements.txt and _instrumentation_patch
76+
patch = [
77+
"botocore ~= 1.0",
78+
]
7779
test = []
7880

7981
[project.entry-points.opentelemetry_configurator]

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/aws_opentelemetry_distro.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33
import os
44

5-
from amazon.opentelemetry.distro._instrumentation_patch import apply_instrumentation_patches
5+
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
66
from opentelemetry.distro import OpenTelemetryDistro
77
from opentelemetry.environment_variables import OTEL_PROPAGATORS, OTEL_PYTHON_ID_GENERATOR
88
from opentelemetry.sdk.environment_variables import OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_instrumentation_patch.py renamed to aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_botocore_patches.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,13 @@
22
# SPDX-License-Identifier: Apache-2.0
33
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
44
import importlib
5-
import ssl
6-
from urllib.request import Request, urlopen
75

8-
import opentelemetry.sdk.extension.aws.resource.ec2 as ec2_resource
9-
import opentelemetry.sdk.extension.aws.resource.eks as eks_resource
106
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
117
from opentelemetry.instrumentation.botocore.extensions.sqs import _SqsExtension
128
from opentelemetry.instrumentation.botocore.extensions.types import _AttributeMapT, _AwsSdkExtension
139
from opentelemetry.semconv.trace import SpanAttributes
1410

1511

16-
def apply_instrumentation_patches() -> None:
17-
"""Apply patches to upstream instrumentation libraries.
18-
19-
This method is invoked to apply changes to upstream instrumentation libraries, typically when changes to upstream
20-
are required on a timeline that cannot wait for upstream release. Generally speaking, patches should be short-term
21-
local solutions that are comparable to long-term upstream solutions.
22-
23-
Where possible, automated testing should be run to catch upstream changes resulting in broken patches
24-
"""
25-
_apply_botocore_instrumentation_patches()
26-
27-
_apply_resource_detector_patches()
28-
29-
3012
def _apply_botocore_instrumentation_patches() -> None:
3113
"""Botocore instrumentation patches
3214
@@ -37,38 +19,6 @@ def _apply_botocore_instrumentation_patches() -> None:
3719
_apply_botocore_sqs_patch()
3820

3921

40-
# The OpenTelemetry Authors code
41-
def _apply_resource_detector_patches() -> None:
42-
"""AWS Resource Detector patches for getting the following unreleased change (as of v2.0.1) in the upstream:
43-
https://github.com/open-telemetry/opentelemetry-python-contrib/commit/a5ec3f7f55494cb80b4b53c652e31c465b8d5e80
44-
"""
45-
46-
def patch_ec2_aws_http_request(method, path, headers):
47-
with urlopen(
48-
Request("http://169.254.169.254" + path, headers=headers, method=method),
49-
timeout=5,
50-
) as response:
51-
return response.read().decode("utf-8")
52-
53-
def patch_eks_aws_http_request(method, path, cred_value):
54-
with urlopen(
55-
Request(
56-
"https://kubernetes.default.svc" + path,
57-
headers={"Authorization": cred_value},
58-
method=method,
59-
),
60-
timeout=5,
61-
context=ssl.create_default_context(cafile="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"),
62-
) as response:
63-
return response.read().decode("utf-8")
64-
65-
ec2_resource._aws_http_request = patch_ec2_aws_http_request
66-
eks_resource._aws_http_request = patch_eks_aws_http_request
67-
68-
69-
# END The OpenTelemetry Authors code
70-
71-
7222
def _apply_botocore_kinesis_patch() -> None:
7323
"""Botocore instrumentation patch for Kinesis
7424
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
4+
import sys
5+
from logging import Logger, getLogger
6+
7+
import pkg_resources
8+
9+
from amazon.opentelemetry.distro.patches._resource_detector_patches import _apply_resource_detector_patches
10+
11+
_logger: Logger = getLogger(__name__)
12+
13+
14+
def apply_instrumentation_patches() -> None:
15+
"""Apply patches to upstream instrumentation libraries.
16+
17+
This method is invoked to apply changes to upstream instrumentation libraries, typically when changes to upstream
18+
are required on a timeline that cannot wait for upstream release. Generally speaking, patches should be short-term
19+
local solutions that are comparable to long-term upstream solutions.
20+
21+
Where possible, automated testing should be run to catch upstream changes resulting in broken patches
22+
"""
23+
24+
if _is_installed("botocore ~= 1.0"):
25+
# pylint: disable=import-outside-toplevel
26+
# Delay import to only occur if patches is safe to apply (e.g. the instrumented library is installed).
27+
from amazon.opentelemetry.distro.patches._botocore_patches import _apply_botocore_instrumentation_patches
28+
29+
_apply_botocore_instrumentation_patches()
30+
31+
# No need to check if library is installed as this patches opentelemetry.sdk,
32+
# which must be installed for the distro to work at all.
33+
_apply_resource_detector_patches()
34+
35+
36+
def _is_installed(req: str) -> bool:
37+
if req in sys.modules:
38+
return True
39+
40+
try:
41+
pkg_resources.get_distribution(req)
42+
except Exception as exc: # pylint: disable=broad-except
43+
_logger.debug("Skipping instrumentation patch: package %s, exception: %s", req, exc)
44+
return False
45+
return True
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
# Modifications Copyright The OpenTelemetry Authors. Licensed under the Apache License 2.0 License.
4+
import ssl
5+
from urllib.request import Request, urlopen
6+
7+
import opentelemetry.sdk.extension.aws.resource.ec2 as ec2_resource
8+
import opentelemetry.sdk.extension.aws.resource.eks as eks_resource
9+
10+
11+
# The OpenTelemetry Authors code
12+
def _apply_resource_detector_patches() -> None:
13+
"""AWS Resource Detector patches for getting the following unreleased change (as of v2.0.1) in the upstream:
14+
https://github.com/open-telemetry/opentelemetry-python-contrib/commit/a5ec3f7f55494cb80b4b53c652e31c465b8d5e80
15+
"""
16+
17+
def patch_ec2_aws_http_request(method, path, headers):
18+
with urlopen(
19+
Request("http://169.254.169.254" + path, headers=headers, method=method),
20+
timeout=5,
21+
) as response:
22+
return response.read().decode("utf-8")
23+
24+
def patch_eks_aws_http_request(method, path, cred_value):
25+
with urlopen(
26+
Request(
27+
"https://kubernetes.default.svc" + path,
28+
headers={"Authorization": cred_value},
29+
method=method,
30+
),
31+
timeout=5,
32+
context=ssl.create_default_context(cafile="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"),
33+
) as response:
34+
return response.read().decode("utf-8")
35+
36+
ec2_resource._aws_http_request = patch_ec2_aws_http_request
37+
eks_resource._aws_http_request = patch_eks_aws_http_request
38+
39+
40+
# END The OpenTelemetry Authors code

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from typing import Dict
44
from unittest import TestCase
5-
from unittest.mock import MagicMock
5+
from unittest.mock import MagicMock, patch
66

7-
from amazon.opentelemetry.distro._instrumentation_patch import apply_instrumentation_patches
7+
import pkg_resources
8+
9+
from amazon.opentelemetry.distro.patches._instrumentation_patch import apply_instrumentation_patches
810
from opentelemetry.instrumentation.botocore.extensions import _KNOWN_EXTENSIONS
911
from opentelemetry.semconv.trace import SpanAttributes
1012

@@ -15,10 +17,43 @@
1517

1618

1719
class TestInstrumentationPatch(TestCase):
18-
def test_apply_instrumentation_patches(self):
20+
@classmethod
21+
def setUpClass(cls):
22+
super().setUpClass()
23+
cls.mock_get_distribution = patch(
24+
"amazon.opentelemetry.distro.patches._instrumentation_patch.pkg_resources.get_distribution"
25+
).start()
26+
27+
@classmethod
28+
def tearDownClass(cls):
29+
super().tearDownClass()
30+
cls.mock_get_distribution.stop()
31+
32+
def test_botocore_not_installed(self):
33+
# Test scenario 1: Botocore package not installed
34+
self.mock_get_distribution.side_effect = pkg_resources.DistributionNotFound
35+
apply_instrumentation_patches()
36+
with patch(
37+
"amazon.opentelemetry.distro.patches._botocore_patches._apply_botocore_instrumentation_patches"
38+
) as mock_apply_patches:
39+
mock_apply_patches.assert_not_called()
40+
41+
def test_botocore_installed_wrong_version(self):
42+
# Test scenario 2: Botocore package installed with wrong version
43+
self.mock_get_distribution.side_effect = pkg_resources.VersionConflict("botocore==1.0.0", "botocore==0.0.1")
44+
apply_instrumentation_patches()
45+
with patch(
46+
"amazon.opentelemetry.distro.patches._botocore_patches._apply_botocore_instrumentation_patches"
47+
) as mock_apply_patches:
48+
mock_apply_patches.assert_not_called()
49+
50+
def test_botocore_installed_correct_version(self):
51+
# Test scenario 3: Botocore package installed with correct version
1952
# Validate unpatched upstream behaviour - important to detect upstream changes that may break instrumentation
2053
self._validate_unpatched_botocore_instrumentation()
2154

55+
self.mock_get_distribution.return_value = "CorrectDistributionObject"
56+
2257
# Apply patches
2358
apply_instrumentation_patches()
2459

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ codespell==2.1.0
1414
requests==2.31.0
1515
ruamel.yaml==0.17.21
1616
flaky==3.7.0
17+
botocore==1.34.67

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ commands_pre =
2929
3.{7,8,9,10,11}: python -m pip install -U pip setuptools wheel
3030
; Install common packages for all the tests. These are not needed in all the
3131
; cases but it saves a lot of boilerplate in this file.
32+
test: pip install botocore
3233
test: pip install "opentelemetry-api[test] @ {env:CORE_REPO}#egg=opentelemetry-api&subdirectory=opentelemetry-api"
3334
test: pip install "opentelemetry-sdk[test] @ {env:CORE_REPO}#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk"
3435
test: pip install "opentelemetry-instrumentation[test] @ {env:CONTRIB_REPO}#egg=opentelemetry-instrumentation&subdirectory=opentelemetry-instrumentation"

0 commit comments

Comments
 (0)