From b9edbc4f913598baca90e22a943f90635ceb7a29 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 17 Oct 2025 18:37:09 +0200 Subject: frrender: T7927: improve class description --- python/vyos/frrender.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index 75824fcf9..e619cf67c 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -14,7 +14,11 @@ # along with this library. If not, see . """ -Library used to interface with FRRs mgmtd introduced in version 10.0 +Helper class attached to vyos-configd to interfact between our CLI configuration +and FRR. Class will render one full FRR configuration and apply this via +frr-reload.py, if the configuration has no errors. + +Will fail early if the rendered configuration has any errors. """ import os -- cgit v1.2.3 From cf46662573525e0f0791c69405bbf10cde917184 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 17 Oct 2025 18:37:45 +0200 Subject: frrender: T7927: de-nest DHCP and PPPoE interface section for VRFs This update refactors the code path to eliminate unnecessary nesting when processing interfaces using PPPoE or DHCP that are bound to a VRF. Previously, the configuration dictionary was only populated if a static route was defined under the corresponding VRF node. This behavior was incorrect, as interfaces tied to a VRF can receive dynamic routes - such as a default route via DHCP - without having any static routing configuration. A typical use case is a VRF dedicated to out-of-band management. --- python/vyos/frrender.py | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) (limited to 'python') diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index e619cf67c..decec8aa2 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -23,10 +23,16 @@ Will fail early if the rendered configuration has any errors. import os +from copy import deepcopy from time import sleep +from vyos.config import Config +from vyos.config import config_dict_merge +from vyos.configdict import get_dhcp_interfaces +from vyos.configdict import get_pppoe_interfaces from vyos.defaults import frr_debug_enable from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_set_nested from vyos.utils.file import write_file from vyos.utils.process import cmd from vyos.utils.process import rc_cmd @@ -62,12 +68,7 @@ ripng_daemon = 'ripngd' zebra_daemon = 'zebra' nhrp_daemon = 'nhrpd' -def get_frrender_dict(conf, argv=None) -> dict: - from copy import deepcopy - from vyos.config import config_dict_merge - from vyos.configdict import get_dhcp_interfaces - from vyos.configdict import get_pppoe_interfaces - +def get_frrender_dict(conf: Config, argv=None) -> dict: # We need to re-set the CLI path to the root level, as this function uses # conf.exists() with an absolute path form the CLI root conf.set_level([]) @@ -439,17 +440,10 @@ def get_frrender_dict(conf, argv=None) -> dict: # T3680 - get a list of all interfaces currently configured to use DHCP tmp = get_dhcp_interfaces(conf) - if tmp: - if 'static' in dict: - dict['static'].update({'dhcp' : tmp}) - else: - dict.update({'static' : {'dhcp' : tmp}}) + if tmp: dict_set_nested('static.dhcp', tmp, dict) + tmp = get_pppoe_interfaces(conf) - if tmp: - if 'static' in dict: - dict['static'].update({'pppoe' : tmp}) - else: - dict.update({'static' : {'pppoe' : tmp}}) + if tmp: dict_set_nested('static.pppoe', tmp, dict) # keep a re-usable list of dependent VRFs dependent_vrfs_default = {} @@ -577,16 +571,17 @@ def get_frrender_dict(conf, argv=None) -> dict: static = conf.get_config_dict(static_vrf_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf, vrf_name) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf, vrf_name) - if tmp: static.update({'pppoe' : tmp}) - vrf['name'][vrf_name]['protocols'].update({'static': static}) elif conf.exists_effective(static_vrf_path): vrf['name'][vrf_name]['protocols'].update({'static': {'deleted' : ''}}) + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf, vrf_name) + if tmp: dict_set_nested(f'name.{vrf_name}.protocols.static.dhcp', tmp, vrf) + + tmp = get_pppoe_interfaces(conf, vrf_name) + if tmp: dict_set_nested(f'name.{vrf_name}.protocols.static.pppoe', tmp, vrf) + vrf_vni_path = ['vrf', 'name', vrf_name, 'vni'] if conf.exists(vrf_vni_path): vrf_config.update({'vni': conf.return_value(vrf_vni_path)}) -- cgit v1.2.3 From 7c62df1f49bf539aa69fbbedd15656c01d47c17c Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 17 Oct 2025 18:41:36 +0200 Subject: configdict: T7927: refactor check_dhcp() with local VRF argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While debugging and resolving the underlying VRF assignment issue in T7929, this code path was refactored for clarity. Previously, the check_dhcp() helper function relied on the parent scope’s vrf variable. Although this worked correctly, it was not clean or explicit. The function now takes and uses its own vrf argument, improving readability and maintainability. --- python/vyos/configdict.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index d91d88d88..ea244247c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -355,47 +355,48 @@ def is_source_interface(conf, interface, intftype=None): def get_dhcp_interfaces(conf, vrf=None): """ Common helper functions to retrieve all interfaces from current CLI - sessions that have DHCP configured. """ + sessions that have DHCP configured. Only DHCP interfaces in the defined VRF + will be returned. If vrf is None - the default VRF interfaces will be + returned """ + dhcp_interfaces = {} - dict = conf.get_config_dict(['interfaces'], get_first_key=True) - if not dict: + interface_dict = conf.get_config_dict(['interfaces'], get_first_key=True) + if not interface_dict: return dhcp_interfaces - def check_dhcp(config): - ifname = config['ifname'] + def check_dhcp(if_config: dict, vrf=None) -> dict: + ifname = if_config['ifname'] tmp = {} - if 'address' in config and 'dhcp' in config['address']: + if 'address' in if_config and 'dhcp' in if_config['address']: options = {} - if dict_search('dhcp_options.default_route_distance', config) != None: - options.update({'dhcp_options' : config['dhcp_options']}) - if 'vrf' in config: - if vrf == config['vrf']: tmp.update({ifname : options}) + if dict_search('dhcp_options.default_route_distance', if_config) != None: + options.update({'dhcp_options' : if_config['dhcp_options']}) + if 'vrf' in if_config: + if vrf == if_config['vrf']: tmp.update({ifname : options}) else: if vrf is None: tmp.update({ifname : options}) return tmp - for section, interface in dict.items(): + for section, interface in interface_dict.items(): for ifname in interface: - # always reset config level, as get_interface_dict() will alter it - conf.set_level([]) # we already have a dict representation of the config from get_config_dict(), # but with the extended information from get_interface_dict() we also # get the DHCP client default-route-distance default option if not specified. _, ifconfig = get_interface_dict(conf, ['interfaces', section], ifname) - tmp = check_dhcp(ifconfig) + tmp = check_dhcp(ifconfig, vrf=vrf) dhcp_interfaces.update(tmp) # check per VLAN interfaces for vif, vif_config in ifconfig.get('vif', {}).items(): - tmp = check_dhcp(vif_config) + tmp = check_dhcp(vif_config, vrf=vrf) dhcp_interfaces.update(tmp) # check QinQ VLAN interfaces for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items(): - tmp = check_dhcp(vif_s_config) + tmp = check_dhcp(vif_s_config, vrf=vrf) dhcp_interfaces.update(tmp) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - tmp = check_dhcp(vif_c_config) + tmp = check_dhcp(vif_c_config, vrf=vrf) dhcp_interfaces.update(tmp) return dhcp_interfaces @@ -404,7 +405,7 @@ def get_pppoe_interfaces(conf, vrf=None): """ Common helper functions to retrieve all interfaces from current CLI sessions that have DHCP configured. """ pppoe_interfaces = {} - conf.set_level([]) + conf.set_level([]) # required for list_nodes() for ifname in conf.list_nodes(['interfaces', 'pppoe']): # always reset config level, as get_interface_dict() will alter it conf.set_level([]) -- cgit v1.2.3