Skip to content

Commit ec80c75

Browse files
authored
frr: T6746: additional improvements after 10.2 upgrade (#4259)
* smoketest: T6746: add substring search in getFRRconfig() Some daemons (e.g. bgpd) have several nested substrings/sections like router bgp 100 address-family ipv4 unicast .. exit-address-family exit We can now use getFRRconfig() with the substring option to extract only address-family ipv4 unicast .. exit-address-family Making config validation more granular * frrender: T6746: only re-render FRR config if config_dict did change * frrender: T6746: fix naming glitch isis/eigrp * frrender: T6746: add --stdout option when running with debug flags * smoketest: T6746: remove unneeded commit_guard time It was an invalid workarround as the underlaying issue seems to be a race condition in CStore. The commit process is not finished until all pending files from VYATTA_CHANGES_ONLY_DIR are copied to VYATTA_ACTIVE_CONFIGURATION_DIR. This is done inside libvyatta-cfg1 and the FUSE UnionFS part. On large non-interactive commits FUSE UnionFS might not replicate the real state in time, leading to errors when querying the working and effective configuration. TO BE DELETED AFTER SWITCH TO IN MEMORY CONFIG
1 parent b58576d commit ec80c75

22 files changed

+265
-241
lines changed

python/vyos/frrender.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,11 @@ def dict_helper_pim_defaults(pim, path):
218218
# values present on the CLI - that's why we have if conf.exists()
219219
eigrp_cli_path = ['protocols', 'eigrp']
220220
if conf.exists(eigrp_cli_path):
221-
isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
222-
get_first_key=True,
223-
no_tag_node_value_mangle=True,
224-
with_recursive_defaults=True)
225-
dict.update({'eigrp' : isis})
221+
eigrp = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'),
222+
get_first_key=True,
223+
no_tag_node_value_mangle=True,
224+
with_recursive_defaults=True)
225+
dict.update({'eigrp' : eigrp})
226226
elif conf.exists_effective(eigrp_cli_path):
227227
dict.update({'eigrp' : {'deleted' : ''}})
228228

@@ -537,14 +537,25 @@ def dict_helper_pim_defaults(pim, path):
537537
return dict
538538

539539
class FRRender:
540+
cached_config_dict = {}
540541
def __init__(self):
541542
self._frr_conf = '/run/frr/config/vyos.frr.conf'
542543

543544
def generate(self, config_dict) -> None:
545+
"""
546+
Generate FRR configuration file
547+
Returns False if no changes to configuration were made, otherwise True
548+
"""
544549
if not isinstance(config_dict, dict):
545550
tmp = type(config_dict)
546551
raise ValueError(f'Config must be of type "dict" and not "{tmp}"!')
547552

553+
554+
if self.cached_config_dict == config_dict:
555+
debug('FRR: NO CHANGES DETECTED')
556+
return False
557+
self.cached_config_dict = config_dict
558+
548559
def inline_helper(config_dict) -> str:
549560
output = '!\n'
550561
if 'babel' in config_dict and 'deleted' not in config_dict['babel']:
@@ -639,7 +650,7 @@ def inline_helper(config_dict) -> str:
639650
debug(output)
640651
write_file(self._frr_conf, output)
641652
debug('FRR: RENDERING CONFIG COMPLETE')
642-
return None
653+
return True
643654

644655
def apply(self, count_max=5):
645656
count = 0
@@ -649,7 +660,7 @@ def apply(self, count_max=5):
649660
debug(f'FRR: reloading configuration - tries: {count} | Python class ID: {id(self)}')
650661
cmdline = '/usr/lib/frr/frr-reload.py --reload'
651662
if os.path.exists(frr_debug_enable):
652-
cmdline += ' --debug'
663+
cmdline += ' --debug --stdout'
653664
rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}')
654665
if rc != 0:
655666
sleep(2)

smoketest/scripts/cli/base_vyostest_shim.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import pprint
1919

2020
from time import sleep
21-
from time import time
2221
from typing import Type
2322

2423
from vyos.configsession import ConfigSession
@@ -27,10 +26,17 @@
2726
from vyos.defaults import commit_lock
2827
from vyos.utils.process import cmd
2928
from vyos.utils.process import run
30-
from vyos.utils.process import process_named_running
3129

3230
save_config = '/tmp/vyos-smoketest-save'
3331

