diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b0fd8796b2f..fa5f5d73f3f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -45,6 +45,9 @@ ddtrace/internal/settings/_config.py @DataDog/python-guild @DataDog/ap docs/ @DataDog/python-guild tests/utils.py @DataDog/python-guild tests/suitespec.yml @DataDog/python-guild @DataDog/apm-core-python +tests/contrib/suitespec.yml @DataDog/python-guild +tests/contrib/flask/app.py @DataDog/python-guild +tests/contrib/django/django1_app/urls.py @DataDog/python-guild tests/suitespec.py @DataDog/python-guild @DataDog/apm-core-python scripts/bump_ddtrace.py @DataDog/python-guild tests/smoke_test.py @DataDog/python-guild diff --git a/tests/contrib/urllib3/test_urllib3.py b/tests/contrib/urllib3/test_urllib3.py index 3598ffb2608..f3634d7cfd0 100644 --- a/tests/contrib/urllib3/test_urllib3.py +++ b/tests/contrib/urllib3/test_urllib3.py @@ -12,7 +12,6 @@ from ddtrace.contrib.internal.urllib3.patch import unpatch from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from ddtrace.internal.settings.asm import config as asm_config from tests.contrib.config import HTTPBIN_CONFIG from tests.utils import TracerTestCase from tests.utils import snapshot @@ -499,97 +498,6 @@ def test_distributed_tracing_disabled(self): timeout=mock.ANY, ) - @pytest.mark.skip(reason="urlib3 does not set the ASM Manual keep tag so x-datadog headers are not propagated") - def test_distributed_tracing_apm_opt_out_true(self): - """Tests distributed tracing headers are passed by default""" - # Check that distributed tracing headers are passed down; raise an error rather than make the - # request since we don't care about the response at all - config.urllib3["distributed_tracing"] = True - self.tracer.enabled = False - # Ensure the ASM SpanProcessor is set - self.tracer.configure(apm_tracing_disabled=True, appsec_enabled=True) - assert asm_config._apm_opt_out - with mock.patch( - "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError - ) as m_make_request: - with pytest.raises(ValueError): - self.http.request("GET", URL_200) - - spans = self.pop_spans() - s = spans[0] - expected_headers = { - "x-datadog-trace-id": str(s._trace_id_64bits), - "x-datadog-parent-id": str(s.span_id), - "x-datadog-sampling-priority": "1", - "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid={}".format(_get_64_highest_order_bits_as_hex(s.trace_id)), - "traceparent": s.context._traceparent, - # outgoing headers must contain last parent span id in tracestate - "tracestate": s.context._tracestate.replace("dd=", "dd=p:{:016x};".format(s.span_id)), - } - - if int(urllib3.__version__.split(".")[0]) >= 2: - m_make_request.assert_called_with( - mock.ANY, - "GET", - "/status/200", - body=None, - chunked=mock.ANY, - headers=expected_headers, - timeout=mock.ANY, - retries=mock.ANY, - response_conn=mock.ANY, - preload_content=mock.ANY, - decode_content=mock.ANY, - ) - else: - m_make_request.assert_called_with( - mock.ANY, - "GET", - "/status/200", - body=None, - chunked=mock.ANY, - headers=expected_headers, - timeout=mock.ANY, - ) - - def test_distributed_tracing_apm_opt_out_false(self): - """Test with distributed tracing disabled does not propagate the headers""" - config.urllib3["distributed_tracing"] = True - # Ensure the ASM SpanProcessor is set. - self.tracer.configure(apm_tracing_disabled=False, appsec_enabled=True) - self.tracer.enabled = False - assert not asm_config._apm_opt_out - with mock.patch( - "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError - ) as m_make_request: - with pytest.raises(ValueError): - self.http.request("GET", URL_200) - - if int(urllib3.__version__.split(".")[0]) >= 2: - m_make_request.assert_called_with( - mock.ANY, - "GET", - "/status/200", - body=None, - chunked=mock.ANY, - headers={}, - timeout=mock.ANY, - retries=mock.ANY, - response_conn=mock.ANY, - preload_content=mock.ANY, - decode_content=mock.ANY, - ) - else: - m_make_request.assert_called_with( - mock.ANY, - "GET", - "/status/200", - body=None, - chunked=mock.ANY, - headers={}, - timeout=mock.ANY, - ) - @pytest.fixture() def patch_urllib3(): diff --git a/tests/contrib/urllib3/test_urllib3_appsec.py b/tests/contrib/urllib3/test_urllib3_appsec.py new file mode 100644 index 00000000000..985ed405c47 --- /dev/null +++ b/tests/contrib/urllib3/test_urllib3_appsec.py @@ -0,0 +1,108 @@ +import mock +import pytest +import urllib3 + +from ddtrace import config +from ddtrace._trace.span import _get_64_highest_order_bits_as_hex +from ddtrace.internal.settings.asm import config as asm_config +from tests.contrib.config import HTTPBIN_CONFIG +from tests.contrib.urllib3.test_urllib3 import BaseUrllib3TestCase + + +HOST = HTTPBIN_CONFIG["host"] +PORT = HTTPBIN_CONFIG["port"] +SOCKET = "{}:{}".format(HOST, PORT) +URL_200 = "http://{}/status/200".format(SOCKET) + + +class TestUrllib3(BaseUrllib3TestCase): + @pytest.mark.skip(reason="urlib3 does not set the ASM Manual keep tag so x-datadog headers are not propagated") + def test_distributed_tracing_apm_opt_out_true(self): + """Tests distributed tracing headers are passed by default""" + # Check that distributed tracing headers are passed down; raise an error rather than make the + # request since we don't care about the response at all + config.urllib3["distributed_tracing"] = True + self.tracer.enabled = False + # Ensure the ASM SpanProcessor is set + self.tracer.configure(apm_tracing_disabled=True, appsec_enabled=True) + assert asm_config._apm_opt_out + with mock.patch( + "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError + ) as m_make_request: + with pytest.raises(ValueError): + self.http.request("GET", URL_200) + + spans = self.pop_spans() + s = spans[0] + expected_headers = { + "x-datadog-trace-id": str(s._trace_id_64bits), + "x-datadog-parent-id": str(s.span_id), + "x-datadog-sampling-priority": "1", + "x-datadog-tags": "_dd.p.dm=-0,_dd.p.tid={}".format(_get_64_highest_order_bits_as_hex(s.trace_id)), + "traceparent": s.context._traceparent, + # outgoing headers must contain last parent span id in tracestate + "tracestate": s.context._tracestate.replace("dd=", "dd=p:{:016x};".format(s.span_id)), + } + + if int(urllib3.__version__.split(".")[0]) >= 2: + m_make_request.assert_called_with( + mock.ANY, + "GET", + "/status/200", + body=None, + chunked=mock.ANY, + headers=expected_headers, + timeout=mock.ANY, + retries=mock.ANY, + response_conn=mock.ANY, + preload_content=mock.ANY, + decode_content=mock.ANY, + ) + else: + m_make_request.assert_called_with( + mock.ANY, + "GET", + "/status/200", + body=None, + chunked=mock.ANY, + headers=expected_headers, + timeout=mock.ANY, + ) + + def test_distributed_tracing_apm_opt_out_false(self): + """Test with distributed tracing disabled does not propagate the headers""" + config.urllib3["distributed_tracing"] = True + # Ensure the ASM SpanProcessor is set. + self.tracer.configure(apm_tracing_disabled=False, appsec_enabled=True) + self.tracer.enabled = False + assert not asm_config._apm_opt_out + with mock.patch( + "urllib3.connectionpool.HTTPConnectionPool._make_request", side_effect=ValueError + ) as m_make_request: + with pytest.raises(ValueError): + self.http.request("GET", URL_200) + + if int(urllib3.__version__.split(".")[0]) >= 2: + m_make_request.assert_called_with( + mock.ANY, + "GET", + "/status/200", + body=None, + chunked=mock.ANY, + headers={}, + timeout=mock.ANY, + retries=mock.ANY, + response_conn=mock.ANY, + preload_content=mock.ANY, + decode_content=mock.ANY, + ) + else: + m_make_request.assert_called_with( + mock.ANY, + "GET", + "/status/200", + body=None, + chunked=mock.ANY, + headers={}, + timeout=mock.ANY, + ) diff --git a/tests/telemetry/test_telemetry_appsec_metrics.py b/tests/telemetry/test_telemetry_appsec_metrics.py new file mode 100644 index 00000000000..3fe35f6c185 --- /dev/null +++ b/tests/telemetry/test_telemetry_appsec_metrics.py @@ -0,0 +1,119 @@ +from ddtrace.internal.telemetry.constants import TELEMETRY_EVENT_TYPE +from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE + + +def _assert_metric( + test_agent, + expected_metrics, + namespace=TELEMETRY_NAMESPACE.TRACERS, + type_payload=TELEMETRY_EVENT_TYPE.METRICS, +): + assert len(expected_metrics) > 0, "expected_metrics should not be empty" + test_agent.telemetry_writer.periodic(force_flush=True) + metrics_events = test_agent.get_events(type_payload.value) + assert len(metrics_events) > 0, "captured metrics events should not be empty" + + metrics = [] + for event in metrics_events: + if event["payload"]["namespace"] == namespace.value: + for metric in event["payload"]["series"]: + metric["tags"].sort() + metrics.append(metric) + + for expected_metric in expected_metrics: + expected_metric["tags"].sort() + assert expected_metric in metrics + + +def test_send_appsec_rate_metric(telemetry_writer, test_agent_session, mock_time): + telemetry_writer.add_rate_metric( + TELEMETRY_NAMESPACE.APPSEC, + "test-metric", + 6, + (("hi", "HELLO"), ("NAME", "CANDY")), + ) + telemetry_writer.add_rate_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) + telemetry_writer.add_rate_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) + + expected_series = [ + { + "common": True, + "interval": 10, + "metric": "test-metric", + "points": [[1642544540, 0.6]], + "tags": ["hi:hello", "name:candy"], + "type": "rate", + }, + { + "common": True, + "interval": 10, + "metric": "test-metric", + "points": [[1642544540, 1.2]], + "tags": [], + "type": "rate", + }, + ] + + _assert_metric(test_agent_session, expected_series, namespace=TELEMETRY_NAMESPACE.APPSEC) + + +def test_send_appsec_gauge_metric(telemetry_writer, test_agent_session, mock_time): + telemetry_writer.add_gauge_metric( + TELEMETRY_NAMESPACE.APPSEC, + "test-metric", + 5, + ( + ("hi", "HELLO"), + ("NAME", "CANDY"), + ), + ) + telemetry_writer.add_gauge_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 5, (("a", "b"),)) + telemetry_writer.add_gauge_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) + + expected_series = [ + { + "common": True, + "interval": 10, + "metric": "test-metric", + "points": [[1642544540, 5.0]], + "tags": ["hi:hello", "name:candy"], + "type": "gauge", + }, + { + "common": True, + "interval": 10, + "metric": "test-metric", + "points": [[1642544540, 5.0]], + "tags": ["a:b"], + "type": "gauge", + }, + { + "common": True, + "interval": 10, + "metric": "test-metric", + "points": [[1642544540, 6.0]], + "tags": [], + "type": "gauge", + }, + ] + _assert_metric(test_agent_session, expected_series, namespace=TELEMETRY_NAMESPACE.APPSEC) + + +def test_send_appsec_distributions_metric(telemetry_writer, test_agent_session, mock_time): + telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 4, tuple()) + telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 5, tuple()) + telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) + + expected_series = [ + { + "metric": "test-metric", + "points": [4.0, 5.0, 6.0], + "tags": [], + } + ] + _assert_metric( + test_agent_session, + expected_series, + namespace=TELEMETRY_NAMESPACE.APPSEC, + type_payload=TELEMETRY_EVENT_TYPE.DISTRIBUTIONS, + ) diff --git a/tests/telemetry/test_telemetry_metrics.py b/tests/telemetry/test_telemetry_metrics.py index c9d6272e751..eaedb98c9ce 100644 --- a/tests/telemetry/test_telemetry_metrics.py +++ b/tests/telemetry/test_telemetry_metrics.py @@ -200,100 +200,6 @@ def test_send_tracers_count_metric(telemetry_writer, test_agent_session, mock_ti _assert_metric(test_agent_session, expected_series) -def test_send_appsec_rate_metric(telemetry_writer, test_agent_session, mock_time): - telemetry_writer.add_rate_metric( - TELEMETRY_NAMESPACE.APPSEC, - "test-metric", - 6, - (("hi", "HELLO"), ("NAME", "CANDY")), - ) - telemetry_writer.add_rate_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) - telemetry_writer.add_rate_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) - - expected_series = [ - { - "common": True, - "interval": 10, - "metric": "test-metric", - "points": [[1642544540, 0.6]], - "tags": ["hi:hello", "name:candy"], - "type": "rate", - }, - { - "common": True, - "interval": 10, - "metric": "test-metric", - "points": [[1642544540, 1.2]], - "tags": [], - "type": "rate", - }, - ] - - _assert_metric(test_agent_session, expected_series, namespace=TELEMETRY_NAMESPACE.APPSEC) - - -def test_send_appsec_gauge_metric(telemetry_writer, test_agent_session, mock_time): - telemetry_writer.add_gauge_metric( - TELEMETRY_NAMESPACE.APPSEC, - "test-metric", - 5, - ( - ("hi", "HELLO"), - ("NAME", "CANDY"), - ), - ) - telemetry_writer.add_gauge_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 5, (("a", "b"),)) - telemetry_writer.add_gauge_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) - - expected_series = [ - { - "common": True, - "interval": 10, - "metric": "test-metric", - "points": [[1642544540, 5.0]], - "tags": ["hi:hello", "name:candy"], - "type": "gauge", - }, - { - "common": True, - "interval": 10, - "metric": "test-metric", - "points": [[1642544540, 5.0]], - "tags": ["a:b"], - "type": "gauge", - }, - { - "common": True, - "interval": 10, - "metric": "test-metric", - "points": [[1642544540, 6.0]], - "tags": [], - "type": "gauge", - }, - ] - _assert_metric(test_agent_session, expected_series, namespace=TELEMETRY_NAMESPACE.APPSEC) - - -def test_send_appsec_distributions_metric(telemetry_writer, test_agent_session, mock_time): - telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 4, tuple()) - telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 5, tuple()) - telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 6, tuple()) - - expected_series = [ - { - "metric": "test-metric", - "points": [4.0, 5.0, 6.0], - "tags": [], - } - ] - _assert_metric( - test_agent_session, - expected_series, - namespace=TELEMETRY_NAMESPACE.APPSEC, - type_payload=TELEMETRY_EVENT_TYPE.DISTRIBUTIONS, - ) - - def test_send_metric_flush_and_distributions_series_is_restarted(telemetry_writer, test_agent_session, mock_time): """Check the queue of metrics is empty after run periodic method of PeriodicService""" telemetry_writer.add_distribution_metric(TELEMETRY_NAMESPACE.APPSEC, "test-metric", 4, tuple()) diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index a096a4fdf8f..8b0970befc5 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -680,9 +680,9 @@ def test_app_client_configuration_changed_event(telemetry_writer, test_agent_ses telemetry_writer.periodic(force_flush=True) """asserts that queuing a configuration sends a valid telemetry request""" with override_global_config(dict()): - telemetry_writer.add_configuration("appsec_enabled", True, "env_var") + telemetry_writer.add_configuration("product_enabled", True, "env_var") telemetry_writer.add_configuration("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "datadog", "default") - telemetry_writer.add_configuration("appsec_enabled", False, "code") + telemetry_writer.add_configuration("product_enabled", False, "code") telemetry_writer.periodic(force_flush=True) @@ -695,13 +695,13 @@ def test_app_client_configuration_changed_event(telemetry_writer, test_agent_ses < received_configurations[2]["seq_id"] ) # assert that all configuration values are sent to the agent in the order they were added (by seq_id) - assert received_configurations[0]["name"] == "appsec_enabled" + assert received_configurations[0]["name"] == "product_enabled" assert received_configurations[0]["origin"] == "env_var" assert received_configurations[0]["value"] is True assert received_configurations[1]["name"] == "DD_TRACE_PROPAGATION_STYLE_EXTRACT" assert received_configurations[1]["origin"] == "default" assert received_configurations[1]["value"] == "datadog" - assert received_configurations[2]["name"] == "appsec_enabled" + assert received_configurations[2]["name"] == "product_enabled" assert received_configurations[2]["origin"] == "code" assert received_configurations[2]["value"] is False