class DriverScheduler(object): """ Class to facilitate event scheduling in drivers. jobs. """ def __init__(self, config = None): """ config structure: { test_name: { trigger: {} callback: some_function } } @param config: job configuration structure. """ self._scheduler = PolledScheduler() if(config): self.add_config(config) def shutdown(self): self._scheduler.shutdown() def run_job(self, name): """ Try to run a polled job with the passed in name. If it runs then return true, otherwise false. @param name: name of the job @raise LookupError if we fail to find the job """ return self._scheduler.run_polled_job(name) def add_config(self, config): """ Add new jobs to the scheduler using the passed in config config structure: { test_name: { trigger: {} callback: some_function } } @param config: job configuration structure. @raise SchedulerException if we fail to add the job """ if(not isinstance(config, dict)): raise SchedulerException("scheduler config not a dict") if(len(config.keys()) == 0): raise SchedulerException("scheduler config empty") for (name, config) in config.items(): try: self._add_job(name, config) except ValueError as e: raise SchedulerException("failed to schedule job: %s" % e) except TypeError as e: raise SchedulerException("failed to schedule job: %s" % e) if(not self._scheduler.running): self._scheduler.start() def remove_job(self, callback): self._scheduler.unschedule_func(callback) def _add_job(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ log.debug(" Config name: %s value: %s" % (name, config)) if(config == None): raise SchedulerException("job config empty") if(not isinstance(config, dict)): raise SchedulerException("job config not a dict") trigger = self._get_trigger_from_config(config) trigger_type = trigger.get(DriverSchedulerConfigKey.TRIGGER_TYPE) if(trigger_type == None): raise SchedulerException("trigger type missing") if(trigger_type == TriggerType.ABSOLUTE): self._add_job_absolute(name, config) elif(trigger_type == TriggerType.CRON): self._add_job_cron(name, config) elif(trigger_type == TriggerType.INTERVAL): self._add_job_interval(name, config) elif(trigger_type == TriggerType.POLLED_INTERVAL): self._add_job_polled_interval(name, config) else: raise SchedulerException("unknown trigger type '%s'" % trigger_type) def _get_trigger_from_config(self, config): """ get and validate the trigger dictionary from the config object. @param config: configuration object to inspect @return: dictionary from the config for the trigger config """ trigger = config.get(DriverSchedulerConfigKey.TRIGGER) if(trigger == None): raise SchedulerException("trigger definition missing") if(not isinstance(trigger, dict)): raise SchedulerException("config missing trigger definition") return trigger def _get_callback_from_config(self, config): """ get and verify the callback parameter from a job config. @param config: configuration object to inspect @return: callback method from the config for the trigger config """ callback = config.get(DriverSchedulerConfigKey.CALLBACK) if(callback == None): raise SchedulerException("callback definition missing") if(not callable(callback)): raise SchedulerException("callback incorrect type: '%s'" % type(callback)) return callback def _add_job_absolute(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if(not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) dt = trigger.get(DriverSchedulerConfigKey.DATE) if(dt == None): raise SchedulerException("trigger missing parameter: %s" % DriverSchedulerConfigKey.DATE) self._scheduler.add_date_job(callback, dt) def _add_job_cron(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if(not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) year = trigger.get(DriverSchedulerConfigKey.YEAR) month = trigger.get(DriverSchedulerConfigKey.MONTH) day = trigger.get(DriverSchedulerConfigKey.DAY) week = trigger.get(DriverSchedulerConfigKey.WEEK) day_of_week = trigger.get(DriverSchedulerConfigKey.DAY_OF_WEEK) hour = trigger.get(DriverSchedulerConfigKey.HOUR) minute = trigger.get(DriverSchedulerConfigKey.MINUTE) second = trigger.get(DriverSchedulerConfigKey.SECOND) if(year==None and month==None and day==None and week==None and day_of_week==None and hour==None and minute==None and second==None): raise SchedulerException("at least one cron parameter required!") self._scheduler.add_cron_job(callback, year=year, month=month, day=day, week=week, day_of_week=day_of_week, hour=hour, minute=minute, second=second) def _add_job_interval(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if(not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) weeks = trigger.get(DriverSchedulerConfigKey.WEEKS, 0) days = trigger.get(DriverSchedulerConfigKey.DAYS, 0) hours = trigger.get(DriverSchedulerConfigKey.HOURS, 0) minutes = trigger.get(DriverSchedulerConfigKey.MINUTES, 0) seconds = trigger.get(DriverSchedulerConfigKey.SECONDS, 0) if(not (weeks or days or hours or minutes or seconds)): raise SchedulerException("at least interval parameter required!") self._scheduler.add_interval_job(callback, weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) def _add_job_polled_interval(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if(not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) min_interval = trigger.get(DriverSchedulerConfigKey.MINIMAL_INTERVAL) max_interval = trigger.get(DriverSchedulerConfigKey.MAXIMUM_INTERVAL) if(min_interval == None): raise SchedulerException("%s missing from trigger configuration" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) if(not isinstance(min_interval, dict)): raise SchedulerException("%s trigger configuration not a dict" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) min_weeks = min_interval.get(DriverSchedulerConfigKey.WEEKS, 0) min_days = min_interval.get(DriverSchedulerConfigKey.DAYS, 0) min_hours = min_interval.get(DriverSchedulerConfigKey.HOURS, 0) min_minutes = min_interval.get(DriverSchedulerConfigKey.MINUTES, 0) min_seconds = min_interval.get(DriverSchedulerConfigKey.SECONDS, 0) if(not (min_weeks or min_days or min_hours or min_minutes or min_seconds)): raise SchedulerException("at least interval parameter required!") min_interval_obj = self._scheduler.interval(min_weeks, min_days, min_hours, min_minutes, min_seconds) max_interval_obj = None if(max_interval != None): if(not isinstance(max_interval, dict)): raise SchedulerException("%s trigger configuration not a dict" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) max_weeks = max_interval.get(DriverSchedulerConfigKey.WEEKS, 0) max_days = max_interval.get(DriverSchedulerConfigKey.DAYS, 0) max_hours = max_interval.get(DriverSchedulerConfigKey.HOURS, 0) max_minutes = max_interval.get(DriverSchedulerConfigKey.MINUTES, 0) max_seconds = max_interval.get(DriverSchedulerConfigKey.SECONDS, 0) if(max_weeks or max_days or max_hours or max_minutes or max_seconds): max_interval_obj = self._scheduler.interval(max_weeks, max_days, max_hours, max_minutes, max_seconds) self._scheduler.add_polled_job(callback, name, min_interval_obj, max_interval_obj)
class TestScheduler(MiUnitTest): """ Test the scheduler """ def setUp(self): """ Setup the test case """ self._scheduler = PolledScheduler() self._scheduler.start() self._triggered =[] self.assertTrue(self._scheduler.daemonic) def tearDown(self): self._scheduler.shutdown() def _callback(self): """ event callback for event processing """ log.debug("Event triggered.") self._triggered.append(datetime.datetime.now()) def assert_datetime_close(self, ldate, rdate, delta_seconds=0.1): """ compare two date time objects to see if they are equal within delta_seconds param: ldate left hand date param: rdate right hand date param: delta_seconds tolerance """ delta = ldate - rdate seconds = timedelta_seconds(delta) self.assertLessEqual(abs(seconds), delta_seconds) def assert_event_triggered(self, expected_arrival = None, poll_time = 0.5, timeout = 10): """ Verify a timer was triggered within the timeout, and if if expected arrival is set, check the date time arrived for a match too. @param expected_arival datetime object with time we expect the triggered event to fire @param poll_time time to sleep between arrival queue checks, also sets the precision of expected_arrival @param timeout seconds to wait for an event """ endtime = datetime.datetime.now() + datetime.timedelta(0,timeout) while(len(self._triggered) == 0 and datetime.datetime.now() < endtime): log.trace("Wait for event.") time.sleep(poll_time) log.debug("Out of test loop") self.assertGreater(len(self._triggered), 0) arrival_time = self._triggered.pop() self.assertIsNotNone(arrival_time) if(not expected_arrival == None): self.assert_datetime_close(arrival_time, expected_arrival, poll_time) def assert_event_not_triggered(self, poll_time = 0.5, timeout = 10): """ Verify a timer was not triggered within the timeout @param poll_time time to sleep between arrival queue checks, also sets the precision of expected_arrival @param timeout seconds to wait for an event """ endtime = datetime.datetime.now() + datetime.timedelta(0,timeout) while(len(self._triggered) == 0 and datetime.datetime.now() < endtime): log.trace("Wait for event.") time.sleep(poll_time) log.debug("Out of test loop") self.assertEqual(len(self._triggered), 0) def test_absolute_time(self): """ Test with absolute time. Not an exhaustive test because it's implemented in the library not our code. """ dt = datetime.datetime.now() + datetime.timedelta(0,1) job = self._scheduler.add_date_job(self._callback, dt) self.assert_event_triggered(dt) def test_elapse_time(self): """ Test with elapse time. Not an exhaustive test because it's implemented in the library not our code. """ now = datetime.datetime.now() interval = PolledScheduler.interval(seconds=3) job = self._scheduler.add_interval_job(self._callback, seconds=3) self.assert_event_triggered(now + interval) self.assert_event_triggered(now + interval + interval) self.assert_event_triggered(now + interval + interval + interval) # Now shutdown the scheduler and verify we aren't firing events self._scheduler.shutdown() self._triggered = [] self.assert_event_not_triggered() def test_cron_syntax(self): """ Test with cron syntax. Not an exhaustive test because it's implemented in the library not our code. """ job = self._scheduler.add_cron_job(self._callback, second='*/3') self.assert_event_triggered() self.assert_event_triggered() @unittest.skip("TODO, fix this test. Failing on buildbot not in dev") def test_polled_time(self): """ Test a polled job with an interval. Also test some exceptions """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval, max_interval) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assert_event_triggered(now+max_interval) # after a triggered event the min time should be extended. self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assert_event_triggered(now + min_interval + max_interval) # after a polled event the wait time should also be exited self.assert_event_triggered(now + min_interval + max_interval + max_interval) # Test exceptions. Job name doesn't exist with self.assertRaises(LookupError): self._scheduler.run_polled_job('foo') # Verify that an exception is raised if we try to add a job with the same name with self.assertRaises(ValueError): job = self._scheduler.add_polled_job(self._callback, test_name, min_interval, max_interval) def test_polled_time_no_interval(self): """ Test the scheduler with a polled job with no interval """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(2) self.assertTrue(self._scheduler.run_polled_job(test_name)) def test_polled_time_no_interval_not_started(self): """ Try to setup some jobs with the scheduler before the scheduler has been started. Then try to startup and see if the job is setup properly. """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) self._scheduler = PolledScheduler() self.assertFalse(self._scheduler.running) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval) self.assertIsNotNone(job) self.assertEqual(len(self._scheduler.get_jobs()), 0) self.assertEqual(len(self._scheduler._pending_jobs), 1) self._scheduler.start() log.debug("JOBS: %s" % self._scheduler.get_jobs()) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(2) self.assertTrue(self._scheduler.run_polled_job(test_name)) #################################################################################################### # Test our new polled trigger #################################################################################################### def test_polled_interval_trigger(self): """ test the trigger mechanism. """ ### # Test all constructors and exceptions ### trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1)) self.assertEqual(trigger.min_interval_length, 1) self.assertIsNone(trigger.max_interval) self.assertIsNone(trigger.max_interval_length) self.assertIsInstance(trigger.next_min_date, datetime.datetime) self.assertIsNone(trigger.next_max_date) trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3) ) self.assertEqual(trigger.min_interval_length, 1) self.assertEqual(trigger.max_interval_length, 3) trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3), datetime.datetime.now() ) self.assertEqual(trigger.min_interval_length, 1) self.assertEqual(trigger.max_interval_length, 3) # Test Type Error Exception with self.assertRaises(TypeError): trigger = PolledIntervalTrigger('boom') with self.assertRaises(TypeError): trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=3), 'boom' ) # Test Value Error Exception with self.assertRaises(ValueError): trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=3), PolledScheduler.interval(seconds=1) ) ### # Verify min and max dates are incremented correctly. ### now = datetime.datetime.now() log.debug("Now: %s" % now) min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) trigger = PolledIntervalTrigger(min_interval, max_interval, now) # Initialized correctly? self.assert_datetime_close(trigger.next_min_date, now) self.assert_datetime_close(trigger.next_max_date, now + max_interval) self.assert_datetime_close(trigger.get_next_fire_time(now), now + max_interval) # First call should be successful, but second should not. self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) self.assert_datetime_close(trigger.next_min_date, now + min_interval) self.assert_datetime_close(trigger.next_max_date, now + max_interval) self.assert_datetime_close(trigger.get_next_fire_time(now), now + max_interval) # Wait for the minimum interval and it should succeed again! time.sleep(2) now = datetime.datetime.now() self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) ### # Now do the same sequence, but with no max_interval ### now = datetime.datetime.now() log.debug("Now: %s" % now) min_interval = PolledScheduler.interval(seconds=1) max_interval = None trigger = PolledIntervalTrigger(min_interval, max_interval, now) # Initialized correctly? self.assert_datetime_close(trigger.next_min_date, now) self.assertIsNone(trigger.next_max_date) self.assertIsNone(trigger.get_next_fire_time(now)) # First call should be successful, but second should not. self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) self.assert_datetime_close(trigger.next_min_date, now + min_interval) self.assertIsNone(trigger.next_max_date) self.assertIsNone(trigger.get_next_fire_time(now)) # Wait for the minimum interval and it should succeed again! time.sleep(2) now = datetime.datetime.now() self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) def test_trigger_string(self): """ test the str and repr methods """ now = datetime.datetime.now() trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3), now) self.assertEqual(str(trigger), "min_interval[0:00:01] max_interval[0:00:03]") self.assertEqual(repr(trigger), "<PolledIntervalTrigger (min_interval=datetime.timedelta(0, 1), max_interval=datetime.timedelta(0, 3))>") trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1), None, now) self.assertEqual(str(trigger), "min_interval[0:00:01] max_interval[None]") self.assertEqual(repr(trigger), "<PolledIntervalTrigger (min_interval=datetime.timedelta(0, 1), max_interval=None)>") #################################################################################################### # Test our new polled job #################################################################################################### def test_polled_job(self): """ Test features of the specialized job class that we overloaded. """ now = datetime.datetime.now() min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) trigger = PolledIntervalTrigger(min_interval, max_interval, now) job = PolledIntervalJob(trigger, self._callback, [], {}, 1, 1, name='test_job') self.assertIsNotNone(job) log.debug("H: %s" % repr(job)) next_time = job.compute_next_run_time(now) self.assert_datetime_close(next_time, now + max_interval) self.assertEqual(job.name, 'test_job') self.assertTrue(job.ready_to_run()) next_time = job.compute_next_run_time(now) self.assertFalse(job.ready_to_run()) self.assert_datetime_close(next_time, now + max_interval) time.sleep(2) now = datetime.datetime.now() self.assertTrue(job.ready_to_run()) next_time = job.compute_next_run_time(now) self.assertFalse(job.ready_to_run()) self.assert_datetime_close(next_time, now + max_interval)
class DriverScheduler(object): """ Class to facilitate event scheduling in drivers. jobs. """ def __init__(self, config=None): """ config structure: { test_name: { trigger: {} callback: some_function } } @param config: job configuration structure. """ self._scheduler = PolledScheduler() if (config): self.add_config(config) def shutdown(self): self._scheduler.shutdown() def run_job(self, name): """ Try to run a polled job with the passed in name. If it runs then return true, otherwise false. @param name: name of the job @raise LookupError if we fail to find the job """ return self._scheduler.run_polled_job(name) def add_config(self, config): """ Add new jobs to the scheduler using the passed in config config structure: { test_name: { trigger: {} callback: some_function } } @param config: job configuration structure. @raise SchedulerException if we fail to add the job """ if (not isinstance(config, dict)): raise SchedulerException("scheduler config not a dict") if (len(config.keys()) == 0): raise SchedulerException("scheduler config empty") for (name, config) in config.items(): try: self._add_job(name, config) except ValueError as e: raise SchedulerException("failed to schedule job: %s" % e) except TypeError as e: raise SchedulerException("failed to schedule job: %s" % e) if (not self._scheduler.running): self._scheduler.start() def remove_job(self, callback): self._scheduler.unschedule_func(callback) def _add_job(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ log.debug(" Config name: %s value: %s" % (name, config)) if (config == None): raise SchedulerException("job config empty") if (not isinstance(config, dict)): raise SchedulerException("job config not a dict") trigger = self._get_trigger_from_config(config) trigger_type = trigger.get(DriverSchedulerConfigKey.TRIGGER_TYPE) if (trigger_type == None): raise SchedulerException("trigger type missing") if (trigger_type == TriggerType.ABSOLUTE): self._add_job_absolute(name, config) elif (trigger_type == TriggerType.CRON): self._add_job_cron(name, config) elif (trigger_type == TriggerType.INTERVAL): self._add_job_interval(name, config) elif (trigger_type == TriggerType.POLLED_INTERVAL): self._add_job_polled_interval(name, config) else: raise SchedulerException("unknown trigger type '%s'" % trigger_type) def _get_trigger_from_config(self, config): """ get and validate the trigger dictionary from the config object. @param config: configuration object to inspect @return: dictionary from the config for the trigger config """ trigger = config.get(DriverSchedulerConfigKey.TRIGGER) if (trigger == None): raise SchedulerException("trigger definition missing") if (not isinstance(trigger, dict)): raise SchedulerException("config missing trigger definition") return trigger def _get_callback_from_config(self, config): """ get and verify the callback parameter from a job config. @param config: configuration object to inspect @return: callback method from the config for the trigger config """ callback = config.get(DriverSchedulerConfigKey.CALLBACK) if (callback == None): raise SchedulerException("callback definition missing") if (not callable(callback)): raise SchedulerException("callback incorrect type: '%s'" % type(callback)) return callback def _add_job_absolute(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if (not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) dt = trigger.get(DriverSchedulerConfigKey.DATE) if (dt == None): raise SchedulerException("trigger missing parameter: %s" % DriverSchedulerConfigKey.DATE) self._scheduler.add_date_job(callback, dt) def _add_job_cron(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if (not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) year = trigger.get(DriverSchedulerConfigKey.YEAR) month = trigger.get(DriverSchedulerConfigKey.MONTH) day = trigger.get(DriverSchedulerConfigKey.DAY) week = trigger.get(DriverSchedulerConfigKey.WEEK) day_of_week = trigger.get(DriverSchedulerConfigKey.DAY_OF_WEEK) hour = trigger.get(DriverSchedulerConfigKey.HOUR) minute = trigger.get(DriverSchedulerConfigKey.MINUTE) second = trigger.get(DriverSchedulerConfigKey.SECOND) if (year == None and month == None and day == None and week == None and day_of_week == None and hour == None and minute == None and second == None): raise SchedulerException("at least one cron parameter required!") self._scheduler.add_cron_job(callback, year=year, month=month, day=day, week=week, day_of_week=day_of_week, hour=hour, minute=minute, second=second) def _add_job_interval(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if (not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) weeks = trigger.get(DriverSchedulerConfigKey.WEEKS, 0) days = trigger.get(DriverSchedulerConfigKey.DAYS, 0) hours = trigger.get(DriverSchedulerConfigKey.HOURS, 0) minutes = trigger.get(DriverSchedulerConfigKey.MINUTES, 0) seconds = trigger.get(DriverSchedulerConfigKey.SECONDS, 0) if (not (weeks or days or hours or minutes or seconds)): raise SchedulerException("at least interval parameter required!") self._scheduler.add_interval_job(callback, weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) def _add_job_polled_interval(self, name, config): """ Add a new job to the scheduler based on the trigger configuration @param name: name of the job @param config: job configuration @raise SchedulerError if we fail to add the job """ if (not isinstance(config, dict)): raise SchedulerException("config not a dict") callback = self._get_callback_from_config(config) trigger = self._get_trigger_from_config(config) min_interval = trigger.get(DriverSchedulerConfigKey.MINIMAL_INTERVAL) max_interval = trigger.get(DriverSchedulerConfigKey.MAXIMUM_INTERVAL) if (min_interval == None): raise SchedulerException("%s missing from trigger configuration" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) if (not isinstance(min_interval, dict)): raise SchedulerException("%s trigger configuration not a dict" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) min_weeks = min_interval.get(DriverSchedulerConfigKey.WEEKS, 0) min_days = min_interval.get(DriverSchedulerConfigKey.DAYS, 0) min_hours = min_interval.get(DriverSchedulerConfigKey.HOURS, 0) min_minutes = min_interval.get(DriverSchedulerConfigKey.MINUTES, 0) min_seconds = min_interval.get(DriverSchedulerConfigKey.SECONDS, 0) if (not (min_weeks or min_days or min_hours or min_minutes or min_seconds)): raise SchedulerException("at least interval parameter required!") min_interval_obj = self._scheduler.interval(min_weeks, min_days, min_hours, min_minutes, min_seconds) max_interval_obj = None if (max_interval != None): if (not isinstance(max_interval, dict)): raise SchedulerException( "%s trigger configuration not a dict" % DriverSchedulerConfigKey.MINIMAL_INTERVAL) max_weeks = max_interval.get(DriverSchedulerConfigKey.WEEKS, 0) max_days = max_interval.get(DriverSchedulerConfigKey.DAYS, 0) max_hours = max_interval.get(DriverSchedulerConfigKey.HOURS, 0) max_minutes = max_interval.get(DriverSchedulerConfigKey.MINUTES, 0) max_seconds = max_interval.get(DriverSchedulerConfigKey.SECONDS, 0) if (max_weeks or max_days or max_hours or max_minutes or max_seconds): max_interval_obj = self._scheduler.interval( max_weeks, max_days, max_hours, max_minutes, max_seconds) self._scheduler.add_polled_job(callback, name, min_interval_obj, max_interval_obj)
class TestScheduler(MiUnitTest): """ Test the scheduler """ def setUp(self): """ Setup the test case """ self._scheduler = PolledScheduler() self._scheduler.start() self._triggered = [] self.assertTrue(self._scheduler.daemonic) def tearDown(self): self._scheduler.shutdown() def _callback(self): """ event callback for event processing """ log.debug("Event triggered.") self._triggered.append(datetime.datetime.now()) def assert_datetime_close(self, ldate, rdate, delta_seconds=0.1): """ compare two date time objects to see if they are equal within delta_seconds param: ldate left hand date param: rdate right hand date param: delta_seconds tolerance """ delta = ldate - rdate seconds = timedelta_seconds(delta) self.assertLessEqual(abs(seconds), delta_seconds) def assert_event_triggered(self, expected_arrival=None, poll_time=0.5, timeout=10): """ Verify a timer was triggered within the timeout, and if if expected arrival is set, check the date time arrived for a match too. @param expected_arival datetime object with time we expect the triggered event to fire @param poll_time time to sleep between arrival queue checks, also sets the precision of expected_arrival @param timeout seconds to wait for an event """ endtime = datetime.datetime.now() + datetime.timedelta(0, timeout) while (len(self._triggered) == 0 and datetime.datetime.now() < endtime): log.trace("Wait for event.") time.sleep(poll_time) log.debug("Out of test loop") self.assertGreater(len(self._triggered), 0) arrival_time = self._triggered.pop() self.assertIsNotNone(arrival_time) if (not expected_arrival == None): self.assert_datetime_close(arrival_time, expected_arrival, poll_time) def assert_event_not_triggered(self, poll_time=0.5, timeout=10): """ Verify a timer was not triggered within the timeout @param poll_time time to sleep between arrival queue checks, also sets the precision of expected_arrival @param timeout seconds to wait for an event """ endtime = datetime.datetime.now() + datetime.timedelta(0, timeout) while (len(self._triggered) == 0 and datetime.datetime.now() < endtime): log.trace("Wait for event.") time.sleep(poll_time) log.debug("Out of test loop") self.assertEqual(len(self._triggered), 0) def test_absolute_time(self): """ Test with absolute time. Not an exhaustive test because it's implemented in the library not our code. """ dt = datetime.datetime.now() + datetime.timedelta(0, 1) job = self._scheduler.add_date_job(self._callback, dt) self.assert_event_triggered(dt) def test_elapse_time(self): """ Test with elapse time. Not an exhaustive test because it's implemented in the library not our code. """ now = datetime.datetime.now() interval = PolledScheduler.interval(seconds=3) job = self._scheduler.add_interval_job(self._callback, seconds=3) self.assert_event_triggered(now + interval) self.assert_event_triggered(now + interval + interval) self.assert_event_triggered(now + interval + interval + interval) # Now shutdown the scheduler and verify we aren't firing events self._scheduler.shutdown() self._triggered = [] self.assert_event_not_triggered() def test_cron_syntax(self): """ Test with cron syntax. Not an exhaustive test because it's implemented in the library not our code. """ job = self._scheduler.add_cron_job(self._callback, second='*/3') self.assert_event_triggered() self.assert_event_triggered() @unittest.skip("TODO, fix this test. Failing on buildbot not in dev") def test_polled_time(self): """ Test a polled job with an interval. Also test some exceptions """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval, max_interval) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assert_event_triggered(now + max_interval) # after a triggered event the min time should be extended. self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assert_event_triggered(now + min_interval + max_interval) # after a polled event the wait time should also be exited self.assert_event_triggered(now + min_interval + max_interval + max_interval) # Test exceptions. Job name doesn't exist with self.assertRaises(LookupError): self._scheduler.run_polled_job('foo') # Verify that an exception is raised if we try to add a job with the same name with self.assertRaises(ValueError): job = self._scheduler.add_polled_job(self._callback, test_name, min_interval, max_interval) @unittest.skip("TODO, fix this test. Failing on buildbot not in dev") def test_polled_time_no_interval(self): """ Test the scheduler with a polled job with no interval """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(2) self.assertTrue(self._scheduler.run_polled_job(test_name)) def test_polled_time_no_interval_not_started(self): """ Try to setup some jobs with the scheduler before the scheduler has been started. Then try to startup and see if the job is setup properly. """ now = datetime.datetime.now() test_name = 'test_job' min_interval = PolledScheduler.interval(seconds=1) self._scheduler = PolledScheduler() self.assertFalse(self._scheduler.running) # Verify that triggered events work. job = self._scheduler.add_polled_job(self._callback, test_name, min_interval) self.assertIsNotNone(job) self.assertEqual(len(self._scheduler.get_jobs()), 0) self.assertEqual(len(self._scheduler._pending_jobs), 1) self._scheduler.start() log.debug("JOBS: %s" % self._scheduler.get_jobs()) self.assertEqual(len(self._scheduler.get_jobs()), 1) self.assertTrue(self._scheduler.run_polled_job(test_name)) self.assertFalse(self._scheduler.run_polled_job(test_name)) time.sleep(2) self.assertTrue(self._scheduler.run_polled_job(test_name)) #################################################################################################### # Test our new polled trigger #################################################################################################### def test_polled_interval_trigger(self): """ test the trigger mechanism. """ ### # Test all constructors and exceptions ### trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1)) self.assertEqual(trigger.min_interval_length, 1) self.assertIsNone(trigger.max_interval) self.assertIsNone(trigger.max_interval_length) self.assertIsInstance(trigger.next_min_date, datetime.datetime) self.assertIsNone(trigger.next_max_date) trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3)) self.assertEqual(trigger.min_interval_length, 1) self.assertEqual(trigger.max_interval_length, 3) trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3), datetime.datetime.now()) self.assertEqual(trigger.min_interval_length, 1) self.assertEqual(trigger.max_interval_length, 3) # Test Type Error Exception with self.assertRaises(TypeError): trigger = PolledIntervalTrigger('boom') with self.assertRaises(TypeError): trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=3), 'boom') # Test Value Error Exception with self.assertRaises(ValueError): trigger = PolledIntervalTrigger( PolledScheduler.interval(seconds=3), PolledScheduler.interval(seconds=1)) ### # Verify min and max dates are incremented correctly. ### now = datetime.datetime.now() log.debug("Now: %s" % now) min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) trigger = PolledIntervalTrigger(min_interval, max_interval, now) # Initialized correctly? self.assert_datetime_close(trigger.next_min_date, now) self.assert_datetime_close(trigger.next_max_date, now + max_interval) self.assert_datetime_close(trigger.get_next_fire_time(now), now + max_interval) # First call should be successful, but second should not. self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) self.assert_datetime_close(trigger.next_min_date, now + min_interval) self.assert_datetime_close(trigger.next_max_date, now + max_interval) self.assert_datetime_close(trigger.get_next_fire_time(now), now + max_interval) # Wait for the minimum interval and it should succeed again! time.sleep(2) now = datetime.datetime.now() self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) ### # Now do the same sequence, but with no max_interval ### now = datetime.datetime.now() log.debug("Now: %s" % now) min_interval = PolledScheduler.interval(seconds=1) max_interval = None trigger = PolledIntervalTrigger(min_interval, max_interval, now) # Initialized correctly? self.assert_datetime_close(trigger.next_min_date, now) self.assertIsNone(trigger.next_max_date) self.assertIsNone(trigger.get_next_fire_time(now)) # First call should be successful, but second should not. self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) self.assert_datetime_close(trigger.next_min_date, now + min_interval) self.assertIsNone(trigger.next_max_date) self.assertIsNone(trigger.get_next_fire_time(now)) # Wait for the minimum interval and it should succeed again! time.sleep(2) now = datetime.datetime.now() self.assertTrue(trigger.pull_trigger()) self.assertFalse(trigger.pull_trigger()) def test_trigger_string(self): """ test the str and repr methods """ now = datetime.datetime.now() trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1), PolledScheduler.interval(seconds=3), now) self.assertEqual(str(trigger), "min_interval[0:00:01] max_interval[0:00:03]") self.assertEqual( repr(trigger), "<PolledIntervalTrigger (min_interval=datetime.timedelta(0, 1), max_interval=datetime.timedelta(0, 3))>" ) trigger = PolledIntervalTrigger(PolledScheduler.interval(seconds=1), None, now) self.assertEqual(str(trigger), "min_interval[0:00:01] max_interval[None]") self.assertEqual( repr(trigger), "<PolledIntervalTrigger (min_interval=datetime.timedelta(0, 1), max_interval=None)>" ) #################################################################################################### # Test our new polled job #################################################################################################### def test_polled_job(self): """ Test features of the specialized job class that we overloaded. """ now = datetime.datetime.now() min_interval = PolledScheduler.interval(seconds=1) max_interval = PolledScheduler.interval(seconds=3) trigger = PolledIntervalTrigger(min_interval, max_interval, now) job = PolledIntervalJob(trigger, self._callback, [], {}, 1, 1, name='test_job') self.assertIsNotNone(job) log.debug("H: %s" % repr(job)) next_time = job.compute_next_run_time(now) self.assert_datetime_close(next_time, now + max_interval) self.assertEqual(job.name, 'test_job') self.assertTrue(job.ready_to_run()) next_time = job.compute_next_run_time(now) self.assertFalse(job.ready_to_run()) self.assert_datetime_close(next_time, now + max_interval) time.sleep(2) now = datetime.datetime.now() self.assertTrue(job.ready_to_run()) next_time = job.compute_next_run_time(now) self.assertFalse(job.ready_to_run()) self.assert_datetime_close(next_time, now + max_interval)