Esempio n. 1
0
def test_eval_once_loop_interval(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_once_loop_interval"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "once": "2017-12-13T13:00:00"
            }
        }
    }
    # Randomn second loop interval
    LOOP_INTERVAL = random.randint(0, 59)
    schedule.opts["loop_interval"] = LOOP_INTERVAL

    # Run the job at the right plus LOOP_INTERVAL
    run_time = dateutil.parser.parse("12/13/2017 1:00pm") + datetime.timedelta(
        seconds=LOOP_INTERVAL)

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate at the run time
    schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)
Esempio n. 2
0
def test_eval(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm"
            }
        }
    }
    run_time2 = dateutil.parser.parse("11/29/2017 4:00pm")
    run_time1 = run_time2 - datetime.timedelta(seconds=1)

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second before the run time
    schedule.eval(now=run_time1)
    ret = schedule.job_status(job_name)
    assert "_last_run" not in ret

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time2)
    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time2
Esempio n. 3
0
def test_eval_whens(schedule):
    """
    verify that scheduled job runs
    """

    schedule.opts["grains"]["whens"] = {"tea time": "11/29/2017 12:00pm"}

    job_name = "test_eval_whens"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": "tea time"
            }
        }
    }
    run_time = dateutil.parser.parse("11/29/2017 12:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate run time1
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time
Esempio n. 4
0
def test_eval_run_on_start(schedule):
    """
    verify that scheduled job is run when minion starts
    """

    job_name = "test_eval_run_on_start"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "hours": "1",
                "run_on_start": True
            }
        }
    }

    # Add job to schedule
    schedule.opts.update(job)

    # eval at 2:00pm, will run.
    run_time = dateutil.parser.parse("11/29/2017 2:00pm")
    schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)

    # eval at 3:00pm, will run.
    run_time = dateutil.parser.parse("11/29/2017 3:00pm")
    schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)
Esempio n. 5
0
def test_eval_splay_global(schedule):
    """
    verify that scheduled job runs with splayed time
    """

    job_name = "job_eval_splay_global"
    job = {
        "schedule": {
            "splay": {
                "start": 5,
                "end": 10
            },
            job_name: {
                "function": "test.ping",
                "seconds": "30"
            },
        }
    }

    # Add job to schedule
    schedule.opts.update(job)

    with patch("random.randint", MagicMock(return_value=10)):
        # eval at 2:00pm to prime, simulate minion start up.
        run_time = dateutil.parser.parse("11/29/2017 2:00pm")
        schedule.eval(now=run_time)

        # eval at 2:00:40pm, will run.
        run_time = dateutil.parser.parse("11/29/2017 2:00:40pm")
        schedule.eval(now=run_time)
        assert _check_last_run(schedule, job_name, run_time)
Esempio n. 6
0
def test_eval_enabled_key(schedule):
    """
    verify that scheduled job runs
    when the enabled key is in place
    https://github.com/saltstack/salt/issues/47695
    """

    job_name = "test_eval_enabled_key"
    job = {
        "schedule": {
            "enabled": True,
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm"
            },
        }
    }
    run_time2 = dateutil.parser.parse("11/29/2017 4:00pm")
    run_time1 = run_time2 - datetime.timedelta(seconds=1)

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second before the run time
    schedule.eval(now=run_time1)
    ret = schedule.job_status("test_eval_enabled_key")
    assert "_last_run" not in ret

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time2)
    assert _check_last_run(schedule, job_name, run_time2)
