summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--python/vyos/configdict.py495
-rw-r--r--python/vyos/frrender.py520
-rw-r--r--python/vyos/ifconfig/interface.py11
-rw-r--r--python/vyos/ifconfig/macvlan.py5
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py2
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py2
-rwxr-xr-xsrc/conf_mode/policy.py2
-rwxr-xr-xsrc/conf_mode/protocols_babel.py2
-rwxr-xr-xsrc/conf_mode/protocols_bfd.py2
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/protocols_eigrp.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py2
-rwxr-xr-xsrc/conf_mode/protocols_mpls.py2
-rw-r--r--src/conf_mode/protocols_openfabric.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py2
-rwxr-xr-xsrc/conf_mode/protocols_pim.py2
-rwxr-xr-xsrc/conf_mode/protocols_pim6.py2
-rwxr-xr-xsrc/conf_mode/protocols_rip.py2
-rwxr-xr-xsrc/conf_mode/protocols_ripng.py2
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py2
-rwxr-xr-xsrc/conf_mode/protocols_segment-routing.py2
-rwxr-xr-xsrc/conf_mode/protocols_static.py2
-rwxr-xr-xsrc/conf_mode/system_ip.py2
-rwxr-xr-xsrc/conf_mode/system_ipv6.py2
-rwxr-xr-xsrc/conf_mode/vrf.py2
-rwxr-xr-xsrc/services/vyos-configd3
27 files changed, 534 insertions, 544 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index cbcbf9f72..5a353b110 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -19,7 +19,6 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.
import os
import json
-from vyos.defaults import frr_debug_enable
from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
@@ -665,497 +664,3 @@ def get_accel_dict(config, base, chap_secrets, with_pki=False):
dict['authentication']['radius']['server'][server]['acct_port'] = '0'
return dict
-
-def get_frrender_dict(conf, argv=None) -> dict:
- from copy import deepcopy
- from vyos.config import config_dict_merge
- from vyos.frrender import frr_protocols
-
- # Create an empty dictionary which will be filled down the code path and
- # returned to the caller
- dict = {}
-
- if argv and len(argv) > 1:
- dict['vrf_context'] = argv[1]
-
- def dict_helper_ospf_defaults(ospf, path):
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
- get_first_key=True, recursive=True)
-
- # We have to cleanup the default dict, as default values could enable features
- # which are not explicitly enabled on the CLI. Example: default-information
- # originate comes with a default metric-type of 2, which will enable the
- # entire default-information originate tree, even when not set via CLI so we
- # need to check this first and probably drop that key.
- if dict_search('default_information.originate', ospf) is None:
- del default_values['default_information']
- if 'mpls_te' not in ospf:
- del default_values['mpls_te']
- if 'graceful_restart' not in ospf:
- del default_values['graceful_restart']
- for area_num in default_values.get('area', []):
- if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
- del default_values['area'][area_num]['area_type']['nssa']
-
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
- if dict_search(f'redistribute.{protocol}', ospf) is None:
- del default_values['redistribute'][protocol]
- if not bool(default_values['redistribute']):
- del default_values['redistribute']
-
- for interface in ospf.get('interface', []):
- # We need to reload the defaults on every pass b/c of
- # hello-multiplier dependency on dead-interval
- # If hello-multiplier is set, we need to remove the default from
- # dead-interval.
- if 'hello_multiplier' in ospf['interface'][interface]:
- del default_values['interface'][interface]['dead_interval']
-
- ospf = config_dict_merge(default_values, ospf)
- return ospf
-
- def dict_helper_ospfv3_defaults(ospfv3, path):
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
- get_first_key=True, recursive=True)
-
- # We have to cleanup the default dict, as default values could enable features
- # which are not explicitly enabled on the CLI. Example: default-information
- # originate comes with a default metric-type of 2, which will enable the
- # entire default-information originate tree, even when not set via CLI so we
- # need to check this first and probably drop that key.
- if dict_search('default_information.originate', ospfv3) is None:
- del default_values['default_information']
- if 'graceful_restart' not in ospfv3:
- del default_values['graceful_restart']
-
- for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
- if dict_search(f'redistribute.{protocol}', ospfv3) is None:
- del default_values['redistribute'][protocol]
- if not bool(default_values['redistribute']):
- del default_values['redistribute']
-
- default_values.pop('interface', {})
-
- # merge in remaining default values
- ospfv3 = config_dict_merge(default_values, ospfv3)
- return ospfv3
-
- def dict_helper_pim_defaults(pim, path):
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
- get_first_key=True, recursive=True)
-
- # We have to cleanup the default dict, as default values could enable features
- # which are not explicitly enabled on the CLI.
- for interface in pim.get('interface', []):
- if 'igmp' not in pim['interface'][interface]:
- del default_values['interface'][interface]['igmp']
-
- pim = config_dict_merge(default_values, pim)
- return pim
-
- # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR
- tmp = {}
- for if_type in ['ethernet', 'bonding']:
- interface_path = ['interfaces', if_type]
- if not conf.exists(interface_path):
- continue
- for interface in conf.list_nodes(interface_path):
- evpn_path = interface_path + [interface, 'evpn']
- if not conf.exists(evpn_path):
- continue
-
- evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_'))
- tmp.update({interface : evpn})
- # At least one participating EVPN interface found, add to result dict
- if tmp: dict['interfaces'] = tmp
-
- # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols
- for ip_version in ['ip', 'ipv6']:
- ip_cli_path = ['system', ip_version]
- ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'),
- get_first_key=True, with_recursive_defaults=True)
- if ip_dict:
- ip_dict['afi'] = ip_version
- dict.update({ip_version : ip_dict})
-
- # Enable SNMP agentx support
- # SNMP AgentX support cannot be disabled once enabled
- if conf.exists(['service', 'snmp']):
- dict['snmp'] = {}
-
- # We will always need the policy key
- dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # We need to check the CLI if the BABEL node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- babel_cli_path = ['protocols', 'babel']
- if conf.exists(babel_cli_path):
- babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
- dict.update({'babel' : babel})
-
- # We need to check the CLI if the BFD node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- bfd_cli_path = ['protocols', 'bfd']
- if conf.exists(bfd_cli_path):
- bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
- dict.update({'bfd' : bfd})
-
- # We need to check the CLI if the BGP node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- bgp_cli_path = ['protocols', 'bgp']
- if conf.exists(bgp_cli_path):
- bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
- bgp['dependent_vrfs'] = {}
- dict.update({'bgp' : bgp})
- elif conf.exists_effective(bgp_cli_path):
- dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}})
-
- # We need to check the CLI if the EIGRP node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- eigrp_cli_path = ['protocols', 'eigrp']
- if conf.exists(eigrp_cli_path):
- isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
- dict.update({'eigrp' : isis})
- elif conf.exists_effective(eigrp_cli_path):
- dict.update({'eigrp' : {'deleted' : ''}})
-
- # We need to check the CLI if the ISIS node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- isis_cli_path = ['protocols', 'isis']
- if conf.exists(isis_cli_path):
- isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
- dict.update({'isis' : isis})
- elif conf.exists_effective(isis_cli_path):
- dict.update({'isis' : {'deleted' : ''}})
-
- # We need to check the CLI if the MPLS node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- mpls_cli_path = ['protocols', 'mpls']
- if conf.exists(mpls_cli_path):
- mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'),
- get_first_key=True)
- dict.update({'mpls' : mpls})
- elif conf.exists_effective(mpls_cli_path):
- dict.update({'mpls' : {'deleted' : ''}})
-
- # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- openfabric_cli_path = ['protocols', 'openfabric']
- if conf.exists(openfabric_cli_path):
- openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
- dict.update({'openfabric' : openfabric})
- elif conf.exists_effective(openfabric_cli_path):
- dict.update({'openfabric' : {'deleted' : ''}})
-
- # We need to check the CLI if the OSPF node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- ospf_cli_path = ['protocols', 'ospf']
- if conf.exists(ospf_cli_path):
- ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'),
- get_first_key=True)
- ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path)
- dict.update({'ospf' : ospf})
- elif conf.exists_effective(ospf_cli_path):
- dict.update({'ospf' : {'deleted' : ''}})
-
- # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- ospfv3_cli_path = ['protocols', 'ospfv3']
- if conf.exists(ospfv3_cli_path):
- ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'),
- get_first_key=True)
- ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path)
- dict.update({'ospfv3' : ospfv3})
- elif conf.exists_effective(ospfv3_cli_path):
- dict.update({'ospfv3' : {'deleted' : ''}})
-
- # We need to check the CLI if the PIM node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- pim_cli_path = ['protocols', 'pim']
- if conf.exists(pim_cli_path):
- pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'),
- get_first_key=True)
- pim = dict_helper_pim_defaults(pim, pim_cli_path)
- dict.update({'pim' : pim})
- elif conf.exists_effective(pim_cli_path):
- dict.update({'pim' : {'deleted' : ''}})
-
- # We need to check the CLI if the PIM6 node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- pim6_cli_path = ['protocols', 'pim6']
- if conf.exists(pim6_cli_path):
- pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
- dict.update({'pim6' : pim6})
- elif conf.exists_effective(pim6_cli_path):
- dict.update({'pim6' : {'deleted' : ''}})
-
- # We need to check the CLI if the RIP node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- rip_cli_path = ['protocols', 'rip']
- if conf.exists(rip_cli_path):
- rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
- dict.update({'rip' : rip})
- elif conf.exists_effective(rip_cli_path):
- dict.update({'rip' : {'deleted' : ''}})
-
- # We need to check the CLI if the RIPng node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- ripng_cli_path = ['protocols', 'ripng']
- if conf.exists(ripng_cli_path):
- ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
- dict.update({'ripng' : ripng})
- elif conf.exists_effective(ripng_cli_path):
- dict.update({'ripng' : {'deleted' : ''}})
-
- # We need to check the CLI if the RPKI node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- rpki_cli_path = ['protocols', 'rpki']
- if conf.exists(rpki_cli_path):
- rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'),
- get_first_key=True, with_pki=True,
- with_recursive_defaults=True)
- rpki_ssh_key_base = '/run/frr/id_rpki'
- for cache, cache_config in rpki.get('cache',{}).items():
- if 'ssh' in cache_config:
- cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
- cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
- dict.update({'rpki' : rpki})
- elif conf.exists_effective(rpki_cli_path):
- dict.update({'rpki' : {'deleted' : ''}})
-
- # We need to check the CLI if the Segment Routing node is present and thus load in
- # all the default values present on the CLI - that's why we have if conf.exists()
- sr_cli_path = ['protocols', 'segment-routing']
- if conf.exists(sr_cli_path):
- sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True)
- dict.update({'segment_routing' : sr})
- elif conf.exists_effective(sr_cli_path):
- dict.update({'segment_routing' : {'deleted' : ''}})
-
- # We need to check the CLI if the static node is present and thus load in
- # all the default values present on the CLI - that's why we have if conf.exists()
- static_cli_path = ['protocols', 'static']
- if conf.exists(static_cli_path):
- static = conf.get_config_dict(static_cli_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)
- if tmp: static.update({'dhcp' : tmp})
- tmp = get_pppoe_interfaces(conf)
- if tmp: static.update({'pppoe' : tmp})
-
- dict.update({'static' : static})
- elif conf.exists_effective(static_cli_path):
- dict.update({'static' : {'deleted' : ''}})
-
- # keep a re-usable list of dependent VRFs
- dependent_vrfs_default = {}
- if 'bgp' in dict:
- dependent_vrfs_default = deepcopy(dict['bgp'])
- # we do not need to nest the 'dependent_vrfs' key - simply remove it
- if 'dependent_vrfs' in dependent_vrfs_default:
- del dependent_vrfs_default['dependent_vrfs']
-
- vrf_cli_path = ['vrf', 'name']
- if conf.exists(vrf_cli_path):
- vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
- get_first_key=False,
- no_tag_node_value_mangle=True)
- # We do not have any VRF related default values on the CLI. The defaults will only
- # come into place under the protocols tree, thus we can safely merge them with the
- # appropriate routing protocols
- for vrf_name, vrf_config in vrf['name'].items():
- bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp']
- if 'bgp' in vrf_config.get('protocols', []):
- # We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
- default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'),
- get_first_key=True, recursive=True)
-
- # merge in remaining default values
- vrf_config['protocols']['bgp'] = config_dict_merge(default_values,
- vrf_config['protocols']['bgp'])
-
- # Add this BGP VRF instance as dependency into the default VRF
- if 'bgp' in dict:
- dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)})
-
- vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict(
- vrf_cli_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
-
- # We can safely delete ourself from the dependent VRF list
- if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']:
- del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name]
-
- # Add dependency on possible existing default VRF to this VRF
- if 'bgp' in dict:
- vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': {
- 'bgp': dependent_vrfs_default}}})
- elif conf.exists_effective(bgp_vrf_path):
- # Add this BGP VRF instance as dependency into the default VRF
- tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])}
- # We can safely delete ourself from the dependent VRF list
- if vrf_name in tmp['dependent_vrfs']:
- del tmp['dependent_vrfs'][vrf_name]
-
- # Add dependency on possible existing default VRF to this VRF
- if 'bgp' in dict:
- tmp['dependent_vrfs'].update({'default': {'protocols': {
- 'bgp': dependent_vrfs_default}}})
-
- if 'bgp' in dict:
- dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} })
-
- if 'protocols' not in vrf['name'][vrf_name]:
- vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}})
- else:
- vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp})
-
- # We need to check the CLI if the EIGRP node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp']
- if 'eigrp' in vrf_config.get('protocols', []):
- eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True)
- vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis})
- elif conf.exists_effective(eigrp_vrf_path):
- vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}})
-
- # We need to check the CLI if the ISIS node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis']
- if 'isis' in vrf_config.get('protocols', []):
- isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True,
- no_tag_node_value_mangle=True, with_recursive_defaults=True)
- vrf['name'][vrf_name]['protocols'].update({'isis' : isis})
- elif conf.exists_effective(isis_vrf_path):
- vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}})
-
- # We need to check the CLI if the OSPF node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf']
- if 'ospf' in vrf_config.get('protocols', []):
- ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True)
- ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path)
- vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf})
- elif conf.exists_effective(ospf_vrf_path):
- vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}})
-
- # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3']
- if 'ospfv3' in vrf_config.get('protocols', []):
- ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True)
- ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path)
- vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3})
- elif conf.exists_effective(ospfv3_vrf_path):
- vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
-
- # We need to check the CLI if the static node is present and thus load in all the default
- # values present on the CLI - that's why we have if conf.exists()
- static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
- if 'static' in vrf_config.get('protocols', []):
- 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' : ''}})
-
- vrf_vni_path = ['vrf', 'name', vrf_name, 'vni']
- if conf.exists(vrf_vni_path):
- vrf_config.update({'vni': conf.return_value(vrf_vni_path)})
-
- dict.update({'vrf' : vrf})
- elif conf.exists_effective(vrf_cli_path):
- effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
- get_first_key=False,
- no_tag_node_value_mangle=True,
- effective=True)
- vrf = {'name' : {}}
- for vrf_name, vrf_config in effective_vrf.get('name', {}).items():
- vrf['name'].update({vrf_name : {}})
- for protocol in frr_protocols:
- if protocol in vrf_config.get('protocols', []):
- # Create initial protocols key if not present
- if 'protocols' not in vrf['name'][vrf_name]:
- vrf['name'][vrf_name].update({'protocols' : {}})
- # All routing protocols are deleted when we pass this point
- tmp = {'deleted' : ''}
-
- # Special treatment for BGP routing protocol
- if protocol == 'bgp':
- tmp['dependent_vrfs'] = {}
- if 'name' in vrf:
- tmp['dependent_vrfs'] = conf.get_config_dict(
- vrf_cli_path, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True,
- effective=True)
- # Add dependency on possible existing default VRF to this VRF
- if 'bgp' in dict:
- tmp['dependent_vrfs'].update({'default': {'protocols': {
- 'bgp': dependent_vrfs_default}}})
- # We can safely delete ourself from the dependent VRF list
- if vrf_name in tmp['dependent_vrfs']:
- del tmp['dependent_vrfs'][vrf_name]
-
- # Update VRF related dict
- vrf['name'][vrf_name]['protocols'].update({protocol : tmp})
-
- dict.update({'vrf' : vrf})
-
- if os.path.exists(frr_debug_enable):
- print('======== < BEGIN > ==========')
- import pprint
- pprint.pprint(dict)
- print('========= < END > ===========')
-
- # Use singleton instance of the FRR render class
- if hasattr(conf, 'frrender_cls'):
- frrender = getattr(conf, 'frrender_cls')
- dict.update({'frrender_cls' : frrender})
- frrender.generate(dict)
-
- return dict
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
index 0c9dde315..badc5d59f 100644
--- a/python/vyos/frrender.py
+++ b/python/vyos/frrender.py
@@ -22,16 +22,15 @@ import os
from time import sleep
from vyos.defaults import frr_debug_enable
+from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
from vyos.template import render_to_string
from vyos import ConfigError
-DEBUG_ON = os.path.exists(frr_debug_enable)
-
def debug(message):
- if not DEBUG_ON:
+ if not os.path.exists(frr_debug_enable):
return
print(message)
@@ -54,13 +53,496 @@ rip_daemon = 'ripd'
ripng_daemon = 'ripngd'
zebra_daemon = 'zebra'
+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
+
+ # Create an empty dictionary which will be filled down the code path and
+ # returned to the caller
+ dict = {}
+
+ if argv and len(argv) > 1:
+ dict['vrf_context'] = argv[1]
+
+ def dict_helper_ospf_defaults(ospf, path):
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # We have to cleanup the default dict, as default values could enable features
+ # which are not explicitly enabled on the CLI. Example: default-information
+ # originate comes with a default metric-type of 2, which will enable the
+ # entire default-information originate tree, even when not set via CLI so we
+ # need to check this first and probably drop that key.
+ if dict_search('default_information.originate', ospf) is None:
+ del default_values['default_information']
+ if 'mpls_te' not in ospf:
+ del default_values['mpls_te']
+ if 'graceful_restart' not in ospf:
+ del default_values['graceful_restart']
+ for area_num in default_values.get('area', []):
+ if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None:
+ del default_values['area'][area_num]['area_type']['nssa']
+
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospf) is None:
+ del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
+
+ for interface in ospf.get('interface', []):
+ # We need to reload the defaults on every pass b/c of
+ # hello-multiplier dependency on dead-interval
+ # If hello-multiplier is set, we need to remove the default from
+ # dead-interval.
+ if 'hello_multiplier' in ospf['interface'][interface]:
+ del default_values['interface'][interface]['dead_interval']
+
+ ospf = config_dict_merge(default_values, ospf)
+ return ospf
+
+ def dict_helper_ospfv3_defaults(ospfv3, path):
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # We have to cleanup the default dict, as default values could enable features
+ # which are not explicitly enabled on the CLI. Example: default-information
+ # originate comes with a default metric-type of 2, which will enable the
+ # entire default-information originate tree, even when not set via CLI so we
+ # need to check this first and probably drop that key.
+ if dict_search('default_information.originate', ospfv3) is None:
+ del default_values['default_information']
+ if 'graceful_restart' not in ospfv3:
+ del default_values['graceful_restart']
+
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospfv3) is None:
+ del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
+
+ default_values.pop('interface', {})
+
+ # merge in remaining default values
+ ospfv3 = config_dict_merge(default_values, ospfv3)
+ return ospfv3
+
+ def dict_helper_pim_defaults(pim, path):
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # We have to cleanup the default dict, as default values could enable features
+ # which are not explicitly enabled on the CLI.
+ for interface in pim.get('interface', []):
+ if 'igmp' not in pim['interface'][interface]:
+ del default_values['interface'][interface]['igmp']
+
+ pim = config_dict_merge(default_values, pim)
+ return pim
+
+ # Ethernet and bonding interfaces can participate in EVPN which is configured via FRR
+ tmp = {}
+ for if_type in ['ethernet', 'bonding']:
+ interface_path = ['interfaces', if_type]
+ if not conf.exists(interface_path):
+ continue
+ for interface in conf.list_nodes(interface_path):
+ evpn_path = interface_path + [interface, 'evpn']
+ if not conf.exists(evpn_path):
+ continue
+
+ evpn = conf.get_config_dict(evpn_path, key_mangling=('-', '_'))
+ tmp.update({interface : evpn})
+ # At least one participating EVPN interface found, add to result dict
+ if tmp: dict['interfaces'] = tmp
+
+ # Zebra prefix exchange for Kernel IP/IPv6 and routing protocols
+ for ip_version in ['ip', 'ipv6']:
+ ip_cli_path = ['system', ip_version]
+ ip_dict = conf.get_config_dict(ip_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, with_recursive_defaults=True)
+ if ip_dict:
+ ip_dict['afi'] = ip_version
+ dict.update({ip_version : ip_dict})
+
+ # Enable SNMP agentx support
+ # SNMP AgentX support cannot be disabled once enabled
+ if conf.exists(['service', 'snmp']):
+ dict['snmp'] = {}
+
+ # We will always need the policy key
+ dict['policy'] = conf.get_config_dict(['policy'], key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # We need to check the CLI if the BABEL node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ babel_cli_path = ['protocols', 'babel']
+ if conf.exists(babel_cli_path):
+ babel = conf.get_config_dict(babel_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'babel' : babel})
+
+ # We need to check the CLI if the BFD node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ bfd_cli_path = ['protocols', 'bfd']
+ if conf.exists(bfd_cli_path):
+ bfd = conf.get_config_dict(bfd_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'bfd' : bfd})
+
+ # We need to check the CLI if the BGP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ bgp_cli_path = ['protocols', 'bgp']
+ if conf.exists(bgp_cli_path):
+ bgp = conf.get_config_dict(bgp_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ bgp['dependent_vrfs'] = {}
+ dict.update({'bgp' : bgp})
+ elif conf.exists_effective(bgp_cli_path):
+ dict.update({'bgp' : {'deleted' : '', 'dependent_vrfs' : {}}})
+
+ # We need to check the CLI if the EIGRP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ eigrp_cli_path = ['protocols', 'eigrp']
+ if conf.exists(eigrp_cli_path):
+ isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'eigrp' : isis})
+ elif conf.exists_effective(eigrp_cli_path):
+ dict.update({'eigrp' : {'deleted' : ''}})
+
+ # We need to check the CLI if the ISIS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ isis_cli_path = ['protocols', 'isis']
+ if conf.exists(isis_cli_path):
+ isis = conf.get_config_dict(isis_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'isis' : isis})
+ elif conf.exists_effective(isis_cli_path):
+ dict.update({'isis' : {'deleted' : ''}})
+
+ # We need to check the CLI if the MPLS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ mpls_cli_path = ['protocols', 'mpls']
+ if conf.exists(mpls_cli_path):
+ mpls = conf.get_config_dict(mpls_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ dict.update({'mpls' : mpls})
+ elif conf.exists_effective(mpls_cli_path):
+ dict.update({'mpls' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OPENFABRIC node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ openfabric_cli_path = ['protocols', 'openfabric']
+ if conf.exists(openfabric_cli_path):
+ openfabric = conf.get_config_dict(openfabric_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+ dict.update({'openfabric' : openfabric})
+ elif conf.exists_effective(openfabric_cli_path):
+ dict.update({'openfabric' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPF node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospf_cli_path = ['protocols', 'ospf']
+ if conf.exists(ospf_cli_path):
+ ospf = conf.get_config_dict(ospf_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ ospf = dict_helper_ospf_defaults(ospf, ospf_cli_path)
+ dict.update({'ospf' : ospf})
+ elif conf.exists_effective(ospf_cli_path):
+ dict.update({'ospf' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospfv3_cli_path = ['protocols', 'ospfv3']
+ if conf.exists(ospfv3_cli_path):
+ ospfv3 = conf.get_config_dict(ospfv3_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ ospfv3 = dict_helper_ospfv3_defaults(ospfv3, ospfv3_cli_path)
+ dict.update({'ospfv3' : ospfv3})
+ elif conf.exists_effective(ospfv3_cli_path):
+ dict.update({'ospfv3' : {'deleted' : ''}})
+
+ # We need to check the CLI if the PIM node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ pim_cli_path = ['protocols', 'pim']
+ if conf.exists(pim_cli_path):
+ pim = conf.get_config_dict(pim_cli_path, key_mangling=('-', '_'),
+ get_first_key=True)
+ pim = dict_helper_pim_defaults(pim, pim_cli_path)
+ dict.update({'pim' : pim})
+ elif conf.exists_effective(pim_cli_path):
+ dict.update({'pim' : {'deleted' : ''}})
+
+ # We need to check the CLI if the PIM6 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ pim6_cli_path = ['protocols', 'pim6']
+ if conf.exists(pim6_cli_path):
+ pim6 = conf.get_config_dict(pim6_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'pim6' : pim6})
+ elif conf.exists_effective(pim6_cli_path):
+ dict.update({'pim6' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RIP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rip_cli_path = ['protocols', 'rip']
+ if conf.exists(rip_cli_path):
+ rip = conf.get_config_dict(rip_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'rip' : rip})
+ elif conf.exists_effective(rip_cli_path):
+ dict.update({'rip' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RIPng node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ripng_cli_path = ['protocols', 'ripng']
+ if conf.exists(ripng_cli_path):
+ ripng = conf.get_config_dict(ripng_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
+ dict.update({'ripng' : ripng})
+ elif conf.exists_effective(ripng_cli_path):
+ dict.update({'ripng' : {'deleted' : ''}})
+
+ # We need to check the CLI if the RPKI node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rpki_cli_path = ['protocols', 'rpki']
+ if conf.exists(rpki_cli_path):
+ rpki = conf.get_config_dict(rpki_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, with_pki=True,
+ with_recursive_defaults=True)
+ rpki_ssh_key_base = '/run/frr/id_rpki'
+ for cache, cache_config in rpki.get('cache',{}).items():
+ if 'ssh' in cache_config:
+ cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
+ cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
+ dict.update({'rpki' : rpki})
+ elif conf.exists_effective(rpki_cli_path):
+ dict.update({'rpki' : {'deleted' : ''}})
+
+ # We need to check the CLI if the Segment Routing node is present and thus load in
+ # all the default values present on the CLI - that's why we have if conf.exists()
+ sr_cli_path = ['protocols', 'segment-routing']
+ if conf.exists(sr_cli_path):
+ sr = conf.get_config_dict(sr_cli_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True)
+ dict.update({'segment_routing' : sr})
+ elif conf.exists_effective(sr_cli_path):
+ dict.update({'segment_routing' : {'deleted' : ''}})
+
+ # We need to check the CLI if the static node is present and thus load in
+ # all the default values present on the CLI - that's why we have if conf.exists()
+ static_cli_path = ['protocols', 'static']
+ if conf.exists(static_cli_path):
+ static = conf.get_config_dict(static_cli_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)
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf)
+ if tmp: static.update({'pppoe' : tmp})
+
+ dict.update({'static' : static})
+ elif conf.exists_effective(static_cli_path):
+ dict.update({'static' : {'deleted' : ''}})
+
+ # keep a re-usable list of dependent VRFs
+ dependent_vrfs_default = {}
+ if 'bgp' in dict:
+ dependent_vrfs_default = deepcopy(dict['bgp'])
+ # we do not need to nest the 'dependent_vrfs' key - simply remove it
+ if 'dependent_vrfs' in dependent_vrfs_default:
+ del dependent_vrfs_default['dependent_vrfs']
+
+ vrf_cli_path = ['vrf', 'name']
+ if conf.exists(vrf_cli_path):
+ vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=False,
+ no_tag_node_value_mangle=True)
+ # We do not have any VRF related default values on the CLI. The defaults will only
+ # come into place under the protocols tree, thus we can safely merge them with the
+ # appropriate routing protocols
+ for vrf_name, vrf_config in vrf['name'].items():
+ bgp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'bgp']
+ if 'bgp' in vrf_config.get('protocols', []):
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(bgp_vrf_path, key_mangling=('-', '_'),
+ get_first_key=True, recursive=True)
+
+ # merge in remaining default values
+ vrf_config['protocols']['bgp'] = config_dict_merge(default_values,
+ vrf_config['protocols']['bgp'])
+
+ # Add this BGP VRF instance as dependency into the default VRF
+ if 'bgp' in dict:
+ dict['bgp']['dependent_vrfs'].update({vrf_name : deepcopy(vrf_config)})
+
+ vrf_config['protocols']['bgp']['dependent_vrfs'] = conf.get_config_dict(
+ vrf_cli_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in vrf_config['protocols']['bgp']['dependent_vrfs']:
+ del vrf_config['protocols']['bgp']['dependent_vrfs'][vrf_name]
+
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ vrf_config['protocols']['bgp']['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+ elif conf.exists_effective(bgp_vrf_path):
+ # Add this BGP VRF instance as dependency into the default VRF
+ tmp = {'deleted' : '', 'dependent_vrfs': deepcopy(vrf['name'])}
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in tmp['dependent_vrfs']:
+ del tmp['dependent_vrfs'][vrf_name]
+
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ tmp['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+
+ if 'bgp' in dict:
+ dict['bgp']['dependent_vrfs'].update({vrf_name : {'protocols': tmp} })
+
+ if 'protocols' not in vrf['name'][vrf_name]:
+ vrf['name'][vrf_name].update({'protocols': {'bgp' : tmp}})
+ else:
+ vrf['name'][vrf_name]['protocols'].update({'bgp' : tmp})
+
+ # We need to check the CLI if the EIGRP node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ eigrp_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'eigrp']
+ if 'eigrp' in vrf_config.get('protocols', []):
+ eigrp = conf.get_config_dict(eigrp_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+ vrf['name'][vrf_name]['protocols'].update({'eigrp' : isis})
+ elif conf.exists_effective(eigrp_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'eigrp' : {'deleted' : ''}})
+
+ # We need to check the CLI if the ISIS node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ isis_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'isis']
+ if 'isis' in vrf_config.get('protocols', []):
+ isis = conf.get_config_dict(isis_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True, with_recursive_defaults=True)
+ vrf['name'][vrf_name]['protocols'].update({'isis' : isis})
+ elif conf.exists_effective(isis_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'isis' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPF node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospf_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospf']
+ if 'ospf' in vrf_config.get('protocols', []):
+ ospf = conf.get_config_dict(ospf_vrf_path, key_mangling=('-', '_'), get_first_key=True)
+ ospf = dict_helper_ospf_defaults(vrf_config['protocols']['ospf'], ospf_vrf_path)
+ vrf['name'][vrf_name]['protocols'].update({'ospf' : ospf})
+ elif conf.exists_effective(ospf_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'ospf' : {'deleted' : ''}})
+
+ # We need to check the CLI if the OSPFv3 node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ ospfv3_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'ospfv3']
+ if 'ospfv3' in vrf_config.get('protocols', []):
+ ospfv3 = conf.get_config_dict(ospfv3_vrf_path, key_mangling=('-', '_'), get_first_key=True)
+ ospfv3 = dict_helper_ospfv3_defaults(vrf_config['protocols']['ospfv3'], ospfv3_vrf_path)
+ vrf['name'][vrf_name]['protocols'].update({'ospfv3' : ospfv3})
+ elif conf.exists_effective(ospfv3_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
+
+ # We need to check the CLI if the static node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
+ if 'static' in vrf_config.get('protocols', []):
+ 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' : ''}})
+
+ vrf_vni_path = ['vrf', 'name', vrf_name, 'vni']
+ if conf.exists(vrf_vni_path):
+ vrf_config.update({'vni': conf.return_value(vrf_vni_path)})
+
+ dict.update({'vrf' : vrf})
+ elif conf.exists_effective(vrf_cli_path):
+ effective_vrf = conf.get_config_dict(vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=False,
+ no_tag_node_value_mangle=True,
+ effective=True)
+ vrf = {'name' : {}}
+ for vrf_name, vrf_config in effective_vrf.get('name', {}).items():
+ vrf['name'].update({vrf_name : {}})
+ for protocol in frr_protocols:
+ if protocol in vrf_config.get('protocols', []):
+ # Create initial protocols key if not present
+ if 'protocols' not in vrf['name'][vrf_name]:
+ vrf['name'][vrf_name].update({'protocols' : {}})
+ # All routing protocols are deleted when we pass this point
+ tmp = {'deleted' : ''}
+
+ # Special treatment for BGP routing protocol
+ if protocol == 'bgp':
+ tmp['dependent_vrfs'] = {}
+ if 'name' in vrf:
+ tmp['dependent_vrfs'] = conf.get_config_dict(
+ vrf_cli_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True,
+ effective=True)
+ # Add dependency on possible existing default VRF to this VRF
+ if 'bgp' in dict:
+ tmp['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': dependent_vrfs_default}}})
+ # We can safely delete ourself from the dependent VRF list
+ if vrf_name in tmp['dependent_vrfs']:
+ del tmp['dependent_vrfs'][vrf_name]
+
+ # Update VRF related dict
+ vrf['name'][vrf_name]['protocols'].update({protocol : tmp})
+
+ dict.update({'vrf' : vrf})
+
+ return dict
+
class FRRender:
def __init__(self):
self._frr_conf = '/run/frr/config/vyos.frr.conf'
- def generate(self, config):
- if not isinstance(config, dict):
- tmp = type(config)
+ def generate(self, config_dict) -> None:
+ if not isinstance(config_dict, dict):
+ tmp = type(config_dict)
raise ValueError(f'Config must be of type "dict" and not "{tmp}"!')
def inline_helper(config_dict) -> str:
@@ -124,23 +606,23 @@ class FRRender:
output += '\n'
return output
- debug('======< RENDERING CONFIG >======')
+ debug('FRR: START CONFIGURATION RENDERING')
# we can not reload an empty file, thus we always embed the marker
output = '!\n'
# Enable SNMP agentx support
# SNMP AgentX support cannot be disabled once enabled
- if 'snmp' in config:
+ if 'snmp' in config_dict:
output += 'agentx\n'
# Add routing protocols in global VRF
- output += inline_helper(config)
+ output += inline_helper(config_dict)
# Interface configuration for EVPN is not VRF related
- if 'interfaces' in config:
- output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config['interfaces']})
+ if 'interfaces' in config_dict:
+ output += render_to_string('frr/evpn.mh.frr.j2', {'interfaces' : config_dict['interfaces']})
output += '\n'
- if 'vrf' in config and 'name' in config['vrf']:
- output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config['vrf'])
- for vrf, vrf_config in config['vrf']['name'].items():
+ if 'vrf' in config_dict and 'name' in config_dict['vrf']:
+ output += render_to_string('frr/zebra.vrf.route-map.frr.j2', config_dict['vrf'])
+ for vrf, vrf_config in config_dict['vrf']['name'].items():
if 'protocols' not in vrf_config:
continue
for protocol in vrf_config['protocols']:
@@ -155,23 +637,25 @@ class FRRender:
raise ConfigError('FRR configuration contains "!!" which is not allowed')
debug(output)
- debug('======< RENDERING CONFIG COMPLETE >======')
write_file(self._frr_conf, output)
+ debug('FRR: RENDERING CONFIG COMPLETE')
+ return None
def apply(self, count_max=5):
count = 0
emsg = ''
while count < count_max:
count += 1
- debug(f'FRR: Reloading configuration - tries: {count} | Python class ID: {id(self)}')
+ debug(f'FRR: reloading configuration - tries: {count} | Python class ID: {id(self)}')
cmdline = '/usr/lib/frr/frr-reload.py --reload'
- if DEBUG_ON: cmdline += ' --debug'
+ if os.path.exists(frr_debug_enable):
+ cmdline += ' --debug'
rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}')
if rc != 0:
sleep(2)
continue
debug(emsg)
- debug('======< DONE APPLYING CONFIG >======')
+ debug('FRR: configuration reload complete')
break
if count >= count_max:
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index cd562e1fe..eac9f61f5 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -26,8 +26,9 @@ from netifaces import ifaddresses
# this is not the same as socket.AF_INET/INET6
from netifaces import AF_INET
from netifaces import AF_INET6
+from netaddr import EUI
+from netaddr import mac_unix_expanded
-from vyos import ConfigError
from vyos.configdict import list_diff
from vyos.configdict import dict_merge
from vyos.configdict import get_vlan_ids
@@ -61,9 +62,7 @@ from vyos.ifconfig.control import Control
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
from vyos.ifconfig import Section
-
-from netaddr import EUI
-from netaddr import mac_unix_expanded
+from vyos import ConfigError
link_local_prefix = 'fe80::/64'
@@ -339,8 +338,8 @@ class Interface(Control):
# Any instance of Interface, such as Interface('eth0') can be used
# safely to access the generic function in this class as 'type' is
# unset, the class can not be created
- if not self.iftype:
- raise Exception(f'interface "{ifname}" not found')
+ if not hasattr(self, 'iftype'):
+ raise ConfigError(f'Interface "{ifname}" has no "iftype" attribute defined!')
self.config['type'] = self.iftype
# Should an Instance of a child class (EthernetIf, DummyIf, ..)
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index 2266879ec..fb7f1d298 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2024 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
@@ -42,6 +42,5 @@ class MACVLANIf(Interface):
self.set_admin_state('down')
def set_mode(self, mode):
- ifname = self.config['ifname']
- cmd = f'ip link set dev {ifname} type macvlan mode {mode}'
+ cmd = f'ip link set dev {self.ifname} type {self.iftype} mode {mode}'
return self._cmd(cmd)
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 0844d2913..4f1141dcb 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -17,7 +17,6 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
@@ -32,6 +31,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import BondIf
from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig import Section
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 5024e6982..41c89fdf8 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -20,7 +20,6 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
@@ -35,6 +34,7 @@ from vyos.configverify import verify_bond_bridge_member
from vyos.configverify import verify_eapol
from vyos.ethtool import Ethtool
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import EthernetIf
from vyos.ifconfig import BondIf
from vyos.utils.dict import dict_search
diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py
index 5e71a612d..a90e33e81 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -17,10 +17,10 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
from vyos.frrender import frr_protocols
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py
index 48b7ae734..80a847af8 100755
--- a/src/conf_mode/protocols_babel.py
+++ b/src/conf_mode/protocols_babel.py
@@ -17,11 +17,11 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py
index 2e7d40676..d3bc3e961 100755
--- a/src/conf_mode/protocols_bfd.py
+++ b/src/conf_mode/protocols_bfd.py
@@ -15,10 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_vrf
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.template import is_ipv6
from vyos.utils.network import is_ipv6_link_local
from vyos.utils.process import is_systemd_service_running
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 60f3f2ad0..c4af717af 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,12 +19,12 @@ from sys import argv
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_prefix_list
from vyos.configverify import verify_route_map
from vyos.configverify import verify_vrf
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.template import is_ip
from vyos.template import is_interface
from vyos.utils.dict import dict_search
diff --git a/src/conf_mode/protocols_eigrp.py b/src/conf_mode/protocols_eigrp.py
index 8f49bb151..324ff883f 100755
--- a/src/conf_mode/protocols_eigrp.py
+++ b/src/conf_mode/protocols_eigrp.py
@@ -18,11 +18,11 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_vrf
from vyos.utils.process import is_systemd_service_running
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos import ConfigError
from vyos import airbag
airbag.enable()
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 1e5f0d6e8..1c994492e 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -18,11 +18,11 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_interface_exists
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py
index e8097b7ff..33d9a6dae 100755
--- a/src/conf_mode/protocols_mpls.py
+++ b/src/conf_mode/protocols_mpls.py
@@ -20,9 +20,9 @@ from sys import exit
from glob import glob
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
from vyos.utils.process import is_systemd_service_running
diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py
index 41c5d9544..7df11fb20 100644
--- a/src/conf_mode/protocols_openfabric.py
+++ b/src/conf_mode/protocols_openfabric.py
@@ -18,11 +18,11 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_interface_exists
from vyos.configverify import has_frr_protocol_in_dict
from vyos.utils.process import is_systemd_service_running
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos import ConfigError
from vyos import airbag
airbag.enable()
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index f2c95a63c..c06c0aafc 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -18,13 +18,13 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_route_map
from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_access_list
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
from vyos.utils.process import is_systemd_service_running
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index ac189c378..2563eb7d5 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -18,12 +18,12 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_route_map
from vyos.configverify import verify_interface_exists
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import Interface
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_config
diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py
index 477895b0b..632099964 100755
--- a/src/conf_mode/protocols_pim.py
+++ b/src/conf_mode/protocols_pim.py
@@ -22,10 +22,10 @@ from signal import SIGTERM
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import verify_interface_exists
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.frrender import pim_daemon
from vyos.utils.process import is_systemd_service_running
from vyos.utils.process import process_named_running
diff --git a/src/conf_mode/protocols_pim6.py b/src/conf_mode/protocols_pim6.py
index 3a9b876cc..03a79139a 100755
--- a/src/conf_mode/protocols_pim6.py
+++ b/src/conf_mode/protocols_pim6.py
@@ -19,11 +19,11 @@ from ipaddress import IPv6Network
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_interface_exists
from vyos.utils.process import is_systemd_service_running
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos import ConfigError
from vyos import airbag
airbag.enable()
diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py
index 39743f965..ec9dfbb8b 100755
--- a/src/conf_mode/protocols_rip.py
+++ b/src/conf_mode/protocols_rip.py
@@ -17,12 +17,12 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py
index 14f038444..9a9ac8ec8 100755
--- a/src/conf_mode/protocols_ripng.py
+++ b/src/conf_mode/protocols_ripng.py
@@ -17,12 +17,12 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_access_list
from vyos.configverify import verify_prefix_list
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index 5ad656586..ef0250e3d 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -20,9 +20,9 @@ from glob import glob
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.pki import wrap_openssh_public_key
from vyos.pki import wrap_openssh_private_key
from vyos.utils.dict import dict_search_args
diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py
index 99cf87556..f2bd42a79 100755
--- a/src/conf_mode/protocols_segment-routing.py
+++ b/src/conf_mode/protocols_segment-routing.py
@@ -17,10 +17,10 @@
from sys import exit
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configdict import list_diff
from vyos.configverify import has_frr_protocol_in_dict
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import Section
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_running
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 9d02db6dd..1b9e51167 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -19,11 +19,11 @@ from sys import exit
from sys import argv
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_vrf
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.process import is_systemd_service_running
from vyos.template import render
from vyos import ConfigError
diff --git a/src/conf_mode/system_ip.py b/src/conf_mode/system_ip.py
index 86843eb78..7f3796168 100755
--- a/src/conf_mode/system_ip.py
+++ b/src/conf_mode/system_ip.py
@@ -19,10 +19,10 @@ from sys import exit
from vyos.config import Config
from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_route_map
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import is_systemd_service_running
diff --git a/src/conf_mode/system_ipv6.py b/src/conf_mode/system_ipv6.py
index 593b8f7f3..309869b2f 100755
--- a/src/conf_mode/system_ipv6.py
+++ b/src/conf_mode/system_ipv6.py
@@ -20,10 +20,10 @@ from sys import exit
from vyos.config import Config
from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
-from vyos.configdict import get_frrender_dict
from vyos.configverify import has_frr_protocol_in_dict
from vyos.configverify import verify_route_map
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.process import is_systemd_service_active
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 6533f493f..74780b601 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -19,11 +19,11 @@ from jmespath import search
from json import loads
from vyos.config import Config
-from vyos.configdict import get_frrender_dict
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
from vyos.firewall import conntrack_required
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos.ifconfig import Interface
from vyos.template import render
from vyos.utils.dict import dict_search
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index ecad85801..d558e8c26 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -38,6 +38,7 @@ from vyos.configsource import ConfigSourceError
from vyos.configdiff import get_commit_scripts
from vyos.config import Config
from vyos.frrender import FRRender
+from vyos.frrender import get_frrender_dict
from vyos import ConfigError
CFG_GROUP = 'vyattacfg'
@@ -333,6 +334,8 @@ if __name__ == '__main__':
if hasattr(config, 'frrender_cls') and res == R_SUCCESS:
frrender_cls = getattr(config, 'frrender_cls')
+ tmp = get_frrender_dict(config)
+ frrender_cls.generate(tmp)
frrender_cls.apply()
else:
logger.critical(f'Unexpected message: {message}')