This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 5e65f218508 Fail the Teradata compute cluster trigger on an unknown
operation (#72696)
5e65f218508 is described below
commit 5e65f218508e7249fb562d10186c4ceb0a04218b
Author: rjgoyln <[email protected]>
AuthorDate: Wed Sep 9 19:09:53 2026 +0800
Fail the Teradata compute cluster trigger on an unknown operation (#72696)
* Fail the Teradata compute cluster trigger on an unknown operation
The polling loop only exits once the compute profile reaches the status the
operation waits for. An operation type the trigger does not recognise never
leaves it, so the trigger polls on instead of reporting the bad input.
No shipped operator passes such a value, so this is hardening rather than a
fault reachable from a Dag. The "Invalid operation" event meant to cover the
case sat after the loop, unreachable, as did the two timeout events beside
it -
the triggerer's own deferral timeout has always superseded those.
* Name the rejected Teradata operation type and correct its docstring
The operator surfaces the trigger's error message verbatim as the task's
AirflowException, so a bare "Invalid operation" gave no way to tell which
value
was rejected. The docstring is now the only place the accepted values are
written down, and it named a parameter that does not exist while omitting
the
two CREATE operations.
---
generated/known_airflow_exceptions.txt | 1 -
.../teradata/triggers/teradata_compute_cluster.py | 77 ++++++----------------
.../triggers/test_teradata_compute_cluster.py | 43 ++++++++++++
3 files changed, 62 insertions(+), 59 deletions(-)
diff --git a/generated/known_airflow_exceptions.txt
b/generated/known_airflow_exceptions.txt
index cd9b8e3c99c..2c619d8eaff 100644
--- a/generated/known_airflow_exceptions.txt
+++ b/generated/known_airflow_exceptions.txt
@@ -400,7 +400,6 @@
providers/telegram/src/airflow/providers/telegram/operators/telegram.py::2
providers/teradata/src/airflow/providers/teradata/hooks/bteq.py::10
providers/teradata/src/airflow/providers/teradata/hooks/ttu.py::1
providers/teradata/src/airflow/providers/teradata/operators/teradata_compute_cluster.py::9
-providers/teradata/src/airflow/providers/teradata/triggers/teradata_compute_cluster.py::1
providers/teradata/src/airflow/providers/teradata/utils/bteq_util.py::2
providers/trino/src/airflow/providers/trino/hooks/trino.py::1
providers/vespa/src/airflow/providers/vespa/operators/vespa_ingest.py::1
diff --git
a/providers/teradata/src/airflow/providers/teradata/triggers/teradata_compute_cluster.py
b/providers/teradata/src/airflow/providers/teradata/triggers/teradata_compute_cluster.py
index f9c90313d83..9b31997bbc8 100644
---
a/providers/teradata/src/airflow/providers/teradata/triggers/teradata_compute_cluster.py
+++
b/providers/teradata/src/airflow/providers/teradata/triggers/teradata_compute_cluster.py
@@ -20,7 +20,6 @@ import asyncio
from collections.abc import AsyncIterator
from typing import Any
-from airflow.providers.common.compat.sdk import AirflowException
from airflow.providers.common.sql.hooks.handlers import fetch_one_handler
from airflow.providers.teradata.hooks.teradata import TeradataHook
from airflow.providers.teradata.utils.constants import Constants
@@ -29,13 +28,13 @@ from airflow.triggers.base import BaseTrigger, TriggerEvent
class TeradataComputeClusterSyncTrigger(BaseTrigger):
"""
- Fetch the status of the suspend or resume operation for the specified
compute cluster.
+ Fetch the status of the requested operation for the specified compute
cluster.
:param teradata_conn_id: The :ref:`Teradata connection id
<howto/connection:teradata>`
reference to a specific Teradata database.
:param compute_profile_name: Name of the Compute Profile to manage.
:param compute_group_name: Name of compute group to which compute profile
belongs.
- :param opr_type: Compute cluster operation - SUSPEND/RESUME
+ :param operation_type: Compute cluster operation -
CREATE/CREATE_SUSPEND/RESUME/SUSPEND
:param poll_interval: polling period in minutes to check for the status
"""
@@ -69,70 +68,32 @@ class TeradataComputeClusterSyncTrigger(BaseTrigger):
async def run(self) -> AsyncIterator[TriggerEvent]:
"""Wait for Compute Cluster operation to complete."""
+ if self.operation_type in (Constants.CC_SUSPEND_OPR,
Constants.CC_CREATE_SUSPEND_OPR):
+ expected_status = Constants.CC_SUSPEND_DB_STATUS
+ elif self.operation_type in (Constants.CC_RESUME_OPR,
Constants.CC_CREATE_OPR):
+ expected_status = Constants.CC_RESUME_DB_STATUS
+ else:
+ yield TriggerEvent({"status": "error", "message": f"Invalid
operation: {self.operation_type}"})
+ return
try:
while True:
status = await self.get_status()
if status is None or len(status) == 0:
- raise AirflowException(Constants.CC_GRP_PRP_NON_EXISTS_MSG
% "manage")
- if (
- self.operation_type == Constants.CC_SUSPEND_OPR
- or self.operation_type == Constants.CC_CREATE_SUSPEND_OPR
- ):
- if status == Constants.CC_SUSPEND_DB_STATUS:
- break
- elif (
- self.operation_type == Constants.CC_RESUME_OPR
- or self.operation_type == Constants.CC_CREATE_OPR
- ):
- if status == Constants.CC_RESUME_DB_STATUS:
- break
+ raise ValueError(Constants.CC_GRP_PRP_NON_EXISTS_MSG %
"manage")
+ if status == expected_status:
+ break
if self.poll_interval is not None:
self.poll_interval = float(self.poll_interval)
else:
self.poll_interval = float(Constants.CC_POLL_INTERVAL)
await asyncio.sleep(self.poll_interval)
- if (
- self.operation_type == Constants.CC_SUSPEND_OPR
- or self.operation_type == Constants.CC_CREATE_SUSPEND_OPR
- ):
- if status == Constants.CC_SUSPEND_DB_STATUS:
- yield TriggerEvent(
- {
- "status": "success",
- "message": Constants.CC_OPR_SUCCESS_STATUS_MSG
- % (self.compute_profile_name, self.operation_type),
- }
- )
- else:
- yield TriggerEvent(
- {
- "status": "error",
- "message": Constants.CC_OPR_TIMEOUT_ERROR
- % (self.operation_type, self.compute_profile_name),
- }
- )
- elif (
- self.operation_type == Constants.CC_RESUME_OPR
- or self.operation_type == Constants.CC_CREATE_OPR
- ):
- if status == Constants.CC_RESUME_DB_STATUS:
- yield TriggerEvent(
- {
- "status": "success",
- "message": Constants.CC_OPR_SUCCESS_STATUS_MSG
- % (self.compute_profile_name, self.operation_type),
- }
- )
- else:
- yield TriggerEvent(
- {
- "status": "error",
- "message": Constants.CC_OPR_TIMEOUT_ERROR
- % (self.operation_type, self.compute_profile_name),
- }
- )
- else:
- yield TriggerEvent({"status": "error", "message": "Invalid
operation"})
+ yield TriggerEvent(
+ {
+ "status": "success",
+ "message": Constants.CC_OPR_SUCCESS_STATUS_MSG
+ % (self.compute_profile_name, self.operation_type),
+ }
+ )
except Exception as e:
yield TriggerEvent({"status": "error", "message": str(e)})
except asyncio.CancelledError:
diff --git
a/providers/teradata/tests/unit/teradata/triggers/test_teradata_compute_cluster.py
b/providers/teradata/tests/unit/teradata/triggers/test_teradata_compute_cluster.py
index a9a1fedce6c..ee454e5c4eb 100644
---
a/providers/teradata/tests/unit/teradata/triggers/test_teradata_compute_cluster.py
+++
b/providers/teradata/tests/unit/teradata/triggers/test_teradata_compute_cluster.py
@@ -125,6 +125,49 @@ async def test_run_resume_failure():
mock_get_status.assert_called_once()
[email protected]
+async def test_run_invalid_operation():
+ trigger = TeradataComputeClusterSyncTrigger(
+ teradata_conn_id="test_conn_id",
+ compute_profile_name="test_profile",
+ operation_type="INVALID",
+ poll_interval=1,
+ )
+ with patch.object(trigger, "get_status", autospec=True) as mock_get_status:
+ events = [event async for event in trigger.run()]
+ assert events == [TriggerEvent({"status": "error", "message": "Invalid
operation: INVALID"})]
+ mock_get_status.assert_not_called()
+
+
[email protected]
[email protected](
+ ("operation_type", "db_status"),
+ [
+ (Constants.CC_CREATE_OPR, Constants.CC_RESUME_DB_STATUS),
+ (Constants.CC_CREATE_SUSPEND_OPR, Constants.CC_SUSPEND_DB_STATUS),
+ ],
+)
+async def test_run_create_success(operation_type, db_status):
+ trigger = TeradataComputeClusterSyncTrigger(
+ teradata_conn_id="test_conn_id",
+ compute_profile_name="test_profile",
+ operation_type=operation_type,
+ poll_interval=1,
+ )
+ with patch.object(trigger, "get_status", autospec=True) as mock_get_status:
+ mock_get_status.return_value = db_status
+ events = [event async for event in trigger.run()]
+ assert events == [
+ TriggerEvent(
+ {
+ "status": "success",
+ "message": Constants.CC_OPR_SUCCESS_STATUS_MSG %
("test_profile", operation_type),
+ }
+ )
+ ]
+ mock_get_status.assert_called_once()
+
+
@pytest.fixture
def mock_teradata_hook_run():
with patch.object(TeradataHook, "run") as mock_run: