summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/vpp.xml.in3
-rw-r--r--python/vyos/component_version.py10
-rw-r--r--python/vyos/utils/file.py36
-rw-r--r--python/vyos/vpp/config_resource_checks/resource_defaults.py2
-rw-r--r--python/vyos/vpp/config_verify.py34
-rwxr-xr-xsrc/conf_mode/vpp.py3
-rwxr-xr-xsrc/helpers/vyos-save-config.py16
-rw-r--r--src/services/api/rest/routers.py6
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')