diff options
-rw-r--r-- | data/templates/vpp/sysctl.conf.j2 | 15 | ||||
-rw-r--r-- | python/vyos/utils/system.py | 82 | ||||
-rwxr-xr-x | src/conf_mode/vpp.py | 49 |
3 files changed, 112 insertions, 34 deletions
diff --git a/data/templates/vpp/sysctl.conf.j2 b/data/templates/vpp/sysctl.conf.j2 deleted file mode 100644 index 2207e2e38..000000000 --- a/data/templates/vpp/sysctl.conf.j2 +++ /dev/null @@ -1,15 +0,0 @@ -# Number of 2MB hugepages desired -vm.nr_hugepages=1024 - -# Must be greater than or equal to (2 * vm.nr_hugepages). -vm.max_map_count=3096 - -# All groups allowed to access hugepages -vm.hugetlb_shm_group=0 - -# Shared Memory Max must be greater or equal to the total size of hugepages. -# For 2MB pages, TotalHugepageSize = vm.nr_hugepages * 2 * 1024 * 1024 -# If the existing kernel.shmmax setting (cat /proc/sys/kernel/shmmax) -# is greater than the calculated TotalHugepageSize then set this parameter -# to current shmmax value. -kernel.shmmax=2147483648 diff --git a/python/vyos/utils/system.py b/python/vyos/utils/system.py new file mode 100644 index 000000000..7102d5985 --- /dev/null +++ b/python/vyos/utils/system.py @@ -0,0 +1,82 @@ +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +from subprocess import run + + +def sysctl_read(name: str) -> str: + """Read and return current value of sysctl() option + + Args: + name (str): sysctl key name + + Returns: + str: sysctl key value + """ + tmp = run(['sysctl', '-nb', name], capture_output=True) + return tmp.stdout.decode() + + +def sysctl_write(name: str, value: str | int) -> bool: + """Change value via sysctl() + + Args: + name (str): sysctl key name + value (str | int): sysctl key value + + Returns: + bool: True if changed, False otherwise + """ + # convert other types to string before comparison + if not isinstance(value, str): + value = str(value) + # do not change anything if a value is already configured + if sysctl_read(name) == value: + return True + # return False if sysctl call failed + if run(['sysctl', '-wq', f'{name}={value}']).returncode != 0: + return False + # compare old and new values + # sysctl may apply value, but its actual value will be + # different from requested + if sysctl_read(name) == value: + return True + # False in other cases + return False + + +def sysctl_apply(sysctl_dict: dict[str, str], revert: bool = True) -> bool: + """Apply sysctl values. + + Args: + sysctl_dict (dict[str, str]): dictionary with sysctl keys with values + revert (bool, optional): Revert to original values if new were not + applied. Defaults to True. + + Returns: + bool: True if all params configured properly, False in other cases + """ + # get current values + sysctl_original: dict[str, str] = {} + for key_name in sysctl_dict.keys(): + sysctl_original[key_name] = sysctl_read(key_name) + # apply new values and revert in case one of them was not applied + for key_name, value in sysctl_dict.items(): + if not sysctl_write(key_name, value): + if revert: + sysctl_apply(sysctl_original, revert=False) + return False + # everything applied + return True diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index dc13f4e60..87ebc3ea9 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import psutil +from psutil import virtual_memory from pathlib import Path from re import search as re_search, MULTILINE as re_M @@ -26,6 +26,7 @@ from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.ifconfig import Section from vyos.util import call, rc_cmd, boot_configuration_complete +from vyos.utils.system import sysctl_read, sysctl_apply from vyos.template import render from vyos.xml import defaults @@ -39,10 +40,10 @@ airbag.enable() service_name = 'vpp' service_conf = Path(f'/run/vpp/{service_name}.conf') systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf' -sysctl_vpp = '/etc/sysctl.d/80-vpp.conf' -# Min memory 6GB (2GB reserved for vpp) -MIN_TOTAL_MEMORY = 6 +# Free memory required for VPP +# 2 GB for hugepages + 1 GB for other services +MIN_AVAILABLE_MEMORY: int = 3 * 1024**3 def _get_pci_address_by_interface(iface) -> str: @@ -64,7 +65,6 @@ def _get_pci_address_by_interface(iface) -> str: raise ConfigError(f'Cannot find PCI address for interface {iface}') - def get_config(config=None): if config: conf = config @@ -131,32 +131,45 @@ def verify(config): return None if 'interface' not in config: - raise ConfigError(f'"interface" is required but not set!') + raise ConfigError('"interface" is required but not set!') if 'cpu' in config: - if 'corelist_workers' in config['cpu'] and 'main_core' not in config['cpu']: - raise ConfigError(f'"cpu main-core" is required but not set!') + if 'corelist_workers' in config['cpu'] and 'main_core' not in config[ + 'cpu']: + raise ConfigError('"cpu main-core" is required but not set!') - memory = psutil.virtual_memory() - memory_total = round(memory.total / (1024 ** 3), 2) - if memory_total < MIN_TOTAL_MEMORY: + memory_available: int = virtual_memory().available + if memory_available < MIN_AVAILABLE_MEMORY: raise ConfigError( - f'Not enough installed memory {memory_total}GB! ' - f'The minimum required memory is {MIN_TOTAL_MEMORY}GB.' - ) + 'Not enough free memory to start VPP:\n' + f'available: {round(memory_available / 1024**3, 1)}GB\n' + f'required: {round(MIN_AVAILABLE_MEMORY / 1024**3, 1)}GB') def generate(config): if not config or (len(config) == 1 and 'removed_ifaces' in config): # Remove old config and return service_conf.unlink(missing_ok=True) - if os.path.isfile(sysctl_vpp): - os.unlink(sysctl_vpp) return None render(service_conf, 'vpp/startup.conf.j2', config) render(systemd_override, 'vpp/override.conf.j2', config) - render(sysctl_vpp, 'vpp/sysctl.conf.j2', config) + + # apply default sysctl values from + # https://github.com/FDio/vpp/blob/v23.06/src/vpp/conf/80-vpp.conf + sysctl_config: dict[str, str] = { + 'vm.nr_hugepages': '1024', + 'vm.max_map_count': '3096', + 'vm.hugetlb_shm_group': '0', + 'kernel.shmmax': '2147483648' + } + # we do not want to reduce `kernel.shmmax` + kernel_shmnax_current: str = sysctl_read('kernel.shmmax') + if int(kernel_shmnax_current) > int(sysctl_config['kernel.shmmax']): + sysctl_config['kernel.shmmax'] = kernel_shmnax_current + + if not sysctl_apply(sysctl_config): + raise ConfigError('Cannot configure sysctl parameters for VPP') return None @@ -168,8 +181,6 @@ def apply(config): call('systemctl daemon-reload') call(f'systemctl restart {service_name}.service') - call(f'sysctl -qp {sysctl_vpp}') - # Initialize interfaces removed from VPP for iface in config.get('removed_ifaces', []): host_control = HostControl() |