apache-airflow-providers-cncf-kubernetes 10.1.0rc2__py3-none-any.whl → 10.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apache-airflow-providers-cncf-kubernetes might be problematic. Click here for more details.

Files changed (31) hide show
  1. airflow/providers/cncf/kubernetes/LICENSE +0 -52
  2. airflow/providers/cncf/kubernetes/__init__.py +1 -1
  3. airflow/providers/cncf/kubernetes/backcompat/backwards_compat_converters.py +2 -3
  4. airflow/providers/cncf/kubernetes/callbacks.py +90 -8
  5. airflow/providers/cncf/kubernetes/cli/kubernetes_command.py +3 -4
  6. airflow/providers/cncf/kubernetes/decorators/kubernetes.py +10 -5
  7. airflow/providers/cncf/kubernetes/exceptions.py +29 -0
  8. airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py +36 -113
  9. airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py +27 -15
  10. airflow/providers/cncf/kubernetes/get_provider_info.py +14 -21
  11. airflow/providers/cncf/kubernetes/hooks/kubernetes.py +20 -10
  12. airflow/providers/cncf/kubernetes/kube_config.py +0 -4
  13. airflow/providers/cncf/kubernetes/kubernetes_helper_functions.py +1 -1
  14. airflow/providers/cncf/kubernetes/operators/custom_object_launcher.py +3 -3
  15. airflow/providers/cncf/kubernetes/operators/job.py +4 -4
  16. airflow/providers/cncf/kubernetes/operators/kueue.py +2 -2
  17. airflow/providers/cncf/kubernetes/operators/pod.py +102 -44
  18. airflow/providers/cncf/kubernetes/operators/resource.py +1 -1
  19. airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py +23 -19
  20. airflow/providers/cncf/kubernetes/pod_generator.py +51 -21
  21. airflow/providers/cncf/kubernetes/resource_convert/env_variable.py +1 -2
  22. airflow/providers/cncf/kubernetes/secret.py +1 -2
  23. airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py +1 -2
  24. airflow/providers/cncf/kubernetes/template_rendering.py +10 -2
  25. airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py +1 -2
  26. airflow/providers/cncf/kubernetes/utils/pod_manager.py +12 -11
  27. {apache_airflow_providers_cncf_kubernetes-10.1.0rc2.dist-info → apache_airflow_providers_cncf_kubernetes-10.2.0.dist-info}/METADATA +10 -27
  28. {apache_airflow_providers_cncf_kubernetes-10.1.0rc2.dist-info → apache_airflow_providers_cncf_kubernetes-10.2.0.dist-info}/RECORD +30 -30
  29. airflow/providers/cncf/kubernetes/pod_generator_deprecated.py +0 -309
  30. {apache_airflow_providers_cncf_kubernetes-10.1.0rc2.dist-info → apache_airflow_providers_cncf_kubernetes-10.2.0.dist-info}/WHEEL +0 -0
  31. {apache_airflow_providers_cncf_kubernetes-10.1.0rc2.dist-info → apache_airflow_providers_cncf_kubernetes-10.2.0.dist-info}/entry_points.txt +0 -0
@@ -199,55 +199,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
201
  limitations under the License.
