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() try: spec = {'_id': ObjectId(schedule_id)} except InvalidId: raise exceptions.InvalidValue(['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_first_run_now(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') first_run_s = call._calculate_times()[1] # make sure this gives us a timestamp that reasonably represents "now" self.assertTrue(time.time() - first_run_s < 1)
def test_no_last_run(self): call = ScheduledCall('PT1M', 'pulp.tasks.dosomething') entry = call.as_schedule_entry() # celery actually calculates it, so we don't need to test the value self.assertTrue(isinstance(entry.last_run_at, datetime))
def test_get(self, mock_path, mock_ok, mock_utils_get): call = ScheduledCall('PT1M', 'pulp.tasks.frequent') mock_utils_get.return_value = [call] ret = self.controller._get(call.id) schedule = mock_ok.call_args[0][0] self.assertEqual(ret, mock_ok.return_value) self.assertEqual(len(mock_ok.call_args[0]), 1) # spot-check the schedule self.assertEqual(schedule['_id'], call.id) self.assertEqual(schedule['schedule'], 'PT1M') self.assertEqual(schedule['task'], 'pulp.tasks.frequent') self.assertEqual(schedule['_href'], mock_path.return_value) # next_run is calculated on-demand, and there is a small chance that it # will be re-calculated in the call.for_display() call as 1 second later # than it was calculated above. Thus we will test that equality here # with a tolerance of 1 second for_display = call.for_display() call_next_run = dateutils.parse_iso8601_datetime(call.next_run) display_next_run = dateutils.parse_iso8601_datetime(for_display['next_run']) self.assertTrue(display_next_run - call_next_run <= timedelta(seconds=1)) # now check overall equality with the actual for_display value del schedule['_href'] del schedule['next_run'] del for_display['next_run'] self.assertEqual(schedule, for_display) # make sure we called the manager layer correctly mock_utils_get.assert_called_once_with([call.id])
def increment_failure_count(schedule_id): """ Increment the number of consecutive failures, and if it has met or exceeded the threshold, disable the schedule. :param schedule_id: ID of the schedule whose count should be incremented :type schedule_id: str """ try: spec = {'_id': ObjectId(schedule_id)} except InvalidId: raise exceptions.InvalidValue(['schedule_id']) delta = { '$inc': {'consecutive_failures': 1}, '$set': {'last_updated': time.time()}, } schedule = ScheduledCall.get_collection().find_and_modify( query=spec, update=delta, new=True) if schedule: scheduled_call = ScheduledCall.from_db(schedule) if scheduled_call.failure_threshold is None or not scheduled_call.enabled: return if scheduled_call.consecutive_failures >= scheduled_call.failure_threshold: _logger.info(_('disabling schedule %(id)s with %(count)d consecutive failures') % { 'id': schedule_id, 'count': scheduled_call.consecutive_failures }) delta = {'$set': { 'enabled': False, 'last_updated': time.time(), }} ScheduledCall.get_collection().update(spec, delta)
def test_now(self): call = ScheduledCall('PT1H', 'pulp.tasks.dosomething') now = datetime.utcnow().replace(tzinfo=dateutils.utc_tz()) next_run = dateutils.parse_iso8601_datetime(call.calculate_next_run()) self.assertTrue(next_run - now < timedelta(seconds=1))
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_run_every(self): call = ScheduledCall('2014-01-03T10:15Z/PT1H', 'pulp.tasks.dosomething') run_every_s = call._calculate_times()[3] # 1 hour, as specified in the ISO8601 string above self.assertEqual(run_every_s, 3600)
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 create_schedule(cls, action, consumer_id, units, options, schedule, failure_threshold=None, enabled=True): """ Creates a new schedule for a consumer action :param action: a unique identified for an action, one of UNIT_INSTALL_ACTION, UNIT_UPDATE_ACTION, UNIT_UNINSTALL_ACTION :type action: basestring :param consumer_id: a unique ID for a consumer :type consumer_id: basestring :param units: A list of content units to be installed, each as a dict in the form: { type_id:<str>, unit_key:<dict> } :type units: list :param options: a dictionary that will be passed to the action-appropriate task as the "options" argument :type options: dict :param schedule: ISO8601 string representation of the schedule :type schedule: basestring :param failure_threshold: optional positive integer indicating how many times this schedule's execution can fail before being automatically disabled. :type failure_threshold: int or NoneType :param enabled: boolean indicating if this schedule should be actively loaded and executed by the scheduler. Defaults to True. :type enabled: bool :return: instance of the new ScheduledCal :rtype: pulp.server.db.models.dispatch.ScheduledCall :raise: pulp.server.exceptions.MissingValue """ cls._validate_consumer(consumer_id) utils.validate_initial_schedule_options(schedule, failure_threshold, enabled) if not units: raise MissingValue(['units']) task = ACTIONS_TO_TASKS[action] args = [consumer_id] kwargs = {'units': units, 'options': options} resource = Consumer.build_resource_tag(consumer_id) schedule = ScheduledCall(schedule, task, args=args, kwargs=kwargs, resource=resource, failure_threshold=failure_threshold, enabled=enabled) schedule.save() return schedule
def test_last_scheduled_run_no_first_run(self): call = ScheduledCall('PT1H', 'pulp.tasks.dosomething') last_scheduled_run_s = call._calculate_times()[4] first_run_s = call._calculate_times()[1] self.assertEqual(last_scheduled_run_s, first_run_s)
def tearDown(self): super(SchedulerTests, self).tearDown() ScheduledCall.get_collection().drop() self.scheduler = None dispatch_factory.coordinator = self._coordinator_factory self._coordinator_factory = None dispatch_factory._SCHEDULER = None
def test_since_first(self): call = ScheduledCall('2014-01-03T10:15Z/PT1H', 'pulp.tasks.dosomething') since_first = call._calculate_times()[2] now = time.time() self.assertTrue(since_first + 1388744100 - now < 1)
def setUp(self): super(TestGet, self).setUp() self.manager = ConsumerScheduleManager() self.calls = [ ScheduledCall('PT1H', ACTIONS_TO_TASKS[UNIT_INSTALL_ACTION]), ScheduledCall('PT4H', ACTIONS_TO_TASKS[UNIT_UPDATE_ACTION]) ]
def test_get(self, mock_path, mock_ok, mock_utils_get): call = ScheduledCall('PT1M', 'pulp.tasks.frequent') mock_utils_get.return_value = [call] ret = self.controller._get(call.id) schedule = mock_ok.call_args[0][0] self.assertEqual(ret, mock_ok.return_value) self.assertEqual(len(mock_ok.call_args[0]), 1) # spot-check the schedule self.assertEqual(schedule['_id'], call.id) self.assertEqual(schedule['schedule'], 'PT1M') self.assertEqual(schedule['task'], 'pulp.tasks.frequent') self.assertEqual(schedule['_href'], mock_path.return_value) # next_run is calculated on-demand, and there is a small chance that it # will be re-calculated in the call.for_display() call as 1 second later # than it was calculated above. Thus we will test that equality here # with a tolerance of 1 second for_display = call.for_display() call_next_run = dateutils.parse_iso8601_datetime(call.next_run) display_next_run = dateutils.parse_iso8601_datetime( for_display['next_run']) self.assertTrue( display_next_run - call_next_run <= timedelta(seconds=1)) # now check overall equality with the actual for_display value del schedule['_href'] del schedule['next_run'] del for_display['next_run'] self.assertEqual(schedule, for_display) # make sure we called the manager layer correctly mock_utils_get.assert_called_once_with([call.id])
def delete_by_resource(resource): """ Deletes all schedules for the specified resource :param resource: string indicating a unique resource :type resource: basestring """ ScheduledCall.get_collection().remove({'resource': resource}, safe=True)
def test_first_run_scheduled(self): call = ScheduledCall('2014-01-03T10:15Z/PT1H', 'pulp.tasks.dosomething') 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_last_scheduled_run_with_first_run(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') last_scheduled_run_s = call._calculate_times()[4] self.assertEqual(last_scheduled_run_s, 1389305700)
def test_no_runs(self): call = ScheduledCall('PT1H', 'pulp.tasks.dosomething') entry = call.as_schedule_entry() is_due, seconds = entry.is_due() self.assertTrue(is_due) # make sure this is very close to one hour self.assertTrue(3600 - seconds < 1)
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 create(cls, repo_id, distributor_id, publish_options, schedule, failure_threshold=None, enabled=True): """ Create a new scheduled publish for the given repository and distributor. :param repo_id: unique ID for a repository :type repo_id: basestring :param distributor_id: unique ID for a distributor :type distributor_id: basestring :param publish_options: dictionary that contains the key 'override_config', whose value should be passed as the 'overrides' parameter to the publish 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 """ dist = model.Distributor.objects.get_or_404( repo_id=repo_id, distributor_id=distributor_id) utils.validate_keys(publish_options, _PUBLISH_OPTION_KEYS) utils.validate_initial_schedule_options(schedule, failure_threshold, enabled) task = repo_controller.queue_publish.name args = [repo_id, distributor_id] kwargs = {'overrides': publish_options['override_config']} schedule = ScheduledCall(schedule, task, args=args, kwargs=kwargs, resource=dist.resource_tag, failure_threshold=failure_threshold, enabled=enabled) schedule.save() try: model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=distributor_id) except exceptions.MissingResource: # back out of this whole thing, since the distributor disappeared utils.delete(schedule.id) raise return schedule
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_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 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 delete(schedule_id): """ Deletes the schedule with unique ID schedule_id :param schedule_id: a unique ID for a schedule :type schedule_id: basestring """ try: ScheduledCall.get_collection().remove({'_id': ObjectId(schedule_id)}, safe=True) except InvalidId: raise exceptions.InvalidValue(['schedule_id'])
def create_schedule(cls, action, consumer_id, units, options, schedule, failure_threshold=None, enabled=True): """ Creates a new schedule for a consumer action :param action: a unique identified for an action, one of UNIT_INSTALL_ACTION, UNIT_UPDATE_ACTION, UNIT_UNINSTALL_ACTION :type action: basestring :param consumer_id: a unique ID for a consumer :type consumer_id: basestring :param units: A list of content units to be installed, each as a dict in the form: { type_id:<str>, unit_key:<dict> } :type units: list :param options: a dictionary that will be passed to the action-appropriate task as the "options" argument :type options: dict :param schedule: ISO8601 string representation of the schedule :type schedule: basestring :param failure_threshold: optional positive integer indicating how many times this schedule's execution can fail before being automatically disabled. :type failure_threshold: int or NoneType :param enabled: boolean indicating if this schedule should be actively loaded and executed by the scheduler. Defaults to True. :type enabled: bool :return: instance of the new ScheduledCal :rtype: pulp.server.db.models.dispatch.ScheduledCall :raise: pulp.server.exceptions.MissingValue """ cls._validate_consumer(consumer_id) utils.validate_initial_schedule_options(schedule, failure_threshold, enabled) if not units: raise MissingValue(["units"]) task = ACTIONS_TO_TASKS[action] args = [consumer_id] kwargs = {"units": units, "options": options} resource = Consumer.build_resource_tag(consumer_id) schedule = ScheduledCall( schedule, task, args=args, kwargs=kwargs, resource=resource, failure_threshold=failure_threshold, enabled=enabled, ) schedule.save() return schedule
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 remove(self, schedule_id): """ Remove a scheduled call request @param schedule_id: id of the schedule for the call request @type schedule_id: str """ if isinstance(schedule_id, basestring): schedule_id = ObjectId(schedule_id) if ScheduledCall.get_collection().find_one(schedule_id) is None: raise pulp_exceptions.MissingResource(schedule=str(schedule_id)) scheduled_call_collection = ScheduledCall.get_collection() scheduled_call_collection.remove({'_id': schedule_id}, safe=True)
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 reset_failure_count(schedule_id): """ Reset the consecutive failure count on a schedule to 0, presumably because it ran successfully. :param schedule_id: ID of the schedule whose count should be reset :type schedule_id: str """ spec = {'_id': ObjectId(schedule_id)} delta = {'$set': { 'consecutive_failures': 0, 'last_updated': time.time(), }} ScheduledCall.get_collection().update(spec=spec, document=delta)
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 create(cls, repo_id, distributor_id, publish_options, schedule, failure_threshold=None, enabled=True): """ Create a new scheduled publish for the given repository and distributor. :param repo_id: unique ID for a repository :type repo_id: basestring :param distributor_id: unique ID for a distributor :type distributor_id: basestring :param publish_options: dictionary that contains the key 'override_config', whose value should be passed as the 'overrides' parameter to the publish 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 """ dist = model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=distributor_id) utils.validate_keys(publish_options, _PUBLISH_OPTION_KEYS) utils.validate_initial_schedule_options(schedule, failure_threshold, enabled) task = repo_controller.queue_publish.name args = [repo_id, distributor_id] kwargs = {"overrides": publish_options["override_config"]} schedule = ScheduledCall( schedule, task, args=args, kwargs=kwargs, resource=dist.resource_tag, failure_threshold=failure_threshold, enabled=enabled, ) schedule.save() try: model.Distributor.objects.get_or_404(repo_id=repo_id, distributor_id=distributor_id) except exceptions.MissingResource: # back out of this whole thing, since the distributor disappeared utils.delete(schedule.id) raise return schedule
def add(self, call_request, schedule, **schedule_options): """ Add a scheduled call request Valid schedule options: * failure_threshold: max number of consecutive failures, before scheduled call is disabled, None means no max * last_run: datetime of the last run of the call request or None if no last run * enabled: boolean flag if the scheduled call is enabled or not @param call_request: call request to schedule @type call_request: pulp.server.dispatch.call.CallRequest @param schedule: iso8601 formatted interval schedule @type schedule: str @param schedule_options: keyword options for this schedule @type schedule_options: dict @return: schedule id if successfully scheduled or None otherwise @rtype: str or None """ validate_schedule_options(schedule, schedule_options) scheduled_call = ScheduledCall(call_request, schedule, **schedule_options) next_run = self.calculate_next_run(scheduled_call) if next_run is None: return None scheduled_call_collection = ScheduledCall.get_collection() scheduled_call['next_run'] = next_run scheduled_call_collection.insert(scheduled_call, safe=True) return str(scheduled_call['_id'])