Skip to content

Commit dbb1621

Browse files
author
Michele Mancioppi
committed
Implement aws.ecs.* resource attributes
1 parent 14077a9 commit dbb1621

File tree

5 files changed

+275
-2
lines changed

5 files changed

+275
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
([#1206](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1206))
1515
- Add psycopg2 native tags to sqlcommenter
1616
([#1203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1203))
17+
- Implement [`aws.ecs.*` resource attributes](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/cloud_provider/aws/ecs.md) in the `AwsEcsResourceDetector` detector
18+
([#1212](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1212))
1719

1820
### Added
1921
- `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients

sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/resource/ecs.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import json
1516
import logging
1617
import os
1718
import socket
19+
from urllib.request import Request, urlopen
1820

1921
from opentelemetry.sdk.resources import Resource, ResourceDetector
2022
from opentelemetry.semconv.resource import (
@@ -58,18 +60,69 @@ def detect(self) -> "Resource":
5860
"Failed to get container ID on ECS: %s.", exception
5961
)
6062

61-
return Resource(
63+
base_resource = Resource(
6264
{
6365
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
6466
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_ECS.value,
6567
ResourceAttributes.CONTAINER_NAME: socket.gethostname(),
6668
ResourceAttributes.CONTAINER_ID: container_id,
6769
}
6870
)
71+
72+
metadata_v4_endpoint = os.environ.get(
73+
"ECS_CONTAINER_METADATA_URI_V4"
74+
)
75+
76+
if not metadata_v4_endpoint:
77+
return base_resource
78+
79+
# Returns https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4.html#task-metadata-endpoint-v4-response
80+
metadata_container = json.loads(_http_get(metadata_v4_endpoint))
81+
metadata_task = json.loads(
82+
_http_get(f"{metadata_v4_endpoint}/task")
83+
)
84+
85+
task_arn = metadata_task["TaskARN"]
86+
base_arn = task_arn[0 : task_arn.rindex(":")] # noqa
87+
cluster: str = metadata_task["Cluster"]
88+
cluster_arn = (
89+
cluster
90+
if cluster.startswith("arn:")
91+
else f"{base_arn}:cluster/{cluster}"
92+
)
93+
94+
return base_resource.merge(
95+
Resource(
96+
{
97+
ResourceAttributes.AWS_ECS_CONTAINER_ARN: metadata_container[
98+
"ContainerARN"
99+
],
100+
ResourceAttributes.AWS_ECS_CLUSTER_ARN: cluster_arn,
101+
ResourceAttributes.AWS_ECS_LAUNCHTYPE: metadata_task[
102+
"LaunchType"
103+
],
104+
ResourceAttributes.AWS_ECS_TASK_ARN: task_arn,
105+
ResourceAttributes.AWS_ECS_TASK_FAMILY: metadata_task[
106+
"Family"
107+
],
108+
ResourceAttributes.AWS_ECS_TASK_REVISION: metadata_task[
109+
"Revision"
110+
],
111+
}
112+
)
113+
)
69114
# pylint: disable=broad-except
70115
except Exception as exception:
71116
if self.raise_on_error:
72117
raise exception
73118

74119
logger.warning("%s failed: %s", self.__class__.__name__, exception)
75120
return Resource.get_empty()
121+
122+
123+
def _http_get(url):
124+
with urlopen(
125+
Request(url, method="GET"),
126+
timeout=5,
127+
) as response:
128+
return response.read().decode("utf-8")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66",
3+
"Name": "curl",
4+
"DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600",
5+
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
6+
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
7+
"Labels": {
8+
"com.amazonaws.ecs.cluster": "default",
9+
"com.amazonaws.ecs.container-name": "curl",
10+
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665",
11+
"com.amazonaws.ecs.task-definition-family": "curltest",
12+
"com.amazonaws.ecs.task-definition-version": "24"
13+
},
14+
"DesiredStatus": "RUNNING",
15+
"KnownStatus": "RUNNING",
16+
"Limits": {
17+
"CPU": 10,
18+
"Memory": 128
19+
},
20+
"CreatedAt": "2020-10-02T00:15:07.620912337Z",
21+
"StartedAt": "2020-10-02T00:15:08.062559351Z",
22+
"Type": "NORMAL",
23+
"LogDriver": "awslogs",
24+
"LogOptions": {
25+
"awslogs-create-group": "true",
26+
"awslogs-group": "/ecs/metadata",
27+
"awslogs-region": "us-west-2",
28+
"awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665"
29+
},
30+
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9",
31+
"Networks": [
32+
{
33+
"NetworkMode": "awsvpc",
34+
"IPv4Addresses": [
35+
"10.0.2.100"
36+
],
37+
"AttachmentIndex": 0,
38+
"MACAddress": "0e:9e:32:c7:48:85",
39+
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
40+
"PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal",
41+
"SubnetGatewayIpv4Address": "10.0.2.1/24"
42+
}
43+
]
44+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"Cluster": "default",
3+
"TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
4+
"Family": "curltest",
5+
"Revision": "26",
6+
"DesiredStatus": "RUNNING",
7+
"KnownStatus": "RUNNING",
8+
"PullStartedAt": "2020-10-02T00:43:06.202617438Z",
9+
"PullStoppedAt": "2020-10-02T00:43:06.31288465Z",
10+
"AvailabilityZone": "us-west-2d",
11+
"LaunchType": "EC2",
12+
"Containers": [
13+
{
14+
"DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38",
15+
"Name": "~internal~ecs~pause",
16+
"DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00",
17+
"Image": "amazon/amazon-ecs-pause:0.1.0",
18+
"ImageID": "",
19+
"Labels": {
20+
"com.amazonaws.ecs.cluster": "default",
21+
"com.amazonaws.ecs.container-name": "~internal~ecs~pause",
22+
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
23+
"com.amazonaws.ecs.task-definition-family": "curltest",
24+
"com.amazonaws.ecs.task-definition-version": "26"
25+
},
26+
"DesiredStatus": "RESOURCES_PROVISIONED",
27+
"KnownStatus": "RESOURCES_PROVISIONED",
28+
"Limits": {
29+
"CPU": 0,
30+
"Memory": 0
31+
},
32+
"CreatedAt": "2020-10-02T00:43:05.602352471Z",
33+
"StartedAt": "2020-10-02T00:43:06.076707576Z",
34+
"Type": "CNI_PAUSE",
35+
"Networks": [
36+
{
37+
"NetworkMode": "awsvpc",
38+
"IPv4Addresses": [
39+
"10.0.2.61"
40+
],
41+
"AttachmentIndex": 0,
42+
"MACAddress": "0e:10:e2:01:bd:91",
43+
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
44+
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
45+
"SubnetGatewayIpv4Address": "10.0.2.1/24"
46+
}
47+
]
48+
},
49+
{
50+
"DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca",
51+
"Name": "curl",
52+
"DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00",
53+
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
54+
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
55+
"Labels": {
56+
"com.amazonaws.ecs.cluster": "default",
57+
"com.amazonaws.ecs.container-name": "curl",
58+
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
59+
"com.amazonaws.ecs.task-definition-family": "curltest",
60+
"com.amazonaws.ecs.task-definition-version": "26"
61+
},
62+
"DesiredStatus": "RUNNING",
63+
"KnownStatus": "RUNNING",
64+
"Limits": {
65+
"CPU": 10,
66+
"Memory": 128
67+
},
68+
"CreatedAt": "2020-10-02T00:43:06.326590752Z",
69+
"StartedAt": "2020-10-02T00:43:06.767535449Z",
70+
"Type": "NORMAL",
71+
"LogDriver": "awslogs",
72+
"LogOptions": {
73+
"awslogs-create-group": "true",
74+
"awslogs-group": "/ecs/metadata",
75+
"awslogs-region": "us-west-2",
76+
"awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c"
77+
},
78+
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d",
79+
"Networks": [
80+
{
81+
"NetworkMode": "awsvpc",
82+
"IPv4Addresses": [
83+
"10.0.2.61"
84+
],
85+
"AttachmentIndex": 0,
86+
"MACAddress": "0e:10:e2:01:bd:91",
87+
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
88+
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
89+
"SubnetGatewayIpv4Address": "10.0.2.1/24"
90+
}
91+
]
92+
}
93+
]
94+
}

sdk-extension/opentelemetry-sdk-extension-aws/tests/resource/test_ecs.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
1516
import unittest
1617
from collections import OrderedDict
1718
from unittest.mock import mock_open, patch
@@ -31,6 +32,27 @@
3132
}
3233

3334

35+
def _read_file(filename: str) -> str:
36+
with open(os.path.join(os.path.dirname(__file__), filename)) as f:
37+
return f.read()
38+
39+
40+
MetadataV4Uri = "mock-uri-4"
41+
42+
43+
MetadataV4ContainerResponse = _read_file("metadatav4-response-container.json")
44+
45+
46+
MetadataV4TaskResponse = _read_file("metadatav4-response-task.json")
47+
48+
49+
def _http_get_function(url: str, *args, **kwargs) -> str:
50+
if url == MetadataV4Uri:
51+
return MetadataV4ContainerResponse
52+
if url == f"{MetadataV4Uri}/task":
53+
return MetadataV4TaskResponse
54+
55+
3456
class AwsEcsResourceDetectorTest(unittest.TestCase):
3557
@patch.dict(
3658
"os.environ",
@@ -60,8 +82,66 @@ class AwsEcsResourceDetectorTest(unittest.TestCase):
6082
1:cpuset:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
6183
""",
6284
)
63-
def test_simple_create(self, mock_open_function, mock_socket_gethostname):
85+
def test_simple_create_metadata_v3(
86+
self,
87+
mock_open_function,
88+
mock_socket_gethostname,
89+
):
6490
actual = AwsEcsResourceDetector().detect()
6591
self.assertDictEqual(
6692
actual.attributes.copy(), OrderedDict(MockEcsResourceAttributes)
6793
)
94+
95+
@patch.dict(
96+
"os.environ",
97+
{"ECS_CONTAINER_METADATA_URI_V4": MetadataV4Uri},
98+
clear=True,
99+
)
100+
@patch(
101+
"socket.gethostname",
102+
return_value=f"{MockEcsResourceAttributes[ResourceAttributes.CONTAINER_NAME]}",
103+
)
104+
@patch(
105+
"builtins.open",
106+
new_callable=mock_open,
107+
read_data=f"""14:name=systemd:/docker/{MockEcsResourceAttributes[ResourceAttributes.CONTAINER_ID]}
108+
13:rdma:/
109+
12:pids:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
110+
11:hugetlb:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
111+
10:net_prio:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
112+
9:perf_event:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
113+
8:net_cls:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
114+
7:freezer:/docker/
115+
6:devices:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
116+
5:memory:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
117+
4:blkio:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
118+
3:cpuacct:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
119+
2:cpu:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
120+
1:cpuset:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
121+
""",
122+
)
123+
@patch(
124+
"opentelemetry.sdk.extension.aws.resource.ecs._http_get",
125+
)
126+
def test_simple_create_metadata_v4(
127+
self,
128+
mock_http_get_function,
129+
mock_open_function,
130+
mock_socket_gethostname,
131+
):
132+
mock_http_get_function.side_effect = _http_get_function
133+
actual = AwsEcsResourceDetector().detect()
134+
self.assertDictEqual(
135+
actual.attributes.copy(),
136+
OrderedDict(
137+
{
138+
**MockEcsResourceAttributes,
139+
ResourceAttributes.AWS_ECS_CONTAINER_ARN: "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9",
140+
ResourceAttributes.AWS_ECS_CLUSTER_ARN: "arn:aws:ecs:us-west-2:111122223333:cluster/default",
141+
ResourceAttributes.AWS_ECS_LAUNCHTYPE: "EC2",
142+
ResourceAttributes.AWS_ECS_TASK_ARN: "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
143+
ResourceAttributes.AWS_ECS_TASK_FAMILY: "curltest",
144+
ResourceAttributes.AWS_ECS_TASK_REVISION: "26",
145+
}
146+
),
147+
)

0 commit comments

Comments
 (0)