-
Notifications
You must be signed in to change notification settings - Fork 16.5k
[AIRFLOW-6944] Allow AWS DataSync to "catch up" when Task is already … #7585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import random | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import List | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from airflow.exceptions import AirflowException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from airflow.models import BaseOperator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -105,6 +107,12 @@ class AWSDataSyncOperator(BaseOperator): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ui_color = "#44b5e2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Control when we execute a Task, based on initial Task status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TASK_STATUS_WAIT_BEFORE_START: List[str] = ['CREATING'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TASK_STATUS_START: List[str] = ['AVAILABLE'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TASK_STATUS_SKIP_START: List[str] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TASK_STATUS_FAIL: List[str] = ['UNAVAILABLE', 'QUEUED', 'RUNNING'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @apply_defaults | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __init__( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -174,6 +182,7 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.source_location_arn = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.destination_location_arn = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_execution_arn = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_status = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_hook(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create and return AWSDataSyncHook. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -208,12 +217,41 @@ def execute(self, context): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info("Using DataSync TaskArn %s", self.task_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Update the DataSync Task | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Update the DataSync Task definition | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.update_task_kwargs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._update_datasync_task() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Execute the DataSync Task | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._execute_datasync_task() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait for the Task to be in a valid state to Start | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_status = self._wait_get_status_before_start() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info('Task status is %s.', self.task_status) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.task_status in self.TASK_STATUS_START: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'The Task will be started because its status is in %s.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.TASK_STATUS_START) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Start the DataSync Task | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._start_datasync_task() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif self.task_status in self.TASK_STATUS_SKIP_START: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'The Task will NOT be started because its status is in %s.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.TASK_STATUS_SKIP_START) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.task_execution_arn: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task_description = self.get_hook().get_task_description(self.task_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'CurrentTaskExecutionArn' in task_description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_execution_arn = task_description['CurrentTaskExecutionArn'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Starting the Task was skipped,' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' but no CurrentTaskExecutionArn was found.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif self.task_status in self.TASK_STATUS_FAIL: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Task cannot be started because its status is in %s.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| % self.TASK_STATUS_FAIL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+247
to
+250
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Please use |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException('Unexpected task status %s.' % self.task_status) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._wait_for_datasync_task_execution() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.task_execution_arn: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException("Nothing was executed") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -222,7 +260,10 @@ def execute(self, context): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if self.delete_task_after_execution: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._delete_datasync_task() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {"TaskArn": self.task_arn, "TaskExecutionArn": self.task_execution_arn} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "TaskArn": self.task_arn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "TaskExecutionArn": self.task_execution_arn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_tasks_and_locations(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Find existing DataSync Task based on source and dest Locations.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -331,16 +372,52 @@ def _update_datasync_task(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info("Updated TaskArn %s", self.task_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return self.task_arn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _execute_datasync_task(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create and monitor an AWSDataSync TaskExecution for a Task.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hook = self.get_hook() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _wait_get_status_before_start( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_iterations=12 * 180): # wait_interval_seconds*12*180=180 minutes by default | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You already have that comment in the docs below. Even better it would be to add |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Wait until the Task can be started. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The Task can be started when its Status is not in TASK_STATUS_WAIT_BEFORE_START | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Uses wait_interval_seconds (which is also used while waiting for TaskExecution) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| So, max_iterations=12*180 gives 180 minutes wait by default. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please also add |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hook = self.get_hook() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task_status = hook.get_task_description(self.task_arn)['Status'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iteration = 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while task_status in self.TASK_STATUS_WAIT_BEFORE_START: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Task status is %s.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' Waiting for it to not be %s.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' Iteration %s/%s.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task_status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.TASK_STATUS_WAIT_BEFORE_START, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iteration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_iterations) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time.sleep(self.wait_interval_seconds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task_status = hook.get_task_description(self.task_arn)['Status'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iteration = iteration + 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if iteration >= max_iterations: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return task_status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+385
to
+403
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
WDYT? |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _start_datasync_task(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create an AWSDataSync TaskExecution for a Task.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hook = self.get_hook() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create a task execution: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info("Starting execution for TaskArn %s", self.task_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_execution_arn = hook.start_task_execution( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_arn, **self.task_execution_kwargs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info("Started TaskExecutionArn %s", self.task_execution_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _wait_for_datasync_task_execution(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Monitor an AWSDataSync TaskExecution for a Task.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hook = self.get_hook() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.task_execution_arn: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Unable to wait for TaskExecutionArn to complete' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' because none was provided') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait for task execution to complete | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.info("Waiting for TaskExecutionArn %s", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.task_execution_arn) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -355,9 +432,10 @@ def _execute_datasync_task(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Log some meaningful statuses | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| level = logging.ERROR if not result else logging.INFO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.log(level, 'Status=%s', task_execution_description['Status']) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for k, v in task_execution_description['Result'].items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'Status' in k or 'Error' in k: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.log(level, '%s=%s', k, v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'Result' in task_execution_description: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for k, v in task_execution_description['Result'].items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'Status' in k or 'Error' in k: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.log.log(level, '%s=%s', k, v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not result: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise AirflowException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.