From 3cb84afe3a6b9f22a35e0a887f7ca350644d6c8a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 21 Mar 2021 11:35:21 +0100 Subject: isis: T3417: move from cli tagNode to node As there can only be one running IS-IS process (FRR limitation) there is no need in having a tagNode here. This adds artifical restrictions/limitations when moving on to support VRFs for IS-IS protocol. --- src/conf_mode/protocols_isis.py | 162 +++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 92 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index b7afad473..24db52567 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 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 @@ -23,7 +23,6 @@ from vyos.configdict import node_changed from vyos import ConfigError from vyos.util import call from vyos.util import dict_search -from vyos.template import render from vyos.template import render_to_string from vyos import frr from vyos import airbag @@ -35,9 +34,8 @@ def get_config(config=None): else: conf = Config() base = ['protocols', 'isis'] - - isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - + isis = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) return isis def verify(isis): @@ -45,74 +43,70 @@ def verify(isis): if not isis: return None - for process, isis_config in isis.items(): - # If more then one isis process is defined (Frr only supports one) - # http://docs.frrouting.org/en/latest/isisd.html#isis-router - if len(isis) > 1: - raise ConfigError('Only one isis process can be defined') - - # If network entity title (net) not defined - if 'net' not in isis_config: - raise ConfigError('ISIS net format iso is mandatory!') - - # If interface not set - if 'interface' not in isis_config: - raise ConfigError('ISIS interface is mandatory!') - - # If md5 and plaintext-password set at the same time - if 'area_password' in isis_config: - if {'md5', 'plaintext_password'} <= set(isis_config['encryption']): - raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!') - - # If one param from delay set, but not set others - if 'spf_delay_ietf' in isis_config: - required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn'] - exist_timers = [] - for elm_timer in required_timers: - if elm_timer in isis_config['spf_delay_ietf']: - exist_timers.append(elm_timer) - - exist_timers = set(required_timers).difference(set(exist_timers)) - if len(exist_timers) > 0: - raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) - - # If Redistribute set, but level don't set - if 'redistribute' in isis_config: - proc_level = isis_config.get('level','').replace('-','_') - for proto, proto_config in isis_config.get('redistribute', {}).get('ipv4', {}).items(): - if 'level_1' not in proto_config and 'level_2' not in proto_config: - raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto)) - for redistribute_level in proto_config.keys(): - if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level: - raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level)) - - # Segment routing checks - if dict_search('segment_routing', isis_config): - if dict_search('segment_routing.global_block', isis_config): - high_label_value = dict_search('segment_routing.global_block.high_label_value', isis_config) - low_label_value = dict_search('segment_routing.global_block.low_label_value', isis_config) - # If segment routing global block high value is blank, throw error - if low_label_value and not high_label_value: - raise ConfigError('Segment routing global block high value must not be left blank') - # If segment routing global block low value is blank, throw error - if high_label_value and not low_label_value: - raise ConfigError('Segment routing global block low value must not be left blank') - # If segment routing global block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing global block low value must be lower than high value') - - if dict_search('segment_routing.local_block', isis_config): - high_label_value = dict_search('segment_routing.local_block.high_label_value', isis_config) - low_label_value = dict_search('segment_routing.local_block.low_label_value', isis_config) - # If segment routing local block high value is blank, throw error - if low_label_value and not high_label_value: - raise ConfigError('Segment routing local block high value must not be left blank') - # If segment routing local block low value is blank, throw error - if high_label_value and not low_label_value: - raise ConfigError('Segment routing local block low value must not be left blank') - # If segment routing local block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing local block low value must be lower than high value') + if 'domain' not in isis: + raise ConfigError('Routing domain name/tag must be set!') + + if 'net' not in isis: + raise ConfigError('Network entity is mandatory!') + + # If interface not set + if 'interface' not in isis: + raise ConfigError('Interface used for routing updates is mandatory!') + + # If md5 and plaintext-password set at the same time + if 'area_password' in isis: + if {'md5', 'plaintext_password'} <= set(isis['encryption']): + raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!') + + # If one param from delay set, but not set others + if 'spf_delay_ietf' in isis: + required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn'] + exist_timers = [] + for elm_timer in required_timers: + if elm_timer in isis['spf_delay_ietf']: + exist_timers.append(elm_timer) + + exist_timers = set(required_timers).difference(set(exist_timers)) + if len(exist_timers) > 0: + raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) + + # If Redistribute set, but level don't set + if 'redistribute' in isis: + proc_level = isis.get('level','').replace('-','_') + for proto, proto_config in isis.get('redistribute', {}).get('ipv4', {}).items(): + if 'level_1' not in proto_config and 'level_2' not in proto_config: + raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto)) + for redistribute_level in proto_config.keys(): + if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level: + raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level)) + + # Segment routing checks + if dict_search('segment_routing', isis): + if dict_search('segment_routing.global_block', isis): + high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + # If segment routing global block high value is blank, throw error + if low_label_value and not high_label_value: + raise ConfigError('Segment routing global block high value must not be left blank') + # If segment routing global block low value is blank, throw error + if high_label_value and not low_label_value: + raise ConfigError('Segment routing global block low value must not be left blank') + # If segment routing global block low value is higher than the high value, throw error + if int(low_label_value) > int(high_label_value): + raise ConfigError('Segment routing global block low value must be lower than high value') + + if dict_search('segment_routing.local_block', isis): + high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) + low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) + # If segment routing local block high value is blank, throw error + if low_label_value and not high_label_value: + raise ConfigError('Segment routing local block high value must not be left blank') + # If segment routing local block low value is blank, throw error + if high_label_value and not low_label_value: + raise ConfigError('Segment routing local block low value must not be left blank') + # If segment routing local block low value is higher than the high value, throw error + if int(low_label_value) > int(high_label_value): + raise ConfigError('Segment routing local block low value must be lower than high value') return None @@ -121,22 +115,15 @@ def generate(isis): isis['new_frr_config'] = '' return None - # only one ISIS process is supported, so we can directly send the first key - # of the config dict - process = list(isis.keys())[0] - isis[process]['process'] = process - - isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', - isis[process]) - + isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', isis) return None def apply(isis): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() frr_cfg.load_configuration(daemon='isisd') - frr_cfg.modify_section(r'interface \S+', '') - frr_cfg.modify_section(f'router isis \S+', '') + frr_cfg.modify_section('^interface \S+$', '') + frr_cfg.modify_section('^router isis \S+$', '') frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', isis['new_frr_config']) frr_cfg.commit_configuration(daemon='isisd') @@ -146,15 +133,6 @@ def apply(isis): for a in range(5): frr_cfg.commit_configuration(daemon='isisd') - # Debugging - ''' - print('') - print('--------- DEBUGGING ----------') - print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') - print(f'Replacement config:\n{isis["new_frr_config"]}\n\n') - print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') - ''' - return None if __name__ == '__main__': -- cgit v1.2.3 From a234f616b9fff24b74f2b33dc83cf7c7edef3342 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 21 Mar 2021 13:33:06 +0100 Subject: isis: T3417: add VRF support VRF support can be tested using: set vrf name red table 1000 set vrf name red protocols isis domain FOOO set vrf name red protocols isis net 49.0001.1921.6800.1002.00 set vrf name red protocols isis interface eth1 --- data/templates/frr/isis.frr.tmpl | 4 +- .../include/isis/isis-common-config.xml.i | 764 +++++++++++++++++++++ interface-definitions/protocols-isis.xml.in | 763 +------------------- interface-definitions/vrf.xml.in | 29 +- smoketest/scripts/cli/test_protocols_isis.py | 40 +- src/conf_mode/protocols_isis.py | 72 +- 6 files changed, 887 insertions(+), 785 deletions(-) create mode 100644 interface-definitions/include/isis/isis-common-config.xml.i (limited to 'src/conf_mode') diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl index d4500403c..da098abac 100644 --- a/data/templates/frr/isis.frr.tmpl +++ b/data/templates/frr/isis.frr.tmpl @@ -1,5 +1,5 @@ ! -router isis {{ domain }} +router isis {{ domain }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} net {{ net }} {% if dynamic_hostname is defined %} hostname dynamic @@ -133,7 +133,7 @@ router isis {{ domain }} ! {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} -interface {{ iface }} +interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} ip router isis {{ domain }} {% if iface_config.bfd is defined %} isis bfd diff --git a/interface-definitions/include/isis/isis-common-config.xml.i b/interface-definitions/include/isis/isis-common-config.xml.i new file mode 100644 index 000000000..94a6e3592 --- /dev/null +++ b/interface-definitions/include/isis/isis-common-config.xml.i @@ -0,0 +1,764 @@ + + + + Configure the authentication password for an area + + + + + Plain-text authentication type + + txt + Level-wide password + + + + + + MD5 authentication type + + txt + Level-wide password + + + + + + + + Control distribution of default information + + + + + Distribute a default route + + + + + Distribute default route for IPv4 + + + + + Distribute default route into level-1 + + + + + + Distribute default route into level-2 + + + + + + + + Distribute default route for IPv6 + + + + + Distribute default route into level-1 + + always + + + always + Always advertise default route + + + + + + Distribute default route into level-2 + + always + + + always + Always advertise default route + + + + + + + + + + + + Domain name used for this instance + + txt + IS-IS routing area domain/tag + + + + + + Set the authentication password for a routing domain + + + + + Plain-text authentication type + + txt + Level-wide password + + + + + + + + + Dynamic hostname for IS-IS + + + + + + IS-IS level number + + level-1 level-1-2 level-2 + + + level-1 + Act as a station router + + + level-1-2 + Act as both a station and an area router + + + level-2 + Act as an area router + + + (level-1|level-1-2|level-2) + + + + + + Minimum interval between regenerating same LSP + + u32:1-120 + Minimum interval in seconds + + + + + + + + + Configure the maximum size of generated LSPs + + u32:128-4352 + Maximum size of generated LSPs + + + + + + + + + LSP refresh interval + + u32:1-65235 + LSP refresh interval in seconds + + + + + + + + + Maximum LSP lifetime + + u32:350-65535 + LSP lifetime in seconds + + + + + + + + + Use old-style (ISO 10589) or new-style packet formats + + narrow transition wide + + + narrow + Use old style of TLVs with narrow metric + + + transition + Send and accept both styles of TLVs during transition + + + wide + Use new style of TLVs to carry wider metric + + + (narrow|transition|wide) + + + + + + A Network Entity Title for this process (ISO only) + + XX.XXXX. ... .XXX.XX + Network entity title (NET) + + + [a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2} + + + + + + Use the RFC 6232 purge-originator + + + + + + Show IS-IS neighbor adjacencies + + + + + Enable MPLS traffic engineering extensions + + + + + + + MPLS traffic engineering router ID + + ipv4 + IPv4 address + + + + + + + + + + + Segment-Routing (SPRING) settings + + + + + Enable segment-routing functionality + + + + + + Global block label range + + + + + The lower bound of the global block + + u32:16-1048575 + MPLS label value + + + + + + + + + The upper bound of the global block + + u32:16-1048575 + MPLS label value + + + + + + + + + + + + Maximum MPLS labels allowed for this router + + u32:1-16 + MPLS label depth + + + + + + + + + Static IPv4/IPv6 prefix segment/label mapping + + ipv4net + IPv4 prefix segment + + + ipv6net + IPv6 prefix segment + + + + + + + + + + Specify the absolute value of prefix segment/label ID + + + + + Specify the absolute value of prefix segment/label ID + + u32:16-1048575 + The absolute segment/label ID value + + + + + + + + + Request upstream neighbor to replace segment/label with explicit null label + + + + + + Do not request penultimate hop popping for segment/label + + + + + + + + Specify the index value of prefix segment/label ID + + + + + Specify the index value of prefix segment/label ID + + u32:0-65535 + The index segment/label ID value + + + + + + + + + Request upstream neighbor to replace segment/label with explicit null label + + + + + + Do not request penultimate hop popping for segment/label + + + + + + + + + + + + Redistribute information from another routing protocol + + + + + Redistribute IPv4 routes + + + + + Border Gateway Protocol (BGP) + + + #include + + + + + Redistribute connected routes into IS-IS + + + #include + + + + + Redistribute kernel routes into IS-IS + + + #include + + + + + Redistribute OSPF routes into IS-IS + + + #include + + + + + Redistribute RIP routes into IS-IS + + + #include + + + + + Redistribute static routes into IS-IS + + + #include + + + + + + + + + Set attached bit to identify as L1/L2 router for inter-area traffic + + + + + + Set overload bit to avoid any transit traffic + + + + + + IETF SPF delay algorithm + + + + + Delay used while in QUIET state + + u32:0-60000 + Delay used while in QUIET state (in ms) + + + + + + + + + Delay used while in SHORT_WAIT state + + u32:0-60000 + Delay used while in SHORT_WAIT state (in ms) + + + + + + + + + Delay used while in LONG_WAIT + + u32:0-60000 + Delay used while in LONG_WAIT state (in ms) + + + + + + + + + Time with no received IGP events before considering IGP stable + + u32:0-60000 + Time with no received IGP events before considering IGP stable (in ms) + + + + + + + + + Maximum duration needed to learn all the events related to a single failure + + u32:0-60000 + Maximum duration needed to learn all the events related to a single failure (in ms) + + + + + + + + + + + Minimum interval between SPF calculations + + u32:1-120 + Minimum interval between consecutive SPFs in seconds + + + + + + + + + + Interface params + + + + + + #include + + + Configure circuit type for interface + + level-1 level-1-2 level-2-only + + + level-1 + Level-1 only adjacencies are formed + + + level-1-2 + Level-1-2 adjacencies are formed + + + level-2-only + Level-2 only adjacencies are formed + + + (level-1|level-1-2|level-2-only) + + + + + + Add padding to IS-IS hello packets + + + + + + Set Hello interval + + u32:1-600 + Set Hello interval + + + + + + + + + Set Hello interval + + u32:2-100 + Set multiplier for Hello holding time + + + + + + + + + Set default metric for circuit + + u32:0-16777215 + Default metric value + + + + + + + + + Set network type + + + + + point-to-point network type + + + + + + #include + + + Configure the authentication password for a circuit + + + + + Plain-text authentication type + + txt + Circuit password + + + + + + + + Set priority for Designated Router election + + u32:0-127 + Priority value + + + + + + + + + Set PSNP interval in seconds + + u32:0-127 + Priority value + + + + + + + + + Disable three-way handshake + + + + + + diff --git a/interface-definitions/protocols-isis.xml.in b/interface-definitions/protocols-isis.xml.in index e6982aca3..1bc890446 100644 --- a/interface-definitions/protocols-isis.xml.in +++ b/interface-definitions/protocols-isis.xml.in @@ -8,768 +8,7 @@ 610 - - - Configure the authentication password for an area - - - - - Plain-text authentication type - - txt - Level-wide password - - - - - - MD5 authentication type - - txt - Level-wide password - - - - - - - - Control distribution of default information - - - - - Distribute a default route - - - - - Distribute default route for IPv4 - - - - - Distribute default route into level-1 - - - - - - Distribute default route into level-2 - - - - - - - - Distribute default route for IPv6 - - - - - Distribute default route into level-1 - - always - - - always - Always advertise default route - - - - - - Distribute default route into level-2 - - always - - - always - Always advertise default route - - - - - - - - - - - - Domain name used for this instance - - txt - IS-IS routing area domain/tag - - - - - - Set the authentication password for a routing domain - - - - - Plain-text authentication type - - txt - Level-wide password - - - - - - - - - Dynamic hostname for IS-IS - - - - - - IS-IS level number - - level-1 level-1-2 level-2 - - - level-1 - Act as a station router - - - level-1-2 - Act as both a station and an area router - - - level-2 - Act as an area router - - - (level-1|level-1-2|level-2) - - - - - - Minimum interval between regenerating same LSP - - u32:1-120 - Minimum interval in seconds - - - - - - - - - Configure the maximum size of generated LSPs - - u32:128-4352 - Maximum size of generated LSPs - - - - - - - - - LSP refresh interval - - u32:1-65235 - LSP refresh interval in seconds - - - - - - - - - Maximum LSP lifetime - - u32:350-65535 - LSP lifetime in seconds - - - - - - - - - Use old-style (ISO 10589) or new-style packet formats - - narrow transition wide - - - narrow - Use old style of TLVs with narrow metric - - - transition - Send and accept both styles of TLVs during transition - - - wide - Use new style of TLVs to carry wider metric - - - (narrow|transition|wide) - - - - - - A Network Entity Title for this process (ISO only) - - XX.XXXX. ... .XXX.XX - Network entity title (NET) - - - [a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2} - - - - - - Use the RFC 6232 purge-originator - - - - - - Show IS-IS neighbor adjacencies - - - - - Enable MPLS traffic engineering extensions - - - - - - - MPLS traffic engineering router ID - - ipv4 - IPv4 address - - - - - - - - - - - Segment-Routing (SPRING) settings - - - - - Enable segment-routing functionality - - - - - - Global block label range - - - - - The lower bound of the global block - - u32:16-1048575 - MPLS label value - - - - - - - - - The upper bound of the global block - - u32:16-1048575 - MPLS label value - - - - - - - - - - - - Maximum MPLS labels allowed for this router - - u32:1-16 - MPLS label depth - - - - - - - - - Static IPv4/IPv6 prefix segment/label mapping - - ipv4net - IPv4 prefix segment - - - ipv6net - IPv6 prefix segment - - - - - - - - - - Specify the absolute value of prefix segment/label ID - - - - - Specify the absolute value of prefix segment/label ID - - u32:16-1048575 - The absolute segment/label ID value - - - - - - - - - Request upstream neighbor to replace segment/label with explicit null label - - - - - - Do not request penultimate hop popping for segment/label - - - - - - - - Specify the index value of prefix segment/label ID - - - - - Specify the index value of prefix segment/label ID - - u32:0-65535 - The index segment/label ID value - - - - - - - - - Request upstream neighbor to replace segment/label with explicit null label - - - - - - Do not request penultimate hop popping for segment/label - - - - - - - - - - - - Redistribute information from another routing protocol - - - - - Redistribute IPv4 routes - - - - - Border Gateway Protocol (BGP) - - - #include - - - - - Redistribute connected routes into IS-IS - - - #include - - - - - Redistribute kernel routes into IS-IS - - - #include - - - - - Redistribute OSPF routes into IS-IS - - - #include - - - - - Redistribute RIP routes into IS-IS - - - #include - - - - - Redistribute static routes into IS-IS - - - #include - - - - - - - - - Set attached bit to identify as L1/L2 router for inter-area traffic - - - - - - Set overload bit to avoid any transit traffic - - - - - - IETF SPF delay algorithm - - - - - Delay used while in QUIET state - - u32:0-60000 - Delay used while in QUIET state (in ms) - - - - - - - - - Delay used while in SHORT_WAIT state - - u32:0-60000 - Delay used while in SHORT_WAIT state (in ms) - - - - - - - - - Delay used while in LONG_WAIT - - u32:0-60000 - Delay used while in LONG_WAIT state (in ms) - - - - - - - - - Time with no received IGP events before considering IGP stable - - u32:0-60000 - Time with no received IGP events before considering IGP stable (in ms) - - - - - - - - - Maximum duration needed to learn all the events related to a single failure - - u32:0-60000 - Maximum duration needed to learn all the events related to a single failure (in ms) - - - - - - - - - - - Minimum interval between SPF calculations - - u32:1-120 - Minimum interval between consecutive SPFs in seconds - - - - - - - - - - Interface params - - - - - - #include - - - Configure circuit type for interface - - level-1 level-1-2 level-2-only - - - level-1 - Level-1 only adjacencies are formed - - - level-1-2 - Level-1-2 adjacencies are formed - - - level-2-only - Level-2 only adjacencies are formed - - - (level-1|level-1-2|level-2-only) - - - - - - Add padding to IS-IS hello packets - - - - - - Set Hello interval - - u32:1-600 - Set Hello interval - - - - - - - - - Set Hello interval - - u32:2-100 - Set multiplier for Hello holding time - - - - - - - - - Set default metric for circuit - - u32:0-16777215 - Default metric value - - - - - - - - - Set network type - - - - - point-to-point network type - - - - - - #include - - - Configure the authentication password for a circuit - - - - - Plain-text authentication type - - txt - Circuit password - - - - - - - - Set priority for Designated Router election - - u32:0-127 - Priority value - - - - - - - - - Set PSNP interval in seconds - - u32:0-127 - Priority value - - - - - - - - - Disable three-way handshake - - - - - + #include diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in index de605e89b..95428eb94 100644 --- a/interface-definitions/vrf.xml.in +++ b/interface-definitions/vrf.xml.in @@ -33,16 +33,6 @@ Routing protocol parameters - - - Static route parameters - 481 - - - #include - #include - - Border Gateway Protocol (BGP) @@ -59,6 +49,15 @@ #include + + + Intermediate System to Intermediate System (IS-IS) + 611 + + + #include + + Open Shortest Path First (OSPF) @@ -68,6 +67,16 @@ #include + + + Static route parameters + 481 + + + #include + #include + + diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 765e0ef4e..79d497186 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -36,7 +36,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) - def test_isis_redistribute(self): + def test_isis_01_redistribute(self): prefix_list = 'EXPORT-ISIS' route_map = 'EXPORT-ISIS' rule = '10' @@ -57,7 +57,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', end='') + # XXX: FRR represents router isis with a trailing whitespace :/ + tmp = self.getFRRconfig(f'router isis {domain} ') self.assertIn(f' net {net}', tmp) self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) @@ -67,5 +68,40 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_delete(['policy']) + + def test_isis_02_vrfs(self): + vrfs = ['red', 'green', 'blue'] + # It is safe to assume that when the basic VRF test works, all other + # IS-IS related features work, as we entirely inherit the CLI templates + # and Jinja2 FRR template. + table = '1000' + vrf = 'red' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'isis', 'domain', domain]) + self.cli_set(vrf_base + ['protocols', 'isis', 'net', net]) + self.cli_set(vrf_base + ['protocols', 'isis', 'interface', vrf_iface]) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) + + # Also set a default VRF IS-IS config + self.cli_set(base_path + ['domain', domain]) + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', 'eth0']) + self.cli_commit() + + # Verify FRR isisd configuration + # XXX: FRR represents router isis with a trailing whitespace :/ + tmp = self.getFRRconfig(f'router isis {domain} ') + self.assertIn(f'router isis {domain}', tmp) + self.assertIn(f' net {net}', tmp) + + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}') + self.assertIn(f'router isis {domain} vrf {vrf}', tmp) + self.assertIn(f' net {net}', tmp) + + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 24db52567..8acff58cc 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -17,30 +17,61 @@ import os from sys import exit +from sys import argv from vyos.config import Config from vyos.configdict import node_changed -from vyos import ConfigError from vyos.util import call from vyos.util import dict_search +from vyos.util import get_interface_config from vyos.template import render_to_string +from vyos import ConfigError from vyos import frr from vyos import airbag airbag.enable() +frr_daemon = 'isisd' + def get_config(config=None): if config: conf = config else: conf = Config() - base = ['protocols', 'isis'] + + vrf = None + if len(argv) > 1: + vrf = argv[1] + + base_path = ['protocols', 'isis'] + + # eqivalent of the C foo ? 'a' : 'b' statement + base = vrf and ['vrf', 'name', vrf, 'protocols', 'isis'] or base_path isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # Assign the name of our VRF context. This MUST be done before the return + # statement below, else on deletion we will delete the default instance + # instead of the VRF instance. + if vrf: isis['vrf'] = vrf + + # As we no re-use this Python handler for both VRF and non VRF instances for + # IS-IS we need to find out if any interfaces changed so properly adjust + # the FRR configuration and not by acctident change interfaces from a + # different VRF. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + isis['interface_removed'] = list(interfaces_removed) + + # Bail out early if configuration tree does not exist + if not conf.exists(base): + isis.update({'deleted' : ''}) + return isis + return isis def verify(isis): # bail out early - looks like removal from running config - if not isis: + if not isis or 'deleted' in isis: return None if 'domain' not in isis: @@ -53,6 +84,17 @@ def verify(isis): if 'interface' not in isis: raise ConfigError('Interface used for routing updates is mandatory!') + for interface in isis['interface']: + if 'vrf' in isis: + # If interface specific options are set, we must ensure that the + # interface is bound to our requesting VRF. Due to the VyOS + # priorities the interface is bound to the VRF after creation of + # the VRF itself, and before any routing protocol is configured. + vrf = isis['vrf'] + tmp = get_interface_config(interface) + if 'master' not in tmp or tmp['master'] != vrf: + raise ConfigError(f'Interface {interface} is not a member of VRF {vrf}!') + # If md5 and plaintext-password set at the same time if 'area_password' in isis: if {'md5', 'plaintext_password'} <= set(isis['encryption']): @@ -111,7 +153,7 @@ def verify(isis): return None def generate(isis): - if not isis: + if not isis or 'deleted' in isis: isis['new_frr_config'] = '' return None @@ -121,17 +163,29 @@ def generate(isis): def apply(isis): # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() - frr_cfg.load_configuration(daemon='isisd') - frr_cfg.modify_section('^interface \S+$', '') - frr_cfg.modify_section('^router isis \S+$', '') + frr_cfg.load_configuration(frr_daemon) + + # Generate empty helper string which can be ammended to FRR commands, + # it will be either empty (default VRF) or contain the "vrf Date: Sun, 21 Mar 2021 13:52:25 +0100 Subject: ospf: vrf: T2271: ease FRR interaction for config reload Instead of multiple if/else paths, use a common vrf string variable which is either populated or not. In addtion when interfaces are configured for a given VRF, harden the regex for config reload. --- smoketest/scripts/cli/test_protocols_ospf.py | 25 ++++++++++++------------- src/conf_mode/protocols_ospf.py | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) (limited to 'src/conf_mode') diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 385f50918..8d94c86cb 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -288,16 +288,16 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): def test_ospf_11_vrfs(self): - vrfs = ['red', 'green', 'blue'] # It is safe to assume that when the basic VRF test works, all # other OSPF related features work, as we entirely inherit the CLI # templates and Jinja2 FRR template. table = '1000' - for vrf in vrfs: - vrf_base = ['vrf', 'name', vrf] - self.cli_set(vrf_base + ['table', table]) - self.cli_set(vrf_base + ['protocols', 'ospf']) - table = str(int(table) + 1000) + vrf = 'blue' + vrf_base = ['vrf', 'name', vrf] + vrf_iface = 'eth1' + self.cli_set(vrf_base + ['table', table]) + self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface]) + self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) # Also set a default VRF OSPF config self.cli_set(base_path) @@ -309,14 +309,13 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - for vrf in vrfs: - frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}') - self.assertIn(f'router ospf vrf {vrf}', frrconfig) - self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) - self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - - self.cli_delete(['vrf', 'name', vrf]) + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}') + self.assertIn(f'router ospf vrf {vrf}', frrconfig) + self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.cli_delete(['vrf', 'name', vrf]) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 08347aa52..a655eaeca 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -154,11 +154,10 @@ def verify(ospf): f'concurrently for {interface}!') if 'vrf' in ospf: - # If interface specific options are set, we must ensure that - # the interface is bound to our requesting VRF. Due to the VyOS - # priorities the interface is bound to the VRF after creation - # of the VRF itself, and before any routing protocol is - # configured. + # If interface specific options are set, we must ensure that the + # interface is bound to our requesting VRF. Due to the VyOS + # priorities the interface is bound to the VRF after creation of + # the VRF itself, and before any routing protocol is configured. vrf = ospf['vrf'] tmp = get_interface_config(interface) if 'master' not in tmp or tmp['master'] != vrf: @@ -179,17 +178,18 @@ def apply(ospf): frr_cfg = frr.FRRConfig() frr_cfg.load_configuration(frr_daemon) + # Generate empty helper string which can be ammended to FRR commands, + # it will be either empty (default VRF) or contain the "vrf Date: Sun, 21 Mar 2021 14:32:28 +0100 Subject: isis: T3417: cleanup verify() --- src/conf_mode/protocols_isis.py | 68 ++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 32 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 8acff58cc..34cd392f6 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -115,40 +115,44 @@ def verify(isis): # If Redistribute set, but level don't set if 'redistribute' in isis: proc_level = isis.get('level','').replace('-','_') - for proto, proto_config in isis.get('redistribute', {}).get('ipv4', {}).items(): - if 'level_1' not in proto_config and 'level_2' not in proto_config: - raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto)) - for redistribute_level in proto_config.keys(): - if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level: - raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level)) + for afi in ['ipv4']: + if afi not in isis['redistribute']: + continue + + for proto, proto_config in isis['redistribute'][afi].items(): + if 'level_1' not in proto_config and 'level_2' not in proto_config: + raise ConfigError(f'Redistribute level-1 or level-2 should be specified in ' \ + f'"protocols isis {process} redistribute {afi} {proto}"!') + + for redistr_level, redistr_config in proto_config.items(): + if proc_level and proc_level != 'level_1_2' and proc_level != redistr_level: + raise ConfigError(f'"protocols isis {process} redistribute {afi} {proto} {redistr_level}" ' \ + f'can not be used with \"protocols isis {process} level {proc_level}\"') # Segment routing checks - if dict_search('segment_routing', isis): - if dict_search('segment_routing.global_block', isis): - high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) - # If segment routing global block high value is blank, throw error - if low_label_value and not high_label_value: - raise ConfigError('Segment routing global block high value must not be left blank') - # If segment routing global block low value is blank, throw error - if high_label_value and not low_label_value: - raise ConfigError('Segment routing global block low value must not be left blank') - # If segment routing global block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing global block low value must be lower than high value') - - if dict_search('segment_routing.local_block', isis): - high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) - low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) - # If segment routing local block high value is blank, throw error - if low_label_value and not high_label_value: - raise ConfigError('Segment routing local block high value must not be left blank') - # If segment routing local block low value is blank, throw error - if high_label_value and not low_label_value: - raise ConfigError('Segment routing local block low value must not be left blank') - # If segment routing local block low value is higher than the high value, throw error - if int(low_label_value) > int(high_label_value): - raise ConfigError('Segment routing local block low value must be lower than high value') + if dict_search('segment_routing.global_block', isis): + high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) + low_label_value = dict_search('segment_routing.global_block.low_label_value', isis) + + # If segment routing global block high value is blank, throw error + if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): + raise ConfigError('Segment routing global block requires both low and high value!') + + # If segment routing global block low value is higher than the high value, throw error + if int(low_label_value) > int(high_label_value): + raise ConfigError('Segment routing global block low value must be lower than high value') + + if dict_search('segment_routing.local_block', isis): + high_label_value = dict_search('segment_routing.local_block.high_label_value', isis) + low_label_value = dict_search('segment_routing.local_block.low_label_value', isis) + + # If segment routing local block high value is blank, throw error + if (low_label_value and not high_label_value) or (high_label_value and not low_label_value): + raise ConfigError('Segment routing local block requires both high and low value!') + + # If segment routing local block low value is higher than the high value, throw error + if int(low_label_value) > int(high_label_value): + raise ConfigError('Segment routing local block low value must be lower than high value') return None -- cgit v1.2.3 From 018eda75b6202dc2006b62ad0132298087d696bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 21 Mar 2021 14:43:10 +0100 Subject: isis: T3417: verify route-map used in redistribute exists --- src/conf_mode/protocols_isis.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 34cd392f6..68934b565 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -20,7 +20,9 @@ from sys import exit from sys import argv from vyos.config import Config +from vyos.configdict import dict_merge from vyos.configdict import node_changed +from vyos.configverify import verify_interface_exists from vyos.util import call from vyos.util import dict_search from vyos.util import get_interface_config @@ -67,6 +69,13 @@ def get_config(config=None): isis.update({'deleted' : ''}) return isis + # We also need some additional information from the config, prefix-lists + # and route-maps for instance. They will be used in verify() + base = ['policy'] + tmp = conf.get_config_dict(base, key_mangling=('-', '_')) + # Merge policy dict into OSPF dict + isis = dict_merge(tmp, isis) + return isis def verify(isis): @@ -85,6 +94,7 @@ def verify(isis): raise ConfigError('Interface used for routing updates is mandatory!') for interface in isis['interface']: + verify_interface_exists(interface) if 'vrf' in isis: # If interface specific options are set, we must ensure that the # interface is bound to our requesting VRF. Due to the VyOS @@ -129,6 +139,12 @@ def verify(isis): raise ConfigError(f'"protocols isis {process} redistribute {afi} {proto} {redistr_level}" ' \ f'can not be used with \"protocols isis {process} level {proc_level}\"') + if 'route_map' in redistr_config: + name = redistr_config['route_map'] + tmp = name.replace('-', '_') + if dict_search(f'policy.route_map.{tmp}', isis) == None: + raise ConfigError(f'Route-map {name} does not exist!') + # Segment routing checks if dict_search('segment_routing.global_block', isis): high_label_value = dict_search('segment_routing.global_block.high_label_value', isis) -- cgit v1.2.3 From d89455ee7f5dc21d00bbeddd57eaee2e32f45f99 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 21 Mar 2021 18:18:52 +0100 Subject: isis: T3417: add workaround for FRR issue We need to adjust the regex pattern for the default VRF as a trailing whitespace is required due to an FRR issue: https://github.com/FRRouting/frr/issues/8300 --- src/conf_mode/protocols_isis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 68934b565..530f55ce4 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -189,9 +189,9 @@ def apply(isis): # it will be either empty (default VRF) or contain the "vrf Date: Sun, 21 Mar 2021 18:27:36 +0100 Subject: isis: T3417: last byte of IS-IS network entity title must always be 0 --- src/conf_mode/protocols_isis.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 530f55ce4..adce00ee3 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -89,6 +89,11 @@ def verify(isis): if 'net' not in isis: raise ConfigError('Network entity is mandatory!') + # last byte in IS-IS area address must be 0 + tmp = isis['net'].split('.') + if int(tmp[-1]) != 0: + raise ConfigError('Last byte of IS-IS network entity title must always be 0!') + # If interface not set if 'interface' not in isis: raise ConfigError('Interface used for routing updates is mandatory!') -- cgit v1.2.3 From 0e050cb357972eb83412da1d0edecff46fa515bd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 21 Mar 2021 19:26:22 +0100 Subject: isis: T3417: drop artificial "domain" node identifying the IS-IS process name As we and FRR do not support multiple FRR process instances, there is no need to make this configurable for a user. We rather rely on a solid default "VyOS". --- data/templates/frr/isis.frr.tmpl | 4 ++-- interface-definitions/include/isis/isis-common-config.xml.i | 9 --------- smoketest/scripts/cli/test_protocols_isis.py | 5 +---- src/conf_mode/protocols_isis.py | 5 +---- src/migration-scripts/isis/0-to-1 | 4 +--- 5 files changed, 5 insertions(+), 22 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl index da098abac..7f996b134 100644 --- a/data/templates/frr/isis.frr.tmpl +++ b/data/templates/frr/isis.frr.tmpl @@ -1,5 +1,5 @@ ! -router isis {{ domain }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} +router isis VyOS {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} net {{ net }} {% if dynamic_hostname is defined %} hostname dynamic @@ -134,7 +134,7 @@ router isis {{ domain }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none } {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} - ip router isis {{ domain }} + ip router isis VyOS {% if iface_config.bfd is defined %} isis bfd {% endif %} diff --git a/interface-definitions/include/isis/isis-common-config.xml.i b/interface-definitions/include/isis/isis-common-config.xml.i index 94a6e3592..8b753b082 100644 --- a/interface-definitions/include/isis/isis-common-config.xml.i +++ b/interface-definitions/include/isis/isis-common-config.xml.i @@ -88,15 +88,6 @@ - - - Domain name used for this instance - - txt - IS-IS routing area domain/tag - - - Set the authentication password for a routing domain diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index 79d497186..10c722eca 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -25,7 +25,7 @@ from vyos.util import process_named_running PROCESS_NAME = 'isisd' base_path = ['protocols', 'isis'] -domain = 'FOOO' +domain = 'VyOS' net = '49.0001.1921.6800.1002.00' class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): @@ -45,7 +45,6 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'action', 'permit']) self.cli_set(['policy', 'route-map', route_map, 'rule', rule, 'match', 'ip', 'address', 'prefix-list', prefix_list]) - self.cli_set(base_path + ['domain', domain]) self.cli_set(base_path + ['net', net]) self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map]) @@ -79,13 +78,11 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): vrf_base = ['vrf', 'name', vrf] vrf_iface = 'eth1' self.cli_set(vrf_base + ['table', table]) - self.cli_set(vrf_base + ['protocols', 'isis', 'domain', domain]) self.cli_set(vrf_base + ['protocols', 'isis', 'net', net]) self.cli_set(vrf_base + ['protocols', 'isis', 'interface', vrf_iface]) self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) # Also set a default VRF IS-IS config - self.cli_set(base_path + ['domain', domain]) self.cli_set(base_path + ['net', net]) self.cli_set(base_path + ['interface', 'eth0']) self.cli_commit() diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index adce00ee3..bcd9960ed 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -83,9 +83,6 @@ def verify(isis): if not isis or 'deleted' in isis: return None - if 'domain' not in isis: - raise ConfigError('Routing domain name/tag must be set!') - if 'net' not in isis: raise ConfigError('Network entity is mandatory!') @@ -196,7 +193,7 @@ def apply(isis): if 'vrf' in isis: vrf = 'vrf ' + isis['vrf'] - frr_cfg.modify_section(f'^router isis \S+ {vrf}', '') + frr_cfg.modify_section(f'^router isis VyOS {vrf}', '') for key in ['interface', 'interface_removed']: if key not in isis: continue diff --git a/src/migration-scripts/isis/0-to-1 b/src/migration-scripts/isis/0-to-1 index 5f51f986e..6773f4009 100755 --- a/src/migration-scripts/isis/0-to-1 +++ b/src/migration-scripts/isis/0-to-1 @@ -38,8 +38,7 @@ if not config.exists(base): exit(0) # Only one IS-IS process is supported, thus this operation is save -process = config.list_nodes(base) -isis_base = base + process +isis_base = base + config.list_nodes(base) # We need a temporary copy of the config tmp_base = ['protocols', 'isis2'] @@ -50,7 +49,6 @@ config.delete(base) # Rename temporary copy to new final config and set domain key config.rename(tmp_base, 'isis') -config.set(base + ['domain'], process[0]) try: with open(file_name, 'w') as f: -- cgit v1.2.3