Skip to content

Add e2e test suite for Airflow event-driven DAGs with Apache Kafka - #64833

Merged
jason810496 merged 12 commits into
apache:mainfrom
jason810496:ci/airflow-e2e-tests/event-driven-dag
May 14, 2026
Merged

Add e2e test suite for Airflow event-driven DAGs with Apache Kafka#64833
jason810496 merged 12 commits into
apache:mainfrom
jason810496:ci/airflow-e2e-tests/event-driven-dag

Conversation

@jason810496

Copy link
Copy Markdown
Member

Why

Airflow 3 introduces event-driven DAGs with Assets, AssetWatchers, and MessageQueueTrigger to enable reactive, message-driven pipelines. This PR adds a comprehensive e2e
test suite to verify that Kafka-based event routing works correctly, including proper handling of malformed messages and dead-letter queues.

What

  • Dag pattern: Implemented a producer-consumer pattern where producer sends 9 messages (8 valid orders + 1 malformed) to a Kafka topic, and consumer triggers 9 times via AssetWatcher
  • Test verification: Consumer Dag uses retries (3), short-circuit on malformed messages, and routes failures to a dead-letter queue topic
  • CI integration: Extended selective-checks to detect event-driven test file changes and trigger the suite; added workflow inputs/outputs for test mode selection
  • Kafka integration: The kafka.yaml and update_run.sh are copied from breeze --integration kafka
Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below) Claude Code following the guidelines

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an event-driven (Kafka-backed) E2E test suite for Airflow 3 Assets/AssetWatchers and wires it into Breeze selective checks + CI workflows so the suite runs when relevant files change.

Changes:

  • Introduces Kafka producer/consumer example DAGs and a new E2E test validating triggers, retries/DLQ behavior, and Kafka offsets.
  • Adds Kafka docker-compose + init script and updates the E2E harness to spin up Kafka and create required topics.
  • Extends Breeze selective checks and GitHub workflows to support an event_driven E2E test mode.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
FileDescription
dev/breeze/src/airflow_breeze/utils/selective_checks.pyAdds a new selective-checks file group + toggle for event-driven E2E tests.
dev/breeze/src/airflow_breeze/commands/testing_commands.pyExtends --e2e-test-mode choices with event_driven.
dev/breeze/doc/images/output_testing_airflow-e2e-tests.txtUpdates generated CLI help output artifact.
dev/breeze/doc/images/output_testing_airflow-e2e-tests.svgUpdates generated CLI help output artifact (includes event_driven).
airflow-e2e-tests/tests/airflow_e2e_tests/event_driven_tests/test_event_driven.pyAdds Kafka event-driven E2E test validating DAG triggers + Kafka offsets.
airflow-e2e-tests/tests/airflow_e2e_tests/event_driven_tests/init.pyAdds package init for the new test module.
airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.pyAdds list_dag_runs helper used by the new test.
airflow-e2e-tests/tests/airflow_e2e_tests/dags/example_event_driven.pyAdds example producer/consumer event-driven DAGs (Assets + Kafka trigger + DLQ).
airflow-e2e-tests/tests/airflow_e2e_tests/constants.pyAdds Kafka docker directory path constant.
airflow-e2e-tests/tests/airflow_e2e_tests/conftest.pyAdds event_driven mode setup, Kafka file copying, and topic creation.
airflow-e2e-tests/docker/kafka/update_run.shAdds broker init script for Kafka container startup.
airflow-e2e-tests/docker/kafka.ymlAdds Kafka broker docker-compose definition for E2E runs.
.github/workflows/ci-amd-arm.ymlPlumbs through the new selective-check output for event-driven E2E tests.
.github/workflows/airflow-e2e-tests.ymlUpdates workflow input docs to include event_driven mode.
.github/workflows/additional-prod-image-tests.ymlAdds a new job to run event-driven E2E tests when selected.

Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/dags/example_event_driven.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
Comment threaddev/breeze/src/airflow_breeze/utils/selective_checks.py
Comment threadairflow-e2e-tests/docker/kafka.yml Outdated

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 6 comments.

Comment threaddev/breeze/src/airflow_breeze/utils/selective_checks.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/conftest.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/dags/example_event_driven.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/dags/example_event_driven.py Outdated
@jason810496
jason810496force-pushed the ci/airflow-e2e-tests/event-driven-dag branch 3 times, most recently from 9ef8404 to 8e10497CompareMay 2, 2026 14:35

@jason810496jason810496 left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI failure require additional PR to fix the AwaitTrigger first:

 airflow-triggerer-1 | 2026-05-02T15:25:00.831436Z [error ] Trigger ID 1 exited with error 'AwaitMessageTrigger' object has no attribute '_task_instance' [airflow.jobs.triggerer_job_runner] error_detail=[{'exc_type': 'AttributeError', 'exc_value': "'AwaitMessageTrigger' object has no attribute '_task_instance'", 'exc_notes': [], 'syntax_error': None, 'is_cause': False, 'frames': [{'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/jobs/triggerer_job_runner.py', 'lineno': 1238, 'name': 'cleanup_finished_triggers'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 119, 'name': 'greenback_shim'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 208, 'name': '_greenback_shim'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 84, 'name': 'trampoline'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/outcome/_impl.py', 'lineno': 185, 'name': 'send'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/jobs/triggerer_job_runner.py', 'lineno': 1387, 'name': 'run_trigger'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/triggers/base.py', 'lineno': 104, 'name': 'task_instance'}], 'is_group': False, 'exceptions': []}] loc=triggerer_job_runner.py:929
airflow-triggerer-1 | 2026-05-02T15:25:00.832067Z [error ] Trigger exited without sending an event. Dependent tasks will be failed. [airflow.jobs.triggerer_job_runner] loc=triggerer_job_runner.py:929 name='ID 1'
airflow-triggerer-1 | 2026-05-02T15:25:02.858280Z [error ] Trigger ID 1 exited with error 'AwaitMessageTrigger' object has no attribute '_task_instance' [airflow.jobs.triggerer_job_runner] error_detail=[{'exc_type': 'AttributeError', 'exc_value': "'AwaitMessageTrigger' object has no attribute '_task_instance'", 'exc_notes': [], 'syntax_error': None, 'is_cause': False, 'frames': [{'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/jobs/triggerer_job_runner.py', 'lineno': 1238, 'name': 'cleanup_finished_triggers'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 119, 'name': 'greenback_shim'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 208, 'name': '_greenback_shim'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/greenback/_impl.py', 'lineno': 84, 'name': 'trampoline'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/outcome/_impl.py', 'lineno': 185, 'name': 'send'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/jobs/triggerer_job_runner.py', 'lineno': 1387, 'name': 'run_trigger'}, {'filename': '/home/airflow/.local/lib/python3.10/site-packages/airflow/triggers/base.py', 'lineno': 104, 'name': 'task_instance'}], 'is_group': False, 'exceptions': []}] loc=triggerer_job_runner.py:929

@parkhojeongparkhojeong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i reviewed about term usage :)

Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py Outdated
Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/conftest.py Outdated
@jason810496
jason810496force-pushed the ci/airflow-e2e-tests/event-driven-dag branch from 8e10497 to bed3898CompareMay 13, 2026 07:26
@jason810496
jason810496 requested a review from CopilotMay 13, 2026 07:30

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

dev/breeze/src/airflow_breeze/utils/run_tests.py:170

  • run_docker_compose_tests() now adds -sv to pytest args for all docker-compose based suites. This forces verbose + no-capture output in CI (and duplicates -s for the quick-start path), which can significantly increase logs and make failures harder to scan. Consider keeping the previous default and only enabling -s/-v when explicitly requested (e.g., via a CLI flag / env var) or only for specific suites that need streaming output.
 # Always with color
pytest_args = ["--color=yes", "-sv"]
if test_type == "task-sdk-integration":
test_path = Path("tests") / "task_sdk_tests"
cwd = TASK_SDK_INTEGRATION_TESTS_ROOT_PATH.as_posix()
elif test_type == "airflow-e2e-tests":
test_path = Path("tests") / "airflow_e2e_tests" / f"{test_mode}_tests"
cwd = AIRFLOW_E2E_TESTS_ROOT_PATH.as_posix()
elif test_type == "airflow-ctl-integration":
test_path = Path("tests") / "airflowctl_tests"
cwd = AIRFLOW_CTL_TESTS_ROOT_PATH.as_posix()
else:
test_path = Path("tests") / "docker_tests" / "test_docker_compose_quick_start.py"
cwd = DOCKER_TESTS_ROOT_PATH.as_posix()
pytest_args.append("-s") # -s to see print outputs as they come

Comment thread.github/workflows/ci-arm.yml
@jason810496
jason810496force-pushed the ci/airflow-e2e-tests/event-driven-dag branch from bed3898 to 9326560CompareMay 13, 2026 14:02
@jason810496
jason810496 requested a review from CopilotMay 13, 2026 14:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

dev/breeze/src/airflow_breeze/utils/run_tests.py:169

  • run_docker_compose_tests() now unconditionally adds -sv for all docker-compose-based test types (task-sdk integration, airflow-e2e-tests, airflow-ctl integration, and even the quick-start). This changes log volume/capture behavior globally and also makes the later pytest_args.append('-s') for the quick-start redundant. Consider scoping -s/-v to the specific test type(s) that need streaming output (e.g. only airflow-e2e-tests), or wiring this to an explicit Breeze flag/env var so CI log size and output format don’t change unexpectedly.
 # Always with color
pytest_args = ["--color=yes", "-sv"]
if test_type == "task-sdk-integration":
test_path = Path("tests") / "task_sdk_tests"
cwd = TASK_SDK_INTEGRATION_TESTS_ROOT_PATH.as_posix()
elif test_type == "airflow-e2e-tests":
test_path = Path("tests") / "airflow_e2e_tests" / f"{test_mode}_tests"
cwd = AIRFLOW_E2E_TESTS_ROOT_PATH.as_posix()
elif test_type == "airflow-ctl-integration":
test_path = Path("tests") / "airflowctl_tests"
cwd = AIRFLOW_CTL_TESTS_ROOT_PATH.as_posix()
else:
test_path = Path("tests") / "docker_tests" / "test_docker_compose_quick_start.py"
cwd = DOCKER_TESTS_ROOT_PATH.as_posix()
pytest_args.append("-s") # -s to see print outputs as they come

@jscheffl

Copy link
Copy Markdown
Contributor

Oh, cool!

FYI @renat-sagut

Comment threadairflow-e2e-tests/tests/airflow_e2e_tests/conftest.py Outdated

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I assume you master to make CI green. Comments are non blocking just some thoughts. But also easy to apply.

- Use timedelta(seconds=1) for retry_delay instead of bare int
- Return exactly expected_count runs from _wait_for_consumer_dag_runs
- Replace fixed 30s sleep with polling for Kafka consumer group registration
- Pin --partitions 1 --replication-factor 1 for deterministic topic creation
- Add kafka.yml pattern to selective checks file group
- Remove container_name and unused JMX port from kafka.yml
- Regenerate breeze CLI doc images
…minology and update regex patterns for Kafka provider paths
@jscheffl
jschefflforce-pushed the ci/airflow-e2e-tests/event-driven-dag branch from 9b94762 to 9771b31CompareMay 13, 2026 18:53
jason810496and others added 2 commits May 14, 2026 10:28
Co-authored-by: Jens Scheffler <95105677+jscheffl@users.noreply.github.com>
@jason810496
jason810496 merged commit 50fa403 into apache:mainMay 14, 2026
144 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jason810496@jscheffl@parkhojeong