Skip to content

Commit f9a6684

Browse files
committed
T8046: traffic-engineering: support link-params
Add 'traffic-engineering' commands under 'protocols'. set protocols traffic-engineering admin-group ADMINGROUP bit-position 1 set protocols traffic-engineering interface INTERFACE admin-group ADMINGROUP set protocols traffic-engineering interface INTERFACE max-bandwidth 1280 set protocols traffic-engineering interface INTERFACE max-reservable-bandwidth 1280 Also add set protocols isis traffic-engineering export
1 parent dffe642 commit f9a6684

File tree

7 files changed

+411
-3
lines changed

7 files changed

+411
-3
lines changed

data/templates/frr/isisd.frr.j2

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ advertise-passive-only
153153
{% endif %}
154154
mpls-te inter-as{{ level }}
155155
{% endif %}
156+
{% if traffic_engineering.export is vyos_defined %}
157+
mpls-te export
158+
{% endif %}
156159
{% if segment_routing is vyos_defined %}
157160
{% if segment_routing.maximum_label_depth is vyos_defined %}
158161
segment-routing node-msd {{ segment_routing.maximum_label_depth }}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{% if interface is vyos_defined %}
2+
!
3+
{% set ns = namespace(mask_by_admin_group=dict()) %}
4+
{% if admin_group is vyos_defined %}
5+
{% for ag in admin_group %}
6+
{% set _ = ns.mask_by_admin_group.update({ag: 1.__lshift__(admin_group[ag]['bit_position'] | int)}) %}
7+
{% endfor %}
8+
{% endif %}
9+
{% set mbit_per_second_2_byte_per_second = 1024 * 1024 / 8 %}
10+
{% for ifname, data in interface.items() %}
11+
interface {{ ifname }}
12+
link-params
13+
{% if 'metric' in data %}
14+
metric {{ data['metric'] }}
15+
{% endif %}
16+
{% if 'admin_group' in data %}
17+
{% set val = namespace(x=0) %}
18+
{% for ag in data['admin_group'] %}
19+
{% set val.x = (val.x | int).__or__(ns.mask_by_admin_group[ag]) %}
20+
{% endfor %}
21+
admin-grp {{ '0x{:x}'.format(val.x) }}
22+
{% endif %}
23+
{% if 'max_bandwidth' in data %}
24+
max-bw {{ (data['max_bandwidth'] | int) * mbit_per_second_2_byte_per_second }}
25+
{% endif %}
26+
{% if 'max_reservable_bandwidth' in data %}
27+
{% set v = (data['max_reservable_bandwidth'] | int) * mbit_per_second_2_byte_per_second %}
28+
max-rsv-bw {{ v }}
29+
{% for i in range(8) %}
30+
unrsv-bw {{ i }} {{ v }}
31+
{% endfor %}
32+
{% endif %}
33+
exit-link-params
34+
exit
35+
!
36+
{% endfor %}
37+
{% endif %}

interface-definitions/include/isis/protocol-common-config.xml.i

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@
266266
</constraint>
267267
</properties>
268268
</leafNode>
269+
<leafNode name="export">
270+
<properties>
271+
<help>Export Traffic Engineering DataBase to other daemons. See options under protocols traffic-engineering</help>
272+
<valueless/>
273+
</properties>
274+
</leafNode>
269275
</children>
270276
</node>
271277
<node name="segment-routing">
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<interfaceDefinition>
3+
<node name="protocols">
4+
<children>
5+
<node name="traffic-engineering" owner="${vyos_conf_scripts_dir}/protocols_traffic_engineering.py">
6+
<properties>
7+
<help>Traffic Engineering link parameters</help>
8+
<priority>605</priority>
9+
</properties>
10+
<children>
11+
<tagNode name="admin-group">
12+
<properties>
13+
<help>Configure administrative groups that can be used in interface configuration</help>
14+
<valueHelp>
15+
<format>txt</format>
16+
<description>Administrative group name</description>
17+
</valueHelp>
18+
</properties>
19+
<children>
20+
<leafNode name="bit-position">
21+
<properties>
22+
<help>Specify bit position of the admin group</help>
23+
<valueHelp>
24+
<format>u8:0-31</format>
25+
<description>Bit position of admin group</description>
26+
</valueHelp>
27+
<constraint>
28+
<validator name="numeric" argument="--range 0-31"/>
29+
</constraint>
30+
</properties>
31+
</leafNode>
32+
</children>
33+
</tagNode>
34+
<tagNode name="interface">
35+
<properties>
36+
<help>Traffic engineering parameters for interface</help>
37+
<completionHelp>
38+
<script>${vyos_completion_dir}/list_interfaces</script>
39+
</completionHelp>
40+
<valueHelp>
41+
<format>txt</format>
42+
<description>Interface to configure name</description>
43+
</valueHelp>
44+
<constraint>
45+
#include <include/constraint/interface-name.xml.i>
46+
</constraint>
47+
</properties>
48+
<children>
49+
<leafNode name="metric">
50+
<properties>
51+
<help>TE metric</help>
52+
<valueHelp>
53+
<format>u32:1-4294967295</format>
54+
<description>TE Metric (different from the OSPF or ISIS metric)</description>
55+
</valueHelp>
56+
<constraint>
57+
<validator name="numeric" argument="--range 1-4294967295"/>
58+
</constraint>
59+
</properties>
60+
</leafNode>
61+
<leafNode name="max-bandwidth">
62+
<properties>
63+
<help>Maximum bandwidth (interface speed by default)</help>
64+
<valueHelp>
65+
<format>u32:1-4294967295</format>
66+
<description>Maximum bandwidth in Mbits/sec</description>
67+
</valueHelp>
68+
<constraint>
69+
<validator name="numeric" argument="--range 1-4294967295"/>
70+
</constraint>
71+
</properties>
72+
</leafNode>
73+
<leafNode name="max-reservable-bandwidth">
74+
<properties>
75+
<help>Maximum reservable bandwidth</help>
76+
<valueHelp>
77+
<format>u32:1-4294967295</format>
78+
<description>Maximum reservable bandwidth in Mbits/sec</description>
79+
</valueHelp>
80+
<constraint>
81+
<validator name="numeric" argument="--range 1-4294967295"/>
82+
</constraint>
83+
</properties>
84+
</leafNode>
85+
<leafNode name="admin-group">
86+
<properties>
87+
<help>Admin groups of interface</help>
88+
<valueHelp>
89+
<format>txt</format>
90+
<description>Admin group of interface</description>
91+
</valueHelp>
92+
<multi/>
93+
</properties>
94+
</leafNode>
95+
</children>
96+
</tagNode>
97+
</children>
98+
</node>
99+
</children>
100+
</node>
101+
</interfaceDefinition>

