Skip to content

Commit 0bc9079

Browse files
authored
Add kernel modules parameters check (#33)
* add check and tests * sa, comments * refactor, changelog * minor bug * minor refactor * mypy * indent, comments * change names to expected * use warn, set of exp vals * use list of exp vals * minor - rm comment * minor docstring
1 parent 084743a commit 0bc9079

File tree

3 files changed

+339
-3
lines changed

3 files changed

+339
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
The v1 release supports Cisco IOS-XR release versions from 7.7.1 to 7.9.1.
77

8+
### v1.1.11 (2023-06-23)
9+
10+
- Add a check in `host-check` to verify the correct kernel modules parameters are being used.
11+
812
### v1.1.10 (2023-05-30)
913

1014
- Add a new 'error' check state to `host-check` for when checks fail to run and update the output message at the bottom of the script, inline with this change.

scripts/host-check

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ REQUIRED_CGROUP_MOUNTS = ["systemd", "memory", "pids", "cpu", "cpuset"]
9494
DASHED_LINE = "----------------------------------------------------------------------------"
9595
DOUBLE_DASHED_LINE = "============================================================================"
9696

97+
# Kernel parameter values which XRd expects and has been tested with.
98+
MODULE_EXPECTED_PARAMS = {
99+
"vfio-pci": {
100+
"nointxmask": ["N"],
101+
"disable_idle_d3": ["N"],
102+
"enable_sriov": ["N"],
103+
},
104+
"igb_uio": {
105+
"intr_mode": ["msix", "(null)"],
106+
},
107+
}
108+
97109
# -----------------------------------------------------------------------------
98110
# Colours
99111
# -----------------------------------------------------------------------------
@@ -1169,6 +1181,86 @@ def check_shmem_pages_max_size() -> CheckFuncReturn:
11691181
return CheckState.SUCCESS, f"{shared_mem_max_page_size:.1f} GiB"
11701182

11711183

1184+
def _module_params_compare(
1185+
module: str, exp_param_vals: Dict[str, List[str]]
1186+
) -> Dict[str, Optional[str]]:
1187+
"""
1188+
For a kernel module, check if the runtime parameters have the expected
1189+
values.
1190+
1191+
:param module:
1192+
Name of the module
1193+
1194+
:param exp_param_vals:
1195+
Dictionary mapping parameter name to the expected value of the parameter
1196+
1197+
:return:
1198+
A dict containing parameters with unexpected values
1199+
If failed to check value for a parameter, returns None as the dict
1200+
value for the parameter
1201+
"""
1202+
param_unexpected: Dict[str, Optional[str]] = {}
1203+
1204+
if not _is_module_loaded(
1205+
module.replace("-", "_")
1206+
) and not _is_module_builtin(module):
1207+
# Skip module param check if module is not loaded
1208+
return param_unexpected
1209+
# sysfs filesystem requires the underscore version of the kernel module
1210+
# name
1211+
module = module.replace("-", "_")
1212+
all_params_str, _ = run_cmd(["modinfo", "--field", "parm", module])
1213+
for param, exp_val in exp_param_vals.items():
1214+
if not re.search(rf"^{param}:", all_params_str, re.MULTILINE):
1215+
# if 'param' is absent in modinfo output, it means either the
1216+
# parameter was introduced in a later kernel version, or the
1217+
# specific linux distribution does not support the parameter.
1218+
continue
1219+
try:
1220+
path = f"/sys/module/{module}/parameters/{param}"
1221+
with open(path, "r", encoding="utf-8") as f:
1222+
param_val = f.read().strip()
1223+
if param_val not in exp_val:
1224+
param_unexpected[param] = param_val
1225+
except FileNotFoundError:
1226+
param_unexpected[param] = None
1227+
1228+
return param_unexpected
1229+
1230+
1231+
def check_modules_expected_params() -> CheckFuncReturn:
1232+
"""
1233+
Checks the kernel modules have been loaded with expected runtime parameters
1234+
(Checks parameters in MODULE_EXPECTED_PARAMS)
1235+
"""
1236+
all_modules_str = ""
1237+
for module, param_defaults in MODULE_EXPECTED_PARAMS.items():
1238+
module_str = ""
1239+
param_non_default = _module_params_compare(module, param_defaults)
1240+
for param, param_val in param_non_default.items():
1241+
if param_val is not None:
1242+
module_str += (
1243+
f"\nThe expected value for parameter {param} is "
1244+
f"{'/'.join(param_defaults[param])}, but it is set to {param_val}"
1245+
)
1246+
else:
1247+
module_str += f"\nFailed to check value for parameter: {param}"
1248+
if module_str:
1249+
module_str = textwrap.indent(module_str, " ")
1250+
all_modules_str += f"\nFor kernel module: {module}{module_str}"
1251+
if all_modules_str:
1252+
return (
1253+
CheckState.WARNING,
1254+
"XRd has not been tested with these kernel module parameters."
1255+
+ all_modules_str,
1256+
)
1257+
else:
1258+
return (
1259+
CheckState.SUCCESS,
1260+
"Kernel modules loaded with expected parameters.",
1261+
)
1262+
1263+
11721264
# -------------------------------------
11731265
# Docker checks
11741266
# -------------------------------------
@@ -1384,6 +1476,7 @@ BASE_CHECKS = [
13841476
Check("Core pattern", check_core_pattern, []),
13851477
Check("ASLR", check_userspace_aslr, []),
13861478
Check("Linux Security Modules", check_linux_security_modules, []),
1479+
Check("Kernel module parameters", check_modules_expected_params, []),
13871480
]
13881481

13891482
CONTROL_PLANE_CHECKS = [

tests/test_host_check.py

Lines changed: 242 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def perform_check(
6060
name: str,
6161
*,
6262
cmds: Optional[Union[Tuple[str, Any], List[Tuple[str, Any]]]] = None,
63-
files: Optional[Union[Tuple[str, str], List[Tuple[str, str]]]] = None,
63+
files: Optional[Union[Tuple[str, Any], List[Tuple[str, Any]]]] = None,
6464
deps: Optional[List[str]] = None,
6565
failed_deps: Optional[List[str]] = None,
6666
) -> Tuple[CheckState, str]:
@@ -142,7 +142,8 @@ def perform_check(
142142
effects.append(
143143
mock.mock_open(read_data=effect).return_value
144144
)
145-
else:
145+
# Skip file if effect is None
146+
elif effect is not None:
146147
effects.append(effect)
147148
mock_open = ctxs.enter_context(
148149
mock.patch("builtins.open", side_effect=effects)
@@ -166,7 +167,8 @@ def perform_check(
166167
# Check the expected files were read.
167168
if files:
168169
actual_files = [call[0][0] for call in mock_open.call_args_list]
169-
assert [f for f, _ in files] == actual_files
170+
# Only expect the "not None" files to be read.
171+
assert [f for f, e in files if e is not None] == actual_files
170172

171173
output = capsys.readouterr().out
172174
if deps:
@@ -1930,6 +1932,243 @@ def test_read_failure(self, capsys):
19301932
assert result is CheckState.ERROR
19311933

19321934

1935+
class TestKernelModuleParameters(_CheckTestBase):
1936+
"""Tests for default Kernel Parameters check."""
1937+
1938+
check_group = "base"
1939+
check_name = "Kernel module parameters"
1940+
files = [
1941+
"/sys/module/vfio_pci/parameters/nointxmask",
1942+
"/sys/module/vfio_pci/parameters/disable_idle_d3",
1943+
"/sys/module/vfio_pci/parameters/enable_sriov",
1944+
"/sys/module/igb_uio/parameters/intr_mode",
1945+
]
1946+
cmds = [
1947+
"lsmod | grep -q '^vfio_pci '", # loaded
1948+
"grep -q /vfio-pci.ko /lib/modules/*/modules.builtin", # builtin
1949+
"modinfo --field parm vfio_pci",
1950+
"lsmod | grep -q '^igb_uio '", # loaded
1951+
"grep -q /igb_uio.ko /lib/modules/*/modules.builtin", # builtin
1952+
"modinfo --field parm igb_uio",
1953+
]
1954+
# Output of 'modinfo --field parm vfio_pci' with all parameters enabled
1955+
vfio_pci_parms = """\
1956+
ids:Initial PCI IDs to add to the vfio driver, format is "vendor:device...
1957+
nointxmask:Disable support for PCI 2.3 style INTx masking. If this ...
1958+
disable_idle_d3:Disable using the PCI D3 low power state for ...
1959+
enable_sriov:Enable support for SR-IOV configuration. Enabling S...
1960+
disable_denylist:Disable use of device denylist. Disabling the deny...
1961+
"""
1962+
# Output of 'modinfo --field parm igb_uio' with all parameters enabled
1963+
igb_uio_parms = """\
1964+
intr_mode:igb_uio interrupt mode (default=msix):
1965+
msix Use MSIX interrupt
1966+
msi Use MSI interrupt
1967+
legacy Use Legacy interrupt
1968+
1969+
(charp)
1970+
wc_activate:Activate support for write combining (WC) (default=0)
1971+
0 - disable
1972+
other - enable
1973+
(int)
1974+
"""
1975+
1976+
def test_success(self, capsys):
1977+
"""
1978+
Test the success case.
1979+
Both modules loaded and all parameters supported and enabled with
1980+
default values
1981+
"""
1982+
1983+
cmd_outputs = [
1984+
"",
1985+
None,
1986+
self.vfio_pci_parms,
1987+
"",
1988+
None,
1989+
self.igb_uio_parms,
1990+
]
1991+
files_content = ["N", "N", "N", "msix"]
1992+
result, output = self.perform_check(
1993+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
1994+
)
1995+
assert textwrap.dedent(output) == textwrap.dedent(
1996+
"""\
1997+
PASS -- Kernel module parameters
1998+
Kernel modules loaded with expected parameters.
1999+
"""
2000+
)
2001+
assert result is CheckState.SUCCESS
2002+
2003+
def test_one_parm_disable(self, capsys):
2004+
"""Test one parameter not supported by the kernel."""
2005+
vfio_pci_test_parm = """\
2006+
ids:Initial PCI IDs to add to the vfio driver, format is "vendor:device...
2007+
nointxmask:Disable support for PCI 2.3 style INTx masking. If this ...
2008+
disable_idle_d3:Disable using the PCI D3 low power state for ...
2009+
disable_denylist:Disable use of device denylist. Disabling the deny...
2010+
"""
2011+
cmd_outputs = [
2012+
"",
2013+
None,
2014+
vfio_pci_test_parm,
2015+
"",
2016+
None,
2017+
self.igb_uio_parms,
2018+
]
2019+
files_content = ["N", "N", None, "msix"]
2020+
result, output = self.perform_check(
2021+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2022+
)
2023+
assert textwrap.dedent(output) == textwrap.dedent(
2024+
"""\
2025+
PASS -- Kernel module parameters
2026+
Kernel modules loaded with expected parameters.
2027+
"""
2028+
)
2029+
assert result is CheckState.SUCCESS
2030+
2031+
def test_one_parm_non_default(self, capsys):
2032+
"""Test one parameter having a non-default value."""
2033+
cmd_outputs = [
2034+
"",
2035+
None,
2036+
self.vfio_pci_parms,
2037+
"",
2038+
None,
2039+
self.igb_uio_parms,
2040+
]
2041+
files_content = ["N", "Y", "N", "msix"]
2042+
result, output = self.perform_check(
2043+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2044+
)
2045+
assert textwrap.dedent(output) == textwrap.dedent(
2046+
"""\
2047+
WARN -- Kernel module parameters
2048+
XRd has not been tested with these kernel module parameters.
2049+
For kernel module: vfio-pci
2050+
The expected value for parameter disable_idle_d3 is N, but it is set to Y
2051+
"""
2052+
)
2053+
assert result is CheckState.WARNING
2054+
2055+
def test_two_parm_non_default(self, capsys):
2056+
"""Test two parameters having non-default values."""
2057+
cmd_outputs = [
2058+
"",
2059+
None,
2060+
self.vfio_pci_parms,
2061+
"",
2062+
None,
2063+
self.igb_uio_parms,
2064+
]
2065+
files_content = ["N", "Y", "N", "legacy"]
2066+
result, output = self.perform_check(
2067+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2068+
)
2069+
assert textwrap.dedent(output) == textwrap.dedent(
2070+
"""\
2071+
WARN -- Kernel module parameters
2072+
XRd has not been tested with these kernel module parameters.
2073+
For kernel module: vfio-pci
2074+
The expected value for parameter disable_idle_d3 is N, but it is set to Y
2075+
For kernel module: igb_uio
2076+
The expected value for parameter intr_mode is msix/(null), but it is set to legacy
2077+
"""
2078+
)
2079+
assert result is CheckState.WARNING
2080+
2081+
def test_one_module_not_loaded(self, capsys):
2082+
"""Test where one module is not loaded."""
2083+
cmd_outputs = [
2084+
subprocess.SubprocessError(1, ""),
2085+
subprocess.SubprocessError(1, ""),
2086+
None,
2087+
"",
2088+
None,
2089+
self.igb_uio_parms,
2090+
]
2091+
files_content = [None, None, None, "msix"]
2092+
result, output = self.perform_check(
2093+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2094+
)
2095+
assert textwrap.dedent(output) == textwrap.dedent(
2096+
"""\
2097+
PASS -- Kernel module parameters
2098+
Kernel modules loaded with expected parameters.
2099+
"""
2100+
)
2101+
assert result is CheckState.SUCCESS
2102+
2103+
def test_both_module_not_loaded(self, capsys):
2104+
"""Test where both modules are not loaded."""
2105+
cmd_outputs = [
2106+
subprocess.SubprocessError(1, ""),
2107+
subprocess.SubprocessError(1, ""),
2108+
None,
2109+
subprocess.SubprocessError(1, ""),
2110+
subprocess.SubprocessError(1, ""),
2111+
None,
2112+
]
2113+
files_content = [None, None, None, None]
2114+
result, output = self.perform_check(
2115+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2116+
)
2117+
assert textwrap.dedent(output) == textwrap.dedent(
2118+
"""\
2119+
PASS -- Kernel module parameters
2120+
Kernel modules loaded with expected parameters.
2121+
"""
2122+
)
2123+
assert result is CheckState.SUCCESS
2124+
2125+
def test_parm_file_not_found(self, capsys):
2126+
"""Test one parameter file not being found"""
2127+
cmd_outputs = [
2128+
"",
2129+
None,
2130+
self.vfio_pci_parms,
2131+
"",
2132+
None,
2133+
self.igb_uio_parms,
2134+
]
2135+
files_content = ["N", "N", "N", FileNotFoundError]
2136+
result, output = self.perform_check(
2137+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2138+
)
2139+
assert textwrap.dedent(output) == textwrap.dedent(
2140+
"""\
2141+
WARN -- Kernel module parameters
2142+
XRd has not been tested with these kernel module parameters.
2143+
For kernel module: igb_uio
2144+
Failed to check value for parameter: intr_mode
2145+
"""
2146+
)
2147+
assert result is CheckState.WARNING
2148+
2149+
def test_parm_null_value(self, capsys):
2150+
"""Test intr_mode parameter having (null) value"""
2151+
cmd_outputs = [
2152+
"",
2153+
None,
2154+
self.vfio_pci_parms,
2155+
"",
2156+
None,
2157+
self.igb_uio_parms,
2158+
]
2159+
files_content = ["N", "N", "N", "(null)"]
2160+
result, output = self.perform_check(
2161+
capsys, cmd_effects=cmd_outputs, read_effects=files_content
2162+
)
2163+
assert textwrap.dedent(output) == textwrap.dedent(
2164+
"""\
2165+
PASS -- Kernel module parameters
2166+
Kernel modules loaded with expected parameters.
2167+
"""
2168+
)
2169+
assert result is CheckState.SUCCESS
2170+
2171+
19332172
# -------------------------------------
19342173
# Platform checks tests
19352174
# -------------------------------------

0 commit comments

Comments
 (0)