3e24ae9666
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=204
821 lines
31 KiB
Diff
821 lines
31 KiB
Diff
From 7803275a8aaeedf2124706f51b6a54cfcfb2d032 Mon Sep 17 00:00:00 2001
|
|
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
|
Date: Thu, 1 Sep 2022 14:45:13 +0300
|
|
Subject: [PATCH] Fix the regression in schedule module releasded in
|
|
3004 (bsc#1202631)
|
|
|
|
Co-authored-by: Gareth J. Greenaway <gareth@saltstack.com>
|
|
---
|
|
changelog/61324.changed | 1 +
|
|
salt/modules/schedule.py | 449 ++++++++++++++------
|
|
tests/pytests/unit/modules/test_schedule.py | 138 +++++-
|
|
3 files changed, 442 insertions(+), 146 deletions(-)
|
|
create mode 100644 changelog/61324.changed
|
|
|
|
diff --git a/changelog/61324.changed b/changelog/61324.changed
|
|
new file mode 100644
|
|
index 0000000000..d67051a8da
|
|
--- /dev/null
|
|
+++ b/changelog/61324.changed
|
|
@@ -0,0 +1 @@
|
|
+Adding the ability to add, delete, purge, and modify Salt scheduler jobs when the Salt minion is not running.
|
|
diff --git a/salt/modules/schedule.py b/salt/modules/schedule.py
|
|
index bcd64f2851..913a101ea6 100644
|
|
--- a/salt/modules/schedule.py
|
|
+++ b/salt/modules/schedule.py
|
|
@@ -15,6 +15,7 @@ import salt.utils.event
|
|
import salt.utils.files
|
|
import salt.utils.odict
|
|
import salt.utils.yaml
|
|
+import yaml
|
|
|
|
try:
|
|
import dateutil.parser as dateutil_parser
|
|
@@ -64,7 +65,35 @@ SCHEDULE_CONF = [
|
|
]
|
|
|
|
|
|
-def list_(show_all=False, show_disabled=True, where=None, return_yaml=True):
|
|
+def _get_schedule_config_file():
|
|
+ """
|
|
+ Return the minion schedule configuration file
|
|
+ """
|
|
+ config_dir = __opts__.get("conf_dir", None)
|
|
+ if config_dir is None and "conf_file" in __opts__:
|
|
+ config_dir = os.path.dirname(__opts__["conf_file"])
|
|
+ if config_dir is None:
|
|
+ config_dir = salt.syspaths.CONFIG_DIR
|
|
+
|
|
+ minion_d_dir = os.path.join(
|
|
+ config_dir,
|
|
+ os.path.dirname(
|
|
+ __opts__.get(
|
|
+ "default_include",
|
|
+ salt.config.DEFAULT_MINION_OPTS["default_include"],
|
|
+ )
|
|
+ ),
|
|
+ )
|
|
+
|
|
+ if not os.path.isdir(minion_d_dir):
|
|
+ os.makedirs(minion_d_dir)
|
|
+
|
|
+ return os.path.join(minion_d_dir, "_schedule.conf")
|
|
+
|
|
+
|
|
+def list_(
|
|
+ show_all=False, show_disabled=True, where=None, return_yaml=True, offline=False
|
|
+):
|
|
"""
|
|
List the jobs currently scheduled on the minion
|
|
|
|
@@ -83,24 +112,33 @@ def list_(show_all=False, show_disabled=True, where=None, return_yaml=True):
|
|
"""
|
|
|
|
schedule = {}
|
|
- try:
|
|
- with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
- res = __salt__["event.fire"](
|
|
- {"func": "list", "where": where}, "manage_schedule"
|
|
- )
|
|
- if res:
|
|
- event_ret = event_bus.get_event(
|
|
- tag="/salt/minion/minion_schedule_list_complete", wait=30
|
|
+ if offline:
|
|
+ schedule_config = _get_schedule_config_file()
|
|
+ if os.path.exists(schedule_config):
|
|
+ with salt.utils.files.fopen(schedule_config) as fp_:
|
|
+ schedule_yaml = fp_.read()
|
|
+ if schedule_yaml:
|
|
+ schedule_contents = yaml.safe_load(schedule_yaml)
|
|
+ schedule = schedule_contents.get("schedule", {})
|
|
+ else:
|
|
+ try:
|
|
+ with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
+ res = __salt__["event.fire"](
|
|
+ {"func": "list", "where": where}, "manage_schedule"
|
|
)
|
|
- if event_ret and event_ret["complete"]:
|
|
- schedule = event_ret["schedule"]
|
|
- except KeyError:
|
|
- # Effectively a no-op, since we can't really return without an event system
|
|
- ret = {}
|
|
- ret["comment"] = "Event module not available. Schedule list failed."
|
|
- ret["result"] = True
|
|
- log.debug("Event module not available. Schedule list failed.")
|
|
- return ret
|
|
+ if res:
|
|
+ event_ret = event_bus.get_event(
|
|
+ tag="/salt/minion/minion_schedule_list_complete", wait=30
|
|
+ )
|
|
+ if event_ret and event_ret["complete"]:
|
|
+ schedule = event_ret["schedule"]
|
|
+ except KeyError:
|
|
+ # Effectively a no-op, since we can't really return without an event system
|
|
+ ret = {}
|
|
+ ret["comment"] = "Event module not available. Schedule list failed."
|
|
+ ret["result"] = True
|
|
+ log.debug("Event module not available. Schedule list failed.")
|
|
+ return ret
|
|
|
|
_hidden = ["enabled", "skip_function", "skip_during_range"]
|
|
for job in list(schedule.keys()): # iterate over a copy since we will mutate it
|
|
@@ -139,14 +177,11 @@ def list_(show_all=False, show_disabled=True, where=None, return_yaml=True):
|
|
# remove _seconds from the listing
|
|
del schedule[job]["_seconds"]
|
|
|
|
- if schedule:
|
|
- if return_yaml:
|
|
- tmp = {"schedule": schedule}
|
|
- return salt.utils.yaml.safe_dump(tmp, default_flow_style=False)
|
|
- else:
|
|
- return schedule
|
|
+ if return_yaml:
|
|
+ tmp = {"schedule": schedule}
|
|
+ return salt.utils.yaml.safe_dump(tmp, default_flow_style=False)
|
|
else:
|
|
- return {"schedule": {}}
|
|
+ return schedule
|
|
|
|
|
|
def is_enabled(name=None):
|
|
@@ -186,11 +221,18 @@ def purge(**kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' schedule.purge
|
|
+
|
|
+ # Purge jobs on Salt minion
|
|
+ salt '*' schedule.purge
|
|
+
|
|
"""
|
|
|
|
- ret = {"comment": [], "result": True}
|
|
+ ret = {"comment": [], "changes": {}, "result": True}
|
|
|
|
- for name in list_(show_all=True, return_yaml=False):
|
|
+ current_schedule = list_(
|
|
+ show_all=True, return_yaml=False, offline=kwargs.get("offline")
|
|
+ )
|
|
+ for name in pycopy.deepcopy(current_schedule):
|
|
if name == "enabled":
|
|
continue
|
|
if name.startswith("__"):
|
|
@@ -202,37 +244,65 @@ def purge(**kwargs):
|
|
"Job: {} would be deleted from schedule.".format(name)
|
|
)
|
|
else:
|
|
- persist = kwargs.get("persist", True)
|
|
+ if kwargs.get("offline"):
|
|
+ del current_schedule[name]
|
|
|
|
- try:
|
|
- with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
- res = __salt__["event.fire"](
|
|
- {"name": name, "func": "delete", "persist": persist},
|
|
- "manage_schedule",
|
|
- )
|
|
- if res:
|
|
- event_ret = event_bus.get_event(
|
|
- tag="/salt/minion/minion_schedule_delete_complete", wait=30
|
|
+ ret["comment"].append("Deleted job: {} from schedule.".format(name))
|
|
+ ret["changes"][name] = "removed"
|
|
+
|
|
+ else:
|
|
+ persist = kwargs.get("persist", True)
|
|
+ try:
|
|
+ with salt.utils.event.get_event(
|
|
+ "minion", opts=__opts__
|
|
+ ) as event_bus:
|
|
+ res = __salt__["event.fire"](
|
|
+ {"name": name, "func": "delete", "persist": persist},
|
|
+ "manage_schedule",
|
|
)
|
|
- if event_ret and event_ret["complete"]:
|
|
- _schedule_ret = event_ret["schedule"]
|
|
- if name not in _schedule_ret:
|
|
- ret["result"] = True
|
|
- ret["comment"].append(
|
|
- "Deleted job: {} from schedule.".format(name)
|
|
- )
|
|
- else:
|
|
- ret["comment"].append(
|
|
- "Failed to delete job {} from schedule.".format(
|
|
- name
|
|
+ if res:
|
|
+ event_ret = event_bus.get_event(
|
|
+ tag="/salt/minion/minion_schedule_delete_complete",
|
|
+ wait=30,
|
|
+ )
|
|
+ if event_ret and event_ret["complete"]:
|
|
+ _schedule_ret = event_ret["schedule"]
|
|
+ if name not in _schedule_ret:
|
|
+ ret["result"] = True
|
|
+ ret["changes"][name] = "removed"
|
|
+ ret["comment"].append(
|
|
+ "Deleted job: {} from schedule.".format(name)
|
|
)
|
|
- )
|
|
- ret["result"] = True
|
|
+ else:
|
|
+ ret["comment"].append(
|
|
+ "Failed to delete job {} from schedule.".format(
|
|
+ name
|
|
+ )
|
|
+ )
|
|
+ ret["result"] = True
|
|
+
|
|
+ except KeyError:
|
|
+ # Effectively a no-op, since we can't really return without an event system
|
|
+ ret["comment"] = "Event module not available. Schedule add failed."
|
|
+ ret["result"] = True
|
|
+
|
|
+ # wait until the end to write file in offline mode
|
|
+ if kwargs.get("offline"):
|
|
+ schedule_conf = _get_schedule_config_file()
|
|
+
|
|
+ try:
|
|
+ with salt.utils.files.fopen(schedule_conf, "wb+") as fp_:
|
|
+ fp_.write(
|
|
+ salt.utils.stringutils.to_bytes(
|
|
+ salt.utils.yaml.safe_dump({"schedule": current_schedule})
|
|
+ )
|
|
+ )
|
|
+ except OSError:
|
|
+ log.error(
|
|
+ "Failed to persist the updated schedule",
|
|
+ exc_info_on_loglevel=logging.DEBUG,
|
|
+ )
|
|
|
|
- except KeyError:
|
|
- # Effectively a no-op, since we can't really return without an event system
|
|
- ret["comment"] = "Event module not available. Schedule add failed."
|
|
- ret["result"] = True
|
|
return ret
|
|
|
|
|
|
@@ -245,6 +315,10 @@ def delete(name, **kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' schedule.delete job1
|
|
+
|
|
+ # Delete job on Salt minion when the Salt minion is not running
|
|
+ salt '*' schedule.delete job1
|
|
+
|
|
"""
|
|
|
|
ret = {
|
|
@@ -260,45 +334,86 @@ def delete(name, **kwargs):
|
|
ret["comment"] = "Job: {} would be deleted from schedule.".format(name)
|
|
ret["result"] = True
|
|
else:
|
|
- persist = kwargs.get("persist", True)
|
|
+ if kwargs.get("offline"):
|
|
+ current_schedule = list_(
|
|
+ show_all=True,
|
|
+ where="opts",
|
|
+ return_yaml=False,
|
|
+ offline=kwargs.get("offline"),
|
|
+ )
|
|
|
|
- if name in list_(show_all=True, where="opts", return_yaml=False):
|
|
- event_data = {"name": name, "func": "delete", "persist": persist}
|
|
- elif name in list_(show_all=True, where="pillar", return_yaml=False):
|
|
- event_data = {
|
|
- "name": name,
|
|
- "where": "pillar",
|
|
- "func": "delete",
|
|
- "persist": False,
|
|
- }
|
|
- else:
|
|
- ret["comment"] = "Job {} does not exist.".format(name)
|
|
- return ret
|
|
+ del current_schedule[name]
|
|
|
|
- try:
|
|
- with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
- res = __salt__["event.fire"](event_data, "manage_schedule")
|
|
- if res:
|
|
- event_ret = event_bus.get_event(
|
|
- tag="/salt/minion/minion_schedule_delete_complete",
|
|
- wait=30,
|
|
+ schedule_conf = _get_schedule_config_file()
|
|
+
|
|
+ try:
|
|
+ with salt.utils.files.fopen(schedule_conf, "wb+") as fp_:
|
|
+ fp_.write(
|
|
+ salt.utils.stringutils.to_bytes(
|
|
+ salt.utils.yaml.safe_dump({"schedule": current_schedule})
|
|
+ )
|
|
)
|
|
- if event_ret and event_ret["complete"]:
|
|
- schedule = event_ret["schedule"]
|
|
- if name not in schedule:
|
|
- ret["result"] = True
|
|
- ret["comment"] = "Deleted Job {} from schedule.".format(
|
|
- name
|
|
- )
|
|
- ret["changes"][name] = "removed"
|
|
- else:
|
|
- ret[
|
|
- "comment"
|
|
- ] = "Failed to delete job {} from schedule.".format(name)
|
|
- return ret
|
|
- except KeyError:
|
|
- # Effectively a no-op, since we can't really return without an event system
|
|
- ret["comment"] = "Event module not available. Schedule add failed."
|
|
+ except OSError:
|
|
+ log.error(
|
|
+ "Failed to persist the updated schedule",
|
|
+ exc_info_on_loglevel=logging.DEBUG,
|
|
+ )
|
|
+
|
|
+ ret["result"] = True
|
|
+ ret["comment"] = "Deleted Job {} from schedule.".format(name)
|
|
+ ret["changes"][name] = "removed"
|
|
+ else:
|
|
+ persist = kwargs.get("persist", True)
|
|
+
|
|
+ if name in list_(
|
|
+ show_all=True,
|
|
+ where="opts",
|
|
+ return_yaml=False,
|
|
+ offline=kwargs.get("offline"),
|
|
+ ):
|
|
+ event_data = {"name": name, "func": "delete", "persist": persist}
|
|
+ elif name in list_(
|
|
+ show_all=True,
|
|
+ where="pillar",
|
|
+ return_yaml=False,
|
|
+ offline=kwargs.get("offline"),
|
|
+ ):
|
|
+ event_data = {
|
|
+ "name": name,
|
|
+ "where": "pillar",
|
|
+ "func": "delete",
|
|
+ "persist": False,
|
|
+ }
|
|
+ else:
|
|
+ ret["comment"] = "Job {} does not exist.".format(name)
|
|
+ return ret
|
|
+
|
|
+ try:
|
|
+ with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
+ res = __salt__["event.fire"](event_data, "manage_schedule")
|
|
+ if res:
|
|
+ event_ret = event_bus.get_event(
|
|
+ tag="/salt/minion/minion_schedule_delete_complete",
|
|
+ wait=30,
|
|
+ )
|
|
+ if event_ret and event_ret["complete"]:
|
|
+ schedule = event_ret["schedule"]
|
|
+ if name not in schedule:
|
|
+ ret["result"] = True
|
|
+ ret["comment"] = "Deleted Job {} from schedule.".format(
|
|
+ name
|
|
+ )
|
|
+ ret["changes"][name] = "removed"
|
|
+ else:
|
|
+ ret[
|
|
+ "comment"
|
|
+ ] = "Failed to delete job {} from schedule.".format(
|
|
+ name
|
|
+ )
|
|
+ return ret
|
|
+ except KeyError:
|
|
+ # Effectively a no-op, since we can't really return without an event system
|
|
+ ret["comment"] = "Event module not available. Schedule add failed."
|
|
return ret
|
|
|
|
|
|
@@ -438,6 +553,10 @@ def add(name, **kwargs):
|
|
salt '*' schedule.add job1 function='test.ping' seconds=3600
|
|
# If function have some arguments, use job_args
|
|
salt '*' schedule.add job2 function='cmd.run' job_args="['date >> /tmp/date.log']" seconds=60
|
|
+
|
|
+ # Add job to Salt minion when the Salt minion is not running
|
|
+ salt '*' schedule.add job1 function='test.ping' seconds=3600 offline=True
|
|
+
|
|
"""
|
|
|
|
ret = {
|
|
@@ -445,8 +564,11 @@ def add(name, **kwargs):
|
|
"result": False,
|
|
"changes": {},
|
|
}
|
|
+ current_schedule = list_(
|
|
+ show_all=True, return_yaml=False, offline=kwargs.get("offline")
|
|
+ )
|
|
|
|
- if name in list_(show_all=True, return_yaml=False):
|
|
+ if name in current_schedule:
|
|
ret["comment"] = "Job {} already exists in schedule.".format(name)
|
|
ret["result"] = False
|
|
return ret
|
|
@@ -486,32 +608,56 @@ def add(name, **kwargs):
|
|
ret["comment"] = "Job: {} would be added to schedule.".format(name)
|
|
ret["result"] = True
|
|
else:
|
|
- try:
|
|
- with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
- res = __salt__["event.fire"](
|
|
- {
|
|
- "name": name,
|
|
- "schedule": schedule_data,
|
|
- "func": "add",
|
|
- "persist": persist,
|
|
- },
|
|
- "manage_schedule",
|
|
+ if kwargs.get("offline"):
|
|
+ current_schedule.update(schedule_data)
|
|
+
|
|
+ schedule_conf = _get_schedule_config_file()
|
|
+
|
|
+ try:
|
|
+ with salt.utils.files.fopen(schedule_conf, "wb+") as fp_:
|
|
+ fp_.write(
|
|
+ salt.utils.stringutils.to_bytes(
|
|
+ salt.utils.yaml.safe_dump({"schedule": current_schedule})
|
|
+ )
|
|
+ )
|
|
+ except OSError:
|
|
+ log.error(
|
|
+ "Failed to persist the updated schedule",
|
|
+ exc_info_on_loglevel=logging.DEBUG,
|
|
)
|
|
- if res:
|
|
- event_ret = event_bus.get_event(
|
|
- tag="/salt/minion/minion_schedule_add_complete",
|
|
- wait=30,
|
|
+
|
|
+ ret["result"] = True
|
|
+ ret["comment"] = "Added job: {} to schedule.".format(name)
|
|
+ ret["changes"][name] = "added"
|
|
+ else:
|
|
+ try:
|
|
+ with salt.utils.event.get_event("minion", opts=__opts__) as event_bus:
|
|
+ res = __salt__["event.fire"](
|
|
+ {
|
|
+ "name": name,
|
|
+ "schedule": schedule_data,
|
|
+ "func": "add",
|
|
+ "persist": persist,
|
|
+ },
|
|
+ "manage_schedule",
|
|
)
|
|
- if event_ret and event_ret["complete"]:
|
|
- schedule = event_ret["schedule"]
|
|
- 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
|
|
- ret["comment"] = "Event module not available. Schedule add failed."
|
|
+ if res:
|
|
+ event_ret = event_bus.get_event(
|
|
+ tag="/salt/minion/minion_schedule_add_complete",
|
|
+ wait=30,
|
|
+ )
|
|
+ if event_ret and event_ret["complete"]:
|
|
+ schedule = event_ret["schedule"]
|
|
+ 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
|
|
+ ret["comment"] = "Event module not available. Schedule add failed."
|
|
return ret
|
|
|
|
|
|
@@ -524,6 +670,10 @@ def modify(name, **kwargs):
|
|
.. code-block:: bash
|
|
|
|
salt '*' schedule.modify job1 function='test.ping' seconds=3600
|
|
+
|
|
+ # Modify job on Salt minion when the Salt minion is not running
|
|
+ salt '*' schedule.modify job1 function='test.ping' seconds=3600 offline=True
|
|
+
|
|
"""
|
|
|
|
ret = {"comment": "", "changes": {}, "result": True}
|
|
@@ -549,7 +699,9 @@ def modify(name, **kwargs):
|
|
ret["comment"] = 'Unable to use "when" and "cron" options together. Ignoring.'
|
|
return ret
|
|
|
|
- current_schedule = list_(show_all=True, return_yaml=False)
|
|
+ current_schedule = list_(
|
|
+ show_all=True, return_yaml=False, offline=kwargs.get("offline")
|
|
+ )
|
|
|
|
if name not in current_schedule:
|
|
ret["comment"] = "Job {} does not exist in schedule.".format(name)
|
|
@@ -566,8 +718,7 @@ def modify(name, **kwargs):
|
|
_current["seconds"] = _current.pop("_seconds")
|
|
|
|
# Copy _current _new, then update values from kwargs
|
|
- _new = pycopy.deepcopy(_current)
|
|
- _new.update(kwargs)
|
|
+ _new = build_schedule_item(name, **kwargs)
|
|
|
|
# Remove test from kwargs, it's not a valid schedule option
|
|
_new.pop("test", None)
|
|
@@ -587,29 +738,51 @@ def modify(name, **kwargs):
|
|
if "test" in kwargs and kwargs["test"]:
|
|
ret["comment"] = "Job: {} would be modified in schedule.".format(name)
|
|
else:
|
|
- persist = kwargs.get("persist", True)
|
|
- if name in list_(show_all=True, where="opts", return_yaml=False):
|
|
- event_data = {
|
|
- "name": name,
|
|
- "schedule": _new,
|
|
- "func": "modify",
|
|
- "persist": persist,
|
|
- }
|
|
- elif name in list_(show_all=True, where="pillar", return_yaml=False):
|
|
- event_data = {
|
|
- "name": name,
|
|
- "schedule": _new,
|
|
- "where": "pillar",
|
|
- "func": "modify",
|
|
- "persist": False,
|
|
- }
|
|
+ if kwargs.get("offline"):
|
|
+ current_schedule[name].update(_new)
|
|
|
|
- out = __salt__["event.fire"](event_data, "manage_schedule")
|
|
- if out:
|
|
+ schedule_conf = _get_schedule_config_file()
|
|
+
|
|
+ try:
|
|
+ with salt.utils.files.fopen(schedule_conf, "wb+") as fp_:
|
|
+ fp_.write(
|
|
+ salt.utils.stringutils.to_bytes(
|
|
+ salt.utils.yaml.safe_dump({"schedule": current_schedule})
|
|
+ )
|
|
+ )
|
|
+ except OSError:
|
|
+ log.error(
|
|
+ "Failed to persist the updated schedule",
|
|
+ exc_info_on_loglevel=logging.DEBUG,
|
|
+ )
|
|
+
|
|
+ ret["result"] = True
|
|
ret["comment"] = "Modified job: {} in schedule.".format(name)
|
|
+
|
|
else:
|
|
- ret["comment"] = "Failed to modify job {} in schedule.".format(name)
|
|
- ret["result"] = False
|
|
+ persist = kwargs.get("persist", True)
|
|
+ if name in list_(show_all=True, where="opts", return_yaml=False):
|
|
+ event_data = {
|
|
+ "name": name,
|
|
+ "schedule": _new,
|
|
+ "func": "modify",
|
|
+ "persist": persist,
|
|
+ }
|
|
+ elif name in list_(show_all=True, where="pillar", return_yaml=False):
|
|
+ event_data = {
|
|
+ "name": name,
|
|
+ "schedule": _new,
|
|
+ "where": "pillar",
|
|
+ "func": "modify",
|
|
+ "persist": False,
|
|
+ }
|
|
+
|
|
+ out = __salt__["event.fire"](event_data, "manage_schedule")
|
|
+ if out:
|
|
+ ret["comment"] = "Modified job: {} in schedule.".format(name)
|
|
+ else:
|
|
+ ret["comment"] = "Failed to modify job {} in schedule.".format(name)
|
|
+ ret["result"] = False
|
|
return ret
|
|
|
|
|
|
diff --git a/tests/pytests/unit/modules/test_schedule.py b/tests/pytests/unit/modules/test_schedule.py
|
|
index e6cb134982..02914be82f 100644
|
|
--- a/tests/pytests/unit/modules/test_schedule.py
|
|
+++ b/tests/pytests/unit/modules/test_schedule.py
|
|
@@ -8,7 +8,8 @@ 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
|
|
+from salt.utils.odict import OrderedDict
|
|
+from tests.support.mock import MagicMock, call, mock_open, patch
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
@@ -29,6 +30,11 @@ def sock_dir(tmp_path):
|
|
return str(tmp_path / "test-socks")
|
|
|
|
|
|
+@pytest.fixture
|
|
+def schedule_config_file(tmp_path):
|
|
+ return "/etc/salt/minion.d/_schedule.conf"
|
|
+
|
|
+
|
|
@pytest.fixture
|
|
def configure_loader_modules():
|
|
return {schedule: {}}
|
|
@@ -36,24 +42,56 @@ def configure_loader_modules():
|
|
|
|
# 'purge' function tests: 1
|
|
@pytest.mark.slow_test
|
|
-def test_purge(sock_dir):
|
|
+def test_purge(sock_dir, job1, schedule_config_file):
|
|
"""
|
|
Test if it purge all the jobs currently scheduled on the minion.
|
|
"""
|
|
+ _schedule_data = {"job1": job1}
|
|
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."],
|
|
+ with patch.object(
|
|
+ schedule, "list_", MagicMock(return_value=_schedule_data)
|
|
+ ):
|
|
+ assert schedule.purge() == {
|
|
+ "comment": ["Deleted job: job1 from schedule."],
|
|
+ "changes": {"job1": "removed"},
|
|
+ "result": True,
|
|
+ }
|
|
+
|
|
+ _schedule_data = {"job1": job1, "job2": job1, "job3": job1}
|
|
+ comm = [
|
|
+ "Deleted job: job1 from schedule.",
|
|
+ "Deleted job: job2 from schedule.",
|
|
+ "Deleted job: job3 from schedule.",
|
|
+ ]
|
|
+
|
|
+ changes = {"job1": "removed", "job2": "removed", "job3": "removed"}
|
|
+
|
|
+ with patch.dict(
|
|
+ schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": sock_dir}
|
|
+ ):
|
|
+ with patch("salt.utils.files.fopen", mock_open(read_data="")) as fopen_mock:
|
|
+ with patch.object(
|
|
+ schedule, "list_", MagicMock(return_value=_schedule_data)
|
|
+ ):
|
|
+ assert schedule.purge(offline=True) == {
|
|
+ "comment": comm,
|
|
+ "changes": changes,
|
|
"result": True,
|
|
}
|
|
+ _call = call(b"schedule: {}\n")
|
|
+ write_calls = fopen_mock.filehandles[schedule_config_file][
|
|
+ 0
|
|
+ ].write._mock_mock_calls
|
|
+ assert _call in write_calls
|
|
|
|
|
|
# 'delete' function tests: 1
|
|
@pytest.mark.slow_test
|
|
-def test_delete(sock_dir):
|
|
+def test_delete(sock_dir, job1, schedule_config_file):
|
|
"""
|
|
Test if it delete a job from the minion's schedule.
|
|
"""
|
|
@@ -68,6 +106,28 @@ def test_delete(sock_dir):
|
|
"result": False,
|
|
}
|
|
|
|
+ _schedule_data = {"job1": job1}
|
|
+ comm = "Deleted Job job1 from schedule."
|
|
+ changes = {"job1": "removed"}
|
|
+ with patch.dict(
|
|
+ schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": sock_dir}
|
|
+ ):
|
|
+ with patch("salt.utils.files.fopen", mock_open(read_data="")) as fopen_mock:
|
|
+ with patch.object(
|
|
+ schedule, "list_", MagicMock(return_value=_schedule_data)
|
|
+ ):
|
|
+ assert schedule.delete("job1", offline="True") == {
|
|
+ "comment": comm,
|
|
+ "changes": changes,
|
|
+ "result": True,
|
|
+ }
|
|
+
|
|
+ _call = call(b"schedule: {}\n")
|
|
+ write_calls = fopen_mock.filehandles[schedule_config_file][
|
|
+ 0
|
|
+ ].write._mock_mock_calls
|
|
+ assert _call in write_calls
|
|
+
|
|
|
|
# 'build_schedule_item' function tests: 1
|
|
def test_build_schedule_item(sock_dir):
|
|
@@ -120,7 +180,7 @@ def test_build_schedule_item_invalid_when(sock_dir):
|
|
|
|
|
|
@pytest.mark.slow_test
|
|
-def test_add(sock_dir):
|
|
+def test_add(sock_dir, schedule_config_file):
|
|
"""
|
|
Test if it add a job to the schedule.
|
|
"""
|
|
@@ -163,6 +223,24 @@ def test_add(sock_dir):
|
|
"result": True,
|
|
}
|
|
|
|
+ comm1 = "Added job: job3 to schedule."
|
|
+ changes1 = {"job3": "added"}
|
|
+ with patch.dict(
|
|
+ schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": sock_dir}
|
|
+ ):
|
|
+ with patch("salt.utils.files.fopen", mock_open(read_data="")) as fopen_mock:
|
|
+ assert schedule.add(
|
|
+ "job3", function="test.ping", seconds=3600, offline="True"
|
|
+ ) == {"comment": comm1, "changes": changes1, "result": True}
|
|
+
|
|
+ _call = call(
|
|
+ b"schedule:\n job3: {function: test.ping, seconds: 3600, maxrunning: 1, name: job3, enabled: true,\n jid_include: true}\n"
|
|
+ )
|
|
+ write_calls = fopen_mock.filehandles[schedule_config_file][
|
|
+ 1
|
|
+ ].write._mock_mock_calls
|
|
+ assert _call in write_calls
|
|
+
|
|
|
|
# 'run_job' function tests: 1
|
|
|
|
@@ -444,7 +522,7 @@ def test_copy(sock_dir, job1):
|
|
|
|
|
|
@pytest.mark.slow_test
|
|
-def test_modify(sock_dir):
|
|
+def test_modify(sock_dir, job1, schedule_config_file):
|
|
"""
|
|
Test if modifying job to the schedule.
|
|
"""
|
|
@@ -564,7 +642,6 @@ def test_modify(sock_dir):
|
|
for key in [
|
|
"maxrunning",
|
|
"function",
|
|
- "seconds",
|
|
"jid_include",
|
|
"name",
|
|
"enabled",
|
|
@@ -586,6 +663,51 @@ def test_modify(sock_dir):
|
|
ret = schedule.modify("job2", function="test.version", test=True)
|
|
assert ret == expected5
|
|
|
|
+ _schedule_data = {"job1": job1}
|
|
+ comm = "Modified job: job1 in schedule."
|
|
+ changes = {"job1": "removed"}
|
|
+
|
|
+ changes = {
|
|
+ "job1": {
|
|
+ "new": OrderedDict(
|
|
+ [
|
|
+ ("function", "test.version"),
|
|
+ ("maxrunning", 1),
|
|
+ ("name", "job1"),
|
|
+ ("enabled", True),
|
|
+ ("jid_include", True),
|
|
+ ]
|
|
+ ),
|
|
+ "old": OrderedDict(
|
|
+ [
|
|
+ ("function", "test.ping"),
|
|
+ ("maxrunning", 1),
|
|
+ ("name", "job1"),
|
|
+ ("jid_include", True),
|
|
+ ("enabled", True),
|
|
+ ]
|
|
+ ),
|
|
+ }
|
|
+ }
|
|
+ with patch.dict(
|
|
+ schedule.__opts__, {"schedule": {"job1": "salt"}, "sock_dir": sock_dir}
|
|
+ ):
|
|
+ with patch("salt.utils.files.fopen", mock_open(read_data="")) as fopen_mock:
|
|
+ with patch.object(
|
|
+ schedule, "list_", MagicMock(return_value=_schedule_data)
|
|
+ ):
|
|
+ assert schedule.modify(
|
|
+ "job1", function="test.version", offline="True"
|
|
+ ) == {"comment": comm, "changes": changes, "result": True}
|
|
+
|
|
+ _call = call(
|
|
+ b"schedule:\n job1: {enabled: true, function: test.version, jid_include: true, maxrunning: 1,\n name: job1}\n"
|
|
+ )
|
|
+ write_calls = fopen_mock.filehandles[schedule_config_file][
|
|
+ 0
|
|
+ ].write._mock_mock_calls
|
|
+ assert _call in write_calls
|
|
+
|
|
|
|
# 'is_enabled' function tests: 1
|
|
|
|
--
|
|
2.37.2
|
|
|
|
|