Esempio n. 7
0
def test_eval_global_disabled_job_enabled(schedule):
    """
    verify that scheduled job does not run
    """

    job_name = "test_eval_global_disabled"
    job = {
        "schedule": {
            "enabled": False,
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm",
                "enabled": True,
            },
        }
    }
    run_time1 = dateutil.parser.parse("11/29/2017 4:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time1)
    ret = schedule.job_status(job_name)
    assert "_last_run" not in ret
    assert ret["_skip_reason"] == "disabled"

    # Ensure job is still enabled
    assert ret["enabled"]
Esempio n. 8
0
def test_eval_cron_loop_interval(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_cron_loop_interval"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "cron": "0 16 29 11 *"
            }
        }
    }
    # Randomn second loop interval
    LOOP_INTERVAL = random.randint(0, 59)
    schedule.opts["loop_interval"] = LOOP_INTERVAL

    # Add the job to the scheduler
    schedule.opts.update(job)

    run_time = dateutil.parser.parse("11/29/2017 4:00pm")
    with patch("croniter.croniter.get_next", MagicMock(return_value=run_time)):
        schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)
Esempio n. 9
0
def test_eval_loop_interval(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_loop_interval"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm"
            }
        }
    }
    # 30 second loop interval
    LOOP_INTERVAL = random.randint(30, 59)
    schedule.opts["loop_interval"] = LOOP_INTERVAL

    run_time2 = dateutil.parser.parse("11/29/2017 4:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL))

    assert _check_last_run(
        schedule, job_name,
        run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL))