32+
# The commit process is not finished until all pending files from
33+
# VYATTA_CHANGES_ONLY_DIR are copied to VYATTA_ACTIVE_CONFIGURATION_DIR. This
34+
# is done inside libvyatta-cfg1 and the FUSE UnionFS part. On large non-
35+
# interactive commits FUSE UnionFS might not replicate the real state in time,
36+
# leading to errors when querying the working and effective configuration.
37+
# TO BE DELETED AFTER SWITCH TO IN MEMORY CONFIG
38+
CSTORE_GUARD_TIME = 4
39+
3440
# This class acts as shim between individual Smoketests developed for VyOS and
3541
# the Python UnitTest framework. Before every test is loaded, we dump the current
3642
# system configuration and reload it after the test - despite the test results.
@@ -45,7 +51,6 @@ class TestCase(unittest.TestCase):
4551
# trigger the certain failure condition.
4652
# Use "self.debug = True" in derived classes setUp() method
4753
debug = False
48-
commit_guard = time()
4954
@classmethod
5055
def setUpClass(cls):
5156
cls._session = ConfigSession(os.getpid())
@@ -86,14 +91,12 @@ def cli_commit(self):
8691
if self.debug:
8792
print('commit')
8893
self._session.commit()
89-
# during a commit there is a process opening commit_lock, and run() returns 0
94+
# During a commit there is a process opening commit_lock, and run()
95+
# returns 0
9096
while run(f'sudo lsof -nP {commit_lock}') == 0:
9197
sleep(0.250)
92-
# wait for FRR reload to be complete
93-
while process_named_running('frr-reload.py'):
94-
sleep(0.250)
95-
# reset getFRRconfig() guard timer
96-
self.commit_guard = time()
98+
# Wait for CStore completion for fast non-interactive commits
99+
sleep(CSTORE_GUARD_TIME)
97100

