diff --git a/lib/ansible/modules/network/nxos/nxos_pim_interface.py b/lib/ansible/modules/network/nxos/nxos_pim_interface.py index 227f2179b6..5cd08bbd94 100644 --- a/lib/ansible/modules/network/nxos/nxos_pim_interface.py +++ b/lib/ansible/modules/network/nxos/nxos_pim_interface.py @@ -40,6 +40,14 @@ options: - Enable/disable sparse-mode on the interface. type: bool default: no + bfd: + description: + - Enables BFD for PIM at the interface level. This overrides the bfd variable set at the pim global level. + - Valid values are 'enable', 'disable' or 'default'. + - "Dependency: 'feature bfd'" + version_added: "2.9" + type: str + choices: ['enable', 'disable', 'default'] dr_prio: description: - Configures priority for PIM DR election on interface. @@ -112,6 +120,11 @@ EXAMPLES = r''' jp_type_in: routemap jp_type_out: routemap +- name: disable bfd on the interface + nxos_pim_interface: + interface: eth1/33 + bfd: disable + - name: Ensure defaults are in place nxos_pim_interface: interface: eth1/33 @@ -123,8 +136,11 @@ commands: description: command sent to the device returned: always type: list - sample: ["interface eth1/33", "ip pim neighbor-policy test", - "ip pim neighbor-policy test"] + sample: ["interface eth1/33", + "ip pim neighbor-policy test", + "ip pim bfd-instance disable", + "ip pim neighbor-policy test" + ] ''' import re @@ -137,6 +153,7 @@ from ansible.module_utils.six import string_types PARAM_TO_COMMAND_KEYMAP = { 'interface': '', + 'bfd': 'ip pim bfd-instance', 'sparse': 'ip pim sparse-mode', 'dr_prio': 'ip pim dr-priority {0}', 'hello_interval': 'ip pim hello-interval {0}', @@ -151,6 +168,7 @@ PARAM_TO_COMMAND_KEYMAP = { } PARAM_TO_DEFAULT_KEYMAP = { + 'bfd': 'default', 'dr_prio': '1', 'hello_interval': '30000', 'sparse': False, @@ -158,6 +176,13 @@ PARAM_TO_DEFAULT_KEYMAP = { 'hello_auth_key': False, } +BFD_KEYMAP = { + None: None, + 'default': 'no ip pim bfd-instance', + 'disable': 'ip pim bfd-instance disable', + 'enable': 'ip pim bfd-instance', +} + def execute_show_command(command, module, text=False): if text: @@ -222,6 +247,7 @@ def get_pim_interface(module, interface): pim_interface = {} body = get_config(module, flags=['interface {0}'.format(interface)]) + pim_interface['bfd'] = 'default' pim_interface['neighbor_type'] = None pim_interface['neighbor_policy'] = None pim_interface['jp_policy_in'] = None @@ -263,6 +289,11 @@ def get_pim_interface(module, interface): pim_interface['isauth'] = True elif 'sparse-mode' in each: pim_interface['sparse'] = True + elif 'bfd-instance' in each: + value = 'default' + m = re.search(r'ip pim bfd-instance(?P disable)?', each) + if m: + pim_interface['bfd'] = 'disable' if m.group('disable') else 'enable' elif 'border' in each: pim_interface['border'] = True elif 'hello-interval' in each: @@ -299,9 +330,11 @@ def config_pim_interface(delta, existing, jp_bidir, isauth): commands.append(command) for k, v in delta.items(): - if k in ['dr_prio', 'hello_interval', 'hello_auth_key', 'border', + if k in ['bfd', 'dr_prio', 'hello_interval', 'hello_auth_key', 'border', 'sparse']: - if v: + if k == 'bfd': + command = BFD_KEYMAP[v] + elif v: command = PARAM_TO_COMMAND_KEYMAP.get(k).format(v) elif k == 'hello_auth_key': if isauth: @@ -350,12 +383,17 @@ def config_pim_interface(delta, existing, jp_bidir, isauth): commands.append(command) command = None + if 'no ip pim sparse-mode' in commands: + # sparse is long-running on some platforms, process it last + commands.remove('no ip pim sparse-mode') + commands.append('no ip pim sparse-mode') return commands def get_pim_interface_defaults(): args = dict(dr_prio=PARAM_TO_DEFAULT_KEYMAP.get('dr_prio'), + bfd=PARAM_TO_DEFAULT_KEYMAP.get('bfd'), border=PARAM_TO_DEFAULT_KEYMAP.get('border'), sparse=PARAM_TO_DEFAULT_KEYMAP.get('sparse'), hello_interval=PARAM_TO_DEFAULT_KEYMAP.get('hello_interval'), @@ -430,6 +468,15 @@ def config_pim_interface_defaults(existing, jp_bidir, isauth): return command +def normalize_proposed_values(proposed): + keys = proposed.keys() + if 'bfd' in keys: + # bfd is a tri-state string: enable, disable, default + proposed['bfd'] = proposed['bfd'].lower() + if 'hello_interval' in keys: + proposed['hello_interval'] = str(proposed['hello_interval'] * 1000) + + def main(): argument_spec = dict( interface=dict(type='str', required=True), @@ -441,6 +488,7 @@ def main(): jp_policy_in=dict(type='str'), jp_type_out=dict(type='str', choices=['prefix', 'routemap']), jp_type_in=dict(type='str', choices=['prefix', 'routemap']), + bfd=dict(type='str', choices=['enable', 'disable', 'default']), border=dict(type='bool', default=False), neighbor_policy=dict(type='str'), neighbor_type=dict(type='str', choices=['prefix', 'routemap']), @@ -484,9 +532,7 @@ def main(): args = PARAM_TO_COMMAND_KEYMAP.keys() proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) - - if hello_interval: - proposed['hello_interval'] = str(proposed['hello_interval'] * 1000) + normalize_proposed_values(proposed) delta = dict(set(proposed.items()).difference(existing.items())) diff --git a/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml b/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml index 74a771a0a0..26d0656554 100644 --- a/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml +++ b/test/integration/targets/nxos_pim_interface/tests/common/sanity.yaml @@ -3,17 +3,20 @@ - debug: msg="Using provider={{ connection.transport }}" when: ansible_connection == "local" -- name: "Disable feature PIM" +- name: "Setup: Disable features" nxos_feature: &disable_feature - feature: pim + feature: "{{ item }}" provider: "{{ connection }}" state: disabled + loop: ['pim', 'bfd'] + ignore_errors: yes -- name: "Enable feature PIM" +- name: "Setup: Enable features" nxos_feature: - feature: pim + feature: "{{ item }}" provider: "{{ connection }}" state: enabled + loop: ['pim', 'bfd'] - set_fact: testint="{{ nxos_int1 }}" @@ -115,6 +118,7 @@ hello_interval: 40 sparse: True border: True + bfd: enable provider: "{{ connection }}" state: present register: result @@ -132,6 +136,7 @@ interface: "{{ testint }}" sparse: False border: False + bfd: disable provider: "{{ connection }}" state: present register: result @@ -193,5 +198,6 @@ always: - name: "Disable feature PIM" nxos_feature: *disable_feature + loop: ['pim', 'bfd'] - debug: msg="END connection={{ ansible_connection }} nxos_pim_interface sanity test" diff --git a/test/units/modules/network/nxos/test_nxos_pim_interface_bfd.py b/test/units/modules/network/nxos/test_nxos_pim_interface_bfd.py new file mode 100644 index 0000000000..151ac3e8a4 --- /dev/null +++ b/test/units/modules/network/nxos/test_nxos_pim_interface_bfd.py @@ -0,0 +1,177 @@ +# (c) 2019 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from units.compat.mock import patch +from ansible.modules.network.nxos import nxos_pim_interface +from .nxos_module import TestNxosModule, load_fixture, set_module_args + + +class TestNxosPimInterfaceBfdModule(TestNxosModule): + + module = nxos_pim_interface + + def setUp(self): + super(TestNxosPimInterfaceBfdModule, self).setUp() + + self.mock_get_interface_mode = patch('ansible.modules.network.nxos.nxos_pim_interface.get_interface_mode') + self.get_interface_mode = self.mock_get_interface_mode.start() + + self.mock_get_config = patch('ansible.modules.network.nxos.nxos_pim_interface.get_config') + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch('ansible.modules.network.nxos.nxos_pim_interface.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_run_commands = patch('ansible.modules.network.nxos.nxos_pim_interface.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestNxosPimInterfaceBfdModule, self).tearDown() + self.mock_get_interface_mode.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None, device=''): + self.load_config.return_value = None + + def test_bfd_1(self): + # default (None) -> enable + self.get_config.return_value = None + set_module_args(dict(interface='eth2/1', bfd='enable')) + self.execute_module( + changed=True, + commands=[ + 'interface eth2/1', + 'ip pim bfd-instance', + ]) + + # default (None) -> disable + set_module_args(dict(interface='eth2/1', bfd='disable')) + self.execute_module( + changed=True, + commands=[ + 'interface eth2/1', + 'ip pim bfd-instance disable', + ]) + + # default (None) -> default (None) (idempotence) + set_module_args(dict(interface='eth2/1', bfd='default')) + self.execute_module(changed=False,) + + # default (None) -> interface state 'default' + set_module_args(dict(interface='Ethernet9/3', state='default')) + self.execute_module(changed=False,) + + # default (None) -> interface state 'absent' + set_module_args(dict(interface='Ethernet9/3', state='absent')) + self.execute_module(changed=False,) + + def test_bfd_2(self): + # From disable + self.get_config.return_value = ''' + interface Ethernet9/2 + ip pim bfd-instance disable + ''' + # disable -> enable + set_module_args(dict(interface='Ethernet9/2', bfd='enable')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/2', + 'ip pim bfd-instance', + ]) + + # disable -> disable (idempotence) + set_module_args(dict(interface='Ethernet9/2', bfd='disable')) + self.execute_module(changed=False,) + + # disable -> default (None) + set_module_args(dict(interface='Ethernet9/2', bfd='default')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/2', + 'no ip pim bfd-instance', + ]) + # disable -> interface state 'default' + set_module_args(dict(interface='Ethernet9/3', state='default')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'no ip pim bfd-instance', + ]) + + # disable -> interface state 'absent' + set_module_args(dict(interface='Ethernet9/3', state='absent')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'no ip pim bfd-instance', + ]) + + def test_bfd_3(self): + # From enable + self.get_config.return_value = ''' + interface Ethernet9/2 + ip pim bfd-instance + ''' + # enable -> disabled + set_module_args(dict(interface='Ethernet9/3', bfd='disable')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'ip pim bfd-instance disable', + ]) + + # enable -> enable (idempotence) + set_module_args(dict(interface='Ethernet9/3', bfd='enable')) + self.execute_module(changed=False,) + + # enable -> default (None) + set_module_args(dict(interface='Ethernet9/3', bfd='default')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'no ip pim bfd-instance', + ]) + + # enable -> interface state 'default' + set_module_args(dict(interface='Ethernet9/3', state='default')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'no ip pim bfd-instance', + ]) + + # enable -> interface state 'absent' + set_module_args(dict(interface='Ethernet9/3', state='absent')) + self.execute_module( + changed=True, + commands=[ + 'interface Ethernet9/3', + 'no ip pim bfd-instance', + ])