diff options
| author | Viacheslav Hletenko <v.gletenko@vyos.io> | 2026-02-17 16:05:48 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-17 16:05:48 +0200 |
| commit | 9785f396ba1e82b6b359e42af7da03828b4ff6ed (patch) | |
| tree | 3ca807b3c1210e871fca08c10f167be751c16e7c | |
| parent | 33752c9a41f2e79c1ef3884188968d0f48bc79d8 (diff) | |
| parent | 2414c156cc21bfe51b65f8726f186c55b533bb97 (diff) | |
| download | vyos-1x-9785f396ba1e82b6b359e42af7da03828b4ff6ed.tar.gz vyos-1x-9785f396ba1e82b6b359e42af7da03828b4ff6ed.zip | |
Merge pull request #4993 from natali-rs1985/T8268
vpp: T8268: Unify CPU settings into a single 'cpu-cores' node under 'resource-allocation'
| -rw-r--r-- | data/templates/vpp/startup.conf.j2 | 3 | ||||
| -rw-r--r-- | interface-definitions/vpp.xml.in | 81 | ||||
| -rw-r--r-- | python/vyos/vpp/config_resource_checks/cpu.py | 76 | ||||
| -rw-r--r-- | python/vyos/vpp/config_resource_checks/memory.py | 11 | ||||
| -rw-r--r-- | python/vyos/vpp/config_verify.py | 116 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_vpp.py | 133 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 81 | ||||
| -rw-r--r-- | src/migration-scripts/vpp/7-to-8 | 35 |
8 files changed, 150 insertions, 386 deletions
diff --git a/data/templates/vpp/startup.conf.j2 b/data/templates/vpp/startup.conf.j2 index 218e399d4..b93b53bbd 100644 --- a/data/templates/vpp/startup.conf.j2 +++ b/data/templates/vpp/startup.conf.j2 @@ -18,9 +18,6 @@ cpu { {% if cpu.main_core is vyos_defined %} main-core {{ cpu.main_core }} {% endif %} -{% if cpu.corelist_workers is vyos_defined %} - corelist-workers {{ cpu.corelist_workers | join(',') }} -{% endif %} {% if cpu.skip_cores is vyos_defined %} skip-cores {{ cpu.skip_cores }} {% endif %} diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index 70e3e102c..6c9440302 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -527,6 +527,26 @@ <help>VPP settings</help> </properties> <children> + <node name="resource-allocation"> + <properties> + <help>Resource allocation settings</help> + </properties> + <children> + <leafNode name="cpu-cores"> + <properties> + <help>Create worker threads (including main-core)</help> + <valueHelp> + <format>u32:1-512</format> + <description>Worker threads</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-512"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> + </node> <node name="buffers"> <properties> <help>Buffer settings</help> @@ -572,67 +592,6 @@ </leafNode> </children> </node> - <node name="cpu"> - <properties> - <help>CPU settings</help> - </properties> - <children> - <leafNode name="corelist-workers"> - <properties> - <help>List of cores worker threads</help> - <valueHelp> - <format><id></format> - <description>CPU core id</description> - </valueHelp> - <valueHelp> - <format><idN>-<idM></format> - <description>CPU core id range (use '-' as delimiter)</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--allow-range --range 0-512"/> - </constraint> - <constraintErrorMessage>not a valid CPU core value or range</constraintErrorMessage> - <multi/> - </properties> - </leafNode> - <leafNode name="main-core"> - <properties> - <help>Main core</help> - <valueHelp> - <format>u32:0-512</format> - <description>Assign main thread to specific core</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-512"/> - </constraint> - </properties> - </leafNode> - <leafNode name="skip-cores"> - <properties> - <help>Skip cores</help> - <valueHelp> - <format>u32:1-512</format> - <description>Skip cores</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-512"/> - </constraint> - </properties> - </leafNode> - <leafNode name="workers"> - <properties> - <help>Create worker threads</help> - <valueHelp> - <format>u32:0-4294967295</format> - <description>Worker threads</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-512"/> - </constraint> - </properties> - </leafNode> - </children> - </node> <tagNode name="interface"> <properties> <help>Interface</help> diff --git a/python/vyos/vpp/config_resource_checks/cpu.py b/python/vyos/vpp/config_resource_checks/cpu.py deleted file mode 100644 index b319608e4..000000000 --- a/python/vyos/vpp/config_resource_checks/cpu.py +++ /dev/null @@ -1,76 +0,0 @@ -# Used for validating estimated CPU/physical cores use -# -# Copyright (C) VyOS Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from vyos.utils.cpu import get_available_cpus, get_core_count - -from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map - - -# Get default value for reserved cpu cores -reserved_cpus = default_resource_map.get('reserved_cpu_cores') - - -def available_cores_count(cpu_settings: dict) -> int: - core_count = get_core_count() - - if cpu_settings.get('main_core'): - core_count -= 1 - - skip_cores = int(cpu_settings.get('skip_cores', 0)) - # The default settings assume that - # at least 2 CPU cores should remain reserved for system use - # (only in case of current runtime is not smoke test) - if skip_cores < reserved_cpus: - core_count -= reserved_cpus - else: - core_count -= skip_cores - - return core_count - - -def available_cores_list(skip_cores: int) -> list: - # Available cores are all CPU cores without first N skipped cores that will not be used - # Get all available physical cores - use set to filter out unique values - cpu_cores = set(map(lambda el: el['cpu'], get_available_cpus())) - cpu_cores = list(cpu_cores) - - return cpu_cores[skip_cores:] - - -def worker_cores_list(iface: str, worker_ranges: list) -> list: - all_core_numbers = [] - for worker_range in worker_ranges: - core_numbers = worker_range.split('-') - - if int(core_numbers[0]) > int(core_numbers[-1]): - raise ValueError( - f'Range for "{iface} workers {worker_range}" is not correct' - ) - - all_core_numbers.extend(range(int(core_numbers[0]), int(core_numbers[-1]) + 1)) - - # Check for duplicates - duplicates = set( - [str(x) for n, x in enumerate(all_core_numbers) if x in all_core_numbers[:n]] - ) - if duplicates: - raise ValueError( - f'Some workers in "{iface} workers" are duplicated: #{",".join(list(duplicates))}' - ) - - return all_core_numbers diff --git a/python/vyos/vpp/config_resource_checks/memory.py b/python/vyos/vpp/config_resource_checks/memory.py index 03fe097e9..f1ae6f7bc 100644 --- a/python/vyos/vpp/config_resource_checks/memory.py +++ b/python/vyos/vpp/config_resource_checks/memory.py @@ -174,10 +174,11 @@ def total_memory_required(settings: dict) -> dict: return memory -def buffers_required(settings: dict, workers) -> int: +def buffers_required(settings: dict) -> int: """ Calculate total VPP buffer requirements based on interface settings and workers. """ + workers = int(settings['resource_allocation']['cpu_cores']) buffers_total = 0 for ifname, iface_config in settings.get('interface', {}).items(): # Do not include XDP interfaces in buffer calculations. @@ -185,14 +186,14 @@ def buffers_required(settings: dict, workers) -> int: # so buffer requirements cannot be derived from descriptors here. # Buffers for XDP are handled internally by the kernel/XDP layer, # not by VPP’s buffer allocator. - if iface_config.get('driver') == 'xdp': - continue + # if iface_config.get('driver') == 'xdp': + # continue + dpdk_options = iface_config.get('dpdk_options', {}) rx_queues = int(dpdk_options.get('num_rx_queues', 1)) rx_desc = int(dpdk_options.get('num_rx_desc')) # default TX queues is equal to number of worker threads - # plus 1 main thread - tx_queues = int(dpdk_options.get('num_tx_queues', workers + 1)) + tx_queues = int(dpdk_options.get('num_tx_queues', workers)) tx_desc = int(dpdk_options.get('num_tx_desc')) # buffers for RX/TX queues for interface diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py index bcc10257f..f8bfb214a 100644 --- a/python/vyos/vpp/config_verify.py +++ b/python/vyos/vpp/config_verify.py @@ -22,7 +22,7 @@ from vyos import ConfigError from vyos.base import Warning from vyos.utils.cpu import get_core_count as total_core_count, get_cpus -from vyos.vpp.config_resource_checks import cpu as cpu_checks, memory as mem_checks +from vyos.vpp.config_resource_checks import memory as mem_checks from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map from vyos.vpp.utils import human_memory_to_bytes, bytes_to_human_memory @@ -183,17 +183,13 @@ def verify_dev_driver(driver_type: str, driver: str) -> bool: return False -def create_cpu_error_message( - cpus_required: int, cpus_available: int = None, skip_cores: int = 0 -) -> str: +def create_cpu_error_message(cpus_required: int, cpus_available: int = None) -> str: cpu_info = get_cpus() logical_cores = sum( [int(s.get('siblings')) if 'siblings' in s else 1 for s in cpu_info] ) reserved_cpus = default_resource_map.get('reserved_cpu_cores') - if skip_cores > reserved_cpus: - reserved_cpus = skip_cores available_str = ( ( @@ -320,100 +316,19 @@ def verify_vpp_memory(config: dict): ) -def verify_vpp_settings_cpu_skip_cores(skip_cores: int): - cpu_cores = total_core_count() - - # The number of skipped cores must not be greater than - # available CPU cores in the system - 1 for main thread - if skip_cores > (cpu_cores - 1): - raise ConfigError( - f'The system does not have enough available CPUs to skip ' - f'(reduce "cpu skip-cores" to {cpu_cores} or less)' - ) - - -def verify_vpp_settings_cpu(settings: dict): - """ - Verify 'cpu main-core' is set if worker-related settings are used. - `set vpp settings cpu workers` and `set vpp settings cpu corelist-workers` - are mutually exclusive! - """ - worker_related = ('corelist_workers', 'workers', 'skip_cores') - - if any(key in settings for key in worker_related) and 'main_core' not in settings: - raise ConfigError('"cpu main-core" is required but not set!') - - if 'corelist_workers' in settings and 'workers' in settings: - raise ConfigError( - '"cpu corelist-workers" and "cpu workers" cannot be used at the same time!' - ) - - -def verify_vpp_cpu_main_core(cpu_settings: dict) -> None: - """Check that the main core is available""" - skip_cores = int(cpu_settings.get('skip_cores', 0)) - available_cores = cpu_checks.available_cores_list(skip_cores) - main_core = int(cpu_settings['main_core']) - - if main_core not in available_cores: - raise ConfigError( - 'Cannot set main core for VPP process: ' - f'CPU#{main_core} is not available.' - ) - - -def verify_vpp_settings_cpu_workers(cpu_settings: dict): +def verify_vpp_cpu_cores(cpu_cores: int): """ Verify that the system has enough available CPU cores to run a given amount of worker processes (1 worker/core) """ - workers = int(cpu_settings.get('workers', 0)) - available_cores = cpu_checks.available_cores_count(cpu_settings) - - if workers > available_cores: - raise ConfigError( - f'Not enough free physical CPU cores for {workers} VPP workers '.ljust(72) - + create_cpu_error_message( - workers, available_cores, cpu_settings.get('skip_cores', 0) - ) - ) - - -def verify_vpp_settings_cpu_corelist_workers(cpu_settings: dict): - """ - Verify that the CPU cores provided to the config are free and can be used by VPP - """ - workers = cpu_settings.get('corelist_workers') - main_core = int(cpu_settings.get('main_core')) - skip_cores = int(cpu_settings.get('skip_cores', 0)) - available_cores = cpu_checks.available_cores_list(skip_cores) - try: - all_core_nums = cpu_checks.worker_cores_list( - iface='cpu corelist', worker_ranges=workers - ) - except ValueError as e: - raise ConfigError(str(e)) - - error_msg = 'Cannot set VPP "cpu corelist-workers": '.ljust(72) - - if main_core in all_core_nums: - raise ConfigError( - f'CPU#{main_core} is set as main core and should not ' - 'be included to the corelist-workers' - ) - - invalid_cores = [str(el) for el in all_core_nums if el not in available_cores] - if invalid_cores: - raise ConfigError(error_msg + f'CPU# {",".join(invalid_cores)} not available.') + total_cores = total_core_count() + reserved_cpus = default_resource_map.get('reserved_cpu_cores') + available_cores = total_cores - reserved_cpus - available_cores_count = cpu_checks.available_cores_count(cpu_settings) - if len(all_core_nums) > available_cores_count: + if cpu_cores > available_cores: raise ConfigError( - error_msg - + 'Not enough free physical CPUs in the system.'.ljust(72) - + create_cpu_error_message( - len(all_core_nums), available_cores_count, skip_cores - ) + f'Not enough free physical CPU cores for {cpu_cores} "cpu-cores" '.ljust(72) + + create_cpu_error_message(cpu_cores, available_cores) ) @@ -441,12 +356,12 @@ def verify_vpp_interfaces_dpdk_num_queues(qtype: str, num_queues: int, workers: if num_queues > workers: raise ConfigError( - f'The number of {qtype} queues cannot be greater than the number of configured VPP workers: ' - f'workers: {workers}, queues: {num_queues}' + f'The number of {qtype} queues cannot be greater than the number of configured VPP "cpu-cores": ' + f'cpu-cores: {workers}, queues: {num_queues}' ) -def verify_routes_count(settings: dict, workers: int): +def verify_routes_count(settings: dict): """ Maximum routes count depending on main heap size, statistics segment size and workers @@ -454,12 +369,13 @@ def verify_routes_count(settings: dict, workers: int): counters = 2 # 2 counters for each route bytes = 16 # each counter consumes 16 bytes statseg_scale = 2 + cpu_cores = int(settings['resource_allocation']['cpu_cores']) statseg_size = settings['statseg']['size'] statseg_size_in_bytes = human_memory_to_bytes(statseg_size) main_heap = settings['memory']['main_heap_size'] main_heap_in_gb = human_memory_to_bytes(main_heap) >> 30 - formula = (workers + 1) * counters * bytes * statseg_scale + formula = cpu_cores * counters * bytes * statseg_scale routes_count_statseg = statseg_size_in_bytes / formula routes_count_statseg = round(routes_count_statseg / 1_000_000, 2) routes_count_mh = main_heap_in_gb * 2 @@ -472,10 +388,10 @@ def verify_routes_count(settings: dict, workers: int): ) -def verify_vpp_buffers(settings: dict, workers: int): +def verify_vpp_buffers(settings: dict): buffers_configured = int(settings['buffers']['buffers_per_numa']) - buffers_required = mem_checks.buffers_required(settings, workers) + buffers_required = mem_checks.buffers_required(settings) if buffers_required > buffers_configured: raise ConfigError( diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py index 6cc70a4ac..e6264bbfc 100755 --- a/smoketest/scripts/cli/test_vpp.py +++ b/smoketest/scripts/cli/test_vpp.py @@ -26,6 +26,7 @@ from json import loads from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.utils.cpu import get_available_cpus from vyos.utils.process import process_named_running from vyos.utils.file import read_file from vyos.utils.process import rc_cmd @@ -33,6 +34,7 @@ from vyos.utils.system import sysctl_read from vyos.system import image from vyos.vpp import VPPControl from vyos.vpp.utils import vpp_iface_name_transform +from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map PROCESS_NAME = 'vpp_main' VPP_CONF = '/run/vpp/vpp.conf' @@ -81,6 +83,14 @@ def get_address(interface): return ip_address +def get_vpp_cpu_allocation(): + reserved_cpus = default_resource_map.get('reserved_cpu_cores') + # Get sorted list of available CPU IDs + available = sorted({cpu['cpu'] for cpu in get_available_cpus()}) + main_core = available[reserved_cpus] # first non-reserved CPU + return reserved_cpus, main_core + + class TestVPP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -116,18 +126,10 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): super().tearDown() def test_01_vpp_basic(self): - main_core = '0' poll_sleep = '0' mtu = '2500' + skip_cores, main_core = get_vpp_cpu_allocation() - # Main core must be verified - # expect raise ConfigError - self.cli_set(base_path + ['settings', 'cpu', 'main-core', '99']) - - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core]) self.cli_set(base_path + ['settings', 'poll-sleep-usec', poll_sleep]) # commit changes @@ -135,6 +137,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): config_entries = ( f'poll-sleep-usec {poll_sleep}', + f'skip-cores {skip_cores}', f'main-core {main_core}', 'plugin default { disable }', 'plugin dpdk_plugin.so { enable }', @@ -1128,11 +1131,10 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): dpdk_options = { 'num-rx-desc': '512', 'num-tx-desc': '512', - 'num-rx-queues': '1', - 'num-tx-queues': '1', + 'num-rx-queues': '2', + 'num-tx-queues': '2', } - main_core = '0' - workers = '1' + cpu_cores = '2' base_interface_path = base_path + ['settings', 'interface', interface] @@ -1144,8 +1146,9 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError): self.cli_commit() - self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core]) - self.cli_set(base_path + ['settings', 'cpu', 'workers', workers]) + self.cli_set( + base_path + ['settings', 'resource-allocation', 'cpu-cores', cpu_cores] + ) # # DPDK driver expect only dpdk-options and not xdp-options to be set # # expect raise ConfigError @@ -1165,77 +1168,25 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): for option, value in dpdk_options.items(): self.assertIn(f'{option} {value}', config) - def test_11_vpp_cpu_settings(self): - main_core = '2' - workers = '1' - skip_cores = '1' - - self.cli_set(base_path + ['settings', 'cpu', 'workers', workers]) - - # "cpu workers" reqiures main-core to be set - # expect raise ConfigError - with self.assertRaises(ConfigSessionError): - self.cli_commit() + def test_11_vpp_cpu_cores(self): + cpu_cores = '2' + skip_cores, main_core = get_vpp_cpu_allocation() - self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core]) - - self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', '99']) - - # "cpu skip-cores" cannot be more than number of available CPUs - 1 + # verify 'cpu-cores' are set not correctly # expect raise ConfigError + self.cli_set(base_path + ['settings', 'resource-allocation', 'cpu-cores', '99']) with self.assertRaises(ConfigSessionError): self.cli_commit() - self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', skip_cores]) - - self.cli_commit() - - config_entries = ( - f'skip-cores {skip_cores}', - f'main-core {main_core}', - f'workers {workers}', - 'dev 0000:00:00.0', + self.cli_set( + base_path + ['settings', 'resource-allocation', 'cpu-cores', cpu_cores] ) - - # Check configured options - config = read_file(VPP_CONF) - for config_entry in config_entries: - self.assertIn(config_entry, config) - - def test_12_vpp_cpu_corelist_workers(self): - main_core = '0' - corelist_workers = ['3'] - - for worker in corelist_workers: - self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', worker]) - - # "cpu corelist-workers" reqiures main-core to be set - # expect raise ConfigError - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core]) - - # corelist-workers and workers cannot be used at the same time - # expect raise ConfigError - self.cli_set(base_path + ['settings', 'cpu', 'workers', '2']) - with self.assertRaises(ConfigSessionError): - self.cli_commit() - self.cli_delete(base_path + ['settings', 'cpu', 'workers']) - - # verify corelist-workers are set not correctly - # expect raise ConfigError - self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', '99-101']) - with self.assertRaises(ConfigSessionError): - self.cli_commit() - - self.cli_delete(base_path + ['settings', 'cpu', 'corelist-workers', '99-101']) - self.cli_commit() config_entries = ( - f'main-core {main_core}', - f'corelist-workers {",".join(corelist_workers)}', + f'skip-cores {skip_cores}', # reserved cpus skipped for system use + f'main-core {main_core}', # first available core is set as main-core + f'workers {int(cpu_cores) - 1}', 'dev 0000:00:00.0', ) @@ -1244,7 +1195,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): for config_entry in config_entries: self.assertIn(config_entry, config) - def test_13_1_buffer_page_size(self): + def test_12_1_buffer_page_size(self): sizes = ['4K', '2M'] for size in sizes: self.cli_set(base_path + ['settings', 'buffers', 'page-size', size]) @@ -1253,7 +1204,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): conf = get_vpp_config() self.assertEqual(conf['buffers']['page-size'], size) - def test_13_2_statseg_page_size(self): + def test_12_2_statseg_page_size(self): sizes = ['4K', '2M'] for size in sizes: self.cli_set(base_path + ['settings', 'statseg', 'page-size', size]) @@ -1262,7 +1213,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): conf = get_vpp_config() self.assertEqual(conf['statseg']['page-size'], size) - def test_13_3_mem_page_size(self): + def test_12_3_mem_page_size(self): sizes = ['4K', '2M'] for size in sizes: self.cli_set( @@ -1273,7 +1224,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): conf = get_vpp_config() self.assertEqual(conf['memory']['main-heap-page-size'], size) - def test_14_vpp_ipsec_xfrm_nl(self): + def test_13_vpp_ipsec_xfrm_nl(self): base_lcp = base_path + ['settings', 'lcp'] batch_delay = '250' batch_size = '150' @@ -1299,7 +1250,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): for config_entry in config_entries: self.assertIn(config_entry, config) - def test_15_1_vpp_cgnat(self): + def test_14_1_vpp_cgnat(self): base_cgnat = base_path + ['nat', 'cgnat'] iface_out = 'eth0' iface_inside = 'eth1' @@ -1338,7 +1289,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'tcp transitory timeout: {timeout_tcp_trans}sec', out) self.assertIn(f'icmp timeout: {timeout_icmp}sec', out) - def test_15_2_vpp_cgnat_bond_with_vifs(self): + def test_14_2_vpp_cgnat_bond_with_vifs(self): base_cgnat = base_path + ['nat', 'cgnat'] base_kernel = base_path + ['kernel-interfaces'] base_bond = base_path + ['interfaces', 'bonding'] @@ -1382,7 +1333,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): lines = out.split('\n') self.assertTrue(len(lines) == 3) - def test_16_vpp_nat44(self): + def test_15_vpp_nat44(self): base_nat = base_path + ['nat', 'nat44'] exclude_local_addr = '100.64.0.52' exclude_local_port = '22' @@ -1479,7 +1430,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): out = vpp.api.nat44_show_running_config().forwarding_enabled self.assertTrue(out) - def test_17_vpp_sflow(self): + def test_16_vpp_sflow(self): base_sflow = ['system', 'sflow'] sampling_rate = '1500' polling_interval = '55' @@ -1540,7 +1491,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): _, out = rc_cmd('sudo vppctl show sflow') self.assertIn('interfaces enabled: 0', out) - def test_18_resource_limits(self): + def test_17_resource_limits(self): max_map_count = '100000' shmmax = '55555555555555' hr_path = ['system', 'option', 'resource-limits'] @@ -1567,7 +1518,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.assertEqual(sysctl_read('vm.max_map_count'), '65530') self.assertEqual(sysctl_read('kernel.shmmax'), '8589934592') - def test_19_vpp_pppoe_mapping(self): + def test_18_vpp_pppoe_mapping(self): config_file = '/run/accel-pppd/pppoe.conf' pool = "TEST-POOL" vni = '23' @@ -1617,7 +1568,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_delete(['interfaces', 'ethernet', interface, 'vif']) self.cli_commit() - def test_20_kernel_options_hugepages(self): + def test_19_kernel_options_hugepages(self): default_hp_size = '2M' hp_size_1g = '1G' hp_size_2m = '2M' @@ -1650,7 +1601,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' hugepagesz={hp_size_1g} hugepages={hp_count_1g}', tmp) self.assertIn(f' hugepagesz={hp_size_2m} hugepages={hp_count_2m}', tmp) - def test_21_static_arp(self): + def test_20_static_arp(self): host = '192.0.2.10' mac = '00:01:02:03:04:0a' path_static_arp = ['protocols', 'static', 'arp'] @@ -1674,7 +1625,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): self.cli_delete(path_static_arp) - def test_22_1_vpp_ipfix(self): + def test_21_1_vpp_ipfix(self): base_ipfix = base_path + ['ipfix'] base_collector = base_ipfix + ['collector'] collector_ip = '127.0.0.2' @@ -1761,7 +1712,7 @@ class TestVPP(VyOSUnitTestSHIM.TestCase): len(non_default_exporters), 0, 'Exporters not cleaned up properly' ) - def test_22_2_vpp_ipfix_bond(self): + def test_21_2_vpp_ipfix_bond(self): base_ipfix = base_path + ['ipfix'] base_bond = base_path + ['interfaces', 'bonding'] iface_bond = 'bond0' diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index 2634eae44..c961132d8 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -35,6 +35,7 @@ from vyos.ifconfig import Section from vyos.logger import getLogger from vyos.template import render from vyos.utils.boot import boot_configuration_complete +from vyos.utils.cpu import get_available_cpus from vyos.utils.kernel import check_kmod from vyos.utils.kernel import unload_kmod from vyos.utils.kernel import list_loaded_modules @@ -49,11 +50,7 @@ from vyos.vpp.config_verify import ( verify_dev_driver, verify_vpp_minimum_cpus, verify_vpp_minimum_memory, - verify_vpp_settings_cpu, - verify_vpp_settings_cpu_corelist_workers, - verify_vpp_cpu_main_core, - verify_vpp_settings_cpu_skip_cores, - verify_vpp_settings_cpu_workers, + verify_vpp_cpu_cores, verify_vpp_memory, verify_vpp_statseg_size, verify_vpp_interfaces_dpdk_num_queues, @@ -62,6 +59,7 @@ from vyos.vpp.config_verify import ( verify_vpp_buffers, ) from vyos.vpp.config_resource_checks import memory +from vyos.vpp.config_resource_checks.resource_defaults import default_resource_map from vyos.vpp.config_filter import iface_filter_eth from vyos.vpp.utils import EthtoolGDrvinfo from vyos.vpp.configdb import JSONStorage @@ -152,25 +150,28 @@ def _unload_module(module_name: str): raise -def _get_workers_count(cpu_settings: dict) -> int: - if 'corelist_workers' in cpu_settings: - corelist_workers = [] - for worker_range in cpu_settings['corelist_workers']: - core_numbers = worker_range.split('-') - corelist_workers.extend( - range(int(core_numbers[0]), int(core_numbers[-1]) + 1) - ) +def _configure_vpp_cpu_settings(config: dict): + """Configure VPP CPU settings: main-core, workers and skip-cores based on 'cpu-cores'""" + cpu_cores = int(config['settings']['resource_allocation']['cpu_cores']) + reserved_cpus = default_resource_map.get('reserved_cpu_cores') - return len(corelist_workers) + # Get sorted list of available CPU IDs + available = sorted({cpu['cpu'] for cpu in get_available_cpus()}) - return int(cpu_settings.get('workers', 0)) + if reserved_cpus < len(available): + main_core = available[reserved_cpus] # first non-reserved CPU + config['settings']['cpu'] = { + 'main_core': str(main_core), + 'skip_cores': str(reserved_cpus), + } + if cpu_cores > 1: + config['settings']['cpu']['workers'] = str(cpu_cores - 1) def _normalize_buffers(config: dict): """Replace 'auto' buffers_per_numa with calculated value""" if config['settings']['buffers']['buffers_per_numa'] == 'auto': - workers = _get_workers_count(config['settings'].get('cpu', {})) - buffers = memory.buffers_required(config['settings'], workers) + buffers = memory.buffers_required(config['settings']) config['settings']['buffers']['buffers_per_numa'] = str(buffers) @@ -398,15 +399,18 @@ def get_config(config=None): ) if 'zero-copy' in iface_config['xdp_options']: xdp_api_params['mode'] = 'zero-copy' - if iface_config.get('rx_mode') in ('interrupt', 'adaptive') and any( - key in config['settings'].get('cpu', {}) - for key in ('workers', 'corelist_workers') + if ( + iface_config.get('rx_mode') in ('interrupt', 'adaptive') + and int(config['settings']['resource_allocation']['cpu_cores']) + > 1 ): xdp_api_params['flags'] = 'no_syscall_lock' iface_config['xdp_api_params'] = xdp_api_params # Buffer normalization (auto → computed) _normalize_buffers(config) + # Configure VPP main-core and workers 'cpu-cores' settings + _configure_vpp_cpu_settings(config) if removed_ifaces: config['removed_ifaces'] = removed_ifaces @@ -507,38 +511,15 @@ def verify(config): raise ConfigError(f'Interface {iface} does not exist or is not Ethernet!') # Resource usage checks - cpu_settings = config['settings'].get('cpu', {}) - - if not any(key in cpu_settings for key in ('workers', 'corelist_workers')): - verify_vpp_minimum_cpus() - - if 'cpu' in config['settings']: - # Check whether the workers and corelist-workers are configured properly - verify_vpp_settings_cpu(cpu_settings) - - # Check if there are enough CPU cores to skip according to config - if 'skip_cores' in cpu_settings: - skip_cores = int(cpu_settings['skip_cores']) - verify_vpp_settings_cpu_skip_cores(skip_cores) - - # Check if there are enough CPU cores to add workers - if 'workers' in cpu_settings: - verify_vpp_settings_cpu_workers(cpu_settings) - - if 'main_core' in cpu_settings: - verify_vpp_cpu_main_core(cpu_settings) - - # Check the CPU main core not falling to the corelist-workers - if 'corelist_workers' in cpu_settings: - verify_vpp_settings_cpu_corelist_workers(cpu_settings) - - workers = _get_workers_count(config['settings'].get('cpu', {})) + cpu_cores = int(config['settings']['resource_allocation']['cpu_cores']) + verify_vpp_minimum_cpus() + verify_vpp_cpu_cores(cpu_cores) verify_vpp_main_heap_size(config['settings']) verify_vpp_statseg_size(config['settings']) # Check buffers - verify_vpp_buffers(config['settings'], workers) + verify_vpp_buffers(config['settings']) # Check if available memory is enough for current VPP config verify_vpp_memory(config) @@ -579,13 +560,13 @@ def verify(config): if 'num_rx_queues' in iface_config['dpdk_options']: rx_queues = int(iface_config['dpdk_options']['num_rx_queues']) verify_vpp_interfaces_dpdk_num_queues( - qtype='receive', num_queues=rx_queues, workers=workers + qtype='receive', num_queues=rx_queues, workers=cpu_cores ) if 'num_tx_queues' in iface_config['dpdk_options']: tx_queues = int(iface_config['dpdk_options']['num_tx_queues']) verify_vpp_interfaces_dpdk_num_queues( - qtype='transmit', num_queues=tx_queues, workers=workers + qtype='transmit', num_queues=tx_queues, workers=cpu_cores ) # RX-mode verification @@ -652,7 +633,7 @@ def verify(config): f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed' ) - verify_routes_count(config['settings'], workers) + verify_routes_count(config['settings']) def generate(config): diff --git a/src/migration-scripts/vpp/7-to-8 b/src/migration-scripts/vpp/7-to-8 index fa105a6b5..0b2f81b4c 100644 --- a/src/migration-scripts/vpp/7-to-8 +++ b/src/migration-scripts/vpp/7-to-8 @@ -20,6 +20,8 @@ # Drop settings for nat workers (T8254) # # Delete 'ipsec' node and all settings and replace it with single 'ipsec-acceleration' flag (T8262) +# +# Unify CPU settings into a single 'cpu-cores' node under 'resource-allocation' (T8268) from vyos.configtree import ConfigTree @@ -71,6 +73,38 @@ def _migrate_vpp_ipsec(config: ConfigTree) -> None: config.set(base + ['ipsec-acceleration']) config.delete(base + ['ipsec']) +def _migrate_vpp_cpu(config: ConfigTree) -> None: + settings_path = ['vpp', 'settings'] + cpu_path = settings_path + ['cpu'] + + if not config.exists(cpu_path): + # Nothing to do + return + + # get number of configured workers + workers = 0 + + if config.exists(cpu_path + ['workers']): + workers += int(config.return_value(cpu_path + ['workers'])) + # add main-core to total workers + workers += 1 + + if config.exists(cpu_path + ['corelist-workers']): + def _count_range(item: str) -> int: + if '-' in item: + start, end = map(int, item.split('-')) + return end - start + 1 + return 1 + + tmp = config.return_values(cpu_path + ['corelist-workers']) + workers = sum(_count_range(item) for item in tmp) + 1 # + main core + + # set 'resource-allocation cpu-cores' + if workers: + config.set(settings_path + ['resource-allocation', 'cpu-cores'], value=str(workers)) + + config.delete(cpu_path) + def migrate(config: ConfigTree) -> None: if not config.exists(['vpp']): @@ -81,3 +115,4 @@ def migrate(config: ConfigTree) -> None: _migrate_vpp_log(config) _migrate_vpp_nat44(config) _migrate_vpp_ipsec(config) + _migrate_vpp_cpu(config) |