python/vyos/frrender.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,26 @@ def debug(message):
5151
'new routing daemon configuration. To ensure network stability and avoid ' \
5252
'potential connectivity disruptions, the configuration was not applied!'
5353

54-
frr_protocols = ['babel', 'bfd', 'bgp', 'eigrp', 'isis', 'mpls', 'nhrp',
55-
'openfabric', 'ospf', 'ospfv3', 'pim', 'pim6', 'rip',
56-
'ripng', 'rpki', 'segment_routing', 'static']
54+
frr_protocols = [
55+
'babel',
56+
'bfd',
57+
'bgp',
58+
'eigrp',
59+
'isis',
60+
'mpls',
61+
'nhrp',
62+
'openfabric',
63+
'ospf',
64+
'ospfv3',
65+
'pim',
66+
'pim6',
67+
'rip',
68+
'ripng',
69+
'rpki',
70+
'segment_routing',
71+
'static',
72+
'traffic_engineering',
73+
]
5774

5875
babel_daemon = 'babeld'
5976
bfd_daemon = 'bfdd'
@@ -426,6 +443,21 @@ def dict_helper_nhrp_defaults(nhrp):
426443
elif conf.exists_effective(sr_cli_path):
427444
dict.update({'segment_routing' : deleted_protocol})
428445

