Skip to content

Commit 6f0b600

Browse files
Fix delay in Dataproc CreateBatch operator (#26126)
1 parent 7601460 commit 6f0b600

4 files changed

Lines changed: 75 additions & 5 deletions

File tree

airflow/providers/google/cloud/hooks/dataproc.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,15 @@ def get_batch_client(self, region: str | None = None) -> BatchControllerClient:
253253
credentials=self.get_credentials(), client_info=CLIENT_INFO, client_options=client_options
254254
)
255255

256-
def wait_for_operation(self, operation: Operation, timeout: float | None = None):
256+
def wait_for_operation(
257+
self,
258+
operation: Operation,
259+
timeout: float | None = None,
260+
result_retry: Retry | _MethodDefault = DEFAULT,
261+
):
257262
"""Waits for long-lasting operation to complete."""
258263
try:
259-
return operation.result(timeout=timeout)
264+
return operation.result(timeout=timeout, retry=result_retry)
260265
except Exception:
261266
error = operation.exception(timeout=timeout)
262267
raise AirflowException(error)

airflow/providers/google/cloud/operators/dataproc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,8 @@ class DataprocCreateBatchOperator(BaseOperator):
20382038
the first ``google.longrunning.Operation`` created and stored in the backend is returned.
20392039
:param retry: A retry object used to retry requests. If ``None`` is specified, requests will not be
20402040
retried.
2041+
:param result_retry: Result retry object used to retry requests. Is used to decrease delay between
2042+
executing chained tasks in a DAG by specifying exact amount of seconds for executing.
20412043
:param timeout: The amount of time, in seconds, to wait for the request to complete. Note that if
20422044
``retry`` is specified, the timeout applies to each individual attempt.
20432045
:param metadata: Additional metadata that is provided to the method.
@@ -2074,6 +2076,7 @@ def __init__(
20742076
metadata: Sequence[tuple[str, str]] = (),
20752077
gcp_conn_id: str = "google_cloud_default",
20762078
impersonation_chain: str | Sequence[str] | None = None,
2079+
result_retry: Retry | _MethodDefault = DEFAULT,
20772080
**kwargs,
20782081
):
20792082
super().__init__(**kwargs)
@@ -2083,6 +2086,7 @@ def __init__(
20832086
self.batch_id = batch_id
20842087
self.request_id = request_id
20852088
self.retry = retry
2089+
self.result_retry = result_retry
20862090
self.timeout = timeout
20872091
self.metadata = metadata
20882092
self.gcp_conn_id = gcp_conn_id
@@ -2107,7 +2111,9 @@ def execute(self, context: Context):
21072111
)
21082112
if self.operation is None:
21092113
raise RuntimeError("The operation should be set here!")
2110-
result = hook.wait_for_operation(timeout=self.timeout, operation=self.operation)
2114+
result = hook.wait_for_operation(
2115+
timeout=self.timeout, result_retry=self.result_retry, operation=self.operation
2116+
)
21112117
self.log.info("Batch %s created", self.batch_id)
21122118
except AlreadyExists:
21132119
self.log.info("Batch with given id already exists")

tests/providers/google/cloud/operators/test_dataproc.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@
193193

194194
TIMEOUT = 120
195195
RETRY = mock.MagicMock(Retry)
196+
RESULT_RETRY = mock.MagicMock(Retry)
196197
METADATA = [("key", "value")]
197198
REQUEST_ID = "request_id_uuid"
198199

@@ -1706,6 +1707,36 @@ def test_execute(self, mock_hook, to_dict_mock):
17061707
metadata=METADATA,
17071708
)
17081709

