Skip to content

Commit

Permalink
Merge branch 'main' into taegyunkim/endpoint-test
Browse files Browse the repository at this point in the history
  • Loading branch information
taegyunkim authored Sep 20, 2024
2 parents 713dae6 + 3fc23d7 commit 6060251
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/system-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,15 @@ jobs:
with:
name: logs_parametric
path: artifact.tar.gz

finished:
runs-on: ubuntu-latest
needs: [parametric, system-tests]
if: success() || failure()
steps:
- name: True when everything else succeeded
if: needs.parametric.result == 'success' && needs.system-tests.result == 'success'
run: exit 0
- name: Fails if anything else failed
if: needs.parametric.result != 'success' || needs.system-tests.result != 'success'
run: exit 1
Empty file.
101 changes: 101 additions & 0 deletions ddtrace/_trace/utils_botocore/span_pointers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from typing import Any
from typing import Dict
from typing import List

from ddtrace._trace._span_pointer import _SpanPointerDescription
from ddtrace._trace._span_pointer import _SpanPointerDirection
from ddtrace._trace._span_pointer import _standard_hashing_function
from ddtrace.internal.logger import get_logger


log = get_logger(__name__)


def extract_span_pointers_from_successful_botocore_response(
endpoint_name: str,
operation_name: str,
request_parameters: Dict[str, Any],
response: Dict[str, Any],
) -> List[_SpanPointerDescription]:
if endpoint_name == "s3":
return _extract_span_pointers_for_s3_response(operation_name, request_parameters, response)

return []


def _extract_span_pointers_for_s3_response(
operation_name: str,
request_parameters: Dict[str, Any],
response: Dict[str, Any],
) -> List[_SpanPointerDescription]:
if operation_name == "PutObject":
return _extract_span_pointers_for_s3_put_object_response(request_parameters, response)

return []


def _extract_span_pointers_for_s3_put_object_response(
request_parameters: Dict[str, Any],
response: Dict[str, Any],
) -> List[_SpanPointerDescription]:
# Endpoint Reference:
# https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html

try:
bucket = request_parameters["Bucket"]
key = request_parameters["Key"]
etag = response["ETag"]

# The ETag is surrounded by double quotes for some reason.
if etag.startswith('"') and etag.endswith('"'):
etag = etag[1:-1]

except KeyError as e:
log.warning(
"missing a parameter or response field required to make span pointer for S3.PutObject: %s",
str(e),
)
return []

try:
return [
_aws_s3_object_span_pointer_description(
pointer_direction=_SpanPointerDirection.DOWNSTREAM,
bucket=bucket,
key=key,
etag=etag,
)
]
except Exception as e:
log.warning(
"failed to generate S3.PutObject span pointer: %s",
str(e),
)
return []


def _aws_s3_object_span_pointer_description(
pointer_direction: _SpanPointerDirection,
bucket: str,
key: str,
etag: str,
) -> _SpanPointerDescription:
return _SpanPointerDescription(
pointer_kind="aws.s3.object",
pointer_direction=pointer_direction,
pointer_hash=_aws_s3_object_span_pointer_hash(bucket, key, etag),
extra_attributes={},
)


def _aws_s3_object_span_pointer_hash(bucket: str, key: str, etag: str) -> str:
if '"' in etag:
# Some AWS API endpoints put the ETag in double quotes. We expect the
# calling code to have correctly fixed this already.
raise ValueError(f"ETag should not have double quotes: {etag}")

return _standard_hashing_function(
bucket.encode("ascii"),
key.encode("utf-8"),
etag.encode("ascii"),
)
3 changes: 3 additions & 0 deletions ddtrace/internal/core/crashtracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ def start() -> bool:
if not is_available:
return False

import platform

crashtracker.set_url(crashtracker_config.debug_url or agent.get_trace_url())
crashtracker.set_service(config.service)
crashtracker.set_version(config.version)
crashtracker.set_env(config.env)
crashtracker.set_runtime_id(get_runtime_id())
crashtracker.set_runtime_version(platform.python_version())
crashtracker.set_library_version(version.get_version())
crashtracker.set_alt_stack(bool(crashtracker_config.alt_stack))
crashtracker.set_wait_for_receiver(bool(crashtracker_config.wait_for_receiver))
Expand Down
3 changes: 3 additions & 0 deletions tests/profiling/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from tests.utils import call_program
from tests.utils import flaky

from . import utils

Expand Down Expand Up @@ -105,6 +106,8 @@ def test_multiprocessing(method, tmp_path, monkeypatch):
utils.check_pprof_file(filename + "." + str(child_pid) + ".1")


