apache-airflow-providers-cncf-kubernetes 10.0.1rc1__tar.gz → 10.1.0rc1__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-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/PKG-INFO +9 -9
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/README.rst +4 -4
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/__init__.py +3 -3
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/cli/kubernetes_command.py +1 -5
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py +7 -3
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/get_provider_info.py +4 -2
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/hooks/kubernetes.py +72 -8
- apache_airflow_providers_cncf_kubernetes-10.1.0rc1/airflow/providers/cncf/kubernetes/operators/kueue.py +111 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/pod.py +42 -11
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/utils/pod_manager.py +89 -6
- apache_airflow_providers_cncf_kubernetes-10.1.0rc1/airflow/providers/cncf/kubernetes/version_compat.py +36 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/pyproject.toml +5 -5
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/LICENSE +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/backcompat/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/backcompat/backwards_compat_converters.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/callbacks.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/cli/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/decorators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/decorators/kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/executors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_types.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/executors/local_kubernetes_executor.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/hooks/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/k8s_model.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/kube_client.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/kube_config.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/kubernetes_executor_templates/basic_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/kubernetes_helper_functions.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/custom_object_launcher.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/job.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/resource.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/operators/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_generator.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_generator_deprecated.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_image_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/dags_in_volume_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/pod_template_file_examples/git_sync_template.yaml +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/python_kubernetes_script.jinja2 +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/python_kubernetes_script.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/resource_convert/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/resource_convert/configmap.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/resource_convert/env_variable.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/resource_convert/secret.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/secret.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/sensors/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/sensors/spark_kubernetes.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/template_rendering.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/triggers/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/triggers/job.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/triggers/pod.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/utils/__init__.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/utils/delete_from.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/utils/k8s_resource_iterator.py +0 -0
- {apache_airflow_providers_cncf_kubernetes-10.0.1rc1 → apache_airflow_providers_cncf_kubernetes-10.1.0rc1}/airflow/providers/cncf/kubernetes/utils/xcom_sidecar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: apache-airflow-providers-cncf-kubernetes
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.1.0rc1
|
|
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>
|
|
@@ -21,18 +21,18 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Topic :: System :: Monitoring
|
|
23
23
|
Requires-Dist: aiofiles>=23.2.0
|
|
24
|
-
Requires-Dist: apache-airflow>=2.
|
|
24
|
+
Requires-Dist: apache-airflow>=2.9.0rc0
|
|
25
25
|
Requires-Dist: asgiref>=3.5.2
|
|
26
26
|
Requires-Dist: cryptography>=41.0.0
|
|
27
27
|
Requires-Dist: google-re2>=1.0
|
|
28
28
|
Requires-Dist: kubernetes>=29.0.0,<=31.0.0
|
|
29
29
|
Requires-Dist: kubernetes_asyncio>=29.0.0,<=31.0.0
|
|
30
30
|
Project-URL: Bug Tracker, https://github.com/apache/airflow/issues
|
|
31
|
-
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
32
|
-
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
31
|
+
Project-URL: Changelog, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/changelog.html
|
|
32
|
+
Project-URL: Documentation, https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0
|
|
33
33
|
Project-URL: Slack Chat, https://s.apache.org/airflow-slack
|
|
34
34
|
Project-URL: Source Code, https://github.com/apache/airflow
|
|
35
|
-
Project-URL: Twitter, https://
|
|
35
|
+
Project-URL: Twitter, https://x.com/ApacheAirflow
|
|
36
36
|
Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
37
37
|
|
|
38
38
|
|
|
@@ -79,7 +79,7 @@ Project-URL: YouTube, https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/
|
|
|
79
79
|
|
|
80
80
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
81
81
|
|
|
82
|
-
Release: ``10.0.
|
|
82
|
+
Release: ``10.1.0.rc1``
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
`Kubernetes <https://kubernetes.io/>`__
|
|
@@ -92,7 +92,7 @@ This is a provider package for ``cncf.kubernetes`` provider. All classes for thi
|
|
|
92
92
|
are in ``airflow.providers.cncf.kubernetes`` python package.
|
|
93
93
|
|
|
94
94
|
You can find package information and changelog for the provider
|
|
95
|
-
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
95
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/>`_.
|
|
96
96
|
|
|
97
97
|
Installation
|
|
98
98
|
------------
|
|
@@ -110,7 +110,7 @@ Requirements
|
|
|
110
110
|
PIP package Version required
|
|
111
111
|
====================== =====================
|
|
112
112
|
``aiofiles`` ``>=23.2.0``
|
|
113
|
-
``apache-airflow`` ``>=2.
|
|
113
|
+
``apache-airflow`` ``>=2.9.0``
|
|
114
114
|
``asgiref`` ``>=3.5.2``
|
|
115
115
|
``cryptography`` ``>=41.0.0``
|
|
116
116
|
``kubernetes`` ``>=29.0.0,<=31.0.0``
|
|
@@ -119,4 +119,4 @@ PIP package Version required
|
|
|
119
119
|
====================== =====================
|
|
120
120
|
|
|
121
121
|
The changelog for the provider package can be found in the
|
|
122
|
-
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
122
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/changelog.html>`_.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
Package ``apache-airflow-providers-cncf-kubernetes``
|
|
44
44
|
|
|
45
|
-
Release: ``10.0.
|
|
45
|
+
Release: ``10.1.0.rc1``
|
|
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/10.0
|
|
58
|
+
in the `documentation <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/>`_.
|
|
59
59
|
|
|
60
60
|
Installation
|
|
61
61
|
------------
|
|
@@ -73,7 +73,7 @@ Requirements
|
|
|
73
73
|
PIP package Version required
|
|
74
74
|
====================== =====================
|
|
75
75
|
``aiofiles`` ``>=23.2.0``
|
|
76
|
-
``apache-airflow`` ``>=2.
|
|
76
|
+
``apache-airflow`` ``>=2.9.0``
|
|
77
77
|
``asgiref`` ``>=3.5.2``
|
|
78
78
|
``cryptography`` ``>=41.0.0``
|
|
79
79
|
``kubernetes`` ``>=29.0.0,<=31.0.0``
|
|
@@ -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/10.0
|
|
85
|
+
`changelog <https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/changelog.html>`_.
|
|
@@ -29,11 +29,11 @@ from airflow import __version__ as airflow_version
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["__version__"]
|
|
31
31
|
|
|
32
|
-
__version__ = "10.0
|
|
32
|
+
__version__ = "10.1.0"
|
|
33
33
|
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
|
35
|
-
"2.
|
|
35
|
+
"2.9.0"
|
|
36
36
|
):
|
|
37
37
|
raise RuntimeError(
|
|
38
|
-
f"The package `apache-airflow-providers-cncf-kubernetes:{__version__}` needs Apache Airflow 2.
|
|
38
|
+
f"The package `apache-airflow-providers-cncf-kubernetes:{__version__}` needs Apache Airflow 2.9.0+"
|
|
39
39
|
)
|
|
@@ -25,22 +25,18 @@ from datetime import datetime, timedelta
|
|
|
25
25
|
from kubernetes import client
|
|
26
26
|
from kubernetes.client.api_client import ApiClient
|
|
27
27
|
from kubernetes.client.rest import ApiException
|
|
28
|
-
from packaging.version import Version
|
|
29
28
|
|
|
30
|
-
from airflow import __version__ as airflow_version
|
|
31
29
|
from airflow.models import DagRun, TaskInstance
|
|
32
30
|
from airflow.providers.cncf.kubernetes import pod_generator
|
|
33
31
|
from airflow.providers.cncf.kubernetes.executors.kubernetes_executor import KubeConfig
|
|
34
32
|
from airflow.providers.cncf.kubernetes.kube_client import get_kube_client
|
|
35
33
|
from airflow.providers.cncf.kubernetes.kubernetes_helper_functions import create_unique_id
|
|
36
34
|
from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
|
|
35
|
+
from airflow.providers.cncf.kubernetes.version_compat import AIRFLOW_V_3_0_PLUS
|
|
37
36
|
from airflow.utils import cli as cli_utils, yaml
|
|
38
37
|
from airflow.utils.cli import get_dag
|
|
39
38
|
from airflow.utils.providers_configuration_loader import providers_configuration_loaded
|
|
40
39
|
|
|
41
|
-
AIRFLOW_VERSION = Version(airflow_version)
|
|
42
|
-
AIRFLOW_V_3_0_PLUS = Version(AIRFLOW_VERSION.base_version) >= Version("3.0.0")
|
|
43
|
-
|
|
44
40
|
|
|
45
41
|
@cli_utils.action_cli
|
|
46
42
|
@providers_configuration_loaded
|
|
@@ -239,11 +239,12 @@ class KubernetesExecutor(BaseExecutor):
|
|
|
239
239
|
from airflow.models.taskinstance import TaskInstance
|
|
240
240
|
|
|
241
241
|
hybrid_executor_enabled = hasattr(TaskInstance, "executor")
|
|
242
|
-
|
|
242
|
+
default_executor_alias = None
|
|
243
243
|
if hybrid_executor_enabled:
|
|
244
244
|
from airflow.executors.executor_loader import ExecutorLoader
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
default_executor_name = ExecutorLoader.get_default_executor_name()
|
|
247
|
+
default_executor_alias = default_executor_name.alias
|
|
247
248
|
|
|
248
249
|
with Stats.timer("kubernetes_executor.clear_not_launched_queued_tasks.duration"):
|
|
249
250
|
self.log.debug("Clearing tasks that have not been launched")
|
|
@@ -253,7 +254,10 @@ class KubernetesExecutor(BaseExecutor):
|
|
|
253
254
|
)
|
|
254
255
|
if self.kubernetes_queue:
|
|
255
256
|
query = query.where(TaskInstance.queue == self.kubernetes_queue)
|
|
256
|
-
|
|
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:
|
|
257
261
|
query = query.where(
|
|
258
262
|
or_(
|
|
259
263
|
TaskInstance.executor == KUBERNETES_EXECUTOR,
|
|
@@ -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": 1734537609,
|
|
32
32
|
"versions": [
|
|
33
|
+
"10.1.0",
|
|
33
34
|
"10.0.1",
|
|
34
35
|
"10.0.0",
|
|
35
36
|
"9.0.1",
|
|
@@ -101,7 +102,7 @@ def get_provider_info():
|
|
|
101
102
|
],
|
|
102
103
|
"dependencies": [
|
|
103
104
|
"aiofiles>=23.2.0",
|
|
104
|
-
"apache-airflow>=2.
|
|
105
|
+
"apache-airflow>=2.9.0",
|
|
105
106
|
"asgiref>=3.5.2",
|
|
106
107
|
"cryptography>=41.0.0",
|
|
107
108
|
"kubernetes>=29.0.0,<=31.0.0",
|
|
@@ -128,6 +129,7 @@ def get_provider_info():
|
|
|
128
129
|
"integration-name": "Kubernetes",
|
|
129
130
|
"python-modules": [
|
|
130
131
|
"airflow.providers.cncf.kubernetes.operators.custom_object_launcher",
|
|
132
|
+
"airflow.providers.cncf.kubernetes.operators.kueue",
|
|
131
133
|
"airflow.providers.cncf.kubernetes.operators.pod",
|
|
132
134
|
"airflow.providers.cncf.kubernetes.operators.spark_kubernetes",
|
|
133
135
|
"airflow.providers.cncf.kubernetes.operators.resource",
|
|
@@ -26,9 +26,11 @@ from time import sleep
|
|
|
26
26
|
from typing import TYPE_CHECKING, Any
|
|
27
27
|
|
|
28
28
|
import aiofiles
|
|
29
|
+
import requests
|
|
29
30
|
import tenacity
|
|
30
31
|
from asgiref.sync import sync_to_async
|
|
31
|
-
from kubernetes import client, config, watch
|
|
32
|
+
from kubernetes import client, config, utils, watch
|
|
33
|
+
from kubernetes.client.models import V1Deployment
|
|
32
34
|
from kubernetes.config import ConfigException
|
|
33
35
|
from kubernetes_asyncio import client as async_client, config as async_config
|
|
34
36
|
from urllib3.exceptions import HTTPError
|
|
@@ -47,7 +49,7 @@ from airflow.utils import yaml
|
|
|
47
49
|
|
|
48
50
|
if TYPE_CHECKING:
|
|
49
51
|
from kubernetes.client import V1JobList
|
|
50
|
-
from kubernetes.client.models import
|
|
52
|
+
from kubernetes.client.models import V1Job, V1Pod
|
|
51
53
|
|
|
52
54
|
LOADING_KUBE_CONFIG_FILE_RESOURCE = "Loading Kubernetes configuration file kube_config from {}..."
|
|
53
55
|
|
|
@@ -489,12 +491,9 @@ class KubernetesHook(BaseHook, PodOperatorHookProtocol):
|
|
|
489
491
|
:param name: Name of Deployment to retrieve
|
|
490
492
|
:param namespace: Deployment namespace
|
|
491
493
|
"""
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
)
|
|
496
|
-
except Exception as exc:
|
|
497
|
-
raise exc
|
|
494
|
+
return self.apps_v1_client.read_namespaced_deployment_status(
|
|
495
|
+
name=name, namespace=namespace, pretty=True, **kwargs
|
|
496
|
+
)
|
|
498
497
|
|
|
499
498
|
@tenacity.retry(
|
|
500
499
|
stop=tenacity.stop_after_attempt(3),
|
|
@@ -644,6 +643,71 @@ class KubernetesHook(BaseHook, PodOperatorHookProtocol):
|
|
|
644
643
|
body=body,
|
|
645
644
|
)
|
|
646
645
|
|
|
646
|
+
def apply_from_yaml_file(
|
|
647
|
+
self,
|
|
648
|
+
api_client: Any = None,
|
|
649
|
+
yaml_file: str | None = None,
|
|
650
|
+
yaml_objects: list[dict] | None = None,
|
|
651
|
+
verbose: bool = False,
|
|
652
|
+
namespace: str = "default",
|
|
653
|
+
):
|
|
654
|
+
"""
|
|
655
|
+
Perform an action from a yaml file.
|
|
656
|
+
|
|
657
|
+
:param api_client: A Kubernetes client application.
|
|
658
|
+
:param yaml_file: Contains the path to yaml file.
|
|
659
|
+
:param yaml_objects: List of YAML objects; used instead of reading the yaml_file.
|
|
660
|
+
:param verbose: If True, print confirmation from create action. Default is False.
|
|
661
|
+
:param namespace: Contains the namespace to create all resources inside. The namespace must
|
|
662
|
+
preexist otherwise the resource creation will fail.
|
|
663
|
+
"""
|
|
664
|
+
utils.create_from_yaml(
|
|
665
|
+
k8s_client=api_client or self.api_client,
|
|
666
|
+
yaml_objects=yaml_objects,
|
|
667
|
+
yaml_file=yaml_file,
|
|
668
|
+
verbose=verbose,
|
|
669
|
+
namespace=namespace or self.get_namespace(),
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
def check_kueue_deployment_running(
|
|
673
|
+
self, name: str, namespace: str, timeout: float = 300.0, polling_period_seconds: float = 2.0
|
|
674
|
+
) -> None:
|
|
675
|
+
_timeout = timeout
|
|
676
|
+
while _timeout > 0:
|
|
677
|
+
try:
|
|
678
|
+
deployment = self.get_deployment_status(name=name, namespace=namespace)
|
|
679
|
+
except Exception as e:
|
|
680
|
+
self.log.exception("Exception occurred while checking for Deployment status.")
|
|
681
|
+
raise e
|
|
682
|
+
|
|
683
|
+
deployment_status = V1Deployment.to_dict(deployment)["status"]
|
|
684
|
+
replicas = deployment_status["replicas"]
|
|
685
|
+
ready_replicas = deployment_status["ready_replicas"]
|
|
686
|
+
unavailable_replicas = deployment_status["unavailable_replicas"]
|
|
687
|
+
if (
|
|
688
|
+
replicas is not None
|
|
689
|
+
and ready_replicas is not None
|
|
690
|
+
and unavailable_replicas is None
|
|
691
|
+
and replicas == ready_replicas
|
|
692
|
+
):
|
|
693
|
+
return
|
|
694
|
+
else:
|
|
695
|
+
self.log.info("Waiting until Deployment will be ready...")
|
|
696
|
+
sleep(polling_period_seconds)
|
|
697
|
+
|
|
698
|
+
_timeout -= polling_period_seconds
|
|
699
|
+
|
|
700
|
+
raise AirflowException("Deployment timed out")
|
|
701
|
+
|
|
702
|
+
@staticmethod
|
|
703
|
+
def get_yaml_content_from_file(kueue_yaml_url) -> list[dict]:
|
|
704
|
+
"""Download content of YAML file and separate it into several dictionaries."""
|
|
705
|
+
response = requests.get(kueue_yaml_url, allow_redirects=True)
|
|
706
|
+
if response.status_code != 200:
|
|
707
|
+
raise AirflowException("Was not able to read the yaml file from given URL")
|
|
708
|
+
|
|
709
|
+
return list(yaml.safe_load_all(response.text))
|
|
710
|
+
|
|
647
711
|
|
|
648
712
|
def _get_bool(val) -> bool | None:
|
|
649
713
|
"""Convert val to bool if can be done with certainty; if we cannot infer intention we return None."""
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
"""Manage a Kubernetes Kueue."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from collections.abc import Sequence
|
|
23
|
+
from functools import cached_property
|
|
24
|
+
|
|
25
|
+
from kubernetes.utils import FailToCreateError
|
|
26
|
+
|
|
27
|
+
from airflow.exceptions import AirflowException
|
|
28
|
+
from airflow.models import BaseOperator
|
|
29
|
+
from airflow.providers.cncf.kubernetes.hooks.kubernetes import KubernetesHook
|
|
30
|
+
from airflow.providers.cncf.kubernetes.operators.job import KubernetesJobOperator
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class KubernetesInstallKueueOperator(BaseOperator):
|
|
34
|
+
"""
|
|
35
|
+
Installs a Kubernetes Kueue.
|
|
36
|
+
|
|
37
|
+
.. seealso::
|
|
38
|
+
For more information on how to use this operator, take a look at the guide:
|
|
39
|
+
:ref:`howto/operator:KubernetesInstallKueueOperator`
|
|
40
|
+
|
|
41
|
+
:param kueue_version: The Kubernetes Kueue version to install.
|
|
42
|
+
:param kubernetes_conn_id: The :ref:`kubernetes connection id <howto/connection:kubernetes>`
|
|
43
|
+
for the Kubernetes cluster.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
template_fields: Sequence[str] = (
|
|
47
|
+
"kueue_version",
|
|
48
|
+
"kubernetes_conn_id",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __init__(self, kueue_version: str, kubernetes_conn_id: str = "kubernetes_default", *args, **kwargs):
|
|
52
|
+
super().__init__(*args, **kwargs)
|
|
53
|
+
self.kubernetes_conn_id = kubernetes_conn_id
|
|
54
|
+
self.kueue_version = kueue_version
|
|
55
|
+
self._kueue_yaml_url = (
|
|
56
|
+
f"https://github.com/kubernetes-sigs/kueue/releases/download/{self.kueue_version}/manifests.yaml"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@cached_property
|
|
60
|
+
def hook(self) -> KubernetesHook:
|
|
61
|
+
return KubernetesHook(conn_id=self.kubernetes_conn_id)
|
|
62
|
+
|
|
63
|
+
def execute(self, context):
|
|
64
|
+
yaml_objects = self.hook.get_yaml_content_from_file(kueue_yaml_url=self._kueue_yaml_url)
|
|
65
|
+
try:
|
|
66
|
+
self.hook.apply_from_yaml_file(yaml_objects=yaml_objects)
|
|
67
|
+
except FailToCreateError as ex:
|
|
68
|
+
error_bodies = [json.loads(e.body) for e in ex.api_exceptions]
|
|
69
|
+
if next((e for e in error_bodies if e.get("reason") == "AlreadyExists"), None):
|
|
70
|
+
self.log.info("Kueue is already enabled for the cluster")
|
|
71
|
+
|
|
72
|
+
if errors := [e for e in error_bodies if e.get("reason") != "AlreadyExists"]:
|
|
73
|
+
error_message = "\n".join(e.get("body") for e in errors)
|
|
74
|
+
raise AirflowException(error_message)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
self.hook.check_kueue_deployment_running(name="kueue-controller-manager", namespace="kueue-system")
|
|
78
|
+
self.log.info("Kueue installed successfully!")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class KubernetesStartKueueJobOperator(KubernetesJobOperator):
|
|
82
|
+
"""
|
|
83
|
+
Executes a Kubernetes Job in Kueue.
|
|
84
|
+
|
|
85
|
+
.. seealso::
|
|
86
|
+
For more information on how to use this operator, take a look at the guide:
|
|
87
|
+
:ref:`howto/operator:KubernetesStartKueueJobOperator`
|
|
88
|
+
|
|
89
|
+
:param queue_name: The name of the Queue in the cluster
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
template_fields = tuple({"queue_name"} | set(KubernetesJobOperator.template_fields))
|
|
93
|
+
|
|
94
|
+
def __init__(self, queue_name: str, *args, **kwargs) -> None:
|
|
95
|
+
super().__init__(*args, **kwargs)
|
|
96
|
+
self.queue_name = queue_name
|
|
97
|
+
|
|
98
|
+
if self.suspend is False:
|
|
99
|
+
raise AirflowException(
|
|
100
|
+
"The `suspend` parameter can't be False. If you want to use Kueue for running Job"
|
|
101
|
+
" in a Kubernetes cluster, set the `suspend` parameter to True.",
|
|
102
|
+
)
|
|
103
|
+
elif self.suspend is None:
|
|
104
|
+
self.log.info(
|
|
105
|
+
"You have not set parameter `suspend` in class %s. "
|
|
106
|
+
"For running a Job in Kueue the `suspend` parameter has been set to True.",
|
|
107
|
+
self.__class__.__name__,
|
|
108
|
+
)
|
|
109
|
+
self.suspend = True
|
|
110
|
+
self.labels.update({"kueue.x-k8s.io/queue-name": self.queue_name})
|
|
111
|
+
self.annotations.update({"kueue.x-k8s.io/queue-name": self.queue_name})
|
|
@@ -155,6 +155,9 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
155
155
|
:param startup_timeout_seconds: timeout in seconds to startup the pod.
|
|
156
156
|
:param startup_check_interval_seconds: interval in seconds to check if the pod has already started
|
|
157
157
|
:param get_logs: get the stdout of the base container as logs of the tasks.
|
|
158
|
+
:param init_container_logs: list of init containers whose logs will be published to stdout
|
|
159
|
+
Takes a sequence of containers, a single container name or True. If True,
|
|
160
|
+
all the containers logs are published.
|
|
158
161
|
:param container_logs: list of containers whose logs will be published to stdout
|
|
159
162
|
Takes a sequence of containers, a single container name or True. If True,
|
|
160
163
|
all the containers logs are published. Works in conjunction with get_logs param.
|
|
@@ -278,6 +281,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
278
281
|
startup_check_interval_seconds: int = 5,
|
|
279
282
|
get_logs: bool = True,
|
|
280
283
|
base_container_name: str | None = None,
|
|
284
|
+
init_container_logs: Iterable[str] | str | Literal[True] | None = None,
|
|
281
285
|
container_logs: Iterable[str] | str | Literal[True] | None = None,
|
|
282
286
|
image_pull_policy: str | None = None,
|
|
283
287
|
annotations: dict | None = None,
|
|
@@ -352,6 +356,7 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
352
356
|
# Fallback to the class variable BASE_CONTAINER_NAME here instead of via default argument value
|
|
353
357
|
# in the init method signature, to be compatible with subclasses overloading the class variable value.
|
|
354
358
|
self.base_container_name = base_container_name or self.BASE_CONTAINER_NAME
|
|
359
|
+
self.init_container_logs = init_container_logs
|
|
355
360
|
self.container_logs = container_logs or self.base_container_name
|
|
356
361
|
self.image_pull_policy = image_pull_policy
|
|
357
362
|
self.node_selector = node_selector or {}
|
|
@@ -396,7 +401,9 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
396
401
|
self.remote_pod: k8s.V1Pod | None = None
|
|
397
402
|
self.log_pod_spec_on_failure = log_pod_spec_on_failure
|
|
398
403
|
self.on_finish_action = OnFinishAction(on_finish_action)
|
|
399
|
-
|
|
404
|
+
# The `is_delete_operator_pod` parameter should have been removed in provider version 10.0.0.
|
|
405
|
+
# TODO: remove it from here and from the operator's parameters list when the next major version bumped
|
|
406
|
+
self._is_delete_operator_pod = self.on_finish_action == OnFinishAction.DELETE_POD
|
|
400
407
|
self.termination_message_policy = termination_message_policy
|
|
401
408
|
self.active_deadline_seconds = active_deadline_seconds
|
|
402
409
|
self.logging_interval = logging_interval
|
|
@@ -598,6 +605,9 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
598
605
|
self.callbacks.on_pod_creation(
|
|
599
606
|
pod=self.remote_pod, client=self.client, mode=ExecutionMode.SYNC
|
|
600
607
|
)
|
|
608
|
+
|
|
609
|
+
self.await_init_containers_completion(pod=self.pod)
|
|
610
|
+
|
|
601
611
|
self.await_pod_start(pod=self.pod)
|
|
602
612
|
if self.callbacks:
|
|
603
613
|
self.callbacks.on_pod_starting(
|
|
@@ -633,6 +643,22 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
633
643
|
if self.do_xcom_push:
|
|
634
644
|
return result
|
|
635
645
|
|
|
646
|
+
@tenacity.retry(
|
|
647
|
+
wait=tenacity.wait_exponential(max=15),
|
|
648
|
+
retry=tenacity.retry_if_exception_type(PodCredentialsExpiredFailure),
|
|
649
|
+
reraise=True,
|
|
650
|
+
)
|
|
651
|
+
def await_init_containers_completion(self, pod: k8s.V1Pod):
|
|
652
|
+
try:
|
|
653
|
+
if self.init_container_logs:
|
|
654
|
+
self.pod_manager.fetch_requested_init_container_logs(
|
|
655
|
+
pod=pod,
|
|
656
|
+
init_containers=self.init_container_logs,
|
|
657
|
+
follow_logs=True,
|
|
658
|
+
)
|
|
659
|
+
except kubernetes.client.exceptions.ApiException as exc:
|
|
660
|
+
self._handle_api_exception(exc, pod)
|
|
661
|
+
|
|
636
662
|
@tenacity.retry(
|
|
637
663
|
wait=tenacity.wait_exponential(max=15),
|
|
638
664
|
retry=tenacity.retry_if_exception_type(PodCredentialsExpiredFailure),
|
|
@@ -651,16 +677,21 @@ class KubernetesPodOperator(BaseOperator):
|
|
|
651
677
|
):
|
|
652
678
|
self.pod_manager.await_container_completion(pod=pod, container_name=self.base_container_name)
|
|
653
679
|
except kubernetes.client.exceptions.ApiException as exc:
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
680
|
+
self._handle_api_exception(exc, pod)
|
|
681
|
+
|
|
682
|
+
def _handle_api_exception(
|
|
683
|
+
self,
|
|
684
|
+
exc: kubernetes.client.exceptions.ApiException,
|
|
685
|
+
pod: k8s.V1Pod,
|
|
686
|
+
):
|
|
687
|
+
if exc.status and str(exc.status) == "401":
|
|
688
|
+
self.log.warning(
|
|
689
|
+
"Failed to check container status due to permission error. Refreshing credentials and retrying."
|
|
690
|
+
)
|
|
691
|
+
self._refresh_cached_properties()
|
|
692
|
+
self.pod_manager.read_pod(pod=pod) # attempt using refreshed credentials, raises if still invalid
|
|
693
|
+
raise PodCredentialsExpiredFailure("Kubernetes credentials expired, retrying after refresh.")
|
|
694
|
+
raise exc
|
|
664
695
|
|
|
665
696
|
def _refresh_cached_properties(self):
|
|
666
697
|
del self.hook
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import enum
|
|
22
|
+
import itertools
|
|
22
23
|
import json
|
|
23
24
|
import math
|
|
24
25
|
import time
|
|
@@ -117,7 +118,13 @@ class PodOperatorHookProtocol(Protocol):
|
|
|
117
118
|
|
|
118
119
|
def get_container_status(pod: V1Pod, container_name: str) -> V1ContainerStatus | None:
|
|
119
120
|
"""Retrieve container status."""
|
|
120
|
-
|
|
121
|
+
if pod and pod.status:
|
|
122
|
+
container_statuses = itertools.chain(
|
|
123
|
+
pod.status.container_statuses, pod.status.init_container_statuses
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
container_statuses = None
|
|
127
|
+
|
|
121
128
|
if container_statuses:
|
|
122
129
|
# In general the variable container_statuses can store multiple items matching different containers.
|
|
123
130
|
# The following generator expression yields all items that have name equal to the container_name.
|
|
@@ -166,6 +173,19 @@ def container_is_succeeded(pod: V1Pod, container_name: str) -> bool:
|
|
|
166
173
|
return container_status.state.terminated.exit_code == 0
|
|
167
174
|
|
|
168
175
|
|
|
176
|
+
def container_is_wait(pod: V1Pod, container_name: str) -> bool:
|
|
177
|
+
"""
|
|
178
|
+
Examine V1Pod ``pod`` to determine whether ``container_name`` is waiting.
|
|
179
|
+
|
|
180
|
+
If that container is present and waiting, returns True. Returns False otherwise.
|
|
181
|
+
"""
|
|
182
|
+
container_status = get_container_status(pod, container_name)
|
|
183
|
+
if not container_status:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
return container_status.state.waiting is not None
|
|
187
|
+
|
|
188
|
+
|
|
169
189
|
def container_is_terminated(pod: V1Pod, container_name: str) -> bool:
|
|
170
190
|
"""
|
|
171
191
|
Examine V1Pod ``pod`` to determine whether ``container_name`` is terminated.
|
|
@@ -451,7 +471,10 @@ class PodManager(LoggingMixin):
|
|
|
451
471
|
line=line, client=self._client, mode=ExecutionMode.SYNC
|
|
452
472
|
)
|
|
453
473
|
if message_to_log is not None:
|
|
454
|
-
|
|
474
|
+
if is_log_group_marker(message_to_log):
|
|
475
|
+
print(message_to_log)
|
|
476
|
+
else:
|
|
477
|
+
self.log.info("[%s] %s", container_name, message_to_log)
|
|
455
478
|
last_captured_timestamp = message_timestamp
|
|
456
479
|
message_to_log = message
|
|
457
480
|
message_timestamp = line_timestamp
|
|
@@ -467,7 +490,10 @@ class PodManager(LoggingMixin):
|
|
|
467
490
|
line=line, client=self._client, mode=ExecutionMode.SYNC
|
|
468
491
|
)
|
|
469
492
|
if message_to_log is not None:
|
|
470
|
-
|
|
493
|
+
if is_log_group_marker(message_to_log):
|
|
494
|
+
print(message_to_log)
|
|
495
|
+
else:
|
|
496
|
+
self.log.info("[%s] %s", container_name, message_to_log)
|
|
471
497
|
last_captured_timestamp = message_timestamp
|
|
472
498
|
except TimeoutError as e:
|
|
473
499
|
# in case of timeout, increment return time by 2 seconds to avoid
|
|
@@ -503,7 +529,7 @@ class PodManager(LoggingMixin):
|
|
|
503
529
|
time.sleep(1)
|
|
504
530
|
|
|
505
531
|
def _reconcile_requested_log_containers(
|
|
506
|
-
self, requested: Iterable[str] | str | bool, actual: list[str], pod_name
|
|
532
|
+
self, requested: Iterable[str] | str | bool | None, actual: list[str], pod_name
|
|
507
533
|
) -> list[str]:
|
|
508
534
|
"""Return actual containers based on requested."""
|
|
509
535
|
containers_to_log = []
|
|
@@ -546,6 +572,31 @@ class PodManager(LoggingMixin):
|
|
|
546
572
|
self.log.error("Could not retrieve containers for the pod: %s", pod_name)
|
|
547
573
|
return containers_to_log
|
|
548
574
|
|
|
575
|
+
def fetch_requested_init_container_logs(
|
|
576
|
+
self, pod: V1Pod, init_containers: Iterable[str] | str | Literal[True] | None, follow_logs=False
|
|
577
|
+
) -> list[PodLoggingStatus]:
|
|
578
|
+
"""
|
|
579
|
+
Follow the logs of containers in the specified pod and publish it to airflow logging.
|
|
580
|
+
|
|
581
|
+
Returns when all the containers exit.
|
|
582
|
+
|
|
583
|
+
:meta private:
|
|
584
|
+
"""
|
|
585
|
+
pod_logging_statuses = []
|
|
586
|
+
all_containers = self.get_init_container_names(pod)
|
|
587
|
+
containers_to_log = self._reconcile_requested_log_containers(
|
|
588
|
+
requested=init_containers,
|
|
589
|
+
actual=all_containers,
|
|
590
|
+
pod_name=pod.metadata.name,
|
|
591
|
+
)
|
|
592
|
+
# sort by spec.initContainers because containers runs sequentially
|
|
593
|
+
containers_to_log = sorted(containers_to_log, key=lambda cn: all_containers.index(cn))
|
|
594
|
+
for c in containers_to_log:
|
|
595
|
+
self._await_init_container_start(pod=pod, container_name=c)
|
|
596
|
+
status = self.fetch_container_logs(pod=pod, container_name=c, follow=follow_logs)
|
|
597
|
+
pod_logging_statuses.append(status)
|
|
598
|
+
return pod_logging_statuses
|
|
599
|
+
|
|
549
600
|
def fetch_requested_container_logs(
|
|
550
601
|
self, pod: V1Pod, containers: Iterable[str] | str | Literal[True], follow_logs=False
|
|
551
602
|
) -> list[PodLoggingStatus]:
|
|
@@ -673,9 +724,22 @@ class PodManager(LoggingMixin):
|
|
|
673
724
|
post_termination_timeout=post_termination_timeout,
|
|
674
725
|
)
|
|
675
726
|
|
|
727
|
+
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential(), reraise=True)
|
|
728
|
+
def get_init_container_names(self, pod: V1Pod) -> list[str]:
|
|
729
|
+
"""
|
|
730
|
+
Return container names from the POD except for the airflow-xcom-sidecar container.
|
|
731
|
+
|
|
732
|
+
:meta private:
|
|
733
|
+
"""
|
|
734
|
+
return [container_spec.name for container_spec in pod.spec.init_containers]
|
|
735
|
+
|
|
676
736
|
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential(), reraise=True)
|
|
677
737
|
def get_container_names(self, pod: V1Pod) -> list[str]:
|
|
678
|
-
"""
|
|
738
|
+
"""
|
|
739
|
+
Return container names from the POD except for the airflow-xcom-sidecar container.
|
|
740
|
+
|
|
741
|
+
:meta private:
|
|
742
|
+
"""
|
|
679
743
|
pod_info = self.read_pod(pod)
|
|
680
744
|
return [
|
|
681
745
|
container_spec.name
|
|
@@ -791,7 +855,7 @@ class PodManager(LoggingMixin):
|
|
|
791
855
|
_preload_content=False,
|
|
792
856
|
)
|
|
793
857
|
) as resp:
|
|
794
|
-
self._exec_pod_command(resp, "kill -2
|
|
858
|
+
self._exec_pod_command(resp, "kill -2 $(pgrep -u $(whoami) -f trap)")
|
|
795
859
|
|
|
796
860
|
def _exec_pod_command(self, resp, command: str) -> str | None:
|
|
797
861
|
res = ""
|
|
@@ -813,6 +877,20 @@ class PodManager(LoggingMixin):
|
|
|
813
877
|
return res
|
|
814
878
|
return None
|
|
815
879
|
|
|
880
|
+
def _await_init_container_start(self, pod: V1Pod, container_name: str):
|
|
881
|
+
while True:
|
|
882
|
+
remote_pod = self.read_pod(pod)
|
|
883
|
+
|
|
884
|
+
if (
|
|
885
|
+
remote_pod.status is not None
|
|
886
|
+
and remote_pod.status.phase != PodPhase.PENDING
|
|
887
|
+
and get_container_status(remote_pod, container_name) is not None
|
|
888
|
+
and not container_is_wait(remote_pod, container_name)
|
|
889
|
+
):
|
|
890
|
+
return
|
|
891
|
+
|
|
892
|
+
time.sleep(1)
|
|
893
|
+
|
|
816
894
|
|
|
817
895
|
class OnFinishAction(str, enum.Enum):
|
|
818
896
|
"""Action to take when the pod finishes."""
|
|
@@ -820,3 +898,8 @@ class OnFinishAction(str, enum.Enum):
|
|
|
820
898
|
KEEP_POD = "keep_pod"
|
|
821
899
|
DELETE_POD = "delete_pod"
|
|
822
900
|
DELETE_SUCCEEDED_POD = "delete_succeeded_pod"
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def is_log_group_marker(line: str) -> bool:
|
|
904
|
+
"""Check if the line is a log group marker like `::group::` or `::endgroup::`."""
|
|
905
|
+
return line.startswith("::group::") or line.startswith("::endgroup::")
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
#
|
|
18
|
+
# NOTE! THIS FILE IS COPIED MANUALLY IN OTHER PROVIDERS DELIBERATELY TO AVOID ADDING UNNECESSARY
|
|
19
|
+
# DEPENDENCIES BETWEEN PROVIDERS. IF YOU WANT TO ADD CONDITIONAL CODE IN YOUR PROVIDER THAT DEPENDS
|
|
20
|
+
# ON AIRFLOW VERSION, PLEASE COPY THIS FILE TO THE ROOT PACKAGE OF YOUR PROVIDER AND IMPORT
|
|
21
|
+
# THOSE CONSTANTS FROM IT RATHER THAN IMPORTING THEM FROM ANOTHER PROVIDER OR TEST CODE
|
|
22
|
+
#
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_base_airflow_version_tuple() -> tuple[int, int, int]:
|
|
27
|
+
from packaging.version import Version
|
|
28
|
+
|
|
29
|
+
from airflow import __version__
|
|
30
|
+
|
|
31
|
+
airflow_version = Version(__version__)
|
|
32
|
+
return airflow_version.major, airflow_version.minor, airflow_version.micro
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
AIRFLOW_V_2_10_PLUS = get_base_airflow_version_tuple() >= (2, 10, 0)
|
|
36
|
+
AIRFLOW_V_3_0_PLUS = get_base_airflow_version_tuple() >= (3, 0, 0)
|
|
@@ -27,7 +27,7 @@ build-backend = "flit_core.buildapi"
|
|
|
27
27
|
|
|
28
28
|
[project]
|
|
29
29
|
name = "apache-airflow-providers-cncf-kubernetes"
|
|
30
|
-
version = "10.0.
|
|
30
|
+
version = "10.1.0.rc1"
|
|
31
31
|
description = "Provider package apache-airflow-providers-cncf-kubernetes for Apache Airflow"
|
|
32
32
|
readme = "README.rst"
|
|
33
33
|
authors = [
|
|
@@ -55,7 +55,7 @@ classifiers = [
|
|
|
55
55
|
requires-python = "~=3.9"
|
|
56
56
|
dependencies = [
|
|
57
57
|
"aiofiles>=23.2.0",
|
|
58
|
-
"apache-airflow>=2.
|
|
58
|
+
"apache-airflow>=2.9.0rc0",
|
|
59
59
|
"asgiref>=3.5.2",
|
|
60
60
|
"cryptography>=41.0.0",
|
|
61
61
|
"google-re2>=1.0",
|
|
@@ -64,12 +64,12 @@ dependencies = [
|
|
|
64
64
|
]
|
|
65
65
|
|
|
66
66
|
[project.urls]
|
|
67
|
-
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
68
|
-
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.0
|
|
67
|
+
"Documentation" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0"
|
|
68
|
+
"Changelog" = "https://airflow.apache.org/docs/apache-airflow-providers-cncf-kubernetes/10.1.0/changelog.html"
|
|
69
69
|
"Bug Tracker" = "https://github.com/apache/airflow/issues"
|
|
70
70
|
"Source Code" = "https://github.com/apache/airflow"
|
|
71
71
|
"Slack Chat" = "https://s.apache.org/airflow-slack"
|
|
72
|
-
"Twitter" = "https://
|
|
72
|
+
"Twitter" = "https://x.com/ApacheAirflow"
|
|
73
73
|
"YouTube" = "https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/"
|
|
74
74
|
|
|
75
75
|
[project.entry-points."apache_airflow_provider"]
|
|
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
|