98101
def op_mode(self, path : list) -> None:
99102
"""
@@ -108,20 +111,27 @@ def op_mode(self, path : list) -> None:
108111
pprint.pprint(out)
109112
return out
110113

111-
def getFRRconfig(self, string=None, end='$', endsection='^!', daemon='', guard_time=10, empty_retry=0):
112-
""" Retrieve current "running configuration" from FRR """
113-
# Sometimes FRR needs some time after reloading the configuration to
114-
# appear in vtysh. This is a workaround addiung a 10 second guard timer
115-
# between the last cli_commit() and the first read of FRR config via vtysh
116-
while (time() - self.commit_guard) < guard_time:
117-
sleep(0.250) # wait 250 milliseconds
118-
command = f'vtysh -c "show run {daemon} no-header"'
119-
if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"'
114+
def getFRRconfig(self, string=None, end='$', endsection='^!',
115+
substring=None, endsubsection=None, empty_retry=0):
116+
"""
117+
Retrieve current "running configuration" from FRR
118+
119+
string: search for a specific start string in the configuration
120+
end: end of the section to search for (line ending)
121+
endsection: end of the configuration
122+
substring: search section under the result found by string
123+
endsubsection: end of the subsection (usually something with "exit")
124+
"""
125+
command = f'vtysh -c "show run no-header"'
126+
if string:
127+
command += f' | sed -n "/^{string}{end}/,/{endsection}/p"'
128+
if substring and endsubsection:
129+
command += f' | sed -n "/^{substring}/,/{endsubsection}/p"'
120130
out = cmd(command)
121131
if self.debug:
122132
print(f'\n\ncommand "{command}" returned:\n')
123133
pprint.pprint(out)
124-
if empty_retry:
134+
if empty_retry > 0:
125135
retry_count = 0
126136
while not out and retry_count < empty_retry:
127137
if self.debug and retry_count % 10 == 0:

smoketest/scripts/cli/test_interfaces_bonding.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from vyos.configsession import ConfigSessionError
2525
from vyos.utils.network import get_interface_config
2626
from vyos.utils.file import read_file
27-
from vyos.frrender import mgmt_daemon
2827

2928
class BondingInterfaceTest(BasicInterfaceTest.TestCase):
3029
@classmethod
@@ -287,7 +286,7 @@ def test_bonding_evpn_multihoming(self):
287286

288287
id = '5'
289288
for interface in self._interfaces:
290-
frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
289+
frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit')
291290

292291
self.assertIn(f' evpn mh es-id {id}', frrconfig)
293292
self.assertIn(f' evpn mh es-df-pref {id}', frrconfig)
@@ -304,7 +303,7 @@ def test_bonding_evpn_multihoming(self):
304303

305304
id = '5'
306305
for interface in self._interfaces:
307-
frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
306+
frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit')
308307
self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig)
309308
self.assertIn(f' evpn mh uplink', frrconfig)
310309

smoketest/scripts/cli/test_interfaces_ethernet.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@
2727
from base_interfaces_test import BasicInterfaceTest
2828
from vyos.configsession import ConfigSessionError
2929
from vyos.ifconfig import Section
30-
from vyos.frrender import mgmt_daemon
3130
from vyos.utils.file import read_file
3231
from vyos.utils.network import is_intf_addr_assigned
3332
from vyos.utils.network import is_ipv6_link_local
3433
from vyos.utils.process import cmd
3534
from vyos.utils.process import popen
3635

37-
3836
class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
3937
@classmethod
4038
def setUpClass(cls):
@@ -222,14 +220,14 @@ def test_ethtool_flow_control(self):
222220
out = loads(out)
223221
self.assertFalse(out[0]['autonegotiate'])
224222

225-
def test_ethtool_evpn_uplink_tarcking(self):
223+
def test_ethtool_evpn_uplink_tracking(self):
226224
for interface in self._interfaces:
227225
self.cli_set(self._base_path + [interface, 'evpn', 'uplink'])
228226

229227
self.cli_commit()
230228

231229
for interface in self._interfaces:
232-
frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon)
230+
frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit')
233231
self.assertIn(' evpn mh uplink', frrconfig)
234232

235233
def test_switchdev(self):
@@ -241,6 +239,5 @@ def test_switchdev(self):
241239

242240
self.cli_delete(self._base_path + [interface, 'switchdev'])
243241

244-
245242
if __name__ == '__main__':
246243
unittest.main(verbosity=2)

smoketest/scripts/cli/test_protocols_babel.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_01_basic(self):
6262

6363
self.cli_commit()
6464

65-
frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
65+
frrconfig = self.getFRRconfig('router babel', endsection='^exit')
6666
self.assertIn(f' babel diversity', frrconfig)
6767
self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig)
6868
self.assertIn(f' babel resend-delay {resend_delay}', frrconfig)
@@ -81,7 +81,7 @@ def test_02_redistribute(self):
8181

8282
self.cli_commit()
8383

84-
frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon, empty_retry=5)
84+
frrconfig = self.getFRRconfig('router babel', endsection='^exit', empty_retry=5)
8585
for protocol in ipv4_protos:
8686
self.assertIn(f' redistribute ipv4 {protocol}', frrconfig)
8787
for protocol in ipv6_protos:
@@ -150,7 +150,7 @@ def test_03_distribute_list(self):
150150

151151
self.cli_commit()
152152

153-
frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
153+
frrconfig = self.getFRRconfig('router babel', endsection='^exit')
154154
self.assertIn(f' distribute-list {access_list_in4} in', frrconfig)
155155
self.assertIn(f' distribute-list {access_list_out4} out', frrconfig)
156156
self.assertIn(f' ipv6 distribute-list {access_list_in6} in', frrconfig)
@@ -198,11 +198,11 @@ def test_04_interfaces(self):
198198

199199
self.cli_commit()
200200

201-
frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon)
201+
frrconfig = self.getFRRconfig('router babel', endsection='^exit')
202202
for interface in self._interfaces:
203203
self.assertIn(f' network {interface}', frrconfig)
204204

205-
iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon)
205+
iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit')
206206
self.assertIn(f' babel channel {channel}', iface_config)
207207
self.assertIn(f' babel enable-timestamps', iface_config)
208208
self.assertIn(f' babel update-interval {def_update_interval}', iface_config)

smoketest/scripts/cli/test_protocols_bfd.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from vyos.utils.process import process_named_running
2323

2424
base_path = ['protocols', 'bfd']
25-
frr_endsection = '^ exit'
2625

2726
dum_if = 'dum1001'
2827
vrf_name = 'red'
@@ -131,7 +130,7 @@ def test_bfd_peer(self):
131130
self.cli_commit()
132131

133132
# Verify FRR bgpd configuration
134-
frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=bfd_daemon)
133+
frrconfig = self.getFRRconfig('bfd', endsection='^exit')
135134
for peer, peer_config in peers.items():
136135
tmp = f'peer {peer}'
137136
if 'multihop' in peer_config:
@@ -144,8 +143,8 @@ def test_bfd_peer(self):
144143
tmp += f' vrf {peer_config["vrf"]}'
145144

146145
self.assertIn(tmp, frrconfig)
147-
peerconfig = self.getFRRconfig(f' peer {peer}', end='', endsection=frr_endsection,
148-
daemon=bfd_daemon)
146+
peerconfig = self.getFRRconfig('bfd', endsection='^exit', substring=f' peer {peer}',
147+
endsubsection='^ exit')
149148
if 'echo_mode' in peer_config:
150149
self.assertIn(f'echo-mode', peerconfig)
151150
if 'intv_echo' in peer_config:
@@ -207,7 +206,8 @@ def test_bfd_profile(self):
207206

208207
# Verify FRR bgpd configuration
209208
for profile, profile_config in profiles.items():
210-
config = self.getFRRconfig(f' profile {profile}', endsection=frr_endsection)
209+
config = self.getFRRconfig('bfd', endsection='^exit',
210+
substring=f' profile {profile}', endsubsection='^ exit',)
211211
if 'echo_mode' in profile_config:
212212
self.assertIn(f' echo-mode', config)
213213
if 'intv_echo' in profile_config:
@@ -229,8 +229,8 @@ def test_bfd_profile(self):
229229
self.assertNotIn(f'shutdown', config)
230230

231231
for peer, peer_config in peers.items():
232-
peerconfig = self.getFRRconfig(f' peer {peer}', end='',
233-
endsection=frr_endsection, daemon=bfd_daemon)
232+
peerconfig = self.getFRRconfig('bfd', endsection='^exit',
233+
substring=f' peer {peer}', endsubsection='^ exit')
234234
if 'profile' in peer_config:
235235
self.assertIn(f' profile {peer_config["profile"]}', peerconfig)
236236

0 commit comments

Comments
 (0)