Skip to content

Commit fc0bd21

Browse files
authored
Merge branch 'main' into otel-long-running-schedule
2 parents bfc01cc + edc9cb4 commit fc0bd21

37 files changed

Lines changed: 2675 additions & 10 deletions

.github/scripts/tests/test_opentelemetry_conformance_workflow.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
WORKFLOW_PATH = (
55
Path(__file__).parents[2] / "workflows" / "opentelemetry-conformance-tests.yml"
66
)
7-
ORCHESTRATOR_REVISION = "397d523d01bdf97ff8461ab749ddaa445bbf67ca"
7+
ORCHESTRATOR_REVISION = "8de1bbfdccfeb36d5f9ccee99f21533f0bcb1d72"
8+
EXAMPLES_DIR = (
9+
".build/durable-sdk/packages/aws-durable-execution-sdk-python-conformance-tests-otel"
10+
)
811

912

1013
def test_opentelemetry_conformance_caller_uses_current_workflow_contract() -> None:
@@ -26,9 +29,8 @@ def test_opentelemetry_conformance_caller_uses_current_workflow_contract() -> No
2629
"sdk_repository: aws/aws-durable-execution-sdk-python",
2730
"sdk_ref: ${{ github.event.pull_request.head.sha || github.sha }}",
2831
"conformance_test_ref: ${{ inputs.conformance_test_ref || 'main' }}",
29-
"checkout_sdk: false",
30-
"packages/aws-durable-execution-conformance-tests-otel/"
31-
"tests/test_python_examples.py",
32+
"checkout_sdk: true",
33+
f"examples_dir: {EXAMPLES_DIR}",
3234
"adot_release_repository: aws-observability/aws-otel-python-instrumentation",
3335
"collector_compatible_runtime: python3.13",
3436
"collector_otlp_endpoint: http://localhost:4318",
@@ -50,3 +52,23 @@ def test_opentelemetry_conformance_caller_uses_current_workflow_contract() -> No
5052
"DATADOG_OTLP_HEADERS",
5153
):
5254
assert f"{obsolete_secret_name}:" not in workflow
55+
56+
57+
def test_opentelemetry_conformance_handlers_come_from_this_repository() -> None:
58+
workflow = WORKFLOW_PATH.read_text()
59+
60+
# The handlers live here now, so the conformance repository's bundled Python
61+
# example project and its contract test no longer take part in the run.
62+
assert "contract_test_command" not in workflow
63+
assert "packages/aws-durable-execution-conformance-tests-otel/" not in workflow
64+
65+
66+
def test_opentelemetry_conformance_runs_when_the_handlers_change() -> None:
67+
workflow = WORKFLOW_PATH.read_text()
68+
69+
trigger_path = (
70+
" - "
71+
'"packages/aws-durable-execution-sdk-python-conformance-tests-otel/**"'
72+
)
73+
# Once for pull_request, once for push.
74+
assert workflow.count(trigger_path) == 2

.github/workflows/opentelemetry-conformance-tests.yml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
name: OpenTelemetry Conformance Tests
22