202
-
203
- ============================================================================
204
- APACHE AIRFLOW SUBCOMPONENTS:
205
-
206
- The Apache Airflow project contains subcomponents with separate copyright
207
- notices and license terms. Your use of the source code for the these
208
- subcomponents is subject to the terms and conditions of the following
209
- licenses.
210
-
211
-
212
- ========================================================================
213
- Third party Apache 2.0 licenses
214
- ========================================================================
215
-
216
- The following components are provided under the Apache 2.0 License.
217
- See project link for details. The text of each license is also included
218
- at 3rd-party-licenses/LICENSE-[project].txt.
219
-
220
- (ALv2 License) hue v4.3.0 (https://github.com/cloudera/hue/)
221
- (ALv2 License) jqclock v2.3.0 (https://github.com/JohnRDOrazio/jQuery-Clock-Plugin)
222
- (ALv2 License) bootstrap3-typeahead v4.0.2 (https://github.com/bassjobsen/Bootstrap-3-Typeahead)
223
- (ALv2 License) connexion v2.7.0 (https://github.com/zalando/connexion)
224
-
225
- ========================================================================
226
- MIT licenses
227
- ========================================================================
228
-
229
- The following components are provided under the MIT License. See project link for details.
230
- The text of each license is also included at 3rd-party-licenses/LICENSE-[project].txt.
231
-
232
- (MIT License) jquery v3.5.1 (https://jquery.org/license/)
233
- (MIT License) dagre-d3 v0.6.4 (https://github.com/cpettitt/dagre-d3)
234
- (MIT License) bootstrap v3.4.1 (https://github.com/twbs/bootstrap/)
235
- (MIT License) d3-tip v0.9.1 (https://github.com/Caged/d3-tip)
236
- (MIT License) dataTables v1.10.25 (https://datatables.net)
237
- (MIT License) normalize.css v3.0.2 (http://necolas.github.io/normalize.css/)
238
- (MIT License) ElasticMock v1.3.2 (https://github.com/vrcmarcos/elasticmock)
239
- (MIT License) MomentJS v2.24.0 (http://momentjs.com/)
240
- (MIT License) eonasdan-bootstrap-datetimepicker v4.17.49 (https://github.com/eonasdan/bootstrap-datetimepicker/)
241
-
242
- ========================================================================
243
- BSD 3-Clause licenses
244
- ========================================================================
245
- The following components are provided under the BSD 3-Clause license. See project links for details.
246
- The text of each license is also included at 3rd-party-licenses/LICENSE-[project].txt.
247
-
248
- (BSD 3 License) d3 v5.16.0 (https://d3js.org)
249
- (BSD 3 License) d3-shape v2.1.0 (https://github.com/d3/d3-shape)
250
- (BSD 3 License) cgroupspy 0.2.1 (https://github.com/cloudsigma/cgroupspy)
251
-
252
- ========================================================================
253
- See 3rd-party-licenses/LICENSES-ui.txt for packages used in `/airflow/www`
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "10.1.0"
32
+ __version__ = "10.2.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
35
  "2.9.0"
@@ -18,9 +18,8 @@
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
- from kubernetes.client import ApiClient, models as k8s
22
-
23
21
  from airflow.exceptions import AirflowException
22
+ from kubernetes.client import ApiClient, models as k8s
24
23
 
25
24
 
26
25
  def _convert_kube_model_object(obj, new_class):
@@ -74,7 +73,7 @@ def convert_env_vars(env_vars: list[k8s.V1EnvVar] | dict[str, str]) -> list[k8s.
74
73
  """
75
74
  Coerce env var collection for kubernetes.
76
75
 
77
- If the collection is a str-str dict, convert it into a list of ``V1EnvVar``s.
76
+ If the collection is a str-str dict, convert it into a list of ``V1EnvVar`` variables.
78
77
  """
79
78
  if isinstance(env_vars, dict):
80
79
  return [k8s.V1EnvVar(name=k, value=v) for k, v in env_vars.items()]
@@ -17,11 +17,16 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from enum import Enum
20
- from typing import Union
20
+ from typing import TYPE_CHECKING, Union
21
21
 
22
- import kubernetes.client as k8s
23
22
  import kubernetes_asyncio.client as async_k8s
24
23
 
24
+ import kubernetes.client as k8s
25
+
26
+ if TYPE_CHECKING:
27
+ from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator
28
+ from airflow.utils.context import Context
29
+
25
30
  client_type = Union[k8s.CoreV1Api, async_k8s.CoreV1Api]
26
31
 
27
32
 
@@ -41,7 +46,7 @@ class KubernetesPodOperatorCallback:
41
46
  """
42
47
 
43
48
  @staticmethod
44
- def on_sync_client_creation(*, client: k8s.CoreV1Api, **kwargs) -> None:
49
+ def on_sync_client_creation(*, client: k8s.CoreV1Api, operator: KubernetesPodOperator, **kwargs) -> None:
45
50
  """
46
51
  Invoke this callback after creating the sync client.
47
52
 
@@ -50,7 +55,34 @@ class KubernetesPodOperatorCallback:
50
55
  pass
51
56
 
52
57
  @staticmethod
53
- def on_pod_creation(*, pod: k8s.V1Pod, client: client_type, mode: str, **kwargs) -> None:
58
+ def on_pod_manifest_created(
59
+ *,
60
+ pod_request: k8s.V1Pod,
61
+ client: client_type,
62
+ mode: str,
63
+ operator: KubernetesPodOperator,
64
+ context: Context,
65
+ **kwargs,
66
+ ) -> None:
67
+ """
68
+ Invoke this callback after KPO creates the V1Pod manifest but before the pod is created.
69
+
70
+ :param pod_request: the kubernetes pod manifest
71
+ :param client: the Kubernetes client that can be used in the callback.
72
+ :param mode: the current execution mode, it's one of (`sync`, `async`).
73
+ """
74
+ pass
75
+
76
+ @staticmethod
77
+ def on_pod_creation(
78
+ *,
79
+ pod: k8s.V1Pod,
80
+ client: client_type,
81
+ mode: str,
82
+ operator: KubernetesPodOperator,
83
+ context: Context,
84
+ **kwargs,
85
+ ) -> None:
54
86
  """
55
87
  Invoke this callback after creating the pod.
56
88
 
@@ -61,7 +93,15 @@ class KubernetesPodOperatorCallback:
61
93
  pass
62
94
 
63
95
  @staticmethod
64
- def on_pod_starting(*, pod: k8s.V1Pod, client: client_type, mode: str, **kwargs) -> None:
96
+ def on_pod_starting(
97
+ *,
98
+ pod: k8s.V1Pod,
99
+ client: client_type,
100
+ mode: str,
101
+ operator: KubernetesPodOperator,
102
+ context: Context,
103
+ **kwargs,
104
+ ) -> None:
65
105
  """
66
106
  Invoke this callback when the pod starts.
67
107
 
@@ -72,7 +112,15 @@ class KubernetesPodOperatorCallback:
72
112
  pass
73
113
 
74
114
  @staticmethod
75
- def on_pod_completion(*, pod: k8s.V1Pod, client: client_type, mode: str, **kwargs) -> None:
115
+ def on_pod_completion(
116
+ *,
117
+ pod: k8s.V1Pod,
118
+ client: client_type,
119
+ mode: str,
120
+ operator: KubernetesPodOperator,
121
+ context: Context,
122
+ **kwargs,
123
+ ) -> None:
76
124
  """
77
125
  Invoke this callback when the pod completes.
78
126
 
@@ -83,7 +131,34 @@ class KubernetesPodOperatorCallback:
83
131
  pass
84
132
 
85
133
  @staticmethod
86
- def on_pod_cleanup(*, pod: k8s.V1Pod, client: client_type, mode: str, **kwargs):
134
+ def on_pod_teardown(
135
+ *,
136
+ pod: k8s.V1Pod,
137
+ client: client_type,
138
+ mode: str,
139
+ operator: KubernetesPodOperator,
140
+ context: Context,
141
+ **kwargs,
142
+ ) -> None:
143
+ """
144
+ Invoke this callback after all pod completion callbacks but before the pod is deleted.
145
+
146
+ :param pod: the completed pod.
147
+ :param client: the Kubernetes client that can be used in the callback.
148
+ :param mode: the current execution mode, it's one of (`sync`, `async`).
149
+ """
150
+ pass
151
+
152
+ @staticmethod
153
+ def on_pod_cleanup(
154
+ *,
155
+ pod: k8s.V1Pod,
156
+ client: client_type,
157
+ mode: str,
158
+ operator: KubernetesPodOperator,
159
+ context: Context,
160
+ **kwargs,
161
+ ):
87
162
  """
88
163
  Invoke this callback after cleaning/deleting the pod.
89
164
 
@@ -95,7 +170,14 @@ class KubernetesPodOperatorCallback:
95
170
 
96
171
  @staticmethod
97
172
  def on_operator_resuming(
98
- *, pod: k8s.V1Pod, event: dict, client: client_type, mode: str, **kwargs
173
+ *,
174
+ pod: k8s.V1Pod,
175
+ event: dict,
176
+ client: client_type,
177
+ mode: str,
178
+ operator: KubernetesPodOperator,
179
+ context: Context,
180
+ **kwargs,
99
181
  ) -> None:
100
182
  """
101
183
  Invoke this callback when resuming the `KubernetesPodOperator` from deferred state.
@@ -22,10 +22,6 @@ import os
22
22
  import sys
23
23
  from datetime import datetime, timedelta
24
24
 
25
- from kubernetes import client
26
- from kubernetes.client.api_client import ApiClient
27
- from kubernetes.client.rest import ApiException
28
-
29
25
  from airflow.models import DagRun, TaskInstance
30
26
  from airflow.providers.cncf.kubernetes import pod_generator
31
27
  from airflow.providers.cncf.kubernetes.executors.kubernetes_executor import KubeConfig
@@ -36,6 +32,9 @@ from airflow.providers.cncf.kubernetes.version_compat import AIRFLOW_V_3_0_PLUS
36
32
  from airflow.utils import cli as cli_utils, yaml
37
33
  from airflow.utils.cli import get_dag
38
34
  from airflow.utils.providers_configuration_loader import providers_configuration_loaded
35
+ from kubernetes import client
36
+ from kubernetes.client.api_client import ApiClient
37
+ from kubernetes.client.rest import ApiException
39
38
 
40
39
 
41
40
  @cli_utils.action_cli
@@ -19,20 +19,19 @@ from __future__ import annotations
19
19
  import base64
20
20
  import os
21
21
  import pickle
22
- import uuid
23
22
  from collections.abc import Sequence
24
23
  from shlex import quote
25
24
  from tempfile import TemporaryDirectory
26
25
  from typing import TYPE_CHECKING, Callable
27
26
 
28
27
  import dill
29
- from kubernetes.client import models as k8s
30
28
 
31
29
  from airflow.decorators.base import DecoratedOperator, TaskDecorator, task_decorator_factory
32
30
  from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator
33
31
  from airflow.providers.cncf.kubernetes.python_kubernetes_script import (
34
32
  write_python_script,
35
33
  )
34
+ from kubernetes.client import models as k8s
36
35
 
37
36
  if TYPE_CHECKING:
38
37
  from airflow.utils.context import Context
@@ -68,9 +67,15 @@ class _KubernetesDecoratedOperator(DecoratedOperator, KubernetesPodOperator):
68
67
 
69
68
  def __init__(self, namespace: str | None = None, use_dill: bool = False, **kwargs) -> None:
70
69
  self.use_dill = use_dill
70
+
71
+ # If the name was not provided, we generate operator name from the python_callable
72
+ # we also instruct operator to add a random suffix to avoid collisions by default
73
+ op_name = kwargs.pop("name", f"k8s-airflow-pod-{kwargs['python_callable'].__name__}")
74
+ random_name_suffix = kwargs.pop("random_name_suffix", True)
71
75
  super().__init__(
72
76
  namespace=namespace,
73
- name=kwargs.pop("name", f"k8s_airflow_pod_{uuid.uuid4().hex}"),
77
+ name=op_name,
78
+ random_name_suffix=random_name_suffix,
74
79
  cmds=["placeholder-command"],
75
80
  **kwargs,
76
81
  )
@@ -119,7 +124,7 @@ class _KubernetesDecoratedOperator(DecoratedOperator, KubernetesPodOperator):
119
124
  }
120
125
  write_python_script(jinja_context=jinja_context, filename=script_filename)
121
126
 
122
- self.env_vars = [
127
+ self.env_vars: list[k8s.V1EnvVar] = [
123
128
  *self.env_vars,
124
129
  k8s.V1EnvVar(name=_PYTHON_SCRIPT_ENV, value=_read_file_contents(script_filename)),
125
130
  k8s.V1EnvVar(name=_PYTHON_INPUT_ENV, value=_read_file_contents(input_filename)),
@@ -138,7 +143,7 @@ def kubernetes_task(
138
143
  Kubernetes operator decorator.
139
144
 
140
145
  This wraps a function to be executed in K8s using KubernetesPodOperator.
141
- Also accepts any argument that DockerOperator will via ``kwargs``. Can be
146
+ Also accepts any argument that KubernetesPodOperator will via ``kwargs``. Can be
142
147
  reused in a single DAG.
143
148
 
144
149
  :param python_callable: Function to decorate
@@ -0,0 +1,29 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ from __future__ import annotations
18
+
19
+ from airflow.exceptions import (
20
+ AirflowException,
21
+ )
22
+
23
+
24
+ class PodMutationHookException(AirflowException):
25
+ """Raised when exception happens during Pod Mutation Hook execution."""
26
+
27
+
28
+ class PodReconciliationError(AirflowException):
29
+ """Raised when an error is encountered while trying to merge pod configs."""
@@ -37,8 +37,11 @@ from queue import Empty, Queue
37
37
  from typing import TYPE_CHECKING, Any
38
38
 
39
39
  from deprecated import deprecated
40
+ from sqlalchemy import select
41
+
42
+ from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
43
+ from airflow.providers.cncf.kubernetes.version_compat import AIRFLOW_V_3_0_PLUS
40
44
  from kubernetes.dynamic import DynamicClient
41
- from sqlalchemy import or_, select, update
42
45
 
43
46
  try:
44
47
  from airflow.cli.cli_config import ARG_LOGICAL_DATE
@@ -60,16 +63,14 @@ from airflow.cli.cli_config import (
60
63
  from airflow.configuration import conf
61
64
  from airflow.exceptions import AirflowProviderDeprecationWarning
62
65
  from airflow.executors.base_executor import BaseExecutor
63
- from airflow.executors.executor_constants import KUBERNETES_EXECUTOR
66
+ from airflow.providers.cncf.kubernetes.exceptions import PodMutationHookException, PodReconciliationError
64
67
  from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import (
65
68
  ADOPTED,
66
69
  POD_EXECUTOR_DONE_KEY,
67
70
  )
68
71
  from airflow.providers.cncf.kubernetes.kube_config import KubeConfig
69
72
  from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import annotations_to_key
70
- from airflow.providers.cncf.kubernetes.pod_generator import PodMutationHookException, PodReconciliationError
71
73
  from airflow.stats import Stats
72
- from airflow.utils.event_scheduler import EventScheduler
73
74
  from airflow.utils.log.logging_mixin import remove_escape_codes
74
75
  from airflow.utils.session import NEW_SESSION, provide_session
75
76
  from airflow.utils.state import TaskInstanceState
@@ -77,10 +78,9 @@ from airflow.utils.state import TaskInstanceState
77
78
  if TYPE_CHECKING:
78
79
  import argparse
79
80
 
80
- from kubernetes import client
81
- from kubernetes.client import models as k8s
82
81
  from sqlalchemy.orm import Session
83
82
 
83
+ from airflow.executors import workloads
84
84
  from airflow.executors.base_executor import CommandType
85
85
  from airflow.models.taskinstance import TaskInstance
86
86
  from airflow.models.taskinstancekey import TaskInstanceKey
@@ -91,6 +91,8 @@ if TYPE_CHECKING:
91
91
  from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils import (
92
92
  AirflowKubernetesScheduler,
93
93
  )
94
+ from kubernetes import client
95
+ from kubernetes.client import models as k8s
94
96
 
95
97
  # CLI Args
96
98
  ARG_NAMESPACE = Arg(
@@ -137,6 +139,11 @@ class KubernetesExecutor(BaseExecutor):
137
139
  RUNNING_POD_LOG_LINES = 100
138
140
  supports_ad_hoc_ti_run: bool = True
139
141
 
142
+ if TYPE_CHECKING and AIRFLOW_V_3_0_PLUS:
143
+ # In the v3 path, we store workloads, not commands as strings.
144
+ # TODO: TaskSDK: move this type change into BaseExecutor
145
+ queued_tasks: dict[TaskInstanceKey, workloads.All] # type: ignore[assignment]
146
+
140
147
  def __init__(self):
141
148
  self.kube_config = KubeConfig()
142
149
  self._manager = multiprocessing.Manager()
@@ -145,7 +152,6 @@ class KubernetesExecutor(BaseExecutor):
145
152
  self.kube_scheduler: AirflowKubernetesScheduler | None = None
146
153
  self.kube_client: client.CoreV1Api | None = None
147
154
  self.scheduler_job_id: str | None = None
148
- self.event_scheduler: EventScheduler | None = None
149
155
  self.last_handled: dict[TaskInstanceKey, float] = {}
150
156
  self.kubernetes_queue: str | None = None
151
157
  self.task_publish_retries: Counter[TaskInstanceKey] = Counter()
@@ -218,96 +224,6 @@ class KubernetesExecutor(BaseExecutor):
218
224
  pod_combined_search_str_to_pod_map[search_str] = pod
219
225
  return pod_combined_search_str_to_pod_map
220
226
 
221
- @provide_session
222
- def clear_not_launched_queued_tasks(self, session: Session = NEW_SESSION) -> None:
223
- """
224
- Clear tasks that were not yet launched, but were previously queued.
225
-
226
- Tasks can end up in a "Queued" state when a rescheduled/deferred operator
227
- comes back up for execution (with the same try_number) before the
228
- pod of its previous incarnation has been fully removed (we think).
229
-
230
- It's also possible when an executor abruptly shuts down (leaving a non-empty
231
- task_queue on that executor), but that scenario is handled via normal adoption.
232
-
233
- This method checks each of our queued tasks to see if the corresponding pod
234
- is around, and if not, and there's no matching entry in our own
235
- task_queue, marks it for re-execution.
236
- """
237
- if TYPE_CHECKING:
238
- assert self.kube_client
239
- from airflow.models.taskinstance import TaskInstance
240
-
241
- hybrid_executor_enabled = hasattr(TaskInstance, "executor")
242
- default_executor_alias = None
243
- if hybrid_executor_enabled:
244
- from airflow.executors.executor_loader import ExecutorLoader
245
-
246
- default_executor_name = ExecutorLoader.get_default_executor_name()
247
- default_executor_alias = default_executor_name.alias
248
-
249
- with Stats.timer("kubernetes_executor.clear_not_launched_queued_tasks.duration"):
250
- self.log.debug("Clearing tasks that have not been launched")
251
- query = select(TaskInstance).where(
252
- TaskInstance.state == TaskInstanceState.QUEUED,
253
- TaskInstance.queued_by_job_id == self.job_id,
254
- )
255
- if self.kubernetes_queue:
256
- query = query.where(TaskInstance.queue == self.kubernetes_queue)
257
- # KUBERNETES_EXECUTOR is the string name/alias of the "core" executor represented by this
258
- # module. The ExecutorName for "core" executors always contains an alias and cannot be modified
259
- # to be different from the constant (in this case KUBERNETES_EXECUTOR).
260
- elif hybrid_executor_enabled and default_executor_alias == KUBERNETES_EXECUTOR:
261
- query = query.where(
262
- or_(
263
- TaskInstance.executor == KUBERNETES_EXECUTOR,
264
- TaskInstance.executor.is_(None),
265
- ),
266
- )
267
- elif hybrid_executor_enabled:
268
- query = query.where(TaskInstance.executor == KUBERNETES_EXECUTOR)
269
- queued_tis: list[TaskInstance] = session.scalars(query).all()
270
- self.log.info("Found %s queued task instances", len(queued_tis))
271
-
272
- # Go through the "last seen" dictionary and clean out old entries
273
- allowed_age = self.kube_config.worker_pods_queued_check_interval * 3
274
- for key, timestamp in list(self.last_handled.items()):
275
- if time.time() - timestamp > allowed_age:
276
- del self.last_handled[key]
277
-
278
- if not queued_tis:
279
- return
280
-
281
- pod_combined_search_str_to_pod_map = self.get_pod_combined_search_str_to_pod_map()
282
-
283
- for ti in queued_tis:
284
- self.log.debug("Checking task instance %s", ti)
285
-
286
- # Check to see if we've handled it ourselves recently
287
- if ti.key in self.last_handled:
288
- continue
289
-
290
- # Build the pod selector
291
- base_selector = f"dag_id={ti.dag_id},task_id={ti.task_id}"
292
- if ti.map_index >= 0:
293
- # Old tasks _couldn't_ be mapped, so we don't have to worry about compat
294
- base_selector += f",map_index={ti.map_index}"
295
-
296
- search_str = f"{base_selector},run_id={ti.run_id}"
297
- if search_str in pod_combined_search_str_to_pod_map:
298
- continue
299
- self.log.info("TaskInstance: %s found in queued state but was not launched, rescheduling", ti)
300
- session.execute(
301
- update(TaskInstance)
302
- .where(
303
- TaskInstance.dag_id == ti.dag_id,
304
- TaskInstance.task_id == ti.task_id,
305
- TaskInstance.run_id == ti.run_id,
306
- TaskInstance.map_index == ti.map_index,
307
- )
308
- .values(state=TaskInstanceState.SCHEDULED)
309
- )
310
-
311
227
  def start(self) -> None:
312
228
  """Start the executor."""
313
229
  self.log.info("Start Kubernetes executor")
@@ -325,15 +241,6 @@ class KubernetesExecutor(BaseExecutor):
325
241
  kube_client=self.kube_client,
326
242
  scheduler_job_id=self.scheduler_job_id,
327
243
  )
328
- self.event_scheduler = EventScheduler()
329
-
330
- self.event_scheduler.call_regular_interval(
331
- self.kube_config.worker_pods_queued_check_interval,
332
- self.clear_not_launched_queued_tasks,
333
- )
334
- # We also call this at startup as that's the most likely time to see
335
- # stuck queued tasks
336
- self.clear_not_launched_queued_tasks()
337
244
 
338
245
  def execute_async(
339
246
  self,
@@ -351,8 +258,6 @@ class KubernetesExecutor(BaseExecutor):
351
258
  else:
352
259
  self.log.info("Add task %s with command %s", key, command)
353
260
 
354
- from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
355
-
356
261
  try:
357
262
  kube_executor_config = PodGenerator.from_obj(executor_config)
358
263
  except Exception:
@@ -370,6 +275,29 @@ class KubernetesExecutor(BaseExecutor):
370
275
  # try and remove it from the QUEUED state while we process it
371
276
  self.last_handled[key] = time.time()
372
277
 
278
+ def queue_workload(self, workload: workloads.All, session: Session | None) -> None:
279
+ from airflow.executors import workloads
280
+
281
+ if not isinstance(workload, workloads.ExecuteTask):
282
+ raise RuntimeError(f"{type(self)} cannot handle workloads of type {type(workload)}")
283
+ ti = workload.ti
284
+ self.queued_tasks[ti.key] = workload
285
+
286
+ def _process_workloads(self, workloads: list[workloads.All]) -> None:
287
+ # Airflow V3 version
288
+ for w in workloads:
289
+ # TODO: AIP-72 handle populating tokens once https://github.com/apache/airflow/issues/45107 is handled.
290
+ command = [w]
291
+ key = w.ti.key # type: ignore[union-attr]
292
+ queue = w.ti.queue # type: ignore[union-attr]
293
+
294
+ # TODO: will be handled by https://github.com/apache/airflow/issues/46892
295
+ executor_config = {} # type: ignore[var-annotated]
296
+
297
+ del self.queued_tasks[key]
298
+ self.execute_async(key=key, command=command, queue=queue, executor_config=executor_config) # type: ignore[arg-type]
299
+ self.running.add(key)
300
+
373
301
  def sync(self) -> None:
374
302
  """Synchronize task state."""
375
303
  if TYPE_CHECKING:
@@ -378,7 +306,6 @@ class KubernetesExecutor(BaseExecutor):
378
306
  assert self.kube_config
379
307
  assert self.result_queue
380
308
  assert self.task_queue
381
- assert self.event_scheduler
382
309
 
383
310
  if self.running:
384
311
  self.log.debug("self.running: %s", self.running)
@@ -466,10 +393,6 @@ class KubernetesExecutor(BaseExecutor):
466
393
  finally:
467
394
  self.task_queue.task_done()
468
395
 
469
- # Run any pending timed events
470
- next_event = self.event_scheduler.run(blocking=False)
471
- self.log.debug("Next timed event is in %f", next_event)
472
-
473
396
  @provide_session
474
397
  def _change_state(
475
398
  self,
@@ -23,8 +23,6 @@ import time
23
23
  from queue import Empty, Queue
24
24
  from typing import TYPE_CHECKING, Any
25
25
 
26
- from kubernetes import client, watch
27
- from kubernetes.client.rest import ApiException
28
26
  from urllib3.exceptions import ReadTimeoutError
29
27
 
30
28
  from airflow.exceptions import AirflowException
@@ -45,15 +43,16 @@ from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
45
43
  from airflow.utils.log.logging_mixin import LoggingMixin
46
44
  from airflow.utils.singleton import Singleton
47
45
  from airflow.utils.state import TaskInstanceState
46
+ from kubernetes import client, watch
47
+ from kubernetes.client.rest import ApiException
48
48
 
49
49
  if TYPE_CHECKING:
50
- from kubernetes.client import Configuration, models as k8s
51
-
52
50
  from airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types import (
53
51
  KubernetesJobType,
54
52
  KubernetesResultsType,
55
53
  KubernetesWatchType,
56
54
  )
55
+ from kubernetes.client import Configuration, models as k8s
57
56
 
58
57
 
59
58
  class ResourceVersion(metaclass=Singleton):
@@ -147,10 +146,10 @@ class KubernetesJobWatcher(multiprocessing.Process, LoggingMixin):
147
146
  # For info about k8s timeout settings see
148
147
  # https://github.com/kubernetes-client/python/blob/v29.0.0/examples/watch/timeout-settings.md
149
148
  # and https://github.com/kubernetes-client/python/blob/v29.0.0/kubernetes/client/api_client.py#L336-L339
150
- client_timeout = 30
151
- server_conn_timeout = 3600
152
- kwargs["_request_timeout"] = client_timeout
153
- kwargs["timeout_seconds"] = server_conn_timeout
149
+ if "_request_timeout" not in kwargs:
150
+ kwargs["_request_timeout"] = 30
151
+ if "timeout_seconds" not in kwargs:
152
+ kwargs["timeout_seconds"] = 3600
154
153
 
155
154
  logical_date_key = get_logical_date_key()
156
155
  for event in self._pod_events(kube_client=kube_client, query_kwargs=kwargs):
@@ -231,12 +230,8 @@ class KubernetesJobWatcher(multiprocessing.Process, LoggingMixin):
231
230
  )
232
231
  elif status == "Pending":
233
232
  # deletion_timestamp is set by kube server when a graceful deletion is requested.
234
- # since kube server have received request to delete pod set TI state failed
235
233
  if event["type"] == "DELETED" and pod.metadata.deletion_timestamp:
236
234
  self.log.info("Event: Failed to start pod %s, annotations: %s", pod_name, annotations_string)
237
- self.watcher_queue.put(
238
- (pod_name, namespace, TaskInstanceState.FAILED, annotations, resource_version)
239
- )
240
235
  elif (
241
236
  self.kube_config.worker_pod_pending_fatal_container_state_reasons
242
237
  and "status" in event["raw_object"]
@@ -393,8 +388,24 @@ class AirflowKubernetesScheduler(LoggingMixin):
393
388
  key, command, kube_executor_config, pod_template_file = next_job
394
389
 
395
390
  dag_id, task_id, run_id, try_number, map_index = key
396
-
397
- if command[0:3] != ["airflow", "tasks", "run"]:
391
+ ser_input = ""
392
+ if len(command) == 1:
393
+ from airflow.executors.workloads import ExecuteTask
394
+
395
+ if isinstance(command[0], ExecuteTask):
396
+ workload = command[0]
397
+ ser_input = workload.model_dump_json()
398
+ command = [
399
+ "python",
400
+ "-m",
401
+ "airflow.sdk.execution_time.execute_workload",
402
+ "/tmp/execute/input.json",
403
+ ]
404
+ else:
405
+ raise ValueError(
406
+ f"KubernetesExecutor doesn't know how to handle workload of type: {type(command[0])}"
407
+ )
408
+ elif command[0:3] != ["airflow", "tasks", "run"]:
398
409
  raise ValueError('The command must start with ["airflow", "tasks", "run"].')
399
410
 
400
411
  base_worker_pod = get_base_pod_from_template(pod_template_file, self.kube_config)
@@ -415,7 +426,8 @@ class AirflowKubernetesScheduler(LoggingMixin):
415
426
  map_index=map_index,
416
427
  date=None,
417
428
  run_id=run_id,
418
- args=command,
429
+ args=list(command),
430
+ content_json_for_volume=ser_input,
419
431
  pod_override_object=kube_executor_config,
420
432
  base_worker_pod=base_worker_pod,
421
433
  with_mutation_hook=True,