Esempio n. 10
0
def test_eval_when_splay_in_past(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_when_splay_in_past"
    splay = 300
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": ["11/29/2017 6:00am"],
                "splay": splay,
            }
        }
    }
    run_time1 = dateutil.parser.parse("11/29/2017 4:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate to prime
    run_time = dateutil.parser.parse("11/29/2017 3:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)

    # Evaluate at expected runtime1, should not run
    # and _next_fire_time should be None
    schedule.eval(now=run_time1)
    ret = schedule.job_status(job_name)
    assert "_last_run" not in ret
    assert ret["_next_fire_time"] is None
Esempio n. 11
0
def test_eval_multiple_whens(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_multiple_whens"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": ["11/29/2017 4:00pm", "11/29/2017 5:00pm"],
            }
        }
    }
    if salt.utils.platform.is_darwin():
        job["schedule"][job_name]["dry_run"] = True

    run_time1 = dateutil.parser.parse("11/29/2017 4:00pm")
    run_time2 = dateutil.parser.parse("11/29/2017 5:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate run time1
    schedule.eval(now=run_time1)

    # Give the job a chance to finish
    assert _check_last_run(schedule, job_name, run_time1)

    # Evaluate run time2
    schedule.eval(now=run_time2)

    # Give the job a chance to finish and check
    assert _check_last_run(schedule, job_name, run_time2)
Esempio n. 12
0
def test_eval_splay(schedule):
    """
    verify that scheduled job runs with splayed time
    """

    job_name = "job_eval_splay"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "seconds": "30",
                "splay": "10"
            }
        }
    }

    # Add job to schedule
    schedule.opts.update(job)

    with patch("random.randint", MagicMock(return_value=10)):
        # eval at 2:00pm to prime, simulate minion start up.
        run_time = dateutil.parser.parse("11/29/2017 2:00pm")
        schedule.eval(now=run_time)
        ret = schedule.job_status(job_name)

        # eval at 2:00:40pm, will run.
        run_time = dateutil.parser.parse("11/29/2017 2:00:40pm")
        schedule.eval(now=run_time)
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time
Esempio n. 13
0
def test_eval_minutes(schedule):
    """
    verify that scheduled job run mutiple times with minutes
    """

    with patch.dict(schedule.opts, {"run_schedule_jobs_in_background": False}):
        job_name = "job_eval_minutes"
        job = {
            "schedule": {
                job_name: {
                    "function": "test.ping",
                    "minutes": "30"
                }
            }
        }

        if salt.utils.platform.is_darwin():
            job["schedule"][job_name]["dry_run"] = True

        # Add job to schedule
        schedule.opts.update(job)

        # eval at 2:00pm to prime, simulate minion start up.
        run_time = dateutil.parser.parse("11/29/2017 2:00pm")
        next_run_time = run_time + datetime.timedelta(minutes=30)
        jids = schedule.eval(now=run_time)
        assert len(jids) == 0
        ret = schedule.job_status(job_name)
        assert ret["_next_fire_time"] == next_run_time

        # eval at 2:00:01pm, will not run.
        run_time = dateutil.parser.parse("11/29/2017 2:00:01pm")
        jids = schedule.eval(now=run_time)
        assert len(jids) == 0
        ret = schedule.job_status(job_name)
        assert "_last_run" not in ret
        assert ret["_next_fire_time"] == next_run_time

        # eval at 2:30:00pm, will run.
        run_time = dateutil.parser.parse("11/29/2017 2:30:00pm")
        jids = schedule.eval(now=run_time)
        assert len(jids) == 1
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time

        # eval at 3:00:00pm, will run.
        run_time = dateutil.parser.parse("11/29/2017 3:00:00pm")
        jids = schedule.eval(now=run_time)
        assert len(jids) == 1
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time

        # eval at 3:30:00pm, will run.
        run_time = dateutil.parser.parse("11/29/2017 3:30:00pm")
        jids = schedule.eval(now=run_time)
        assert len(jids) == 1
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time
Esempio n. 14
0
def test_eval_after(schedule):
    """
    verify that scheduled job is skipped until after the specified
    time has been reached.
    """

    job_name = "test_eval_after"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "hours": "1",
                "after": "11/29/2017 5:00pm",
            }
        }
    }

    # Add job to schedule
    schedule.opts.update(job)

    # eval at 2:00pm to prime, simulate minion start up.
    run_time = dateutil.parser.parse("11/29/2017 2:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)

    # eval at 3:00pm, will not run.
    run_time = dateutil.parser.parse("11/29/2017 3:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_skip_reason"] == "after_not_passed"
    assert ret["_skipped_time"] == run_time

    # eval at 4:00pm, will not run.
    run_time = dateutil.parser.parse("11/29/2017 4:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_skip_reason"] == "after_not_passed"
    assert ret["_skipped_time"] == run_time

    # eval at 5:00pm, will not run
    run_time = dateutil.parser.parse("11/29/2017 5:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_skip_reason"] == "after_not_passed"
    assert ret["_skipped_time"] == run_time

    # eval at 6:00pm, will run
    run_time = dateutil.parser.parse("11/29/2017 6:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time
Esempio n. 15
0
def test_eval_schedule_time(schedule):
    """
    Tests eval if the schedule setting time is in the future
    """
    schedule.opts.update({"pillar": {"schedule": {}}})
    schedule.opts.update(
        {"schedule": {
            "testjob": {
                "function": "test.true",
                "seconds": 60
            }
        }})
    now = datetime.datetime.now()
    schedule.eval()
    assert schedule.opts["schedule"]["testjob"]["_next_fire_time"] > now
Esempio n. 16
0
def test_eval_until(schedule):
    """
    verify that scheduled job is skipped once the current
    time reaches the specified until time
    """

    job_name = "test_eval_until"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "hours": "1",
                "until": "11/29/2017 5:00pm",
            }
        }
    }

    if salt.utils.platform.is_darwin():
        job["schedule"][job_name]["dry_run"] = True

    # Add job to schedule
    schedule.delete_job("test_eval_until")
    schedule.opts.update(job)

    # eval at 2:00pm to prime, simulate minion start up.
    run_time = dateutil.parser.parse("11/29/2017 2:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)

    # eval at 3:00pm, will run.
    run_time = dateutil.parser.parse("11/29/2017 3:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)

    # Give the job a chance to finish
    time.sleep(5)

    assert ret["_last_run"] == run_time

    # eval at 4:00pm, will run.
    run_time = dateutil.parser.parse("11/29/2017 4:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time

    # Give the job a chance to finish
    time.sleep(5)

    # eval at 5:00pm, will not run
    run_time = dateutil.parser.parse("11/29/2017 5:00pm")
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)

    # Give the job a chance to finish
    time.sleep(5)

    assert ret["_skip_reason"] == "until_passed"
    assert ret["_skipped_time"] == run_time
Esempio n. 17
0
def test_eval_schedule_cron(schedule):
    """
    Tests eval if the schedule is defined with cron expression
    """
    schedule.opts.update({"pillar": {"schedule": {}}})
    schedule.opts.update({
        "schedule": {
            "testjob": {
                "function": "test.true",
                "cron": "* * * * *"
            }
        }
    })
    now = datetime.datetime.now()
    schedule.eval()
    assert schedule.opts["schedule"]["testjob"]["_next_fire_time"] > now
Esempio n. 18
0
def test_eval_multiple_whens_loop_interval(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_multiple_whens_loop_interval"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": ["11/29/2017 4:00pm", "11/29/2017 5:00pm"],
            }
        }
    }
    if salt.utils.platform.is_darwin():
        job["schedule"][job_name]["dry_run"] = True

    # 30 second loop interval
    LOOP_INTERVAL = random.randint(30, 59)
    schedule.opts["loop_interval"] = LOOP_INTERVAL

    run_time1 = dateutil.parser.parse(
        "11/29/2017 4:00pm") + datetime.timedelta(seconds=LOOP_INTERVAL)
    run_time2 = dateutil.parser.parse(
        "11/29/2017 5:00pm") + datetime.timedelta(seconds=LOOP_INTERVAL)

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time1)
    ret = schedule.job_status(job_name)

    # Give the job a chance to finish
    time.sleep(5)

    assert ret["_last_run"] == run_time1

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time2)

    # Give the job a chance to finish
    time.sleep(5)

    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time2
