Skip to content

Commit 9935f48

Browse files
authored
[chore][processor/resourcedetection] Add E2E tests for ecs detector (#45626)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Add E2E tests for ecs detector <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue Relates to #24671 Signed-off-by: Paulo Dias <paulodias.gm@gmail.com>
1 parent e644fa9 commit 9935f48

File tree

7 files changed

+478
-0
lines changed

7 files changed

+478
-0
lines changed

processor/resourcedetectionprocessor/e2e_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,53 @@ func TestE2EHetznerDetector(t *testing.T) {
806806
}, 3*time.Minute, 1*time.Second)
807807
}
808808

809+
// TestE2EECSDetector tests the ECS detector by deploying a metadata-server
810+
// sidecar that simulates the ECS Task Metadata Endpoint V4 and verifying that
811+
// the resource attributes are correctly detected and attached to metrics.
812+
func TestE2EECSDetector(t *testing.T) {
813+
var expected pmetric.Metrics
814+
expectedFile := filepath.Join("testdata", "e2e", "ecs", "expected.yaml")
815+
expected, err := golden.ReadMetrics(expectedFile)
816+
require.NoError(t, err)
817+
818+
k8sClient, err := k8stest.NewK8sClient(testKubeConfig)
819+
require.NoError(t, err)
820+
821+
metricsConsumer := new(consumertest.MetricsSink)
822+
shutdownSink := startUpSink(t, metricsConsumer)
823+
defer shutdownSink()
824+
825+
testID := uuid.NewString()[:8]
826+
collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "ecs", "collector"), map[string]string{}, "")
827+
828+
defer func() {
829+
for _, obj := range collectorObjs {
830+
require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName())
831+
}
832+
}()
833+
834+
wantEntries := 10
835+
waitForData(t, wantEntries, metricsConsumer)
836+
837+
// Uncomment to regenerate golden file
838+
// golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1])
839+
840+
require.EventuallyWithT(t, func(tt *assert.CollectT) {
841+
assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1],
842+
pmetrictest.IgnoreTimestamp(),
843+
pmetrictest.IgnoreStartTimestamp(),
844+
pmetrictest.IgnoreScopeVersion(),
845+
pmetrictest.IgnoreResourceMetricsOrder(),
846+
pmetrictest.IgnoreMetricsOrder(),
847+
pmetrictest.IgnoreScopeMetricsOrder(),
848+
pmetrictest.IgnoreMetricDataPointsOrder(),
849+
pmetrictest.IgnoreMetricValues(),
850+
pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"),
851+
),
852+
)
853+
}, 3*time.Minute, 1*time.Second)
854+
}
855+
809856
func replaceWithStar(_ string) string {
810857
return "*"
811858
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: {{ .Name }}-metadata-config
5+
namespace: default
6+
data:
7+
server.py: |
8+
import json
9+
import os
10+
from http.server import BaseHTTPRequestHandler, HTTPServer
11+
from urllib.parse import urlparse
12+
13+
# ECS Task Metadata Endpoint V4 format
14+
# See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4.html
15+
16+
# Container metadata (returned for root path - self container)
17+
CONTAINER_METADATA = {
18+
"DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086571",
19+
"Name": "otelcol",
20+
"DockerName": "ecs-test-task-1-otelcol",
21+
"Image": "otelcontribcol:latest",
22+
"ImageID": "sha256:2b7b2e1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e",
23+
"Labels": {
24+
"com.amazonaws.ecs.cluster": "test-ecs-cluster",
25+
"com.amazonaws.ecs.container-name": "otelcol",
26+
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:123456789012:task/test-ecs-cluster/abcdef1234567890abcdef1234567890",
27+
"com.amazonaws.ecs.task-definition-family": "test-task-family",
28+
"com.amazonaws.ecs.task-definition-version": "5"
29+
},
30+
"DesiredStatus": "RUNNING",
31+
"KnownStatus": "RUNNING",
32+
"Limits": {
33+
"CPU": 256,
34+
"Memory": 512
35+
},
36+
"CreatedAt": "2024-01-15T10:30:00.000000000Z",
37+
"StartedAt": "2024-01-15T10:30:05.000000000Z",
38+
"Type": "NORMAL",
39+
"Networks": [
40+
{
41+
"NetworkMode": "awsvpc",
42+
"IPv4Addresses": ["10.0.0.100"]
43+
}
44+
],
45+
"ContainerARN": "arn:aws:ecs:us-west-2:123456789012:container/test-ecs-cluster/abcdef1234567890abcdef1234567890/otelcol-id",
46+
"LogDriver": "awslogs",
47+
"LogOptions": {
48+
"awslogs-group": "otelcol-logs",
49+
"awslogs-region": "us-west-2",
50+
"awslogs-stream": "ecs/otelcol/abcdef1234567890"
51+
}
52+
}
53+
54+
# Application container metadata
55+
APP_CONTAINER_METADATA = {
56+
"DockerId": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
57+
"Name": "application",
58+
"DockerName": "ecs-test-task-1-application",
59+
"Image": "my-app:latest",
60+
"ImageID": "sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
61+
"Labels": {
62+
"com.amazonaws.ecs.cluster": "test-ecs-cluster",
63+
"com.amazonaws.ecs.container-name": "application",
64+
"com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:123456789012:task/test-ecs-cluster/abcdef1234567890abcdef1234567890",
65+
"com.amazonaws.ecs.task-definition-family": "test-task-family",
66+
"com.amazonaws.ecs.task-definition-version": "5"
67+
},
68+
"DesiredStatus": "RUNNING",
69+
"KnownStatus": "RUNNING",
70+
"Limits": {
71+
"CPU": 512,
72+
"Memory": 1024
73+
},
74+
"CreatedAt": "2024-01-15T10:30:00.000000000Z",
75+
"StartedAt": "2024-01-15T10:30:05.000000000Z",
76+
"Type": "NORMAL",
77+
"Networks": [
78+
{
79+
"NetworkMode": "awsvpc",
80+
"IPv4Addresses": ["10.0.0.100"]
81+
}
82+
],
83+
"ContainerARN": "arn:aws:ecs:us-west-2:123456789012:container/test-ecs-cluster/abcdef1234567890abcdef1234567890/app-id",
84+
"LogDriver": "awslogs",
85+
"LogOptions": {
86+
"awslogs-group": "application-logs",
87+
"awslogs-region": "us-west-2",
88+
"awslogs-stream": "ecs/application/abcdef1234567890"
89+
}
90+
}
91+
92+
# Task metadata (returned for /task path)
93+
TASK_METADATA = {
94+
"Cluster": "test-ecs-cluster",
95+
"TaskARN": "arn:aws:ecs:us-west-2:123456789012:task/test-ecs-cluster/abcdef1234567890abcdef1234567890",
96+
"Family": "test-task-family",
97+
"Revision": "5",
98+
"DesiredStatus": "RUNNING",
99+
"KnownStatus": "RUNNING",
100+
"Limits": {
101+
"CPU": 1.0,
102+
"Memory": 2048
103+
},
104+
"PullStartedAt": "2024-01-15T10:29:55.000000000Z",
105+
"PullStoppedAt": "2024-01-15T10:30:00.000000000Z",
106+
"AvailabilityZone": "us-west-2a",
107+
"LaunchType": "FARGATE",
108+
"Containers": [CONTAINER_METADATA, APP_CONTAINER_METADATA]
109+
}
110+
111+
class Handler(BaseHTTPRequestHandler):
112+
def do_GET(self):
113+
parsed = urlparse(self.path)
114+
path = parsed.path
115+
116+
# Health check endpoint
117+
if path == "/healthz":
118+
body = b"ok"
119+
self.send_response(200)
120+
self.send_header("Content-Type", "text/plain; charset=utf-8")
121+
self.send_header("Content-Length", str(len(body)))
122+
self.end_headers()
123+
self.wfile.write(body)
124+
return
125+
126+
# ECS Task Metadata Endpoint V4 - Task metadata
127+
if path == "/task":
128+
body = json.dumps(TASK_METADATA).encode("utf-8")
129+
self.send_response(200)
130+
self.send_header("Content-Type", "application/json; charset=utf-8")
131+
self.send_header("Content-Length", str(len(body)))
132+
self.end_headers()
133+
self.wfile.write(body)
134+
return
135+
136+
# ECS Task Metadata Endpoint V4 - Container metadata (root path)
137+
if path == "/" or path == "":
138+
body = json.dumps(CONTAINER_METADATA).encode("utf-8")
139+
self.send_response(200)
140+
self.send_header("Content-Type", "application/json; charset=utf-8")
141+
self.send_header("Content-Length", str(len(body)))
142+
self.end_headers()
143+
self.wfile.write(body)
144+
return
145+
146+
# Not found
147+
self.send_response(404)
148+
self.end_headers()
149+
150+
def log_message(self, fmt, *args):
151+
return
152+
153+
if __name__ == "__main__":
154+
port = int(os.environ.get("PORT", "80"))
155+
server = HTTPServer(("", port), Handler)
156+
server.serve_forever()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: {{ .Name }}-config
5+
namespace: default
6+
data:
7+
relay: |
8+
exporters:
9+
otlp:
10+
endpoint: {{ .HostEndpoint }}:4317
11+
tls:
12+
insecure: true
13+
extensions:
14+
health_check:
15+
endpoint: 0.0.0.0:13133
16+
processors:
17+
resourcedetection:
18+
detectors: [ecs]
19+
timeout: 2s
20+
override: false
21+
receivers:
22+
hostmetrics:
23+
collection_interval: 1s
24+
scrapers:
25+
cpu:
26+
service:
27+
telemetry:
28+
logs:
29+
level: "debug"
30+
extensions:
31+
- health_check
32+
pipelines:
33+
metrics:
34+
receivers:
35+
- hostmetrics
36+
processors:
37+
- resourcedetection
38+
exporters:
39+
- otlp
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: {{ .Name }}-sa
5+
namespace: default
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: {{ .Name }}
5+
namespace: default
6+
spec:
7+
selector:
8+
app: {{ .Name }}
9+
ports:
10+
- name: health
11+
port: 13133
12+
targetPort: 13133
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ .Name }}
5+
namespace: default
6+
spec:
7+
replicas: 1
8+
selector:
9+
matchLabels:
10+
app: {{ .Name }}
11+
template:
12+
metadata:
13+
labels:
14+
app: {{ .Name }}
15+
spec:
16+
serviceAccountName: {{ .Name }}-sa
17+
initContainers:
18+
- name: metadata-server
19+
image: python:3.13-alpine
20+
imagePullPolicy: IfNotPresent
21+
restartPolicy: Always
22+
securityContext:
23+
runAsUser: 0
24+
capabilities:
25+
drop:
26+
- "ALL"
27+
add:
28+
- "NET_BIND_SERVICE"
29+
command:
30+
- python3
31+
- /scripts/server.py
32+
env:
33+
- name: PORT
34+
value: "8080"
35+
ports:
36+
- containerPort: 8080
37+
name: metadata
38+
startupProbe:
39+
httpGet:
40+
path: /healthz
41+
port: 8080
42+
initialDelaySeconds: 1
43+
periodSeconds: 1
44+
failureThreshold: 10
45+
volumeMounts:
46+
- name: metadata-script
47+
mountPath: /scripts
48+
containers:
49+
- name: otelcol
50+
image: otelcontribcol:latest
51+
imagePullPolicy: Never
52+
args:
53+
- "--config=/conf/relay"
54+
env:
55+
# ECS Task Metadata Endpoint V4 environment variable
56+
# The detector reads this env var to find the metadata endpoint
57+
- name: ECS_CONTAINER_METADATA_URI_V4
58+
value: "http://127.0.0.1:8080"
59+
volumeMounts:
60+
- name: config
61+
mountPath: /conf
62+
volumes:
63+
- name: config
64+
configMap:
65+
name: {{ .Name }}-config
66+
items:
67+
- key: relay
68+
path: relay
69+
- name: metadata-script
70+
configMap:
71+
name: {{ .Name }}-metadata-config

0 commit comments

Comments
 (0)