diff --git a/changelog/62772.fixed b/changelog/62772.fixed new file mode 100644 index 000000000000..c81a3a5908ea --- /dev/null +++ b/changelog/62772.fixed @@ -0,0 +1 @@ +Fix kmod.* functions hard code relative command name diff --git a/salt/modules/kmod.py b/salt/modules/kmod.py index ff7a344fb5fc..108bc6cad82c 100644 --- a/salt/modules/kmod.py +++ b/salt/modules/kmod.py @@ -8,6 +8,7 @@ import salt.utils.files import salt.utils.path +from salt.exceptions import CommandExecutionError log = logging.getLogger(__name__) @@ -108,6 +109,16 @@ def _remove_persistent_module(mod, comment): return {mod_name} +def _which(cmd): + """ + Utility function wrapper to error out early if a command is not found + """ + _cmd = salt.utils.path.which(cmd) + if not _cmd: + raise CommandExecutionError("Command '{}' cannot be found".format(cmd)) + return _cmd + + def available(): """ Return a list of all available kernel modules @@ -169,7 +180,7 @@ def lsmod(): salt '*' kmod.lsmod """ ret = [] - for line in __salt__["cmd.run"]("lsmod").splitlines(): + for line in __salt__["cmd.run"](_which("lsmod")).splitlines(): comps = line.split() if not len(comps) > 2: continue @@ -237,7 +248,9 @@ def load(mod, persist=False): salt '*' kmod.load kvm """ pre_mods = lsmod() - res = __salt__["cmd.run_all"]("modprobe {}".format(mod), python_shell=False) + res = __salt__["cmd.run_all"]( + "{} {}".format(_which("modprobe"), mod), python_shell=False + ) if res["retcode"] == 0: post_mods = lsmod() mods = _new_mods(pre_mods, post_mods) @@ -283,7 +296,9 @@ def remove(mod, persist=False, comment=True): salt '*' kmod.remove kvm """ pre_mods = lsmod() - res = __salt__["cmd.run_all"]("rmmod {}".format(mod), python_shell=False) + res = __salt__["cmd.run_all"]( + "{} {}".format(_which("rmmod"), mod), python_shell=False + ) if res["retcode"] == 0: post_mods = lsmod() mods = _rm_mods(pre_mods, post_mods) diff --git a/tests/pytests/unit/modules/test_kmod.py b/tests/pytests/unit/modules/test_kmod.py new file mode 100644 index 000000000000..36f7d61b0f3b --- /dev/null +++ b/tests/pytests/unit/modules/test_kmod.py @@ -0,0 +1,137 @@ +import os + +import pytest + +import salt.modules.kmod as kmod +from salt.exceptions import CommandExecutionError +from tests.support.mock import MagicMock, patch +from tests.support.unit import skipIf + + +@pytest.fixture +def configure_loader_modules(): + return {kmod: {}} + + +def test_available(): + """ + Tests return a list of all available kernel modules + """ + with patch("salt.modules.kmod.available", MagicMock(return_value=["kvm"])): + assert ["kvm"] == kmod.available() + + +def test_check_available(): + """ + Tests if the specified kernel module is available + """ + with patch("salt.modules.kmod.available", MagicMock(return_value=["kvm"])): + assert kmod.check_available("kvm") is True + + +def test_lsmod(): + """ + Tests return information about currently loaded modules + """ + ret_str = """Module Size Used by + kvm_intel 233472 0 + """ + expected = [{"size": "233472", "module": "kvm_intel", "depcount": "0", "deps": []}] + mock_cmd = MagicMock(return_value=ret_str) + with patch( + "salt.utils.path.which", MagicMock(side_effect=[None, "/sbin/lsmod"]) + ), patch.dict(kmod.__salt__, {"cmd.run": mock_cmd}): + with pytest.raises(CommandExecutionError): + kmod.lsmod() + assert expected == kmod.lsmod() + + +@skipIf(not os.path.isfile("/etc/modules"), "/etc/modules not present") +def test_mod_list(): + """ + Tests return a list of the loaded module names + """ + with patch( + "salt.modules.kmod._get_modules_conf", + MagicMock(return_value="/etc/modules"), + ): + with patch( + "salt.modules.kmod._strip_module_name", MagicMock(return_value="lp") + ): + assert ["lp"] == kmod.mod_list(True) + + mock_ret = [{"size": 100, "module": None, "depcount": 10, "deps": None}] + with patch("salt.modules.kmod.lsmod", MagicMock(return_value=mock_ret)): + assert [None] == kmod.mod_list(False) + + +def test_load(): + """ + Tests to loads specified kernel module. + """ + mod = "cheese" + err_msg = "Module too moldy, refusing to load" + mock_persist = MagicMock(return_value={mod}) + mock_lsmod = MagicMock( + return_value=[{"size": 100, "module": None, "depcount": 10, "deps": None}] + ) + mock_run_all_0 = MagicMock(return_value={"retcode": 0}) + mock_run_all_1 = MagicMock(return_value={"retcode": 1, "stderr": err_msg}) + + with patch("salt.modules.kmod._set_persistent_module", mock_persist): + with patch( + "salt.utils.path.which", + MagicMock(side_effect=[None, "/sbin/modprobe", "/sbin/modprobe"]), + ), patch("salt.modules.kmod.lsmod", mock_lsmod): + with patch.dict( + kmod.__salt__, {"cmd.run_all": mock_run_all_0} + ), pytest.raises(CommandExecutionError): + kmod.load(mod, True) + + with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_0}): + assert [mod] == kmod.load(mod, True) + + with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_1}): + assert "Error loading module {}: {}".format(mod, err_msg) == kmod.load( + mod + ) + + +def test_is_loaded(): + """ + Tests if specified kernel module is loaded. + """ + with patch("salt.modules.kmod.mod_list", MagicMock(return_value={"lp"})): + assert kmod.is_loaded("lp") is True + + +def test_remove(): + """ + Tests to remove the specified kernel module + """ + mod = "cheese" + err_msg = "Cannot find module: it has been eaten" + mock_persist = MagicMock(return_value={mod}) + mock_lsmod = MagicMock( + return_value=[{"size": 100, "module": None, "depcount": 10, "deps": None}] + ) + mock_run_all_0 = MagicMock(return_value={"retcode": 0}) + mock_run_all_1 = MagicMock(return_value={"retcode": 1, "stderr": err_msg}) + + with patch("salt.modules.kmod._remove_persistent_module", mock_persist): + with patch( + "salt.utils.path.which", + MagicMock(side_effect=[None, "/sbin/rmmod", "/sbin/rmmod", "/sbin/rmmod"]), + ), patch("salt.modules.kmod.lsmod", mock_lsmod): + with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_0}): + with pytest.raises(CommandExecutionError): + kmod.remove(mod) + + assert [mod] == kmod.remove(mod, True) + + assert [] == kmod.remove(mod) + + with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_1}): + assert "Error removing module {}: {}".format( + mod, err_msg + ) == kmod.remove(mod, True) diff --git a/tests/unit/modules/test_kmod.py b/tests/unit/modules/test_kmod.py deleted file mode 100644 index 4cd1e8580141..000000000000 --- a/tests/unit/modules/test_kmod.py +++ /dev/null @@ -1,133 +0,0 @@ -""" - :codeauthor: Jayesh Kariya -""" - - -import os - -import salt.modules.kmod as kmod -from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, patch -from tests.support.unit import TestCase, skipIf - - -class KmodTestCase(TestCase, LoaderModuleMockMixin): - """ - TestCase for salt.modules.kmod - """ - - def setup_loader_modules(self): - return {kmod: {}} - - # 'available' function tests: 1 - - def test_available(self): - """ - Tests return a list of all available kernel modules - """ - with patch("salt.modules.kmod.available", MagicMock(return_value=["kvm"])): - self.assertEqual(["kvm"], kmod.available()) - - # 'check_available' function tests: 1 - - def test_check_available(self): - """ - Tests if the specified kernel module is available - """ - with patch("salt.modules.kmod.available", MagicMock(return_value=["kvm"])): - self.assertTrue(kmod.check_available("kvm")) - - # 'lsmod' function tests: 1 - - def test_lsmod(self): - """ - Tests return information about currently loaded modules - """ - mock_ret = [{"size": 100, "module": None, "depcount": 10, "deps": None}] - with patch("salt.modules.kmod.lsmod", MagicMock(return_value=mock_ret)): - mock_cmd = MagicMock(return_value=1) - with patch.dict(kmod.__salt__, {"cmd.run": mock_cmd}): - self.assertListEqual(mock_ret, kmod.lsmod()) - - # 'mod_list' function tests: 1 - - @skipIf(not os.path.isfile("/etc/modules"), "/etc/modules not present") - def test_mod_list(self): - """ - Tests return a list of the loaded module names - """ - with patch( - "salt.modules.kmod._get_modules_conf", - MagicMock(return_value="/etc/modules"), - ): - with patch( - "salt.modules.kmod._strip_module_name", MagicMock(return_value="lp") - ): - self.assertListEqual(["lp"], kmod.mod_list(True)) - - mock_ret = [{"size": 100, "module": None, "depcount": 10, "deps": None}] - with patch("salt.modules.kmod.lsmod", MagicMock(return_value=mock_ret)): - self.assertListEqual([None], kmod.mod_list(False)) - - # 'load' function tests: 1 - - def test_load(self): - """ - Tests to loads specified kernel module. - """ - mod = "cheese" - err_msg = "Module too moldy, refusing to load" - mock_persist = MagicMock(return_value={mod}) - mock_lsmod = MagicMock( - return_value=[{"size": 100, "module": None, "depcount": 10, "deps": None}] - ) - mock_run_all_0 = MagicMock(return_value={"retcode": 0}) - mock_run_all_1 = MagicMock(return_value={"retcode": 1, "stderr": err_msg}) - - with patch("salt.modules.kmod._set_persistent_module", mock_persist): - with patch("salt.modules.kmod.lsmod", mock_lsmod): - with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_0}): - self.assertEqual([mod], kmod.load(mod, True)) - - with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_1}): - self.assertEqual( - "Error loading module {}: {}".format(mod, err_msg), - kmod.load(mod), - ) - - # 'is_loaded' function tests: 1 - - def test_is_loaded(self): - """ - Tests if specified kernel module is loaded. - """ - with patch("salt.modules.kmod.mod_list", MagicMock(return_value={"lp"})): - self.assertTrue(kmod.is_loaded("lp")) - - # 'remove' function tests: 1 - - def test_remove(self): - """ - Tests to remove the specified kernel module - """ - mod = "cheese" - err_msg = "Cannot find module: it has been eaten" - mock_persist = MagicMock(return_value={mod}) - mock_lsmod = MagicMock( - return_value=[{"size": 100, "module": None, "depcount": 10, "deps": None}] - ) - mock_run_all_0 = MagicMock(return_value={"retcode": 0}) - mock_run_all_1 = MagicMock(return_value={"retcode": 1, "stderr": err_msg}) - - with patch("salt.modules.kmod._remove_persistent_module", mock_persist): - with patch("salt.modules.kmod.lsmod", mock_lsmod): - with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_0}): - self.assertEqual([mod], kmod.remove(mod, True)) - - self.assertEqual([], kmod.remove(mod)) - - with patch.dict(kmod.__salt__, {"cmd.run_all": mock_run_all_1}): - self.assertEqual( - "Error removing module {}: {}".format(mod, err_msg), - kmod.remove(mod, True), - )