Repository: apache/airflow

Issue:
Mapped KubernetesPodOperator "fails" but UI shows it is as still running
### Apache Airflow version

2.3.0b1 (pre-release)

### What happened

This dag has a problem.  The `name` kwarg is missing from one of the mapped instances.

```python3
from datetime import datetime
from airflow import DAG
from airflow.providers.cncf.kubernetes.operators.kubernetes_pod import (
    KubernetesPodOperator,
)

from airflow.configuration import conf

namespace = conf.get("kubernetes", "NAMESPACE")

with DAG(
    dag_id="kpo_mapped",
    start_date=datetime(1970, 1, 1),
    schedule_interval=None,
) as dag:

    KubernetesPodOperator(
        task_id="cowsay_static_named",
        name="cowsay_statc",
        namespace=namespace,
        image="docker.io/rancher/cowsay",
        cmds=["cowsay"],
        arguments=["moo"],
    )

    KubernetesPodOperator.partial(
        task_id="cowsay_mapped",
        # name="cowsay_mapped",  # required field missing
        image="docker.io/rancher/cowsay",
        namespace=namespace,
        cmds=["cowsay"],
    ).expand(arguments=[["mooooove"], ["cow"], ["get out the way"]])

    KubernetesPodOperator.partial(
        task_id="cowsay_mapped_named",
        name="cowsay_mapped",
        namespace=namespace,
        image="docker.io/rancher/cowsay",
        cmds=["cowsay"],
    ).expand(arguments=[["mooooove"], ["cow"], ["get out the way"]])

```

If you omit that field in an unmapped task, you get a dag parse error, which is appropriate.  But omitting it from the mapped task gives you this runtime error in the task logs:

```
[2022-04-20, 05:11:02 UTC] {standard_task_runner.py:52} INFO - Started process 60 to run task
[2022-04-20, 05:11:02 UTC] {standard_task_runner.py:79} INFO - Running: ['airflow', 'tasks', 'run', 'kpo_mapped', 'cowsay_mapped', 'manual__2022-04-20T05:11:01+00:00', '--job-id', '12', '--raw', '--subdir', 'DAGS_FOLDER/dags/taskmap/kpo_mapped.py', '--cfg-path', '/tmp/tmp_g3sj496', '--map-index', '0', '--error-file', '/tmp/tmp2_313wxj']
[2022-04-20, 05:11:02 UTC] {standard_task_runner.py:80} INFO - Job 12: Subtask cowsay_mapped
[2022-04-20, 05:11:02 UTC] {task_command.py:369} INFO - Running <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T05:11:01+00:00 map_index=0 [running]> on host airflow-worker-65f9fd9d5b-vpgnk
[2022-04-20, 05:11:02 UTC] {taskinstance.py:1863} WARNING - We expected to get frame set in local storage but it was not. Please report this as an issue with full logs at https://github.com/apache/airflow/issues/new
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 1440, in _run_raw_task
    self._execute_task_with_callbacks(context, test_mode)
  File "/usr/local/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 1544, in _execute_task_with_callbacks
    task_orig = self.render_templates(context=context)
  File "/usr/local/lib/python3.9/site-packages/airflow/models/taskinstance.py", line 2210, in render_templates
    rendered_task = self.task.render_template_fields(context)
  File "/usr/local/lib/python3.9/site-packages/airflow/models/mappedoperator.py", line 722, in render_template_fields
    unmapped_task = self.unmap(unmap_kwargs=kwargs)
  File "/usr/local/lib/python3.9/site-packages/airflow/models/mappedoperator.py", line 508, in unmap
    op = self.operator_class(**unmap_kwargs, _airflow_from_mapped=True)
  File "/usr/local/lib/python3.9/site-packages/airflow/models/baseoperator.py", line 390, in apply_defaults
    result = func(self, **kwargs, default_args=default_args)
  File "/usr/local/lib/python3.9/site-packages/airflow/providers/cncf/kubernetes/operators/kubernetes_pod.py", line 259, in __init__
    self.name = self._set
_name(name)
  File "/usr/local/lib/python3.9/site-packages/airflow/providers/cncf/kubernetes/operators/kubernetes_pod.py", line 442, in _set_name
    raise AirflowException("`name` is required unless `pod_template_file` or `full_pod_spec` is set")
airflow.exceptions.AirflowException: `name` is required unless `pod_template_file` or `full_pod_spec` is set
```

But rather than failing the task, Airflow just thinks that the task is still running:

