From f5a1cb7b58edbcccd26c3bc01cc921299ed3cdff Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Wed, 13 Nov 2024 07:04:03 -0800 Subject: [PATCH] add smoke test for crash logs from python [INPLAT-36] (#3243) Co-authored-by: Tony Hsu Co-authored-by: brettlangdon Co-authored-by: Brett Langdon --- .../django_app.py | 7 +++ manifests/dd_apm_inject.yml | 4 +- tests/docker_ssi/test_docker_ssi_crash.py | 49 +++++++++++++++++++ utils/_context/_scenarios/docker_ssi.py | 10 ++-- utils/_features.py | 10 ++++ utils/interfaces/_test_agent.py | 18 +++++++ 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/docker_ssi/test_docker_ssi_crash.py diff --git a/lib-injection/build/docker/python/dd-lib-python-init-test-django/django_app.py b/lib-injection/build/docker/python/dd-lib-python-init-test-django/django_app.py index 77116f1a3f..ec3d6c447b 100644 --- a/lib-injection/build/docker/python/dd-lib-python-init-test-django/django_app.py +++ b/lib-injection/build/docker/python/dd-lib-python-init-test-django/django_app.py @@ -24,6 +24,13 @@ def index(request): return HttpResponse("test") +def crashme(request): + import ctypes + + ctypes.string_at(0) + + urlpatterns = [ path("", index), + path("crashme", crashme), ] diff --git a/manifests/dd_apm_inject.yml b/manifests/dd_apm_inject.yml index 7fef50e18c..7bf0e79fc8 100644 --- a/manifests/dd_apm_inject.yml +++ b/manifests/dd_apm_inject.yml @@ -1,4 +1,6 @@ tests/: docker_ssi/: test_docker_ssi.py: - TestDockerSSIFeatures: v0.19.1 \ No newline at end of file + TestDockerSSIFeatures: v0.19.1 + test_docker_ssi_crash.py: + TestDockerSSICrash: v0.19.1 diff --git a/tests/docker_ssi/test_docker_ssi_crash.py b/tests/docker_ssi/test_docker_ssi_crash.py new file mode 100644 index 0000000000..0e659b3ca5 --- /dev/null +++ b/tests/docker_ssi/test_docker_ssi_crash.py @@ -0,0 +1,49 @@ +from urllib.parse import urlparse + +from utils import ( + bug, + scenarios, + features, + context, + interfaces, +) +from utils import weblog +from utils.tools import logger + + +@scenarios.docker_ssi +class TestDockerSSICrash: + """Test the ssi in a simulated host injection environment (docker container + test agent) + We test scenarios when the application crashes and sends a crash report.""" + + _r = None + + def setup_crash(self): + if TestDockerSSICrash._r is None: + parsed_url = urlparse(context.scenario.weblog_url + "/crashme") + logger.info(f"Setting up Docker SSI installation WEBLOG_URL {context.scenario.weblog_url}") + TestDockerSSICrash._r = weblog.request( + "GET", parsed_url.path, domain=parsed_url.hostname, port=parsed_url.port + ) + logger.info(f"Setup Docker SSI installation {TestDockerSSICrash._r}") + + self.r = TestDockerSSICrash._r + + @features.ssi_crashtracking + @bug(condition=context.library != "python", reason="INPLAT-11") + def test_crash(self): + """Validate that a crash report is generated when the application crashes""" + logger.info(f"Testing Docker SSI crash tracking: {context.scenario.library.library}") + assert ( + self.r.status_code is None + ), f"Response from request {context.scenario.weblog_url + '/crashme'} was supposed to fail: {self.r}" + + # No traces should have been generated + assert not interfaces.test_agent.get_traces( + self.r + ), f"Traces found for request {context.scenario.weblog_url + '/crashme'}" + + # Crash report should have been generated + crash_reports = interfaces.test_agent.get_crash_reports() + assert crash_reports, "No crash report found" + assert len(crash_reports) == 1, "More than one crash report found" diff --git a/utils/_context/_scenarios/docker_ssi.py b/utils/_context/_scenarios/docker_ssi.py index d1cfd8b569..ebc6bd47fd 100644 --- a/utils/_context/_scenarios/docker_ssi.py +++ b/utils/_context/_scenarios/docker_ssi.py @@ -141,9 +141,13 @@ def fill_context(self, json_tested_components): logger.stdout(f"{key}: {self._tested_components[key]}") def post_setup(self): - logger.stdout("--- Waiting for all traces to be sent to test agent ---") - time.sleep(5) # wait for the traces to be sent to the test agent - interfaces.test_agent.collect_data(f"{self.host_log_folder}/interfaces/test_agent") + logger.stdout("--- Waiting for all traces and telemetry to be sent to test agent ---") + data = None + attempts = 0 + while attempts < 30 and not data: + attempts += 1 + data = interfaces.test_agent.collect_data(f"{self.host_log_folder}/interfaces/test_agent") + time.sleep(5) @property def library(self): diff --git a/utils/_features.py b/utils/_features.py index 0c3f3e15ab..16fc3264ba 100644 --- a/utils/_features.py +++ b/utils/_features.py @@ -2407,6 +2407,16 @@ def ssi_guardrails(test_object): pytest.mark.features(feature_id=322)(test_object) return test_object + @staticmethod + def ssi_crashtracking(test_object): + """ + Docker ssi crashtracking + + https://feature-parity.us1.prod.dog/#/?feature=340 + """ + pytest.mark.features(feature_id=340)(test_object) + return test_object + @staticmethod def ssi_service_naming(test_object): """ diff --git a/utils/interfaces/_test_agent.py b/utils/interfaces/_test_agent.py index 89de7f187c..702e852952 100644 --- a/utils/interfaces/_test_agent.py +++ b/utils/interfaces/_test_agent.py @@ -56,6 +56,11 @@ def get_telemetry_for_runtime(self, runtime_id): return telemetry_msgs + def get_crashlog_for_runtime(self, runtime_id): + logger.debug(f"Try to find a crashlog related to runtime-id {runtime_id}") + assert runtime_id is not None, "Runtime ID not found" + return [l for l in self.get_telemetry_logs() if l["runtime_id"] == runtime_id] + def get_telemetry_for_autoinject(self): logger.debug("Try to find telemetry data related to autoinject") injection_metrics = [] @@ -79,3 +84,16 @@ def get_telemetry_for_autoinject_library_entrypoint(self): if str(series["metric"]).startswith("library_entrypoint.") ] return injection_metrics + + def get_telemetry_logs(self): + logger.debug("Try to find telemetry data related to logs") + return [t for t in self._data_telemetry_list if t["request_type"] == "logs"] + + def get_crash_reports(self): + logger.debug("Try to find telemetry data related to crash reports") + return [ + p + for t in self.get_telemetry_logs() + for p in t["payload"] + if "signame" in p.get("tags", "") or "signum" in p.get("tags", "") + ]