Esempio n. 19
0
def test_eval_schedule_cron_splay(schedule):
    """
    Tests eval if the schedule is defined with cron expression plus splay
    """
    schedule.opts.update({"pillar": {"schedule": {}}})
    schedule.opts.update({
        "schedule": {
            "testjob": {
                "function": "test.true",
                "cron": "* * * * *",
                "splay": 5
            }
        }
    })
    schedule.eval()
    assert (schedule.opts["schedule"]["testjob"]["_splay"] >
            schedule.opts["schedule"]["testjob"]["_next_fire_time"])
Esempio n. 20
0
def test_eval_schedule_time_eval(schedule):
    """
    Tests eval if the schedule setting time is in the future plus splay
    """
    schedule.opts.update({"pillar": {"schedule": {}}})
    schedule.opts.update({
        "schedule": {
            "testjob": {
                "function": "test.true",
                "seconds": 60,
                "splay": 5
            }
        }
    })
    now = datetime.datetime.now()
    schedule.eval()
    assert schedule.opts["schedule"]["testjob"][
        "_splay"] - now > datetime.timedelta(seconds=60)
Esempio n. 21
0
def test_eval_cron(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_cron"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "cron": "0 16 29 11 *"
            }
        }
    }

    # Add the job to the scheduler
    schedule.opts.update(job)

    run_time = dateutil.parser.parse("11/29/2017 4:00pm")
    with patch("croniter.croniter.get_next", MagicMock(return_value=run_time)):
        schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)
Esempio n. 22
0
def test_eval_enabled(schedule):
    """
    verify that scheduled job does not run
    """

    job_name = "test_eval_enabled"
    job = {
        "schedule": {
            "enabled": True,
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm"
            },
        }
    }
    run_time1 = dateutil.parser.parse("11/29/2017 4:00pm")

    # Add the job to the scheduler
    schedule.opts.update(job)

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time1)
    assert _check_last_run(schedule, job_name, run_time1)
Esempio n. 23
0
def test_eval_once(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_once"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "once": "2017-12-13T13:00:00"
            }
        }
    }
    run_time = dateutil.parser.parse("12/13/2017 1:00pm")

    # Add the job to the scheduler
    schedule.opts["schedule"] = {}
    schedule.opts.update(job)

    # Evaluate 1 second at the run time
    schedule.eval(now=run_time)
    assert _check_last_run(schedule, job_name, run_time)