<img width="833" alt="Screen Shot 2022-04-19 at 11 13 47 PM" src="https://user-images.githubusercontent.com/5834582/164156155-41986d3a-d171-4943-8443-a0fc3c542988.png">



### What you think should happen instead

Ideally this error would be surfaced when the dag is first parsed.  If that's not possible, then it should fail the task completely (i.e. a red square should show up in the grid view).

### How to reproduce

Run the dag above

### Operating System

ubuntu (microk8s)

### Versions of Apache Airflow Providers

apache-airflow-providers-cncf-kubernetes  | 4.0.0

### Deployment

Astronomer

### Deployment details

Deployed via the astronomer airflow helm chart, values:

```
airflow:
  airflowHome: /usr/local/airflow
  defaultAirflowRepository: 172.28.11.191:30500/airflow
  defaultAirflowTag: tb11c-inner-operator-expansion
  env:
  - name: AIRFLOW__CORE__DAGBAG_IMPORT_ERROR_TRACEBACK_DEPTH
    value: '99'
  executor: CeleryExecutor
  gid: 50000
  images:
    airflow:
      pullPolicy: Always
      repository: 172.28.11.191:30500/airflow
    flower:
      pullPolicy: Always
    pod_template:
      pullPolicy: Always
  logs:
    persistence:
      enabled: true
      size: 2Gi
  scheduler:
    livenessProbe:
      timeoutSeconds: 45
  triggerer:
    livenessProbe:
      timeoutSeconds: 45
```

Image base: `quay.io/astronomer/ap-airflow-dev:main`
Airflow version: `2.3.0.dev20220414`



### Anything else

_No response_

### Are you willing to submit PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)



Discussion:
Yeah, two things we can do here:

1. Improve the validation for KPO when mapped  (framework is in place already)
2. (This one is most important) Find out why the task isn't being detected as failed!
Curious, something is up with the zombie detection too:

```[2022-04-20 08:42:36,843]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1360}} WARNING - Failing (3) jobs without heartbeat after 2022-04-20 07:37:36.841761+00:00
[2022-04-20 08:42:36,844]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=2 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b5ebc0>, 'is_failure_callback': True}
[2022-04-20 08:42:36,844]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=1 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088c1cd60>, 'is_failure_callback': True}
[2022-04-20 08:42:36,844]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=0 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b7e320>, 'is_failure_callback': True}
[2022-04-20 08:42:46,880]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1360}} WARNING - Failing (3) jobs without heartbeat after 2022-04-20 07:37:46.878339+00:00
[2022-04-20 08:42:46,881]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=2 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b8bd30>, 'is_failure_callback': True}
[2022-04-20 08:42:46,881]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=1 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b7b8e0>, 'is_failure_callback': True}
[2022-04-20 08:42:46,881]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=0 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b5ebc0>, 'is_failure_callback': True}
[2022-04-20 08:42:49 +0100] [331200] [INFO] Handling signal: winch
[2022-04-20 08:42:56,915]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1360}} WARNING - Failing (3) jobs without heartbeat after 2022-04-20 07:37:56.912725+00:00
[2022-04-20 08:42:56,915]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=2 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b5d6f0>, 'is_failure_callback': True}
[2022-04-20 08:42:56,915]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=1 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b7e320>, 'is_failure_callback': True}
[2022-04-20 08:42:56,915]  331186 MainProcess {{airflow.jobs.scheduler_job.SchedulerJob scheduler_job.py:1368}} ERROR - Detected zombie job: {'full_filepath': '/home/ash/code/airflow/airflow/kpodag.py', 'msg': 'Detected <TaskInstance: kpo_mapped.cowsay_mapped manual__2022-04-20T07:42:30.995741+00:00 map_index=0 [running]> as zombie', 'simple_task_instance': <airflow.models.taskinstance.SimpleTaskInstance object at 0x7f6088b7f460>, 'is_failure_callback': True}
```
```
[2022-04-20, 05:11:02 UTC] {taskinstance.py:1863} WARNING - We expected to get frame set in local storage but it was not. Please report this as an issue with full logs at https://github.com/apache/airflow/issues/new
```

This line looks suspicious
@uranusjr That error happens if the excpetion occurs "outside" of user task code (before or after). Not a problem here, but we should fix it, yes.

