def test_reschedule(self): """ Test reschedule. """ self.assertEqual(1, Run.objects.filter(job_id=1).count()) Job.objects.get(pk=1).reschedule() # because of the pre-scheduling, reschedule should result in max # 2 scheduled runs. self.assertEqual(2, Run.objects.filter(job_id=1).count()) Job.objects.get(pk=1).reschedule() self.assertEqual(2, Run.objects.filter(job_id=1).count()) Run.objects.filter(pk=1).update(schedule_dts=timezone.now(), enqueue_dts=timezone.now(), return_dts=timezone.now()) # we just completed one run, so reschedule should result in a new # schedule. Job.objects.get(pk=1).reschedule() self.assertEqual(3, Run.objects.filter(job_id=1).count()) # make sure the last run is scheduled based on the schedule_dts of the # pre-last run runs = Run.objects.filter(job_id=1).all() self.assertEqual( correct_dst_difference(runs[1].schedule_dts, runs[1].schedule_dts + timedelta(days=1)), runs[2].schedule_dts)
def reschedule(self): """ Reschedule job. This will check if the job is setup for reschedule. Then it will try to get the next reschedule date. When getting this date fails (eg: when the rescheduling always falls within the reschedule exclude), it will send out an e-mail to the Job-Runner admins and the e-mail addresses that are setup for this job, script and server. """ # there is already an other run which is not finished yet, do # not re-schedule, it will be rescheduled when the other job # finishes active_runs = self.run_set.filter(return_dts__isnull=True) active_schedule_ids = [] # TODO: is there a way in the Django ORM to achieve the same? for active_run in active_runs: if not active_run.schedule_id in active_schedule_ids: active_schedule_ids.append(active_run.schedule_id) # since we are pre-scheduling (a new run is created, before the # scheduled run is sent to the worker), having one schedule id is valid if len(active_schedule_ids) > 1: logger.error('Multiple ({0}) active schedule ids for {1}'.format( active_schedule_ids, self.log_name())) return logger.info('Attempting rescheduling {0}'.format(self.log_name())) # check if job is setup for re-scheduling if self.reschedule_interval_type and self.reschedule_interval: try: # order by -pk to get the last non-manual scheduled run # from which we need to increment last_run = self.run_set.filter( is_manual=False).order_by('-pk')[0] except IndexError: logger.error('Reschedule failed for {0}: IndexError'.format( self.log_name())) return try: reschedule_date = self._get_reschedule_date( last_run.schedule_dts) # correct daylight saving-time changes to make sure we keep # re-scheduling at the same hour (in local time). reschedule_date = correct_dst_difference( last_run.schedule_dts, reschedule_date) self.schedule(reschedule_date) logger.info('Rescheduled {0} for {1}'.format( self.log_name(), reschedule_date)) except RescheduleException: logger.error( 'Reschedule failed for {0}: RescheduleException'.format( self.log_name())) notifications.reschedule_failed(self)
def test_reschedule_not_in_past(self): """ Test reschedule dts is never in the past. """ dts_now = timezone.now() Run.objects.filter(pk=1).update( schedule_dts=dts_now - timedelta(days=31), enqueue_dts=dts_now - timedelta(days=31), return_dts=dts_now - timedelta(days=31) ) Job.objects.get(pk=1).reschedule() self.assertEqual(2, Run.objects.filter(job_id=1).count()) run = Run.objects.get(pk=3) # we have to make sure to use correct_dst_difference since between # 31 days ago and tomorrow, we might have changed from or to DTS. self.assertEqual( run.schedule_dts, correct_dst_difference( dts_now - timedelta(days=31), dts_now + timedelta(days=1) ) )
def test_reschedule(self): """ Test reschedule. """ self.assertEqual(1, Run.objects.filter(job_id=1).count()) Job.objects.get(pk=1).reschedule() # because of the pre-scheduling, reschedule should result in max # 2 scheduled runs. self.assertEqual(2, Run.objects.filter(job_id=1).count()) Job.objects.get(pk=1).reschedule() self.assertEqual(2, Run.objects.filter(job_id=1).count()) Run.objects.filter(pk=1).update( schedule_dts=timezone.now(), enqueue_dts=timezone.now(), return_dts=timezone.now() ) # we just completed one run, so reschedule should result in a new # schedule. Job.objects.get(pk=1).reschedule() self.assertEqual(3, Run.objects.filter(job_id=1).count()) # make sure the last run is scheduled based on the schedule_dts of the # pre-last run runs = Run.objects.filter(job_id=1).all() self.assertEqual( correct_dst_difference( runs[1].schedule_dts, runs[1].schedule_dts + timedelta(days=1) ), runs[2].schedule_dts )
def test_correct_dts_difference(self): """ Test :func:`.correct_dst_difference`. This will test that during a DST change (from +1 to +2), the :func:`.correct_dst_difference` function will make sure that the local will stay the same. """ tzinfo = get_current_timezone() prev_dts = tzinfo.localize(datetime(2013, 3, 30, 4)) next_dts = prev_dts + timedelta(days=1) expected_next_dts = tzinfo.localize(datetime(2013, 3, 31, 4)) # test that indeed there is a DST change self.assertEqual(timedelta(hours=1), prev_dts.utcoffset()) self.assertEqual(timedelta(hours=2), expected_next_dts.utcoffset()) # test that normally, the job would be scheduled at 05:00 self.assertEqual( tzinfo.localize(datetime(2013, 3, 31, 5)), next_dts ) # test that correct_dst_difference is correcting this to 04:00 self.assertEqual( expected_next_dts, correct_dst_difference(prev_dts, next_dts) )
def test_reschedule_monthly_after_schedule_dts(self): """ Test monthy reschedule after schedule dts. """ in_and_expected = [ (datetime(2032, 1, 1), datetime(2032, 2, 1)), (datetime(2032, 2, 1), datetime(2032, 3, 1)), (datetime(2032, 3, 1), datetime(2032, 4, 1)), (datetime(2032, 4, 1), datetime(2032, 5, 1)), (datetime(2032, 6, 1), datetime(2032, 7, 1)), (datetime(2032, 12, 1), datetime(2033, 1, 1)), ] for in_dts, expected_dts in in_and_expected: # if you do not use utc as a timezone here, you get funny results # because of day light saving time switching in_dts = timezone.make_aware( in_dts, timezone.utc) expected_dts = timezone.make_aware( expected_dts, timezone.utc) job = Job.objects.get(pk=1) job.reschedule_interval_type = 'MONTH' job.save() job.run_set.all().delete() Run.objects.create( job=job, schedule_dts=in_dts, enqueue_dts=in_dts, start_dts=in_dts, return_dts=in_dts, ) job.reschedule() run = Run.objects.filter(job=1, enqueue_dts__isnull=True)[0] self.assertEqual( correct_dst_difference(in_dts, expected_dts), run.schedule_dts )
def test_reschedule_not_in_past(self): """ Test reschedule dts is never in the past. """ dts_now = timezone.now() Run.objects.filter(pk=1).update( schedule_dts=dts_now - timedelta(days=31), enqueue_dts=dts_now - timedelta(days=31), return_dts=dts_now - timedelta(days=31)) Job.objects.get(pk=1).reschedule() self.assertEqual(2, Run.objects.filter(job_id=1).count()) run = Run.objects.get(pk=3) # we have to make sure to use correct_dst_difference since between # 31 days ago and tomorrow, we might have changed from or to DTS. self.assertEqual( run.schedule_dts, correct_dst_difference(dts_now - timedelta(days=31), dts_now + timedelta(days=1)))
def test_reschedule_monthly_after_schedule_dts(self): """ Test monthy reschedule after schedule dts. """ in_and_expected = [ (datetime(2032, 1, 1), datetime(2032, 2, 1)), (datetime(2032, 2, 1), datetime(2032, 3, 1)), (datetime(2032, 3, 1), datetime(2032, 4, 1)), (datetime(2032, 4, 1), datetime(2032, 5, 1)), (datetime(2032, 6, 1), datetime(2032, 7, 1)), (datetime(2032, 12, 1), datetime(2033, 1, 1)), ] for in_dts, expected_dts in in_and_expected: # if you do not use utc as a timezone here, you get funny results # because of day light saving time switching in_dts = timezone.make_aware(in_dts, timezone.utc) expected_dts = timezone.make_aware(expected_dts, timezone.utc) job = Job.objects.get(pk=1) job.reschedule_interval_type = 'MONTH' job.save() job.run_set.all().delete() Run.objects.create( job=job, schedule_dts=in_dts, enqueue_dts=in_dts, start_dts=in_dts, return_dts=in_dts, ) job.reschedule() run = Run.objects.filter(job=1, enqueue_dts__isnull=True)[0] self.assertEqual(correct_dst_difference(in_dts, expected_dts), run.schedule_dts)
def test_correct_dts_difference(self): """ Test :func:`.correct_dst_difference`. This will test that during a DST change (from +1 to +2), the :func:`.correct_dst_difference` function will make sure that the local will stay the same. """ tzinfo = get_current_timezone() prev_dts = tzinfo.localize(datetime(2013, 3, 30, 4)) next_dts = prev_dts + timedelta(days=1) expected_next_dts = tzinfo.localize(datetime(2013, 3, 31, 4)) # test that indeed there is a DST change self.assertEqual(timedelta(hours=1), prev_dts.utcoffset()) self.assertEqual(timedelta(hours=2), expected_next_dts.utcoffset()) # test that normally, the job would be scheduled at 05:00 self.assertEqual(tzinfo.localize(datetime(2013, 3, 31, 5)), next_dts) # test that correct_dst_difference is correcting this to 04:00 self.assertEqual(expected_next_dts, correct_dst_difference(prev_dts, next_dts))