Esempio n. 24
0
def test_eval_when_splay(schedule):
    """
    verify that scheduled job runs
    """

    job_name = "test_eval_when_splay"
    splay = 300
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "when": "11/29/2017 4:00pm",
                "splay": splay,
            }
        }
    }
    run_time1 = dateutil.parser.parse("11/29/2017 4:00pm")
    run_time2 = run_time1 + datetime.timedelta(seconds=splay)
    run_time3 = run_time2 + datetime.timedelta(seconds=1)

    # Add the job to the scheduler
    schedule.opts.update(job)

    with patch("random.randint", MagicMock(return_value=splay)):
        # Evaluate to prime
        run_time = dateutil.parser.parse("11/29/2017 3:00pm")
        schedule.eval(now=run_time)
        ret = schedule.job_status(job_name)

        # Evaluate at expected runtime1, should not run
        schedule.eval(now=run_time1)
        ret = schedule.job_status(job_name)
        assert "_last_run" not in ret

        # Evaluate at expected runtime2, should run
        schedule.eval(now=run_time2)
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time2

        # Evaluate at expected runtime3, should not run
        # _next_fire_time should be None
        schedule.eval(now=run_time3)
        ret = schedule.job_status(job_name)
        assert ret["_last_run"] == run_time2
        assert ret["_next_fire_time"] is None
Esempio n. 25
0
def test_eval_days(schedule):
    """
    verify that scheduled job run mutiple times with days
    """

    job_name = "job_eval_days"
    job = {
        "schedule": {
            job_name: {
                "function": "test.ping",
                "days": "2",
                "dry_run": True
            }
        }
    }

    if salt.utils.platform.is_darwin():
        job["schedule"][job_name]["dry_run"] = True

    # Add job to schedule
    schedule.opts.update(job)

    # eval at 11/23/2017 2:00pm to prime, simulate minion start up.
    run_time = dateutil.parser.parse("11/23/2017 2:00pm")
    next_run_time = run_time + datetime.timedelta(days=2)
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_next_fire_time"] == next_run_time

    # eval at 11/25/2017 2:00:00pm, will run.
    run_time = dateutil.parser.parse("11/25/2017 2:00:00pm")
    next_run_time = run_time + datetime.timedelta(days=2)
    schedule.eval(now=run_time)
    ret = schedule.job_status(job_name)
    assert ret["_last_run"] == run_time
    assert ret["_next_fire_time"] == next_run_time

    # eval at 11/26/2017 2:00:00pm, will not run.
    run_time = dateutil.parser.parse("11/26/2017 2:00:00pm")
    last_run_time = run_time - datetime.timedelta(days=1)
    schedule.eval(now=run_time)

    # Give the job a chance to finish
    time.sleep(5)

    ret = schedule.job_status(job_name)

    assert ret["_last_run"] == last_run_time
    assert ret["_next_fire_time"] == next_run_time

    # eval at 11/27/2017 2:00:00pm, will run.
    run_time = dateutil.parser.parse("11/27/2017 2:00:00pm")
    next_run_time = run_time + datetime.timedelta(days=2)
    schedule.eval(now=run_time)

    # Give the job a chance to finish
    time.sleep(5)

    ret = schedule.job_status(job_name)

    assert ret["_last_run"] == run_time
    assert ret["_next_fire_time"] == next_run_time

    # eval at 11/28/2017 2:00:00pm, will not run.
    run_time = dateutil.parser.parse("11/28/2017 2:00:00pm")
    last_run_time = run_time - datetime.timedelta(days=1)
    schedule.eval(now=run_time)

    # Give the job a chance to finish
    time.sleep(5)

    ret = schedule.job_status(job_name)

    assert ret["_last_run"] == last_run_time
    assert ret["_next_fire_time"] == next_run_time

    # eval at 11/29/2017 2:00:00pm, will run.
    run_time = dateutil.parser.parse("11/29/2017 2:00:00pm")
    next_run_time = run_time + datetime.timedelta(days=2)
    schedule.eval(now=run_time)

    # Give the job a chance to finish
    time.sleep(5)

    ret = schedule.job_status(job_name)

    assert ret["_last_run"] == run_time
    assert ret["_next_fire_time"] == next_run_time