nxos_bgp_neighbor_af does not want required_together (#26370)

* nxos_bgp_neighbor_af does not want required_together

* fixup tests

* Fix max_prefix_* issues

* Require address-family

* Fix idempotency for next_hop_third_party

* Fix idempotency for allowas_in*

* Fix idempotency for *_in and *_out

* Reorder command generation again

`default` is first, then `max-prefix`, then booleans
This commit is contained in:
Nathaniel Case 2017-08-09 15:54:34 -04:00 committed by GitHub
parent 4fb114174b
commit a78f3faa6c
3 changed files with 72 additions and 70 deletions

View file

@ -158,7 +158,7 @@ options:
max_prefix_interval: max_prefix_interval:
description: description:
- Optional restart interval. Valid values are an integer. - Optional restart interval. Valid values are an integer.
Requires max_prefix_limit. Requires max_prefix_limit. May not be combined with max_prefix_warning.
required: false required: false
default: null default: null
max_prefix_threshold: max_prefix_threshold:
@ -170,7 +170,8 @@ options:
default: null default: null
max_prefix_warning: max_prefix_warning:
description: description:
- Optional warning-only keyword. Requires max_prefix_limit. - Optional warning-only keyword. Requires max_prefix_limit. May not be
combined with max_prefix_interval.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
@ -312,18 +313,18 @@ PARAM_TO_COMMAND_KEYMAP = {
'as_override': 'as-override', 'as_override': 'as-override',
'default_originate': 'default-originate', 'default_originate': 'default-originate',
'default_originate_route_map': 'default-originate route-map', 'default_originate_route_map': 'default-originate route-map',
'filter_list_in': 'filter-list', 'filter_list_in': 'filter-list in',
'filter_list_out': 'filter-list', 'filter_list_out': 'filter-list out',
'max_prefix_limit': 'maximum-prefix', 'max_prefix_limit': 'maximum-prefix',
'max_prefix_interval': 'maximum-prefix options', 'max_prefix_interval': 'maximum-prefix interval',
'max_prefix_threshold': 'maximum-prefix options', 'max_prefix_threshold': 'maximum-prefix threshold',
'max_prefix_warning': 'maximum-prefix options', 'max_prefix_warning': 'maximum-prefix warning',
'next_hop_self': 'next-hop-self', 'next_hop_self': 'next-hop-self',
'next_hop_third_party': 'next-hop-third-party', 'next_hop_third_party': 'next-hop-third-party',
'prefix_list_in': 'prefix-list', 'prefix_list_in': 'prefix-list in',
'prefix_list_out': 'prefix-list', 'prefix_list_out': 'prefix-list out',
'route_map_in': 'route-map', 'route_map_in': 'route-map in',
'route_map_out': 'route-map', 'route_map_out': 'route-map out',
'route_reflector_client': 'route-reflector-client', 'route_reflector_client': 'route-reflector-client',
'safi': 'address-family', 'safi': 'address-family',
'send_community': 'send-community', 'send_community': 'send-community',
@ -338,7 +339,6 @@ PARAM_TO_COMMAND_KEYMAP = {
def get_value(arg, config, module): def get_value(arg, config, module):
custom = [ custom = [
'allowas_in_max',
'additional_paths_send', 'additional_paths_send',
'additional_paths_receive', 'additional_paths_receive',
'advertise_map_exist', 'advertise_map_exist',
@ -347,28 +347,31 @@ def get_value(arg, config, module):
'max_prefix_interval', 'max_prefix_interval',
'max_prefix_threshold', 'max_prefix_threshold',
'max_prefix_warning', 'max_prefix_warning',
'next_hop_third_party',
'soft_reconfiguration_in' 'soft_reconfiguration_in'
] ]
command = PARAM_TO_COMMAND_KEYMAP[arg] command = PARAM_TO_COMMAND_KEYMAP[arg]
has_command = re.search(r'\s+{0}\s*'.format(command), config, re.M) has_command = re.search(r'^\s+{0}\s*'.format(command), config, re.M)
has_command_val = re.search(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M) has_command_val = re.search(r'(?:{0}\s)(?P<value>.*)$'.format(command), config, re.M)
value = '' value = ''
if arg in custom: if arg in custom:
value = get_custom_value(arg, config, module) value = get_custom_value(arg, config, module)
elif arg == 'next_hop_third_party':
has_no_command = re.search(r'^\s+no\s+{0}\s*$'.format(command), config, re.M)
value = False
if not has_no_command:
value = True
elif arg in BOOL_PARAMS: elif arg in BOOL_PARAMS:
value = False value = False
if has_command: if has_command:
value = True value = True
elif command.split()[0] in ['filter-list', 'prefix-list', 'route-map']: elif command.split()[0] in ['filter-list', 'prefix-list', 'route-map']:
direction = arg.rsplit('_', 1)[1] has_cmd_direction_val = re.search(r'{0}\s(?P<value>.*)\s{1}$'.format(*command.split()), config, re.M)
if has_command_val: if has_cmd_direction_val:
params = has_command_val.group('value').split() value = has_cmd_direction_val.group('value')
if params[-1] == direction:
value = params[0]
elif arg == 'send_community': elif arg == 'send_community':
if has_command: if has_command:
@ -416,11 +419,6 @@ def get_custom_value(arg, config, module):
): ):
splitted_line = line.split() splitted_line = line.split()
value = [splitted_line[1], splitted_line[3]] value = [splitted_line[1], splitted_line[3]]
elif arg == 'allowas_in_max':
if has_command_val:
split_line = has_command_val.group('value').split()
if len(split_line) == 2:
value = splitted_line[-1]
elif arg.startswith('max_prefix'): elif arg.startswith('max_prefix'):
for line in splitted_config: for line in splitted_config:
if 'maximum-prefix' in line: if 'maximum-prefix' in line:
@ -445,11 +443,6 @@ def get_custom_value(arg, config, module):
value = 'always' value = 'always'
else: else:
value = 'enable' value = 'enable'
elif arg == 'next_hop_third_party':
no_command_re = re.compile(r'\s+no\s+{0}\s*'.format(command), re.M)
value = False
if not no_command_re.search(config) and command_re.search(config):
value = True
return value return value
@ -528,8 +521,7 @@ def get_default_command(key, value, existing_commands):
elif key == 'route-map out': elif key == 'route-map out':
command = 'no route-map {0} out'.format(existing_value) command = 'no route-map {0} out'.format(existing_value)
elif key.startswith('maximum-prefix'): elif key.startswith('maximum-prefix'):
command = 'no maximum-prefix {0}'.format( command = 'no maximum-prefix'
existing_commands.get('maximum-prefix'))
elif key == 'allowas-in max': elif key == 'allowas-in max':
command = ['no allowas-in {0}'.format(existing_value)] command = ['no allowas-in {0}'.format(existing_value)]
command.append('allowas-in') command.append('allowas-in')
@ -545,17 +537,11 @@ def fix_proposed(module, proposed):
allowas_in = proposed.get('allowas_in') allowas_in = proposed.get('allowas_in')
allowas_in_max = proposed.get('allowas_in_max') allowas_in_max = proposed.get('allowas_in_max')
if allowas_in is False and allowas_in_max: if allowas_in_max and not allowas_in:
proposed.pop('allowas_in_max') proposed.pop('allowas_in_max')
elif allowas_in and allowas_in_max: elif allowas_in and allowas_in_max:
proposed.pop('allowas_in') proposed.pop('allowas_in')
for key, value in proposed.items():
if key in ['filter_list_in', 'prefix_list_in', 'route_map_in']:
proposed[key] = [value, 'in']
elif key in ['filter_list_out', 'prefix_list_out', 'route_map_out']:
proposed[key] = [value, 'out']
return proposed return proposed
@ -566,7 +552,18 @@ def state_present(module, existing, proposed, candidate):
proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed) proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing) existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
for key, value in proposed_commands.items(): for key, value in proposed_commands.items():
if key.startswith('maximum-prefix'): if value in ['inherit', 'default']:
command = get_default_command(key, value, existing_commands)
if isinstance(command, str):
if command and command not in commands:
commands.append(command)
elif isinstance(command, list):
for cmd in command:
if cmd not in commands:
commands.append(cmd)
elif key.startswith('maximum-prefix'):
command = 'maximum-prefix {0}'.format(module.params['max_prefix_limit']) command = 'maximum-prefix {0}'.format(module.params['max_prefix_limit'])
if module.params['max_prefix_threshold']: if module.params['max_prefix_threshold']:
command += ' {0}'.format(module.params['max_prefix_threshold']) command += ' {0}'.format(module.params['max_prefix_threshold'])
@ -580,17 +577,6 @@ def state_present(module, existing, proposed, candidate):
commands.append(key) commands.append(key)
elif value is False: elif value is False:
commands.append('no {0}'.format(key)) commands.append('no {0}'.format(key))
elif value == 'default' or value == 'inherit':
command = get_default_command(key, value, existing_commands)
if isinstance(command, str):
if command and command not in commands:
commands.append(command)
elif isinstance(command, list):
for cmd in command:
if cmd not in commands:
commands.append(cmd)
elif key == 'address-family': elif key == 'address-family':
commands.append("address-family {0} {1}".format(module.params['afi'], module.params['safi'])) commands.append("address-family {0} {1}".format(module.params['afi'], module.params['safi']))
elif key.startswith('capability additional-paths'): elif key.startswith('capability additional-paths'):
@ -601,8 +587,8 @@ def state_present(module, existing, proposed, candidate):
elif key.startswith('advertise-map'): elif key.startswith('advertise-map'):
direction = key.split()[1] direction = key.split()[1]
commands.append('advertise-map {1} {0} {2}'.format(direction, *value)) commands.append('advertise-map {1} {0} {2}'.format(direction, *value))
elif key in ['filter-list', 'prefix-list', 'route-map']: elif key.split()[0] in ['filter-list', 'prefix-list', 'route-map']:
commands.append('{0} {1} {2}'.format(key, *value)) commands.append('{1} {0} {2}'.format(value, *key.split()))
elif key == 'soft-reconfiguration inbound': elif key == 'soft-reconfiguration inbound':
command = '' command = ''
@ -628,16 +614,12 @@ def state_present(module, existing, proposed, candidate):
parents.append('neighbor {0}'.format(module.params['neighbor'])) parents.append('neighbor {0}'.format(module.params['neighbor']))
if len(commands) == 1: af_command = 'address-family {0} {1}'.format(
candidate.add(commands, parents=parents) module.params['afi'], module.params['safi'])
elif len(commands) > 1: parents.append(af_command)
af_command = 'address-family {0} {1}'.format( if af_command in commands:
module.params['afi'], module.params['safi']) commands.remove(af_command)
if af_command in commands: candidate.add(commands, parents=parents)
commands.remove(af_command)
parents.append('address-family {0} {1}'.format(
module.params['afi'], module.params['safi']))
candidate.add(commands, parents=parents)
def state_absent(module, existing, candidate): def state_absent(module, existing, candidate):
@ -693,10 +675,8 @@ def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=argument_spec, argument_spec=argument_spec,
mutually_exclusive=[['advertise_map_exist', 'advertise_map_non_exist']], mutually_exclusive=[['advertise_map_exist', 'advertise_map_non_exist'],
required_together=[['max_prefix_limit', 'max_prefix_interval'], ['max_prefix_interval', 'max_prefix_warning']],
['max_prefix_limit', 'max_prefix_warning'],
['max_prefix_limit', 'max_prefix_threshold']],
supports_check_mode=True, supports_check_mode=True,
) )
@ -705,7 +685,11 @@ def main():
result = dict(changed=False, warnings=warnings) result = dict(changed=False, warnings=warnings)
state = module.params['state'] state = module.params['state']
for key in ['max_prefix_interval', 'max_prefix_warning', 'max_prefix_threshold']:
if module.params[key] and not module.params['max_prefix_limit']:
module.fail_json(
msg='max_prefix_limit is required when using %s' % key
)
if module.params['vrf'] == 'default' and module.params['soo']: if module.params['vrf'] == 'default' and module.params['soo']:
module.fail_json(msg='SOO is only allowed in non-default VRF') module.fail_json(msg='SOO is only allowed in non-default VRF')

