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
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Depends:
file,
iproute2 (>= 6.0.0),
linux-cpupower,
kexec-tools,
# ipaddrcheck is widely used in IP value validators
ipaddrcheck,
ethtool (>= 6.10),
Expand Down
149 changes: 146 additions & 3 deletions src/conf_mode/system_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@

import os
import psutil
import shlex

from sys import exit
from time import sleep


from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.cpu import get_cpus
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.file import read_file
from vyos.utils.kernel import check_kmod
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
Expand Down Expand Up @@ -58,6 +61,28 @@
'virtual-host': 'virtual-host',
}

# Managed keys
MANAGED_SINGLE = {
'mitigations',
'intel_idle.max_cstate',
'processor.max_cstate',
'initcall_blacklist',
'amd_pstate',
'quiet',
'panic',
'hpet',
'mce',
'nosoftlockup',
'isolcpus',
'nohz_full',
'rcu_nocbs',
'nmi_watchdog',
'numa_balancing',
'default_hugepagesz',
}

MANAGED_HUGEPAGES = {'hugepagesz', 'hugepages'}


def _get_total_hugepages_and_memory(config):
unit_map = {'M': 1 << 20, 'G': 1 << 30}
Expand Down Expand Up @@ -112,10 +137,10 @@ def verify(options):
if 'source_address' in config:
address = config['source_address']
if not is_addr_assigned(config['source_address']):
raise ConfigError('No interface with address "{address}" configured!')
raise ConfigError(f'No interface with address "{address}" configured!')

if 'source_interface' in config:
# verify_source_interface reuires key 'ifname'
# verify_source_interface requires key 'ifname'
config['ifname'] = config['source_interface']
verify_source_interface(config)
if 'source_address' in config:
Expand Down Expand Up @@ -244,10 +269,128 @@ def generate(options):

grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))

options['cmdline_options'] = cmdline_options

return None


def generate_cmdline_for_kexec(options):
"""
Build an updated kernel cmdline string based on desired options and the
currently running /proc/cmdline.

Returns:
(kexec_required: bool, new_cmdline: str)
- kexec_required is True if kernel options were added, removed,
or modified.
"""

def parse_token(tok):
return tok.split('=', 1) if '=' in tok else [tok, None]

# Read current cmdline
raw_cmdline = read_file('/proc/cmdline').strip()
tokens = shlex.split(raw_cmdline)

# Separate normal keys vs hugepages
current_single_opts = {}
hugepages_current = []

# Split existing cmdline into normal keys and hugepages pairs
i = 0
while i < len(tokens):
key, val = parse_token(tokens[i])
if key == 'hugepagesz':
size = val
count = None
if i + 1 < len(tokens):
nk, nv = parse_token(tokens[i + 1])
if nk == 'hugepages':
count = nv
i += 1
hugepages_current.append([size, count])
else:
current_single_opts[key] = val
i += 1

kexec_required = False

# Extract desired single-value options from options['cmdline_options']
desired_single_opts = {}
for entry in options.get("cmdline_options", []):
for tok in entry.split():
k, v = parse_token(tok)
if k not in MANAGED_HUGEPAGES:
desired_single_opts[k] = v

# Remove obsolete managed options
for k in list(current_single_opts.keys()):
if k in MANAGED_SINGLE and k not in desired_single_opts:
kexec_required = True
current_single_opts.pop(k)

# Add/update managed single-value options
for k, v in desired_single_opts.items():
if current_single_opts.get(k, '') != v:
kexec_required = True
current_single_opts[k] = v

# Build desired hugepages dictionary
hp_cfg = options.get('kernel', {}).get('memory', {}).get('hugepage_size', {})
desired_hp = {size: cfg.get('hugepage_count') for size, cfg in hp_cfg.items()}

new_hugepages = []
seen_sizes = set()

# Update existing hugepages_pairs and remove obsolete
for size, count in hugepages_current:
if size in desired_hp:
if count != desired_hp[size]:
kexec_required = True
count = desired_hp[size]
new_hugepages.append([size, count])
seen_sizes.add(size)
else:
# obsolete hugepages -> remove
kexec_required = True

# Add new hugepages that do not exist in current cmdline
for size, count in desired_hp.items():
if size not in seen_sizes:
new_hugepages.append([size, count])
kexec_required = True

# Reconstruct final cmdline
new_tokens = []
for key, value in current_single_opts.items():
new_tokens.append(f'{key}={value}' if value is not None else key)

# Append hugepages pairs
for size, count in new_hugepages:
new_tokens.append(f'hugepagesz={size}')
if count is not None:
new_tokens.append(f'hugepages={count}')

cmdline_new = ' '.join(new_tokens)

return kexec_required, cmdline_new


def apply(options):
kexec_required, cmdline_new = generate_cmdline_for_kexec(options)
if kexec_required:
if not boot_configuration_complete() and os.environ.get('VYOS_CONFIGD'):
cmd(
f'sudo kexec -l /boot/vmlinuz --initrd=/boot/initrd.img --command-line="{cmdline_new}"'
)
os.sync()
cmd('sudo systemctl kexec')
elif boot_configuration_complete():
Warning(
'Kernel configuration options have changed. '
'To apply these changes, you must save the configuration and reboot the system!'
)

# System bootup beep
beep_service = 'vyos-beep.service'
if 'startup_beep' in options:
Expand Down
Loading