From bd844bc11cc2bcf30f7c66fde35887a387ea4ba8 Mon Sep 17 00:00:00 2001 From: Chris Van Heuveln Date: Fri, 7 Jun 2019 00:28:29 -0400 Subject: [PATCH] nxos_interface_ospf: Add bfd support (#56807) * nxos_interface_ospf: Add bfd support Add support for `bfd` state in `nxos_interface_ospf` - Feature Pull Request `nxos_interface_ospf` * Fix pep issues * sanity loop: syntax * bfd states changed from T/F to enable/disable/default * doc hdr fixes --- .../network/nxos/nxos_interface_ospf.py | 41 +++++++++++++- .../tests/common/sanity.yaml | 37 +++++++------ .../fixtures/nxos_interface_ospf/config.cfg | 10 ++++ .../network/nxos/test_nxos_interface_ospf.py | 53 +++++++++++++++++++ 4 files changed, 122 insertions(+), 19 deletions(-) diff --git a/lib/ansible/modules/network/nxos/nxos_interface_ospf.py b/lib/ansible/modules/network/nxos/nxos_interface_ospf.py index 97663a94b28..d483fbcc703 100644 --- a/lib/ansible/modules/network/nxos/nxos_interface_ospf.py +++ b/lib/ansible/modules/network/nxos/nxos_interface_ospf.py @@ -53,6 +53,14 @@ options: Valid values are a string, formatted as an IP address (i.e. "0.0.0.0") or as an integer. required: true + bfd: + description: + - Enables bfd at interface level. This overrides the bfd variable set at the ospf router level. + - Valid values are 'enable', 'disable' or 'default'. + - "Dependency: 'feature bfd'" + version_added: "2.9" + type: str + choices: ['enable', 'disable', 'default'] cost: description: - The cost associated with this cisco_interface_ospf instance. @@ -112,12 +120,14 @@ EXAMPLES = ''' interface: ethernet1/32 ospf: 1 area: 1 + bfd: disable cost: default - nxos_interface_ospf: interface: loopback0 ospf: prod area: 0.0.0.0 + bfd: enable network: point-to-point state: present ''' @@ -127,7 +137,7 @@ commands: description: commands sent to the device returned: always type: list - sample: ["interface Ethernet1/32", "ip router ospf 1 area 0.0.0.1"] + sample: ["interface Ethernet1/32", "ip router ospf 1 area 0.0.0.1", "ip ospf bfd disable"] ''' @@ -148,6 +158,7 @@ PARAM_TO_COMMAND_KEYMAP = { 'cost': 'ip ospf cost', 'ospf': 'ip router ospf', 'area': 'ip router ospf', + 'bfd': 'ip ospf bfd', 'hello_interval': 'ip ospf hello-interval', 'dead_interval': 'ip ospf dead-interval', 'passive_interface': 'ip ospf passive-interface', @@ -198,6 +209,12 @@ def get_value(arg, config, module): value = True else: value = None + elif arg == 'bfd': + m = re.search(r'\s*ip ospf bfd(?P disable)?', config) + if m: + value = 'disable' if m.group('disable') else 'enable' + else: + value = 'default' elif arg in BOOL_PARAMS: value = bool(has_command) else: @@ -254,6 +271,8 @@ def get_default_commands(existing, proposed, existing_commands, key, module): encryption_type, existing['message_digest_password']) commands.append(command) + elif 'ip ospf bfd' in key: + commands.append('no {0}'.format(key)) elif 'passive-interface' in key: commands.append('default ip ospf passive-interface') else: @@ -310,6 +329,16 @@ def state_present(module, existing, proposed, candidate): module.fail_json(msg='loopback interface does not support passive_interface') if key == 'ip ospf network' and value == 'broadcast' and module.params.get('interface').upper().startswith('LO'): module.fail_json(msg='loopback interface does not support ospf network type broadcast') + + if key == 'ip ospf bfd': + cmd = key + if 'disable' in value: + cmd += ' disable' + elif 'default' in value and existing.get('bfd') is not None: + cmd = 'no ' + cmd + commands.append(cmd) + continue + if value is True: commands.append(key) elif value is False: @@ -339,7 +368,12 @@ def state_absent(module, existing, proposed, candidate): existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) for key, value in existing_commands.items(): - if 'ip ospf passive-interface' in key: + if 'ip ospf bfd' in key: + if 'default' not in value: + # cli is present when enabled or disabled; this removes either case + commands.append('no ip ospf bfd') + continue + if 'ip ospf passive-interface' in key and value is not None: # cli is present for both enabled or disabled; 'no' will not remove commands.append('default ip ospf passive-interface') continue @@ -388,6 +422,7 @@ def main(): interface=dict(required=True, type='str'), ospf=dict(required=True, type='str'), area=dict(required=True, type='str'), + bfd=dict(choices=['enable', 'disable', 'default'], required=False, type='str'), cost=dict(required=False, type='str'), hello_interval=dict(required=False, type='str'), dead_interval=dict(required=False, type='str'), @@ -447,6 +482,8 @@ def main(): value = False elif str(value).lower() == 'default': value = 'default' + elif key == 'bfd': + value = str(value).lower() if existing.get(key) or (not existing.get(key) and value): proposed[key] = value elif 'passive_interface' in key and existing.get(key) is None and value is False: diff --git a/test/integration/targets/nxos_interface_ospf/tests/common/sanity.yaml b/test/integration/targets/nxos_interface_ospf/tests/common/sanity.yaml index 98b0030b4bb..d4faa84fd94 100644 --- a/test/integration/targets/nxos_interface_ospf/tests/common/sanity.yaml +++ b/test/integration/targets/nxos_interface_ospf/tests/common/sanity.yaml @@ -5,19 +5,20 @@ - set_fact: testint="{{ nxos_int1 }}" -- name: "Setup - Disable feature OSPF" - nxos_feature: &disable - feature: ospf +- name: Setup - Disable features + nxos_feature: + feature: "{{ item }}" provider: "{{ connection }}" state: disabled + loop: ['ospf', 'bfd'] ignore_errors: yes -- name: "Setup - Enable feature OSPF" - nxos_feature: &enable - feature: ospf +- name: Setup - Enable features + nxos_feature: + feature: "{{ item }}" provider: "{{ connection }}" state: enabled - ignore_errors: yes + loop: ['ospf', 'bfd'] - name: "Put interface into default state" nxos_config: &intdefault @@ -51,6 +52,7 @@ interface: "{{ nxos_int1|upper }}" ospf: 1 area: 12345678 + bfd: enable cost: 55 passive_interface: true hello_interval: 15 @@ -99,6 +101,7 @@ interface: "{{ testint }}" ospf: 1 area: 12345678 + bfd: default cost: default hello_interval: 10 dead_interval: default @@ -266,6 +269,7 @@ interface: "{{ testint }}" ospf: 1 area: 12345678 + bfd: disable cost: 55 passive_interface: true hello_interval: 15 @@ -282,22 +286,21 @@ - assert: *false - - name: "Disable feature OSPF" - nxos_feature: *disable + always: + - name: Disable features + nxos_feature: + feature: "{{ item }}" + provider: "{{ connection }}" + state: disabled + loop: ['ospf', 'bfd'] ignore_errors: yes - name: "Interface cleanup" nxos_config: *intdefault - - rescue: - - name: "Disable feature OSPF" - nxos_feature: *disable - - - name: "Interface cleanup" - nxos_config: *intdefault + ignore_errors: yes - name: "Remove port-channel and loopback ints" nxos_config: *removepcandlb + ignore_errors: yes - always: - debug: msg="END connection={{ ansible_connection }} nxos_interface_ospf sanity test" diff --git a/test/units/modules/network/nxos/fixtures/nxos_interface_ospf/config.cfg b/test/units/modules/network/nxos/fixtures/nxos_interface_ospf/config.cfg index 2dd83b3a49c..2061d4d71a5 100644 --- a/test/units/modules/network/nxos/fixtures/nxos_interface_ospf/config.cfg +++ b/test/units/modules/network/nxos/fixtures/nxos_interface_ospf/config.cfg @@ -1,7 +1,17 @@ interface Ethernet1/33 +interface Ethernet1/33.101 + ip router ospf 1 area 0.0.0.1 interface Ethernet1/34 ip router ospf 1 area 0.0.0.1 ip ospf passive-interface interface Ethernet1/35 ip router ospf 1 area 0.0.0.1 no ip ospf passive-interface + +interface Ethernet1/36 + ip router ospf 1 area 0.0.0.1 + ip ospf bfd + +interface Ethernet1/37 + ip router ospf 1 area 0.0.0.1 + ip ospf bfd disable diff --git a/test/units/modules/network/nxos/test_nxos_interface_ospf.py b/test/units/modules/network/nxos/test_nxos_interface_ospf.py index d1b843b56b3..d4156b43dd9 100644 --- a/test/units/modules/network/nxos/test_nxos_interface_ospf.py +++ b/test/units/modules/network/nxos/test_nxos_interface_ospf.py @@ -51,6 +51,59 @@ class TestNxosInterfaceOspfModule(TestNxosModule): set_module_args(dict(interface='ethernet1/32', ospf=1, area=1)) self.execute_module(changed=True, commands=['interface Ethernet1/32', 'ip router ospf 1 area 0.0.0.1']) + def test_bfd_1(self): + # default -> enable + set_module_args(dict(interface='ethernet1/33', ospf=1, area=1, bfd='enable')) + self.execute_module(changed=True, commands=['interface Ethernet1/33', 'ip router ospf 1 area 0.0.0.1', 'ip ospf bfd']) + + # default -> disable + set_module_args(dict(interface='ethernet1/33', ospf=1, area=1, bfd='disable')) + self.execute_module(changed=True, commands=['interface Ethernet1/33', 'ip router ospf 1 area 0.0.0.1', 'ip ospf bfd disable']) + + def test_bfd_2(self): + # default -> default + set_module_args(dict(interface='ethernet1/33.101', ospf=1, area=1, bfd='default')) + self.execute_module(changed=False) + + # enable -> default + set_module_args(dict(interface='ethernet1/36', ospf=1, area=1, bfd='default')) + self.execute_module(changed=True, commands=['interface Ethernet1/36', 'no ip ospf bfd']) + + # disable -> default + set_module_args(dict(interface='ethernet1/37', ospf=1, area=1, bfd='default')) + self.execute_module(changed=True, commands=['interface Ethernet1/37', 'no ip ospf bfd']) + + def test_bfd_3(self): + # enable -> idempotence + set_module_args(dict(interface='ethernet1/36', ospf=1, area=1, bfd='enable')) + self.execute_module(changed=False) + + # disable -> idempotence + set_module_args(dict(interface='ethernet1/37', ospf=1, area=1, bfd='disable')) + self.execute_module(changed=False) + + def test_bfd_4(self): + # None -> absent + set_module_args(dict(interface='ethernet1/33.101', ospf=1, area=1, state='absent')) + self.execute_module(changed=True, commands=['interface Ethernet1/33.101', 'no ip router ospf 1 area 0.0.0.1']) + + # enable -> absent + set_module_args(dict(interface='ethernet1/36', ospf=1, area=1, bfd='enable', state='absent')) + self.execute_module(changed=True, commands=['interface Ethernet1/36', 'no ip router ospf 1 area 0.0.0.1', 'no ip ospf bfd']) + + # disable -> absent + set_module_args(dict(interface='ethernet1/37', ospf=1, area=1, bfd='disable', state='absent')) + self.execute_module(changed=True, commands=['interface Ethernet1/37', 'no ip router ospf 1 area 0.0.0.1', 'no ip ospf bfd']) + + def test_absent_1(self): + # area only -> absent + set_module_args(dict(interface='ethernet1/33.101', ospf=1, area=1, state='absent')) + self.execute_module(changed=True, commands=['interface Ethernet1/33.101', 'no ip router ospf 1 area 0.0.0.1']) + + # None -> absent + set_module_args(dict(interface='ethernet1/33', ospf=1, area=1, state='absent')) + self.execute_module(changed=False) + def test_loopback_interface_failed(self): set_module_args(dict(interface='loopback0', ospf=1, area=0, passive_interface=True)) self.execute_module(failed=True, changed=False)