Allow schedule state module to update schedule when the minion is offline

This commit is contained in:
Gareth J. Greenaway 2023-08-29 14:39:46 -07:00
parent 42b75547a6
commit 62ab889c71
No known key found for this signature in database
GPG key ID: 10B62F8A7CAD7A41
3 changed files with 518 additions and 26 deletions

1
changelog/65033.fixed.md Normal file
View file

@ -0,0 +1 @@
Allow schedule state module to update schedule when the minion is offline.

View file

@ -214,11 +214,15 @@ def present(name, **kwargs):
Whether the scheduled job should run immediately after the skip_during_range time
period ends.
offline
Add the scheduled job to the Salt minion when the Salt minion is not running.
"""
ret = {"name": name, "result": True, "changes": {}, "comment": []}
current_schedule = __salt__["schedule.list"](show_all=True, return_yaml=False)
current_schedule = __salt__["schedule.list"](
show_all=True, return_yaml=False, offline=kwargs.get("offline")
)
if name in current_schedule:
new_item = __salt__["schedule.build_schedule_item"](name, **kwargs)
@ -289,7 +293,9 @@ def absent(name, **kwargs):
ret = {"name": name, "result": True, "changes": {}, "comment": []}
current_schedule = __salt__["schedule.list"](show_all=True, return_yaml=False)
current_schedule = __salt__["schedule.list"](
show_all=True, return_yaml=False, offline=kwargs.get("offline")
)
if name in current_schedule:
if "test" in __opts__ and __opts__["test"]:
kwargs["test"] = True
@ -357,11 +363,15 @@ def disabled(name, **kwargs):
persist
Whether changes to the scheduled job should be saved, defaults to True.
offline
Delete the scheduled job to the Salt minion when the Salt minion is not running.
"""
ret = {"name": name, "result": True, "changes": {}, "comment": []}
current_schedule = __salt__["schedule.list"](show_all=True, return_yaml=False)
current_schedule = __salt__["schedule.list"](
show_all=True, return_yaml=False, offline=kwargs.get("offline")
)
if name in current_schedule:
if "test" in __opts__ and __opts__["test"]:
kwargs["test"] = True

View file

