This is an automated email from the ASF dual-hosted git repository.

shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 5eaf78bd629 Add team_name tags to Kubernetes executor metrics (#69046)
5eaf78bd629 is described below

commit 5eaf78bd629443c040035c0e3306091fc3ca6daa
Author: SameerMesiah97 <[email protected]>
AuthorDate: Wed Jul 1 19:17:48 2026 +0100

    Add team_name tags to Kubernetes executor metrics (#69046)
---
 .../kubernetes/executors/kubernetes_executor.py    |  11 +-
 .../executors/kubernetes_executor_utils.py         |  64 +++-
 .../executors/test_kubernetes_executor.py          | 369 ++++++++++++++++++++-
 3 files changed, 423 insertions(+), 21 deletions(-)

diff --git 
a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py
 
b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py
index 7410b9193a8..50ad4a5e929 100644
--- 
a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py
+++ 
b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor.py
@@ -53,6 +53,7 @@ from 
airflow.providers.cncf.kubernetes.kubernetes_helper_functions import annota
 from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator
 from airflow.providers.cncf.kubernetes.version_compat import AIRFLOW_V_3_0_PLUS
 from airflow.providers.common.compat.sdk import Stats, conf
+from airflow.utils.helpers import prune_dict
 from airflow.utils.log.logging_mixin import remove_escape_codes
 from airflow.utils.session import NEW_SESSION, provide_session
 from airflow.utils.state import TaskInstanceState
@@ -116,6 +117,10 @@ class KubernetesExecutor(BaseExecutor):
         self.completed: dict[tuple[str, str], KubernetesResults] = {}
         self.create_pods_after: datetime | None = None
 
+        # Maintain compatibility with older Airflow releases that do not 
define team_name.
+        if not hasattr(self, "team_name"):
+            self.team_name = None
+
     def _list_pods(self, query_kwargs):
         query_kwargs["header_params"] = {
             "Accept": 
"application/json;as=PartialObjectMetadataList;v=v1;g=meta.k8s.io"
@@ -196,6 +201,7 @@ class KubernetesExecutor(BaseExecutor):
             result_queue=self.result_queue,
             kube_client=self.kube_client,
             scheduler_job_id=self.scheduler_job_id,
+            team_name=self.team_name,
         )
 
     def _coordinator_extra(self, queue: str | None) -> dict[str, Any] | None:
@@ -639,7 +645,10 @@ class KubernetesExecutor(BaseExecutor):
         return messages, ["\n".join(log)]
 
     def try_adopt_task_instances(self, tis: Sequence[TaskInstance]) -> 
Sequence[TaskInstance]:
-        with Stats.timer("kubernetes_executor.adopt_task_instances.duration"):
+        with Stats.timer(
+            "kubernetes_executor.adopt_task_instances.duration",
+            tags=prune_dict({"team_name": self.team_name}),
+        ):
             # Always flush TIs without queued_by_job_id
             tis_to_flush = [ti for ti in tis if not ti.queued_by_job_id]
             scheduler_job_ids = {ti.queued_by_job_id for ti in tis}
diff --git 
a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py
 
b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py
index 787ea73237f..2c4bd306e52 100644
--- 
a/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py
+++ 
b/providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/executors/kubernetes_executor_utils.py
@@ -46,6 +46,7 @@ from 
airflow.providers.cncf.kubernetes.kubernetes_helper_functions import (
 )
 from airflow.providers.cncf.kubernetes.pod_generator import PodGenerator, 
workload_to_command_args
 from airflow.providers.common.compat.sdk import AirflowException, Stats
+from airflow.utils.helpers import prune_dict
 from airflow.utils.log.logging_mixin import LoggingMixin
 from airflow.utils.state import TaskInstanceState
 
@@ -474,6 +475,7 @@ class AirflowKubernetesScheduler(LoggingMixin):
         result_queue: Queue[KubernetesResults],
         kube_client: client.CoreV1Api,
         scheduler_job_id: str,
+        team_name: str | None = None,
     ):
         super().__init__()
         self.log.debug("Creating Kubernetes executor")
@@ -486,6 +488,7 @@ class AirflowKubernetesScheduler(LoggingMixin):
         self.watcher_queue = self._manager.Queue()
         self.scheduler_job_id = scheduler_job_id
         self.kube_watchers = self._make_kube_watchers()
+        self.team_name = team_name
 
     def run_pod_async(self, pod: k8s.V1Pod, **kwargs):
         """Run POD asynchronously."""
@@ -494,18 +497,29 @@ class AirflowKubernetesScheduler(LoggingMixin):
 
         self.log.debug("Pod Creation Request: \n%s", json_pod)
         try:
-            with Stats.timer("kubernetes_executor.pod_creation"):
+            with Stats.timer(
+                "kubernetes_executor.pod_creation", 
tags=prune_dict({"team_name": self.team_name})
+            ):
                 resp = self.kube_client.create_namespaced_pod(
                     body=sanitized_pod, namespace=pod.metadata.namespace, 
**kwargs
                 )
-            Stats.incr("kubernetes_executor.pod_creation_status", 
tags={"status": "200"})
+            Stats.incr(
+                "kubernetes_executor.pod_creation_status",
+                tags=prune_dict({"status": "200", "team_name": 
self.team_name}),
+            )
             self.log.debug("Pod Creation Response: %s", resp)
         except ApiException as e:
-            Stats.incr("kubernetes_executor.pod_creation_status", 
tags={"status": str(e.status)})
+            Stats.incr(
+                "kubernetes_executor.pod_creation_status",
+                tags=prune_dict({"status": str(e.status), "team_name": 
self.team_name}),
+            )
             self.log.exception("Exception when attempting to create Namespaced 
Pod: %s", json_pod)
             raise
         except Exception as e:
-            Stats.incr("kubernetes_executor.pod_creation_status", 
tags={"status": "error"})
+            Stats.incr(
+                "kubernetes_executor.pod_creation_status",
+                tags=prune_dict({"status": "error", "team_name": 
self.team_name}),
+            )
             self.log.exception("Exception when attempting to create Namespaced 
Pod: %s", json_pod)
             raise e
         return resp
@@ -616,16 +630,24 @@ class AirflowKubernetesScheduler(LoggingMixin):
         """Delete Pod from a namespace; does not raise if it does not exist."""
         try:
             self.log.info("Deleting pod %s in namespace %s", pod_name, 
namespace)
-            with Stats.timer("kubernetes_executor.pod_deletion"):
+            with Stats.timer(
+                "kubernetes_executor.pod_deletion", 
tags=prune_dict({"team_name": self.team_name})
+            ):
                 self.kube_client.delete_namespaced_pod(
                     pod_name,
                     namespace,
                     
body=client.V1DeleteOptions(**self.kube_config.delete_option_kwargs),
                     **self.kube_config.kube_client_request_args,
                 )
-            Stats.incr("kubernetes_executor.pod_deletion_status", 
tags={"status": "200"})
+            Stats.incr(
+                "kubernetes_executor.pod_deletion_status",
+                tags=prune_dict({"status": "200", "team_name": 
self.team_name}),
+            )
         except ApiException as e:
-            Stats.incr("kubernetes_executor.pod_deletion_status", 
tags={"status": str(e.status)})
+            Stats.incr(
+                "kubernetes_executor.pod_deletion_status",
+                tags=prune_dict({"status": str(e.status), "team_name": 
self.team_name}),
+            )
             # If the pod is already deleted
             if str(e.status) != "404":
                 raise
@@ -642,30 +664,46 @@ class AirflowKubernetesScheduler(LoggingMixin):
             namespace,
         )
         try:
-            with Stats.timer("kubernetes_executor.pod_patching"):
+            with Stats.timer(
+                "kubernetes_executor.pod_patching", 
tags=prune_dict({"team_name": self.team_name})
+            ):
                 self.kube_client.patch_namespaced_pod(
                     name=pod_name,
                     namespace=namespace,
                     body={"metadata": {"labels": {POD_REVOKED_KEY: "True"}}},
                 )
-            Stats.incr("kubernetes_executor.pod_patching_status", 
tags={"status": "200"})
+            Stats.incr(
+                "kubernetes_executor.pod_patching_status",
+                tags=prune_dict({"status": "200", "team_name": 
self.team_name}),
+            )
         except ApiException as e:
-            Stats.incr("kubernetes_executor.pod_patching_status", 
tags={"status": str(e.status)})
+            Stats.incr(
+                "kubernetes_executor.pod_patching_status",
+                tags=prune_dict({"status": str(e.status), "team_name": 
self.team_name}),
+            )
             self.log.warning("Failed to patch pod %s with pod revoked key.", 
pod_name, exc_info=True)
 
     def patch_pod_executor_done(self, *, pod_name: str, namespace: str):
         """Add a "done" annotation to ensure we don't continually adopt 
pods."""
         self.log.debug("Patching pod %s in namespace %s to mark it as done", 
pod_name, namespace)
         try:
-            with Stats.timer("kubernetes_executor.pod_patching"):
+            with Stats.timer(
+                "kubernetes_executor.pod_patching", 
tags=prune_dict({"team_name": self.team_name})
+            ):
                 self.kube_client.patch_namespaced_pod(
                     name=pod_name,
                     namespace=namespace,
                     body={"metadata": {"labels": {POD_EXECUTOR_DONE_KEY: 
"True"}}},
                 )
-            Stats.incr("kubernetes_executor.pod_patching_status", 
tags={"status": "200"})
+            Stats.incr(
+                "kubernetes_executor.pod_patching_status",
+                tags=prune_dict({"status": "200", "team_name": 
self.team_name}),
+            )
         except ApiException as e:
-            Stats.incr("kubernetes_executor.pod_patching_status", 
tags={"status": str(e.status)})
+            Stats.incr(
+                "kubernetes_executor.pod_patching_status",
+                tags=prune_dict({"status": str(e.status), "team_name": 
self.team_name}),
+            )
             self.log.info("Failed to patch pod %s with done annotation. 
Reason: %s", pod_name, e)
 
     def sync(self) -> None:
diff --git 
a/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor.py
 
b/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor.py
index d17a16b3ddc..e899681f741 100644
--- 
a/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor.py
+++ 
b/providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/test_kubernetes_executor.py
@@ -69,6 +69,7 @@ from airflow.utils.state import State, TaskInstanceState
 from tests_common.test_utils.config import conf_vars
 from tests_common.test_utils.version_compat import (
     AIRFLOW_V_3_0_PLUS,
+    AIRFLOW_V_3_1_PLUS,
     AIRFLOW_V_3_2_PLUS,
     AIRFLOW_V_3_3_PLUS,
 )
@@ -254,49 +255,375 @@ class TestAirflowKubernetesScheduler:
     @pytest.mark.skipif(
         AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
     )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(
+                None,
+                {},
+                {"status": "200"},
+                id="without_team",
+            ),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "200", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
+    
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
+    def test_run_pod_async_emits_metrics_on_success(
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
+    ):
+        pod = mock.MagicMock()
+        pod.metadata.namespace = "default"
+
+        mock_api_client = mock.Mock()
+        mock_api_client.sanitize_for_serialization.return_value = {}
+
+        mock_kube_client.return_value.api_client = mock_api_client
+        mock_kube_client.return_value.create_namespaced_pod = mock.MagicMock()
+
+        kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+        kube_executor.job_id = 1
+        kube_executor.start()
+
+        try:
+            kube_executor.kube_scheduler.run_pod_async(pod)
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_creation",
+                tags=timer_tags,
+            )
+
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_creation_status",
+                tags=status_tags,
+            )
+        finally:
+            kube_executor.end()
+
+    @pytest.mark.skipif(
+        AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
+    )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(
+                None,
+                {},
+                {"status": "429"},
+                id="without_team",
+            ),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "429", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
+    
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
+    def test_run_pod_async_emits_metrics_on_failure(
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
+    ):
+        pod = mock.MagicMock()
+        pod.metadata.namespace = "default"
+
+        mock_api_client = mock.Mock()
+        mock_api_client.sanitize_for_serialization.return_value = {}
+
+        mock_kube_client.return_value.api_client = mock_api_client
+        mock_kube_client.return_value.create_namespaced_pod.side_effect = 
ApiException(status=429)
+
+        kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+        kube_executor.job_id = 1
+        kube_executor.start()
+
+        try:
+            with pytest.raises(ApiException):
+                kube_executor.kube_scheduler.run_pod_async(pod)
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_creation",
+                tags=timer_tags,
+            )
+
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_creation_status",
+                tags=status_tags,
+            )
+        finally:
+            kube_executor.end()
+
+    @pytest.mark.skipif(
+        AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
+    )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(
+                None,
+                {},
+                {"status": "200"},
+                id="without_team",
+            ),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "200", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
     
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
     def test_delete_pod_emits_metrics_on_success(
-        self, mock_watcher, mock_client, mock_kube_client, mock_stats
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
     ):
         pod_name = "my-pod-1"
         namespace = "my-namespace-1"
         mock_kube_client.return_value.delete_namespaced_pod = mock.MagicMock()
 
         kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+
         kube_executor.job_id = 1
         kube_executor.start()
         try:
             kube_executor.kube_scheduler.delete_pod(pod_name, namespace)
-            
mock_stats.timer.assert_any_call("kubernetes_executor.pod_deletion")
-            
mock_stats.incr.assert_any_call("kubernetes_executor.pod_deletion_status", 
tags={"status": "200"})
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_deletion",
+                tags=timer_tags,
+            )
+
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_deletion_status",
+                tags=status_tags,
+            )
         finally:
             kube_executor.end()
 
     @pytest.mark.skipif(
         AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
     )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(
+                None,
+                {},
+                {"status": "429"},
+                id="without_team",
+            ),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "429", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
     
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
     def test_delete_pod_emits_metrics_on_failure(
-        self, mock_watcher, mock_client, mock_kube_client, mock_stats
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
     ):
         pod_name = "my-pod-1"
         namespace = "my-namespace-2"
         mock_kube_client.return_value.delete_namespaced_pod.side_effect = 
ApiException(status=429)
 
         kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+
         kube_executor.job_id = 1
         kube_executor.start()
         try:
             with pytest.raises(ApiException):
                 kube_executor.kube_scheduler.delete_pod(pod_name, namespace)
-            
mock_stats.timer.assert_any_call("kubernetes_executor.pod_deletion")
-            
mock_stats.incr.assert_any_call("kubernetes_executor.pod_deletion_status", 
tags={"status": "429"})
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_deletion",
+                tags=timer_tags,
+            )
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_deletion_status",
+                tags=status_tags,
+            )
+        finally:
+            kube_executor.end()
+
+    @pytest.mark.skipif(
+        AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
+    )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(None, {}, {"status": "200"}, id="without_team"),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "200", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
+    
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
+    def test_patch_pod_emits_metrics_on_success(
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
+    ):
+        mock_kube_client.return_value.patch_namespaced_pod = mock.MagicMock()
+
+        kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+        kube_executor.job_id = 1
+        kube_executor.start()
+
+        try:
+            kube_executor.kube_scheduler.patch_pod_executor_done(
+                pod_name="pod",
+                namespace="default",
+            )
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_patching",
+                tags=timer_tags,
+            )
+
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_patching_status",
+                tags=status_tags,
+            )
+        finally:
+            kube_executor.end()
+
+    @pytest.mark.skipif(
+        AirflowKubernetesScheduler is None, reason="kubernetes python package 
is not installed"
+    )
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags", "status_tags"),
+        [
+            pytest.param(None, {}, {"status": "429"}, id="without_team"),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                {"status": "429", "team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.Stats")
+    
@mock.patch("airflow.providers.cncf.kubernetes.kube_client.get_kube_client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.client")
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor_utils.KubernetesJobWatcher")
+    def test_patch_pod_emits_metrics_on_failure(
+        self,
+        mock_watcher,
+        mock_client,
+        mock_kube_client,
+        mock_stats,
+        team_name,
+        timer_tags,
+        status_tags,
+    ):
+        mock_kube_client.return_value.patch_namespaced_pod.side_effect = 
ApiException(status=429)
+
+        kube_executor = KubernetesExecutor()
+        kube_executor.team_name = team_name
+        kube_executor.job_id = 1
+        kube_executor.start()
+
+        try:
+            kube_executor.kube_scheduler.patch_pod_executor_done(
+                pod_name="pod",
+                namespace="default",
+            )
+
+            mock_stats.timer.assert_any_call(
+                "kubernetes_executor.pod_patching",
+                tags=timer_tags,
+            )
+
+            mock_stats.incr.assert_any_call(
+                "kubernetes_executor.pod_patching_status",
+                tags=status_tags,
+            )
         finally:
             kube_executor.end()
 
@@ -1320,6 +1647,22 @@ class TestKubernetesExecutor:
         finally:
             executor.end()
 
+    @pytest.mark.parametrize(
+        ("team_name", "timer_tags"),
+        [
+            pytest.param(None, {}, id="without_team"),
+            pytest.param(
+                "team_a",
+                {"team_name": "team_a"},
+                id="with_team",
+                marks=pytest.mark.skipif(
+                    not AIRFLOW_V_3_1_PLUS,
+                    reason="team_name metrics require Airflow 3.1+",
+                ),
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor.Stats.timer")
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor.DynamicClient")
     @mock.patch(
         
"airflow.providers.cncf.kubernetes.executors.kubernetes_executor.KubernetesExecutor.adopt_launched_task"
@@ -1328,9 +1671,16 @@ class TestKubernetesExecutor:
         
"airflow.providers.cncf.kubernetes.executors.kubernetes_executor.KubernetesExecutor._adopt_completed_pods"
     )
     def test_try_adopt_task_instances(
-        self, mock_adopt_completed_pods, mock_adopt_launched_task, 
mock_kube_dynamic_client
+        self,
+        mock_adopt_completed_pods,
+        mock_adopt_launched_task,
+        mock_kube_dynamic_client,
+        mock_stats_timer,
+        team_name,
+        timer_tags,
     ):
         executor = self.kubernetes_executor
+        executor.team_name = team_name
         executor.scheduler_job_id = "10"
         ti_key = annotations_to_key(
             {
@@ -1386,6 +1736,11 @@ class TestKubernetesExecutor:
         mock_adopt_completed_pods.assert_called_once()
         assert reset_tis == []  # This time our return is empty - no TIs to 
reset
 
+        mock_stats_timer.assert_any_call(
+            "kubernetes_executor.adopt_task_instances.duration",
+            tags=timer_tags,
+        )
+
     
@mock.patch("airflow.providers.cncf.kubernetes.executors.kubernetes_executor.DynamicClient")
     @mock.patch(
         
"airflow.providers.cncf.kubernetes.executors.kubernetes_executor.KubernetesExecutor._adopt_completed_pods"

Reply via email to