1710+
@mock.patch(DATAPROC_PATH.format("Batch.to_dict"))
1711+
@mock.patch(DATAPROC_PATH.format("DataprocHook"))
1712+
def test_execute_with_result_retry(self, mock_hook, to_dict_mock):
1713+
op = DataprocCreateBatchOperator(
1714+
task_id=TASK_ID,
1715+
gcp_conn_id=GCP_CONN_ID,
1716+
impersonation_chain=IMPERSONATION_CHAIN,
1717+
region=GCP_LOCATION,
1718+
project_id=GCP_PROJECT,
1719+
batch=BATCH,
1720+
batch_id=BATCH_ID,
1721+
request_id=REQUEST_ID,
1722+
retry=RETRY,
1723+
result_retry=RESULT_RETRY,
1724+
timeout=TIMEOUT,
1725+
metadata=METADATA,
1726+
)
1727+
op.execute(context=MagicMock())
1728+
mock_hook.assert_called_once_with(gcp_conn_id=GCP_CONN_ID, impersonation_chain=IMPERSONATION_CHAIN)
1729+
mock_hook.return_value.create_batch.assert_called_once_with(
1730+
region=GCP_LOCATION,
1731+
project_id=GCP_PROJECT,
1732+
batch=BATCH,
1733+
batch_id=BATCH_ID,
1734+
request_id=REQUEST_ID,
1735+
retry=RETRY,
1736+
timeout=TIMEOUT,
1737+
metadata=METADATA,
1738+
)
1739+
17091740
@mock.patch(DATAPROC_PATH.format("Batch.to_dict"))
17101741
@mock.patch(DATAPROC_PATH.format("DataprocHook"))
17111742
def test_execute_batch_failed(self, mock_hook, to_dict_mock):

tests/system/providers/google/cloud/dataproc/example_dataproc_batch.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import os
2323
from datetime import datetime
2424

25+
from google.api_core.retry import Retry
26+
2527
from airflow import models
2628
from airflow.providers.google.cloud.operators.dataproc import (
2729
DataprocCreateBatchOperator,
@@ -36,6 +38,7 @@
3638
PROJECT_ID = os.environ.get("SYSTEM_TESTS_GCP_PROJECT", "")
3739
REGION = "europe-west1"
3840
BATCH_ID = f"test-batch-id-{ENV_ID}"
41+
BATCH_ID_2 = f"test-batch-id-{ENV_ID}-2"
3942
BATCH_CONFIG = {
4043
"spark_batch": {
4144
"jar_file_uris": ["file:///usr/lib/spark/examples/jars/spark-examples.jar"],
@@ -58,14 +61,26 @@
5861
region=REGION,
5962
batch=BATCH_CONFIG,
6063
batch_id=BATCH_ID,
61-
timeout=5.0,
64+
)
65+
66+
create_batch_2 = DataprocCreateBatchOperator(
67+
task_id="create_batch_2",
68+
project_id=PROJECT_ID,
69+
region=REGION,
70+
batch=BATCH_CONFIG,
71+
batch_id=BATCH_ID_2,
72+
result_retry=Retry(maximum=10.0, initial=10.0, multiplier=1.0),
6273
)
6374
# [END how_to_cloud_dataproc_create_batch_operator]
6475

6576
# [START how_to_cloud_dataproc_get_batch_operator]
6677
get_batch = DataprocGetBatchOperator(
6778
task_id="get_batch", project_id=PROJECT_ID, region=REGION, batch_id=BATCH_ID
6879
)
80+
81+
get_batch_2 = DataprocGetBatchOperator(
82+
task_id="get_batch_2", project_id=PROJECT_ID, region=REGION, batch_id=BATCH_ID_2
83+
)
6984
# [END how_to_cloud_dataproc_get_batch_operator]
7085

7186
# [START how_to_cloud_dataproc_list_batches_operator]
@@ -80,10 +95,23 @@
8095
delete_batch = DataprocDeleteBatchOperator(
8196
task_id="delete_batch", project_id=PROJECT_ID, region=REGION, batch_id=BATCH_ID
8297
)
98+
delete_batch.trigger_rule = TriggerRule.ALL_DONE
99+
100+
delete_batch_2 = DataprocDeleteBatchOperator(
101+
task_id="delete_batch_2", project_id=PROJECT_ID, region=REGION, batch_id=BATCH_ID_2
102+
)
83103
# [END how_to_cloud_dataproc_delete_batch_operator]
84104
delete_batch.trigger_rule = TriggerRule.ALL_DONE
85105

86-
create_batch >> get_batch >> list_batches >> delete_batch
106+
(
107+
create_batch
108+
>> create_batch_2
109+
>> get_batch
110+
>> get_batch_2
111+
>> list_batches
112+
>> delete_batch
113+
>> delete_batch_2
114+
)
87115

88116
from tests.system.utils.watcher import watcher
89117

0 commit comments

Comments
 (0)