apache-airflow-providers-cncf-kubernetes 8.4.0__tar.gz → 8.4.1__tar.gz
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.
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/PKG-INFO +6 -6
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/README.rst +3 -3
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/__init__.py +1 -1
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py +10 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/executors/local_kubernetes_executor.py +5 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/get_provider_info.py +2 -1
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/hooks/kubernetes.py +40 -1
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/job.py +53 -2
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/triggers/job.py +54 -1
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/pyproject.toml +3 -3
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/LICENSE +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/backcompat/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/backcompat/backwards_compat_converters.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/callbacks.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/cli/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/cli/kubernetes_command.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/decorators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/decorators/kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/executors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_types.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/hooks/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/k8s_model.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/kube_client.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/kube_config.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/basic_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/kubernetes_helper_functions.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/custom_object_launcher.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/kubernetes_pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/resource.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_generator.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_generator_deprecated.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_launcher_deprecated.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_image_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_volume_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/git_sync_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/python_kubernetes_script.jinja2 +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/python_kubernetes_script.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/resource_convert/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/resource_convert/configmap.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/resource_convert/env_variable.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/resource_convert/secret.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/secret.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/sensors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/template_rendering.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/triggers/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/triggers/kubernetes_pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/triggers/pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/utils/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/utils/delete_from.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/utils/pod_manager.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-8.4.0 → apache_airflow_providers_cncf_kubernetes-8.4.1}/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: apache-airflow-providers-cncf-kubernetes
|
|
3
|
-
Version: 8.4.
|
|
3
|
+
Version: 8.4.1
|
|
4
4
|
Summary: Provider package apache-airflow-providers-cncf-kubernetes for Apache Airflow
|
|
5
5
|
Keywords: airflow-provider,cncf.kubernetes,airflow,integration
|
|
6
6
|
Author-email: Apache Software Foundation <dev@airflow.apache.org>
|
|
@@ -29,8 +29,8 @@ Requires-Dist: google-re2>=1.0
|
|
|
29
29
|
Requires-Dist: kubernetes>=29.0.0,<=30.1.0
|
|
30
30
|
Requires-Dist: kubernetes_asyncio>=29.0.0,<=30.1.0
|
|
31
31
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
32
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
33
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
32
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/changelog.html
|
|
33
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1
|
|
34
34
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
35
35
|
Project-URL: Source Code, https://github.com/apache/airflow
|
|
36
36
|
Project-URL: Twitter, https://twitter.com/ApacheAirflow
|
|
@@ -80,7 +80,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
|
80
80
|
|
|
81
81
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
82
82
|
|
|
83
|
-
Release: ``8.4.
|
|
83
|
+
Release: ``8.4.1``
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
`Kubernetes <https://kubernetes.io/>`__
|
|
@@ -93,7 +93,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
|
|
|
93
93
|
are in ``airflow.providers.cncf.kubernetes`` python package.
|
|
94
94
|
|
|
95
95
|
You can find package information and changelog for the provider
|
|
96
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
96
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/>`_.
|
|
97
97
|
|
|
98
98
|
Installation
|
|
99
99
|
------------
|
|
@@ -120,4 +120,4 @@ PIP package Version required
|
|
|
120
120
|
====================== =====================
|
|
121
121
|
|
|
122
122
|
The changelog for the provider package can be found in the
|
|
123
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
123
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/changelog.html>`_.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
44
44
|
|
|
45
|
-
Release: ``8.4.
|
|
45
|
+
Release: ``8.4.1``
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
`Kubernetes <https://kubernetes.io/>`__
|
|
@@ -55,7 +55,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
|
|
|
55
55
|
are in ``airflow.providers.cncf.kubernetes`` python package.
|
|
56
56
|
|
|
57
57
|
You can find package information and changelog for the provider
|
|
58
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
58
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/>`_.
|
|
59
59
|
|
|
60
60
|
Installation
|
|
61
61
|
------------
|
|
@@ -82,4 +82,4 @@ PIP package Version required
|
|
|
82
82
|
====================== =====================
|
|
83
83
|
|
|
84
84
|
The changelog for the provider package can be found in the
|
|
85
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
85
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/changelog.html>`_.
|
|
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["__version__"]
|
|
31
31
|
|
|
32
|
-
__version__ = "8.4.
|
|
32
|
+
__version__ = "8.4.1"
|
|
33
33
|
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
|
35
35
|
"2.8.0"
|
|
@@ -220,6 +220,16 @@ class KubernetesJobWatcher(multiprocessing.Process, LoggingMixin):
|
|
|
220
220
|
# However, need to free the executor slot from the current executor.
|
|
221
221
|
self.log.info("Event: pod %s adopted, annotations: %s", pod_name, annotations_string)
|
|
222
222
|
self.watcher_queue.put((pod_name, namespace, ADOPTED, annotations, resource_version))
|
|
223
|
+
elif hasattr(pod.status, "reason") and pod.status.reason == "ProviderFailed":
|
|
224
|
+
# Most likely this happens due to Kubernetes setup (virtual kubelet, virtual nodes, etc.)
|
|
225
|
+
self.log.error(
|
|
226
|
+
"Event: %s failed to start with reason ProviderFailed, annotations: %s",
|
|
227
|
+
pod_name,
|
|
228
|
+
annotations_string,
|
|
229
|
+
)
|
|
230
|
+
self.watcher_queue.put(
|
|
231
|
+
(pod_name, namespace, TaskInstanceState.FAILED, annotations, resource_version)
|
|
232
|
+
)
|
|
223
233
|
elif status == "Pending":
|
|
224
234
|
# deletion_timestamp is set by kube server when a graceful deletion is requested.
|
|
225
235
|
# since kube server have received request to delete pod set TI state failed
|
|
@@ -103,6 +103,11 @@ class LocalKubernetesExecutor(LoggingMixin):
|
|
|
103
103
|
"""Number of new tasks this executor instance can accept."""
|
|
104
104
|
return self.local_executor.slots_available
|
|
105
105
|
|
|
106
|
+
@property
|
|
107
|
+
def slots_occupied(self):
|
|
108
|
+
"""Number of tasks this executor instance is currently managing."""
|
|
109
|
+
return len(self.running) + len(self.queued_tasks)
|
|
110
|
+
|
|
106
111
|
def queue_command(
|
|
107
112
|
self,
|
|
108
113
|
task_instance: TaskInstance,
|
|
@@ -28,8 +28,9 @@ def get_provider_info():
|
|
|
28
28
|
"name": "Kubernetes",
|
|
29
29
|
"description": "`Kubernetes <https://kubernetes.io/>`__\n",
|
|
30
30
|
"state": "ready",
|
|
31
|
-
"source-date-epoch":
|
|
31
|
+
"source-date-epoch": 1724520459,
|
|
32
32
|
"versions": [
|
|
33
|
+
"8.4.1",
|
|
33
34
|
"8.4.0",
|
|
34
35
|
"8.3.4",
|
|
35
36
|
"8.3.3",
|
|
@@ -37,7 +37,11 @@ from airflow.hooks.base import BaseHook
|
|
|
37
37
|
from airflow.models import Connection
|
|
38
38
|
from airflow.providers.cncf.kubernetes.kube_client import _disable_verify_ssl, _enable_tcp_keepalive
|
|
39
39
|
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import should_retry_creation
|
|
40
|
-
from airflow.providers.cncf.kubernetes.utils.pod_manager import
|
|
40
|
+
from airflow.providers.cncf.kubernetes.utils.pod_manager import (
|
|
41
|
+
PodOperatorHookProtocol,
|
|
42
|
+
container_is_completed,
|
|
43
|
+
container_is_running,
|
|
44
|
+
)
|
|
41
45
|
from airflow.utils import yaml
|
|
42
46
|
|
|
43
47
|
if TYPE_CHECKING:
|
|
@@ -682,6 +686,7 @@ class AsyncKubernetesHook(KubernetesHook):
|
|
|
682
686
|
|
|
683
687
|
if self.config_dict:
|
|
684
688
|
self.log.debug(LOADING_KUBE_CONFIG_FILE_RESOURCE.format("config dictionary"))
|
|
689
|
+
self._is_in_cluster = False
|
|
685
690
|
await async_config.load_kube_config_from_dict(self.config_dict)
|
|
686
691
|
return async_client.ApiClient()
|
|
687
692
|
|
|
@@ -830,3 +835,37 @@ class AsyncKubernetesHook(KubernetesHook):
|
|
|
830
835
|
return job
|
|
831
836
|
self.log.info("The job '%s' is incomplete. Sleeping for %i sec.", name, poll_interval)
|
|
832
837
|
await asyncio.sleep(poll_interval)
|
|
838
|
+
|
|
839
|
+
async def wait_until_container_complete(
|
|
840
|
+
self, name: str, namespace: str, container_name: str, poll_interval: float = 10
|
|
841
|
+
) -> None:
|
|
842
|
+
"""
|
|
843
|
+
Wait for the given container in the given pod to be completed.
|
|
844
|
+
|
|
845
|
+
:param name: Name of Pod to fetch.
|
|
846
|
+
:param namespace: Namespace of the Pod.
|
|
847
|
+
:param container_name: name of the container within the pod to monitor
|
|
848
|
+
"""
|
|
849
|
+
while True:
|
|
850
|
+
pod = await self.get_pod(name=name, namespace=namespace)
|
|
851
|
+
if container_is_completed(pod=pod, container_name=container_name):
|
|
852
|
+
break
|
|
853
|
+
self.log.info("Waiting for container '%s' state to be completed", container_name)
|
|
854
|
+
await asyncio.sleep(poll_interval)
|
|
855
|
+
|
|
856
|
+
async def wait_until_container_started(
|
|
857
|
+
self, name: str, namespace: str, container_name: str, poll_interval: float = 10
|
|
858
|
+
) -> None:
|
|
859
|
+
"""
|
|
860
|
+
Wait for the given container in the given pod to be started.
|
|
861
|
+
|
|
862
|
+
:param name: Name of Pod to fetch.
|
|
863
|
+
:param namespace: Namespace of the Pod.
|
|
864
|
+
:param container_name: name of the container within the pod to monitor
|
|
865
|
+
"""
|
|
866
|
+
while True:
|
|
867
|
+
pod = await self.get_pod(name=name, namespace=namespace)
|
|
868
|
+
if container_is_running(pod=pod, container_name=container_name):
|
|
869
|
+
break
|
|
870
|
+
self.log.info("Waiting for container '%s' state to be running", container_name)
|
|
871
|
+
await asyncio.sleep(poll_interval)
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import copy
|
|
22
|
+
import json
|
|
22
23
|
import logging
|
|
23
24
|
import os
|
|
24
25
|
from functools import cached_property
|
|
@@ -39,6 +40,7 @@ from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import (
|
|
|
39
40
|
from airflow.providers.cncf.kubernetes.operators.pod import KubernetesPodOperator
|
|
40
41
|
from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator, merge_objects
|
|
41
42
|
from airflow.providers.cncf.kubernetes.triggers.job import KubernetesJobTrigger
|
|
43
|
+
from airflow.providers.cncf.kubernetes.utils.pod_manager import EMPTY_XCOM_RESULT, PodNotFoundException
|
|
42
44
|
from airflow.utils import yaml
|
|
43
45
|
from airflow.utils.context import Context
|
|
44
46
|
|
|
@@ -135,7 +137,7 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
135
137
|
return hook
|
|
136
138
|
|
|
137
139
|
@cached_property
|
|
138
|
-
def
|
|
140
|
+
def job_client(self) -> BatchV1Api:
|
|
139
141
|
return self.hook.batch_v1_client
|
|
140
142
|
|
|
141
143
|
def create_job(self, job_request_obj: k8s.V1Job) -> k8s.V1Job:
|
|
@@ -150,6 +152,11 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
150
152
|
"Deferrable mode is available only with parameter `wait_until_job_complete=True`. "
|
|
151
153
|
"Please, set it up."
|
|
152
154
|
)
|
|
155
|
+
if (self.get_logs or self.do_xcom_push) and not self.wait_until_job_complete:
|
|
156
|
+
self.log.warning(
|
|
157
|
+
"Getting Logs and pushing to XCom are available only with parameter `wait_until_job_complete=True`. "
|
|
158
|
+
"Please, set it up."
|
|
159
|
+
)
|
|
153
160
|
self.job_request_obj = self.build_job_request_obj(context)
|
|
154
161
|
self.job = self.create_job( # must set `self.job` for `on_kill`
|
|
155
162
|
job_request_obj=self.job_request_obj
|
|
@@ -159,16 +166,34 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
159
166
|
ti.xcom_push(key="job_name", value=self.job.metadata.name)
|
|
160
167
|
ti.xcom_push(key="job_namespace", value=self.job.metadata.namespace)
|
|
161
168
|
|
|
169
|
+
if self.pod is None:
|
|
170
|
+
self.pod = self.get_or_create_pod( # must set `self.pod` for `on_kill`
|
|
171
|
+
pod_request_obj=self.pod_request_obj,
|
|
172
|
+
context=context,
|
|
173
|
+
)
|
|
174
|
+
|
|
162
175
|
if self.wait_until_job_complete and self.deferrable:
|
|
163
176
|
self.execute_deferrable()
|
|
164
177
|
return
|
|
165
178
|
|
|
166
179
|
if self.wait_until_job_complete:
|
|
180
|
+
if self.do_xcom_push:
|
|
181
|
+
self.pod_manager.await_container_completion(
|
|
182
|
+
pod=self.pod, container_name=self.base_container_name
|
|
183
|
+
)
|
|
184
|
+
self.pod_manager.await_xcom_sidecar_container_start(pod=self.pod)
|
|
185
|
+
xcom_result = self.extract_xcom(pod=self.pod)
|
|
167
186
|
self.job = self.hook.wait_until_job_complete(
|
|
168
187
|
job_name=self.job.metadata.name,
|
|
169
188
|
namespace=self.job.metadata.namespace,
|
|
170
189
|
job_poll_interval=self.job_poll_interval,
|
|
171
190
|
)
|
|
191
|
+
if self.get_logs:
|
|
192
|
+
self.pod_manager.fetch_requested_container_logs(
|
|
193
|
+
pod=self.pod,
|
|
194
|
+
containers=self.container_logs,
|
|
195
|
+
follow_logs=True,
|
|
196
|
+
)
|
|
172
197
|
|
|
173
198
|
ti.xcom_push(key="job", value=self.job.to_dict())
|
|
174
199
|
if self.wait_until_job_complete:
|
|
@@ -176,17 +201,24 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
176
201
|
raise AirflowException(
|
|
177
202
|
f"Kubernetes job '{self.job.metadata.name}' is failed with error '{error_message}'"
|
|
178
203
|
)
|
|
204
|
+
if self.do_xcom_push:
|
|
205
|
+
return xcom_result
|
|
179
206
|
|
|
180
207
|
def execute_deferrable(self):
|
|
181
208
|
self.defer(
|
|
182
209
|
trigger=KubernetesJobTrigger(
|
|
183
210
|
job_name=self.job.metadata.name, # type: ignore[union-attr]
|
|
184
211
|
job_namespace=self.job.metadata.namespace, # type: ignore[union-attr]
|
|
212
|
+
pod_name=self.pod.metadata.name, # type: ignore[union-attr]
|
|
213
|
+
pod_namespace=self.pod.metadata.namespace, # type: ignore[union-attr]
|
|
214
|
+
base_container_name=self.base_container_name,
|
|
185
215
|
kubernetes_conn_id=self.kubernetes_conn_id,
|
|
186
216
|
cluster_context=self.cluster_context,
|
|
187
217
|
config_file=self.config_file,
|
|
188
218
|
in_cluster=self.in_cluster,
|
|
189
219
|
poll_interval=self.job_poll_interval,
|
|
220
|
+
get_logs=self.get_logs,
|
|
221
|
+
do_xcom_push=self.do_xcom_push,
|
|
190
222
|
),
|
|
191
223
|
method_name="execute_complete",
|
|
192
224
|
)
|
|
@@ -197,6 +229,22 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
197
229
|
if event["status"] == "error":
|
|
198
230
|
raise AirflowException(event["message"])
|
|
199
231
|
|
|
232
|
+
if self.get_logs:
|
|
233
|
+
pod_name = event["pod_name"]
|
|
234
|
+
pod_namespace = event["pod_namespace"]
|
|
235
|
+
self.pod = self.hook.get_pod(pod_name, pod_namespace)
|
|
236
|
+
if not self.pod:
|
|
237
|
+
raise PodNotFoundException("Could not find pod after resuming from deferral")
|
|
238
|
+
self._write_logs(self.pod)
|
|
239
|
+
|
|
240
|
+
if self.do_xcom_push:
|
|
241
|
+
xcom_result = event["xcom_result"]
|
|
242
|
+
if isinstance(xcom_result, str) and xcom_result.rstrip() == EMPTY_XCOM_RESULT:
|
|
243
|
+
self.log.info("xcom result file is empty.")
|
|
244
|
+
return None
|
|
245
|
+
self.log.info("xcom result: \n%s", xcom_result)
|
|
246
|
+
return json.loads(xcom_result)
|
|
247
|
+
|
|
200
248
|
@staticmethod
|
|
201
249
|
def deserialize_job_template_file(path: str) -> k8s.V1Job:
|
|
202
250
|
"""
|
|
@@ -229,7 +277,9 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
229
277
|
}
|
|
230
278
|
if self.termination_grace_period is not None:
|
|
231
279
|
kwargs.update(grace_period_seconds=self.termination_grace_period)
|
|
232
|
-
self.
|
|
280
|
+
self.job_client.delete_namespaced_job(**kwargs)
|
|
281
|
+
if self.pod:
|
|
282
|
+
super().on_kill()
|
|
233
283
|
|
|
234
284
|
def build_job_request_obj(self, context: Context | None = None) -> k8s.V1Job:
|
|
235
285
|
"""
|
|
@@ -254,6 +304,7 @@ class KubernetesJobOperator(KubernetesPodOperator):
|
|
|
254
304
|
metadata=pod_template.metadata,
|
|
255
305
|
spec=pod_template.spec,
|
|
256
306
|
)
|
|
307
|
+
self.pod_request_obj = pod_template
|
|
257
308
|
|
|
258
309
|
job = k8s.V1Job(
|
|
259
310
|
api_version="batch/v1",
|
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
# under the License.
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
import asyncio
|
|
19
20
|
from functools import cached_property
|
|
20
21
|
from typing import TYPE_CHECKING, Any, AsyncIterator
|
|
21
22
|
|
|
22
|
-
from airflow.providers.cncf.kubernetes.hooks.kubernetes import AsyncKubernetesHook
|
|
23
|
+
from airflow.providers.cncf.kubernetes.hooks.kubernetes import AsyncKubernetesHook, KubernetesHook
|
|
24
|
+
from airflow.providers.cncf.kubernetes.utils.pod_manager import PodManager
|
|
25
|
+
from airflow.providers.cncf.kubernetes.utils.xcom_sidecar import PodDefaults
|
|
23
26
|
from airflow.triggers.base import BaseTrigger, TriggerEvent
|
|
24
27
|
|
|
25
28
|
if TYPE_CHECKING:
|
|
@@ -32,32 +35,49 @@ class KubernetesJobTrigger(BaseTrigger):
|
|
|
32
35
|
|
|
33
36
|
:param job_name: The name of the job.
|
|
34
37
|
:param job_namespace: The namespace of the job.
|
|
38
|
+
:param pod_name: The name of the Pod.
|
|
39
|
+
:param pod_namespace: The namespace of the Pod.
|
|
40
|
+
:param base_container_name: The name of the base container in the pod.
|
|
35
41
|
:param kubernetes_conn_id: The :ref:`kubernetes connection id <howto/connection:kubernetes>`
|
|
36
42
|
for the Kubernetes cluster.
|
|
37
43
|
:param cluster_context: Context that points to kubernetes cluster.
|
|
38
44
|
:param config_file: Path to kubeconfig file.
|
|
39
45
|
:param poll_interval: Polling period in seconds to check for the status.
|
|
40
46
|
:param in_cluster: run kubernetes client with in_cluster configuration.
|
|
47
|
+
:param get_logs: get the stdout of the base container as logs of the tasks.
|
|
48
|
+
:param do_xcom_push: If True, the content of the file
|
|
49
|
+
/airflow/xcom/return.json in the container will also be pushed to an
|
|
50
|
+
XCom when the container completes.
|
|
41
51
|
"""
|
|
42
52
|
|
|
43
53
|
def __init__(
|
|
44
54
|
self,
|
|
45
55
|
job_name: str,
|
|
46
56
|
job_namespace: str,
|
|
57
|
+
pod_name: str,
|
|
58
|
+
pod_namespace: str,
|
|
59
|
+
base_container_name: str,
|
|
47
60
|
kubernetes_conn_id: str | None = None,
|
|
48
61
|
poll_interval: float = 10.0,
|
|
49
62
|
cluster_context: str | None = None,
|
|
50
63
|
config_file: str | None = None,
|
|
51
64
|
in_cluster: bool | None = None,
|
|
65
|
+
get_logs: bool = True,
|
|
66
|
+
do_xcom_push: bool = False,
|
|
52
67
|
):
|
|
53
68
|
super().__init__()
|
|
54
69
|
self.job_name = job_name
|
|
55
70
|
self.job_namespace = job_namespace
|
|
71
|
+
self.pod_name = pod_name
|
|
72
|
+
self.pod_namespace = pod_namespace
|
|
73
|
+
self.base_container_name = base_container_name
|
|
56
74
|
self.kubernetes_conn_id = kubernetes_conn_id
|
|
57
75
|
self.poll_interval = poll_interval
|
|
58
76
|
self.cluster_context = cluster_context
|
|
59
77
|
self.config_file = config_file
|
|
60
78
|
self.in_cluster = in_cluster
|
|
79
|
+
self.get_logs = get_logs
|
|
80
|
+
self.do_xcom_push = do_xcom_push
|
|
61
81
|
|
|
62
82
|
def serialize(self) -> tuple[str, dict[str, Any]]:
|
|
63
83
|
"""Serialize KubernetesCreateJobTrigger arguments and classpath."""
|
|
@@ -66,16 +86,36 @@ class KubernetesJobTrigger(BaseTrigger):
|
|
|
66
86
|
{
|
|
67
87
|
"job_name": self.job_name,
|
|
68
88
|
"job_namespace": self.job_namespace,
|
|
89
|
+
"pod_name": self.pod_name,
|
|
90
|
+
"pod_namespace": self.pod_namespace,
|
|
91
|
+
"base_container_name": self.base_container_name,
|
|
69
92
|
"kubernetes_conn_id": self.kubernetes_conn_id,
|
|
70
93
|
"poll_interval": self.poll_interval,
|
|
71
94
|
"cluster_context": self.cluster_context,
|
|
72
95
|
"config_file": self.config_file,
|
|
73
96
|
"in_cluster": self.in_cluster,
|
|
97
|
+
"get_logs": self.get_logs,
|
|
98
|
+
"do_xcom_push": self.do_xcom_push,
|
|
74
99
|
},
|
|
75
100
|
)
|
|
76
101
|
|
|
77
102
|
async def run(self) -> AsyncIterator[TriggerEvent]: # type: ignore[override]
|
|
78
103
|
"""Get current job status and yield a TriggerEvent."""
|
|
104
|
+
if self.get_logs or self.do_xcom_push:
|
|
105
|
+
pod = await self.hook.get_pod(name=self.pod_name, namespace=self.pod_namespace)
|
|
106
|
+
if self.do_xcom_push:
|
|
107
|
+
await self.hook.wait_until_container_complete(
|
|
108
|
+
name=self.pod_name, namespace=self.pod_namespace, container_name=self.base_container_name
|
|
109
|
+
)
|
|
110
|
+
self.log.info("Checking if xcom sidecar container is started.")
|
|
111
|
+
await self.hook.wait_until_container_started(
|
|
112
|
+
name=self.pod_name,
|
|
113
|
+
namespace=self.pod_namespace,
|
|
114
|
+
container_name=PodDefaults.SIDECAR_CONTAINER_NAME,
|
|
115
|
+
)
|
|
116
|
+
self.log.info("Extracting result from xcom sidecar container.")
|
|
117
|
+
loop = asyncio.get_running_loop()
|
|
118
|
+
xcom_result = await loop.run_in_executor(None, self.pod_manager.extract_xcom, pod)
|
|
79
119
|
job: V1Job = await self.hook.wait_until_job_complete(name=self.job_name, namespace=self.job_namespace)
|
|
80
120
|
job_dict = job.to_dict()
|
|
81
121
|
error_message = self.hook.is_job_failed(job=job)
|
|
@@ -83,11 +123,14 @@ class KubernetesJobTrigger(BaseTrigger):
|
|
|
83
123
|
{
|
|
84
124
|
"name": job.metadata.name,
|
|
85
125
|
"namespace": job.metadata.namespace,
|
|
126
|
+
"pod_name": pod.metadata.name if self.get_logs else None,
|
|
127
|
+
"pod_namespace": pod.metadata.namespace if self.get_logs else None,
|
|
86
128
|
"status": "error" if error_message else "success",
|
|
87
129
|
"message": f"Job failed with error: {error_message}"
|
|
88
130
|
if error_message
|
|
89
131
|
else "Job completed successfully",
|
|
90
132
|
"job": job_dict,
|
|
133
|
+
"xcom_result": xcom_result if self.do_xcom_push else None,
|
|
91
134
|
}
|
|
92
135
|
)
|
|
93
136
|
|
|
@@ -99,3 +142,13 @@ class KubernetesJobTrigger(BaseTrigger):
|
|
|
99
142
|
config_file=self.config_file,
|
|
100
143
|
cluster_context=self.cluster_context,
|
|
101
144
|
)
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def pod_manager(self) -> PodManager:
|
|
148
|
+
sync_hook = KubernetesHook(
|
|
149
|
+
conn_id=self.kubernetes_conn_id,
|
|
150
|
+
in_cluster=self.in_cluster,
|
|
151
|
+
config_file=self.config_file,
|
|
152
|
+
cluster_context=self.cluster_context,
|
|
153
|
+
)
|
|
154
|
+
return PodManager(kube_client=sync_hook.core_v1_client)
|
|
@@ -28,7 +28,7 @@ build-backend = "flit_core.buildapi"
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "apache-airflow-providers-cncf-kubernetes"
|
|
31
|
-
version = "8.4.
|
|
31
|
+
version = "8.4.1"
|
|
32
32
|
description = "Provider package apache-airflow-providers-cncf-kubernetes for Apache Airflow"
|
|
33
33
|
readme = "README.rst"
|
|
34
34
|
authors = [
|
|
@@ -66,8 +66,8 @@ dependencies = [
|
|
|
66
66
|
]
|
|
67
67
|
|
|
68
68
|
[project.urls]
|
|
69
|
-
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
70
|
-
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.
|
|
69
|
+
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1"
|
|
70
|
+
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/8.4.1/changelog.html"
|
|
71
71
|
"Bug Tracker" = "https://github.com/apache/airflow/issues"
|
|
72
72
|
"Source Code" = "https://github.com/apache/airflow"
|
|
73
73
|
"Slack Chat" = "https://s.apache.org/airflow-slack"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|