@flaky(1731959126) # Marking as flaky so it will show up in flaky reports
@pytest.mark.skipif(os.environ.get("GITLAB_CI") == "true", reason="Hanging and failing in GitLab CI")
@pytest.mark.subprocess(
ddtrace_run=True,
env=dict(DD_PROFILING_ENABLED="1"),
Expand Down
Empty file.
218 changes: 218 additions & 0 deletions tests/tracer/utils_botocore/test_span_pointers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import logging
import re
from typing import List
from typing import NamedTuple
from typing import Optional

import mock
import pytest

from ddtrace._trace._span_pointer import _SpanPointerDescription
from ddtrace._trace._span_pointer import _SpanPointerDirection
from ddtrace._trace.utils_botocore.span_pointers import _aws_s3_object_span_pointer_hash
from ddtrace._trace.utils_botocore.span_pointers import extract_span_pointers_from_successful_botocore_response


class TestS3ObjectPointer:
class HashingCase(NamedTuple):
name: str
bucket: str
key: str
etag: str
pointer_hash: str

@pytest.mark.parametrize(
"hashing_case",
[
HashingCase(
name="a basic S3 object",
bucket="some-bucket",
key="some-key.data",
etag="ab12ef34",
pointer_hash="e721375466d4116ab551213fdea08413",
),
HashingCase(
name="an S3 object with a non-ascii key",
bucket="some-bucket",
key="some-key.你好",
etag="ab12ef34",
pointer_hash="d1333a04b9928ab462b5c6cadfa401f4",
),
HashingCase(
name="a multipart-uploaded S3 object",
bucket="some-bucket",
key="some-key.data",
etag="ab12ef34-5",
pointer_hash="2b90dffc37ebc7bc610152c3dc72af9f",
),
],
ids=lambda case: case.name,
)
def test_hashing(self, hashing_case: HashingCase) -> None:
assert (
_aws_s3_object_span_pointer_hash(
bucket=hashing_case.bucket,
key=hashing_case.key,
etag=hashing_case.etag,
)
== hashing_case.pointer_hash
)


class TestBotocoreSpanPointers:
class PointersCase(NamedTuple):
name: str
endpoint_name: str
operation_name: str
request_parameters: dict
response: dict
expected_pointers: List[_SpanPointerDescription]
expected_warning_regex: Optional[str]

@pytest.mark.parametrize(
"pointers_case",
[
PointersCase(
name="unknown endpoint",
endpoint_name="unknown",
operation_name="does not matter",
request_parameters={},
response={},
expected_pointers=[],
expected_warning_regex=None,
),
PointersCase(
name="unknown s3 operation",
endpoint_name="s3",
operation_name="unknown",
request_parameters={},
response={},
expected_pointers=[],
expected_warning_regex=None,
),
PointersCase(
name="malformed s3.PutObject, missing bucket",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Key": "some-key.data",
},
response={
"ETag": "ab12ef34",
},
expected_pointers=[],
expected_warning_regex=r"missing a parameter or response field .*: 'Bucket'",
),
PointersCase(
name="malformed s3.PutObject, missing key",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Bucket": "some-bucket",
},
response={
"ETag": "ab12ef34",
},
expected_pointers=[],
expected_warning_regex=r"missing a parameter or response field .*: 'Key'",
),
PointersCase(
name="malformed s3.PutObject, missing etag",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Bucket": "some-bucket",
"Key": "some-key.data",
},
response={},
expected_pointers=[],
expected_warning_regex=r"missing a parameter or response field .*: 'ETag'",
),
PointersCase(
name="malformed s3.PutObject, impossible non-ascii bucket",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Bucket": "some-bucket-你好",
"Key": "some-key.data",
},
response={
"ETag": "ab12ef34",
},
expected_pointers=[],
expected_warning_regex=r".*'ascii' codec can't encode characters.*",
),
PointersCase(
name="s3.PutObject",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Bucket": "some-bucket",
"Key": "some-key.data",
},
response={
"ETag": "ab12ef34",
},
expected_pointers=[
_SpanPointerDescription(
pointer_kind="aws.s3.object",
pointer_direction=_SpanPointerDirection.DOWNSTREAM,
pointer_hash="e721375466d4116ab551213fdea08413",
extra_attributes={},
),
],
expected_warning_regex=None,
),
PointersCase(
name="s3.PutObject with double quoted ETag",
endpoint_name="s3",
operation_name="PutObject",
request_parameters={
"Bucket": "some-bucket",
"Key": "some-key.data",
},
response={
# the ETag can be surrounded by double quotes
"ETag": '"ab12ef34"',
},
expected_pointers=[
_SpanPointerDescription(
pointer_kind="aws.s3.object",
pointer_direction=_SpanPointerDirection.DOWNSTREAM,
pointer_hash="e721375466d4116ab551213fdea08413",
extra_attributes={},
),
],
expected_warning_regex=None,
),
],
ids=lambda case: case.name,
)
def test_pointers(self, pointers_case: PointersCase) -> None:
# We might like to use caplog here but it resulted in inconsistent test
# behavior, so we have to go a bit deeper.

with mock.patch.object(logging.Logger, "warning") as mock_logger:
assert (
extract_span_pointers_from_successful_botocore_response(
endpoint_name=pointers_case.endpoint_name,
operation_name=pointers_case.operation_name,
request_parameters=pointers_case.request_parameters,
response=pointers_case.response,
)
== pointers_case.expected_pointers
)

if pointers_case.expected_warning_regex is None:
mock_logger.assert_not_called()

else:
mock_logger.asser_called_once()

(args, kwargs) = mock_logger.call_args
assert not kwargs
fmt, other_args = args
assert re.match(
pointers_case.expected_warning_regex,
fmt % other_args,
)

0 comments on commit 6060251

Please sign in to comment.