diff options
| author | Nataliia S. <81954790+natali-rs1985@users.noreply.github.com> | 2025-08-19 11:04:38 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-19 11:04:38 +0200 |
| commit | 3bfcf335c5f16a6c814710b01f38342c40f85a8d (patch) | |
| tree | efa1d64693f8851c31b6b1851967fb9e9d6cd2b2 | |
| parent | 96fb2848e61cc5de677d9e6ad7f5c55ae397a698 (diff) | |
| parent | 69cd4845861c88285ab496339f19103dec7a32b6 (diff) | |
| download | vyos-1x-3bfcf335c5f16a6c814710b01f38342c40f85a8d.tar.gz vyos-1x-3bfcf335c5f16a6c814710b01f38342c40f85a8d.zip | |
Merge branch 'current' into T7678
| -rw-r--r-- | interface-definitions/vpp.xml.in | 3 | ||||
| -rw-r--r-- | python/vyos/component_version.py | 10 | ||||
| -rw-r--r-- | python/vyos/utils/file.py | 36 | ||||
| -rw-r--r-- | python/vyos/vpp/config_resource_checks/resource_defaults.py | 2 | ||||
| -rw-r--r-- | python/vyos/vpp/config_verify.py | 34 | ||||
| -rwxr-xr-x | src/conf_mode/vpp.py | 3 | ||||
| -rwxr-xr-x | src/helpers/vyos-save-config.py | 16 | ||||
| -rw-r--r-- | src/services/api/rest/routers.py | 6 |
8 files changed, 100 insertions, 10 deletions
diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in index a0cd0be45..f69e05168 100644 --- a/interface-definitions/vpp.xml.in +++ b/interface-definitions/vpp.xml.in @@ -777,6 +777,7 @@ <help>Main heap page size</help> #include <include/unformat_log2_page_size.xml.i> </properties> + <defaultValue>2M</defaultValue> </leafNode> <leafNode name="default-hugepage-size"> <properties> @@ -904,12 +905,14 @@ <help>Size of stats segment</help> #include <include/unformat_memory_size.xml.i> </properties> + <defaultValue>128M</defaultValue> </leafNode> <leafNode name="page-size"> <properties> <help>Stats page size</help> #include <include/unformat_log2_page_size.xml.i> </properties> + <defaultValue>2M</defaultValue> </leafNode> </children> </node> diff --git a/python/vyos/component_version.py b/python/vyos/component_version.py index 136bd36e8..13fb8333f 100644 --- a/python/vyos/component_version.py +++ b/python/vyos/component_version.py @@ -209,6 +209,16 @@ def version_info_prune_component(x: VersionInfo, y: VersionInfo) -> VersionInfo: x.component = {k: v for k, v in x.component.items() if k in y.component} +def add_system_version_string(config_str: str = None) -> str: + """Wrap config string with system version and return string.""" + version_info = version_info_from_system() + if config_str is not None: + version_info.update_config_body(config_str) + version_info.update_footer() + + return version_info.write_string() + + def add_system_version(config_str: str = None, out_file: str = None): """Wrap config string with system version and write to out_file. diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py index 1e2de2b39..c363b5bdc 100644 --- a/python/vyos/utils/file.py +++ b/python/vyos/utils/file.py @@ -14,6 +14,9 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. import os +import tempfile +from contextlib import contextmanager + from vyos.utils.permission import chown def makedir(path, user=None, group=None): @@ -185,3 +188,36 @@ def wait_for_file_write_complete(file_path, pre_hook=None, timeout=None, sleep_i """ Waits for a process to close a file after opening it in write mode. """ wait_for_inotify(file_path, event_type='IN_CLOSE_WRITE', pre_hook=pre_hook, timeout=timeout, sleep_interval=sleep_interval) + +def copy_chown(source, target): + import shutil + import stat + + shutil.copy2(source, target) + st = os.stat(source) + os.chown(target, st[stat.ST_UID], st[stat.ST_GID]) + + +@contextmanager +def write_file_atomic(file_path, mode='w'): + with open(file_path, mode) as _: + pass + temp_file = tempfile.NamedTemporaryFile( + delete=False, dir=os.path.dirname(file_path) + ) + if os.path.exists(file_path): + copy_chown(file_path, temp_file.name) + + file = open(temp_file.name, mode) + try: + yield file + finally: + file.flush() + os.fsync(file.fileno()) + file.close() + os.replace(temp_file.name, file_path) + if os.path.exists(temp_file.name): + try: + os.unlink(temp_file.name) + except Exception: + pass diff --git a/python/vyos/vpp/config_resource_checks/resource_defaults.py b/python/vyos/vpp/config_resource_checks/resource_defaults.py index 797b45701..d8316fcaf 100644 --- a/python/vyos/vpp/config_resource_checks/resource_defaults.py +++ b/python/vyos/vpp/config_resource_checks/resource_defaults.py @@ -31,7 +31,7 @@ default_resource_map = { # Default size of buffers transferred via netlink 'netlink_rx_buffer_size': 212992, # Default amount of memory allocated for VPP stats segment usage - 'statseg_heap_size': '96M', + 'statseg_heap_size': '128M', # Minimal amount of memory required to start VPP 'min_memory': '8G', # Minimal number of physical CPU cores required to start VPP diff --git a/python/vyos/vpp/config_verify.py b/python/vyos/vpp/config_verify.py index 9c56c8b95..c6f43e340 100644 --- a/python/vyos/vpp/config_verify.py +++ b/python/vyos/vpp/config_verify.py @@ -215,8 +215,8 @@ def verify_vpp_memory(config: dict): main_heap_size = mem_checks.memory_main_heap(config['settings']) main_heap_page_size = mem_checks.main_heap_page_size(config['settings']) - if main_heap_size < 51 << 20: - raise ConfigError('The main heap size must be greater than or equal to 51M') + if main_heap_size < 1 << 30: + raise ConfigError('The main heap size must be greater than or equal to 1G') readable_heap_page = bytes_to_human_memory(main_heap_page_size, 'K') @@ -367,8 +367,8 @@ def verify_vpp_statseg_size(settings: dict): statseg_size = mem_checks.statseg_size(settings) if 'size' in settings.get('statseg'): - if statseg_size < 1 << 20: - raise ConfigError('The statseg size must be greater than or equal to 1M') + if statseg_size < 128 << 20: + raise ConfigError('The statseg size must be greater than or equal to 128M') if 'page_size' in settings['statseg']: statseg_page_size = mem_checks.statseg_page_size(settings) @@ -390,3 +390,29 @@ def verify_vpp_interfaces_dpdk_num_queues(qtype: str, num_queues: int, workers: f'The number of {qtype} queues cannot be greater than the number of configured VPP workers: ' f'workers: {workers}, queues: {num_queues}' ) + + +def verify_routes_count(settings: dict, workers: int): + """ + Maximum routes count depending on main heap size, + statistics segment size and workers + """ + counters = 2 # 2 counters for each route + bytes = 16 # each counter consumes 16 bytes + statseg_scale = 2 + 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 + 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 + routes_count_min = min(routes_count_statseg, routes_count_mh) + Warning( + f'NOTE: Current dataplane capacity (estimated): {routes_count_min} M IPv4 routes. ' + 'Exceeding these values will lead to a dataplane out-of-memory condition and a crash. ' + 'Extensive use of features like ACLs, NAT and others may reduce the numbers above. ' + 'Please read the documentation for details: https://docs.vyos.io/' + ) diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py index ace3c9f9f..966158ab7 100755 --- a/src/conf_mode/vpp.py +++ b/src/conf_mode/vpp.py @@ -52,6 +52,7 @@ from vyos.vpp.config_verify import ( verify_vpp_memory, verify_vpp_statseg_size, verify_vpp_interfaces_dpdk_num_queues, + verify_routes_count, ) from vyos.vpp.config_filter import iface_filter_eth from vyos.vpp.utils import EthtoolGDrvinfo @@ -460,6 +461,8 @@ def verify(config): f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed' ) + verify_routes_count(config['settings'], workers) + def generate(config): if not config or ('removed_ifaces' in config and 'settings' not in config): diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py index adf62b71d..fa1f5625d 100755 --- a/src/helpers/vyos-save-config.py +++ b/src/helpers/vyos-save-config.py @@ -23,15 +23,18 @@ from argparse import ArgumentParser from vyos.config import Config from vyos.remote import urlc -from vyos.component_version import add_system_version +from vyos.component_version import add_system_version_string from vyos.defaults import directories +from vyos.utils.file import write_file_atomic DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') remote_save = None parser = ArgumentParser(description='Save configuration') parser.add_argument('file', type=str, nargs='?', help='Save configuration to file') -parser.add_argument('--write-json-file', type=str, help='Save JSON of configuration to file') +parser.add_argument( + '--write-json-file', type=str, help='Save JSON of configuration to file' +) args = parser.parse_args() file = args.file json_file = args.write_json_file @@ -56,7 +59,14 @@ write_file = save_file if remote_save is None else NamedTemporaryFile(delete=Fal # config_tree is None before boot configuration is complete; # automated saves should check boot_configuration_complete config_str = None if ct is None else ct.to_string() -add_system_version(config_str, write_file) +versioned_config_str = add_system_version_string(config_str) + +try: + with write_file_atomic(write_file) as f: + f.write(versioned_config_str) +except OSError as e: + print(f'failed to write config file: {e}') + sys.exit(1) if json_file is not None and ct is not None: try: diff --git a/src/services/api/rest/routers.py b/src/services/api/rest/routers.py index 329d6e51f..d1383a356 100644 --- a/src/services/api/rest/routers.py +++ b/src/services/api/rest/routers.py @@ -511,12 +511,14 @@ async def configure_op( @router.post('/configure-section') -def configure_section_op( +async def configure_section_op( data: Union[ConfigSectionModel, ConfigSectionListModel, ConfigSectionTreeModel], request: Request, background_tasks: BackgroundTasks, ): - return _configure_op(data, request, background_tasks) + out = await _configure_op(data, request, background_tasks) + + return out @router.post('/retrieve') |