446+
# We need to check the CLI if the Traffic Engineering node is present and thus load in
447+
# all the default values present on the CLI - that's why we have if conf.exists()
448+
te_cli_path = ['protocols', 'traffic-engineering']
449+
if conf.exists(te_cli_path):
450+
te = conf.get_config_dict(
451+
te_cli_path,
452+
key_mangling=('-', '_'),
453+
get_first_key=True,
454+
no_tag_node_value_mangle=True,
455+
with_recursive_defaults=True,
456+
)
457+
dict.update({'traffic_engineering': te})
458+
elif conf.exists_effective(te_cli_path):
459+
dict.update({'traffic_engineering': deleted_protocol})
460+
429461
# We need to check the CLI if the static node is present and thus load in
430462
# all the default values present on the CLI - that's why we have if conf.exists()
431463
static_cli_path = ['protocols', 'static']
@@ -722,6 +754,15 @@ def inline_helper(config_dict) -> str:
722754
if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']:
723755
output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing'])
724756
output += '\n'
757+
if (
758+
'traffic_engineering' in config_dict
759+
and 'deleted' not in config_dict['traffic_engineering']
760+
):
761+
output += render_to_string(
762+
'frr/zebra.traffic_engineering.frr.j2',
763+
config_dict['traffic_engineering'],
764+
)
765+
output += '\n'
725766
if 'static' in config_dict and 'deleted' not in config_dict['static']:
726767
output += render_to_string('frr/staticd.frr.j2', config_dict['static'])
727768
output += '\n'
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License version 2 or later as
7+
# published by the Free Software Foundation.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import unittest
18+
19+
from base_vyostest_shim import VyOSUnitTestSHIM
20+
21+
from vyos.configsession import ConfigSessionError
22+
from vyos.frrender import zebra_daemon
23+
from vyos.utils.process import process_named_running
24+
25+
base_path = ['protocols', 'traffic-engineering']
26+
27+
dummy_if1 = 'dum2191'
28+
dummy_if2 = 'dum2192'
29+
30+
31+
class TestProtocolsTrafficEngineering(VyOSUnitTestSHIM.TestCase):
32+
@classmethod
33+
def setUpClass(cls):
34+
# call base-classes classmethod
35+
super(TestProtocolsTrafficEngineering, cls).setUpClass()
36+
# Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
37+
cls.daemon_pid = process_named_running(zebra_daemon)
38+
# ensure we can also run this test on a live system - so lets clean
39+
# out the current configuration :)
40+
cls.cli_delete(cls, base_path)
41+
42+
cls.cli_set(cls, ['interfaces', 'dummy', dummy_if1])
43+
cls.cli_set(cls, ['interfaces', 'dummy', dummy_if2])
44+
cls.cli_commit(cls)
45+
46+
@classmethod
47+
def tearDownClass(cls):
48+
cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if2])
49+
cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if1])
50+
cls.cli_commit(cls)
51+
52+
super(TestProtocolsTrafficEngineering, cls).tearDownClass()
53+
54+
def tearDown(self):
55+
self.cli_delete(base_path)
56+
self.cli_commit()
57+
58+
# check process health and continuity
59+
self.assertEqual(self.daemon_pid, process_named_running(zebra_daemon))
60+
# always forward to base class
61+
super().tearDown()
62+
63+
def test_te_normal(self):
64+
self.cli_set(base_path + ['admin-group', 'cyan', 'bit-position', '1'])
65+
self.cli_set(base_path + ['admin-group', 'magenta', 'bit-position', '3'])
66+
67+
self.cli_set(base_path + ['interface', dummy_if1, 'admin-group', 'magenta'])
68+
self.cli_set(base_path + ['interface', dummy_if1, 'max-bandwidth', '1024'])
69+
self.cli_set(
70+
base_path + ['interface', dummy_if1, 'max-reservable-bandwidth', '2048']
71+
)
72+
self.cli_set(base_path + ['interface', dummy_if1, 'metric', '74837'])
73+
74+
self.cli_set(base_path + ['interface', dummy_if2, 'admin-group', 'cyan'])
75+
self.cli_set(base_path + ['interface', dummy_if2, 'admin-group', 'magenta'])
76+
77+
self.cli_commit()
78+
79+
frrconfig = self.getFRRconfig(f'^interface {dummy_if1}', stop_section='^exit')
80+
self.assertIn('link-params', frrconfig)
81+
self.assertIn('metric 74837', frrconfig)
82+
self.assertIn('admin-grp 0x8', frrconfig)
83+
self.assertIn('max-bw 1.34218e+08', frrconfig)
84+
self.assertIn('max-rsv-bw 2.68435e+08', frrconfig)
85+
self.assertIn('unrsv-bw 0 2.68435e+08', frrconfig)
86+
self.assertIn('unrsv-bw 1 2.68435e+08', frrconfig)
87+
self.assertIn('unrsv-bw 2 2.68435e+08', frrconfig)
88+
self.assertIn('unrsv-bw 3 2.68435e+08', frrconfig)
89+
self.assertIn('unrsv-bw 4 2.68435e+08', frrconfig)
90+
self.assertIn('unrsv-bw 5 2.68435e+08', frrconfig)
91+
self.assertIn('unrsv-bw 6 2.68435e+08', frrconfig)
92+
self.assertIn('unrsv-bw 7 2.68435e+08', frrconfig)
93+
94+
frrconfig = self.getFRRconfig(f'^interface {dummy_if2}', stop_section='^exit')
95+
self.assertIn('link-params', frrconfig)
96+
self.assertIn('admin-grp 0xa', frrconfig)
97+
98+
def test_te_verify(self):
99+
self.cli_set(base_path + ['interface', dummy_if1, 'admin-group', 'cyan'])
100+
101+
# Unknown group
102+
with self.assertRaises(ConfigSessionError):
103+
self.cli_commit()
104+
105+
self.cli_set(base_path + ['admin-group', 'cyan', 'bit-position', '0'])
106+
self.cli_set(base_path + ['admin-group', 'magenta', 'bit-position', '4'])
107+
108+
# Now group is known
109+
self.cli_commit()
110+
111+
self.cli_set(base_path + ['admin-group', 'red', 'bit-position', '4'])
112+
113+
# Same bit position as other group
114+
with self.assertRaises(ConfigSessionError):
115+
self.cli_commit()
116+
117+
self.cli_set(base_path + ['admin-group', 'red', 'bit-position', '2'])
118+
# Now should be ok
119+
self.cli_commit()
120+
121+
122+
if __name__ == '__main__':
123+
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())

0 commit comments

Comments
 (0)