nxos_bgp updates (#24270)
* Remove invoke from nxos_bgp * Try to simplify get_existing * Streamline state_present * More testing, squash & compact get_value & get_custom_value * Streamline main * nxapi has problems reading CustomNetworkConfig directly
This commit is contained in:
parent
4186b07d21
commit
830f19d444
4 changed files with 170 additions and 214 deletions
|
@ -155,6 +155,9 @@ class NetworkConfig(object):
|
|||
def __str__(self):
|
||||
return '\n'.join([c.raw for c in self.items])
|
||||
|
||||
def __len__(self):
|
||||
return len(self._items)
|
||||
|
||||
def load(self, s):
|
||||
self._items = self.parse(s)
|
||||
|
||||
|
@ -368,6 +371,9 @@ class NetworkConfig(object):
|
|||
|
||||
class CustomNetworkConfig(NetworkConfig):
|
||||
|
||||
def items_text(self):
|
||||
return [item.text for item in self.items]
|
||||
|
||||
def expand_section(self, configobj, S=None):
|
||||
if S is None:
|
||||
S = list()
|
||||
|
|
|
@ -308,6 +308,7 @@ commands:
|
|||
'''
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.nxos import get_config, load_config
|
||||
from ansible.module_utils.nxos import nxos_argument_spec, check_args
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
@ -406,88 +407,54 @@ PARAM_TO_COMMAND_KEYMAP = {
|
|||
}
|
||||
|
||||
|
||||
def invoke(name, *args, **kwargs):
|
||||
func = globals().get(name)
|
||||
if func:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
def get_custom_value(config, arg):
|
||||
if arg.startswith('event_history'):
|
||||
REGEX_SIZE = re.compile(r'(?:{0} size\s)(?P<value>.*)$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
|
||||
REGEX = re.compile(r'\s+{0}\s*$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
|
||||
value = False
|
||||
|
||||
if 'no {0}'.format(PARAM_TO_COMMAND_KEYMAP[arg]) in config:
|
||||
pass
|
||||
elif PARAM_TO_COMMAND_KEYMAP[arg] in config:
|
||||
try:
|
||||
value = REGEX_SIZE.search(config).group('value')
|
||||
except AttributeError:
|
||||
if REGEX.search(config):
|
||||
value = True
|
||||
|
||||
elif arg == 'enforce_first_as' or arg == 'fast_external_fallover':
|
||||
REGEX = re.compile(r'no\s+{0}\s*$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
|
||||
value = True
|
||||
try:
|
||||
if REGEX.search(config):
|
||||
value = False
|
||||
except TypeError:
|
||||
value = True
|
||||
|
||||
elif arg == 'confederation_peers':
|
||||
REGEX = re.compile(r'(?:confederation peers\s)(?P<value>.*)$', re.M)
|
||||
value = ''
|
||||
if 'confederation peers' in config:
|
||||
value = REGEX.search(config).group('value').split()
|
||||
|
||||
elif arg == 'timer_bgp_keepalive':
|
||||
REGEX = re.compile(r'(?:timers bgp\s)(?P<value>.*)$', re.M)
|
||||
value = ''
|
||||
if 'timers bgp' in config:
|
||||
parsed = REGEX.search(config).group('value').split()
|
||||
value = parsed[0]
|
||||
|
||||
elif arg == 'timer_bgp_hold':
|
||||
REGEX = re.compile(r'(?:timers bgp\s)(?P<value>.*)$', re.M)
|
||||
value = ''
|
||||
if 'timers bgp' in config:
|
||||
parsed = REGEX.search(config).group('value').split()
|
||||
if len(parsed) == 2:
|
||||
value = parsed[1]
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def get_value(arg, config):
|
||||
custom = [
|
||||
'event_history_cli',
|
||||
'event_history_events',
|
||||
'event_history_periodic',
|
||||
'event_history_detail',
|
||||
'confederation_peers',
|
||||
'timer_bgp_hold',
|
||||
'timer_bgp_keepalive',
|
||||
'enforce_first_as',
|
||||
'fast_external_fallover'
|
||||
]
|
||||
command = PARAM_TO_COMMAND_KEYMAP.get(arg)
|
||||
|
||||
if arg in custom:
|
||||
value = get_custom_value(config, arg)
|
||||
elif arg in BOOL_PARAMS:
|
||||
REGEX = re.compile(r'\s+{0}\s*$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
|
||||
if command.split()[0] == 'event-history':
|
||||
command_re = re.compile(r'\s+{0}\s*'.format(command), re.M)
|
||||
|
||||
size_re = re.compile(r'(?:{0} size\s)(?P<value>.*)'.format(command), re.M)
|
||||
value = False
|
||||
try:
|
||||
if REGEX.search(config):
|
||||
|
||||
if command_re.search(config):
|
||||
search = size_re.search(config)
|
||||
if search:
|
||||
value = search.group('value')
|
||||
else:
|
||||
value = True
|
||||
except TypeError:
|
||||
|
||||
elif arg in ['enforce_first_as', 'fast_external_fallover']:
|
||||
no_command_re = re.compile(r'no\s+{0}\s*'.format(command), re.M)
|
||||
value = True
|
||||
|
||||
if no_command_re.search(config):
|
||||
value = False
|
||||
|
||||
elif arg in BOOL_PARAMS:
|
||||
command_re = re.compile(r'\s+{0}\s*'.format(command), re.M)
|
||||
value = False
|
||||
|
||||
if command_re.search(config):
|
||||
value = True
|
||||
else:
|
||||
REGEX = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
|
||||
command_val_re = re.compile(r'(?:{0}\s)(?P<value>.*)'.format(command), re.M)
|
||||
value = ''
|
||||
if PARAM_TO_COMMAND_KEYMAP[arg] in config:
|
||||
value = REGEX.search(config).group('value')
|
||||
|
||||
has_command = command_val_re.search(config)
|
||||
if has_command:
|
||||
found_value = has_command.group('value')
|
||||
|
||||
if arg == 'confederation_peers':
|
||||
value = found_value.split()
|
||||
elif arg == 'timer_bgp_keepalive':
|
||||
value = found_value.split()[0]
|
||||
elif arg == 'timer_bgp_hold':
|
||||
split_values = found_value.split()
|
||||
if len(split_values) == 2:
|
||||
value = split_values[1]
|
||||
elif found_value:
|
||||
value = found_value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
@ -495,16 +462,13 @@ def get_existing(module, args, warnings):
|
|||
existing = {}
|
||||
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
|
||||
|
||||
try:
|
||||
asn_regex = r'.*router\sbgp\s(?P<existing_asn>\d+).*'
|
||||
match_asn = re.match(asn_regex, str(netcfg), re.DOTALL)
|
||||
existing_asn_group = match_asn.groupdict()
|
||||
existing_asn = existing_asn_group['existing_asn']
|
||||
except AttributeError:
|
||||
existing_asn = ''
|
||||
asn_re = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+).*', re.S)
|
||||
asn_match = asn_re.match(str(netcfg))
|
||||
|
||||
if existing_asn:
|
||||
if asn_match:
|
||||
existing_asn = asn_match.group('existing_asn')
|
||||
bgp_parent = 'router bgp {0}'.format(existing_asn)
|
||||
|
||||
if module.params['vrf'] != 'default':
|
||||
parents = [bgp_parent, 'vrf {0}'.format(module.params['vrf'])]
|
||||
else:
|
||||
|
@ -513,40 +477,28 @@ def get_existing(module, args, warnings):
|
|||
config = netcfg.get_section(parents)
|
||||
if config:
|
||||
for arg in args:
|
||||
if arg != 'asn':
|
||||
if module.params['vrf'] != 'default':
|
||||
if arg not in GLOBAL_PARAMS:
|
||||
existing[arg] = get_value(arg, config)
|
||||
else:
|
||||
existing[arg] = get_value(arg, config)
|
||||
if arg != 'asn' and (module.params['vrf'] == 'default' or
|
||||
arg not in GLOBAL_PARAMS):
|
||||
existing[arg] = get_value(arg, config)
|
||||
|
||||
existing['asn'] = existing_asn
|
||||
if module.params['vrf'] == 'default':
|
||||
existing['vrf'] = 'default'
|
||||
else:
|
||||
if (module.params['state'] == 'present' and
|
||||
module.params['vrf'] != 'default'):
|
||||
msg = ("VRF {0} doesn't exist. ".format(module.params['vrf']))
|
||||
warnings.append(msg)
|
||||
else:
|
||||
if (module.params['state'] == 'present' and
|
||||
module.params['vrf'] != 'default'):
|
||||
msg = ("VRF {0} doesn't exist. ".format(module.params['vrf']))
|
||||
warnings.append(msg)
|
||||
|
||||
if not existing and module.params['vrf'] != 'default' and module.params['state'] == 'present':
|
||||
msg = ("VRF {0} doesn't exist.".format(module.params['vrf']))
|
||||
warnings.append(msg)
|
||||
|
||||
return existing
|
||||
|
||||
|
||||
def apply_key_map(key_map, table):
|
||||
new_dict = {}
|
||||
for key, value in table.items():
|
||||
for key in table:
|
||||
new_key = key_map.get(key)
|
||||
if new_key:
|
||||
value = table.get(key)
|
||||
if value:
|
||||
new_dict[new_key] = value
|
||||
else:
|
||||
new_dict[new_key] = value
|
||||
new_dict[new_key] = table.get(key)
|
||||
|
||||
return new_dict
|
||||
|
||||
|
||||
|
@ -561,67 +513,56 @@ def state_present(module, existing, proposed, candidate):
|
|||
elif value is False:
|
||||
commands.append('no {0}'.format(key))
|
||||
elif value == 'default':
|
||||
if key in PARAM_TO_DEFAULT_KEYMAP:
|
||||
commands.append('{0} {1}'.format(key, PARAM_TO_DEFAULT_KEYMAP[key]))
|
||||
elif existing_commands.get(key):
|
||||
existing_value = existing_commands.get(key)
|
||||
default_value = PARAM_TO_DEFAULT_KEYMAP.get(key)
|
||||
existing_value = existing_commands.get(key)
|
||||
|
||||
if default_value:
|
||||
commands.append('{0} {1}'.format(key, default_value))
|
||||
elif existing_value:
|
||||
if key == 'confederation peers':
|
||||
commands.append('no {0} {1}'.format(key, ' '.join(existing_value)))
|
||||
else:
|
||||
commands.append('no {0} {1}'.format(key, existing_value))
|
||||
else:
|
||||
if key == 'confederation peers':
|
||||
existing_confederation_peers = existing.get('confederation_peers')
|
||||
|
||||
if existing_confederation_peers:
|
||||
if not isinstance(existing_confederation_peers, list):
|
||||
existing_confederation_peers = [existing_confederation_peers]
|
||||
else:
|
||||
existing_confederation_peers = []
|
||||
|
||||
values = value.split()
|
||||
for each_value in values:
|
||||
if each_value not in existing_confederation_peers:
|
||||
existing_confederation_peers.append(each_value)
|
||||
peer_string = ' '.join(existing_confederation_peers)
|
||||
commands.append('{0} {1}'.format(key, peer_string))
|
||||
elif key.startswith('timers bgp'):
|
||||
command = 'timers bgp {0} {1}'.format(
|
||||
proposed['timer_bgp_keepalive'],
|
||||
proposed['timer_bgp_hold'])
|
||||
if command not in commands:
|
||||
commands.append(command)
|
||||
else:
|
||||
if value.startswith('size'):
|
||||
value = value.replace('_', ' ')
|
||||
command = '{0} {1}'.format(key, value)
|
||||
existing_value = ' '.join(existing_value)
|
||||
commands.append('no {0} {1}'.format(key, existing_value))
|
||||
elif key == 'confederation peers':
|
||||
existing_confederation_peers = set(existing.get('confederation_peers', []))
|
||||
new_values = set(value.split())
|
||||
peer_string = ' '.join(existing_confederation_peers | new_values)
|
||||
commands.append('{0} {1}'.format(key, peer_string))
|
||||
elif key.startswith('timers bgp'):
|
||||
command = 'timers bgp {0} {1}'.format(
|
||||
proposed['timer_bgp_keepalive'],
|
||||
proposed['timer_bgp_hold'])
|
||||
if command not in commands:
|
||||
commands.append(command)
|
||||
else:
|
||||
if value.startswith('size'):
|
||||
value = value.replace('_', ' ')
|
||||
command = '{0} {1}'.format(key, value)
|
||||
commands.append(command)
|
||||
|
||||
parents = []
|
||||
if commands:
|
||||
commands = fix_commands(commands)
|
||||
parents = ['router bgp {0}'.format(module.params['asn'])]
|
||||
if module.params['vrf'] != 'default':
|
||||
parents.append('vrf {0}'.format(module.params['vrf']))
|
||||
candidate.add(commands, parents=parents)
|
||||
elif proposed:
|
||||
if module.params['vrf'] != 'default':
|
||||
commands.append('vrf {0}'.format(module.params['vrf']))
|
||||
parents = ['router bgp {0}'.format(module.params['asn'])]
|
||||
else:
|
||||
commands.append('router bgp {0}'.format(module.params['asn']))
|
||||
parents = []
|
||||
candidate.add(commands, parents=parents)
|
||||
|
||||
candidate.add(commands, parents=parents)
|
||||
|
||||
|
||||
def state_absent(module, existing, proposed, candidate):
|
||||
def state_absent(module, existing, candidate):
|
||||
commands = []
|
||||
parents = []
|
||||
if module.params['vrf'] == 'default':
|
||||
commands.append('no router bgp {0}'.format(module.params['asn']))
|
||||
else:
|
||||
if existing.get('vrf') == module.params['vrf']:
|
||||
commands.append('no vrf {0}'.format(module.params['vrf']))
|
||||
parents = ['router bgp {0}'.format(module.params['asn'])]
|
||||
elif existing.get('vrf') == module.params['vrf']:
|
||||
commands.append('no vrf {0}'.format(module.params['vrf']))
|
||||
parents = ['router bgp {0}'.format(module.params['asn'])]
|
||||
|
||||
candidate.add(commands, parents=parents)
|
||||
|
||||
|
@ -696,11 +637,7 @@ def main():
|
|||
timer_bgp_hold=dict(required=False, type='str'),
|
||||
timer_bgp_keepalive=dict(required=False, type='str'),
|
||||
state=dict(choices=['present', 'absent'], default='present', required=False),
|
||||
include_defaults=dict(default=True),
|
||||
config=dict(),
|
||||
save=dict(type='bool', default=False)
|
||||
)
|
||||
|
||||
argument_spec.update(nxos_argument_spec)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
|
@ -709,63 +646,22 @@ def main():
|
|||
|
||||
warnings = list()
|
||||
check_args(module, warnings)
|
||||
result = dict(changed=False, warnings=warnings)
|
||||
|
||||
state = module.params['state']
|
||||
args = [
|
||||
"asn",
|
||||
"bestpath_always_compare_med",
|
||||
"bestpath_aspath_multipath_relax",
|
||||
"bestpath_compare_neighborid",
|
||||
"bestpath_compare_routerid",
|
||||
"bestpath_cost_community_ignore",
|
||||
"bestpath_med_confed",
|
||||
"bestpath_med_missing_as_worst",
|
||||
"bestpath_med_non_deterministic",
|
||||
"cluster_id",
|
||||
"confederation_id",
|
||||
"confederation_peers",
|
||||
"disable_policy_batching",
|
||||
"disable_policy_batching_ipv4_prefix_list",
|
||||
"disable_policy_batching_ipv6_prefix_list",
|
||||
"enforce_first_as",
|
||||
"event_history_cli",
|
||||
"event_history_detail",
|
||||
"event_history_events",
|
||||
"event_history_periodic",
|
||||
"fast_external_fallover",
|
||||
"flush_routes",
|
||||
"graceful_restart",
|
||||
"graceful_restart_helper",
|
||||
"graceful_restart_timers_restart",
|
||||
"graceful_restart_timers_stalepath_time",
|
||||
"isolate",
|
||||
"local_as",
|
||||
"log_neighbor_changes",
|
||||
"maxas_limit",
|
||||
"neighbor_down_fib_accelerate",
|
||||
"reconnect_interval",
|
||||
"router_id",
|
||||
"shutdown",
|
||||
"suppress_fib_pending",
|
||||
"timer_bestpath_limit",
|
||||
"timer_bgp_hold",
|
||||
"timer_bgp_keepalive",
|
||||
"vrf"
|
||||
]
|
||||
|
||||
if module.params['vrf'] != 'default':
|
||||
for param, inserted_value in module.params.items():
|
||||
if param in GLOBAL_PARAMS and inserted_value:
|
||||
module.fail_json(msg='Global params can be modified only'
|
||||
' under "default" VRF.',
|
||||
for param in GLOBAL_PARAMS:
|
||||
if module.params[param]:
|
||||
module.fail_json(msg='Global params can be modified only under "default" VRF.',
|
||||
vrf=module.params['vrf'],
|
||||
global_param=param)
|
||||
|
||||
args = PARAM_TO_COMMAND_KEYMAP.keys()
|
||||
existing = get_existing(module, args, warnings)
|
||||
|
||||
if existing.get('asn'):
|
||||
if (existing.get('asn') != module.params['asn'] and
|
||||
state == 'present'):
|
||||
if existing.get('asn') and state == 'present':
|
||||
if existing.get('asn') != module.params['asn']:
|
||||
module.fail_json(msg='Another BGP ASN already exists.',
|
||||
proposed_asn=module.params['asn'],
|
||||
existing_asn=existing.get('asn'))
|
||||
|
@ -774,24 +670,23 @@ def main():
|
|||
if v is not None and k in args)
|
||||
proposed = {}
|
||||
for key, value in proposed_args.items():
|
||||
if key != 'asn' and key != 'vrf':
|
||||
if key not in ['asn', 'vrf']:
|
||||
if str(value).lower() == 'default':
|
||||
value = PARAM_TO_DEFAULT_KEYMAP.get(key)
|
||||
if value is None:
|
||||
value = 'default'
|
||||
if existing.get(key) or (not existing.get(key) and value):
|
||||
value = PARAM_TO_DEFAULT_KEYMAP.get(key, 'default')
|
||||
if existing.get(key) != value:
|
||||
proposed[key] = value
|
||||
|
||||
result = dict(changed=False, warnings=warnings)
|
||||
candidate = CustomNetworkConfig(indent=3)
|
||||
if state == 'present':
|
||||
state_present(module, existing, proposed, candidate)
|
||||
elif existing.get('asn') == module.params['asn']:
|
||||
state_absent(module, existing, candidate)
|
||||
|
||||
if state == 'present' or existing.get('asn') == module.params['asn']:
|
||||
candidate = CustomNetworkConfig(indent=3)
|
||||
invoke('state_%s' % state, module, existing, proposed, candidate)
|
||||
|
||||
if (candidate):
|
||||
load_config(module, candidate)
|
||||
result['changed'] = True
|
||||
result['commands'] = [item.text for item in candidate.items]
|
||||
if candidate:
|
||||
candidate = candidate.items_text()
|
||||
load_config(module, candidate)
|
||||
result['changed'] = True
|
||||
result['commands'] = candidate
|
||||
else:
|
||||
result['commands'] = []
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
feature bgp
|
||||
|
||||
router bgp 65535
|
||||
router-id 192.168.1.1
|
||||
event-history cli size medium
|
||||
event-history detail
|
||||
vrf test2
|
||||
timers bgp 1 10
|
|
@ -42,9 +42,56 @@ class TestNxosBgpModule(TestNxosModule):
|
|||
self.mock_get_config.stop()
|
||||
|
||||
def load_fixtures(self, commands=None):
|
||||
self.get_config.return_value = load_fixture('nxos_bgp_config.cfg')
|
||||
self.load_config.return_value = None
|
||||
|
||||
def test_nxos_bgp(self):
|
||||
set_module_args(dict(asn=65535, vrf='test', router_id='1.1.1.1'))
|
||||
set_module_args(dict(asn=65535, router_id='1.1.1.1'))
|
||||
result = self.execute_module(changed=True)
|
||||
self.assertEqual(result['commands'], ['router bgp 65535', 'vrf test', 'router-id 1.1.1.1'])
|
||||
self.assertEqual(result['commands'], ['router bgp 65535', 'router-id 1.1.1.1'])
|
||||
|
||||
def test_nxos_bgp_change_nothing(self):
|
||||
set_module_args(dict(asn=65535, router_id='192.168.1.1'))
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_nxos_bgp_wrong_asn(self):
|
||||
set_module_args(dict(asn=10, router_id='192.168.1.1'))
|
||||
result = self.execute_module(failed=True)
|
||||
self.assertEqual(result['msg'], 'Another BGP ASN already exists.')
|
||||
|
||||
def test_nxos_bgp_remove(self):
|
||||
set_module_args(dict(asn=65535, state='absent'))
|
||||
self.execute_module(changed=True, commands=['no router bgp 65535'])
|
||||
|
||||
def test_nxos_bgp_remove_vrf(self):
|
||||
set_module_args(dict(asn=65535, vrf='test2', state='absent'))
|
||||
self.execute_module(changed=True, commands=['router bgp 65535', 'no vrf test2'])
|
||||
|
||||
def test_nxos_bgp_remove_nonexistant_vrf(self):
|
||||
set_module_args(dict(asn=65535, vrf='foo', state='absent'))
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_nxos_bgp_remove_wrong_asn(self):
|
||||
set_module_args(dict(asn=10, state='absent'))
|
||||
self.execute_module(changed=False)
|
||||
|
||||
def test_nxos_bgp_vrf(self):
|
||||
set_module_args(dict(asn=65535, vrf='test', router_id='1.1.1.1'))
|
||||
result = self.execute_module(changed=True, commands=['router bgp 65535', 'vrf test', 'router-id 1.1.1.1'])
|
||||
self.assertEqual(result['warnings'], ["VRF test doesn't exist."])
|
||||
|
||||
def test_nxos_bgp_global_param(self):
|
||||
set_module_args(dict(asn=65535, shutdown=True))
|
||||
self.execute_module(changed=True, commands=['router bgp 65535', 'shutdown'])
|
||||
|
||||
def test_nxos_bgp_global_param_outside_default(self):
|
||||
set_module_args(dict(asn=65535, vrf='test', shutdown=True))
|
||||
result = self.execute_module(failed=True)
|
||||
self.assertEqual(result['msg'], 'Global params can be modified only under "default" VRF.')
|
||||
|
||||
def test_nxos_bgp_default_value(self):
|
||||
set_module_args(dict(asn=65535, graceful_restart_timers_restart='default'))
|
||||
self.execute_module(
|
||||
changed=True,
|
||||
commands=['router bgp 65535', 'graceful-restart restart-time 120']
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue