From cf38245a2193c84a14f02695de5b12fe15abad0c Mon Sep 17 00:00:00 2001 From: Cheeze_It Date: Wed, 25 Sep 2024 15:59:24 -0600 Subject: [PATCH] T6750: Add initial Segment Routing Traffic Engineering --- data/templates/frr/daemons.frr.tmpl | 2 +- .../frr/zebra.segment_routing.frr.j2 | 36 +++ .../protocols_segment-routing.xml.in | 247 ++++++++++++++++++ .../cli/test_protocols_segment-routing.py | 100 ++++++- src/conf_mode/protocols_segment-routing.py | 48 +++- 5 files changed, 428 insertions(+), 5 deletions(-) diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index 3506528d2e..5c61cf46a9 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -38,7 +38,7 @@ pbrd=no bfdd=yes fabricd=yes vrrpd=no -pathd=no +pathd=yes # # Define defaults for all services even those who shall be kept disabled. diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2 index 7b12fcdd0a..bf542be5d7 100644 --- a/data/templates/frr/zebra.segment_routing.frr.j2 +++ b/data/templates/frr/zebra.segment_routing.frr.j2 @@ -1,4 +1,40 @@ ! +{% if traffic_engineering is vyos_defined %} +segment-routing + traffic-eng + mpls-te on +{% if traffic_engineering.database_import_protocol is vyos_defined %} +{% for protocol, protocol_config in traffic_engineering.database_import_protocol.items() %} + mpls-te import {{ protocol }} +{% endfor %} +{% endif %} +{% if traffic_engineering.segment_list is vyos_defined %} +{% for segment_list, segment_list_config in traffic_engineering.segment_list.items() %} + segment-list {{ segment_list }} +{% if segment_list_config.index.value.items() is vyos_defined %} +{% for index, index_config in segment_list_config.index.value.items() %} +{% if index_config.mpls.label is vyos_defined %} + index {{ index }} mpls label {{ index_config.mpls.label }} +{% endif %} +{% if index_config.nai is vyos_defined %} +{% if index_config.nai.adjacency is vyos_defined %} +{% for address_family, address_family_options in index_config.nai.adjacency.items() %} + index {{ index }} nai adjacency {{ address_family_options.source_identifier }} {{ address_family_options.destination_identifier }} +{% endfor %} +{% endif %} +{% if index_config.nai.prefix is vyos_defined %} +{% for address_family, address_family_options in index_config.nai.prefix.items() %} +{% for prefix, prefix_options in address_family_options.prefix_identifier.items() %} + index {{ index }} nai prefix {{ prefix }} {{ 'algorithm 0' if prefix_options.algorithm.spf is vyos_defined }} {{ 'algorithm 1' if prefix_options.algorithm.strict_spf is vyos_defined }} +{% endfor %} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} {% if srv6.locator is vyos_defined %} segment-routing srv6 diff --git a/interface-definitions/protocols_segment-routing.xml.in b/interface-definitions/protocols_segment-routing.xml.in index c299f624e4..b6d1300dcd 100644 --- a/interface-definitions/protocols_segment-routing.xml.in +++ b/interface-definitions/protocols_segment-routing.xml.in @@ -130,6 +130,253 @@ + + + Segment-Routing traffic engineering configuration + + + + + Traffic engineering database import protocol + + + + + IS-IS originated traffic engineering database + + + + + + OSPFv2 originated traffic engineering database + + + + + + + + + Traffic engineering segment list + + #include + + + + + + Traffic engineering index for segment list + + + + + Traffic engineering index value for segment list + + u32:0-4294967295 + Segment list index value + + + + + + + + + MPLS label for index + + + + + MPLS label value for index + + u32:16-1048575 + MPLS value for index + + + + + + + + + + + Node or Adjacency identifier (nai) for index + + + + + Adjacency identifier for index + + + + + IPv4 address + + + + + Adjacency source address identifier for index + + ipv4net + IPv4 adjacency source address identifier + + + + + + + + + Adjacency destination address identifier for index + + ipv4net + IPv4 adjacency destination address identifier + + + + + + + + + + + IPv6 address + + + + + Adjacency source address identifier for index + + ipv6net + IPv6 adjacency source address identifier + + + + + + + + + Adjacency destination address identifier for index + + ipv6net + IPv6 adjacency destination address identifier + + + + + + + + + + + + + IGP prefix identifier for index + + + + + IPv4 address + + + + + IPv4 IGP prefix address identifier for index + + ipv4net + IPv4 adjacency source address identifier + + + + + + + + + IGP prefix algorithm style + + + + + IGP prefix algorithm normal SPF + + + + + + IGP prefix algorithm strict SPF + + + + + + + + + + + + IPv6 address + + + + + IPv6 IGP prefix address identifier for index + + ipv6net + IPv6 adjacency source address identifier + + + + + + + + + IGP prefix algorithm style + + + + + IGP prefix algorithm normal SPF + + + + + + IGP prefix algorithm strict SPF + + + + + + + + + + + + + + + + + + + + + diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index daa7f088f2..470574a297 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -44,7 +44,7 @@ def tearDown(self): # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) - def test_srv6(self): + def test_segment_routing_01_srv6(self): interfaces = Section.interfaces('ethernet', vlan=False) locators = { 'foo' : { 'prefix' : '2001:a::/64' }, @@ -76,7 +76,7 @@ def test_srv6(self): self.assertIn(f' locator {locator}', frrconfig) self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) - def test_srv6_sysctl(self): + def test_segment_routing_02_srv6_sysctl(self): interfaces = Section.interfaces('ethernet', vlan=False) # HMAC accept @@ -106,5 +106,101 @@ def test_srv6_sysctl(self): self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') + def test_segment_routing_03_srte_database(self): + # Add database-import-protocol for isis and check the config + base_path = ['protocols', 'segment-routing', 'traffic-engineering'] + for protocol in ['isis', 'ospfv2']: + self.cli_set(base_path + ['database-import-protocol', protocol]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='pathd') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' mpls-te import {protocol}', frrconfig) + + self.cli_delete(base_path) + self.cli_commit() + + def test_segment_routing_04_mpls_label(self): + # Add segment-list with an mpls value + base_path = ['protocols', 'segment-routing', 'traffic-engineering'] + mpls_label = '500' + segment_list = 'smoketest-mpls-only-segment-list' + index_value = '0' + + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'mpls', 'label', mpls_label]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='pathd') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + self.assertIn(f' index {index_value} mpls label {mpls_label}', frrconfig) + + self.cli_delete(base_path) + self.cli_commit() + + def test_segment_routing_05_mpls_label_and_adjacency(self): + # Add segment-list with an mpls value and adjacency + base_path = ['protocols', 'segment-routing', 'traffic-engineering'] + mpls_label = '1000' + segment_list = 'smoketest-mpls-with-adjacency-segment-list' + index_value = '0' + + addresses = {'ipv4' : {'source_identifier' : '192.168.255.1', 'destination_identifier' : '192.168.255.2'}, + 'ipv6' : {'source_identifier' : '2003::1', 'destination_identifier' : '2003::2'}} + + for address_family in ['ipv4', 'ipv6']: + source_identifier = addresses[address_family]['source_identifier'] + destination_identifier = addresses[address_family]['destination_identifier'] + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'mpls', 'label', mpls_label]) + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'nai', 'adjacency', address_family, 'source-identifier', source_identifier]) + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'nai', 'adjacency', address_family, 'destination-identifier', destination_identifier]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='pathd') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + self.assertIn(f' index {index_value} mpls label {mpls_label} nai adjacency {source_identifier} {destination_identifier}', frrconfig) + + self.cli_delete(base_path) + self.cli_commit() + + + def test_segment_routing_06_mpls_label_and_prefix(self): + # Add segment-list with an mpls value and prefix + base_path = ['protocols', 'segment-routing', 'traffic-engineering'] + mpls_label = '1500' + segment_list = 'smoketest-mpls-with-prefix-segment-list' + index_value = '0' + + prefixes = {'ipv4' : {'prefix' : '192.168.255.0/24'}, + 'ipv6' : {'prefix' : '2003::/120'}} + + for address_family in ['ipv4', 'ipv6']: + for algorithm_type in ['spf', 'strict-spf']: + prefix = prefixes[address_family]['prefix'] + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'mpls', 'label', mpls_label]) + self.cli_set(base_path + ['segment-list', segment_list, 'index', 'value', index_value, 'nai', 'prefix', address_family, 'prefix-identifier', prefix, 'algorithm', algorithm_type]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', daemon='pathd') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + if algorithm_type == 'spf': + self.assertIn(f' index {index_value} mpls label {mpls_label} nai prefix {prefix} algorithm 0', frrconfig) + elif algorithm_type == 'strict-spf': + self.assertIn(f' index {index_value} mpls label {mpls_label} nai prefix {prefix} algorithm 1', frrconfig) + + self.cli_delete(base_path) + self.cli_commit() + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index b36c2ca117..7b25388698 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -45,9 +45,53 @@ def get_config(config=None): interfaces_removed = node_changed(conf, base + ['interface']) if interfaces_removed: sr['interface_removed'] = list(interfaces_removed) + + if dict_search('traffic_engineering', sr): + # Check for traffic engineering database import having more than one + # protocol configured concurrently + if dict_search('traffic_engineering.database_import_protocol', sr): + if 'isis' in (sr['traffic_engineering']['database_import_protocol'].keys()) \ + and 'ospfv2' in (sr['traffic_engineering']['database_import_protocol'].keys()): + raise ConfigError('Segment routing traffic engineering database import cannot ' \ + 'have isis and ospfv2 configured at the same time!') - import pprint - pprint.pprint(sr) + # Check for traffic engineering per segment list per index nai adjacency + # and prefix configured concurrently + if dict_search('traffic_engineering.segment_list', sr): + for segment_list in sr['traffic_engineering']['segment_list']: + for index in sr['traffic_engineering']['segment_list'][segment_list]['index']['value']: + if dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai', sr): + if 'adjacency' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai', sr)) \ + and 'prefix' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai', sr)): + raise ConfigError(f'Segment routing traffic engineering segment list ' \ + f'{segment_list} on index {index} cannot have prefix and ' \ + 'adjacency segment values configured at the same time!') + + # Check for traffic engineering per segment list per index nai adjacency + # ipv4 and ipv6 configured concurrently + if dict_search('traffic_engineering.segment_list', sr): + for segment_list in sr['traffic_engineering']['segment_list']: + for index in sr['traffic_engineering']['segment_list'][segment_list]['index']['value']: + if dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai', sr): + if dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.adjacency', sr): + if 'ipv4' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.adjacency', sr)) \ + and 'ipv6' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.adjacency', sr)): + raise ConfigError(f'Segment routing traffic engineering segment list ' \ + f'{segment_list} on index {index} on nai adjacency cannot ' \ + 'have ipv4 and ipv6 address families configured at the same time!') + + # Check for traffic engineering per segment list per index nai prefix + # ipv4 and ipv6 configured concurrently + if dict_search('traffic_engineering.segment_list', sr): + for segment_list in sr['traffic_engineering']['segment_list']: + for index in sr['traffic_engineering']['segment_list'][segment_list]['index']['value']: + if dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai', sr): + if dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.prefix', sr): + if 'ipv4' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.prefix', sr)) \ + and 'ipv6' in (dict_search(f'traffic_engineering.segment_list.{segment_list}.index.value.{index}.nai.prefix', sr)): + raise ConfigError(f'Segment routing traffic engineering segment list ' \ + f'{segment_list} on index {index} on nai prefix cannot ' \ + 'have ipv4 and ipv6 address families configured at the same time!') return sr def verify(sr):