imonteroq opened a new issue, #72123:
URL: https://github.com/apache/airflow/issues/72123

   ### Under which category would you file this issue?
   
   Providers
   
   ### Apache Airflow version
   
    3.2.1
   
   ### What happened and how to reproduce it?
   
   **Issue Description** 
   `EmrServerlessDeleteApplicationOperator` never calls the `DeleteApplication` 
API when `deferrable=True`. The task succeeds and logs `EMR serverless 
application <id> deleted successfully`, but the application is only *stopped*, 
never deleted. This is silent: no error, no warning, and the log explicitly 
claims the deletion succeeded.
   
   **Root cause**
   `EmrServerlessDeleteApplicationOperator` subclasses 
`EmrServerlessStopApplicationOperator` and begins `execute()` by delegating to 
the parent (line numbers from 9.35.0, 
`airflow/providers/amazon/aws/operators/emr.py`):
    
      ```python
      # line 1819
      def execute(self, context: Context) -> None:
          # super stops the app (or makes sure it's already stopped)
          super().execute(context)
    
          self.log.info("Now deleting application: %s", self.application_id)   
# line 1821
          response = 
self.hook.conn.delete_application(applicationId=self.application_id)
      ```
    
   With `deferrable=True`, the parent's `execute()` reaches this (line 1713):
    
      ```python
      self.hook.conn.stop_application(applicationId=self.application_id)
      if self.deferrable:
          self.defer(
              trigger=EmrServerlessStopApplicationTrigger(...),
              method_name="execute_complete",
          )
      ```
    
   `self.defer()` is typed `NoReturn` and works by raising `TaskDeferred`, so 
it unwinds the stack. `super().execute(context)` never returns, and line 1821 
onwards never runs.
    
   The task is then resumed at `method_name="execute_complete"`. Because the 
subclass overrides that name, the **stop** trigger's completion is handled by 
the **delete** operator's completion handler (line 1859):
    
      ```python
      def execute_complete(self, context, event=None) -> None:
          validated_event = validate_execute_complete_event(event)
          if validated_event["status"] != "success":
              raise AirflowException(f"Error deleting EMR Serverless 
application: {validated_event}")
          self.log.info("EMR serverless application %s deleted successfully", 
self.application_id)
      ```
    
   The event reports `success` because stopping genuinely succeeded, so the 
handler logs "deleted successfully" and the task turns green. 
`DeleteApplication` is never issued.
    
   `EmrServerlessStopApplicationOperator` hardcodes 
`method_name="execute_complete"` in two places (lines 1713 and 1743), so the 
subclass has no way to insert the delete step between "stopped" and "done".
    
      **Steps to reproduce**
      Self-contained, no AWS account required:
      ```python
      from unittest import mock
    
      from airflow.providers.amazon.aws.hooks.emr import EmrServerlessHook
      from airflow.providers.amazon.aws.operators.emr import 
EmrServerlessDeleteApplicationOperator
    
      with mock.patch.object(EmrServerlessHook, "conn") as conn:
          op = EmrServerlessDeleteApplicationOperator(
              task_id="delete_app", application_id="test-app-id", 
deferrable=True
          )
          try:
              op.execute(None)
          except BaseException as e:
              print("raised:", type(e).__name__)
              print("  trigger    :", type(getattr(e, "trigger", 
None)).__name__)
              print("  method_name:", getattr(e, "method_name", None))
    
          print("stop_application called  :", conn.stop_application.called)
          print("delete_application called:", conn.delete_application.called)
    
          # simulate the triggerer firing a successful STOP event
          op.execute_complete(None, {"status": "success"})
          print("after execute_complete -> delete_application called:", 
conn.delete_application.called)
      ```
    
   Actual output:
      ```
      Stopping application: test-app-id
      raised: TaskDeferred
        trigger    : EmrServerlessStopApplicationTrigger
        method_name: execute_complete
      stop_application called  : True
      delete_application called: False
      EMR serverless application test-app-id deleted successfully
      after execute_complete -> delete_application called: False
      ```
    
   Note the operator defers on the **StopApplication** trigger, and 
`delete_application` is never called on either the initial execution or the 
resume, yet the final log line reports a successful deletion.
    
   Equivalent end-to-end reproduction against real AWS:
      1. Create an EMR Serverless application.
      2. Run `EmrServerlessDeleteApplicationOperator(application_id=..., 
deferrable=True)`.
      3. The task succeeds and logs "deleted successfully".
      4. `aws emr-serverless list-applications` still shows the application in 
`STOPPED` state, and CloudTrail records `StopApplication` but no 
`DeleteApplication`.
    
   With `deferrable=False` the operator behaves correctly: the parent does not 
defer, `super().execute()` returns normally, and `delete_application()` is 
reached.
   
   ### What you think should happen instead?
   
   `DeleteApplication` should be called in both deferrable and non-deferrable 
modes, and the
      task should only log a successful deletion once the application has 
actually been deleted.
    
      Currently the operator reports success while leaving the resource in 
place, which is worse
      than failing, because there is no signal that anything is wrong.
    
      The provider already contains the pattern needed to fix this. In the 
`force_stop` path,
      `EmrServerlessStopApplicationOperator.execute()` defers with
      `method_name="stop_application"` and that method performs the next API 
call before
      deferring again. Chained deferral is therefore established style in this 
same class; the
      delete operator simply does not use it.
    
      A minimal fix would make the parent's post-stop resume target 
overridable, for example a
      class attribute:
    
      ```python
      class 
EmrServerlessStopApplicationOperator(AwsBaseOperator[EmrServerlessHook]):
          #: Method to resume at once the application has stopped. Subclasses 
may override this
          #: to chain further steps instead of finishing the task.
          stop_complete_method_name: str = "execute_complete"
      ```
    
      used in place of both hardcoded `method_name="execute_complete"` 
occurrences (lines 1713
      and 1743). The delete operator then sets `stop_complete_method_name = 
"stop_complete"` and
      implements `stop_complete()` to issue the `DeleteApplication` call and 
defer on
      `EmrServerlessDeleteApplicationTrigger`, leaving `execute_complete` to 
handle only the
      delete trigger's event.
    
      This keeps the deferrable behaviour intact (no worker slot held during 
either wait) and is
      backwards compatible, since `execute_complete` continues to exist for 
task instances that
      were already deferred by an older version at upgrade time.
   
   
   ### Operating System
   
   Reproduced on macOS 15.6 (Darwin 25.6.0), Python 3.12. Also observed in 
production on Amazon Linux (Amazon MWAA). Not OS-dependent.
   
   ### Deployment
   
   Amazon (AWS) MWAA
   
   ### Apache Airflow Provider(s)
   
   amazon
   
   ### Versions of Apache Airflow Providers
   
    ```
      apache-airflow-providers-amazon==9.25.0
      ```
    
   Also verified present in `apache-airflow-providers-amazon==9.35.0` (latest 
release at time of writing) and on the `main` branch. 
`EmrServerlessStopApplicationOperator` and 
`EmrServerlessDeleteApplicationOperator` are byte-identical between 9.25.0 and 
9.35.0.
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   Not Applicable
   
   ### Helm Chart configuration
   
   Not Applicable
   
   ### Docker Image customizations
   
   Not Applicable
   
   ### 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)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to