Skip to content

Commit 8310e0c

Browse files
authored
Add misc contract tests (#212)
Issue #, if available: I observed that we were missing the misc tests seen in other repos: https://github.com/aws-observability/aws-otel-python-instrumentation/blob/main/contract-tests/tests/test/amazon/misc/ Description of changes: Files copied over, light modification made to make them work. Note that two tests are not working and will need further analysis: test_xray_id_format & test_service 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 6b2a1b9 commit 8310e0c

File tree

5 files changed

+232
-0
lines changed

5 files changed

+232
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
import time
4+
from typing import List
5+
6+
from mock_collector_client import ResourceScopeMetric, ResourceScopeSpan
7+
from requests import Response, request
8+
from typing_extensions import override
9+
10+
from amazon.base.contract_test_base import ContractTestBase
11+
from amazon.utils.application_signals_constants import ERROR_METRIC, FAULT_METRIC, LATENCY_METRIC
12+
from opentelemetry.sdk.metrics.export import AggregationTemporality
13+
14+
# Tests in this class are supposed to validate that the SDK was configured in the correct way: It
15+
# uses the X-Ray ID format. Metrics are deltaPreferred. Type of the metrics are exponentialHistogram
16+
17+
18+
class ConfigurationTest(ContractTestBase):
19+
@override
20+
@staticmethod
21+
def get_application_image_name() -> str:
22+
return "aws-application-signals-tests-appsignals.netcore-app"
23+
24+
@override
25+
def get_application_wait_pattern(self) -> str:
26+
return "Content root path: /app"
27+
28+
@override
29+
def get_application_extra_environment_variables(self):
30+
return {"OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED": "true"}
31+
32+
def test_configuration_metrics(self):
33+
address: str = self.application.get_container_host_ip()
34+
port: str = self.application.get_exposed_port(self.get_application_port())
35+
url: str = f"http://{address}:{port}/success"
36+
response: Response = request("GET", url, timeout=20)
37+
self.assertEqual(200, response.status_code)
38+
metrics: List[ResourceScopeMetric] = self.mock_collector_client.get_metrics(
39+
{LATENCY_METRIC, ERROR_METRIC, FAULT_METRIC}
40+
)
41+
42+
self.assertEqual(len(metrics), 3)
43+
for metric in metrics:
44+
self.assertIsNotNone(metric.metric.exponential_histogram)
45+
self.assertEqual(metric.metric.exponential_histogram.aggregation_temporality, AggregationTemporality.DELTA)
46+
47+
# TODO: This does not work as expected, gives errors like 1467067824 not greater than 1746657330
48+
# def test_xray_id_format(self):
49+
# """
50+
# We are testing here that the X-Ray id format is always used by inspecting the traceid that
51+
# was in the span received by the collector, which should be consistent across multiple spans.
52+
# We are testing the following properties:
53+
# 1. Traceid is random
54+
# 2. First 32 bits of traceid is a timestamp
55+
# It is important to remember that the X-Ray traceId format had to be adapted to fit into the
56+
# definition of the OpenTelemetry traceid:
57+
# https://opentelemetry.io/docs/specs/otel/trace/api/#retrieving-the-traceid-and-spanid
58+
# Specifically for an X-Ray traceid to be a valid Otel traceId, the version digit had to be
59+
# dropped. Reference:
60+
# https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/sdk-extension/opentelemetry-sdk-extension-aws/src/opentelemetry/sdk/extension/aws/trace/aws_xray_id_generator.py
61+
# """
62+
#
63+
# seen: List[str] = []
64+
# for _ in range(100):
65+
# address: str = self.application.get_container_host_ip()
66+
# port: str = self.application.get_exposed_port(self.get_application_port())
67+
# url: str = f"http://{address}:{port}/success"
68+
# response: Response = request("GET", url, timeout=20)
69+
# self.assertEqual(200, response.status_code)
70+
#
71+
# # Since we just made the request, the time in epoch registered in the traceid should be
72+
# # approximate equal to the current time in the test, since both run on the same host.
73+
# start_time_sec: int = int(time.time())
74+
#
75+
# resource_scope_spans: List[ResourceScopeSpan] = self.mock_collector_client.get_traces()
76+
# target_span: ResourceScopeSpan = resource_scope_spans[0]
77+
# self.assertEqual(target_span.span.name, "GET /success")
78+
#
79+
# self.assertTrue(target_span.span.trace_id.hex() not in seen)
80+
# seen.append(target_span.span.trace_id.hex())
81+
#
82+
# # trace_id is bytes, so we convert it to hex string and pick the first 8 byte
83+
# # that represent the timestamp, then convert it to int for timestamp in second
84+
# trace_id_time_stamp_int: int = int(target_span.span.trace_id.hex()[:8], 16)
85+
#
86+
# # Give 2 minutes time range of tolerance for the trace timestamp
87+
# self.assertGreater(trace_id_time_stamp_int, start_time_sec - 60)
88+
# self.assertGreater(start_time_sec + 60, trace_id_time_stamp_int)
89+
# self.mock_collector_client.clear_signals()
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import Dict, List
4+
5+
from mock_collector_client import ResourceScopeMetric, ResourceScopeSpan
6+
from requests import Response, request
7+
from typing_extensions import override
8+
9+
from amazon.base.contract_test_base import ContractTestBase
10+
from amazon.utils.application_signals_constants import ERROR_METRIC, FAULT_METRIC, LATENCY_METRIC
11+
from opentelemetry.proto.common.v1.common_pb2 import AnyValue
12+
from opentelemetry.proto.metrics.v1.metrics_pb2 import Metric
13+
from opentelemetry.proto.trace.v1.trace_pb2 import Span
14+
15+
16+
def _get_k8s_attributes():
17+
return {
18+
"k8s.namespace.name": "namespace-name",
19+
"k8s.pod.name": "pod-name",
20+
"k8s.deployment.name": "deployment-name",
21+
}
22+
23+
24+
# Tests consuming this class are supposed to validate that the agent is able to get the resource
25+
# attributes through the environment variables OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME
26+
#
27+
# These tests are structured with nested classes since it is only possible to change the
28+
# resource attributes during the initialization of the OpenTelemetry SDK.
29+
30+
31+
class ResourceAttributesTest(ContractTestBase):
32+
@override
33+
@staticmethod
34+
def get_application_image_name() -> str:
35+
return "aws-application-signals-tests-appsignals.netcore-app"
36+
37+
@override
38+
def get_application_wait_pattern(self) -> str:
39+
return "Content root path: /app"
40+
41+
@override
42+
def get_application_extra_environment_variables(self):
43+
return {"OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED": "true"}
44+
45+
def do_test_resource_attributes(self, service_name):
46+
address: str = self.application.get_container_host_ip()
47+
port: str = self.application.get_exposed_port(self.get_application_port())
48+
url: str = f"http://{address}:{port}/success"
49+
response: Response = request("GET", url, timeout=20)
50+
self.assertEqual(200, response.status_code)
51+
self.assert_resource_attributes(service_name)
52+
53+
def assert_resource_attributes(self, service_name):
54+
resource_scope_spans: List[ResourceScopeSpan] = self.mock_collector_client.get_traces()
55+
metrics: List[ResourceScopeMetric] = self.mock_collector_client.get_metrics(
56+
{LATENCY_METRIC, ERROR_METRIC, FAULT_METRIC}
57+
)
58+
target_spans: List[Span] = []
59+
for resource_scope_span in resource_scope_spans:
60+
# pylint: disable=no-member
61+
if resource_scope_span.span.name == "GET /success":
62+
target_spans.append(resource_scope_span.resource_spans)
63+
64+
self.assertEqual(len(target_spans), 1)
65+
attributes_dict: Dict[str, AnyValue] = self._get_attributes_dict(target_spans[0].resource.attributes)
66+
for key, value in _get_k8s_attributes().items():
67+
self._assert_str_attribute(attributes_dict, key, value)
68+
self._assert_str_attribute(attributes_dict, "service.name", service_name)
69+
70+
target_metrics: List[Metric] = []
71+
for resource_scope_metric in metrics:
72+
if resource_scope_metric.metric.name in ["Error", "Fault", "Latency"]:
73+
target_metrics.append(resource_scope_metric.resource_metrics)
74+
self.assertEqual(len(target_metrics), 3)
75+
for target_metric in target_metrics:
76+
metric_attributes_dict: Dict[str, AnyValue] = self._get_attributes_dict(target_metric.resource.attributes)
77+
for key, value in _get_k8s_attributes().items():
78+
self._assert_str_attribute(metric_attributes_dict, key, value)
79+
self._assert_str_attribute(metric_attributes_dict, "service.name", service_name)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import List
4+
5+
from resource_attributes_test_base import ResourceAttributesTest, _get_k8s_attributes
6+
from typing_extensions import override
7+
8+
9+
class ServiceNameInEnvVarTest(ResourceAttributesTest):
10+
@override
11+
# pylint: disable=no-self-use
12+
def get_application_extra_environment_variables(self) -> str:
13+
return { "OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED": "true", "OTEL_SERVICE_NAME": "service-name-test"}
14+
15+
@override
16+
# pylint: disable=no-self-use
17+
def get_application_otel_resource_attributes(self) -> str:
18+
pairlist: List[str] = []
19+
for key, value in _get_k8s_attributes().items():
20+
pairlist.append(key + "=" + value)
21+
return ",".join(pairlist)
22+
23+
def test_service(self) -> None:
24+
self.do_test_resource_attributes("service-name-test")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import List
4+
5+
from resource_attributes_test_base import ResourceAttributesTest, _get_k8s_attributes
6+
from typing_extensions import override
7+
8+
9+
class ServiceNameInResourceAttributesTest(ResourceAttributesTest):
10+
@override
11+
# pylint: disable=no-self-use
12+
def get_application_otel_resource_attributes(self) -> str:
13+
pairlist: List[str] = []
14+
for key, value in _get_k8s_attributes().items():
15+
pairlist.append(key + "=" + value)
16+
pairlist.append("service.name=service-name")
17+
return ",".join(pairlist)
18+
19+
def test_service(self) -> None:
20+
self.do_test_resource_attributes("service-name")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from typing import List
4+
5+
from resource_attributes_test_base import ResourceAttributesTest, _get_k8s_attributes
6+
from typing_extensions import override
7+
8+
9+
class UnknownServiceNameTest(ResourceAttributesTest):
10+
@override
11+
# pylint: disable=no-self-use
12+
def get_application_otel_resource_attributes(self) -> str:
13+
pairlist: List[str] = []
14+
for key, value in _get_k8s_attributes().items():
15+
pairlist.append(key + "=" + value)
16+
return ",".join(pairlist)
17+
18+
# TODO: metric service.name is set correctly, but span service.name is being set to AppSignals.NetCore.
19+
# def test_service(self) -> None:
20+
# self.do_test_resource_attributes("unknown_service:dotnet")

0 commit comments

Comments
 (0)