View file

@ -9,3 +9,4 @@ router bgp 65535
timers bgp 1 10 timers bgp 1 10
neighbor 3.3.3.5 neighbor 3.3.3.5
address-family ipv4 unicast address-family ipv4 unicast
maximum-prefix 30 30

View file

@ -42,7 +42,7 @@ class TestNxosBgpNeighborAfModule(TestNxosModule):
self.mock_get_config.stop() self.mock_get_config.stop()
def load_fixtures(self, commands=None, device=''): def load_fixtures(self, commands=None, device=''):
self.get_config.return_value = load_fixture('', 'nxos_bgp_config.cfg') self.get_config.return_value = load_fixture('nxos_bgp', 'config.cfg')
self.load_config.return_value = [] self.load_config.return_value = []
def test_nxos_bgp_neighbor_af(self): def test_nxos_bgp_neighbor_af(self):
@ -70,7 +70,7 @@ class TestNxosBgpNeighborAfModule(TestNxosModule):
advertise_map_exist=['my_advertise_map', 'my_exist_map'])) advertise_map_exist=['my_advertise_map', 'my_exist_map']))
self.execute_module( self.execute_module(
changed=True, sort=False, changed=True, sort=False,
commands=['router bgp 65535', 'neighbor 3.3.3.5', 'advertise-map my_advertise_map exist my_exist_map'] commands=['router bgp 65535', 'neighbor 3.3.3.5', 'address-family ipv4 unicast', 'advertise-map my_advertise_map exist my_exist_map']
) )
def test_nxos_bgp_neighbor_af_advertise_map_non_exist(self): def test_nxos_bgp_neighbor_af_advertise_map_non_exist(self):
@ -78,5 +78,22 @@ class TestNxosBgpNeighborAfModule(TestNxosModule):
advertise_map_non_exist=['my_advertise_map', 'my_exist_map'])) advertise_map_non_exist=['my_advertise_map', 'my_exist_map']))
self.execute_module( self.execute_module(
changed=True, sort=False, changed=True, sort=False,
commands=['router bgp 65535', 'neighbor 3.3.3.5', 'advertise-map my_advertise_map non-exist my_exist_map'] commands=['router bgp 65535', 'neighbor 3.3.3.5', 'address-family ipv4 unicast', 'advertise-map my_advertise_map non-exist my_exist_map']
)
def test_nxos_bgp_neighbor_af_max_prefix_limit_default(self):
set_module_args(dict(asn=65535, neighbor='3.3.3.5', afi='ipv4',
safi='unicast', max_prefix_limit='default'))
self.execute_module(
changed=True, sort=False,
commands=['router bgp 65535', 'neighbor 3.3.3.5', 'address-family ipv4 unicast', 'no maximum-prefix']
)
def test_nxos_bgp_neighbor_af_max_prefix(self):
set_module_args(dict(asn=65535, neighbor='3.3.3.5', afi='ipv4',
safi='unicast', max_prefix_threshold=20,
max_prefix_limit=20))
self.execute_module(
changed=True, sort=False,
commands=['router bgp 65535', 'neighbor 3.3.3.5', 'address-family ipv4 unicast', 'maximum-prefix 20 20']
) )