@ -1,11 +1,14 @@
"""
:codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
:codeauthor: Gareth J. Greenaway <ggreenaway@vmware.com>
"""
import pytest
import salt.modules.schedule as schedule_mod
import salt.states.schedule as schedule
from tests.support.mock import MagicMock, patch
from salt.utils.odict import OrderedDict
from tests.support.mock import MagicMock, mock_open, patch
@pytest.fixture
@ -19,49 +22,527 @@ def test_present():
"""
name = "job1"
ret = {"name": name, "changes": {}, "result": False, "comment": ""}
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
mock_lst = MagicMock(side_effect=[{}, {"job1": job1}])
mock_build_schedule = OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "job1"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
)
mock_add = {
"comment": "Added job: test-schedule to schedule.",
"result": True,
"changes": {"test-schedule": "added"},
}
mock_dict = MagicMock(side_effect=[ret, []])
mock_mod = MagicMock(return_value=ret)
mock_lst = MagicMock(side_effect=[{name: {}}, {name: {}}, {}, {}])
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.build_schedule_item": mock_dict,
"schedule.modify": mock_mod,
"schedule.add": mock_mod,
"schedule.build_schedule_item": MagicMock(return_value=mock_build_schedule),
"schedule.add": MagicMock(return_value=mock_add),
},
):
assert schedule.present(name) == ret
ret = {
"name": "job1",
"result": True,
"changes": {"test-schedule": "added"},
"comment": "Adding new job job1 to schedule",
}
_res = schedule.present(name)
assert _res == ret
with patch.dict(schedule.__opts__, {"test": False}):
assert schedule.present(name) == ret
ret = {
"name": "job1",
"result": True,
"changes": {},
"comment": "Job job1 in correct state",
}
_res = schedule.present(name)
assert _res == ret
assert schedule.present(name) == ret
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
job1_update = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "6:00am",
}
mock_lst = MagicMock(side_effect=[{"job1": job1}, {"job1": job1_update}])
mock_build_schedule = OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "job1"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
)
mock_modify = {
"comment": "Modified job: test-schedule in schedule.",
"changes": {
"test-schedule": {
"old": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
),
"new": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
),
}
},
"result": True,
}
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.build_schedule_item": MagicMock(return_value=mock_build_schedule),
"schedule.modify": MagicMock(return_value=mock_modify),
},
):
ret = {
"name": "job1",
"result": True,
"changes": {
"test-schedule": {
"old": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
),
"new": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
),
}
},
"comment": "Modifying job job1 in schedule",
}
_res = schedule.present(name)
assert _res == ret
ret = {
"name": "job1",
"result": True,
"changes": {},
"comment": "Job job1 in correct state",
}
_res = schedule.present(name)
assert _res == ret
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
mock_lst = MagicMock(side_effect=[{}])
mock_build_schedule = OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "job1"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
)
mock_add = {
"comment": "Job: test-schedule would be added to schedule.",
"result": True,
"changes": {},
}
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.build_schedule_item": MagicMock(return_value=mock_build_schedule),
"schedule.add": MagicMock(return_value=mock_add),
},
):
ret = {
"name": "job1",
"result": True,
"changes": {},
"comment": "Job: test-schedule would be added to schedule.",
}
with patch.dict(schedule.__opts__, {"test": True}):
ret.update({"result": True})
assert schedule.present(name) == ret
_res = schedule.present(name)
assert _res == ret
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
job1_update = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "6:00am",
}
mock_lst = MagicMock(side_effect=[{"job1": job1}, {"job1": job1_update}])
mock_build_schedule = OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "job1"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
)
mock_modify = {
"comment": "Job: test-schedule would be modified in schedule.",
"changes": {
"test-schedule": {
"old": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
),
"new": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
),
}
},
"result": True,
}
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.build_schedule_item": MagicMock(return_value=mock_build_schedule),
"schedule.modify": MagicMock(return_value=mock_modify),
},
):
ret = {
"name": "job1",
"result": True,
"changes": {
"test-schedule": {
"old": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
),
"new": OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "test-schedule"),
("enabled", True),
("jid_include", True),
("when", "6:00am"),
]
),
}
},
"comment": "Job: test-schedule would be modified in schedule.",
}
with patch.dict(schedule.__opts__, {"test": True}):
_res = schedule.present(name)
assert _res == ret
# Add job to schedule when offline=True
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
"offline": True,
}
mock_lst = MagicMock(return_value={})
mock_build_schedule = OrderedDict(
[
("function", "test.ping"),
("maxrunning", 1),
("name", "job1"),
("enabled", True),
("jid_include", True),
("when", "4:00am"),
]
)
mock_add = {
"comment": "Adding new job test-schedule to schedule.",
"result": True,
"changes": {"test-schedule": "added"},
}
event_enter = MagicMock()
event_enter.send.side_effect = (lambda data, tag, cb=None, timeout=60: True,)
event = MagicMock()
event.__enter__.return_value = event_enter
with patch("salt.utils.event.get_event", return_value=event):
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.build_schedule_item": MagicMock(
return_value=mock_build_schedule
),
"schedule.add": MagicMock(return_value=mock_add),
},
):
with patch.object(schedule_mod, "list_", mock_lst):
with patch.object(
schedule_mod,
"_get_schedule_config_file",
MagicMock(return_value="/etc/salt/minion.d/_schedule.conf"),
):
with patch("salt.utils.files.fopen", mock_open()):
ret = {
"comment": "Adding new job job1 to schedule",
"result": True,
"name": "job1",
"changes": {"test-schedule": "added"},
}
_res = schedule.present(name, offline=True)
assert _res == ret
assert event.call_count == 0
def test_absent():
"""
Test to ensure a job is absent from the schedule.
"""
# Delete job from schedule
name = "job1"
ret = {"name": name, "changes": {}, "result": False, "comment": ""}
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
mock_lst = MagicMock(side_effect=[{"job1": job1}])
mock_delete = {
"comment": "Deleted job test-schedule from schedule.",
"result": True,
"changes": {"test-schedule": "removed"},
}
mock_mod = MagicMock(return_value=ret)
mock_lst = MagicMock(side_effect=[{name: {}}, {}])
with patch.dict(
schedule.__salt__, {"schedule.list": mock_lst, "schedule.delete": mock_mod}
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.delete": MagicMock(return_value=mock_delete),
},
):
with patch.dict(schedule.__opts__, {"test": False}):
assert schedule.absent(name) == ret
ret = {
"name": "job1",
"result": True,
"changes": {"test-schedule": "removed"},
"comment": "Removed job job1 from schedule",
}
_res = schedule.absent(name)
assert _res == ret
# Delete job from schedule when job does not exist
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
mock_lst = MagicMock(side_effect=[{}])
mock_delete = {
"comment": "Job test-schedule does not exist.",
"result": True,
"changes": {},
}
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.delete": MagicMock(return_value=mock_delete),
},
):
ret = {
"name": "job1",
"result": True,
"changes": {},
"comment": "Job job1 not present in schedule",
}
_res = schedule.absent(name)
assert _res == ret
# Delete job from schedule when test=True
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
}
mock_lst = MagicMock(side_effect=[{"job1": job1}])
mock_delete = {
"comment": "Job: job1 would be deleted from schedule.",
"result": True,
"changes": {},
}
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.delete": MagicMock(return_value=mock_delete),
},
):
ret = {
"name": "job1",
"result": True,
"changes": {},
"comment": "Job: job1 would be deleted from schedule.",
}
with patch.dict(schedule.__opts__, {"test": True}):
comt = "Job job1 not present in schedule"
ret.update({"comment": comt, "result": True})
assert schedule.absent(name) == ret
_res = schedule.absent(name)
assert _res == ret
# Delete job from schedule when offline=True
job1 = {
"function": "test.ping",
"maxrunning": 1,
"name": "job1",
"enabled": True,
"jid_include": True,
"when": "4:00am",
"offline": True,
}
mock_lst = MagicMock(return_value={"job1": job1})
mock_delete = {
"comment": "Deleted Job job1 from schedule.",
"result": True,
"changes": {"job1": "removed"},
}
event_enter = MagicMock()
event_enter.send.side_effect = (lambda data, tag, cb=None, timeout=60: True,)
event = MagicMock()
event.__enter__.return_value = event_enter
with patch("salt.utils.event.get_event", return_value=event):
with patch.dict(
schedule.__salt__,
{
"schedule.list": mock_lst,
"schedule.delete": schedule_mod.delete,
},
):
with patch.object(schedule_mod, "list_", mock_lst):
with patch.object(
schedule_mod,
"_get_schedule_config_file",
MagicMock(return_value="/etc/salt/minion.d/_schedule.conf"),
):
with patch("salt.utils.files.fopen", mock_open()):
ret = {
"comment": "Removed job job1 from schedule",
"result": True,
"name": "job1",
"changes": {"job1": "removed"},
}
_res = schedule.absent(name, offline=True)
assert _res == ret
assert event.call_count == 0