Dan Bungert has proposed merging ~dbungert/curtin:23.1-kdump into curtin:release/23.1.
Commit message: Introduce the kernel-crash-dumps builtin hook Introduce a new hook for builtin_curthooks to configure kernel crash dumps on the target system. The configuration key is `kernel-crash-dumps` and adds the following new section to the curthooks configuration: kernel-crash-dumps: enabled: bool | None By default, `enabled` is `None` will cause kernel crash dumps to be dynamically enabled on the target system if the enablement script provided by kdump-tools is found in: /usr/share/kdump-tools/kdump_set_default The enablement script will inspect the system and either enable or disable kernel crash dumps. Users can also specify `True` or `False` to unconditionally enable or disable kernel crash dumps, respectively. (cherry picked from commit 40cae5c60fa9f4c495c7f61cde28862175f93ce2) Requested reviews: curtin developers (curtin-dev) For more details, see: https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/473954 -- Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:23.1-kdump into curtin:release/23.1.
diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 6fb5f3d..da4eda8 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -3,6 +3,7 @@ import copy import glob import os +import pathlib import platform import re import sys @@ -21,6 +22,7 @@ from curtin import paths from curtin import swap from curtin import util from curtin import version as curtin_version +from curtin import kernel_crash_dumps from curtin.block import deps as bdeps from curtin.distro import DISTROS from curtin.net import deps as ndeps @@ -1480,6 +1482,29 @@ def configure_mdadm(cfg, state_etcd, target, osfamily=DISTROS.debian): data=None, target=target) +def configure_kernel_crash_dumps(cfg, target: pathlib.Path) -> None: + """Configure kernel crash dumps on target system. + + kernel-crash-dumps: + enabled: bool | None + + If `enabled` is `None` then kernel crash dumps will be dynamically enabled + if both the kdump-tools package is installed on the target system and it + also provides the expected enablement script (Starting in 24.10). + """ + kdump_cfg = cfg.get("kernel-crash-dumps", {}) + + enabled: bool = kdump_cfg.get("enabled") + automatic = enabled is None + + if automatic: + kernel_crash_dumps.automatic_detect(target) + elif enabled: + kernel_crash_dumps.manual_enable(target) + else: + kernel_crash_dumps.manual_disable(target) + + def handle_cloudconfig(cfg, base_dir=None): """write cloud-init configuration files into base_dir. @@ -1851,6 +1876,15 @@ def builtin_curthooks(cfg, target, state): if os.path.isdir(udev_rules_d): copy_dname_rules(udev_rules_d, target) + # Setup kernel crash dumps + with events.ReportEventStack( + name=stack_prefix + "/configuring-kernel-crash-dumps", + reporting_enabled=True, + level="INFO", + description="configuring kernel crash dumps settings", + ): + configure_kernel_crash_dumps(cfg, pathlib.Path(target)) + with events.ReportEventStack( name=stack_prefix + '/updating-initramfs-configuration', reporting_enabled=True, level="INFO", diff --git a/curtin/kernel_crash_dumps.py b/curtin/kernel_crash_dumps.py new file mode 100644 index 0000000..9c7ef4d --- /dev/null +++ b/curtin/kernel_crash_dumps.py @@ -0,0 +1,70 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +from pathlib import Path + +from curtin import distro +from curtin.log import LOG +from curtin.util import ChrootableTarget, ProcessExecutionError + +ENABLEMENT_SCRIPT = "/usr/share/kdump-tools/kdump_set_default" + + +def ensure_kdump_installed(target: Path) -> None: + """Ensure kdump-tools installed on target system and install it if not. + + kdump-tools is theoretically part of the base-install in >=24.10 + but we may need to dynamically install it if manual enablement is + requested. + """ + if "kdump-tools" not in distro.get_installed_packages(str(target)): + distro.install_packages(["kdump-tools"], target=str(target)) + + +def detection_script_available(target: Path) -> bool: + """Detect existence of the enablement script. + + Enablement script will only be found on targets where kdump-tools is + pre-installed and it's a version which contains the script. + """ + path = target / ENABLEMENT_SCRIPT[1:] + if path.exists(): + LOG.debug("kernel-crash-dumps enablement script found.") + return True + else: + LOG.debug("kernel-crash-dumps enablement script missing.") + return False + + +def manual_enable(target: Path) -> None: + """Manually enable kernel crash dumps with kdump-tools on target.""" + ensure_kdump_installed(target) + try: + with ChrootableTarget(str(target)) as in_target: + in_target.subp([ENABLEMENT_SCRIPT, "true"]) + except ProcessExecutionError as exc: + # Likely the enablement script hasn't been SRU'd + # Let's not block the install on this. + LOG.warning( + "Unable to run kernel-crash-dumps enablement script: %s", + exc, + ) + + +def manual_disable(target: Path) -> None: + """Manually disable kernel crash dumps with kdump-tools on target.""" + if "kdump-tools" in distro.get_installed_packages(str(target)): + with ChrootableTarget(str(target)) as in_target: + in_target.subp([ENABLEMENT_SCRIPT, "false"]) + + +def automatic_detect(target: Path) -> None: + """Perform conditional enablement with kdump-tools on target. + + Uses the enablement script provided by kdump-tools to detect + system criteria and either enable or disable kernel crash dumps + on the target system. The script is not run if it's not found. + """ + if detection_script_available(target): + LOG.debug("Running conditional enablement script...") + with ChrootableTarget(str(target)) as in_target: + in_target.subp([ENABLEMENT_SCRIPT]) diff --git a/doc/topics/config.rst b/doc/topics/config.rst index c870446..35ba863 100644 --- a/doc/topics/config.rst +++ b/doc/topics/config.rst @@ -21,6 +21,7 @@ Curtin's top level config keys are as follows: - http_proxy (``http_proxy``) - install (``install``) - kernel (``kernel``) +- kernel-crash-dumps (``kernel-crash-dumps``) - kexec (``kexec``) - multipath (``multipath``) - network (``network``) @@ -439,6 +440,51 @@ Specify the exact package to install in the target OS. - xenial: - 4.4.0: -my-custom-kernel +kernel-crash-dumps +~~~~~~~~~~~~~~~~~~ +Configure how Curtin will configure kernel crash dumps in the target system +using the ``kdump-tools`` package. If ``kernel-crash-dumps`` is not configured, +Curtin will attempt to use ``kdump-tools`` to enable kernel crash dumps on the +target machine if certain criteria are met. This requires ``kdump-tools`` to be +installed in the target system before the hook is ran, which will be run +during execution of the hook to determine if the system meets the minimum +requirements based on criteria such as architecture, number of cores, +disk space, and available memory. The hook will not install ``kdump-tools`` +by default. + +**enabled**: *<boolean or None: default None>* + +Enable, disable, or allow ``kdump-tools`` to detect whether kernel crash +dumps should be enabled or disabled on the target system for values of +``true``, ``false``, and ``None``, respectively. + +If ``enabled`` is set to ``true``, Curtin will install the ``kdump-tools`` +package if it is not installed already and then enable kernel crash dumps in +the target system unconditionally. + +If ``enabled`` is set to ``false``, Curtin will ensure kernel crash dumps are +disabled in the target system but it **will not uninstall the package**. + +If ``enabled`` is set to ``null``, Curtin will check that ``kdump-tools`` +is installed in the target system and provides the automatic detection +capability, and if so, will invoke ``kdump-tools`` to detect if the system +meets the minimum criteria and enable or disable kernel crash dumps +accordingly. + +**Examples**:: + + # Default: dynamically enable kernel crash dumps if kdump-tools is installed. + # on the target system. + kernel-crash-dumps: + enabled: null + + # Unconditionally enable kernel-crash-dumps. + kernel-crash-dumps: + enabled: true + + # Unconditionally disable kernel-crash-dumps. + kernel-crash-dumps: + enabled: false kexec ~~~~~ diff --git a/tests/unittests/test_kernel_crash_dumps.py b/tests/unittests/test_kernel_crash_dumps.py new file mode 100644 index 0000000..f1c69e9 --- /dev/null +++ b/tests/unittests/test_kernel_crash_dumps.py @@ -0,0 +1,174 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +from pathlib import Path +from unittest.mock import MagicMock, patch + +from parameterized import parameterized + +from curtin.commands.curthooks import configure_kernel_crash_dumps +from curtin.kernel_crash_dumps import (ENABLEMENT_SCRIPT, automatic_detect, + detection_script_available, + ensure_kdump_installed, manual_disable, + manual_enable) +from tests.unittests.helpers import CiTestCase + + +@patch("curtin.kernel_crash_dumps.manual_disable") +@patch("curtin.kernel_crash_dumps.manual_enable") +@patch("curtin.kernel_crash_dumps.automatic_detect") +class TestKernelCrashDumpsCurthook(CiTestCase): + + @parameterized.expand( + ( + ({"kernel-crash-dumps": {}},), + ({"kernel-crash-dumps": {"enabled": None}},), + ) + ) + def test_config__automatic( + self, + auto_mock, + enable_mock, + disable_mock, + config, + ): + """Test expected automatic configs.""" + + configure_kernel_crash_dumps(config, "/target") + auto_mock.assert_called_once() + enable_mock.assert_not_called() + disable_mock.assert_not_called() + + def test_config__manual_enable( + self, + auto_mock, + enable_mock, + disable_mock, + ): + """Test expected automatic configs.""" + config = {"kernel-crash-dumps": {"enabled": True}} + configure_kernel_crash_dumps(config, "/target") + auto_mock.assert_not_called() + enable_mock.assert_called_once() + disable_mock.assert_not_called() + + def test_config__manual_disable( + self, + auto_mock, + enable_mock, + disable_mock, + ): + """Test expected automatic configs.""" + config = {"kernel-crash-dumps": {"enabled": False}} + configure_kernel_crash_dumps(config, "/target") + auto_mock.assert_not_called() + enable_mock.assert_not_called() + disable_mock.assert_called_once() + + +class TestKernelCrashDumpsUtilities(CiTestCase): + + @parameterized.expand( + ( + (True, True), + (False, False), + ) + ) + def test_detection_script_available(self, preinstalled, expected): + """Test detection_script_available checks for script path.""" + + with patch( + "curtin.kernel_crash_dumps.Path.exists", + return_value=preinstalled, + ): + self.assertEqual(detection_script_available(Path("")), expected) + + @parameterized.expand( + ( + (True,), + (False,), + ) + ) + def test_ensure_kdump_installed(self, preinstalled): + """Test detection of preinstall and install of kdump-tools.""" + + target = Path("/target") + with ( + patch( + "curtin.distro.get_installed_packages", + return_value=["kdump-tools" if preinstalled else ""], + ) + ): + with patch("curtin.distro.install_packages") as do_install: + ensure_kdump_installed(target) + + if preinstalled: + do_install.assert_not_called() + else: + do_install.assert_called_with(["kdump-tools"], target=str(target)) + + def test_manual_enable(self): + """Test manual enablement logic.""" + target = Path("/target") + with patch( + "curtin.kernel_crash_dumps.ensure_kdump_installed", + ) as ensure_mock: + with patch( + "curtin.kernel_crash_dumps.ChrootableTarget", + new=MagicMock(), + ) as chroot_mock: + manual_enable(target) + ensure_mock.assert_called_once() + subp_mock = chroot_mock.return_value.__enter__.return_value.subp + subp_mock.assert_called_with( + [ENABLEMENT_SCRIPT, "true"], + ) + + @parameterized.expand( + ( + (True,), + (False,), + ) + ) + def test_manual_disable(self, preinstalled): + """Test manual disable logic.""" + target = Path("/target") + with patch( + "curtin.distro.get_installed_packages", + return_value=["kdump-tools" if preinstalled else ""], + ): + with patch( + "curtin.kernel_crash_dumps.ChrootableTarget", + new=MagicMock(), + ) as chroot_mock: + manual_disable(target) + + subp_mock = chroot_mock.return_value.__enter__.return_value.subp + if preinstalled: + subp_mock.assert_called_with([ENABLEMENT_SCRIPT, "false"]) + else: + subp_mock.assert_not_called() + + @parameterized.expand( + ( + (True,), + (False,), + ) + ) + def test_automatic_detect(self, wants_enablement): + """Test automatic enablement logic.""" + target = Path("/target") + with patch( + "curtin.kernel_crash_dumps.detection_script_available", + return_value=wants_enablement, + ): + with patch( + "curtin.kernel_crash_dumps.ChrootableTarget", + new=MagicMock(), + ) as chroot_mock: + automatic_detect(target) + + subp_mock = chroot_mock.return_value.__enter__.return_value.subp + if wants_enablement: + subp_mock.assert_called_with([ENABLEMENT_SCRIPT]) + else: + subp_mock.assert_not_called()
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp