salt/tests/pytests/unit/output/test_highstate.py

548 lines
20 KiB
Python

import copy
import pytest
import salt.config
import salt.output.highstate as highstate
from salt.utils.odict import OrderedDict
from tests.support.mock import patch
@pytest.fixture
def configure_loader_modules():
minion_opts = salt.config.DEFAULT_MINION_OPTS.copy()
minion_opts.update({"color": False, "state_output_pct": True})
return {highstate: {"__opts__": minion_opts}}
@pytest.mark.parametrize("data", [None, {"return": None}, {"return": {"data": None}}])
def test_when_data_result_is_None_output_should_be_string_None(data):
expected_output = "None"
actual_output = highstate.output(data=data)
assert actual_output == expected_output
def test_when_data_is_dict_with_return_key_and_return_value_has_data_key_and_data_dict_has_one_dict_element_with_jid_and_fun_keys_and_return_value_is_None_then_output_should_return_literal_None_string():
expected_output = "None"
data = {
"return": {
"data": {
"foo bar quux fnord": {
"jid": "fnordy fnordy fnordy",
"fun": "fnordy fnordy fnord",
"return": {"data": None},
},
}
},
}
actual_output = highstate.output(data=data)
assert actual_output == expected_output
@pytest.mark.parametrize(
"return_value",
[42, "fnord"],
)
def test_when_data_is_dict_with_return_key_and_return_value_has_data_key_and_data_dict_has_one_dict_element_with_jid_and_fun_keys_and_return_value_is_int_or_str_that_value_should_be_returned(
return_value,
):
expected_output = return_value
data = {
"return": {
"data": {
"foo bar quux fnord": {
"jid": "fnordy fnordy fnordy",
"fun": "fnordy fnordy fnord",
"return": {"data": return_value},
},
}
},
}
actual_output = highstate.output(data=data)
assert actual_output == expected_output
def test_when_orchestrator_output_retcode_in_data_the_retcode_should_be_removed():
data = {"something_master": None, "retcode": 42}
actual_output = highstate.output(data)
assert "retcode" not in data
def test_when_more_than_one_local_master_retcode_should_not_be_removed():
expected_retcode = 42
data = {
"something_master": None,
"another_master": None,
"retcode": expected_retcode,
}
actual_output = highstate.output(data)
assert data["retcode"] == expected_retcode
def test_pct_summary_output():
data = {
"data": {
"master": {
"salt_|-call_sleep_state_|-call_sleep_state_|-state": {
"__id__": "call_sleep_state",
"__jid__": "20170418153529810135",
"__run_num__": 0,
"__sls__": "orch.simple",
"changes": {
"out": "highstate",
"ret": {
"minion": {
"module_|-simple-ping_|-test.ping_|-run": {
"__id__": "simple-ping",
"__run_num__": 0,
"__sls__": "simple-ping",
"changes": {"ret": True},
"comment": "Module function test.ping executed",
"duration": 56.179,
"name": "test.ping",
"result": True,
"start_time": "15:35:31.282099",
}
},
"sub_minion": {
"module_|-simple-ping_|-test.ping_|-run": {
"__id__": "simple-ping",
"__run_num__": 0,
"__sls__": "simple-ping",
"changes": {"ret": True},
"comment": "Module function test.ping executed",
"duration": 54.103,
"name": "test.ping",
"result": True,
"start_time": "15:35:31.005606",
}
},
},
},
"comment": (
"States ran successfully. Updating sub_minion, minion."
),
"duration": 1638.047,
"name": "call_sleep_state",
"result": True,
"start_time": "15:35:29.762657",
},
"salt_|-cmd_run_example_|-cmd.run_|-function": {
"__id__": "cmd_run_example",
"__jid__": "20200411195112288850",
"__run_num__": 1,
"__sls__": "orch.simple",
"changes": {
"out": "highstate",
"ret": {"minion": "file1\nfile2\nfile3"},
},
"comment": (
"Function ran successfully. Function cmd.run ran on minion."
),
"duration": 412.397,
"name": "cmd.run",
"result": True,
"start_time": "21:51:12.185868",
},
}
},
"outputter": "highstate",
"retcode": 0,
}
actual_output = highstate.output(data)
assert "Succeeded: 1 (changed=1)" in actual_output
assert "Failed: 0" in actual_output
assert "Success %: 100.0" in actual_output
assert "Failure %: 0.0" in actual_output
assert "Total states run: 1" in actual_output
assert " file2" in actual_output
def test__compress_ids():
"""
Tests for expected data return for _compress_ids
and proper formatting using the state_compress_ids option
"""
# Stop using OrderedDict once we drop Py3.5 support
data = OrderedDict()
# raw data entering the outputter
data["local"] = {
"cmd_|-mix-matched results_|-/bin/false_|-run": {
"__id__": "mix-matched results",
"__run_num__": 7,
"__sls__": "compress_test",
"changes": {"pid": 6554, "retcode": 1, "stderr": "", "stdout": ""},
"comment": "Command " '"/bin/false" ' "run",
"duration": 8.57,
"name": "/bin/false",
"result": False,
"start_time": "15:38:22.666578",
},
"cmd_|-mix-matched results_|-/bin/true_|-run": {
"__id__": "mix-matched results",
"__run_num__": 6,
"__sls__": "compress_test",
"changes": {"pid": 6553, "retcode": 0, "stderr": "", "stdout": ""},
"comment": "Command " '"/bin/true" ' "run",
"duration": 7.728,
"name": "/bin/true",
"result": True,
"start_time": "15:38:22.658452",
},
"cmd_|-mix-matched results_|-false_|-run": {
"__id__": "mix-matched results",
"__run_num__": 5,
"__sls__": "compress_test",
"changes": {"pid": 6552, "retcode": 1, "stderr": "", "stdout": ""},
"comment": "Command " '"false" run',
"duration": 7.832,
"name": "false",
"result": False,
"start_time": "15:38:22.650225",
},
"cmd_|-mix-matched results_|-true_|-run": {
"__id__": "mix-matched results",
"__run_num__": 4,
"__sls__": "compress_test",
"changes": {"pid": 6551, "retcode": 0, "stderr": "", "stdout": ""},
"comment": "Command " '"true" run',
"duration": 8.538,
"name": "true",
"result": True,
"start_time": "15:38:22.641293",
},
"file_|-one clean one changes_|-/tmp/changes_|-managed": {
"__id__": "one clean one changes",
"__run_num__": 13,
"__sls__": "compress_test",
"changes": {"diff": "New file"},
"comment": "File /tmp/changes updated",
"duration": 3.17,
"name": "/tmp/changes",
"result": True,
"start_time": "15:38:22.703770",
},
"file_|-one clean one changes_|-/tmp/clean_|-managed": {
"__id__": "one clean one changes",
"__run_num__": 12,
"__sls__": "compress_test",
"changes": {},
"comment": "File /tmp/clean is in the correct state",
"duration": 20.123,
"name": "/tmp/clean",
"result": True,
"start_time": "15:38:22.683450",
},
"test_|-succeed clean_|-bar_|-succeed_without_changes": {
"__id__": "succeed clean",
"__run_num__": 11,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 0.759,
"name": "bar",
"result": True,
"start_time": "15:38:22.678512",
},
"test_|-succeed clean_|-foo_|-succeed_without_changes": {
"__id__": "succeed clean",
"__run_num__": 10,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 0.676,
"name": "foo",
"result": True,
"start_time": "15:38:22.677678",
},
"test_|-succeed clean_|-hello_|-succeed_without_changes": {
"__id__": "succeed clean",
"__run_num__": 8,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 1.071,
"name": "hello",
"result": True,
"start_time": "15:38:22.675588",
},
"test_|-succeed clean_|-world_|-succeed_without_changes": {
"__id__": "succeed clean",
"__run_num__": 9,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 0.693,
"name": "world",
"result": True,
"start_time": "15:38:22.676826",
},
"test_|-succeed with changes_|-bar_|-succeed_with_changes": {
"__id__": "succeed with changes",
"__run_num__": 3,
"__sls__": "compress_test",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"comment": "Success!",
"duration": 0.829,
"name": "bar",
"result": True,
"start_time": "15:38:22.639625",
},
"test_|-succeed with changes_|-foo_|-succeed_with_changes": {
"__id__": "succeed with changes",
"__run_num__": 2,
"__sls__": "compress_test",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"comment": "Success!",
"duration": 0.739,
"name": "foo",
"result": True,
"start_time": "15:38:22.638724",
},
"test_|-succeed with changes_|-hello_|-succeed_with_changes": {
"__id__": "succeed with changes",
"__run_num__": 0,
"__sls__": "compress_test",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"comment": "Success!",
"duration": 0.812,
"name": "hello",
"result": True,
"start_time": "15:38:22.636883",
},
"test_|-succeed with changes_|-world_|-succeed_with_changes": {
"__id__": "succeed with changes",
"__run_num__": 1,
"__sls__": "compress_test",
"changes": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"comment": "Success!",
"duration": 0.694,
"name": "world",
"result": True,
"start_time": "15:38:22.637872",
},
"test_|-single clean_|-single_|-succeed_without_changes": {
"__id__": "single clean",
"__run_num__": 14,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 0.693,
"name": "single",
"result": True,
"start_time": "15:38:22.676827",
},
}
# Stop using OrderedDict once we drop Py3.5 support
expected_output = OrderedDict()
# expected compressed raw data for outputter
expected_output["local"] = {
"cmd_|-mix-matched results (2)_|-state_compressed_compress_test_mix-matched results_False_|-run": {
"__id__": "mix-matched results",
"__run_num__": 5,
"__sls__": "compress_test",
"changes": {
"compressed changes": {
"/bin/false": {
"pid": 6554,
"retcode": 1,
"stderr": "",
"stdout": "",
},
"false": {
"pid": 6552,
"retcode": 1,
"stderr": "",
"stdout": "",
},
}
},
"comment": "Command " '"/bin/false" ' "run",
"duration": 16.402,
"name": "/bin/false",
"result": False,
"start_time": "15:38:22.650225",
},
"cmd_|-mix-matched results (2)_|-state_compressed_compress_test_mix-matched results_True_|-run": {
"__id__": "mix-matched results",
"__run_num__": 4,
"__sls__": "compress_test",
"changes": {
"compressed changes": {
"/bin/true": {
"pid": 6553,
"retcode": 0,
"stderr": "",
"stdout": "",
},
"true": {"pid": 6551, "retcode": 0, "stderr": "", "stdout": ""},
}
},
"comment": "Command " '"/bin/true" ' "run",
"duration": 16.266,
"name": "/bin/true",
"result": True,
"start_time": "15:38:22.641293",
},
"file_|-one clean one changes (2)_|-state_compressed_compress_test_one clean one changes_True_|-managed": {
"__id__": "one clean one changes",
"__run_num__": 12,
"__sls__": "compress_test",
"changes": {"diff": "New file"},
"comment": "File /tmp/changes updated",
"duration": 23.293,
"name": "/tmp/changes",
"result": True,
"start_time": "15:38:22.683450",
},
"test_|-succeed clean (4)_|-state_compressed_compress_test_succeed clean_True_|-succeed_without_changes": {
"__id__": "succeed clean",
"__run_num__": 8,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 3.199,
"name": "bar",
"result": True,
"start_time": "15:38:22.675588",
},
"test_|-succeed with changes (4)_|-state_compressed_compress_test_succeed with changes_True_|-succeed_with_changes": {
"__id__": "succeed with changes",
"__run_num__": 0,
"__sls__": "compress_test",
"changes": {
"compressed changes": {
"bar": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"foo": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"hello": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
"world": {
"testing": {
"new": "Something pretended to change",
"old": "Unchanged",
}
},
}
},
"comment": "Success!",
"duration": 3.074,
"name": "bar",
"result": True,
"start_time": "15:38:22.636883",
},
"test_|-single clean_|-single_|-succeed_without_changes": {
"__id__": "single clean",
"__run_num__": 14,
"__sls__": "compress_test",
"changes": {},
"comment": "Success!",
"duration": 0.693,
"name": "single",
"result": True,
"start_time": "15:38:22.676827",
},
}
actual_output = highstate._compress_ids(data)
# return properly compressed data
assert actual_output == expected_output
# check output text for formatting
opts = copy.deepcopy(highstate.__opts__)
opts["state_compress_ids"] = True
with patch("salt.output.highstate.__opts__", opts, create=True):
actual_output = highstate.output(data)
assert " ID: succeed with changes (4)" in actual_output
assert (
" Name: state_compressed_compress_test_succeed with changes_True"
in actual_output
)
assert " compressed changes:" in actual_output
assert " ID: mix-matched results (2)" in actual_output
assert (
" Name: state_compressed_compress_test_mix-matched results_True"
in actual_output
)
assert (
" Name: state_compressed_compress_test_mix-matched results_False"
in actual_output
)
assert " ID: succeed clean (4)" in actual_output
assert (
" Name: state_compressed_compress_test_succeed clean_True"
in actual_output
)
assert " ID: one clean one changes (2)" in actual_output
assert (
" Name: state_compressed_compress_test_one clean one changes_True"
in actual_output
)
assert " diff:" in actual_output
assert "Succeeded: 13 (changed=9)" in actual_output
assert "Failed: 2" in actual_output
assert "Success %: 86.67" in actual_output
assert "Failure %: 13.33" in actual_output
assert "Total states run: 15" in actual_output
# pop out a __run_num__ to break the data
data["local"]["cmd_|-mix-matched results_|-/bin/false_|-run"].pop("__run_num__")
actual_output = highstate._compress_ids(data)
# expecting return of original data to let the outputter figure it out
assert actual_output == data
def test__compress_ids_not_dict():
"""
Simple test for returning original malformed data
to let the outputter figure it out.
"""
data = ["malformed"]
actual_output = highstate._compress_ids(data)
assert actual_output == data