mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Updating the add, delete, modify, enable_job, and disable_job functions to return appropriate changes. Moving all scheduler tests to pytest.
This commit is contained in:
parent
02cc401d25
commit
55b48783c0
26 changed files with 2903 additions and 2908 deletions
|
@ -8,7 +8,6 @@ Module for managing the Salt schedule on a minion
|
|||
|
||||
import copy as pycopy
|
||||
import datetime
|
||||
import difflib
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
@ -251,6 +250,7 @@ def delete(name, **kwargs):
|
|||
ret = {
|
||||
"comment": "Failed to delete job {} from schedule.".format(name),
|
||||
"result": False,
|
||||
"changes": {},
|
||||
}
|
||||
|
||||
if not name:
|
||||
|
@ -289,6 +289,7 @@ def delete(name, **kwargs):
|
|||
ret["comment"] = "Deleted Job {} from schedule.".format(
|
||||
name
|
||||
)
|
||||
ret["changes"][name] = "removed"
|
||||
else:
|
||||
ret[
|
||||
"comment"
|
||||
|
@ -440,6 +441,7 @@ def add(name, **kwargs):
|
|||
ret = {
|
||||
"comment": "Failed to add job {} to schedule.".format(name),
|
||||
"result": False,
|
||||
"changes": {},
|
||||
}
|
||||
|
||||
if name in list_(show_all=True, return_yaml=False):
|
||||
|
@ -501,6 +503,7 @@ def add(name, **kwargs):
|
|||
if name in schedule:
|
||||
ret["result"] = True
|
||||
ret["comment"] = "Added job: {} to schedule.".format(name)
|
||||
ret["changes"][name] = "added"
|
||||
return ret
|
||||
except KeyError:
|
||||
# Effectively a no-op, since we can't really return without an event system
|
||||
|
@ -553,11 +556,19 @@ def modify(name, **kwargs):
|
|||
if "function" not in kwargs:
|
||||
kwargs["function"] = _current.get("function")
|
||||
|
||||
# Remove the auto generated _seconds value
|
||||
if "_seconds" in _current:
|
||||
_current["seconds"] = _current["_seconds"]
|
||||
del _current["_seconds"]
|
||||
|
||||
_new = build_schedule_item(name, **kwargs)
|
||||
# Copy _current _new, then update values from kwargs
|
||||
_new = pycopy.deepcopy(_current)
|
||||
_new.update(kwargs)
|
||||
|
||||
# Remove test from kwargs, it's not a valid schedule option
|
||||
if "test" in _new:
|
||||
del _new["test"]
|
||||
|
||||
if "result" in _new and not _new["result"]:
|
||||
return _new
|
||||
|
||||
|
@ -565,13 +576,10 @@ def modify(name, **kwargs):
|
|||
ret["comment"] = "Job {} in correct state".format(name)
|
||||
return ret
|
||||
|
||||
_current_lines = [
|
||||
"{}:{}\n".format(key, value) for (key, value) in sorted(_current.items())
|
||||
]
|
||||
_new_lines = ["{}:{}\n".format(key, value) for (key, value) in sorted(_new.items())]
|
||||
_diff = difflib.unified_diff(_current_lines, _new_lines)
|
||||
|
||||
ret["changes"]["diff"] = "".join(_diff)
|
||||
ret["changes"][name] = {
|
||||
"old": salt.utils.odict.OrderedDict(_current),
|
||||
"new": salt.utils.odict.OrderedDict(_new),
|
||||
}
|
||||
|
||||
if "test" in kwargs and kwargs["test"]:
|
||||
ret["comment"] = "Job: {} would be modified in schedule.".format(name)
|
||||
|
@ -653,7 +661,7 @@ def enable_job(name, **kwargs):
|
|||
salt '*' schedule.enable_job job1
|
||||
"""
|
||||
|
||||
ret = {"comment": [], "result": True}
|
||||
ret = {"comment": [], "result": True, "changes": {}}
|
||||
|
||||
if not name:
|
||||
ret["comment"] = "Job name is required."
|
||||
|
@ -692,6 +700,7 @@ def enable_job(name, **kwargs):
|
|||
if name in schedule and schedule[name]["enabled"]:
|
||||
ret["result"] = True
|
||||
ret["comment"] = "Enabled Job {} in schedule.".format(name)
|
||||
ret["changes"][name] = "enabled"
|
||||
else:
|
||||
ret["result"] = False
|
||||
ret[
|
||||
|
@ -715,7 +724,7 @@ def disable_job(name, **kwargs):
|
|||
salt '*' schedule.disable_job job1
|
||||
"""
|
||||
|
||||
ret = {"comment": [], "result": True}
|
||||
ret = {"comment": [], "result": True, "changes": {}}
|
||||
|
||||
if not name:
|
||||
ret["comment"] = "Job name is required."
|
||||
|
@ -754,6 +763,7 @@ def disable_job(name, **kwargs):
|
|||
if name in schedule and not schedule[name]["enabled"]:
|
||||
ret["result"] = True
|
||||
ret["comment"] = "Disabled Job {} in schedule.".format(name)
|
||||
ret["changes"][name] = "disabled"
|
||||
else:
|
||||
ret["result"] = False
|
||||
ret[
|
||||
|
@ -814,7 +824,7 @@ def enable(**kwargs):
|
|||
salt '*' schedule.enable
|
||||
"""
|
||||
|
||||
ret = {"comment": [], "result": True}
|
||||
ret = {"comment": [], "changes": {}, "result": True}
|
||||
|
||||
if "test" in kwargs and kwargs["test"]:
|
||||
ret["comment"] = "Schedule would be enabled."
|
||||
|
@ -835,6 +845,7 @@ def enable(**kwargs):
|
|||
if "enabled" in schedule and schedule["enabled"]:
|
||||
ret["result"] = True
|
||||
ret["comment"] = "Enabled schedule on minion."
|
||||
ret["changes"]["schedule"] = "enabled"
|
||||
else:
|
||||
ret["result"] = False
|
||||
ret["comment"] = "Failed to enable schedule on minion."
|
||||
|
@ -856,7 +867,7 @@ def disable(**kwargs):
|
|||
salt '*' schedule.disable
|
||||
"""
|
||||
|
||||
ret = {"comment": [], "result": True}
|
||||
ret = {"comment": [], "changes": {}, "result": True}
|
||||
|
||||
if "test" in kwargs and kwargs["test"]:
|
||||
ret["comment"] = "Schedule would be disabled."
|
||||
|
@ -877,6 +888,7 @@ def disable(**kwargs):
|
|||
if "enabled" in schedule and not schedule["enabled"]:
|
||||
ret["result"] = True
|
||||
ret["comment"] = "Disabled schedule on minion."
|
||||
ret["changes"]["schedule"] = "disabled"
|
||||
else:
|
||||
ret["result"] = False
|
||||
ret["comment"] = "Failed to disable schedule on minion."
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Management of the Salt scheduler
|
||||
==============================================
|
||||
|
@ -97,7 +96,6 @@ Management of the Salt scheduler
|
|||
python-dateutil is installed on the minion.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
|
||||
def present(name, **kwargs):
|
||||
|
@ -229,7 +227,7 @@ def present(name, **kwargs):
|
|||
new_item["enabled"] = True
|
||||
|
||||
if new_item == current_schedule[name]:
|
||||
ret["comment"].append("Job {0} in correct state".format(name))
|
||||
ret["comment"].append("Job {} in correct state".format(name))
|
||||
else:
|
||||
if "test" in __opts__ and __opts__["test"]:
|
||||
kwargs["test"] = True
|
||||
|
@ -243,7 +241,7 @@ def present(name, **kwargs):
|
|||
ret["comment"] = result["comment"]
|
||||
return ret
|
||||
else:
|
||||
ret["comment"].append("Modifying job {0} in schedule".format(name))
|
||||
ret["comment"].append("Modifying job {} in schedule".format(name))
|
||||
ret["changes"] = result["changes"]
|
||||
else:
|
||||
if "test" in __opts__ and __opts__["test"]:
|
||||
|
@ -257,7 +255,8 @@ def present(name, **kwargs):
|
|||
ret["comment"] = result["comment"]
|
||||
return ret
|
||||
else:
|
||||
ret["comment"].append("Adding new job {0} to schedule".format(name))
|
||||
ret["comment"].append("Adding new job {} to schedule".format(name))
|
||||
ret["changes"] = result["changes"]
|
||||
|
||||
ret["comment"] = "\n".join(ret["comment"])
|
||||
return ret
|
||||
|
@ -289,9 +288,10 @@ def absent(name, **kwargs):
|
|||
ret["comment"] = result["comment"]
|
||||
return ret
|
||||
else:
|
||||
ret["comment"].append("Removed job {0} from schedule".format(name))
|
||||
ret["comment"].append("Removed job {} from schedule".format(name))
|
||||
ret["changes"] = result["changes"]
|
||||
else:
|
||||
ret["comment"].append("Job {0} not present in schedule".format(name))
|
||||
ret["comment"].append("Job {} not present in schedule".format(name))
|
||||
|
||||
ret["comment"] = "\n".join(ret["comment"])
|
||||
return ret
|
||||
|
@ -321,12 +321,13 @@ def enabled(name, **kwargs):
|
|||
result = __salt__["schedule.enable_job"](name, **kwargs)
|
||||
if not result["result"]:
|
||||
ret["result"] = result["result"]
|
||||
ret["changes"] = result["changes"]
|
||||
ret["comment"] = result["comment"]
|
||||
return ret
|
||||
else:
|
||||
ret["comment"].append("Enabled job {0} from schedule".format(name))
|
||||
ret["comment"].append("Enabled job {} from schedule".format(name))
|
||||
else:
|
||||
ret["comment"].append("Job {0} not present in schedule".format(name))
|
||||
ret["comment"].append("Job {} not present in schedule".format(name))
|
||||
|
||||
ret["comment"] = "\n".join(ret["comment"])
|
||||
return ret
|
||||
|
@ -359,9 +360,9 @@ def disabled(name, **kwargs):
|
|||
ret["comment"] = result["comment"]
|
||||
return ret
|
||||
else:
|
||||
ret["comment"].append("Disabled job {0} from schedule".format(name))
|
||||
ret["comment"].append("Disabled job {} from schedule".format(name))
|
||||
else:
|
||||
ret["comment"].append("Job {0} not present in schedule".format(name))
|
||||
ret["comment"].append("Job {} not present in schedule".format(name))
|
||||
|
||||
ret["comment"] = "\n".join(ret["comment"])
|
||||
return ret
|
||||
|
|
|
@ -238,13 +238,13 @@ salt/utils/docker/*:
|
|||
- unit.utils.test_dockermod
|
||||
|
||||
salt/utils/schedule.py:
|
||||
- unit.utils.scheduler.test_error
|
||||
- unit.utils.scheduler.test_eval
|
||||
- unit.utils.scheduler.test_postpone
|
||||
- unit.utils.scheduler.test_skip
|
||||
- unit.utils.scheduler.test_maxrunning
|
||||
- unit.utils.scheduler.test_helpers
|
||||
- unit.utils.scheduler.test_schedule
|
||||
- pytests.unit.utils.scheduler.test_error
|
||||
- pytests.unit.utils.scheduler.test_eval
|
||||
- pytests.unit.utils.scheduler.test_postpone
|
||||
- pytests.unit.utils.scheduler.test_skip
|
||||
- pytests.unit.utils.scheduler.test_maxrunning
|
||||
- pytests.unit.utils.scheduler.test_helpers
|
||||
- pytests.unit.utils.scheduler.test_schedule
|
||||
|
||||
salt/utils/vt.py:
|
||||
- integration.cli.test_custom_module
|
||||
|
|
630
tests/pytests/unit/modules/test_schedule.py
Normal file
630
tests/pytests/unit/modules/test_schedule.py
Normal file
|
@ -0,0 +1,630 @@
|
|||
"""
|
||||
:codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.modules.schedule as schedule
|
||||
import salt.utils.odict
|
||||
from salt.utils.event import SaltEvent
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
JOB1 = {
|
||||
"function": "test.ping",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"jid_include": True,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sock_dir():
|
||||
with pytest.helpers.temp_directory() as tempdir:
|
||||
sock_dir = os.path.join(tempdir, "test-socks")
|
||||
return sock_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def configure_loader_modules():
|
||||
return {schedule: {}}
|
||||
|
||||
|
||||
# 'purge' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_purge(sock_dir):
|
||||
"""
|
||||
Test if it purge all the jobs currently scheduled on the minion.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.purge() == {
|
||||
"comment": ["Deleted job: schedule from schedule."],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'delete' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete(sock_dir):
|
||||
"""
|
||||
Test if it delete a job from the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.delete("job1") == {
|
||||
"comment": "Job job1 does not exist.",
|
||||
"changes": {},
|
||||
"result": False,
|
||||
}
|
||||
|
||||
|
||||
# 'build_schedule_item' function tests: 1
|
||||
|
||||
|
||||
def test_build_schedule_item(sock_dir):
|
||||
"""
|
||||
Test if it build a schedule job.
|
||||
"""
|
||||
comment = (
|
||||
'Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" or "cron" options.'
|
||||
)
|
||||
comment1 = 'Unable to use "when" and "cron" ' "options together. Ignoring."
|
||||
with patch.dict(schedule.__opts__, {"job1": {}}):
|
||||
assert schedule.build_schedule_item("") == {
|
||||
"comment": "Job name is required.",
|
||||
"result": False,
|
||||
}
|
||||
|
||||
assert schedule.build_schedule_item("job1", function="test.ping") == {
|
||||
"function": "test.ping",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"jid_include": True,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
assert schedule.build_schedule_item(
|
||||
"job1", function="test.ping", seconds=3600, when="2400"
|
||||
) == {"comment": comment, "result": False}
|
||||
|
||||
assert schedule.build_schedule_item(
|
||||
"job1", function="test.ping", when="2400", cron="2"
|
||||
) == {"comment": comment1, "result": False}
|
||||
|
||||
|
||||
# 'build_schedule_item_invalid_when' function tests: 1
|
||||
|
||||
|
||||
def test_build_schedule_item_invalid_when(sock_dir):
|
||||
"""
|
||||
Test if it build a schedule job.
|
||||
"""
|
||||
comment = 'Schedule item garbage for "when" in invalid.'
|
||||
with patch.dict(schedule.__opts__, {"job1": {}}):
|
||||
assert schedule.build_schedule_item(
|
||||
"job1", function="test.ping", when="garbage"
|
||||
) == {"comment": comment, "result": False}
|
||||
|
||||
|
||||
# 'add' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_add(sock_dir):
|
||||
"""
|
||||
Test if it add a job to the schedule.
|
||||
"""
|
||||
comm1 = "Job job1 already exists in schedule."
|
||||
comm2 = (
|
||||
'Error: Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" or "cron" options.'
|
||||
)
|
||||
comm3 = 'Unable to use "when" and "cron" options together. Ignoring.'
|
||||
comm4 = "Job: job2 would be added to schedule."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": {"salt": "salt"}}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.add("job1") == {
|
||||
"comment": comm1,
|
||||
"changes": {},
|
||||
"result": False,
|
||||
}
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.add(
|
||||
"job2", function="test.ping", seconds=3600, when="2400"
|
||||
) == {"comment": comm2, "changes": {}, "result": False}
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.add(
|
||||
"job2", function="test.ping", when="2400", cron="2"
|
||||
) == {"comment": comm3, "changes": {}, "result": False}
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.add("job2", function="test.ping", test=True) == {
|
||||
"comment": comm4,
|
||||
"changes": {},
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'run_job' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_job(sock_dir):
|
||||
"""
|
||||
Test if it run a scheduled job on the minion immediately.
|
||||
"""
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.run_job("job1") == {
|
||||
"comment": "Scheduling Job job1 on minion.",
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'enable_job' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job(sock_dir):
|
||||
"""
|
||||
Test if it enable a job in the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.enable_job("job1") == {
|
||||
"comment": "Job job1 does not exist.",
|
||||
"changes": {},
|
||||
"result": False,
|
||||
}
|
||||
|
||||
|
||||
# 'disable_job' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job(sock_dir):
|
||||
"""
|
||||
Test if it disable a job in the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.disable_job("job1") == {
|
||||
"comment": "Job job1 does not exist.",
|
||||
"changes": {},
|
||||
"result": False,
|
||||
}
|
||||
|
||||
|
||||
# 'save' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_save(sock_dir):
|
||||
"""
|
||||
Test if it save all scheduled jobs on the minion.
|
||||
"""
|
||||
comm1 = "Schedule (non-pillar items) saved."
|
||||
with patch.dict(
|
||||
schedule.__opts__,
|
||||
{"schedule": {}, "default_include": "/tmp", "sock_dir": sock_dir},
|
||||
):
|
||||
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
assert schedule.save() == {"comment": comm1, "result": True}
|
||||
|
||||
|
||||
# 'enable' function tests: 1
|
||||
|
||||
|
||||
def test_enable(sock_dir):
|
||||
"""
|
||||
Test if it enable all scheduled jobs on the minion.
|
||||
"""
|
||||
assert schedule.enable(test=True) == {
|
||||
"comment": "Schedule would be enabled.",
|
||||
"changes": {},
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'disable' function tests: 1
|
||||
|
||||
|
||||
def test_disable(sock_dir):
|
||||
"""
|
||||
Test if it disable all scheduled jobs on the minion.
|
||||
"""
|
||||
assert schedule.disable(test=True) == {
|
||||
"comment": "Schedule would be disabled.",
|
||||
"changes": {},
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'move' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_move(sock_dir):
|
||||
"""
|
||||
Test if it move scheduled job to another minion or minions.
|
||||
"""
|
||||
comm1 = "no servers answered the published schedule.add command"
|
||||
comm2 = "the following minions return False"
|
||||
comm3 = "Moved Job job1 from schedule."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm1,
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
assert schedule.move("job3", "minion1") == {
|
||||
"comment": "Job job3 does not exist.",
|
||||
"result": False,
|
||||
}
|
||||
|
||||
mock = MagicMock(side_effect=[{}, {"job1": {}}])
|
||||
with patch.dict(schedule.__opts__, {"schedule": mock, "sock_dir": sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
with patch.dict(schedule.__pillar__, {"schedule": {"job1": JOB1}}):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm1,
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
assert schedule.move("job1", "minion1") == {
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'copy' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_copy(sock_dir):
|
||||
"""
|
||||
Test if it copy scheduled job to another minion or minions.
|
||||
"""
|
||||
comm1 = "no servers answered the published schedule.add command"
|
||||
comm2 = "the following minions return False"
|
||||
comm3 = "Copied Job job1 from schedule to minion(s)."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": {"job1": JOB1}}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm1,
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
assert schedule.copy("job3", "minion1") == {
|
||||
"comment": "Job job3 does not exist.",
|
||||
"result": False,
|
||||
}
|
||||
|
||||
mock = MagicMock(side_effect=[{}, {"job1": {}}])
|
||||
with patch.dict(schedule.__opts__, {"schedule": mock, "sock_dir": sock_dir}):
|
||||
with patch.dict(schedule.__pillar__, {"schedule": {"job1": JOB1}}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {
|
||||
"complete": True,
|
||||
"schedule": {"job1": {"job1": JOB1}},
|
||||
}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm1,
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
assert schedule.copy("job1", "minion1") == {
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
}
|
||||
|
||||
|
||||
# 'modify' function tests: 1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_modify(sock_dir):
|
||||
"""
|
||||
Test if modifying job to the schedule.
|
||||
"""
|
||||
current_job1 = {
|
||||
"function": "salt",
|
||||
"seconds": "3600",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"enabled": True,
|
||||
"jid_include": True,
|
||||
}
|
||||
|
||||
new_job1 = {
|
||||
"function": "salt",
|
||||
"seconds": "60",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"enabled": True,
|
||||
"jid_include": True,
|
||||
}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
changes1 = {
|
||||
"job1": {
|
||||
"new": salt.utils.odict.OrderedDict(new_job1),
|
||||
"old": salt.utils.odict.OrderedDict(current_job1),
|
||||
}
|
||||
}
|
||||
|
||||
new_job4 = {
|
||||
"function": "test.version",
|
||||
"seconds": "3600",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"enabled": True,
|
||||
"jid_include": True,
|
||||
}
|
||||
|
||||
changes4 = {
|
||||
"job1": {
|
||||
"new": salt.utils.odict.OrderedDict(new_job4),
|
||||
"old": salt.utils.odict.OrderedDict(current_job1),
|
||||
}
|
||||
}
|
||||
|
||||
expected1 = {"comment": comm1, "changes": changes1, "result": True}
|
||||
|
||||
comm2 = (
|
||||
'Error: Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" option.'
|
||||
)
|
||||
expected2 = {"comment": comm2, "changes": {}, "result": False}
|
||||
|
||||
comm3 = 'Unable to use "when" and "cron" options together. Ignoring.'
|
||||
expected3 = {"comment": comm3, "changes": {}, "result": False}
|
||||
|
||||
comm4 = "Job: job1 would be modified in schedule."
|
||||
expected4 = {"comment": comm4, "changes": changes4, "result": True}
|
||||
|
||||
comm5 = "Job job2 does not exist in schedule."
|
||||
expected5 = {"comment": comm5, "changes": {}, "result": False}
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": current_job1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": current_job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job1", seconds="60")
|
||||
assert "job1" in ret["changes"]
|
||||
assert "new" in ret["changes"]["job1"]
|
||||
assert "old" in ret["changes"]["job1"]
|
||||
|
||||
assert (
|
||||
ret["changes"]["job1"]["new"] == expected1["changes"]["job1"]["new"]
|
||||
)
|
||||
assert (
|
||||
ret["changes"]["job1"]["old"] == expected1["changes"]["job1"]["old"]
|
||||
)
|
||||
|
||||
assert ret["comment"] == expected1["comment"]
|
||||
assert ret["result"] == expected1["result"]
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": current_job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify(
|
||||
"job1", function="test.ping", seconds=3600, when="2400"
|
||||
)
|
||||
assert ret == expected2
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": current_job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify(
|
||||
"job1", function="test.ping", when="2400", cron="2"
|
||||
)
|
||||
assert ret == expected3
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": current_job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job1", function="test.version", test=True)
|
||||
|
||||
assert "job1" in ret["changes"]
|
||||
assert "new" in ret["changes"]["job1"]
|
||||
assert "old" in ret["changes"]["job1"]
|
||||
|
||||
assert (
|
||||
ret["changes"]["job1"]["new"] == expected4["changes"]["job1"]["new"]
|
||||
)
|
||||
assert (
|
||||
ret["changes"]["job1"]["old"] == expected4["changes"]["job1"]["old"]
|
||||
)
|
||||
|
||||
assert ret["comment"] == expected4["comment"]
|
||||
assert ret["result"] == expected4["result"]
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job2", function="test.version", test=True)
|
||||
assert ret == expected5
|
||||
|
||||
|
||||
# 'is_enabled' function tests: 1
|
||||
|
||||
|
||||
def test_is_enabled(sock_dir):
|
||||
"""
|
||||
Test is_enabled
|
||||
"""
|
||||
job1 = {"function": "salt", "seconds": 3600}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
|
||||
mock_schedule = {"enabled": True, "job1": job1}
|
||||
|
||||
mock_lst = MagicMock(return_value=mock_schedule)
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": job1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(
|
||||
schedule.__salt__, {"event.fire": mock, "schedule.list": mock_lst}
|
||||
):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.is_enabled("job1")
|
||||
assert ret == job1
|
||||
|
||||
ret = schedule.is_enabled()
|
||||
assert ret
|
||||
|
||||
|
||||
# 'job_status' function tests: 1
|
||||
|
||||
|
||||
def test_job_status(sock_dir):
|
||||
"""
|
||||
Test is_enabled
|
||||
"""
|
||||
job1 = {"function": "salt", "seconds": 3600}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
|
||||
mock_schedule = {"enabled": True, "job1": job1}
|
||||
|
||||
mock_lst = MagicMock(return_value=mock_schedule)
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": job1}, "sock_dir": sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "data": job1}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret == job1
|
0
tests/pytests/unit/utils/scheduler/__init__.py
Normal file
0
tests/pytests/unit/utils/scheduler/__init__.py
Normal file
92
tests/pytests/unit/utils/scheduler/conftest.py
Normal file
92
tests/pytests/unit/utils/scheduler/conftest.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
tests.unit.utils.scheduler.base
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.utils.platform
|
||||
import salt.utils.schedule
|
||||
from salt.modules.test import ping
|
||||
from salt.utils.process import SubprocessList
|
||||
from saltfactories.utils.processes import terminate_process
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root_dir():
|
||||
with pytest.helpers.temp_directory() as tempdir:
|
||||
root_dir = os.path.join(tempdir, "schedule-unit-tests")
|
||||
return root_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sock_dir(root_dir):
|
||||
sock_dir = os.path.join(root_dir, "test-socks")
|
||||
return sock_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_config(root_dir, sock_dir):
|
||||
default_config = salt.config.minion_config(None)
|
||||
default_config["conf_dir"] = root_dir
|
||||
default_config["root_dir"] = root_dir
|
||||
default_config["sock_dir"] = sock_dir
|
||||
default_config["pki_dir"] = os.path.join(root_dir, "pki")
|
||||
default_config["cachedir"] = os.path.join(root_dir, "cache")
|
||||
|
||||
return default_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def subprocess_list():
|
||||
subprocess_list = SubprocessList()
|
||||
return subprocess_list
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def schedule(subprocess_list, default_config):
|
||||
with patch("salt.utils.schedule.clean_proc_dir", MagicMock(return_value=None)):
|
||||
functions = {"test.ping": ping}
|
||||
schedule = salt.utils.schedule.Schedule(
|
||||
copy.deepcopy(default_config), functions, returners={}, new_instance=True,
|
||||
)
|
||||
schedule._subprocess_list = subprocess_list
|
||||
return schedule
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def loop_interval(schedule):
|
||||
schedule.opts["loop_interval"] = 1
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def setup_teardown_vars(subprocess_list):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
processes = subprocess_list.processes
|
||||
schedule.reset()
|
||||
del schedule
|
||||
for proc in processes:
|
||||
if proc.is_alive():
|
||||
terminate_process(proc.pid, kill_children=True, slow_stop=True)
|
||||
subprocess_list.cleanup()
|
||||
processes = subprocess_list.processes
|
||||
if processes:
|
||||
for proc in processes:
|
||||
if proc.is_alive():
|
||||
terminate_process(proc.pid, kill_children=True, slow_stop=False)
|
||||
subprocess_list.cleanup()
|
||||
processes = subprocess_list.processes
|
||||
if processes:
|
||||
log.warning("Processes left running: %s", processes)
|
||||
|
||||
del default_config
|
||||
del subprocess_list
|
213
tests/pytests/unit/utils/scheduler/test_error.py
Normal file
213
tests/pytests/unit/utils/scheduler/test_error.py
Normal file
|
@ -0,0 +1,213 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
try:
|
||||
import croniter # pylint: disable=unused-import
|
||||
|
||||
HAS_CRONITER = True
|
||||
except ImportError:
|
||||
HAS_CRONITER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
HAS_DATEUTIL_PARSER is False,
|
||||
reason="The 'dateutil.parser' library is not available",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_CRONITER, reason="Cannot find croniter python module")
|
||||
def test_eval_cron_invalid(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "cron": "0 16 29 13 *"}}}
|
||||
|
||||
# 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)
|
||||
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret["_error"] == "Invalid cron string. Ignoring job job1."
|
||||
|
||||
|
||||
def test_eval_when_invalid_date(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "when": "13/29/2017 1:00pm"}}}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret["_error"] == "Invalid date string 13/29/2017 1:00pm. Ignoring job job1."
|
||||
|
||||
|
||||
def test_eval_whens_grain_not_dict(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
schedule.opts["grains"]["whens"] = {"tea time": "11/29/2017 12:00pm"}
|
||||
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "when": "tea time"}}}
|
||||
|
||||
schedule.opts["grains"]["whens"] = ["tea time"]
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret["_error"] == 'Grain "whens" must be a dict. Ignoring job job1.'
|
||||
|
||||
|
||||
def test_eval_once_invalid_datestring(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
job = {
|
||||
"schedule": {"job1": {"function": "test.ping", "once": "2017-13-13T13:00:00"}}
|
||||
}
|
||||
run_time = dateutil.parser.parse("12/13/2017 1:00pm")
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
_expected = (
|
||||
"Date string could not be parsed: "
|
||||
"2017-13-13T13:00:00, %Y-%m-%dT%H:%M:%S. "
|
||||
"Ignoring job job1."
|
||||
)
|
||||
assert ret["_error"] == _expected
|
||||
|
||||
|
||||
def test_eval_skip_during_range_invalid_date(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": {"start": "1:00pm", "end": "25:00pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
_expected = (
|
||||
"Invalid date string for end in " "skip_during_range. Ignoring " "job job1."
|
||||
)
|
||||
assert ret["_error"] == _expected
|
||||
|
||||
|
||||
def test_eval_skip_during_range_end_before_start(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": {"start": "1:00pm", "end": "12:00pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
_expected = (
|
||||
"schedule.handle_func: Invalid "
|
||||
"range, end must be larger than "
|
||||
"start. Ignoring job job1."
|
||||
)
|
||||
assert ret["_error"] == _expected
|
||||
|
||||
|
||||
def test_eval_skip_during_range_not_dict(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": ["start", "1:00pm", "end", "12:00pm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
_expected = (
|
||||
"schedule.handle_func: Invalid, "
|
||||
"range must be specified as a "
|
||||
"dictionary. Ignoring job job1."
|
||||
)
|
||||
assert ret["_error"] == _expected
|
924
tests/pytests/unit/utils/scheduler/test_eval.py
Normal file
924
tests/pytests/unit/utils/scheduler/test_eval.py
Normal file
|
@ -0,0 +1,924 @@
|
|||
import datetime
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import salt.utils.platform
|
||||
import salt.utils.schedule
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import skipIf
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
|
||||
try:
|
||||
import croniter # pylint: disable=unused-import
|
||||
|
||||
HAS_CRONITER = True
|
||||
except ImportError:
|
||||
HAS_CRONITER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
HAS_DATEUTIL_PARSER is False,
|
||||
reason="The 'dateutil.parser' library is not available",
|
||||
),
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_multiple_whens(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time1
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# Evaluate run time2
|
||||
schedule.eval(now=run_time2)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time2
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_whens(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_loop_interval(schedule, loop_interval):
|
||||
"""
|
||||
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))
|
||||
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_multiple_whens_loop_interval(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
assert ret["_last_run"] == run_time1
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
schedule.eval(now=run_time2)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time2
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_once(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_once_loop_interval(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@skipIf(not HAS_CRONITER, "Cannot find croniter python module")
|
||||
def test_eval_cron(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@skipIf(not HAS_CRONITER, "Cannot find croniter python module")
|
||||
def test_eval_cron_loop_interval(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_until(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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"] == "until_passed"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_after(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_enabled(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time1
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_enabled_key(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status("test_eval_enabled_key")
|
||||
assert ret["_last_run"] == run_time2
|
||||
|
||||
|
||||
def test_eval_disabled(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
"""
|
||||
job_name = "test_eval_disabled"
|
||||
job = {
|
||||
"schedule": {
|
||||
"enabled": False,
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" not in ret
|
||||
assert ret["_skip_reason"] == "disabled"
|
||||
|
||||
# Ensure job data still matches
|
||||
assert ret == job["schedule"][job_name]
|
||||
|
||||
|
||||
def test_eval_global_disabled_job_enabled(schedule, loop_interval):
|
||||
"""
|
||||
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"]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_run_on_start(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay_range(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job runs with splayed time
|
||||
"""
|
||||
job_name = "job_eval_splay_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"seconds": "30",
|
||||
"splay": {"start": 5, "end": 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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay_global(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_seconds(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job run mutiple times with seconds
|
||||
"""
|
||||
with patch.dict(schedule.opts, {"run_schedule_jobs_in_background": False}):
|
||||
job_name = "job_eval_seconds"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "seconds": "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(seconds=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:00:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:30pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
# eval at 2:01:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:01:00pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
# eval at 2:01:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:01:30pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_minutes(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_hours(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job run mutiple times with hours
|
||||
"""
|
||||
with patch.dict(schedule.opts, {"run_schedule_jobs_in_background": False}):
|
||||
job_name = "job_eval_hours"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "hours": "2"}}}
|
||||
|
||||
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(hours=2)
|
||||
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 4:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 4: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 6:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 6: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 8:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 8:00:00pm")
|
||||
pids = schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_days(schedule, loop_interval):
|
||||
"""
|
||||
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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == last_run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == last_run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
assert ret["_next_fire_time"] == next_run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_when_splay(schedule, loop_interval):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def test_eval_when_splay_in_past(schedule, loop_interval):
|
||||
"""
|
||||
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
|
30
tests/pytests/unit/utils/scheduler/test_helpers.py
Normal file
30
tests/pytests/unit/utils/scheduler/test_helpers.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def loop_interval(schedule):
|
||||
schedule.opts["loop_interval"] = 1
|
||||
|
||||
|
||||
def test_get_schedule(schedule, loop_interval):
|
||||
"""
|
||||
verify that the _get_schedule function works
|
||||
when remove_hidden is True and schedule data
|
||||
contains enabled key
|
||||
"""
|
||||
job_name = "test_get_schedule"
|
||||
job = {
|
||||
"schedule": {
|
||||
"enabled": True,
|
||||
job_name: {"function": "test.ping", "seconds": 60},
|
||||
}
|
||||
}
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
ret = schedule._get_schedule(remove_hidden=True)
|
||||
assert job["schedule"] == ret
|
129
tests/pytests/unit/utils/scheduler/test_maxrunning.py
Normal file
129
tests/pytests/unit/utils/scheduler/test_maxrunning.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
try:
|
||||
import dateutil.parser as dateutil_parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
HAS_DATEUTIL_PARSER is False,
|
||||
reason="The 'dateutil.parser' library is not available",
|
||||
),
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
def test_maxrunning_minion(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
schedule.opts["__role"] = "minion"
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"maxrunning_minion": {
|
||||
"function": "test.ping",
|
||||
"seconds": 10,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_data = {
|
||||
"function": "test.ping",
|
||||
"run": True,
|
||||
"name": "maxrunning_minion",
|
||||
"seconds": 10,
|
||||
"_seconds": 10,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
running_data = [
|
||||
{
|
||||
"fun_args": [],
|
||||
"jid": "20181018165923360935",
|
||||
"schedule": "maxrunning_minion",
|
||||
"pid": 15338,
|
||||
"fun": "test.ping",
|
||||
"id": "host",
|
||||
}
|
||||
]
|
||||
|
||||
run_time = dateutil_parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
with patch("salt.utils.minion.running", MagicMock(return_value=running_data)):
|
||||
with patch("salt.utils.process.os_is_running", MagicMock(return_value=True)):
|
||||
ret = schedule._check_max_running(
|
||||
"test.ping", job_data, schedule.opts, now=run_time
|
||||
)
|
||||
assert "_skip_reason" in ret
|
||||
assert "maxrunning" == ret["_skip_reason"]
|
||||
assert not ret["run"]
|
||||
|
||||
|
||||
def test_maxrunning_master(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
schedule.opts["__role"] = "master"
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"maxrunning_master": {
|
||||
"function": "state.orch",
|
||||
"args": ["test.orch_test"],
|
||||
"minutes": 1,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_data = {
|
||||
"function": "state.orch",
|
||||
"fun_args": ["test.orch_test"],
|
||||
"run": True,
|
||||
"name": "maxrunning_master",
|
||||
"minutes": 1,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
running_data = [
|
||||
{
|
||||
"fun_args": ["test.orch_test"],
|
||||
"jid": "20181018165923360935",
|
||||
"schedule": "maxrunning_master",
|
||||
"pid": 15338,
|
||||
"fun": "state.orch",
|
||||
"id": "host",
|
||||
}
|
||||
]
|
||||
|
||||
run_time = dateutil_parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
with patch(
|
||||
"salt.utils.master.get_running_jobs", MagicMock(return_value=running_data)
|
||||
):
|
||||
with patch("salt.utils.process.os_is_running", MagicMock(return_value=True)):
|
||||
ret = schedule._check_max_running(
|
||||
"state.orch", job_data, schedule.opts, now=run_time
|
||||
)
|
||||
assert "_skip_reason" in ret
|
||||
assert "maxrunning" == ret["_skip_reason"]
|
||||
assert not ret["run"]
|
63
tests/pytests/unit/utils/scheduler/test_postpone.py
Normal file
63
tests/pytests/unit/utils/scheduler/test_postpone.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import datetime
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
HAS_DATEUTIL_PARSER is False,
|
||||
reason="The 'dateutil.parser' library is not available",
|
||||
),
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_postpone(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is postponed until the specified time.
|
||||
"""
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "when": "11/29/2017 4pm"}}}
|
||||
|
||||
# 11/29/2017 4pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
# 5 minute delay
|
||||
delay = 300
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
# Postpone the job by 5 minutes
|
||||
schedule.postpone_job(
|
||||
"job1",
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"new_time": (run_time + datetime.timedelta(seconds=delay)).strftime(
|
||||
"%Y-%m-%dT%H:%M:%S"
|
||||
),
|
||||
},
|
||||
)
|
||||
# Run at the original time
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status("job1")
|
||||
assert "_last_run" not in ret
|
||||
|
||||
# Run 5 minutes later
|
||||
schedule.eval(now=run_time + datetime.timedelta(seconds=delay))
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret["_last_run"] == run_time + datetime.timedelta(seconds=delay)
|
||||
|
||||
# Run 6 minutes later
|
||||
schedule.eval(now=run_time + datetime.timedelta(seconds=delay + 1))
|
||||
ret = schedule.job_status("job1")
|
||||
assert ret["_last_run"] == run_time + datetime.timedelta(seconds=delay)
|
19
tests/pytests/unit/utils/scheduler/test_run_job.py
Normal file
19
tests/pytests/unit/utils/scheduler/test_run_job.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def test_run_job(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
job_name = "test_run_job"
|
||||
job = {"schedule": {job_name: {"function": "test.ping"}}}
|
||||
# Add the job to the scheduler
|
||||
schedule.opts.update(job)
|
||||
|
||||
# Run job
|
||||
schedule.run_job(job_name)
|
||||
ret = schedule.job_status(job_name)
|
||||
expected = {"function": "test.ping", "run": True, "name": "test_run_job"}
|
||||
assert ret == expected
|
478
tests/pytests/unit/utils/scheduler/test_schedule.py
Normal file
478
tests/pytests/unit/utils/scheduler/test_schedule.py
Normal file
|
@ -0,0 +1,478 @@
|
|||
"""
|
||||
:codeauthor: Nicole Thomas <nicole@saltstack.com>
|
||||
"""
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
import salt.config
|
||||
import salt.utils.schedule
|
||||
from salt.utils.schedule import Schedule
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
# pylint: disable=import-error,unused-import
|
||||
try:
|
||||
import croniter
|
||||
|
||||
_CRON_SUPPORTED = True
|
||||
except ImportError:
|
||||
_CRON_SUPPORTED = False
|
||||
# pylint: enable=import-error
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods,invalid-name
|
||||
# delete_job tests
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_exists(schedule):
|
||||
"""
|
||||
Tests ensuring the job exists and deleting it
|
||||
"""
|
||||
schedule.opts.update({"schedule": {"foo": "bar"}, "pillar": {}})
|
||||
assert "foo" in schedule.opts["schedule"]
|
||||
|
||||
schedule.delete_job("foo")
|
||||
assert "foo" not in schedule.opts["schedule"]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_in_pillar(schedule):
|
||||
"""
|
||||
Tests ignoring deletion job from pillar
|
||||
"""
|
||||
schedule.opts.update({"pillar": {"schedule": {"foo": "bar"}}, "schedule": {}})
|
||||
assert "foo" in schedule.opts["pillar"]["schedule"]
|
||||
schedule.delete_job("foo")
|
||||
|
||||
assert "foo" in schedule.opts["pillar"]["schedule"]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_intervals(schedule):
|
||||
"""
|
||||
Tests removing job from intervals
|
||||
"""
|
||||
schedule.opts.update({"pillar": {}, "schedule": {}})
|
||||
schedule.intervals = {"foo": "bar"}
|
||||
schedule.delete_job("foo")
|
||||
assert "foo" not in schedule.intervals
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_prefix(schedule):
|
||||
"""
|
||||
Tests ensuring jobs exists and deleting them by prefix
|
||||
"""
|
||||
schedule.opts.update(
|
||||
{"schedule": {"foobar": "bar", "foobaz": "baz", "fooboo": "boo"}, "pillar": {}}
|
||||
)
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
del ret["schedule"]["foobar"]
|
||||
del ret["schedule"]["foobaz"]
|
||||
schedule.delete_job_prefix("fooba")
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_prefix_in_pillar(schedule):
|
||||
"""
|
||||
Tests ignoring deletion jobs by prefix from pillar
|
||||
"""
|
||||
schedule.opts.update(
|
||||
{
|
||||
"pillar": {"schedule": {"foobar": "bar", "foobaz": "baz", "fooboo": "boo"}},
|
||||
"schedule": {},
|
||||
}
|
||||
)
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
schedule.delete_job_prefix("fooba")
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
# add_job tests
|
||||
def test_add_job_data_not_dict(schedule):
|
||||
"""
|
||||
Tests if data is a dictionary
|
||||
"""
|
||||
data = "foo"
|
||||
pytest.raises(ValueError, Schedule.add_job, schedule, data)
|
||||
|
||||
|
||||
def test_add_job_multiple_jobs(schedule):
|
||||
"""
|
||||
Tests if more than one job is scheduled at a time
|
||||
"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
pytest.raises(ValueError, Schedule.add_job, schedule, data)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_add_job(schedule):
|
||||
"""
|
||||
Tests adding a job to the schedule
|
||||
"""
|
||||
data = {"foo": {"bar": "baz"}}
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret.update(
|
||||
{
|
||||
"schedule": {
|
||||
"foo": {"bar": "baz", "enabled": True},
|
||||
"hello": {"world": "peace", "enabled": True},
|
||||
},
|
||||
"pillar": {},
|
||||
}
|
||||
)
|
||||
schedule.opts.update(
|
||||
{"schedule": {"hello": {"world": "peace", "enabled": True}}, "pillar": {}}
|
||||
)
|
||||
Schedule.add_job(schedule, data)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
# enable_job tests
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job(schedule):
|
||||
"""
|
||||
Tests enabling a job
|
||||
"""
|
||||
schedule.opts.update({"schedule": {"name": {"enabled": "foo"}}})
|
||||
Schedule.enable_job(schedule, "name")
|
||||
assert schedule.opts["schedule"]["name"]["enabled"]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job_pillar(schedule):
|
||||
"""
|
||||
Tests ignoring enable a job from pillar
|
||||
"""
|
||||
schedule.opts.update({"pillar": {"schedule": {"name": {"enabled": False}}}})
|
||||
Schedule.enable_job(schedule, "name", persist=False)
|
||||
assert not schedule.opts["pillar"]["schedule"]["name"]["enabled"]
|
||||
|
||||
|
||||
# disable_job tests
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job(schedule):
|
||||
"""
|
||||
Tests disabling a job
|
||||
"""
|
||||
schedule.opts.update({"schedule": {"name": {"enabled": "foo"}}, "pillar": {}})
|
||||
Schedule.disable_job(schedule, "name")
|
||||
assert not schedule.opts["schedule"]["name"]["enabled"]
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job_pillar(schedule):
|
||||
"""
|
||||
Tests ignoring disable a job in pillar
|
||||
"""
|
||||
schedule.opts.update(
|
||||
{"pillar": {"schedule": {"name": {"enabled": True}}}, "schedule": {}}
|
||||
)
|
||||
Schedule.disable_job(schedule, "name", persist=False)
|
||||
assert schedule.opts["pillar"]["schedule"]["name"]["enabled"]
|
||||
|
||||
|
||||
# modify_job tests
|
||||
@pytest.mark.slow_test
|
||||
def test_modify_job(schedule):
|
||||
"""
|
||||
Tests modifying a job in the scheduler
|
||||
"""
|
||||
schedule_dict = {"foo": "bar"}
|
||||
schedule.opts.update({"schedule": {"name": "baz"}, "pillar": {}})
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret.update({"schedule": {"name": {"foo": "bar"}}})
|
||||
Schedule.modify_job(schedule, "name", schedule_dict)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
def test_modify_job_not_exists(schedule):
|
||||
"""
|
||||
Tests modifying a job in the scheduler if jobs not exists
|
||||
"""
|
||||
schedule_dict = {"foo": "bar"}
|
||||
schedule.opts.update({"schedule": {}, "pillar": {}})
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret.update({"schedule": {"name": {"foo": "bar"}}})
|
||||
Schedule.modify_job(schedule, "name", schedule_dict)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
def test_modify_job_pillar(schedule):
|
||||
"""
|
||||
Tests ignoring modification of job from pillar
|
||||
"""
|
||||
schedule_dict = {"foo": "bar"}
|
||||
schedule.opts.update({"schedule": {}, "pillar": {"schedule": {"name": "baz"}}})
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
Schedule.modify_job(schedule, "name", schedule_dict, persist=False)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
maxDiff = None
|
||||
|
||||
|
||||
# enable_schedule tests
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_schedule(schedule):
|
||||
"""
|
||||
Tests enabling the scheduler
|
||||
"""
|
||||
with patch(
|
||||
"salt.utils.schedule.Schedule.persist", MagicMock(return_value=None)
|
||||
) as persist_mock:
|
||||
schedule.opts.update({"schedule": {"enabled": "foo"}, "pillar": {}})
|
||||
Schedule.enable_schedule(schedule)
|
||||
assert schedule.opts["schedule"]["enabled"]
|
||||
|
||||
persist_mock.assert_called()
|
||||
|
||||
|
||||
# disable_schedule tests
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_schedule(schedule):
|
||||
"""
|
||||
Tests disabling the scheduler
|
||||
"""
|
||||
with patch(
|
||||
"salt.utils.schedule.Schedule.persist", MagicMock(return_value=None)
|
||||
) as persist_mock:
|
||||
schedule.opts.update({"schedule": {"enabled": "foo"}, "pillar": {}})
|
||||
Schedule.disable_schedule(schedule)
|
||||
assert not schedule.opts["schedule"]["enabled"]
|
||||
|
||||
persist_mock.assert_called()
|
||||
|
||||
|
||||
# reload tests
|
||||
def test_reload_update_schedule_key(schedule):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule where both the
|
||||
saved schedule and schedule.opts contain a schedule key
|
||||
"""
|
||||
saved = {"schedule": {"foo": "bar"}}
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret.update({"schedule": {"foo": "bar", "hello": "world"}})
|
||||
schedule.opts.update({"schedule": {"hello": "world"}})
|
||||
Schedule.reload(schedule, saved)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
def test_reload_update_schedule_no_key(schedule):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that does not
|
||||
contain a schedule key but schedule.opts does
|
||||
"""
|
||||
saved = {"foo": "bar"}
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret.update({"schedule": {"foo": "bar", "hello": "world"}})
|
||||
schedule.opts.update({"schedule": {"hello": "world"}})
|
||||
Schedule.reload(schedule, saved)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
def test_reload_no_schedule_in_opts(schedule):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that does not
|
||||
contain a schedule key and neither does schedule.opts
|
||||
"""
|
||||
saved = {"foo": "bar"}
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret["schedule"] = {"foo": "bar"}
|
||||
schedule.opts.pop("schedule", None)
|
||||
Schedule.reload(schedule, saved)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
def test_reload_schedule_in_saved_but_not_opts(schedule):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that contains
|
||||
a schedule key, but schedule.opts does not
|
||||
"""
|
||||
saved = {"schedule": {"foo": "bar"}}
|
||||
ret = copy.deepcopy(schedule.opts)
|
||||
ret["schedule"] = {"foo": "bar"}
|
||||
schedule.opts.pop("schedule", None)
|
||||
Schedule.reload(schedule, saved)
|
||||
assert schedule.opts == ret
|
||||
|
||||
|
||||
# eval tests
|
||||
def test_eval_schedule_is_not_dict(schedule):
|
||||
"""
|
||||
Tests eval if the schedule is not a dictionary
|
||||
"""
|
||||
schedule.opts.update({"schedule": "", "pillar": {"schedule": {}}})
|
||||
pytest.raises(ValueError, Schedule.eval, schedule)
|
||||
|
||||
|
||||
def test_eval_schedule_is_not_dict_in_pillar(schedule):
|
||||
"""
|
||||
Tests eval if the schedule from pillar is not a dictionary
|
||||
"""
|
||||
schedule.opts.update({"schedule": {}, "pillar": {"schedule": ""}})
|
||||
pytest.raises(ValueError, Schedule.eval, schedule)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _CRON_SUPPORTED, reason="croniter module not installed")
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _CRON_SUPPORTED, reason="croniter module not installed")
|
||||
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"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_handle_func_schedule_minion_blackout(schedule):
|
||||
"""
|
||||
Tests eval if the schedule from pillar is not a dictionary
|
||||
"""
|
||||
schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
schedule.opts.update({"grains": {"minion_blackout": True}})
|
||||
|
||||
schedule.opts.update(
|
||||
{"schedule": {"testjob": {"function": "test.true", "seconds": 60}}}
|
||||
)
|
||||
data = {
|
||||
"function": "test.true",
|
||||
"_next_scheduled_fire_time": datetime.datetime(2018, 11, 21, 14, 9, 53, 903438),
|
||||
"run": True,
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch.object(salt.utils.schedule, "log") as log_mock:
|
||||
with patch("salt.utils.process.daemonize"), patch("sys.platform", "linux2"):
|
||||
schedule.handle_func(False, "test.ping", data)
|
||||
assert log_mock.exception.called
|
||||
|
||||
|
||||
def test_handle_func_check_data(schedule):
|
||||
"""
|
||||
Tests handle_func to ensure that __pub_fun_args is not
|
||||
being duplicated in the value of kwargs in data.
|
||||
"""
|
||||
|
||||
data = {
|
||||
"function": "test.arg",
|
||||
"_next_scheduled_fire_time": datetime.datetime(2018, 11, 21, 14, 9, 53, 903438),
|
||||
"run": True,
|
||||
"args": ["arg1", "arg2"],
|
||||
"kwargs": {"key1": "value1", "key2": "value2"},
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch("salt.utils.process.daemonize"), patch("sys.platform", "linux2"):
|
||||
with patch.object(schedule, "standalone", return_value=True):
|
||||
# run handle_func once
|
||||
schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
# run handle_func and ensure __pub_fun_args
|
||||
# is not in kwargs
|
||||
schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
assert "kwargs" in data
|
||||
assert "__pub_fun_args" not in data["kwargs"]
|
||||
|
||||
|
||||
def test_handle_func_check_dicts(schedule):
|
||||
"""
|
||||
Tests that utils, functions, and returners dicts are not
|
||||
empty after handle_func has run on Windows.
|
||||
"""
|
||||
|
||||
data = {
|
||||
"function": "test.arg",
|
||||
"_next_scheduled_fire_time": datetime.datetime(2018, 11, 21, 14, 9, 53, 903438),
|
||||
"run": True,
|
||||
"args": ["arg1", "arg2"],
|
||||
"kwargs": {"key1": "value1", "key2": "value2"},
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch("salt.utils.process.daemonize"):
|
||||
with patch.object(schedule, "standalone", return_value=True):
|
||||
# simulate what happens before handle_func is called on Windows
|
||||
schedule.functions = {}
|
||||
schedule.returners = {}
|
||||
schedule.utils = {}
|
||||
schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
assert {} != schedule.functions
|
||||
assert {} != schedule.returners
|
||||
assert {} != schedule.utils
|
281
tests/pytests/unit/utils/scheduler/test_skip.py
Normal file
281
tests/pytests/unit/utils/scheduler/test_skip.py
Normal file
|
@ -0,0 +1,281 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
HAS_DATEUTIL_PARSER is False,
|
||||
reason="The 'dateutil.parser' library is not available",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is skipped at the specified time
|
||||
"""
|
||||
job_name = "test_skip"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"when": ["11/29/2017 4pm", "11/29/2017 5pm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
schedule.skip_job(
|
||||
job_name,
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"time_fmt": "%Y-%m-%dT%H:%M:%S",
|
||||
},
|
||||
)
|
||||
|
||||
# Run 11/29/2017 at 4pm
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" not in ret
|
||||
assert ret["_skip_reason"] == "skip_explicit"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
# Run 11/29/2017 at 5pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 5:00pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip_during_range(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "test_skip_during_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2pm",
|
||||
"end": "11/29/2017 3pm",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 1:30pm to prime.
|
||||
run_time = dateutil.parser.parse("11/29/2017 1:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" not in ret
|
||||
assert ret["_skip_reason"] == "in_skip_range"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
# eval at 3:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
def test_skip_during_range_invalid_datestring(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is not not and returns the right error string
|
||||
"""
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
|
||||
job_name1 = "skip_during_range_invalid_datestring1"
|
||||
job1 = {
|
||||
"schedule": {
|
||||
job_name1: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"_next_fire_time": run_time,
|
||||
"skip_during_range": {"start": "25pm", "end": "3pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_name2 = "skip_during_range_invalid_datestring2"
|
||||
job2 = {
|
||||
"schedule": {
|
||||
job_name2: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"_next_fire_time": run_time,
|
||||
"skip_during_range": {"start": "2pm", "end": "25pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job1 to schedule
|
||||
schedule.opts.update(job1)
|
||||
|
||||
# Eval
|
||||
schedule.eval(now=run_time)
|
||||
|
||||
# Check the first job
|
||||
ret = schedule.job_status(job_name1)
|
||||
_expected = (
|
||||
"Invalid date string for start in " "skip_during_range. Ignoring " "job {}."
|
||||
).format(job_name1)
|
||||
assert ret["_error"] == _expected
|
||||
|
||||
# Clear out schedule
|
||||
schedule.opts["schedule"] = {}
|
||||
|
||||
# Add job2 to schedule
|
||||
schedule.opts.update(job2)
|
||||
|
||||
# Eval
|
||||
schedule.eval(now=run_time)
|
||||
|
||||
# Check the second job
|
||||
ret = schedule.job_status(job_name2)
|
||||
_expected = (
|
||||
"Invalid date string for end in " "skip_during_range. Ignoring " "job {}."
|
||||
).format(job_name2)
|
||||
assert ret["_error"] == _expected
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip_during_range_global(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "skip_during_range_global"
|
||||
job = {
|
||||
"schedule": {
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2:00pm",
|
||||
"end": "11/29/2017 3:00pm",
|
||||
},
|
||||
job_name: {"function": "test.ping", "hours": "1"},
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 1:30pm to prime.
|
||||
run_time = dateutil.parser.parse("11/29/2017 1:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" not in ret
|
||||
assert ret["_skip_reason"] == "in_skip_range"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
# eval at 3:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_after_skip_range(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "skip_run_after_skip_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"when": "11/29/2017 2:30pm",
|
||||
"run_after_skip_range": True,
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2pm",
|
||||
"end": "11/29/2017 3pm",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" not in ret
|
||||
assert ret["_skip_reason"] == "in_skip_range"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
# eval at 3:00:01pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00:01pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert ret["_last_run"] == run_time
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_seconds_skip(schedule, loop_interval):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "run_seconds_skip"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "seconds": "10"}}}
|
||||
|
||||
# Add job to schedule
|
||||
schedule.opts.update(job)
|
||||
|
||||
# eval at 2:00pm, to prime the scheduler
|
||||
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:10pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:10pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
|
||||
# Skip at 2:00:20pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:20pm")
|
||||
schedule.skip_job(
|
||||
job_name,
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"time_fmt": "%Y-%m-%dT%H:%M:%S",
|
||||
},
|
||||
)
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_next_fire_time" in ret
|
||||
assert ret["_skip_reason"] == "skip_explicit"
|
||||
assert ret["_skipped_time"] == run_time
|
||||
|
||||
# Run at 2:00:30pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:30pm")
|
||||
schedule.eval(now=run_time)
|
||||
ret = schedule.job_status(job_name)
|
||||
assert "_last_run" in ret
|
|
@ -1,588 +0,0 @@
|
|||
"""
|
||||
:codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.modules.schedule as schedule
|
||||
from salt.utils.event import SaltEvent
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
JOB1 = {
|
||||
"function": "test.ping",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"jid_include": True,
|
||||
"enabled": True,
|
||||
}
|
||||
|
||||
|
||||
class ScheduleTestCase(TestCase, LoaderModuleMockMixin):
|
||||
"""
|
||||
Test cases for salt.modules.schedule
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.sock_dir = os.path.join(RUNTIME_VARS.TMP, "test-socks")
|
||||
|
||||
def setup_loader_modules(self):
|
||||
return {schedule: {}}
|
||||
|
||||
# 'purge' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_purge(self):
|
||||
"""
|
||||
Test if it purge all the jobs currently scheduled on the minion.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": self.sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.purge(),
|
||||
{
|
||||
"comment": ["Deleted job: schedule from schedule."],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
# 'delete' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete(self):
|
||||
"""
|
||||
Test if it delete a job from the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": self.sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.delete("job1"),
|
||||
{"comment": "Job job1 does not exist.", "result": False},
|
||||
)
|
||||
|
||||
# 'build_schedule_item' function tests: 1
|
||||
|
||||
def test_build_schedule_item(self):
|
||||
"""
|
||||
Test if it build a schedule job.
|
||||
"""
|
||||
comment = (
|
||||
'Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" or "cron" options.'
|
||||
)
|
||||
comment1 = 'Unable to use "when" and "cron" ' "options together. Ignoring."
|
||||
with patch.dict(schedule.__opts__, {"job1": {}}):
|
||||
self.assertDictEqual(
|
||||
schedule.build_schedule_item(""),
|
||||
{"comment": "Job name is required.", "result": False},
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
schedule.build_schedule_item("job1", function="test.ping"),
|
||||
{
|
||||
"function": "test.ping",
|
||||
"maxrunning": 1,
|
||||
"name": "job1",
|
||||
"jid_include": True,
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
schedule.build_schedule_item(
|
||||
"job1", function="test.ping", seconds=3600, when="2400"
|
||||
),
|
||||
{"comment": comment, "result": False},
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
schedule.build_schedule_item(
|
||||
"job1", function="test.ping", when="2400", cron="2"
|
||||
),
|
||||
{"comment": comment1, "result": False},
|
||||
)
|
||||
|
||||
# 'build_schedule_item_invalid_when' function tests: 1
|
||||
|
||||
def test_build_schedule_item_invalid_when(self):
|
||||
"""
|
||||
Test if it build a schedule job.
|
||||
"""
|
||||
comment = 'Schedule item garbage for "when" in invalid.'
|
||||
with patch.dict(schedule.__opts__, {"job1": {}}):
|
||||
self.assertDictEqual(
|
||||
schedule.build_schedule_item(
|
||||
"job1", function="test.ping", when="garbage"
|
||||
),
|
||||
{"comment": comment, "result": False},
|
||||
)
|
||||
|
||||
# 'add' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_add(self):
|
||||
"""
|
||||
Test if it add a job to the schedule.
|
||||
"""
|
||||
comm1 = "Job job1 already exists in schedule."
|
||||
comm2 = (
|
||||
'Error: Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" or "cron" options.'
|
||||
)
|
||||
comm3 = 'Unable to use "when" and "cron" options together. Ignoring.'
|
||||
comm4 = "Job: job2 would be added to schedule."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": {"salt": "salt"}}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.add("job1"), {"comment": comm1, "result": False}
|
||||
)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.add(
|
||||
"job2", function="test.ping", seconds=3600, when="2400"
|
||||
),
|
||||
{"comment": comm2, "result": False},
|
||||
)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.add(
|
||||
"job2", function="test.ping", when="2400", cron="2"
|
||||
),
|
||||
{"comment": comm3, "result": False},
|
||||
)
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.add("job2", function="test.ping", test=True),
|
||||
{"comment": comm4, "result": True},
|
||||
)
|
||||
|
||||
# 'run_job' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_job(self):
|
||||
"""
|
||||
Test if it run a scheduled job on the minion immediately.
|
||||
"""
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.run_job("job1"),
|
||||
{"comment": "Scheduling Job job1 on minion.", "result": True},
|
||||
)
|
||||
|
||||
# 'enable_job' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job(self):
|
||||
"""
|
||||
Test if it enable a job in the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": self.sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.enable_job("job1"),
|
||||
{"comment": "Job job1 does not exist.", "result": False},
|
||||
)
|
||||
|
||||
# 'disable_job' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job(self):
|
||||
"""
|
||||
Test if it disable a job in the minion's schedule.
|
||||
"""
|
||||
with patch.dict(schedule.__opts__, {"schedule": {}, "sock_dir": self.sock_dir}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.disable_job("job1"),
|
||||
{"comment": "Job job1 does not exist.", "result": False},
|
||||
)
|
||||
|
||||
# 'save' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_save(self):
|
||||
"""
|
||||
Test if it save all scheduled jobs on the minion.
|
||||
"""
|
||||
comm1 = "Schedule (non-pillar items) saved."
|
||||
with patch.dict(
|
||||
schedule.__opts__,
|
||||
{"schedule": {}, "default_include": "/tmp", "sock_dir": self.sock_dir},
|
||||
):
|
||||
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
self.assertDictEqual(
|
||||
schedule.save(), {"comment": comm1, "result": True}
|
||||
)
|
||||
|
||||
# 'enable' function tests: 1
|
||||
|
||||
def test_enable(self):
|
||||
"""
|
||||
Test if it enable all scheduled jobs on the minion.
|
||||
"""
|
||||
self.assertDictEqual(
|
||||
schedule.enable(test=True),
|
||||
{"comment": "Schedule would be enabled.", "result": True},
|
||||
)
|
||||
|
||||
# 'disable' function tests: 1
|
||||
|
||||
def test_disable(self):
|
||||
"""
|
||||
Test if it disable all scheduled jobs on the minion.
|
||||
"""
|
||||
self.assertDictEqual(
|
||||
schedule.disable(test=True),
|
||||
{"comment": "Schedule would be disabled.", "result": True},
|
||||
)
|
||||
|
||||
# 'move' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_move(self):
|
||||
"""
|
||||
Test if it move scheduled job to another minion or minions.
|
||||
"""
|
||||
comm1 = "no servers answered the published schedule.add command"
|
||||
comm2 = "the following minions return False"
|
||||
comm3 = "Moved Job job1 from schedule."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{"comment": comm1, "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{"comment": comm2, "minions": ["minion1"], "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
schedule.move("job3", "minion1"),
|
||||
{"comment": "Job job3 does not exist.", "result": False},
|
||||
)
|
||||
|
||||
mock = MagicMock(side_effect=[{}, {"job1": {}}])
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": mock, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": JOB1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
with patch.dict(schedule.__pillar__, {"schedule": {"job1": JOB1}}):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{"comment": comm1, "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.move("job1", "minion1"),
|
||||
{
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
# 'copy' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_copy(self):
|
||||
"""
|
||||
Test if it copy scheduled job to another minion or minions.
|
||||
"""
|
||||
comm1 = "no servers answered the published schedule.add command"
|
||||
comm2 = "the following minions return False"
|
||||
comm3 = "Copied Job job1 from schedule to minion(s)."
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": JOB1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": {"job1": JOB1}}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{"comment": comm1, "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{"comment": comm2, "minions": ["minion1"], "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job3", "minion1"),
|
||||
{"comment": "Job job3 does not exist.", "result": False},
|
||||
)
|
||||
|
||||
mock = MagicMock(side_effect=[{}, {"job1": {}}])
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": mock, "sock_dir": self.sock_dir}
|
||||
):
|
||||
with patch.dict(schedule.__pillar__, {"schedule": {"job1": JOB1}}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {
|
||||
"complete": True,
|
||||
"schedule": {"job1": {"job1": JOB1}},
|
||||
}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
|
||||
mock = MagicMock(return_value={})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{"comment": comm1, "result": True},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": ""})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{
|
||||
"comment": comm2,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
mock = MagicMock(return_value={"minion1": "job1"})
|
||||
with patch.dict(schedule.__salt__, {"publish.publish": mock}):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
self.assertDictEqual(
|
||||
schedule.copy("job1", "minion1"),
|
||||
{
|
||||
"comment": comm3,
|
||||
"minions": ["minion1"],
|
||||
"result": True,
|
||||
},
|
||||
)
|
||||
|
||||
# 'modify' function tests: 1
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_modify(self):
|
||||
"""
|
||||
Test if modifying job to the schedule.
|
||||
"""
|
||||
job1 = {"function": "salt", "seconds": 3600}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
diff1 = (
|
||||
"--- \n+++ \n@@ -1,3 +1,6 @@\n "
|
||||
"enabled:True\n function:salt\n"
|
||||
"-seconds:3600\n+jid_include:True\n"
|
||||
"+maxrunning:1\n+name:job1\n"
|
||||
"+seconds:60\n"
|
||||
)
|
||||
|
||||
diff4 = (
|
||||
"--- \n+++ \n@@ -1,3 +1,5 @@\n "
|
||||
"enabled:True\n-function:salt\n"
|
||||
"-seconds:3600\n+function:test.version\n"
|
||||
"+jid_include:True\n+maxrunning:1\n"
|
||||
"+name:job1\n"
|
||||
)
|
||||
|
||||
expected1 = {"comment": comm1, "changes": {"diff": diff1}, "result": True}
|
||||
|
||||
comm2 = (
|
||||
'Error: Unable to use "seconds", "minutes", "hours", '
|
||||
'or "days" with "when" option.'
|
||||
)
|
||||
expected2 = {"comment": comm2, "changes": {}, "result": False}
|
||||
|
||||
comm3 = 'Unable to use "when" and "cron" options together. Ignoring.'
|
||||
expected3 = {"comment": comm3, "changes": {}, "result": False}
|
||||
|
||||
comm4 = "Job: job1 would be modified in schedule."
|
||||
expected4 = {"comment": comm4, "changes": {"diff": diff4}, "result": True}
|
||||
|
||||
comm5 = "Job job2 does not exist in schedule."
|
||||
expected5 = {"comment": comm5, "changes": {}, "result": False}
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": job1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job1", seconds="60")
|
||||
self.assertDictEqual(ret, expected1)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify(
|
||||
"job1", function="test.ping", seconds=3600, when="2400"
|
||||
)
|
||||
self.assertDictEqual(ret, expected2)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify(
|
||||
"job1", function="test.ping", when="2400", cron="2"
|
||||
)
|
||||
self.assertDictEqual(ret, expected3)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job1", function="test.version", test=True)
|
||||
self.assertDictEqual(ret, expected4)
|
||||
|
||||
_ret_value = {"complete": True, "schedule": {}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.modify("job2", function="test.version", test=True)
|
||||
self.assertDictEqual(ret, expected5)
|
||||
|
||||
# 'is_enabled' function tests: 1
|
||||
|
||||
def test_is_enabled(self):
|
||||
"""
|
||||
Test is_enabled
|
||||
"""
|
||||
job1 = {"function": "salt", "seconds": 3600}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
|
||||
mock_schedule = {"enabled": True, "job1": job1}
|
||||
|
||||
mock_lst = MagicMock(return_value=mock_schedule)
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": job1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(
|
||||
schedule.__salt__, {"event.fire": mock, "schedule.list": mock_lst}
|
||||
):
|
||||
_ret_value = {"complete": True, "schedule": {"job1": job1}}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.is_enabled("job1")
|
||||
self.assertDictEqual(ret, job1)
|
||||
|
||||
ret = schedule.is_enabled()
|
||||
self.assertEqual(ret, True)
|
||||
|
||||
# 'job_status' function tests: 1
|
||||
|
||||
def test_job_status(self):
|
||||
"""
|
||||
Test is_enabled
|
||||
"""
|
||||
job1 = {"function": "salt", "seconds": 3600}
|
||||
|
||||
comm1 = "Modified job: job1 in schedule."
|
||||
|
||||
mock_schedule = {"enabled": True, "job1": job1}
|
||||
|
||||
mock_lst = MagicMock(return_value=mock_schedule)
|
||||
|
||||
with patch.dict(
|
||||
schedule.__opts__, {"schedule": {"job1": job1}, "sock_dir": self.sock_dir}
|
||||
):
|
||||
mock = MagicMock(return_value=True)
|
||||
with patch.dict(schedule.__salt__, {"event.fire": mock}):
|
||||
_ret_value = {"complete": True, "data": job1}
|
||||
with patch.object(SaltEvent, "get_event", return_value=_ret_value):
|
||||
ret = schedule.job_status("job1")
|
||||
self.assertDictEqual(ret, job1)
|
|
@ -192,14 +192,6 @@ class BadTestModuleNamesTestCase(TestCase):
|
|||
"unit.test_simple",
|
||||
"unit.test_virtualname",
|
||||
"unit.test_zypp_plugins",
|
||||
"unit.utils.scheduler.test_error",
|
||||
"unit.utils.scheduler.test_eval",
|
||||
"unit.utils.scheduler.test_helpers",
|
||||
"unit.utils.scheduler.test_maxrunning",
|
||||
"unit.utils.scheduler.test_postpone",
|
||||
"unit.utils.scheduler.test_run_job",
|
||||
"unit.utils.scheduler.test_schedule",
|
||||
"unit.utils.scheduler.test_skip",
|
||||
"unit.auth.test_auth",
|
||||
)
|
||||
errors = []
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -1,76 +0,0 @@
|
|||
"""
|
||||
tests.unit.utils.scheduler.base
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
|
||||
import salt.utils.platform
|
||||
import salt.utils.schedule
|
||||
from salt.modules.test import ping
|
||||
from salt.utils.process import SubprocessList
|
||||
from saltfactories.utils.processes import terminate_process
|
||||
from tests.support.mixins import SaltReturnAssertsMixin
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SchedulerTestsBase(TestCase, SaltReturnAssertsMixin):
|
||||
"""
|
||||
Validate the pkg module
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
root_dir = os.path.join(RUNTIME_VARS.TMP, "schedule-unit-tests")
|
||||
sock_dir = os.path.join(root_dir, "test-socks")
|
||||
|
||||
default_config = salt.config.minion_config(None)
|
||||
default_config["conf_dir"] = root_dir
|
||||
default_config["root_dir"] = root_dir
|
||||
default_config["sock_dir"] = sock_dir
|
||||
default_config["pki_dir"] = os.path.join(root_dir, "pki")
|
||||
default_config["cachedir"] = os.path.join(root_dir, "cache")
|
||||
cls.default_config = default_config
|
||||
cls.subprocess_list = SubprocessList()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.default_config
|
||||
del cls.subprocess_list
|
||||
|
||||
def setUp(self):
|
||||
with patch("salt.utils.schedule.clean_proc_dir", MagicMock(return_value=None)):
|
||||
functions = {"test.ping": ping}
|
||||
self.schedule = salt.utils.schedule.Schedule(
|
||||
copy.deepcopy(self.default_config),
|
||||
functions,
|
||||
returners={},
|
||||
new_instance=True,
|
||||
)
|
||||
self.schedule._subprocess_list = self.subprocess_list
|
||||
|
||||
def tearDown(self):
|
||||
subprocess_list = self.subprocess_list
|
||||
processes = subprocess_list.processes
|
||||
self.schedule.reset()
|
||||
del self.schedule
|
||||
for proc in processes:
|
||||
if proc.is_alive():
|
||||
terminate_process(proc.pid, kill_children=True, slow_stop=True)
|
||||
subprocess_list.cleanup()
|
||||
processes = subprocess_list.processes
|
||||
if processes:
|
||||
for proc in processes:
|
||||
if proc.is_alive():
|
||||
terminate_process(proc.pid, kill_children=True, slow_stop=False)
|
||||
subprocess_list.cleanup()
|
||||
processes = subprocess_list.processes
|
||||
if processes:
|
||||
log.warning("Processes left running: %s", processes)
|
|
@ -1,223 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import skipIf
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
try:
|
||||
import croniter # pylint: disable=unused-import
|
||||
|
||||
HAS_CRONITER = True
|
||||
except ImportError:
|
||||
HAS_CRONITER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@skipIf(
|
||||
HAS_DATEUTIL_PARSER is False, "The 'dateutil.parser' library is not available",
|
||||
)
|
||||
class SchedulerErrorTest(SchedulerTestsBase):
|
||||
def setUp(self):
|
||||
super(SchedulerErrorTest, self).setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
self.schedule.opts["grains"]["whens"] = {"tea time": "11/29/2017 12:00pm"}
|
||||
|
||||
@skipIf(not HAS_CRONITER, "Cannot find croniter python module")
|
||||
def test_eval_cron_invalid(self):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "cron": "0 16 29 13 *"}}}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.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)):
|
||||
self.schedule.eval(now=run_time)
|
||||
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertEqual(ret["_error"], "Invalid cron string. Ignoring job job1.")
|
||||
|
||||
def test_eval_when_invalid_date(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
job = {
|
||||
"schedule": {"job1": {"function": "test.ping", "when": "13/29/2017 1:00pm"}}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertEqual(
|
||||
ret["_error"], "Invalid date string 13/29/2017 1:00pm. Ignoring job job1."
|
||||
)
|
||||
|
||||
def test_eval_whens_grain_not_dict(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
job = {"schedule": {"job1": {"function": "test.ping", "when": "tea time"}}}
|
||||
|
||||
self.schedule.opts["grains"]["whens"] = ["tea time"]
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertEqual(
|
||||
ret["_error"], 'Grain "whens" must be a dict. Ignoring job job1.'
|
||||
)
|
||||
|
||||
def test_eval_once_invalid_datestring(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {"function": "test.ping", "once": "2017-13-13T13:00:00"}
|
||||
}
|
||||
}
|
||||
run_time = dateutil.parser.parse("12/13/2017 1:00pm")
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
_expected = (
|
||||
"Date string could not be parsed: "
|
||||
"2017-13-13T13:00:00, %Y-%m-%dT%H:%M:%S. "
|
||||
"Ignoring job job1."
|
||||
)
|
||||
self.assertEqual(ret["_error"], _expected)
|
||||
|
||||
def test_eval_skip_during_range_invalid_date(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": {"start": "1:00pm", "end": "25:00pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
_expected = (
|
||||
"Invalid date string for end in " "skip_during_range. Ignoring " "job job1."
|
||||
)
|
||||
self.assertEqual(ret["_error"], _expected)
|
||||
|
||||
def test_eval_skip_during_range_end_before_start(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": {"start": "1:00pm", "end": "12:00pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
_expected = (
|
||||
"schedule.handle_func: Invalid "
|
||||
"range, end must be larger than "
|
||||
"start. Ignoring job job1."
|
||||
)
|
||||
self.assertEqual(ret["_error"], _expected)
|
||||
|
||||
def test_eval_skip_during_range_not_dict(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
and returns the right error
|
||||
"""
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"job1": {
|
||||
"function": "test.ping",
|
||||
"hours": 1,
|
||||
"skip_during_range": ["start", "1:00pm", "end", "12:00pm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 3:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
|
||||
# eval at 4:00pm to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
_expected = (
|
||||
"schedule.handle_func: Invalid, "
|
||||
"range must be specified as a "
|
||||
"dictionary. Ignoring job job1."
|
||||
)
|
||||
self.assertEqual(ret["_error"], _expected)
|
|
@ -1,921 +0,0 @@
|
|||
import datetime
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import salt.utils.platform
|
||||
import salt.utils.schedule
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import skipIf
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
|
||||
try:
|
||||
import croniter # pylint: disable=unused-import
|
||||
|
||||
HAS_CRONITER = True
|
||||
except ImportError:
|
||||
HAS_CRONITER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@skipIf(
|
||||
HAS_DATEUTIL_PARSER is False, "The 'dateutil.parser' library is not available",
|
||||
)
|
||||
@pytest.mark.windows_whitelisted
|
||||
class SchedulerEvalTest(SchedulerTestsBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
self.schedule.opts["grains"]["whens"] = {"tea time": "11/29/2017 12:00pm"}
|
||||
|
||||
def tearDown(self):
|
||||
self.schedule.reset()
|
||||
super().tearDown()
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time2)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_multiple_whens(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate run time1
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time1)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# Evaluate run time2
|
||||
self.schedule.eval(now=run_time2)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_whens(self):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate run time1
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_loop_interval(self):
|
||||
"""
|
||||
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)
|
||||
self.schedule.opts["loop_interval"] = LOOP_INTERVAL
|
||||
|
||||
run_time2 = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL))
|
||||
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(
|
||||
ret["_last_run"], run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL)
|
||||
)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_multiple_whens_loop_interval(self):
|
||||
"""
|
||||
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)
|
||||
self.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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time1)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time2)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_once(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts["schedule"] = {}
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_once_loop_interval(self):
|
||||
"""
|
||||
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)
|
||||
self.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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate at the run time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@skipIf(not HAS_CRONITER, "Cannot find croniter python module")
|
||||
def test_eval_cron(self):
|
||||
"""
|
||||
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
|
||||
self.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)):
|
||||
self.schedule.eval(now=run_time)
|
||||
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@skipIf(not HAS_CRONITER, "Cannot find croniter python module")
|
||||
def test_eval_cron_loop_interval(self):
|
||||
"""
|
||||
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)
|
||||
self.schedule.opts["loop_interval"] = LOOP_INTERVAL
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.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)):
|
||||
self.schedule.eval(now=run_time)
|
||||
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_until(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.delete_job("test_eval_until")
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 2:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 3:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# eval at 4:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# eval at 5:00pm, will not run
|
||||
run_time = dateutil.parser.parse("11/29/2017 5:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_skip_reason"], "until_passed")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_after(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 2:00pm to prime, simulate minion start up.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 3:00pm, will not run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_skip_reason"], "after_not_passed")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 4:00pm, will not run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_skip_reason"], "after_not_passed")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 5:00pm, will not run
|
||||
run_time = dateutil.parser.parse("11/29/2017 5:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_skip_reason"], "after_not_passed")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 6:00pm, will run
|
||||
run_time = dateutil.parser.parse("11/29/2017 6:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_enabled(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time1)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_enabled_key(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second before the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status("test_eval_enabled_key")
|
||||
self.assertNotIn("_last_run", ret)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time2)
|
||||
ret = self.schedule.job_status("test_eval_enabled_key")
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
|
||||
def test_eval_disabled(self):
|
||||
"""
|
||||
verify that scheduled job does not run
|
||||
"""
|
||||
job_name = "test_eval_disabled"
|
||||
job = {
|
||||
"schedule": {
|
||||
"enabled": False,
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "disabled")
|
||||
|
||||
# Ensure job data still matches
|
||||
self.assertEqual(ret, job["schedule"][job_name])
|
||||
|
||||
def test_eval_global_disabled_job_enabled(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate 1 second at the run time
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "disabled")
|
||||
|
||||
# Ensure job is still enabled
|
||||
self.assertEqual(ret["enabled"], True)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_run_on_start(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 2:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
# eval at 3:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay(self):
|
||||
"""
|
||||
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
|
||||
self.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")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:00:40pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:40pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay_range(self):
|
||||
"""
|
||||
verify that scheduled job runs with splayed time
|
||||
"""
|
||||
job_name = "job_eval_splay_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"seconds": "30",
|
||||
"splay": {"start": 5, "end": 10},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
self.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")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:00:40pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:40pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_splay_global(self):
|
||||
"""
|
||||
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
|
||||
self.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")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:00:40pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:40pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_seconds(self):
|
||||
"""
|
||||
verify that scheduled job run mutiple times with seconds
|
||||
"""
|
||||
with patch.dict(self.schedule.opts, {"run_schedule_jobs_in_background": False}):
|
||||
job_name = "job_eval_seconds"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "seconds": "30"}}}
|
||||
|
||||
if salt.utils.platform.is_darwin():
|
||||
job["schedule"][job_name]["dry_run"] = True
|
||||
|
||||
# Add job to schedule
|
||||
self.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(seconds=30)
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(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 = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
# eval at 2:00:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:30pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
# eval at 2:01:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:01:00pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
# eval at 2:01:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:01:30pm")
|
||||
next_run_time = run_time + datetime.timedelta(seconds=30)
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_minutes(self):
|
||||
"""
|
||||
verify that scheduled job run mutiple times with minutes
|
||||
"""
|
||||
with patch.dict(self.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
|
||||
self.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 = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(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 = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(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 = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
# eval at 3:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00:00pm")
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
# eval at 3:30:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:30:00pm")
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_hours(self):
|
||||
"""
|
||||
verify that scheduled job run mutiple times with hours
|
||||
"""
|
||||
with patch.dict(self.schedule.opts, {"run_schedule_jobs_in_background": False}):
|
||||
job_name = "job_eval_hours"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "hours": "2"}}}
|
||||
|
||||
if salt.utils.platform.is_darwin():
|
||||
job["schedule"][job_name]["dry_run"] = True
|
||||
|
||||
# Add job to schedule
|
||||
self.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(hours=2)
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(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 = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 0
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
# eval at 4:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00:00pm")
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
# eval at 6:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 6:00:00pm")
|
||||
jids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
# eval at 8:00:00pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 8:00:00pm")
|
||||
pids = self.schedule.eval(now=run_time)
|
||||
assert len(jids) == 1
|
||||
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_days(self):
|
||||
"""
|
||||
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
|
||||
self.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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], last_run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], last_run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 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)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
self.assertEqual(ret["_next_fire_time"], next_run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_eval_when_splay(self):
|
||||
"""
|
||||
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
|
||||
self.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")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# Evaluate at expected runtime1, should not run
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
|
||||
# Evaluate at expected runtime2, should run
|
||||
self.schedule.eval(now=run_time2)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
|
||||
# Evaluate at expected runtime3, should not run
|
||||
# _next_fire_time should be None
|
||||
self.schedule.eval(now=run_time3)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time2)
|
||||
self.assertEqual(ret["_next_fire_time"], None)
|
||||
|
||||
def test_eval_when_splay_in_past(self):
|
||||
"""
|
||||
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
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Evaluate to prime
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# Evaluate at expected runtime1, should not run
|
||||
# and _next_fire_time should be None
|
||||
self.schedule.eval(now=run_time1)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_next_fire_time"], None)
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SchedulerHelpersTest(SchedulerTestsBase):
|
||||
"""
|
||||
Test scheduler helper functions
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SchedulerHelpersTest, self).setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
def test_get_schedule(self):
|
||||
"""
|
||||
verify that the _get_schedule function works
|
||||
when remove_hidden is True and schedule data
|
||||
contains enabled key
|
||||
"""
|
||||
job_name = "test_get_schedule"
|
||||
job = {
|
||||
"schedule": {
|
||||
"enabled": True,
|
||||
job_name: {"function": "test.ping", "seconds": 60},
|
||||
}
|
||||
}
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
ret = self.schedule._get_schedule(remove_hidden=True)
|
||||
self.assertEqual(job["schedule"], ret)
|
|
@ -1,130 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
import dateutil.parser as dateutil_parser
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SchedulerMaxRunningTest(SchedulerTestsBase):
|
||||
"""
|
||||
Validate the pkg module
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SchedulerMaxRunningTest, self).setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
def test_maxrunning_minion(self):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
self.schedule.opts["__role"] = "minion"
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"maxrunning_minion": {
|
||||
"function": "test.ping",
|
||||
"seconds": 10,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_data = {
|
||||
"function": "test.ping",
|
||||
"run": True,
|
||||
"name": "maxrunning_minion",
|
||||
"seconds": 10,
|
||||
"_seconds": 10,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
running_data = [
|
||||
{
|
||||
"fun_args": [],
|
||||
"jid": "20181018165923360935",
|
||||
"schedule": "maxrunning_minion",
|
||||
"pid": 15338,
|
||||
"fun": "test.ping",
|
||||
"id": "host",
|
||||
}
|
||||
]
|
||||
|
||||
run_time = dateutil_parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
with patch("salt.utils.minion.running", MagicMock(return_value=running_data)):
|
||||
with patch(
|
||||
"salt.utils.process.os_is_running", MagicMock(return_value=True)
|
||||
):
|
||||
ret = self.schedule._check_max_running(
|
||||
"test.ping", job_data, self.schedule.opts, now=run_time
|
||||
)
|
||||
self.assertIn("_skip_reason", ret)
|
||||
self.assertEqual("maxrunning", ret["_skip_reason"])
|
||||
self.assertEqual(False, ret["run"])
|
||||
|
||||
def test_maxrunning_master(self):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
self.schedule.opts["__role"] = "master"
|
||||
|
||||
job = {
|
||||
"schedule": {
|
||||
"maxrunning_master": {
|
||||
"function": "state.orch",
|
||||
"args": ["test.orch_test"],
|
||||
"minutes": 1,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_data = {
|
||||
"function": "state.orch",
|
||||
"fun_args": ["test.orch_test"],
|
||||
"run": True,
|
||||
"name": "maxrunning_master",
|
||||
"minutes": 1,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
}
|
||||
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
running_data = [
|
||||
{
|
||||
"fun_args": ["test.orch_test"],
|
||||
"jid": "20181018165923360935",
|
||||
"schedule": "maxrunning_master",
|
||||
"pid": 15338,
|
||||
"fun": "state.orch",
|
||||
"id": "host",
|
||||
}
|
||||
]
|
||||
|
||||
run_time = dateutil_parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
with patch(
|
||||
"salt.utils.master.get_running_jobs", MagicMock(return_value=running_data)
|
||||
):
|
||||
with patch(
|
||||
"salt.utils.process.os_is_running", MagicMock(return_value=True)
|
||||
):
|
||||
ret = self.schedule._check_max_running(
|
||||
"state.orch", job_data, self.schedule.opts, now=run_time
|
||||
)
|
||||
self.assertIn("_skip_reason", ret)
|
||||
self.assertEqual("maxrunning", ret["_skip_reason"])
|
||||
self.assertEqual(False, ret["run"])
|
|
@ -1,75 +0,0 @@
|
|||
import datetime
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from tests.support.unit import skipIf
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@skipIf(
|
||||
HAS_DATEUTIL_PARSER is False, "The 'dateutil.parser' library is not available",
|
||||
)
|
||||
class SchedulerPostponeTest(SchedulerTestsBase):
|
||||
"""
|
||||
Validate the pkg module
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
def tearDown(self):
|
||||
self.schedule.reset()
|
||||
super().tearDown()
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_postpone(self):
|
||||
"""
|
||||
verify that scheduled job is postponed until the specified time.
|
||||
"""
|
||||
job = {
|
||||
"schedule": {"job1": {"function": "test.ping", "when": "11/29/2017 4pm"}}
|
||||
}
|
||||
|
||||
# 11/29/2017 4pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
|
||||
# 5 minute delay
|
||||
delay = 300
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Postpone the job by 5 minutes
|
||||
self.schedule.postpone_job(
|
||||
"job1",
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"new_time": (run_time + datetime.timedelta(seconds=delay)).strftime(
|
||||
"%Y-%m-%dT%H:%M:%S"
|
||||
),
|
||||
},
|
||||
)
|
||||
# Run at the original time
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertNotIn("_last_run", ret)
|
||||
|
||||
# Run 5 minutes later
|
||||
self.schedule.eval(now=run_time + datetime.timedelta(seconds=delay))
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertEqual(ret["_last_run"], run_time + datetime.timedelta(seconds=delay))
|
||||
|
||||
# Run 6 minutes later
|
||||
self.schedule.eval(now=run_time + datetime.timedelta(seconds=delay + 1))
|
||||
ret = self.schedule.job_status("job1")
|
||||
self.assertEqual(ret["_last_run"], run_time + datetime.timedelta(seconds=delay))
|
|
@ -1,34 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SchedulerRunJobTest(SchedulerTestsBase):
|
||||
"""
|
||||
Validate the pkg module
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SchedulerRunJobTest, self).setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
def test_run_job(self):
|
||||
"""
|
||||
verify that scheduled job runs
|
||||
"""
|
||||
job_name = "test_run_job"
|
||||
job = {"schedule": {job_name: {"function": "test.ping"}}}
|
||||
# Add the job to the scheduler
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# Run job
|
||||
self.schedule.run_job(job_name)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
expected = {"function": "test.ping", "run": True, "name": "test_run_job"}
|
||||
self.assertEqual(ret, expected)
|
|
@ -1,495 +0,0 @@
|
|||
"""
|
||||
:codeauthor: Nicole Thomas <nicole@saltstack.com>
|
||||
"""
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
import salt.config
|
||||
import salt.utils.schedule
|
||||
from salt.utils.schedule import Schedule
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.unit import skipIf
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
# pylint: disable=import-error,unused-import
|
||||
try:
|
||||
import croniter
|
||||
|
||||
_CRON_SUPPORTED = True
|
||||
except ImportError:
|
||||
_CRON_SUPPORTED = False
|
||||
# pylint: enable=import-error
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods,invalid-name
|
||||
class ScheduleTestCase(SchedulerTestsBase):
|
||||
"""
|
||||
Unit tests for salt.utils.schedule module
|
||||
"""
|
||||
|
||||
# delete_job tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_exists(self):
|
||||
"""
|
||||
Tests ensuring the job exists and deleting it
|
||||
"""
|
||||
self.schedule.opts.update({"schedule": {"foo": "bar"}, "pillar": {}})
|
||||
self.assertIn("foo", self.schedule.opts["schedule"])
|
||||
self.schedule.delete_job("foo")
|
||||
self.assertNotIn("foo", self.schedule.opts["schedule"])
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_in_pillar(self):
|
||||
"""
|
||||
Tests ignoring deletion job from pillar
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{"pillar": {"schedule": {"foo": "bar"}}, "schedule": {}}
|
||||
)
|
||||
self.assertIn("foo", self.schedule.opts["pillar"]["schedule"])
|
||||
self.schedule.delete_job("foo")
|
||||
self.assertIn("foo", self.schedule.opts["pillar"]["schedule"])
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_intervals(self):
|
||||
"""
|
||||
Tests removing job from intervals
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {}, "schedule": {}})
|
||||
self.schedule.intervals = {"foo": "bar"}
|
||||
self.schedule.delete_job("foo")
|
||||
self.assertNotIn("foo", self.schedule.intervals)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_prefix(self):
|
||||
"""
|
||||
Tests ensuring jobs exists and deleting them by prefix
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{
|
||||
"schedule": {"foobar": "bar", "foobaz": "baz", "fooboo": "boo"},
|
||||
"pillar": {},
|
||||
}
|
||||
)
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
del ret["schedule"]["foobar"]
|
||||
del ret["schedule"]["foobaz"]
|
||||
self.schedule.delete_job_prefix("fooba")
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_delete_job_prefix_in_pillar(self):
|
||||
"""
|
||||
Tests ignoring deletion jobs by prefix from pillar
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{
|
||||
"pillar": {
|
||||
"schedule": {"foobar": "bar", "foobaz": "baz", "fooboo": "boo"}
|
||||
},
|
||||
"schedule": {},
|
||||
}
|
||||
)
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
self.schedule.delete_job_prefix("fooba")
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
# add_job tests
|
||||
|
||||
def test_add_job_data_not_dict(self):
|
||||
"""
|
||||
Tests if data is a dictionary
|
||||
"""
|
||||
data = "foo"
|
||||
self.assertRaises(ValueError, Schedule.add_job, self.schedule, data)
|
||||
|
||||
def test_add_job_multiple_jobs(self):
|
||||
"""
|
||||
Tests if more than one job is scheduled at a time
|
||||
"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
self.assertRaises(ValueError, Schedule.add_job, self.schedule, data)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_add_job(self):
|
||||
"""
|
||||
Tests adding a job to the schedule
|
||||
"""
|
||||
data = {"foo": {"bar": "baz"}}
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret.update(
|
||||
{
|
||||
"schedule": {
|
||||
"foo": {"bar": "baz", "enabled": True},
|
||||
"hello": {"world": "peace", "enabled": True},
|
||||
},
|
||||
"pillar": {},
|
||||
}
|
||||
)
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {"hello": {"world": "peace", "enabled": True}}, "pillar": {}}
|
||||
)
|
||||
Schedule.add_job(self.schedule, data)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
# enable_job tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job(self):
|
||||
"""
|
||||
Tests enabling a job
|
||||
"""
|
||||
self.schedule.opts.update({"schedule": {"name": {"enabled": "foo"}}})
|
||||
Schedule.enable_job(self.schedule, "name")
|
||||
self.assertTrue(self.schedule.opts["schedule"]["name"]["enabled"])
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_job_pillar(self):
|
||||
"""
|
||||
Tests ignoring enable a job from pillar
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{"pillar": {"schedule": {"name": {"enabled": False}}}}
|
||||
)
|
||||
Schedule.enable_job(self.schedule, "name", persist=False)
|
||||
self.assertFalse(self.schedule.opts["pillar"]["schedule"]["name"]["enabled"])
|
||||
|
||||
# disable_job tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job(self):
|
||||
"""
|
||||
Tests disabling a job
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {"name": {"enabled": "foo"}}, "pillar": {}}
|
||||
)
|
||||
Schedule.disable_job(self.schedule, "name")
|
||||
self.assertFalse(self.schedule.opts["schedule"]["name"]["enabled"])
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_job_pillar(self):
|
||||
"""
|
||||
Tests ignoring disable a job in pillar
|
||||
"""
|
||||
self.schedule.opts.update(
|
||||
{"pillar": {"schedule": {"name": {"enabled": True}}}, "schedule": {}}
|
||||
)
|
||||
Schedule.disable_job(self.schedule, "name", persist=False)
|
||||
self.assertTrue(self.schedule.opts["pillar"]["schedule"]["name"]["enabled"])
|
||||
|
||||
# modify_job tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_modify_job(self):
|
||||
"""
|
||||
Tests modifying a job in the scheduler
|
||||
"""
|
||||
schedule = {"foo": "bar"}
|
||||
self.schedule.opts.update({"schedule": {"name": "baz"}, "pillar": {}})
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret.update({"schedule": {"name": {"foo": "bar"}}})
|
||||
Schedule.modify_job(self.schedule, "name", schedule)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
def test_modify_job_not_exists(self):
|
||||
"""
|
||||
Tests modifying a job in the scheduler if jobs not exists
|
||||
"""
|
||||
schedule = {"foo": "bar"}
|
||||
self.schedule.opts.update({"schedule": {}, "pillar": {}})
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret.update({"schedule": {"name": {"foo": "bar"}}})
|
||||
Schedule.modify_job(self.schedule, "name", schedule)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
def test_modify_job_pillar(self):
|
||||
"""
|
||||
Tests ignoring modification of job from pillar
|
||||
"""
|
||||
schedule = {"foo": "bar"}
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {}, "pillar": {"schedule": {"name": "baz"}}}
|
||||
)
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
Schedule.modify_job(self.schedule, "name", schedule, persist=False)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
maxDiff = None
|
||||
|
||||
# enable_schedule tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_enable_schedule(self):
|
||||
"""
|
||||
Tests enabling the scheduler
|
||||
"""
|
||||
with patch(
|
||||
"salt.utils.schedule.Schedule.persist", MagicMock(return_value=None)
|
||||
) as persist_mock:
|
||||
self.schedule.opts.update({"schedule": {"enabled": "foo"}, "pillar": {}})
|
||||
Schedule.enable_schedule(self.schedule)
|
||||
self.assertTrue(self.schedule.opts["schedule"]["enabled"])
|
||||
|
||||
persist_mock.assert_called()
|
||||
|
||||
# disable_schedule tests
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_disable_schedule(self):
|
||||
"""
|
||||
Tests disabling the scheduler
|
||||
"""
|
||||
with patch(
|
||||
"salt.utils.schedule.Schedule.persist", MagicMock(return_value=None)
|
||||
) as persist_mock:
|
||||
self.schedule.opts.update({"schedule": {"enabled": "foo"}, "pillar": {}})
|
||||
Schedule.disable_schedule(self.schedule)
|
||||
self.assertFalse(self.schedule.opts["schedule"]["enabled"])
|
||||
|
||||
persist_mock.assert_called()
|
||||
|
||||
# reload tests
|
||||
|
||||
def test_reload_update_schedule_key(self):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule where both the
|
||||
saved schedule and self.schedule.opts contain a schedule key
|
||||
"""
|
||||
saved = {"schedule": {"foo": "bar"}}
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret.update({"schedule": {"foo": "bar", "hello": "world"}})
|
||||
self.schedule.opts.update({"schedule": {"hello": "world"}})
|
||||
Schedule.reload(self.schedule, saved)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
def test_reload_update_schedule_no_key(self):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that does not
|
||||
contain a schedule key but self.schedule.opts does
|
||||
"""
|
||||
saved = {"foo": "bar"}
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret.update({"schedule": {"foo": "bar", "hello": "world"}})
|
||||
self.schedule.opts.update({"schedule": {"hello": "world"}})
|
||||
Schedule.reload(self.schedule, saved)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
def test_reload_no_schedule_in_opts(self):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that does not
|
||||
contain a schedule key and neither does self.schedule.opts
|
||||
"""
|
||||
saved = {"foo": "bar"}
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret["schedule"] = {"foo": "bar"}
|
||||
self.schedule.opts.pop("schedule", None)
|
||||
Schedule.reload(self.schedule, saved)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
def test_reload_schedule_in_saved_but_not_opts(self):
|
||||
"""
|
||||
Tests reloading the schedule from saved schedule that contains
|
||||
a schedule key, but self.schedule.opts does not
|
||||
"""
|
||||
saved = {"schedule": {"foo": "bar"}}
|
||||
ret = copy.deepcopy(self.schedule.opts)
|
||||
ret["schedule"] = {"foo": "bar"}
|
||||
self.schedule.opts.pop("schedule", None)
|
||||
Schedule.reload(self.schedule, saved)
|
||||
self.assertEqual(self.schedule.opts, ret)
|
||||
|
||||
# eval tests
|
||||
|
||||
def test_eval_schedule_is_not_dict(self):
|
||||
"""
|
||||
Tests eval if the schedule is not a dictionary
|
||||
"""
|
||||
self.schedule.opts.update({"schedule": "", "pillar": {"schedule": {}}})
|
||||
self.assertRaises(ValueError, Schedule.eval, self.schedule)
|
||||
|
||||
def test_eval_schedule_is_not_dict_in_pillar(self):
|
||||
"""
|
||||
Tests eval if the schedule from pillar is not a dictionary
|
||||
"""
|
||||
self.schedule.opts.update({"schedule": {}, "pillar": {"schedule": ""}})
|
||||
self.assertRaises(ValueError, Schedule.eval, self.schedule)
|
||||
|
||||
def test_eval_schedule_time(self):
|
||||
"""
|
||||
Tests eval if the schedule setting time is in the future
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {"testjob": {"function": "test.true", "seconds": 60}}}
|
||||
)
|
||||
now = datetime.datetime.now()
|
||||
self.schedule.eval()
|
||||
self.assertTrue(
|
||||
self.schedule.opts["schedule"]["testjob"]["_next_fire_time"] > now
|
||||
)
|
||||
|
||||
def test_eval_schedule_time_eval(self):
|
||||
"""
|
||||
Tests eval if the schedule setting time is in the future plus splay
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
self.schedule.opts.update(
|
||||
{
|
||||
"schedule": {
|
||||
"testjob": {"function": "test.true", "seconds": 60, "splay": 5}
|
||||
}
|
||||
}
|
||||
)
|
||||
now = datetime.datetime.now()
|
||||
self.schedule.eval()
|
||||
self.assertTrue(
|
||||
self.schedule.opts["schedule"]["testjob"]["_splay"] - now
|
||||
> datetime.timedelta(seconds=60)
|
||||
)
|
||||
|
||||
@skipIf(not _CRON_SUPPORTED, "croniter module not installed")
|
||||
def test_eval_schedule_cron(self):
|
||||
"""
|
||||
Tests eval if the schedule is defined with cron expression
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {"testjob": {"function": "test.true", "cron": "* * * * *"}}}
|
||||
)
|
||||
now = datetime.datetime.now()
|
||||
self.schedule.eval()
|
||||
self.assertTrue(
|
||||
self.schedule.opts["schedule"]["testjob"]["_next_fire_time"] > now
|
||||
)
|
||||
|
||||
@skipIf(not _CRON_SUPPORTED, "croniter module not installed")
|
||||
def test_eval_schedule_cron_splay(self):
|
||||
"""
|
||||
Tests eval if the schedule is defined with cron expression plus splay
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
self.schedule.opts.update(
|
||||
{
|
||||
"schedule": {
|
||||
"testjob": {
|
||||
"function": "test.true",
|
||||
"cron": "* * * * *",
|
||||
"splay": 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
self.schedule.eval()
|
||||
self.assertTrue(
|
||||
self.schedule.opts["schedule"]["testjob"]["_splay"]
|
||||
> self.schedule.opts["schedule"]["testjob"]["_next_fire_time"]
|
||||
)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_handle_func_schedule_minion_blackout(self):
|
||||
"""
|
||||
Tests eval if the schedule from pillar is not a dictionary
|
||||
"""
|
||||
self.schedule.opts.update({"pillar": {"schedule": {}}})
|
||||
self.schedule.opts.update({"grains": {"minion_blackout": True}})
|
||||
|
||||
self.schedule.opts.update(
|
||||
{"schedule": {"testjob": {"function": "test.true", "seconds": 60}}}
|
||||
)
|
||||
data = {
|
||||
"function": "test.true",
|
||||
"_next_scheduled_fire_time": datetime.datetime(
|
||||
2018, 11, 21, 14, 9, 53, 903438
|
||||
),
|
||||
"run": True,
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch.object(salt.utils.schedule, "log") as log_mock:
|
||||
with patch("salt.utils.process.daemonize"), patch("sys.platform", "linux2"):
|
||||
self.schedule.handle_func(False, "test.ping", data)
|
||||
self.assertTrue(log_mock.exception.called)
|
||||
|
||||
def test_handle_func_check_data(self):
|
||||
"""
|
||||
Tests handle_func to ensure that __pub_fun_args is not
|
||||
being duplicated in the value of kwargs in data.
|
||||
"""
|
||||
|
||||
data = {
|
||||
"function": "test.arg",
|
||||
"_next_scheduled_fire_time": datetime.datetime(
|
||||
2018, 11, 21, 14, 9, 53, 903438
|
||||
),
|
||||
"run": True,
|
||||
"args": ["arg1", "arg2"],
|
||||
"kwargs": {"key1": "value1", "key2": "value2"},
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch("salt.utils.process.daemonize"), patch("sys.platform", "linux2"):
|
||||
with patch.object(self.schedule, "standalone", return_value=True):
|
||||
# run handle_func once
|
||||
self.schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
# run handle_func and ensure __pub_fun_args
|
||||
# is not in kwargs
|
||||
self.schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
self.assertIn("kwargs", data)
|
||||
self.assertNotIn("__pub_fun_args", data["kwargs"])
|
||||
|
||||
# @skipIf(not salt.utils.platform.is_windows(), "Skip on Non-Windows systems")
|
||||
def test_handle_func_check_dicts(self):
|
||||
"""
|
||||
Tests that utils, functions, and returners dicts are not
|
||||
empty after handle_func has run on Windows.
|
||||
"""
|
||||
|
||||
data = {
|
||||
"function": "test.arg",
|
||||
"_next_scheduled_fire_time": datetime.datetime(
|
||||
2018, 11, 21, 14, 9, 53, 903438
|
||||
),
|
||||
"run": True,
|
||||
"args": ["arg1", "arg2"],
|
||||
"kwargs": {"key1": "value1", "key2": "value2"},
|
||||
"name": "testjob",
|
||||
"seconds": 60,
|
||||
"_splay": None,
|
||||
"_seconds": 60,
|
||||
"jid_include": True,
|
||||
"maxrunning": 1,
|
||||
"_next_fire_time": datetime.datetime(2018, 11, 21, 14, 8, 53, 903438),
|
||||
}
|
||||
|
||||
with patch("salt.utils.process.daemonize"):
|
||||
with patch.object(self.schedule, "standalone", return_value=True):
|
||||
# simulate what happens before handle_func is called on Windows
|
||||
self.schedule.functions = {}
|
||||
self.schedule.returners = {}
|
||||
self.schedule.utils = {}
|
||||
self.schedule.handle_func(False, "test.arg", data)
|
||||
|
||||
self.assertNotEqual({}, self.schedule.functions)
|
||||
self.assertNotEqual({}, self.schedule.returners)
|
||||
self.assertNotEqual({}, self.schedule.utils)
|
|
@ -1,288 +0,0 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
from tests.support.unit import skipIf
|
||||
from tests.unit.utils.scheduler.base import SchedulerTestsBase
|
||||
|
||||
try:
|
||||
import dateutil.parser
|
||||
|
||||
HAS_DATEUTIL_PARSER = True
|
||||
except ImportError:
|
||||
HAS_DATEUTIL_PARSER = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@skipIf(
|
||||
HAS_DATEUTIL_PARSER is False, "The 'dateutil.parser' library is not available",
|
||||
)
|
||||
class SchedulerSkipTest(SchedulerTestsBase):
|
||||
"""
|
||||
Validate the pkg module
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.schedule.opts["loop_interval"] = 1
|
||||
|
||||
def tearDown(self):
|
||||
self.schedule.reset()
|
||||
super().tearDown()
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip(self):
|
||||
"""
|
||||
verify that scheduled job is skipped at the specified time
|
||||
"""
|
||||
job_name = "test_skip"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"when": ["11/29/2017 4pm", "11/29/2017 5pm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
run_time = dateutil.parser.parse("11/29/2017 4:00pm")
|
||||
self.schedule.skip_job(
|
||||
job_name,
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"time_fmt": "%Y-%m-%dT%H:%M:%S",
|
||||
},
|
||||
)
|
||||
|
||||
# Run 11/29/2017 at 4pm
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "skip_explicit")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# Run 11/29/2017 at 5pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 5:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip_during_range(self):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "test_skip_during_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2pm",
|
||||
"end": "11/29/2017 3pm",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 1:30pm to prime.
|
||||
run_time = dateutil.parser.parse("11/29/2017 1:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "in_skip_range")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 3:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
def test_skip_during_range_invalid_datestring(self):
|
||||
"""
|
||||
verify that scheduled job is not not and returns the right error string
|
||||
"""
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
|
||||
job_name1 = "skip_during_range_invalid_datestring1"
|
||||
job1 = {
|
||||
"schedule": {
|
||||
job_name1: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"_next_fire_time": run_time,
|
||||
"skip_during_range": {"start": "25pm", "end": "3pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job_name2 = "skip_during_range_invalid_datestring2"
|
||||
job2 = {
|
||||
"schedule": {
|
||||
job_name2: {
|
||||
"function": "test.ping",
|
||||
"hours": "1",
|
||||
"_next_fire_time": run_time,
|
||||
"skip_during_range": {"start": "2pm", "end": "25pm"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job1 to schedule
|
||||
self.schedule.opts.update(job1)
|
||||
|
||||
# Eval
|
||||
self.schedule.eval(now=run_time)
|
||||
|
||||
# Check the first job
|
||||
ret = self.schedule.job_status(job_name1)
|
||||
_expected = (
|
||||
"Invalid date string for start in " "skip_during_range. Ignoring " "job {}."
|
||||
).format(job_name1)
|
||||
self.assertEqual(ret["_error"], _expected)
|
||||
|
||||
# Clear out schedule
|
||||
self.schedule.opts["schedule"] = {}
|
||||
|
||||
# Add job2 to schedule
|
||||
self.schedule.opts.update(job2)
|
||||
|
||||
# Eval
|
||||
self.schedule.eval(now=run_time)
|
||||
|
||||
# Check the second job
|
||||
ret = self.schedule.job_status(job_name2)
|
||||
_expected = (
|
||||
"Invalid date string for end in " "skip_during_range. Ignoring " "job {}."
|
||||
).format(job_name2)
|
||||
self.assertEqual(ret["_error"], _expected)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_skip_during_range_global(self):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "skip_during_range_global"
|
||||
job = {
|
||||
"schedule": {
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2:00pm",
|
||||
"end": "11/29/2017 3:00pm",
|
||||
},
|
||||
job_name: {"function": "test.ping", "hours": "1"},
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 1:30pm to prime.
|
||||
run_time = dateutil.parser.parse("11/29/2017 1:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "in_skip_range")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 3:30pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_after_skip_range(self):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "skip_run_after_skip_range"
|
||||
job = {
|
||||
"schedule": {
|
||||
job_name: {
|
||||
"function": "test.ping",
|
||||
"when": "11/29/2017 2:30pm",
|
||||
"run_after_skip_range": True,
|
||||
"skip_during_range": {
|
||||
"start": "11/29/2017 2pm",
|
||||
"end": "11/29/2017 3pm",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 2:30pm, will not run during range.
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertNotIn("_last_run", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "in_skip_range")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# eval at 3:00:01pm, will run.
|
||||
run_time = dateutil.parser.parse("11/29/2017 3:00:01pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertEqual(ret["_last_run"], run_time)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_run_seconds_skip(self):
|
||||
"""
|
||||
verify that scheduled job is skipped during the specified range
|
||||
"""
|
||||
job_name = "run_seconds_skip"
|
||||
job = {"schedule": {job_name: {"function": "test.ping", "seconds": "10"}}}
|
||||
|
||||
# Add job to schedule
|
||||
self.schedule.opts.update(job)
|
||||
|
||||
# eval at 2:00pm, to prime the scheduler
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# eval at 2:00:10pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:10pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
|
||||
# Skip at 2:00:20pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:20pm")
|
||||
self.schedule.skip_job(
|
||||
job_name,
|
||||
{
|
||||
"time": run_time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
"time_fmt": "%Y-%m-%dT%H:%M:%S",
|
||||
},
|
||||
)
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertIn("_next_fire_time", ret)
|
||||
self.assertEqual(ret["_skip_reason"], "skip_explicit")
|
||||
self.assertEqual(ret["_skipped_time"], run_time)
|
||||
|
||||
# Run at 2:00:30pm
|
||||
run_time = dateutil.parser.parse("11/29/2017 2:00:30pm")
|
||||
self.schedule.eval(now=run_time)
|
||||
ret = self.schedule.job_status(job_name)
|
||||
self.assertIn("_last_run", ret)
|
Loading…
Add table
Reference in a new issue