def test_ok_with_custom_reschedule_exception(self): sensor = self._make_sensor( return_value=None, mode='reschedule') date1 = timezone.utcnow() date2 = date1 + timedelta(seconds=60) date3 = date1 + timedelta(seconds=120) sensor.poke = Mock(side_effect=[ AirflowRescheduleException(date2), AirflowRescheduleException(date3), True, ]) dr = self._make_dag_run() # first poke returns False and task is re-scheduled with freeze_time(date1): self._run(sensor) tis = dr.get_task_instances() self.assertEquals(len(tis), 2) for ti in tis: if ti.task_id == SENSOR_OP: # verify task is re-scheduled, i.e. state set to NONE self.assertEquals(ti.state, State.NONE) # verify one row in task_reschedule table task_reschedules = TaskReschedule.find_for_task_instance(ti) self.assertEquals(len(task_reschedules), 1) self.assertEquals(task_reschedules[0].start_date, date1) self.assertEquals(task_reschedules[0].reschedule_date, date2) if ti.task_id == DUMMY_OP: self.assertEquals(ti.state, State.NONE) # second poke returns False and task is re-scheduled with freeze_time(date2): self._run(sensor) tis = dr.get_task_instances() self.assertEquals(len(tis), 2) for ti in tis: if ti.task_id == SENSOR_OP: # verify task is re-scheduled, i.e. state set to NONE self.assertEquals(ti.state, State.NONE) # verify two rows in task_reschedule table task_reschedules = TaskReschedule.find_for_task_instance(ti) self.assertEquals(len(task_reschedules), 2) self.assertEquals(task_reschedules[1].start_date, date2) self.assertEquals(task_reschedules[1].reschedule_date, date3) if ti.task_id == DUMMY_OP: self.assertEquals(ti.state, State.NONE) # third poke returns True and task succeeds with freeze_time(date3): self._run(sensor) tis = dr.get_task_instances() self.assertEquals(len(tis), 2) for ti in tis: if ti.task_id == SENSOR_OP: self.assertEquals(ti.state, State.SUCCESS) if ti.task_id == DUMMY_OP: self.assertEquals(ti.state, State.NONE)
def execute(self, context: Dict) -> Any: started_at = timezone.utcnow() try_number = 1 log_dag_id = self.dag.dag_id if self.has_dag() else "" if self.reschedule: # If reschedule, use first start date of current try task_reschedules = TaskReschedule.find_for_task_instance( context['ti']) if task_reschedules: started_at = task_reschedules[0].start_date try_number = len(task_reschedules) + 1 while not self.poke(context): if (timezone.utcnow() - started_at).total_seconds() > self.timeout: # If sensor is in soft fail mode but will be retried then # give it a chance and fail with timeout. # This gives the ability to set up non-blocking AND soft-fail sensors. if self.soft_fail and not context['ti'].is_eligible_to_retry(): self._do_skip_downstream_tasks(context) raise AirflowSkipException( f"Snap. Time is OUT. DAG id: {log_dag_id}") else: raise AirflowSensorTimeout( f"Snap. Time is OUT. DAG id: {log_dag_id}") if self.reschedule: reschedule_date = timezone.utcnow() + timedelta( seconds=self._get_next_poke_interval( started_at, try_number)) raise AirflowRescheduleException(reschedule_date) else: sleep(self._get_next_poke_interval(started_at, try_number)) try_number += 1 self.log.info("Success criteria met. Exiting.")
def execute(self, context): started_at = timezone.utcnow() if self.reschedule: # If reschedule, use first start date of current try task_reschedules = TaskReschedule.find_for_task_instance(context['ti']) if task_reschedules: started_at = task_reschedules[0].start_date while not self.poke(context): self.log.info("Poke status",self.poke(context)) if (timezone.utcnow() - started_at).total_seconds() > self.timeout: # If sensor is in soft fail mode but will be retried then # give it a chance and fail with timeout. # This gives the ability to set up non-blocking AND soft-fail sensors. if self.soft_fail and not context['ti'].is_eligible_to_retry(): self._do_skip_downstream_tasks(context) raise AirflowSkipException('Snap. Time is OUT.') else: raise AirflowSensorTimeout('Snap. Time is OUT.') if self.reschedule: reschedule_date = timezone.utcnow() + timedelta( seconds=self.poke_interval) raise AirflowRescheduleException(reschedule_date) else: sleep(self.poke_interval) self.log.info("Success criteria met. Exiting.")
def execute(self, context: Context) -> Any: started_at: Union[datetime.datetime, float] if self.reschedule: # If reschedule, use the start date of the first try (first try can be either the very # first execution of the task, or the first execution after the task was cleared.) first_try_number = context['ti'].max_tries - self.retries + 1 task_reschedules = TaskReschedule.find_for_task_instance( context['ti'], try_number=first_try_number ) if not task_reschedules: start_date = timezone.utcnow() else: start_date = task_reschedules[0].start_date started_at = start_date def run_duration() -> float: # If we are in reschedule mode, then we have to compute diff # based on the time in a DB, so can't use time.monotonic return (timezone.utcnow() - start_date).total_seconds() else: started_at = start_monotonic = time.monotonic() def run_duration() -> float: return time.monotonic() - start_monotonic try_number = 1 log_dag_id = self.dag.dag_id if self.has_dag() else "" xcom_value = None while True: poke_return = self.poke(context) if poke_return: if isinstance(poke_return, PokeReturnValue): xcom_value = poke_return.xcom_value break if run_duration() > self.timeout: # If sensor is in soft fail mode but times out raise AirflowSkipException. if self.soft_fail: raise AirflowSkipException(f"Snap. Time is OUT. DAG id: {log_dag_id}") else: raise AirflowSensorTimeout(f"Snap. Time is OUT. DAG id: {log_dag_id}") if self.reschedule: next_poke_interval = self._get_next_poke_interval(started_at, run_duration, try_number) reschedule_date = timezone.utcnow() + timedelta(seconds=next_poke_interval) if _is_metadatabase_mysql() and reschedule_date > _MYSQL_TIMESTAMP_MAX: raise AirflowSensorTimeout( f"Cannot reschedule DAG {log_dag_id} to {reschedule_date.isoformat()} " f"since it is over MySQL's TIMESTAMP storage limit." ) raise AirflowRescheduleException(reschedule_date) else: time.sleep(self._get_next_poke_interval(started_at, run_duration, try_number)) try_number += 1 self.log.info("Success criteria met. Exiting.") return xcom_value
def execute(self, context: Dict) -> Any: started_at = None if self.reschedule: # If reschedule, use first start date of current try task_reschedules = TaskReschedule.find_for_task_instance( context['ti']) if task_reschedules: started_at = task_reschedules[0].start_date try_number = len(task_reschedules) + 1 else: started_at = timezone.utcnow() def run_duration() -> float: # If we are in reschedule mode, then we have to compute diff # based on the time in a DB, so can't use time.monotonic nonlocal started_at return (timezone.utcnow() - started_at).total_seconds() else: started_at = time.monotonic() def run_duration() -> float: nonlocal started_at return time.monotonic() - started_at try_number = 1 log_dag_id = self.dag.dag_id if self.has_dag() else "" while not self.poke(context): if run_duration() > self.timeout: # If sensor is in soft fail mode but will be retried then # give it a chance and fail with timeout. # This gives the ability to set up non-blocking AND soft-fail sensors. if self.soft_fail and not context['ti'].is_eligible_to_retry(): raise AirflowSkipException( f"Snap. Time is OUT. DAG id: {log_dag_id}") else: raise AirflowSensorTimeout( f"Snap. Time is OUT. DAG id: {log_dag_id}") if self.reschedule: reschedule_date = timezone.utcnow() + timedelta( seconds=self._get_next_poke_interval( started_at, run_duration, try_number)) raise AirflowRescheduleException(reschedule_date) else: time.sleep( self._get_next_poke_interval(started_at, run_duration, try_number)) try_number += 1 self.log.info("Success criteria met. Exiting.")
def execute(self, context: Dict) -> Any: started_at = None if self.reschedule: # If reschedule, use the start date of the first try (first try can be either the very # first execution of the task, or the first execution after the task was cleared.) first_try_number = context['ti'].max_tries - self.retries + 1 task_reschedules = TaskReschedule.find_for_task_instance( context['ti'], try_number=first_try_number) if task_reschedules: started_at = task_reschedules[0].start_date else: started_at = timezone.utcnow() def run_duration() -> float: # If we are in reschedule mode, then we have to compute diff # based on the time in a DB, so can't use time.monotonic nonlocal started_at return (timezone.utcnow() - started_at).total_seconds() else: started_at = time.monotonic() def run_duration() -> float: nonlocal started_at return time.monotonic() - started_at try_number = 1 log_dag_id = self.dag.dag_id if self.has_dag() else "" while not self.poke(context): if run_duration() > self.timeout: # If sensor is in soft fail mode but times out raise AirflowSkipException. if self.soft_fail: raise AirflowSkipException( f"Snap. Time is OUT. DAG id: {log_dag_id}") else: raise AirflowSensorTimeout( f"Snap. Time is OUT. DAG id: {log_dag_id}") if self.reschedule: reschedule_date = timezone.utcnow() + timedelta( seconds=self._get_next_poke_interval( started_at, run_duration, try_number)) raise AirflowRescheduleException(reschedule_date) else: time.sleep( self._get_next_poke_interval(started_at, run_duration, try_number)) try_number += 1 self.log.info("Success criteria met. Exiting.")