Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 48 additions & 23 deletions coriolis/osmorphing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from coriolis.osmorphing.netpreserver import factory

GRUB2_SERIAL = "serial --word=8 --stop=1 --speed=%d --parity=%s --unit=0"
GRUB2_CMDLINE_OPTIONS = ["GRUB_CMDLINE_LINUX", "GRUB_CMDLINE_LINUX_DEFAULT"]
LOG = logging.getLogger(__name__)

IFCFG_TEMPLATE = """
Expand Down Expand Up @@ -756,10 +757,7 @@ def _ensure_cloud_init_not_disabled(self):
if self._test_path(grub_conf_disabler):
contents = self._read_file_sudo(grub_conf_disabler)
if "cloud-init=disabled" in contents:
self._exec_cmd_chroot(
"sed -i '/cloud-init=disabled/d' %s" % grub_conf_disabler
)
self._schedule_grub2_update()
self._update_kernel_cmdline_args(args_to_remove=["cloud-init=disabled"])

@Dany9966 Dany9966 Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that both grub update and grubby are being called. It should be the one or the other, not both.

An ideal implementation would be to call for _update_kernel_cmdline_args indeed, but you only implemented it for redhat based. There should also be a debian implementation which does the former (reads /etc/default/grub, removes/adds/edits the cmdline, schedules grub2 update).

I propose the following:

  1. Use Grub2ConfigEditor whenever possible instead of sed-ing the file directly.
    This implies implementing a new method to remove cmdline entries, call it remove_from_option
    This method should pretty much emulate what grubby does on redhat, and that is if you pass a single option
    (--remove-args=cloud-init), then it will be removed even if it's a key_val, no matter what value cloud-init has
    in cmdline. If you pass a key_val, then only remove the key if the value matches
    (--remove-args=cloud-init=disabled only remove cloud-init from cmdline if it's disabled, but won't
    remove cloud-init=enabled).

  2. We should somehow abstractize this for redhat. On base, when calling for _update_kernel_cmdline_args,
    it should instantiate a Grub2ConfigEditor, append or remove from GRUB_CMDLINE_LINUX and
    GRUB_CMDLINE_LINUX_DEFAULT options (depending on what args_to_add or args_to_remove are being
    passed).
    If it's redhat, then simply use grubby to handle args_to_add/args_to_remove

  3. (only if grubby commands take too long, otherwise treat this as optional) I think the final grubby command should also be run once at the end, so add some schedule_grubby
    methods as well when adding args to remove/add. (similar to _schedule_grub2_update)


def _reset_cloud_init_run(self):
self._exec_cmd_chroot("cloud-init clean --logs")
Expand Down Expand Up @@ -930,26 +928,53 @@ def replace_in_cfg(opt, val):
cfg = self._read_file_sudo(config_obj["location"])
LOG.warning("TEMP CONFIG IS: %r" % cfg)

def _set_grub2_cmdline(self, config_obj, options, clobber=False):
kernel_cmd_def = config_obj["contents"].get("GRUB_CMDLINE_LINUX_DEFAULT")
kernel_cmd = config_obj["contents"].get("GRUB_CMDLINE_LINUX")
replace = kernel_cmd is not None
@staticmethod
def _normalize_kernel_cmdline_args(args):
"""Returns the given kernel command line arguments as a list."""

if clobber:
opt = " ".join(options)
self.set_grub_value("GRUB_CMDLINE_LINUX", opt, config_obj, replace=replace)
return
kernel_cmd_def = kernel_cmd_def or ""
kernel_cmd = kernel_cmd or ""
to_add = []
for option in options:
if option not in kernel_cmd_def and option not in kernel_cmd:
to_add.append(option)
if len(to_add):
kernel_cmd = "%s %s" % (kernel_cmd, " ".join(to_add))
self.set_grub_value(
"GRUB_CMDLINE_LINUX", kernel_cmd, config_obj, replace=replace
if isinstance(args, str):
return [args] if args else []
return list(args or [])

@staticmethod
def _get_kernel_cmdline_arg_value(arg):
"""Converts a kernel command line argument to a Grub2ConfigEditor value."""

key, separator, value = arg.partition("=")
if not separator:
return {"opt_type": "single", "opt_val": key}
return {"opt_type": "key_val", "opt_key": key, "opt_val": value}

def _update_kernel_cmdline_args(self, args_to_add=None, args_to_remove=None):
"""Updates the kernel command line arguments in the GRUB2 defaults.