Resolving patch:
diff --git a/airflow/dag_processing/processor.py b/airflow/dag_processing/processor.py
--- a/airflow/dag_processing/processor.py
+++ b/airflow/dag_processing/processor.py
@@ -604,7 +604,7 @@ def _execute_task_callbacks(self, dagbag: DagBag, request: TaskCallbackRequest):
             if simple_ti.task_id in dag.task_ids:
                 task = dag.get_task(simple_ti.task_id)
                 if request.is_failure_callback:
-                    ti = TI(task, run_id=simple_ti.run_id)
+                    ti = TI(task, run_id=simple_ti.run_id, map_index=simple_ti.map_index)
                     # TODO: Use simple_ti to improve performance here in the future
                     ti.refresh_from_db()
                     ti.handle_failure_with_callback(error=request.msg, test_mode=self.UNIT_TEST_MODE)
diff --git a/airflow/models/taskfail.py b/airflow/models/taskfail.py
--- a/airflow/models/taskfail.py
+++ b/airflow/models/taskfail.py
@@ -63,13 +63,13 @@ class TaskFail(Base):
         viewonly=True,
     )
 
-    def __init__(self, task, run_id, start_date, end_date, map_index):
-        self.dag_id = task.dag_id
-        self.task_id = task.task_id
-        self.run_id = run_id
-        self.map_index = map_index
-        self.start_date = start_date
-        self.end_date = end_date
+    def __init__(self, ti):
+        self.dag_id = ti.dag_id
+        self.task_id = ti.task_id
+        self.run_id = ti.run_id
+        self.map_index = ti.map_index
+        self.start_date = ti.start_date
+        self.end_date = ti.end_date
         if self.end_date and self.start_date:
             self.duration = int((self.end_date - self.start_date).total_seconds())
         else:
diff --git a/airflow/models/taskinstance.py b/airflow/models/taskinstance.py
--- a/airflow/models/taskinstance.py
+++ b/airflow/models/taskinstance.py
@@ -20,6 +20,7 @@
 import hashlib
 import logging
 import math
+import operator
 import os
 import pickle
 import signal
@@ -133,6 +134,7 @@
 
 
 if TYPE_CHECKING:
+    from airflow.models.baseoperator import BaseOperator
     from airflow.models.dag import DAG, DagModel
     from airflow.models.dagrun import DagRun
     from airflow.models.operator import Operator