3+
# Per-PR OpenTelemetry conformance run. Calls the shared orchestrator in
4+
# aws-durable-execution-conformance-tests and points it at this repo's own handlers and
5+
# templates (packages/aws-durable-execution-sdk-python-conformance-tests-otel/) through
6+
# examples_dir, so the handlers run from the commit under test while the orchestration
7+
# (backend matrix, ADOT layer, collector build, long-running cycle) stays centralized.
8+
39
on:
410
pull_request:
511
branches: ["main"]
612
paths:
713
- "packages/aws-durable-execution-sdk-python/**"
814
- "packages/aws-durable-execution-sdk-python-otel/**"
15+
- "packages/aws-durable-execution-sdk-python-conformance-tests-otel/**"
916
- ".github/workflows/opentelemetry-conformance-tests.yml"
1017
push:
1118
branches: ["main"]
1219
paths:
1320
- "packages/aws-durable-execution-sdk-python/**"
1421
- "packages/aws-durable-execution-sdk-python-otel/**"
22+
- "packages/aws-durable-execution-sdk-python-conformance-tests-otel/**"
1523
- ".github/workflows/opentelemetry-conformance-tests.yml"
1624
schedule:
1725
- cron: "0 7 * * *"
@@ -50,17 +58,19 @@ jobs:
5058
actions: write
5159
contents: read
5260
id-token: write
53-
uses: aws/aws-durable-execution-conformance-tests/.github/workflows/opentelemetry-orchestrator.yml@397d523d01bdf97ff8461ab749ddaa445bbf67ca
61+
uses: aws/aws-durable-execution-conformance-tests/.github/workflows/opentelemetry-orchestrator.yml@8de1bbfdccfeb36d5f9ccee99f21533f0bcb1d72
5462
with:
5563
language: python
5664
resource_prefix: p
5765
sdk_repository: aws/aws-durable-execution-sdk-python
5866
sdk_ref: ${{ github.event.pull_request.head.sha || github.sha }}
5967
conformance_test_ref: ${{ inputs.conformance_test_ref || 'main' }}
60-
checkout_sdk: false
61-
contract_test_command: >-
62-
hatch run test:all
63-
packages/aws-durable-execution-conformance-tests-otel/tests/test_python_examples.py
68+
# Check the SDK out so the handlers and templates below are on disk. The handlers
69+
# themselves are installed from sdk_ref by src/requirements.txt during the SAM build.
70+
checkout_sdk: true
71+
# Path is relative to the conformance workspace, where the SDK is checked out at
72+
# .build/durable-sdk.
73+
examples_dir: .build/durable-sdk/packages/aws-durable-execution-sdk-python-conformance-tests-otel
6474
adot_release_repository: aws-observability/aws-otel-python-instrumentation
6575
collector_compatible_runtime: python3.13
6676
collector_otlp_endpoint: http://localhost:4318

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ for worked examples.
5858

5959
All changes MUST include related tests. At minimum, include **unit tests**. Include **e2e integration tests** (in the `tests/e2e/` directory) when the change affects cross-component behavior, public API surfaces, or end-to-end workflows. For isolated bug fixes where a unit test alone sufficiently covers the fix, integration tests are not required.
6060

61-
Do NOT add or modify **conformance tests** without coordinating with the team. Conformance test requirements and the runner live in a separate repository ([aws-durable-execution-conformance-tests](https://github.com/aws/aws-durable-execution-conformance-tests)). The Python handlers live in-repo under `packages/aws-durable-execution-sdk-python-conformance-tests/`, but new requirement IDs must first be registered upstream. If a change warrants a new conformance test, note it in the PR description or [open an issue](https://github.com/aws/aws-durable-execution-conformance-tests/issues/new?template=new_requirement.yml) in that repository.
61+
Do NOT add or modify **conformance tests** without coordinating with the team. Conformance test requirements and the runner live in a separate repository ([aws-durable-execution-conformance-tests](https://github.com/aws/aws-durable-execution-conformance-tests)). If a change warrants a new conformance test, note it in the PR description or [open an issue](https://github.com/aws/aws-durable-execution-conformance-tests/issues/new?template=new_requirement.yml) in that repository.
6262

6363
## Working in this repository
6464

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
build/
2+
dist/
3+
.aws-sam/
4+
history-*/
5+
report-*.xml
6+
report-*.json
7+
__pycache__/
8+
*.pyc
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Durable Execution Python SDK - OpenTelemetry Conformance Tests
2+
3+
OpenTelemetry **conformance test handlers** for the Durable Execution Python SDK
4+
and its OpenTelemetry plugin:
5+
6+
- [`aws-durable-execution-sdk-python`](https://pypi.org/project/aws-durable-execution-sdk-python/)
7+
- [`aws-durable-execution-sdk-python-otel`](https://pypi.org/project/aws-durable-execution-sdk-python-otel/)
8+
9+
The handlers deploy as AWS Lambda functions and are exercised by the
10+
language-agnostic OTel conformance runner in
11+
[`aws/aws-durable-execution-conformance-tests`](https://github.com/aws/aws-durable-execution-conformance-tests),
12+
which invokes each function, collects its spans from the configured backend, and
13+
asserts they match the shared requirement specification. That repository owns the
14+
runner, the requirement specifications, and the orchestration (backend matrix,
15+
ADOT layer resolution, collector build, long-running cycle); this package owns
16+
the Python handlers and SAM templates.
17+
18+
This mirrors the non-OTel
19+
[`aws-durable-execution-sdk-python-conformance-tests`](../aws-durable-execution-sdk-python-conformance-tests)
20+
package: handlers live next to the SDK so a PR runs them against its own commit.
21+
22+
## Layout
23+
24+
```
25+
src/
26+
common.py # plugin selection and input validation
27+
otel_<n>_<name>.py # one module per invocation/execution scenario
28+
otel_long_running_<n>_<name>.py
29+
Makefile # SAM makefile build for every function
30+
requirements.txt # SDK + OTel plugin, resolved from PYTHON_SDK_REF
31+
template.yaml # otel-invocation and otel-execution suites
32+
template-long-running.yaml # otel-long-running suite
33+
tests/ # contract tests for the templates and handlers
34+
```
35+
36+
The 20 invocation and 20 execution requirements reuse the same scenario
37+
handlers; the view is selected per function through the `OTEL_PLUGIN_MODE`
38+
environment variable, which `common.otel_plugin()` reads to pick
39+
`InvocationOtelPlugin` or `ExecutionOtelPlugin`. `template.yaml` deploys only the
40+
view named by its `OtelSuite` parameter.
41+
42+
## Scenarios
43+
44+
| Requirement | Handler | Behavior |
45+
|---|---|---|
46+
| `otel-invocation-1` | `otel_1_success.handler` | Verifies every successful step and attempt span. |
47+
| `otel-invocation-2` | `otel_2_wait_resume.handler` | Verifies every wait, resume, and post-resume step span. |
48+
| `otel-invocation-3` | `otel_3_retry.handler` | Verifies failed and successful retry attempts across invocations. |
49+
| `otel-invocation-4` | `otel_4_terminal_failure.handler` | Verifies complete telemetry for a terminal execution failure. |
50+
| `otel-invocation-5` | `otel_5_child_context.handler` | Verifies every child-context and nested-step span. |
51+
| `otel-invocation-6` | `otel_6_parallel.handler` | Verifies every parallel context, branch, step, and attempt span. |
52+
| `otel-invocation-7` | `otel_7_map.handler` | Verifies every map context, iteration, step, and attempt span. |
53+
| `otel-invocation-8` | `otel_8_handled_failure.handler` | Verifies complete failed-step and recovery telemetry. |
54+
| `otel-invocation-9` | `otel_9_wait_for_condition.handler` | Verifies every condition polling attempt and continuation. |
55+
| `otel-invocation-10` | `otel_10_wait_for_callback.handler` | Verifies callback context, callback, and submitter spans. |
56+
| `otel-invocation-11` | `otel_11_chained_invoke.handler` | Verifies chained-invoke continuation spans. |
57+
| `otel-invocation-12` | `otel_12_child_context_failure.handler` | Verifies a failed child-context span. |
58+
| `otel-invocation-13` | `otel_13_parallel_failure.handler` | Verifies failed parallel-branch telemetry. |
59+
| `otel-invocation-14` | `otel_14_map_failure.handler` | Verifies failed map-iteration telemetry. |
60+
| `otel-invocation-15` | `otel_15_wait_interrupted.handler` | Verifies an interrupted wait when execution times out. |
61+
| `otel-invocation-16` | `otel_16_wait_for_condition_failure.handler` | Verifies failed condition-check telemetry. |
62+
| `otel-invocation-17` | `otel_17_wait_for_callback_failure.handler` | Verifies external callback-failure telemetry. |
63+
| `otel-invocation-18` | `otel_18_chained_invoke_failure.handler` | Verifies failed chained-invoke telemetry. |
64+
| `otel-invocation-19` | `otel_19_execution_failure.handler` | Verifies telemetry for a direct handler failure. |
65+
| `otel-invocation-20` | `otel_20_virtual_context.handler` | Verifies a virtual child-context span without context checkpoints. |
66+
| `otel-execution-1` | `otel_1_success.handler` | Verifies the execution-view workflow, step, and attempt hierarchy. |
67+
| `otel-execution-2` | `otel_2_wait_resume.handler` | Verifies the execution view across a resumed invocation. |
68+
| `otel-execution-3` | `otel_3_retry.handler` | Verifies the execution view across retry attempts. |
69+
| `otel-execution-4` | `otel_4_terminal_failure.handler` | Verifies the failed workflow, step, and attempt hierarchy. |
70+
| `otel-execution-5` | `otel_5_child_context.handler` | Verifies child-context and nested-step parentage. |
71+
| `otel-execution-6` | `otel_6_parallel.handler` | Verifies parallel context, branch, step, and attempt parentage. |
72+
| `otel-execution-7` | `otel_7_map.handler` | Verifies map context, iteration, step, and attempt parentage. |
73+
| `otel-execution-8` | `otel_8_handled_failure.handler` | Verifies failed and recovery operations under a successful workflow. |
74+
| `otel-execution-9` | `otel_9_wait_for_condition.handler` | Verifies condition polling attempts across invocations. |
75+
| `otel-execution-10` | `otel_10_wait_for_callback.handler` | Verifies callback, submitter, and attempt parentage. |
76+
| `otel-execution-11` | `otel_11_chained_invoke.handler` | Verifies source and target workflow roots for a chained invoke. |
77+
| `otel-execution-12` | `otel_12_child_context_failure.handler` | Verifies a failed child context under a failed workflow. |
78+
| `otel-execution-13` | `otel_13_parallel_failure.handler` | Verifies a failed parallel branch under its operation. |
79+
| `otel-execution-14` | `otel_14_map_failure.handler` | Verifies a failed map iteration under its operation. |
80+
| `otel-execution-15` | `otel_15_wait_interrupted.handler` | Verifies a pending invocation when workflow spans do not complete. |
81+
| `otel-execution-16` | `otel_16_wait_for_condition_failure.handler` | Verifies a failed condition operation and attempt. |
82+
| `otel-execution-17` | `otel_17_wait_for_callback_failure.handler` | Verifies failed callback telemetry under one workflow. |
83+
| `otel-execution-18` | `otel_18_chained_invoke_failure.handler` | Verifies source and target failed workflow roots. |
84+
| `otel-execution-19` | `otel_19_execution_failure.handler` | Verifies a failed invocation without a completed workflow. |
85+
| `otel-execution-20` | `otel_20_virtual_context.handler` | Verifies a virtual child-context span under the workflow root. |
86+
| `otel-long-running-1` | `otel_long_running_1_wait.handler` | Verifies wait and resume telemetry across a long durable suspension. |
87+
| `otel-long-running-2` | `otel_long_running_2_retry.handler` | Verifies retry telemetry across a long durable backoff. |
88+
| `otel-long-running-3` | `otel_long_running_3_callback.handler` | Verifies callback telemetry when completion arrives after a long delay. |
89+
| `otel-long-running-4` | `otel_long_running_4_chained_invoke.handler` | Verifies chained-invoke telemetry while the target stays suspended. |
90+
91+
The runner discovers each mapping from `TestingMetadata.TestDescription` on the
92+
functions in the templates.
93+
94+
## How a handler maps to a requirement
95+
96+
```yaml
97+
Otel1Success:
98+
Type: AWS::Serverless::Function
99+
Condition: DeployInvocationView
100+
Metadata:
101+
BuildMethod: makefile
102+
TestingMetadata:
103+
TestDescription:
104+
- otel-invocation-1
105+
Properties:
106+
CodeUri: src/
107+
Handler: otel_1_success.handler
108+
FunctionName: !Sub "${AWS::StackName}-otel-invocation-1"
109+
Role: !Ref LambdaExecutionRoleArn
110+
```
111+
112+
## Building
113+
114+
`src/requirements.txt` installs both SDK packages from the single commit in
115+
`PYTHON_SDK_REF`, so every function in a run uses the same core and plugin
116+
revision. The explicit `src/Makefile` build avoids SAM's package metadata
117+
inspection, which does not support Git monorepo subdirectory dependencies, and
118+
resolves binary dependencies for Lambda's `manylinux2014_x86_64` platform when
119+
building from macOS.
120+
121+
```bash
122+
cd packages/aws-durable-execution-sdk-python-conformance-tests-otel
123+
export PYTHON_SDK_REF=$(git rev-parse HEAD) # must be pushed to the SDK remote
124+
sam build --template-file template.yaml
125+
```
126+
127+
## Running a suite
128+
129+
Prerequisites: the AWS SAM CLI, AWS credentials for an account where Durable
130+
Execution is available, and an execution role allowing Durable Execution, logs,
131+
and X-Ray.
132+
133+
```bash
134+
pip install \
135+
aws-durable-execution-conformance-tests \
136+
aws-durable-execution-conformance-tests-otel
137+
138+
durable-execution-conformance \
139+
--template packages/aws-durable-execution-sdk-python-conformance-tests-otel/template.yaml \
140+
--language python \
141+
--suite otel-invocation \
142+
--parameter-overrides \
143+
LambdaExecutionRoleArn=arn:aws:iam::123456789012:role/example \
144+
OtelSuite=otel-invocation \
145+
--otel-exporter adot \
146+
--otel-layer-arn "$ADOT_LAYER_ARN" \
147+
--otel-service-name durable-execution-conformance \
148+
--otel-backend xray
149+
```
150+
151+
Set `ADOT_LAYER_ARN` to the current regional ARN from the
152+
[ADOT Python release](https://github.com/aws-observability/aws-otel-python-instrumentation/releases/latest).
153+
The runner supplies the remaining OTel SAM parameters.
154+
155+
To assert against official OTLP payloads instead of X-Ray, the runner can target
156+
a collector extension that writes OTLP objects to S3:
157+
158+
```bash
159+
durable-execution-conformance \
160+
--template packages/aws-durable-execution-sdk-python-conformance-tests-otel/template.yaml \
161+
--language python \
162+
--suite otel-invocation \
163+
--parameter-overrides \
164+
LambdaExecutionRoleArn=arn:aws:iam::123456789012:role/example \
165+
OtelSuite=otel-invocation \
166+
OtelCollectorLayerArn="$COLLECTOR_LAYER_ARN" \
167+
OtelCollectorBucket="$OTEL_S3_BUCKET" \
168+
OtelCollectorPrefix=traces \
169+
--otel-exporter community \
170+
--otel-endpoint http://localhost:4318 \
171+
--otel-backend collector \
172+
--otel-backend-endpoint "s3://$OTEL_S3_BUCKET/traces"
173+
```
174+
175+
The collector layer is built by the conformance repository's
176+
`collector/build-lambda-layer.sh` and packages its config at
177+
`/opt/collector-config/config-s3.yaml`. The function role needs prefix-scoped S3
178+
write access; the runner identity needs list, read, and cleanup access.
179+
180+
## Authoring a new scenario
181+
182+
1. Find or add the requirement in the conformance repository under
183+
`test-requirements/<suite>/<id>.yaml`. New requirement IDs must be registered
184+
there first.
185+
2. Add `src/otel_<n>_<name>.py` exporting `handler`. Select the plugin with
186+
`common.otel_plugin()` and guard the input with `common.require_scenario()`.
187+
Use the SDK's real API; never hand-roll behavior to force an expected result.
188+
3. Register the function in `template.yaml` (or `template-long-running.yaml`)
189+
with `Handler: <module>.handler` and `TestDescription: ["<id>"]`, and add a
190+
`build-<LogicalId>` target to `src/Makefile`.
191+
4. Update `tests/test_otel_examples.py`, which pins the template-to-requirement
192+
mapping.
193+
194+
## CI
195+
196+
`.github/workflows/opentelemetry-conformance-tests.yml` calls the shared
197+
orchestrator in the conformance repository and points it at this package with
198+
`examples_dir`, so orchestration stays centralized while the handlers run from
199+
the commit under test. Pull requests run the invocation and execution suites plus
200+
a short (60-second) long-running cycle; the full multi-hour long-running cycle is
201+
driven by `workflow_dispatch` with `phase: launch` and `phase: check`.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "aws-durable-execution-sdk-python-conformance-tests-otel"
7+
version = "0.0.0"
8+
description = "OpenTelemetry conformance test handlers for the AWS Durable Execution SDK for Python, exercised by the aws-durable-execution-conformance-tests OTel suites."
9+
requires-python = ">=3.11"
10+
dependencies = [
11+
"aws-durable-execution-sdk-python==1.8.0",
12+
"aws-durable-execution-sdk-python-otel==0.4.0",
13+
]
14+
15+
[tool.hatch.build.targets.wheel]
16+
# The handlers are flat Lambda modules deployed by SAM from src/, not an
17+
# importable package, so there is nothing to ship in a wheel.
18+
bypass-selection = true
19+
20+
[tool.ruff]
21+
line-length = 88
22+
target-version = "py311"
23+
24+
[tool.ruff.lint]
25+
preview = true
26+
select = ["E4", "E7", "E9", "F", "TID252"] # pycodestyle (E4/E7/E9) + Pyflakes + absolute imports
27+
28+
[tool.ruff.lint.isort]
29+
known-first-party = [
30+
"aws_durable_execution_sdk_python",
31+
"aws_durable_execution_sdk_python_otel",
32+
]
33+
force-single-line = false
34+
lines-after-imports = 2
35+
36+
[tool.ruff.lint.per-file-ignores]
37+
"tests/**" = [
38+
"ARG001",
39+
"ARG002",
40+
"ARG005",
41+
"E402",
42+
"S101",
43+
"PLR2004",
44+
"SIM117",
45+
"TRY301",
46+
]

0 commit comments

Comments
 (0)