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. --- data/templates/frr/isis.frr.tmpl | 4 +- interface-definitions/protocols-isis.xml.in | 17 +-- src/conf_mode/protocols_isis.py | 162 ++++++++++++---------------- src/migration-scripts/isis/0-to-1 | 60 +++++++++++ 4 files changed, 143 insertions(+), 100 deletions(-) create mode 100755 src/migration-scripts/isis/0-to-1 diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl index 4460ab3b5..d4500403c 100644 --- a/data/templates/frr/isis.frr.tmpl +++ b/data/templates/frr/isis.frr.tmpl @@ -1,5 +1,5 @@ ! -router isis {{ process }} +router isis {{ domain }} net {{ net }} {% if dynamic_hostname is defined %} hostname dynamic @@ -134,7 +134,7 @@ router isis {{ process }} {% if interface is defined and interface is not none %} {% for iface, iface_config in interface.items() %} interface {{ iface }} - ip router isis {{ process }} + ip router isis {{ domain }} {% if iface_config.bfd is defined %} isis bfd {% endif %} diff --git a/interface-definitions/protocols-isis.xml.in b/interface-definitions/protocols-isis.xml.in index 0afa8dcb1..e6982aca3 100644 --- a/interface-definitions/protocols-isis.xml.in +++ b/interface-definitions/protocols-isis.xml.in @@ -2,14 +2,10 @@ - + Intermediate System to Intermediate System (IS-IS) 610 - - text(TAG) - ISO Routing area tag - @@ -101,6 +97,15 @@ + + + Domain name used for this instance + + txt + IS-IS routing area domain/tag + + + Set the authentication password for a routing domain @@ -766,7 +771,7 @@ - + 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__': diff --git a/src/migration-scripts/isis/0-to-1 b/src/migration-scripts/isis/0-to-1 new file mode 100755 index 000000000..5f51f986e --- /dev/null +++ b/src/migration-scripts/isis/0-to-1 @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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 +# 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 . + +# T3417: migrate IS-IS tagNode to node as we can only have one IS-IS process + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +base = ['protocols', 'isis'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +# Only one IS-IS process is supported, thus this operation is save +process = config.list_nodes(base) +isis_base = base + process + +# We need a temporary copy of the config +tmp_base = ['protocols', 'isis2'] +config.copy(isis_base, tmp_base) + +# Now it's save to delete the old configuration +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: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3