diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | data/templates/frr/isis.frr.tmpl | 158 | ||||
-rw-r--r-- | interface-definitions/include/isis-redistribute-ipv4.xml.i | 36 | ||||
-rwxr-xr-x | src/conf_mode/protocols_isis.py | 137 |
4 files changed, 299 insertions, 33 deletions
@@ -77,7 +77,6 @@ interface_definitions: $(BUILD_DIR) $(obj) rm -f $(TMPL_DIR)/interfaces/wirelessmodem/node.tag/ipv6/node.def rm -f $(TMPL_DIR)/protocols/node.def rm -rf $(TMPL_DIR)/protocols/nbgp - rm -rf $(TMPL_DIR)/protocols/isis rm -f $(TMPL_DIR)/protocols/static/node.def rm -f $(TMPL_DIR)/system/node.def rm -f $(TMPL_DIR)/vpn/node.def diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl new file mode 100644 index 000000000..70444d0c3 --- /dev/null +++ b/data/templates/frr/isis.frr.tmpl @@ -0,0 +1,158 @@ +{% for dname in isis -%} +! +router isis {{dname}} +{% if isis[dname]['net'] is string -%} + net {{ isis[dname]['net'] }} +{% else -%} +{% for net_iso in isis[dname]['net'] -%} + net {{ net_iso }} +{% endfor -%} +{% endif -%} +{#- end isis net -#} +{% if 'dynamic_hostname' in isis[dname] -%} + hostname dynamic +{% endif -%} +{% if 'purge_originator' in isis[dname] -%} + purge-originator +{% endif -%} +{% if 'set_attached_bit' in isis[dname] -%} + set-attached-bit +{% endif -%} +{% if 'set_overload_bit' in isis[dname] -%} + set-overload-bit +{% endif -%} +{% if isis[dname].domain_password is defined -%} +{% if isis[dname].domain_password.plaintext_password is defined -%} + domain-password clear {{ isis[dname].domain_password.plaintext_password }} +{% endif -%} +{% endif -%} +{#- end isis domain-password -#} +{% if isis[dname]['lsp_gen_interval'] -%} + lsp-gen-interval {{ isis[dname]['lsp_gen_interval'] }} +{% endif -%} +{% if isis[dname]['lsp_mtu'] -%} + lsp-mtu {{ isis[dname]['lsp_mtu'] }} +{% endif -%} +{% if isis[dname]['lsp_refresh_interval'] -%} + lsp-mtu {{ isis[dname]['lsp_refresh_interval'] }} +{% endif -%} +{% if isis[dname]['max_lsp_lifetime'] -%} + lsp-mtu {{ isis[dname]['max_lsp_lifetime'] }} +{% endif -%} +{% if isis[dname]['spf_interval'] -%} + spf-interval {{ isis[dname]['spf_interval'] }} +{% endif -%} +{% for t in isis[dname]['spf_delay_ietf'] -%} +{% if t == "holddown" -%} + spf-delay-ietf holddown {{ isis[dname]['spf_delay_ietf'][t] }} +{% endif -%} +{% endfor -%} +{#- end lsp timers and intervals -#} +{% if isis[dname]['area_password'] is defined -%} +{% for password in isis[dname]['area_password'] -%} +{% if password == "md5" -%} + area-password md5 {{ isis[dname]['area_password'][password] }} +{% endif -%} +{% if password == "plaintext_password" -%} + area-password clear {{ isis[dname]['area_password'][password] }} +{% endif -%} +{% endfor -%} +{% endif -%} +{#- end isis area-password -#} +{% if 'originate' in isis[dname]['default_information'] -%} +{% for ipv in isis[dname]['default_information']['originate'] -%} +{% for lvl in isis[dname]['default_information']['originate'][ipv] -%} +{% if ipv == 'ipv4' -%} + default-information originate {{ ipv }} {{ lvl | replace('_', '-') }} +{% elif ipv == 'ipv6' -%} + default-information originate {{ ipv }} {{ lvl | replace('_', '-') }} always +{% endif -%} +{% endfor -%} +{% endfor -%} +{% endif -%} +{#- end isis default-information originate -#} +{% if isis[dname]['redistribute'] is defined -%} +{% for protocol in isis[dname]['redistribute']['ipv4'] -%} +{% for lvl in isis[dname]['redistribute']['ipv4'][protocol] -%} +{% if isis[dname]['redistribute']['ipv4'][protocol][lvl]['metric'] is defined -%} + redistribute ipv4 {{ protocol }} {{ lvl | replace('_', '-') }} metric {{ isis[dname]['redistribute']['ipv4'][protocol][lvl]['metric'] }} +{% elif isis[dname]['redistribute']['ipv4'][protocol][lvl]['route_map'] is defined -%} + redistribute ipv4 {{ protocol }} {{ lvl | replace('_', '-') }} route-map {{ isis[dname]['redistribute']['ipv4'][protocol][lvl]['route_map'] }} +{% else -%} + redistribute ipv4 {{ protocol }} {{ lvl | replace('_', '-') }} +{% endif -%} +{% endfor -%} +{% endfor -%} +{% endif -%} +{#- end isis redistribute -#} +{% if isis[dname]['level'] -%} +{% if isis[dname]['level'] == 'level-1' -%} + is-type level-1 +{% elif isis[dname]['level'] == 'level-2' -%} + is-type level-2-only +{% elif isis[dname]['level'] == 'level-1-2' -%} + is-type level-1-2 +{% endif -%} +{% endif -%} +{#- end isis level -#} +{% if isis[dname]['spf_delay_ietf'] is defined -%} +{% if isis[dname]['spf_delay_ietf']['init_delay'] or isis[dname]['spf_delay_ietf']['short_delay'] or isis[dname]['spf_delay_ietf']['long_delay'] or isis[dname]['spf_delay_ietf']['holddown'] or isis[dname]['spf_delay_ietf']['time_to_learn'] -%} + init-delay {{ isis[dname]['spf_delay_ietf']['init_delay'] }} short-delay {{ isis[dname]['spf_delay_ietf']['short_delay'] }} long-delay {{ isis[dname]['spf_delay_ietf']['long_delay'] }} holddown {{ isis[dname]['spf_delay_ietf']['holddown'] }} time-to-learn {{ isis[dname]['spf_delay_ietf']['time_to_learn'] }} +{% endif -%} +{% endif -%} +! +{% set isis_iface = isis[dname]['interface'] -%} +{% if isis_iface is string -%} +interface {{ isis_iface }} +ip router isis {{ dname }} +{% else -%} +{% for value in isis_iface -%} +interface {{ value }} +ip router isis {{ dname }} +{% if 'bfd' in isis_iface[value] -%} + isis bfd +{% endif -%} +{% if 'point_to_point' in isis_iface[value]['network'] -%} + isis network point-to-point +{% endif -%} +{% if isis_iface[value]['circuit_type'] == 'level-1' -%} + isis circuit-type level-1 +{% elif isis_iface[value]['circuit_type'] == 'level-2-only' -%} + isis circuit-type level-2-only +{% elif isis_iface[value]['circuit_type'] == 'level-1-2' -%} + isis circuit-type level-1-2 +{% endif -%} +{% if isis_iface[value]['hello_interval'] -%} + isis hello-interval {{ isis_iface[value]['hello_interval'] }} +{% endif -%} +{% if isis_iface[value]['hello_multiplier'] -%} + isis hello-multiplier {{ isis_iface[value]['hello_multiplier'] }} +{% endif -%} +{% if 'hello_padding' in isis_iface[value] -%} + isis hello padding +{% endif -%} +{% if isis_iface[value]['metric'] -%} + isis metric {{ isis_iface[value]['metric'] }} +{% endif -%} +{% if 'passive' in isis_iface[value] -%} + isis passive +{% endif -%} +{% if isis_iface[value]['password'] is defined -%} +{% if isis_iface[value]['password']['plaintext_password'] is defined -%} + isis password clear {{ isis_iface[value]['password']['plaintext_password'] }} +{% endif -%} +{% endif -%} +{% if isis_iface[value]['priority'] -%} + isis priority {{ isis_iface[value]['priority'] }} +{% endif -%} +{% if isis_iface[value]['psnp_interval'] -%} + isis psnp-interval {{ isis_iface[value]['psnp_interval'] }} +{% endif -%} +{% if 'three_way_handshake' in isis_iface[value] -%} + isis three-way-handshake +{% endif -%} +{% endfor -%} +{% endif -%} +{#- end isis interface -#} +! +{% endfor -%} diff --git a/interface-definitions/include/isis-redistribute-ipv4.xml.i b/interface-definitions/include/isis-redistribute-ipv4.xml.i index a40d8dfc2..fd5e75918 100644 --- a/interface-definitions/include/isis-redistribute-ipv4.xml.i +++ b/interface-definitions/include/isis-redistribute-ipv4.xml.i @@ -16,28 +16,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <node name="level-2"> @@ -57,28 +43,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <!-- included end --> diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py new file mode 100755 index 000000000..4ee6b31f6 --- /dev/null +++ b/src/conf_mode/protocols_isis.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-2020 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 <http://www.gnu.org/licenses/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos import ConfigError +from vyos.util import call +from vyos.template import render +from vyos.template import render_to_string +from vyos import frr +from vyos import airbag +from pprint import pprint +airbag.enable() + +config_file = r'/tmp/isis.frr' + +default_config_data = { + 'interface': '' +} + +def get_config(): + conf = Config() + base = ['protocols', 'isis'] + isis = conf.get_config_dict(base, key_mangling=('-', '_')) + if not conf.exists(base): + isis = {} + + pprint(isis) + return isis + +def verify(isis): + # bail out early - looks like removal from running config + if not isis: + return None + + for proc_id, foo_config in isis['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['isis']) > 1: + raise ConfigError('Only one isis process can be definded') + + # If network entity title (net) not defined + if not "net" in foo_config: + raise ConfigError('Define net format iso is mandatory in \"isis {} net"!'.format(proc_id)) + + # If interface not set + if not "interface" in foo_config: + raise ConfigError('Define interface is mandatory in \"isis {} interface"!'.format(proc_id)) + + # If md5 and plaintext-password set at the same time + if 'area_password' in foo_config: + if "md5" in foo_config['area_password'] and "plaintext_password" in foo_config['area_password']: + raise ConfigError('Only one password type should be used in \"isis {} area-password"!'.format(proc_id)) + + # If one param from deley set, but not set others + if 'spf_delay_ietf' in foo_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 foo_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 foo_config: + proc_level = foo_config.get('level','').replace('-','_') + for proto, proto_config in foo_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(proc_id, 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(proc_id, proc_level, proto, redistribute_level)) + + return None + +def generate(isis): + if not isis: + isis['new_frr_config'] = '' + return None + + # render(config) not needed, its only for debug + render(config_file, 'frr/isis.frr.tmpl', isis) + + isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', isis) + + return None + +def apply(isis): + + # Save original configration prior to starting any commit actions + isis['original_config'] = frr.get_configuration(daemon='isisd') + isis['modified_config'] = frr.replace_section(isis['original_config'], isis['new_frr_config'], from_re='router isis .*') + + # Debugging + print('') + print('--------- DEBUGGING ----------') + print(f'Existing config:\n{isis["original_config"]}\n\n') + print(f'Replacement config:\n{isis["new_frr_config"]}\n\n') + print(f'Modified config:\n{isis["modified_config"]}\n\n') + + # Frr Mark configuration will test for syntax errors and exception out if any syntax errors are detected + frr.mark_configuration(isis['modified_config']) + + # Commit the resulting new configuration to frr, this will render an frr.CommitError() Exception on fail + frr.reload_configuration(isis['modified_config'], daemon='isisd') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |