def test_existing(self, mock_get_collection): mock_update = mock_get_collection.return_value.update fake_id = bson.ObjectId() call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', id=fake_id) call.save() expected = call.as_dict() del expected['_id'] mock_update.assert_called_once_with({'_id': fake_id}, expected)
def test_expected_runs_positive(self, mock_time): # specify a start time and current time such that we know the difference mock_time.return_value = 1389307330.966561 call = ScheduledCall('2014-01-09T17:15Z/PT1H', 'pulp.tasks.dosomething') expected_runs = call._calculate_times()[5] # we know that it's been more than 5 hours since the first scheduled run self.assertEqual(expected_runs, 5)
def test_new(self, mock_get_collection): mock_insert = mock_get_collection.return_value.insert call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') call.save() expected = call.as_dict() expected['_id'] = bson.ObjectId(expected['_id']) mock_insert.assert_called_once_with(expected, safe=True) self.assertFalse(call._new)
def test_first_run_string(self): first_run = dateutils.format_iso8601_datetime( datetime.utcnow().replace(tzinfo=dateutils.utc_tz()) + timedelta(days=1)) call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', first_run=first_run) self.assertEqual(first_run, call.first_run)
def create(cls, repo_id, importer_id, sync_options, schedule, failure_threshold=None, enabled=True): """ Create a new sync schedule for a given repository using the given importer. :param repo_id: unique ID for a repository :type repo_id: basestring :param importer_id: unique ID for an importer :type importer_id: basestring :param sync_options: dictionary that contains the key 'override_config', whose value should be passed as the 'overrides' parameter to the sync task. This wasn't originally documented, so it isn't clear why overrides value couldn't be passed directly. :type sync_options: dict :param schedule_data: dictionary that contains the key 'schedule', whose value is an ISO8601 string. This wasn't originally documented, so it isn't clear why the string itself couldn't have been passed directly. :type schedule_data: dict :return: new schedule instance :rtype: pulp.server.db.model.dispatch.ScheduledCall """ # validate the input importer_controller.get_valid_importer(repo_id, importer_id) utils.validate_keys(sync_options, _SYNC_OPTION_KEYS) utils.validate_initial_schedule_options(schedule, failure_threshold, enabled) task = repo_controller.queue_sync_with_auto_publish.name args = [repo_id] kwargs = {'overrides': sync_options['override_config']} resource = importer_controller.build_resource_tag(repo_id, importer_id) schedule = ScheduledCall(schedule, task, args=args, kwargs=kwargs, resource=resource, failure_threshold=failure_threshold, enabled=enabled) schedule.save() try: importer_controller.get_valid_importer(repo_id, importer_id) except exceptions.MissingResource: # back out of this whole thing, since the importer disappeared utils.delete(schedule.id) raise return schedule
def test_first_run_saved(self): """ Test that when the first run is passed in from historical data. """ call = ScheduledCall('PT1H', 'pulp.tasks.dosomething', first_run='2014-01-03T10:15Z') first_run_s = call._calculate_times()[1] # make sure this gives us a timestamp for the date and time # specified above self.assertEqual(first_run_s, 1388744100)
def test_expected_runs_future(self, mock_time): # specify a start time and current time such that the start appears to # be in the future mock_time.return_value = 1389307330.966561 call = ScheduledCall('2014-01-19T17:15Z/PT1H', 'pulp.tasks.dosomething') expected_runs = call._calculate_times()[5] # the first run is scheduled in the future (relative to the mock time), # so there should not be any runs. self.assertEqual(expected_runs, 0)
def test_future(self, mock_time): mock_time.return_value = 1389307330.966561 call = ScheduledCall('2014-01-19T17:15Z/PT1H', 'pulp.tasks.dosomething') next_run = call.calculate_next_run() # make sure the next run is equal to the specified first run. # don't want to compare a generated ISO8601 string directly, because there # could be subtle variations that are valid but break string equality. self.assertEqual(dateutils.parse_iso8601_interval(call.iso_schedule)[1], dateutils.parse_iso8601_datetime(next_run))
def test_first_run_datetime(self): first_run = datetime.utcnow().replace( tzinfo=dateutils.utc_tz()) + timedelta(days=1) call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', first_run=first_run) # make sure it is an ISO8601 string with the correct value self.assertTrue(isinstance(call.first_run, basestring)) self.assertEqual(dateutils.format_iso8601_datetime(first_run), call.first_run)
def test_with_past_runs(self, mock_time): # setup an hourly call that first ran not quite 2 hours ago, ran again # less than one hour ago, and should be scheduled to run at the end of # this hour mock_time.return_value = 1389389758.547976 call = ScheduledCall('2014-01-10T20:00Z/PT1H', 'pulp.tasks.dosomething', total_run_count=2, last_run_at='2014-01-10T21:00Z') next_run = call.calculate_next_run() self.assertEqual(dateutils.parse_iso8601_datetime('2014-01-10T22:00Z'), dateutils.parse_iso8601_datetime(next_run))
def test_past_runs_not_due(self, mock_time): mock_time.return_value = 1389389758 # 2014-01-10T21:35:58 # This call ran at the top of the hour, so it does not need to run again # until the top of the next hour. call = ScheduledCall('2014-01-10T20:00Z/PT1H', 'pulp.tasks.dosomething', last_run_at='2014-01-10T21:00Z', total_run_count=2) entry = call.as_schedule_entry() is_due, seconds = entry.is_due() self.assertFalse(is_due) # this was hand-calculated as the remaining time until the next hourly run self.assertEqual(seconds, 1442)
def test_past_runs_due(self, mock_time): mock_time.return_value = 1389389758 # 2014-01-10T21:35:58 # This call did not run at the top of the hour, so it is overdue and should # run now. Its next run will be back on the normal hourly schedule, at # the top of the next hour. call = ScheduledCall('2014-01-10T20:00Z/PT1H', 'pulp.tasks.dosomething', last_run_at='2014-01-10T20:00Z', total_run_count=1) entry = call.as_schedule_entry() is_due, seconds = entry.is_due() self.assertTrue(is_due) # this was hand-calculated as the remaining time until the next hourly run self.assertEqual(seconds, 1442)
def update(schedule_id, delta): """ Updates the schedule with unique ID schedule_id. This only allows updating of fields in ScheduledCall.USER_UPDATE_FIELDS. :param schedule_id: a unique ID for a schedule :type schedule_id: basestring :param delta: a dictionary of keys with values that should be modified on the schedule. :type delta: dict :return: instance of ScheduledCall representing the post-update state :rtype ScheduledCall :raise exceptions.UnsupportedValue :raise exceptions.MissingResource """ unknown_keys = set(delta.keys()) - ScheduledCall.USER_UPDATE_FIELDS if unknown_keys: raise exceptions.UnsupportedValue(list(unknown_keys)) delta['last_updated'] = time.time() # bz 1139703 - if we update iso_schedule, update the pickled object as well if 'iso_schedule' in delta: interval, start_time, occurrences = dateutils.parse_iso8601_interval( delta['iso_schedule']) delta['schedule'] = pickle.dumps(CelerySchedule(interval)) # set first_run and next_run so that the schedule update will take effect new_schedule_call = ScheduledCall(delta['iso_schedule'], 'dummytaskname') delta['first_run'] = new_schedule_call.first_run delta['next_run'] = new_schedule_call.next_run try: spec = {'_id': ObjectId(schedule_id)} except InvalidId: # During schedule update, MissingResource should be raised even if # schedule_id is invalid object_id. raise exceptions.MissingResource(schedule_id=schedule_id) schedule = ScheduledCall.get_collection().find_and_modify( query=spec, update={'$set': delta}, safe=True, new=True) if schedule is None: raise exceptions.MissingResource(schedule_id=schedule_id) return ScheduledCall.from_db(schedule)
def test_with_months_duration(self, mock_time): """ Test calculating the next run when the interval is a Duration object and uses months """ last_runs = ('2015-01-01T10:00Z', '2015-02-01T10:00Z', '2015-03-01T10:00Z', '2015-04-01T10:00Z') expected_next_runs = ('2015-02-01T10:00Z', '2015-03-01T10:00Z', '2015-04-01T10:00Z', '2015-05-01T10:00Z') times = ( 1422784799.0, # Just before 2015-02-01T10:00Z UTC 1425203999.0, # Just before 2015-03-01T10:00Z UTC 1427882399.0, # Just before 2015-04-01T10:00Z UTC 1430474399.0, # Just before 2015-05-01T10:00Z UTC ) for last_run, current_time, expected_next_run in zip(last_runs, times, expected_next_runs): mock_time.return_value = current_time call = ScheduledCall('2014-12-01T10:00Z/P1M', 'pulp.tasks.dosomething', total_run_count=2, last_run_at=last_run) next_run = call.calculate_next_run() self.assertEqual(dateutils.parse_iso8601_datetime(expected_next_run), dateutils.parse_iso8601_datetime(next_run))
def test_with_years_duration(self, mock_time): """ Test calculating the next run when the interval is a Duration object and uses years """ last_runs = ('2015-01-01T10:00Z', '2016-01-01T10:00Z', '2017-01-01T10:00Z', '2018-01-01T10:00Z') expected_next_runs = ('2016-01-01T10:00Z', '2017-01-01T10:00Z', '2018-01-01T10:00Z', '2019-01-01T10:00Z') times = ( 1451642000.0, # Just before 2016-01-01T10:00Z UTC 1483264000.0, # Just before 2017-01-01T10:00Z UTC 1514800000.0, # Just before 2018-01-01T10:00Z UTC 1546336000.0, # Just before 2019-01-01T10:00Z UTC ) for last_run, current_time, expected_next_run in zip(last_runs, times, expected_next_runs): mock_time.return_value = current_time call = ScheduledCall('2014-01-01T10:00Z/P1M', 'pulp.tasks.dosomething', total_run_count=2, last_run_at=last_run) next_run = call.calculate_next_run() self.assertEqual(dateutils.parse_iso8601_datetime(expected_next_run), dateutils.parse_iso8601_datetime(next_run))
def test_id_added_to_kwargs(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') self.assertEqual(call.kwargs['scheduled_call_id'], call.id)
def test_expected_runs_zero(self): call = ScheduledCall('PT1H', 'pulp.tasks.dosomething') expected_runs = call._calculate_times()[5] self.assertEqual(expected_runs, 0)
def test_pass_in_schedule(self): schedule = pickle.dumps(CelerySchedule(60)) call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', schedule=schedule) self.assertEqual(call.schedule, schedule)
def test_next_run_ignored(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', next_run='foo') self.assertTrue(call.next_run != 'foo')
def test_remaining_runs_in_string(self): call = ScheduledCall('R3/PT1M', 'pulp.tasks.dosomething') self.assertEqual(call.remaining_runs, 3)
def test_remaining_runs_passed_int(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', remaining_runs=2) self.assertEqual(call.remaining_runs, 2)
def test_new(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') # make sure the call generates its own object ID self.assertTrue(len(call.id) > 0) self.assertTrue(isinstance(call._id, bson.ObjectId))
def test_create_principal(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') # See PrincipalManager.get_principal(). It returns either a User or # a dict. Not my idea. self.assertTrue(isinstance(call.principal, (User, dict)))
def test_pass_in_principal(self): principal = User('me', 'letmein') call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', principal=principal) self.assertEqual(call.principal, principal)
def test_captures_scheduled_call(self): call = ScheduledCall('2014-01-19T17:15Z/PT1H', 'pulp.tasks.dosomething') entry = call.as_schedule_entry() self.assertTrue(hasattr(entry, '_scheduled_call')) self.assertTrue(entry._scheduled_call is call)
def test_next_run_calculated(self, mock_calc): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething', next_run='foo') self.assertEqual(call.next_run, mock_calc.return_value) mock_calc.assert_called_once_with()
def setUp(self): super(TestScheduleEntryIsDue, self).setUp() self.call = ScheduledCall('2014-01-19T17:15Z/PT1H', 'pulp.tasks.dosomething', remaining_runs=5) self.entry = self.call.as_schedule_entry()
def test_returns_dict(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') self.assertTrue(isinstance(call.for_display(), dict))
def test_remaining_runs_none(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') self.assertTrue(call.remaining_runs is None)
def test_pass_in_task_name(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') self.assertEqual(call.task, 'pulp.tasks.dosomething')