nxos_pim: Add bfd support (#56908)

* nxos_pim: Add bfd support

* Add integration sanity

* minor cleanup

* bfd T/F now bfd enable/disable
This commit is contained in:
Chris Van Heuveln 2019-06-19 11:52:03 -04:00 committed by Trishna Guha
parent 12d656901f
commit 30830a4482
4 changed files with 139 additions and 48 deletions

View file

@ -31,6 +31,14 @@ description:
- Manages configuration of a Protocol Independent Multicast (PIM) instance. - Manages configuration of a Protocol Independent Multicast (PIM) instance.
author: Gabriele Gerbino (@GGabriele) author: Gabriele Gerbino (@GGabriele)
options: options:
bfd:
description:
- Enables BFD on all PIM interfaces.
- "Dependency: 'feature bfd'"
version_added: "2.9"
type: str
choices: ['enable', 'disable']
ssm_range: ssm_range:
description: description:
- Configure group ranges for Source Specific Multicast (SSM). - Configure group ranges for Source Specific Multicast (SSM).
@ -42,8 +50,9 @@ options:
required: true required: true
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: Configure ssm_range - name: Configure ssm_range, enable bfd
nxos_pim: nxos_pim:
bfd: enable
ssm_range: "224.0.0.0/8" ssm_range: "224.0.0.0/8"
- name: Set to default - name: Set to default
@ -60,7 +69,9 @@ commands:
description: commands sent to the device description: commands sent to the device
returned: always returned: always
type: list type: list
sample: ["ip pim ssm range 224.0.0.0/8"] sample:
- ip pim bfd
- ip pim ssm range 224.0.0.0/8
''' '''
@ -73,7 +84,8 @@ from ansible.module_utils.network.common.config import CustomNetworkConfig
PARAM_TO_COMMAND_KEYMAP = { PARAM_TO_COMMAND_KEYMAP = {
'ssm_range': 'ip pim ssm range' 'bfd': 'ip pim bfd',
'ssm_range': 'ip pim ssm range',
} }
@ -82,15 +94,17 @@ def get_existing(module, args):
config = str(get_config(module)) config = str(get_config(module))
for arg in args: for arg in args:
command = PARAM_TO_COMMAND_KEYMAP[arg] if 'ssm_range' in arg:
has_command = re.search(r'^{0}\s(?P<value>.*)$'.format(command), config, re.M) # <value> may be 'n.n.n.n/s', 'none', or 'default'
m = re.search(r'ssm range (?P<value>(?:[\s\d.\/]+|none|default))?$', config, re.M)
if m:
# Remove rsvd SSM range
value = m.group('value').replace('232.0.0.0/8', '')
existing[arg] = value.split()
elif 'bfd' in arg and 'ip pim bfd' in config:
existing[arg] = 'enable'
value = ''
if has_command:
value = has_command.group('value')
if value == '232.0.0.0/8':
value = '' # remove the reserved value
existing[arg] = value.split()
return existing return existing
@ -98,7 +112,7 @@ def apply_key_map(key_map, table):
new_dict = {} new_dict = {}
for key, value in table.items(): for key, value in table.items():
new_key = key_map.get(key) new_key = key_map.get(key)
if value: if value is not None:
new_dict[new_key] = value new_dict[new_key] = value
return new_dict return new_dict
@ -108,12 +122,20 @@ def get_commands(module, existing, proposed, candidate):
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
for key, value in proposed_commands.items(): for key, value in proposed_commands.items():
if key == 'ip pim ssm range' and value == 'default': command = ''
# no cmd needs a value but the actual value does not matter if key == 'ip pim ssm range':
command = 'no ip pim ssm range none' if value == 'default':
commands.append(command) # no cmd needs a value but the actual value does not matter
else: command = 'no ip pim ssm range none'
command = '{0} {1}'.format(key, value) elif value == 'none':
command = 'ip pim ssm range none'
elif value:
command = 'ip pim ssm range {0}'.format(value)
elif key == 'ip pim bfd':
no_cmd = 'no ' if value == 'disable' else ''
command = no_cmd + key
if command:
commands.append(command) commands.append(command)
if commands: if commands:
@ -122,42 +144,52 @@ def get_commands(module, existing, proposed, candidate):
def main(): def main():
argument_spec = dict( argument_spec = dict(
ssm_range=dict(required=True, type='list'), bfd=dict(required=False, type='str', choices=['enable', 'disable']),
ssm_range=dict(required=False, type='list', default=[]),
) )
argument_spec.update(nxos_argument_spec) argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
warnings = list() warnings = list()
check_args(module, warnings) check_args(module, warnings)
result = {'changed': False, 'commands': [], 'warnings': warnings} result = {'changed': False, 'commands': [], 'warnings': warnings}
ssm_range_list = module.params['ssm_range'] params = module.params
for item in ssm_range_list: args = [k for k in PARAM_TO_COMMAND_KEYMAP.keys() if params[k] is not None]
splitted_ssm_range = item.split('.')
if len(splitted_ssm_range) != 4 and item != 'none' and item != 'default':
module.fail_json(msg="Valid ssm_range values are multicast addresses "
"or the keyword 'none' or the keyword 'default'.")
args = PARAM_TO_COMMAND_KEYMAP.keys() # SSM syntax check
if 'ssm_range' in args:
for item in params['ssm_range']:
if re.search('none|default', item):
break
if len(item.split('.')) != 4:
module.fail_json(msg="Valid ssm_range values are multicast addresses "
"or the keyword 'none' or the keyword 'default'.")
existing = get_existing(module, args) existing = get_existing(module, args)
proposed_args = dict((k, v) for k, v in module.params.items() if k in args) proposed_args = dict((k, v) for k, v in params.items() if k in args)
proposed = {} proposed = {}
for key, value in proposed_args.items(): for key, value in proposed_args.items():
if key == 'ssm_range': if key == 'ssm_range':
if value[0] == 'default': if value and value[0] == 'default':
if existing.get(key): if existing.get(key):
proposed[key] = 'default' proposed[key] = 'default'
else: else:
v = sorted(set([str(i) for i in value])) v = sorted(set([str(i) for i in value]))
ex = sorted(set([str(i) for i in existing.get(key)])) ex = sorted(set([str(i) for i in existing.get(key, [])]))
if v != ex: if v != ex:
proposed[key] = ' '.join(str(s) for s in v) proposed[key] = ' '.join(str(s) for s in v)
elif key == 'bfd':
if value != existing.get('bfd', 'disable'):
proposed[key] = value
elif value != existing.get(key):
proposed[key] = value
candidate = CustomNetworkConfig(indent=3) candidate = CustomNetworkConfig(indent=3)
get_commands(module, existing, proposed, candidate) get_commands(module, existing, proposed, candidate)

View file

@ -3,17 +3,24 @@
- debug: msg="Using provider={{ connection.transport }}" - debug: msg="Using provider={{ connection.transport }}"
when: ansible_connection == "local" when: ansible_connection == "local"
- name: "Setup: Disable feature PIM" - name: "Setup: Disable features"
nxos_feature: &disable_feature nxos_feature: &disable_feature
feature: pim feature: "{{ item }}"
provider: "{{ connection }}" provider: "{{ connection }}"
state: disabled state: disabled
ignore_errors: yes
loop:
- pim
- bfd
- name: "Setup: Enable feature PIM" - name: "Setup: Enable features"
nxos_feature: nxos_feature:
feature: pim feature: "{{ item }}"
provider: "{{ connection }}" provider: "{{ connection }}"
state: enabled state: enabled
loop:
- pim
- bfd
- name: "Setup: Configure ssm_range none" - name: "Setup: Configure ssm_range none"
nxos_pim: &none nxos_pim: &none
@ -21,9 +28,10 @@
provider: "{{ connection }}" provider: "{{ connection }}"
- block: - block:
- name: Configure ssm_range - name: Initial config from none
nxos_pim: &configure nxos_pim: &configure
ssm_range: bfd: enable
ssm_range:
- "239.128.1.0/24" - "239.128.1.0/24"
- "224.0.0.0/8" - "224.0.0.0/8"
provider: "{{ connection }}" provider: "{{ connection }}"
@ -33,7 +41,7 @@
that: that:
- "result.changed == true" - "result.changed == true"
- name: Check idempotence - name: Initial config idempotence
nxos_pim: *configure nxos_pim: *configure
register: result register: result
@ -43,13 +51,14 @@
- name: Configure ssm_range default - name: Configure ssm_range default
nxos_pim: &conf_default nxos_pim: &conf_default
bfd: disable
ssm_range: "default" ssm_range: "default"
provider: "{{ connection }}" provider: "{{ connection }}"
register: result register: result
- assert: *true - assert: *true
- name: Check idempotence - name: ssm_range default idempotence
nxos_pim: *conf_default nxos_pim: *conf_default
register: result register: result
@ -61,14 +70,22 @@
- assert: *true - assert: *true
- name: Check idempotence - meta: end_play
- name: ssm_range none idempotence
nxos_pim: *none nxos_pim: *none
register: result register: result
- assert: *false - assert: *false
always: always:
- name: "Disable feature PIM" - name: "Teardown: Disable features"
nxos_feature: *disable_feature nxos_feature:
feature: "{{ item }}"
provider: "{{ connection }}"
state: disabled
ignore_errors: yes
loop:
- pim
- bfd
- debug: msg="END connection={{ ansible_connection }} nxos_pim sanity test" - debug: msg="END connection={{ ansible_connection }} nxos_pim sanity test"

View file

@ -1 +1,2 @@
ip pim bfd
ip pim ssm range 127.0.0.0/31 ip pim ssm range 127.0.0.0/31

View file

@ -43,17 +43,58 @@ class TestNxosPimModule(TestNxosModule):
self.mock_load_config.stop() self.mock_load_config.stop()
def load_fixtures(self, commands=None, device=''): def load_fixtures(self, commands=None, device=''):
self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
self.load_config.return_value = None self.load_config.return_value = None
def test_nxos_pim(self): def test_nxos_pim_1(self):
set_module_args(dict(ssm_range='232.0.0.0/8')) # Add/ Modify
self.execute_module(changed=True, commands=['ip pim ssm range 232.0.0.0/8']) self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
set_module_args(dict(ssm_range='233.0.0.0/8'))
self.execute_module(changed=True, commands=[
'ip pim ssm range 233.0.0.0/8',
])
def test_nxos_pim_none(self): def test_nxos_pim_2(self):
# Remove existing values
self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
set_module_args(dict(bfd='disable', ssm_range='none'))
self.execute_module(changed=True, commands=[
'no ip pim bfd',
'ip pim ssm range none',
])
def test_nxos_pim_3(self):
# bfd None (disable)-> enable
self.get_config.return_value = None
set_module_args(dict(bfd='enable'))
self.execute_module(changed=True, commands=['ip pim bfd'])
# bfd None (disable) -> disable
set_module_args(dict(bfd='disable'))
self.execute_module(changed=False)
# ssm None to 'default'
set_module_args(dict(ssm_range='default'))
self.execute_module(changed=False)
def test_nxos_pim_4(self):
# SSM 'none'
self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
set_module_args(dict(ssm_range='none')) set_module_args(dict(ssm_range='none'))
self.execute_module(changed=True, commands=['ip pim ssm range none']) self.execute_module(changed=True, commands=['ip pim ssm range none'])
def test_nxos_pim_no_change(self): def test_nxos_pim_5(self):
set_module_args(dict(ssm_range='127.0.0.0/31')) # SSM 'default'
self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
set_module_args(dict(ssm_range='default'))
self.execute_module(changed=True, commands=['no ip pim ssm range none'])
# SSM 'default' idempotence
self.get_config.return_value = None
set_module_args(dict(ssm_range='default'))
self.execute_module(changed=False)
def test_nxos_pim_6(self):
# Idempotence
self.get_config.return_value = load_fixture('nxos_pim', 'config.cfg')
set_module_args(dict(bfd='enable', ssm_range='127.0.0.0/31'))
self.execute_module(changed=False, commands=[]) self.execute_module(changed=False, commands=[])