From d1932fa3c5afff8fcdc03f46cb60e579be1cec71 Mon Sep 17 00:00:00 2001 From: piterpunk Date: Thu, 6 May 2021 23:47:40 -0300 Subject: [PATCH] Added sysfs state module to manage kernel objects --- changelog/60154.added | 1 + doc/ref/states/all/index.rst | 1 + doc/ref/states/all/salt.states.sysfs.rst | 5 + salt/states/sysfs.py | 73 ++++++++++++++ tests/pytests/unit/states/test_sysfs.py | 117 +++++++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 changelog/60154.added create mode 100644 doc/ref/states/all/salt.states.sysfs.rst create mode 100644 salt/states/sysfs.py create mode 100644 tests/pytests/unit/states/test_sysfs.py diff --git a/changelog/60154.added b/changelog/60154.added new file mode 100644 index 00000000000..3957b19ce93 --- /dev/null +++ b/changelog/60154.added @@ -0,0 +1 @@ +State module to manage SysFS attributes diff --git a/doc/ref/states/all/index.rst b/doc/ref/states/all/index.rst index f004faff479..7a70019c225 100644 --- a/doc/ref/states/all/index.rst +++ b/doc/ref/states/all/index.rst @@ -303,6 +303,7 @@ state modules supervisord svn sysctl + sysfs syslog_ng sysrc telemetry_alert diff --git a/doc/ref/states/all/salt.states.sysfs.rst b/doc/ref/states/all/salt.states.sysfs.rst new file mode 100644 index 00000000000..337c4c37651 --- /dev/null +++ b/doc/ref/states/all/salt.states.sysfs.rst @@ -0,0 +1,5 @@ +salt.states.sysfs +================== + +.. automodule:: salt.states.sysfs + :members: diff --git a/salt/states/sysfs.py b/salt/states/sysfs.py new file mode 100644 index 00000000000..b0d89943e81 --- /dev/null +++ b/salt/states/sysfs.py @@ -0,0 +1,73 @@ +""" +Configuration of the kernel using sysfs +======================================= + +Control the kernel object attributes exported by sysfs + +.. code-block:: yaml + + kernel/mm/transparent_hugepage/enabled + sysfs.present: + - value: never + +.. versionadded: TBD +""" + +import re + + +def __virtual__(): + """ + This state is only available on Minions which support sysctl + """ + if "sysfs.attr" in __salt__: + return True + return (False, "sysfs module could not be loaded") + + +def present(name, value, config=None): + """ + Ensure that the named sysfs attribute is set with the defined value + + name + The name of the sysfs attribute to edit + + value + The sysfs value to apply + + """ + ret = {"name": name, "result": True, "changes": {}, "comment": ""} + + current = __salt__["sysfs.read"](name) + if current is False: + ret["result"] = False + ret["comment"] = "SysFS attribute {} doesn't exist.".format(name) + else: + # if the return is a dict, the "name" is an object not an attribute + if isinstance(current, dict): + ret["result"] = False + ret["comment"] = "{} is not a SysFS attribute.".format(name) + else: + # some attribute files lists all available options and the selected one between [] + if isinstance(current, str): + current = re.sub(r"(.*\[|\].*)", "", current) + if value == current: + ret["result"] = True + ret["comment"] = "SysFS attribute {} is already set.".format(name) + else: + ret["result"] = None + + if ret["result"] is None: + if __opts__["test"]: + ret["comment"] = "SysFS attribute {} set to be changed.".format(name) + else: + update = __salt__["sysfs.write"](name, value) + if not update: + ret["result"] = False + ret["comment"] = "Failed to set {} to {}".format(name, value) + else: + ret["result"] = True + ret["changes"] = {name: value} + ret["comment"] = "Updated SysFS attribute {} to {}".format(name, value) + + return ret diff --git a/tests/pytests/unit/states/test_sysfs.py b/tests/pytests/unit/states/test_sysfs.py new file mode 100644 index 00000000000..1285a67bf33 --- /dev/null +++ b/tests/pytests/unit/states/test_sysfs.py @@ -0,0 +1,117 @@ +""" + :codeauthor: Piter Punk +""" + +import pytest +import salt.states.sysfs as sysfs +from tests.support.mock import MagicMock, patch + + +@pytest.fixture +def configure_loader_modules(): + return {sysfs: {}} + + +def test_if_the_sysfs_attribute_exists(): + """ + Test sysfs.present for a non-existent attribute + """ + name = "block/sda/queue/this_does_not_exist" + value = "none" + comment = "SysFS attribute {} doesn't exist.".format(name) + ret = {"name": name, "result": False, "changes": {}, "comment": comment} + + mock_read = MagicMock(return_value=False) + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + assert sysfs.present(name, value) == ret + + +def test_name_is_an_object_and_not_an_attribute(): + """ + Test sysfs.present targeting an object and not one of its attributes + """ + name = "block/sda/queue" + value = "none" + comment = "{} is not a SysFS attribute.".format(name) + ret = {"name": name, "result": False, "changes": {}, "comment": comment} + + read_from_sysfs = { + "rotational": 1, + "rq_affinity": 1, + "scheduler": "[none] mq-deadline", + } + + mock_read = MagicMock(return_value=read_from_sysfs) + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + assert sysfs.present(name, value) == ret + + +def test_already_set(): + """ + Test sysfs.present with equal old and new values + """ + name = "block/sda/queue" + value = "none" + comment = "SysFS attribute {} is already set.".format(name) + ret = {"name": name, "result": True, "changes": {}, "comment": comment} + + read_from_sysfs = "[none] mq-deadline" + + mock_read = MagicMock(return_value=read_from_sysfs) + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + assert sysfs.present(name, value) == ret + + +def test_set_new_value_with_test_equals_true(): + """ + Test sysfs.present setting a new value + """ + name = "devices/system/cpu/cpufreq/policy0" + value = "powersave" + comment = "SysFS attribute {} set to be changed.".format(name) + ret = {"name": name, "result": None, "changes": {}, "comment": comment} + + read_from_sysfs = "performance" + + mock_read = MagicMock(return_value=read_from_sysfs) + with patch.dict(sysfs.__opts__, {"test": True}): + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + assert sysfs.present(name, value) == ret + + +def test_set_new_value_with_success(): + """ + Test sysfs.present setting a new value + """ + name = "block/sda/queue/scheduler" + value = "mq-deadline" + comment = "Updated SysFS attribute {} to {}".format(name, value) + ret = {"name": name, "result": True, "changes": {name: value}, "comment": comment} + + read_from_sysfs = "[none] mq-deadline" + + mock_read = MagicMock(return_value=read_from_sysfs) + with patch.dict(sysfs.__opts__, {"test": False}): + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + mock_write = MagicMock(return_value=True) + with patch.dict(sysfs.__salt__, {"sysfs.write": mock_write}): + assert sysfs.present(name, value) == ret + + +def test_set_new_value_with_failure(): + """ + Test sysfs.present failure writing the value + """ + name = "block/sda/queue/scheduler" + value = "imaginary_scheduler" + comment = "Failed to set {} to {}".format(name, value) + ret = {"name": name, "result": False, "changes": {}, "comment": comment} + + read_from_sysfs = "[none] mq-deadline" + + mock_read = MagicMock(return_value=read_from_sysfs) + with patch.dict(sysfs.__opts__, {"test": False}): + with patch.dict(sysfs.__salt__, {"sysfs.read": mock_read}): + mock_write = MagicMock(return_value=False) + with patch.dict(sysfs.__salt__, {"sysfs.write": mock_write}): + assert sysfs.present(name, value) == ret