The arguments are edited in '/etc/default/grub' and the GRUB2 config
regeneration is scheduled so that they reach the boot entries.
"""
args_to_add = self._normalize_kernel_cmdline_args(args_to_add)
args_to_remove = self._normalize_kernel_cmdline_args(args_to_remove)
if not args_to_add and not args_to_remove:
return False

grub_conf = self._get_grub_default_conf()
if not grub_conf:
LOG.warning(
"Could not find '/etc/default/grub'. Skipping kernel command "
"line arguments update."
)
return False

cfg = utils.Grub2ConfigEditor(self._read_file_sudo(grub_conf))
for option in GRUB2_CMDLINE_OPTIONS:
for arg in args_to_remove:
cfg.remove_from_option(option, self._get_kernel_cmdline_arg_value(arg))
for arg in args_to_add:
cfg.remove_from_option(option, self._get_kernel_cmdline_arg_value(arg))
cfg.append_to_option(option, {"opt_type": "single", "opt_val": arg})
self._write_file_sudo(grub_conf.lstrip("/"), cfg.dump())
self._schedule_grub2_update()

return True

def _get_grub_default_conf(self):
grub_conf = "/etc/default/grub"
Expand Down Expand Up @@ -1055,8 +1080,8 @@ def _set_grub2_console_settings(
c = "console=%s" % console
options.append(c)

self._set_grub2_cmdline(config_obj, options)
self._apply_grub2_config(config_obj, execute_update_grub)
self._update_kernel_cmdline_args(args_to_add=options)

def _add_net_udev_rules(self, net_ifaces_info):
coriolis_udev_rules_file = "etc/udev/rules.d/99-coriolis-net.rules"
Expand Down
26 changes: 2 additions & 24 deletions coriolis/osmorphing/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import yaml
from oslo_log import log as logging

from coriolis import constants, exception, utils
from coriolis import constants, exception
from coriolis.osmorphing import base
from coriolis.osmorphing.osdetect import debian as debian_osdetect

Expand Down Expand Up @@ -69,29 +69,7 @@ def check_os_supported(cls, detected_os_info):
)

def disable_predictable_nic_names(self):
grub_cfg = "etc/default/grub"
if self._test_path_chroot(grub_cfg) is False:
return
contents = self._read_file_sudo(grub_cfg)
cfg = utils.Grub2ConfigEditor(contents)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX_DEFAULT",
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX_DEFAULT",
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX",
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX",
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0},
)
self._write_file_sudo("etc/default/grub", cfg.dump())
self._schedule_grub2_update()
self._update_kernel_cmdline_args(args_to_add=["net.ifnames=0", "biosdevname=0"])

def get_update_grub2_command(self):
return "update-grub"
Expand Down
33 changes: 31 additions & 2 deletions coriolis/osmorphing/redhat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import os
import re
import shlex
import uuid

from oslo_log import log as logging
Expand Down Expand Up @@ -56,8 +57,36 @@ def __init__(
)

def disable_predictable_nic_names(self):
cmd = 'grubby --update-kernel=ALL --args="%s"'
self._exec_cmd_chroot(cmd % "net.ifnames=0 biosdevname=0")
self._update_kernel_cmdline_args(args_to_add=["net.ifnames=0", "biosdevname=0"])

def _update_kernel_cmdline_args(self, args_to_add=None, args_to_remove=None):
"""Updates the kernel command line arguments using 'grubby'.

On BLS-based releases (RHEL 9+), 'grub2-mkconfig' does not propagate
kernel arguments into the '/boot/loader/entries' boot entries.
'grubby --update-kernel=ALL' updates every boot entry and replaces
the base implementation rather than supplementing it, so it must not
be paired with a GRUB2 regeneration for the same update.

"""

args_to_add = self._normalize_kernel_cmdline_args(args_to_add)
args_to_remove = self._normalize_kernel_cmdline_args(args_to_remove)
if not args_to_add and not args_to_remove:
return False

for option, args in (
("--remove-args", args_to_remove),
("--args", args_to_add),
):
if not args:
continue
self._exec_cmd_chroot(
"grubby --update-kernel=ALL %s=%s"
% (option, shlex.quote(" ".join(args)))
)

return True

def get_update_grub2_command(self):
location = self._get_grub2_cfg_location()
Expand Down
28 changes: 1 addition & 27 deletions coriolis/osmorphing/suse.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,7 @@ def check_os_supported(cls, detected_os_info):
return False

def disable_predictable_nic_names(self):
grub_cfg = "etc/default/grub"
if not self._test_path(grub_cfg):
LOG.warning(
"Could not find /%s. Skipping predictable NIC names disabling.",
grub_cfg,
)
return
contents = self._read_file_sudo(grub_cfg)
cfg = utils.Grub2ConfigEditor(contents)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX_DEFAULT",
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX_DEFAULT",
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX",
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0},
)
cfg.append_to_option(
"GRUB_CMDLINE_LINUX",
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0},
)
self._write_file_sudo("etc/default/grub", cfg.dump())
self._schedule_grub2_update()
self._update_kernel_cmdline_args(args_to_add=["net.ifnames=0", "biosdevname=0"])

def set_net_config(self, nics_info, dhcp):
if dhcp:
Expand Down
Loading
Loading