@@ -1901,24 +1903,15 @@ def handle_failure(
         if not test_mode:
             self.refresh_from_db(session)
 
-        task = self.task.unmap()
         self.end_date = timezone.utcnow()
         self.set_duration()
-        Stats.incr(f'operator_failures_{task.task_type}', 1, 1)
+        Stats.incr(f'operator_failures_{self.task.task_type}')
         Stats.incr('ti_failures')
         if not test_mode:
             session.add(Log(State.FAILED, self))
 
             # Log failure duration
-            session.add(
-                TaskFail(
-                    task=task,
-                    run_id=self.run_id,
-                    start_date=self.start_date,
-                    end_date=self.end_date,
-                    map_index=self.map_index,
-                )
-            )
+            session.add(TaskFail(ti=self))
 
         self.clear_next_method_args()
 
@@ -1934,20 +1927,26 @@ def handle_failure(
         # only mark task instance as FAILED if the next task instance
         # try_number exceeds the max_tries ... or if force_fail is truthy
 
+        task = None
+        try:
+            task = self.task.unmap()
+        except Exception:
+            self.log.error("Unable to unmap task, can't determine if we need to send an alert email or not")
+
         if force_fail or not self.is_eligible_to_retry():
             self.state = State.FAILED
-            email_for_state = task.email_on_failure
+            email_for_state = operator.attrgetter('email_on_failure')
         else:
             if self.state == State.QUEUED:
                 # We increase the try_number so as to fail the task if it fails to start after sometime
                 self._try_number += 1
             self.state = State.UP_FOR_RETRY
-            email_for_state = task.email_on_retry
+            email_for_state = operator.attrgetter('email_on_retry')
 
         self._log_state('Immediate failure requested. ' if force_fail else '')
-        if email_for_state and task.email:
+        if task and email_for_state(task) and task.email:
             try:
-                self.email_alert(error)
+                self.email_alert(error, task)
             except Exception:
                 self.log.exception('Failed to send email to: %s', task.email)
 
@@ -2241,11 +2240,15 @@ def render_k8s_pod_yaml(self) -> Optional[dict]:
         sanitized_pod = ApiClient().sanitize_for_serialization(pod)
         return sanitized_pod
 
-    def get_email_subject_content(self, exception: BaseException) -> Tuple[str, str, str]:
+    def get_email_subject_content(
+        self, exception: BaseException, task: Optional["BaseOperator"] = None
+    ) -> Tuple[str, str, str]:
         """Get the email subject content for exceptions."""
         # For a ti from DB (without ti.task), return the default value
         # Reuse it for smart sensor to send default email alert
-        use_default = not hasattr(self, 'task')
+        if task is None:
+            task = getattr(self, 'task')
+        use_default = task is None
         exception_html = str(exception).replace('\n', '<br>')
 
         default_subject = 'Airflow alert: {{ti}}'
@@ -2312,13 +2315,14 @@ def render(key: str, content: str) -> str:
 
         return subject, html_content, html_content_err
 
-    def email_alert(self, exception):
+    def email_alert(self, exception, task: "BaseOperator"):
         """Send alert email with exception information."""
-        subject, html_content, html_content_err = self.get_email_subject_content(exception)
+        subject, html_content, html_content_err = self.get_email_subject_content(exception, task=task)
+        assert task.email
         try:
-            send_email(self.task.email, subject, html_content)
+            send_email(task.email, subject, html_content)
         except Exception:
-            send_email(self.task.email, subject, html_content_err)
+            send_email(task.email, subject, html_content_err)
 
     def set_duration(self) -> None:
         """Set TI duration"""
@@ -2573,9 +2577,10 @@ def __init__(
         dag_id: str,
         task_id: str,
         run_id: str,
-        start_date: datetime,
-        end_date: datetime,
+        start_date: Optional[datetime],
+        end_date: Optional[datetime],
         try_number: int,
+        map_index: int,
         state: str,
         executor_config: Any,
         pool: str,
@@ -2584,21 +2589,20 @@ def __init__(
         run_as_user: Optional[str] = None,
         priority_weight: Optional[int] = None,
     ):
-        self._dag_id: str = dag_id
-        self._task_id: str = task_id
-        self._run_id: str = run_id
-        self._start_date: datetime = start_date
-        self._end_date: datetime = end_date
-        self._try_number: int = try_number
-        self._state: str = state
-        self._executor_config: Any = executor_config
-        self._run_as_user: Optional[str] = None
-        self._run_as_user = run_as_user
-        self._pool: str = pool
-        self._priority_weight: Optional[int] = None
-        self._priority_weight = priority_weight
-        self._queue: str = queue
-        self._key = key
+        self.dag_id = dag_id
+        self.task_id = task_id
+        self.run_id = run_id
+        self.map_index = map_index
+        self.start_date = start_date
+        self.end_date = end_date
+        self.try_number = try_number
+        self.state = state
+        self.executor_config = executor_config
+        self.run_as_user = run_as_user
+        self.pool = pool
+        self.priority_weight = priority_weight
+        self.queue = queue
+        self.key = key
 
     def __eq__(self, other):
         if isinstance(other, self.__class__):
@@ -2611,6 +2615,7 @@ def from_ti(cls, ti: TaskInstance):
             dag_id=ti.dag_id,
             task_id=ti.task_id,
             run_id=ti.run_id,
+            map_index=ti.map_index,
             start_date=ti.start_date,
             end_date=ti.end_date,
             try_number=ti.try_number,
@@ -2625,80 +2630,16 @@ def from_ti(cls, ti: TaskInstance):
 
     @classmethod
     def from_dict(cls, obj_dict: dict):
-        ti_key = obj_dict.get('_key', [])
-        start_date: Union[Any, datetime] = (
-            datetime.fromisoformat(str(obj_dict.get('_start_date')))
-            if obj_dict.get('_start_date') is not None
-            else None
-        )
-        end_date: Union[Any, datetime] = (
-            datetime.fromisoformat(str(obj_dict.get('_end_date')))
-            if obj_dict.get('_end_date') is not None
-            else None
-        )
-        return cls(
-            dag_id=str(obj_dict['_dag_id']),
-            task_id=str(obj_dict.get('_task_id')),
-            run_id=str(obj_dict.get('_run_id')),
-            start_date=start_date,
-            end_date=end_date,
-            try_number=obj_dict.get('_try_number', 1),
-            state=str(obj_dict.get('_state')),
-            executor_config=obj_dict.get('_executor_config'),
-            run_as_user=obj_dict.get('_run_as_user', None),
-            pool=str(obj_dict.get('_pool')),
-            priority_weight=obj_dict.get('_priority_weight', None),
-            queue=str(obj_dict.get('_queue')),
-            key=TaskInstanceKey(ti_key[0], ti_key[1], ti_key[2], ti_key[3], ti_key[4]),
-        )
-
-    @property
-    def dag_id(self) -> str:
-        return self._dag_id
-
-    @property
-    def task_id(self) -> str:
-        return self._task_id
-
-    @property
-    def run_id(self) -> str:
-        return self._run_id
-
-    @property
-    def start_date(self) -> datetime:
-        return self._start_date
-
-    @property
-    def end_date(self) -> datetime:
-        return self._end_date
-
-    @property
-    def try_number(self) -> int:
-        return self._try_number
-
-    @property
-    def state(self) -> str:
-        return self._state
-
-    @property
-    def pool(self) -> str:
-        return self._pool
-
-    @property
-    def priority_weight(self) -> Optional[int]:
-        return self._priority_weight
-
-    @property
-    def queue(self) -> str:
-        return self._queue
-
-    @property
-    def key(self) -> TaskInstanceKey:
-        return self._key
-
-    @property
-    def executor_config(self):
-        return self._executor_config
+        ti_key = TaskInstanceKey(*obj_dict.pop('key'))
+        start_date = None
+        end_date = None
+        start_date_str: Optional[str] = obj_dict.pop('start_date')
+        end_date_str: Optional[str] = obj_dict.pop('end_date')
+        if start_date_str:
+            start_date = timezone.parse(start_date_str)
+        if end_date_str:
+            end_date = timezone.parse(end_date_str)
+        return cls(**obj_dict, start_date=start_date, end_date=end_date, key=ti_key)
 
 
 STATICA_HACK = True


Test patch:
diff --git a/tests/api/common/test_delete_dag.py b/tests/api/common/test_delete_dag.py
--- a/tests/api/common/test_delete_dag.py
+++ b/tests/api/common/test_delete_dag.py
@@ -96,15 +96,7 @@ def setup_dag_models(self, for_sub_dag=False):
                     event="varimport",
                 )
             )
-            session.add(
-                TF(
-                    task=task,
-                    run_id=ti.run_id,
-                    start_date=test_date,
-                    end_date=test_date,
-                    map_index=ti.map_index,
-                )
-            )
+            session.add(TF(ti=ti))
             session.add(
                 TR(
                     task=ti.task,
diff --git a/tests/callbacks/test_callback_requests.py b/tests/callbacks/test_callback_requests.py
--- a/tests/callbacks/test_callback_requests.py
+++ b/tests/callbacks/test_callback_requests.py
@@ -65,6 +65,6 @@ class TestCallbackRequest(unittest.TestCase):
     def test_from_json(self, input, request_class):
         json_str = input.to_json()
 
-        result = getattr(request_class, 'from_json')(json_str=json_str)
+        result = request_class.from_json(json_str=json_str)
 
         self.assertEqual(result, input)
diff --git a/tests/jobs/test_scheduler_job.py b/tests/jobs/test_scheduler_job.py
--- a/tests/jobs/test_scheduler_job.py
+++ b/tests/jobs/test_scheduler_job.py
@@ -246,7 +246,7 @@ def test_process_executor_events(self, mock_stats_incr, mock_task_callback, dag_
         mock_stats_incr.assert_has_calls(
             [
                 mock.call('scheduler.tasks.killed_externally'),
-                mock.call('operator_failures_EmptyOperator', 1, 1),
+                mock.call('operator_failures_EmptyOperator'),
                 mock.call('ti_failures'),
             ],
             any_order=True,
@@ -303,7 +303,7 @@ def test_process_executor_events_with_no_callback(self, mock_stats_incr, mock_ta
         mock_stats_incr.assert_has_calls(
             [
                 mock.call('scheduler.tasks.killed_externally'),
-                mock.call('operator_failures_EmptyOperator', 1, 1),
+                mock.call('operator_failures_EmptyOperator'),
                 mock.call('ti_failures'),
             ],
             any_order=True,
@@ -3793,6 +3793,7 @@ def test_find_zombies(self):
             assert ti.dag_id == requests[0].simple_task_instance.dag_id
             assert ti.task_id == requests[0].simple_task_instance.task_id
             assert ti.run_id == requests[0].simple_task_instance.run_id
+            assert ti.map_index == requests[0].simple_task_instance.map_index
 
             session.query(TaskInstance).delete()
             session.query(LocalTaskJob).delete()