summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/prometheus/frr_exporter.service.j2 (renamed from data/templates/frr_exporter/frr_exporter.service.j2)0
-rw-r--r--data/templates/prometheus/node_exporter.service.j2 (renamed from data/templates/node_exporter/node_exporter.service.j2)0
-rw-r--r--debian/control8
-rw-r--r--interface-definitions/include/version/monitoring-version.xml.i2
-rw-r--r--interface-definitions/service_monitoring_frr_exporter.xml.in25
-rw-r--r--interface-definitions/service_monitoring_node_exporter.xml.in25
-rw-r--r--interface-definitions/service_monitoring_prometheus.xml.in45
-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-xsmoketest/scripts/cli/test_service_dns_dynamic.py9
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_node-exporter.py64
-rwxr-xr-xsmoketest/scripts/cli/test_service_monitoring_prometheus.py (renamed from smoketest/scripts/cli/test_service_monitoring_frr-exporter.py)38
-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/service_monitoring_frr-exporter.py101
-rwxr-xr-xsrc/conf_mode/service_monitoring_node-exporter.py101
-rwxr-xr-xsrc/conf_mode/service_monitoring_prometheus.py145
-rwxr-xr-xsrc/conf_mode/system_ip.py2
-rwxr-xr-xsrc/conf_mode/system_ipv6.py2
-rwxr-xr-xsrc/conf_mode/vrf.py2
-rw-r--r--src/migration-scripts/monitoring/1-to-250
-rwxr-xr-xsrc/services/vyos-configd3
41 files changed, 810 insertions, 881 deletions
diff --git a/data/templates/frr_exporter/frr_exporter.service.j2 b/data/templates/prometheus/frr_exporter.service.j2
index c3892e42b..c3892e42b 100644
--- a/data/templates/frr_exporter/frr_exporter.service.j2
+++ b/data/templates/prometheus/frr_exporter.service.j2
diff --git a/data/templates/node_exporter/node_exporter.service.j2 b/data/templates/prometheus/node_exporter.service.j2
index 62e7e6774..62e7e6774 100644
--- a/data/templates/node_exporter/node_exporter.service.j2
+++ b/data/templates/prometheus/node_exporter.service.j2
diff --git a/debian/control b/debian/control
index 76ca83dcd..08b86356a 100644
--- a/debian/control
+++ b/debian/control
@@ -235,12 +235,12 @@ Depends:
squidclient,
squidguard,
# End "service webproxy"
-# For "service monitoring node-exporter"
+# For "service monitoring prometheus node-exporter"
node-exporter,
-# End "service monitoring node-exporter"
-# For "service monitoring frr-exporter"
+# End "service monitoring prometheus node-exporter"
+# For "service monitoring prometheus frr-exporter"
frr-exporter,
-# End "service monitoring frr-exporter"
+# End "service monitoring prometheus frr-exporter"
# For "service monitoring telegraf"
telegraf (>= 1.20),
# End "service monitoring telegraf"
diff --git a/interface-definitions/include/version/monitoring-version.xml.i b/interface-definitions/include/version/monitoring-version.xml.i
index 6a275a5d8..2e2e0116e 100644
--- a/interface-definitions/include/version/monitoring-version.xml.i
+++ b/interface-definitions/include/version/monitoring-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/monitoring-version.xml.i -->
-<syntaxVersion component='monitoring' version='1'></syntaxVersion>
+<syntaxVersion component='monitoring' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/service_monitoring_frr_exporter.xml.in b/interface-definitions/service_monitoring_frr_exporter.xml.in
deleted file mode 100644
index 96aee3ab4..000000000
--- a/interface-definitions/service_monitoring_frr_exporter.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="monitoring">
- <children>
- <node name="frr-exporter" owner="${vyos_conf_scripts_dir}/service_monitoring_frr-exporter.py">
- <properties>
- <help>Prometheus exporter for FRR metrics</help>
- <priority>1280</priority>
- </properties>
- <children>
- #include <include/listen-address.xml.i>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>9342</defaultValue>
- </leafNode>
- #include <include/interface/vrf.xml.i>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/service_monitoring_node_exporter.xml.in b/interface-definitions/service_monitoring_node_exporter.xml.in
deleted file mode 100644
index a11d2304f..000000000
--- a/interface-definitions/service_monitoring_node_exporter.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="monitoring">
- <children>
- <node name="node-exporter" owner="${vyos_conf_scripts_dir}/service_monitoring_node-exporter.py">
- <properties>
- <help>Prometheus exporter for hardware and operating system metrics</help>
- <priority>1280</priority>
- </properties>
- <children>
- #include <include/listen-address.xml.i>
- #include <include/port-number.xml.i>
- <leafNode name="port">
- <defaultValue>9100</defaultValue>
- </leafNode>
- #include <include/interface/vrf.xml.i>
- </children>
- </node>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/service_monitoring_prometheus.xml.in b/interface-definitions/service_monitoring_prometheus.xml.in
new file mode 100644
index 000000000..24f31e15c
--- /dev/null
+++ b/interface-definitions/service_monitoring_prometheus.xml.in
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="monitoring">
+ <children>
+ <node name="prometheus" owner="${vyos_conf_scripts_dir}/service_monitoring_prometheus.py">
+ <properties>
+ <help>Prometheus metric exporter</help>
+ <priority>1280</priority>
+ </properties>
+ <children>
+ <node name="node-exporter">
+ <properties>
+ <help>Prometheus exporter for hardware and operating system metrics</help>
+ </properties>
+ <children>
+ #include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>9100</defaultValue>
+ </leafNode>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ <node name="frr-exporter">
+ <properties>
+ <help>Prometheus exporter for FRR metrics</help>
+ </properties>
+ <children>
+ #include <include/listen-address.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>9342</defaultValue>
+ </leafNode>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
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/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index c39d4467a..3e14976fc 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -22,11 +22,10 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import cmd
-from vyos.utils.process import process_running
+from vyos.utils.process import process_named_running
DDCLIENT_SYSTEMD_UNIT = '/run/systemd/system/ddclient.service.d/override.conf'
DDCLIENT_CONF = '/run/ddclient/ddclient.conf'
-DDCLIENT_PID = '/run/ddclient/ddclient.pid'
DDCLIENT_PNAME = 'ddclient'
base_path = ['service', 'dns', 'dynamic']
@@ -46,14 +45,14 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
def tearDown(self):
# Check for running process
- self.assertTrue(process_running(DDCLIENT_PID))
+ self.assertTrue(process_named_running(DDCLIENT_PNAME))
# Delete DDNS configuration
self.cli_delete(base_path)
self.cli_commit()
- # PID file must no londer exist after process exited
- self.assertFalse(os.path.exists(DDCLIENT_PID))
+ # Check for process not running anymore
+ self.assertFalse(process_named_running(DDCLIENT_PNAME))
# IPv4 standard DDNS service configuration
def test_01_dyndns_service_standard(self):
diff --git a/smoketest/scripts/cli/test_service_monitoring_node-exporter.py b/smoketest/scripts/cli/test_service_monitoring_node-exporter.py
deleted file mode 100755
index e18a3f7a2..000000000
--- a/smoketest/scripts/cli/test_service_monitoring_node-exporter.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import unittest
-
-from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.utils.process import process_named_running
-from vyos.utils.file import read_file
-
-PROCESS_NAME = 'node_exporter'
-base_path = ['service', 'monitoring', 'node-exporter']
-service_file = '/etc/systemd/system/node_exporter.service'
-listen_if = 'dum3421'
-listen_ip = '192.0.2.1'
-
-
-class TestMonitoringNodeExporter(VyOSUnitTestSHIM.TestCase):
- @classmethod
- def setUpClass(cls):
- # call base-classes classmethod
- super(TestMonitoringNodeExporter, cls).setUpClass()
- # create a test interfaces
- cls.cli_set(
- cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']
- )
-
- @classmethod
- def tearDownClass(cls):
- cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
- super(TestMonitoringNodeExporter, cls).tearDownClass()
-
- def tearDown(self):
- self.cli_delete(base_path)
- self.cli_commit()
- self.assertFalse(process_named_running(PROCESS_NAME))
-
- def test_01_basic_config(self):
- self.cli_set(base_path + ['listen-address', listen_ip])
-
- # commit changes
- self.cli_commit()
-
- file_content = read_file(service_file)
- self.assertIn(f'{listen_ip}:9100', file_content)
-
- # Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py b/smoketest/scripts/cli/test_service_monitoring_prometheus.py
index 230171c11..dae103e4b 100755
--- a/smoketest/scripts/cli/test_service_monitoring_frr-exporter.py
+++ b/smoketest/scripts/cli/test_service_monitoring_prometheus.py
@@ -20,18 +20,21 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
-PROCESS_NAME = 'frr_exporter'
-base_path = ['service', 'monitoring', 'frr-exporter']
-service_file = '/etc/systemd/system/frr_exporter.service'
+NODE_EXPORTER_PROCESS_NAME = 'node_exporter'
+FRR_EXPORTER_PROCESS_NAME = 'frr_exporter'
+
+base_path = ['service', 'monitoring', 'prometheus']
listen_if = 'dum3421'
listen_ip = '192.0.2.1'
+node_exporter_service_file = '/etc/systemd/system/node_exporter.service'
+frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service'
-class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
+class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
# call base-classes classmethod
- super(TestMonitoringFrrExporter, cls).setUpClass()
+ super(TestMonitoringPrometheus, cls).setUpClass()
# create a test interfaces
cls.cli_set(
cls, ['interfaces', 'dummy', listen_if, 'address', listen_ip + '/32']
@@ -40,24 +43,37 @@ class TestMonitoringFrrExporter(VyOSUnitTestSHIM.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, ['interfaces', 'dummy', listen_if])
- super(TestMonitoringFrrExporter, cls).tearDownClass()
+ super(TestMonitoringPrometheus, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
self.cli_commit()
- self.assertFalse(process_named_running(PROCESS_NAME))
+ self.assertFalse(process_named_running(NODE_EXPORTER_PROCESS_NAME))
+ self.assertFalse(process_named_running(FRR_EXPORTER_PROCESS_NAME))
+
+ def test_01_node_exporter(self):
+ self.cli_set(base_path + ['node-exporter', 'listen-address', listen_ip])
+
+ # commit changes
+ self.cli_commit()
+
+ file_content = read_file(node_exporter_service_file)
+ self.assertIn(f'{listen_ip}:9100', file_content)
+
+ # Check for running process
+ self.assertTrue(process_named_running(NODE_EXPORTER_PROCESS_NAME))
- def test_01_basic_config(self):
- self.cli_set(base_path + ['listen-address', listen_ip])
+ def test_02_frr_exporter(self):
+ self.cli_set(base_path + ['frr-exporter', 'listen-address', listen_ip])
# commit changes
self.cli_commit()
- file_content = read_file(service_file)
+ file_content = read_file(frr_exporter_service_file)
self.assertIn(f'{listen_ip}:9342', file_content)
# Check for running process
- self.assertTrue(process_named_running(PROCESS_NAME))
+ self.assertTrue(process_named_running(FRR_EXPORTER_PROCESS_NAME))
if __name__ == '__main__':
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/service_monitoring_frr-exporter.py b/src/conf_mode/service_monitoring_frr-exporter.py
deleted file mode 100755
index 01527d579..000000000
--- a/src/conf_mode/service_monitoring_frr-exporter.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import is_node_changed
-from vyos.configverify import verify_vrf
-from vyos.template import render
-from vyos.utils.process import call
-from vyos import ConfigError
-from vyos import airbag
-
-
-airbag.enable()
-
-service_file = '/etc/systemd/system/frr_exporter.service'
-systemd_service = 'frr_exporter.service'
-
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['service', 'monitoring', 'frr-exporter']
- if not conf.exists(base):
- return None
-
- config_data = conf.get_config_dict(
- base, key_mangling=('-', '_'), get_first_key=True
- )
- config_data = conf.merge_defaults(config_data, recursive=True)
-
- tmp = is_node_changed(conf, base + ['vrf'])
- if tmp:
- config_data.update({'restart_required': {}})
-
- return config_data
-
-
-def verify(config_data):
- # bail out early - looks like removal from running config
- if not config_data:
- return None
-
- verify_vrf(config_data)
- return None
-
-
-def generate(config_data):
- if not config_data:
- # Delete systemd files
- if os.path.isfile(service_file):
- os.unlink(service_file)
- return None
-
- # Render frr_exporter service_file
- render(service_file, 'frr_exporter/frr_exporter.service.j2', config_data)
- return None
-
-
-def apply(config_data):
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
- if not config_data:
- call(f'systemctl stop {systemd_service}')
- return
-
- # we need to restart the service if e.g. the VRF name changed
- systemd_action = 'reload-or-restart'
- if 'restart_required' in config_data:
- systemd_action = 'restart'
-
- call(f'systemctl {systemd_action} {systemd_service}')
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/service_monitoring_node-exporter.py b/src/conf_mode/service_monitoring_node-exporter.py
deleted file mode 100755
index db34bb5d0..000000000
--- a/src/conf_mode/service_monitoring_node-exporter.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from sys import exit
-
-from vyos.config import Config
-from vyos.configdict import is_node_changed
-from vyos.configverify import verify_vrf
-from vyos.template import render
-from vyos.utils.process import call
-from vyos import ConfigError
-from vyos import airbag
-
-
-airbag.enable()
-
-service_file = '/etc/systemd/system/node_exporter.service'
-systemd_service = 'node_exporter.service'
-
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
- base = ['service', 'monitoring', 'node-exporter']
- if not conf.exists(base):
- return None
-
- config_data = conf.get_config_dict(
- base, key_mangling=('-', '_'), get_first_key=True
- )
- config_data = conf.merge_defaults(config_data, recursive=True)
-
- tmp = is_node_changed(conf, base + ['vrf'])
- if tmp:
- config_data.update({'restart_required': {}})
-
- return config_data
-
-
-def verify(config_data):
- # bail out early - looks like removal from running config
- if not config_data:
- return None
-
- verify_vrf(config_data)
- return None
-
-
-def generate(config_data):
- if not config_data:
- # Delete systemd files
- if os.path.isfile(service_file):
- os.unlink(service_file)
- return None
-
- # Render node_exporter service_file
- render(service_file, 'node_exporter/node_exporter.service.j2', config_data)
- return None
-
-
-def apply(config_data):
- # Reload systemd manager configuration
- call('systemctl daemon-reload')
- if not config_data:
- call(f'systemctl stop {systemd_service}')
- return
-
- # we need to restart the service if e.g. the VRF name changed
- systemd_action = 'reload-or-restart'
- if 'restart_required' in config_data:
- systemd_action = 'restart'
-
- call(f'systemctl {systemd_action} {systemd_service}')
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/service_monitoring_prometheus.py b/src/conf_mode/service_monitoring_prometheus.py
new file mode 100755
index 000000000..e0a9fc4ef
--- /dev/null
+++ b/src/conf_mode/service_monitoring_prometheus.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
+from vyos.template import render
+from vyos.utils.process import call
+from vyos import ConfigError
+from vyos import airbag
+
+
+airbag.enable()
+
+node_exporter_service_file = '/etc/systemd/system/node_exporter.service'
+node_exporter_systemd_service = 'node_exporter.service'
+
+frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service'
+frr_exporter_systemd_service = 'frr_exporter.service'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'monitoring', 'prometheus']
+ if not conf.exists(base):
+ return None
+
+ monitoring = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ )
+
+ tmp = is_node_changed(conf, base + ['node-exporter', 'vrf'])
+ if tmp:
+ monitoring.update({'node_exporter_restart_required': {}})
+
+ tmp = is_node_changed(conf, base + ['frr-exporter', 'vrf'])
+ if tmp:
+ monitoring.update({'frr_exporter_restart_required': {}})
+
+ return monitoring
+
+
+def verify(monitoring):
+ if not monitoring:
+ return None
+
+ if 'node_exporter' in monitoring:
+ verify_vrf(monitoring['node_exporter'])
+
+ if 'frr_exporter' in monitoring:
+ verify_vrf(monitoring['frr_exporter'])
+
+ return None
+
+
+def generate(monitoring):
+ if not monitoring or 'node_exporter' not in monitoring:
+ # Delete systemd files
+ if os.path.isfile(node_exporter_service_file):
+ os.unlink(node_exporter_service_file)
+
+ if not monitoring or 'frr_exporter' not in monitoring:
+ # Delete systemd files
+ if os.path.isfile(frr_exporter_service_file):
+ os.unlink(frr_exporter_service_file)
+
+ if not monitoring:
+ return None
+
+ if 'node_exporter' in monitoring:
+ # Render node_exporter node_exporter_service_file
+ render(
+ node_exporter_service_file,
+ 'prometheus/node_exporter.service.j2',
+ monitoring['node_exporter'],
+ )
+
+ if 'frr_exporter' in monitoring:
+ # Render frr_exporter service_file
+ render(
+ frr_exporter_service_file,
+ 'prometheus/frr_exporter.service.j2',
+ monitoring['frr_exporter'],
+ )
+
+ return None
+
+
+def apply(monitoring):
+ # Reload systemd manager configuration
+ call('systemctl daemon-reload')
+ if not monitoring or 'node_exporter' not in monitoring:
+ call(f'systemctl stop {node_exporter_systemd_service}')
+ if not monitoring or 'frr_exporter' not in monitoring:
+ call(f'systemctl stop {frr_exporter_systemd_service}')
+
+ if not monitoring:
+ return
+
+ if 'node_exporter' in monitoring:
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'node_exporter_restart_required' in monitoring:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {node_exporter_systemd_service}')
+
+ if 'frr_exporter' in monitoring:
+ # we need to restart the service if e.g. the VRF name changed
+ systemd_action = 'reload-or-restart'
+ if 'frr_exporter_restart_required' in monitoring:
+ systemd_action = 'restart'
+
+ call(f'systemctl {systemd_action} {frr_exporter_systemd_service}')
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
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/migration-scripts/monitoring/1-to-2 b/src/migration-scripts/monitoring/1-to-2
new file mode 100644
index 000000000..8bdaebae9
--- /dev/null
+++ b/src/migration-scripts/monitoring/1-to-2
@@ -0,0 +1,50 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# T6953: merge node and frr exporter under prometheus section
+
+from vyos.configtree import ConfigTree
+
+old_base = ['service', 'monitoring']
+new_base = ['service', 'monitoring', 'prometheus']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(old_base):
+ # Nothing to do
+ return
+
+ if config.exists(old_base + ['node-exporter']):
+ if config.exists(old_base + ['node-exporter', 'listen-address']):
+ tmp = config.return_value(old_base + ['node-exporter', 'listen-address'])
+ config.set(new_base + ['node-exporter', 'listen-address'], value=tmp)
+ if config.exists(old_base + ['node-exporter', 'port']):
+ tmp = config.return_value(old_base + ['node-exporter', 'port'])
+ config.set(new_base + ['node-exporter', 'port'], value=tmp)
+ if config.exists(old_base + ['node-exporter', 'vrf']):
+ tmp = config.return_value(old_base + ['node-exporter', 'vrf'])
+ config.set(new_base + ['node-exporter', 'vrf'], value=tmp)
+ config.delete(old_base + ['node-exporter'])
+
+ if config.exists(old_base + ['frr-exporter']):
+ if config.exists(old_base + ['frr-exporter', 'listen-address']):
+ tmp = config.return_value(old_base + ['frr-exporter', 'listen-address'])
+ config.set(new_base + ['frr-exporter', 'listen-address'], value=tmp)
+ if config.exists(old_base + ['frr-exporter', 'port']):
+ tmp = config.return_value(old_base + ['frr-exporter', 'port'])
+ config.set(new_base + ['frr-exporter', 'port'], value=tmp)
+ if config.exists(old_base + ['frr-exporter', 'vrf']):
+ tmp = config.return_value(old_base + ['frr-exporter', 'vrf'])
+ config.set(new_base + ['frr-exporter', 'vrf'], value=tmp)
+ config.delete(old_base + ['frr-exporter'])
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}')