Skip to content

Commit

Permalink
Implement aws.ecs.* resource attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Michele Mancioppi committed Aug 5, 2022
1 parent 14077a9 commit 43244d3
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#1206](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1206))
- Add psycopg2 native tags to sqlcommenter
([#1203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1203))
- 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
([#1212](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1212))

### Added
- `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients
Expand Down
Expand Up @@ -12,10 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
import socket

from urllib.request import Request, urlopen

from opentelemetry.sdk.resources import Resource, ResourceDetector
from opentelemetry.semconv.resource import (
CloudPlatformValues,
Expand Down Expand Up @@ -58,18 +61,69 @@ def detect(self) -> "Resource":
"Failed to get container ID on ECS: %s.", exception
)

return Resource(
base_resource = Resource(
{
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_ECS.value,
ResourceAttributes.CONTAINER_NAME: socket.gethostname(),
ResourceAttributes.CONTAINER_ID: container_id,
}
)

metadata_v4_endpoint = os.environ.get(
"ECS_CONTAINER_METADATA_URI_V4"
)

if not metadata_v4_endpoint:
return base_resource

# Returns https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4.html#task-metadata-endpoint-v4-response
metadata_container = json.loads(_http_get(metadata_v4_endpoint))
metadata_task = json.loads(
_http_get(f"{metadata_v4_endpoint}/task")
)

task_arn = metadata_task["TaskARN"]
base_arn = task_arn[0 : task_arn.rindex(":")] # noqa
cluster: str = metadata_task["Cluster"]
cluster_arn = (
cluster
if cluster.startswith("arn:")
else f"{base_arn}:cluster/{cluster}"
)

return base_resource.merge(
Resource(
{
ResourceAttributes.AWS_ECS_CONTAINER_ARN: metadata_container[
"ContainerARN"
],
ResourceAttributes.AWS_ECS_CLUSTER_ARN: cluster_arn,
ResourceAttributes.AWS_ECS_LAUNCHTYPE: metadata_task[
"LaunchType"
],
ResourceAttributes.AWS_ECS_TASK_ARN: task_arn,
ResourceAttributes.AWS_ECS_TASK_FAMILY: metadata_task[
"Family"
],
ResourceAttributes.AWS_ECS_TASK_REVISION: metadata_task[
"Revision"
],
}
)
)
# pylint: disable=broad-except
except Exception as exception:
if self.raise_on_error:
raise exception

logger.warning("%s failed: %s", self.__class__.__name__, exception)
return Resource.get_empty()


def _http_get(url):
with urlopen(
Request(url, method="GET"),
timeout=5,
) as response:
return response.read().decode("utf-8")
@@ -0,0 +1,44 @@
{
"DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66",
"Name": "curl",
"DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600",
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "curl",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "24"
},
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"Limits": {
"CPU": 10,
"Memory": 128
},
"CreatedAt": "2020-10-02T00:15:07.620912337Z",
"StartedAt": "2020-10-02T00:15:08.062559351Z",
"Type": "NORMAL",
"LogDriver": "awslogs",
"LogOptions": {
"awslogs-create-group": "true",
"awslogs-group": "/ecs/metadata",
"awslogs-region": "us-west-2",
"awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665"
},
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.100"
],
"AttachmentIndex": 0,
"MACAddress": "0e:9e:32:c7:48:85",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
}
@@ -0,0 +1,94 @@
{
"Cluster": "default",
"TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"Family": "curltest",
"Revision": "26",
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"PullStartedAt": "2020-10-02T00:43:06.202617438Z",
"PullStoppedAt": "2020-10-02T00:43:06.31288465Z",
"AvailabilityZone": "us-west-2d",
"LaunchType": "EC2",
"Containers": [
{
"DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38",
"Name": "~internal~ecs~pause",
"DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00",
"Image": "amazon/amazon-ecs-pause:0.1.0",
"ImageID": "",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "~internal~ecs~pause",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "26"
},
"DesiredStatus": "RESOURCES_PROVISIONED",
"KnownStatus": "RESOURCES_PROVISIONED",
"Limits": {
"CPU": 0,
"Memory": 0
},
"CreatedAt": "2020-10-02T00:43:05.602352471Z",
"StartedAt": "2020-10-02T00:43:06.076707576Z",
"Type": "CNI_PAUSE",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.61"
],
"AttachmentIndex": 0,
"MACAddress": "0e:10:e2:01:bd:91",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
},
{
"DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca",
"Name": "curl",
"DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00",
"Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest",
"ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553",
"Labels": {
"com.amazonaws.ecs.cluster": "default",
"com.amazonaws.ecs.container-name": "curl",
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
"com.amazonaws.ecs.task-definition-family": "curltest",
"com.amazonaws.ecs.task-definition-version": "26"
},
"DesiredStatus": "RUNNING",
"KnownStatus": "RUNNING",
"Limits": {
"CPU": 10,
"Memory": 128
},
"CreatedAt": "2020-10-02T00:43:06.326590752Z",
"StartedAt": "2020-10-02T00:43:06.767535449Z",
"Type": "NORMAL",
"LogDriver": "awslogs",
"LogOptions": {
"awslogs-create-group": "true",
"awslogs-group": "/ecs/metadata",
"awslogs-region": "us-west-2",
"awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c"
},
"ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d",
"Networks": [
{
"NetworkMode": "awsvpc",
"IPv4Addresses": [
"10.0.2.61"
],
"AttachmentIndex": 0,
"MACAddress": "0e:10:e2:01:bd:91",
"IPv4SubnetCIDRBlock": "10.0.2.0/24",
"PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal",
"SubnetGatewayIpv4Address": "10.0.2.1/24"
}
]
}
]
}
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import unittest
from collections import OrderedDict
from unittest.mock import mock_open, patch
Expand All @@ -31,6 +32,27 @@
}


def _read_file(filename: str) -> str:
with open(os.path.join(os.path.dirname(__file__), filename)) as f:
return f.read()


MetadataV4Uri = "mock-uri-4"


MetadataV4ContainerResponse = _read_file("metadatav4-response-container.json")


MetadataV4TaskResponse = _read_file("metadatav4-response-task.json")


def _http_get_function(url: str, *args, **kwargs) -> str:
if url == MetadataV4Uri:
return MetadataV4ContainerResponse
if url == f"{MetadataV4Uri}/task":
return MetadataV4TaskResponse


class AwsEcsResourceDetectorTest(unittest.TestCase):
@patch.dict(
"os.environ",
Expand Down Expand Up @@ -60,8 +82,55 @@ class AwsEcsResourceDetectorTest(unittest.TestCase):
1:cpuset:/docker/bogusContainerIdThatShouldNotBeOneSetBecauseTheFirstOneWasPicked
""",
)
def test_simple_create(self, mock_open_function, mock_socket_gethostname):
def test_simple_create_metadata_v3(self, mock_open_function, mock_socket_gethostname):
actual = AwsEcsResourceDetector().detect()
self.assertDictEqual(
actual.attributes.copy(), OrderedDict(MockEcsResourceAttributes)
)

@patch.dict(
"os.environ",
{"ECS_CONTAINER_METADATA_URI_V4": MetadataV4Uri},
clear=True,
)
@patch(
"socket.gethostname",
return_value=f"{MockEcsResourceAttributes[ResourceAttributes.CONTAINER_NAME]}",
)
@patch(
"builtins.open",
new_callable=mock_open,
read_data=f"""14:name=systemd:/docker/{MockEcsResourceAttributes[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
""",
)
@patch(
"opentelemetry.sdk.extension.aws.resource.ecs._http_get",
)
def test_simple_create_metadata_v4(self, mock_http_get_function, mock_open_function, mock_socket_gethostname):
mock_http_get_function.side_effect = _http_get_function
actual = AwsEcsResourceDetector().detect()
self.assertDictEqual(
actual.attributes.copy(),
OrderedDict({
**MockEcsResourceAttributes,
ResourceAttributes.AWS_ECS_CONTAINER_ARN: "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9",
ResourceAttributes.AWS_ECS_CLUSTER_ARN: "arn:aws:ecs:us-west-2:111122223333:cluster/default",
ResourceAttributes.AWS_ECS_LAUNCHTYPE: "EC2",
ResourceAttributes.AWS_ECS_TASK_ARN: "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c",
ResourceAttributes.AWS_ECS_TASK_FAMILY: "curltest",
ResourceAttributes.AWS_ECS_TASK_REVISION: "26",
})
)

0 comments on commit 43244d3

Please sign in to comment.