nxos_bgp_af updates (#24171)

* update docs
* clean up nxos-bgp_af
* Remove useless params from bgp_af
* Add bgp_af tests
This commit is contained in:
Nathaniel Case 2017-05-10 16:12:00 -04:00 committed by GitHub
parent 2f26c5285a
commit 09b2964dee
3 changed files with 372 additions and 389 deletions

View file

@ -16,9 +16,11 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# #
ANSIBLE_METADATA = {'metadata_version': '1.0', ANSIBLE_METADATA = {
'status': ['preview'], 'metadata_version': '1.0',
'supported_by': 'community'} 'status': ['preview'],
'supported_by': 'community',
}
DOCUMENTATION = ''' DOCUMENTATION = '''
@ -28,221 +30,221 @@ extends_documentation_fragment: nxos
version_added: "2.2" version_added: "2.2"
short_description: Manages BGP Address-family configuration. short_description: Manages BGP Address-family configuration.
description: description:
- Manages BGP Address-family configurations on NX-OS switches. - Manages BGP Address-family configurations on NX-OS switches.
author: Gabriele Gerbino (@GGabriele) author: Gabriele Gerbino (@GGabriele)
notes: notes:
- C(state=absent) removes the whole BGP ASN configuration - C(state=absent) removes the whole BGP ASN configuration
- Default, where supported, restores params default value. - Default, where supported, restores params default value.
options: options:
asn: asn:
description: description:
- BGP autonomous system number. Valid values are String, - BGP autonomous system number. Valid values are String,
Integer in ASPLAIN or ASDOT notation. Integer in ASPLAIN or ASDOT notation.
required: true required: true
vrf: vrf:
description: description:
- Name of the VRF. The name 'default' is a valid VRF representing - Name of the VRF. The name 'default' is a valid VRF representing
the global bgp. the global bgp.
required: true required: true
afi: afi:
description: description:
- Address Family Identifier. - Address Family Identifier.
required: true required: true
choices: ['ipv4','ipv6', 'vpnv4', 'vpnv6', 'l2vpn'] choices: ['ipv4','ipv6', 'vpnv4', 'vpnv6', 'l2vpn']
safi: safi:
description: description:
- Sub Address Family Identifier. - Sub Address Family Identifier.
required: true required: true
choices: ['unicast','multicast', 'evpn'] choices: ['unicast','multicast', 'evpn']
additional_paths_install: additional_paths_install:
description: description:
- Install a backup path into the forwarding table and provide - Install a backup path into the forwarding table and provide
prefix independent convergence (PIC) in case of a PE-CE link prefix independent convergence (PIC) in case of a PE-CE link
failure. failure.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
additional_paths_receive: additional_paths_receive:
description: description:
- Enables the receive capability of additional paths for all of - Enables the receive capability of additional paths for all of
the neighbors under this address family for which the capability the neighbors under this address family for which the capability
has not been disabled. has not been disabled.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
additional_paths_selection: additional_paths_selection:
description: description:
- Configures the capability of selecting additional paths for - Configures the capability of selecting additional paths for
a prefix. Valid values are a string defining the name of a prefix. Valid values are a string defining the name of
the route-map. the route-map.
required: false required: false
default: null default: null
additional_paths_send: additional_paths_send:
description: description:
- Enables the send capability of additional paths for all of - Enables the send capability of additional paths for all of
the neighbors under this address family for which the capability the neighbors under this address family for which the capability
has not been disabled. has not been disabled.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
advertise_l2vpn_evpn: advertise_l2vpn_evpn:
description: description:
- Advertise evpn routes. - Advertise evpn routes.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
client_to_client: client_to_client:
description: description:
- Configure client-to-client route reflection. - Configure client-to-client route reflection.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
dampen_igp_metric: dampen_igp_metric:
description: description:
- Specify dampen value for IGP metric-related changes, in seconds. - Specify dampen value for IGP metric-related changes, in seconds.
Valid values are integer and keyword 'default'. Valid values are integer and keyword 'default'.
required: false required: false
default: null default: null
dampening_state: dampening_state:
description: description:
- Enable/disable route-flap dampening. - Enable/disable route-flap dampening.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
dampening_half_time: dampening_half_time:
description: description:
- Specify decay half-life in minutes for route-flap dampening. - Specify decay half-life in minutes for route-flap dampening.
Valid values are integer and keyword 'default'. Valid values are integer and keyword 'default'.
required: false required: false
default: null default: null
dampening_max_suppress_time: dampening_max_suppress_time:
description: description:
- Specify max suppress time for route-flap dampening stable route. - Specify max suppress time for route-flap dampening stable route.
Valid values are integer and keyword 'default'. Valid values are integer and keyword 'default'.
required: false required: false
default: null default: null
dampening_reuse_time: dampening_reuse_time:
description: description:
- Specify route reuse time for route-flap dampening. - Specify route reuse time for route-flap dampening.
Valid values are integer and keyword 'default'. Valid values are integer and keyword 'default'.
required: false required: false
dampening_routemap: dampening_routemap:
description: description:
- Specify route-map for route-flap dampening. Valid values are a - Specify route-map for route-flap dampening. Valid values are a
string defining the name of the route-map. string defining the name of the route-map.
required: false required: false
default: null default: null
dampening_suppress_time: dampening_suppress_time:
description: description:
- Specify route suppress time for route-flap dampening. - Specify route suppress time for route-flap dampening.
Valid values are integer and keyword 'default'. Valid values are integer and keyword 'default'.
required: false required: false
default: null default: null
default_information_originate: default_information_originate:
description: description:
- Default information originate. - Default information originate.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
default_metric: default_metric:
description: description:
- Sets default metrics for routes redistributed into BGP. - Sets default metrics for routes redistributed into BGP.
Valid values are Integer or keyword 'default' Valid values are Integer or keyword 'default'
required: false required: false
default: null default: null
distance_ebgp: distance_ebgp:
description: description:
- Sets the administrative distance for eBGP routes. - Sets the administrative distance for eBGP routes.
Valid values are Integer or keyword 'default'. Valid values are Integer or keyword 'default'.
required: false required: false
default: null default: null
distance_ibgp: distance_ibgp:
description: description:
- Sets the administrative distance for iBGP routes. - Sets the administrative distance for iBGP routes.
Valid values are Integer or keyword 'default'. Valid values are Integer or keyword 'default'.
required: false required: false
default: null default: null
distance_local: distance_local:
description: description:
- Sets the administrative distance for local BGP routes. - Sets the administrative distance for local BGP routes.
Valid values are Integer or keyword 'default'. Valid values are Integer or keyword 'default'.
required: false required: false
default: null default: null
inject_map: inject_map:
description: description:
- An array of route-map names which will specify prefixes to - An array of route-map names which will specify prefixes to
inject. Each array entry must first specify the inject-map name, inject. Each array entry must first specify the inject-map name,
secondly an exist-map name, and optionally the copy-attributes secondly an exist-map name, and optionally the copy-attributes
keyword which indicates that attributes should be copied from keyword which indicates that attributes should be copied from
the aggregate. For example [['lax_inject_map', 'lax_exist_map'], the aggregate. For example [['lax_inject_map', 'lax_exist_map'],
['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'], ['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'],
['fsd_inject_map', 'fsd_exist_map']]. ['fsd_inject_map', 'fsd_exist_map']].
required: false required: false
default: null default: null
maximum_paths: maximum_paths:
description: description:
- Configures the maximum number of equal-cost paths for - Configures the maximum number of equal-cost paths for
load sharing. Valid value is an integer in the range 1-64. load sharing. Valid value is an integer in the range 1-64.
default: null default: null
maximum_paths_ibgp: maximum_paths_ibgp:
description: description:
- Configures the maximum number of ibgp equal-cost paths for - Configures the maximum number of ibgp equal-cost paths for
load sharing. Valid value is an integer in the range 1-64. load sharing. Valid value is an integer in the range 1-64.
required: false required: false
default: null default: null
networks: networks:
description: description:
- Networks to configure. Valid value is a list of network - Networks to configure. Valid value is a list of network
prefixes to advertise. The list must be in the form of an array. prefixes to advertise. The list must be in the form of an array.
Each entry in the array must include a prefix address and an Each entry in the array must include a prefix address and an
optional route-map. For example [['10.0.0.0/16', 'routemap_LA'], optional route-map. For example [['10.0.0.0/16', 'routemap_LA'],
['192.168.1.1', 'Chicago'], ['192.168.2.0/24], ['192.168.1.1', 'Chicago'], ['192.168.2.0/24],
['192.168.3.0/24', 'routemap_NYC']]. ['192.168.3.0/24', 'routemap_NYC']].
required: false required: false
default: null default: null
next_hop_route_map: next_hop_route_map:
description: description:
- Configure a route-map for valid nexthops. Valid values are a - Configure a route-map for valid nexthops. Valid values are a
string defining the name of the route-map. string defining the name of the route-map.
required: false required: false
default: null default: null
redistribute: redistribute:
description: description:
- A list of redistribute directives. Multiple redistribute entries - A list of redistribute directives. Multiple redistribute entries
are allowed. The list must be in the form of a nested array. are allowed. The list must be in the form of a nested array.
the first entry of each array defines the source-protocol to the first entry of each array defines the source-protocol to
redistribute from; the second entry defines a route-map name. redistribute from; the second entry defines a route-map name.
A route-map is highly advised but may be optional on some A route-map is highly advised but may be optional on some
platforms, in which case it may be omitted from the array list. platforms, in which case it may be omitted from the array list.
For example [['direct', 'rm_direct'], ['lisp', 'rm_lisp']]. For example [['direct', 'rm_direct'], ['lisp', 'rm_lisp']].
required: false required: false
default: null default: null
suppress_inactive: suppress_inactive:
description: description:
- Advertises only active routes to peers. - Advertises only active routes to peers.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
table_map: table_map:
description: description:
- Apply table-map to filter routes downloaded into URIB. - Apply table-map to filter routes downloaded into URIB.
Valid values are a string. Valid values are a string.
required: false required: false
default: null default: null
table_map_filter: table_map_filter:
description: description:
- Filters routes rejected by the route-map and does not download - Filters routes rejected by the route-map and does not download
them to the RIB. them to the RIB.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
state: state:
description: description:
- Determines whether the config should be present or not - Determines whether the config should be present or not
on the device. on the device.
required: false required: false
default: present default: present
choices: ['present','absent'] choices: ['present','absent']
''' '''
EXAMPLES = ''' EXAMPLES = '''
# configure a simple address-family # configure a simple address-family
@ -256,58 +258,22 @@ EXAMPLES = '''
''' '''
RETURN = ''' RETURN = '''
proposed: commands:
description: k/v pairs of parameters passed into module
returned: verbose mode
type: dict
sample: {"advertise_l2vpn_evpn": true, "afi": "ipv4",
"asn": "65535", "safi": "unicast", "vrf": "TESTING"}
existing:
description: k/v pairs of existing BGP AF configuration
returned: verbose mode
type: dict
sample: {}
end_state:
description: k/v pairs of BGP AF configuration after module execution
returned: verbose mode
type: dict
sample: {"additional_paths_install": false,
"additional_paths_receive": false,
"additional_paths_selection": "",
"additional_paths_send": false,
"advertise_l2vpn_evpn": true, "afi": "ipv4",
"asn": "65535", "client_to_client": true,
"dampen_igp_metric": "600", "dampening_half_time": "",
"dampening_max_suppress_time": "", "dampening_reuse_time": "",
"dampening_routemap": "", "dampening_state": false,
"dampening_suppress_time": "",
"default_information_originate": false, "default_metric": "",
"distance_ebgp": "20", "distance_ibgp": "200",
"distance_local": "220", "inject_map": [], "maximum_paths": "1",
"maximum_paths_ibgp": "1", "networks": [],
"next_hop_route_map": "", "redistribute": [], "safi": "unicast",
"suppress_inactive": false, "table_map": "",
"table_map_filter": false, "vrf": "TESTING"}
updates:
description: commands sent to the device description: commands sent to the device
returned: always returned: always
type: list type: list
sample: ["router bgp 65535", "vrf TESTING", sample: ["router bgp 65535", "vrf TESTING",
"address-family ipv4 unicast", "advertise l2vpn evpn"] "address-family ipv4 unicast", "advertise l2vpn evpn"]
changed:
description: check to see if a change was made on the device
returned: always
type: boolean
sample: true
''' '''
import re import re
from ansible.module_utils.nxos import get_config, load_config, run_commands
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.nxos import nxos_argument_spec, check_args
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netcfg import CustomNetworkConfig from ansible.module_utils.netcfg import CustomNetworkConfig
WARNINGS = []
BOOL_PARAMS = [ BOOL_PARAMS = [
'additional_paths_install', 'additional_paths_install',
'additional_paths_receive', 'additional_paths_receive',
@ -365,24 +331,17 @@ DAMPENING_PARAMS = [
'dampening_suppress_time', 'dampening_suppress_time',
'dampening_reuse_time', 'dampening_reuse_time',
'dampening_max_suppress_time' 'dampening_max_suppress_time'
] ]
def invoke(name, *args, **kwargs):
func = globals().get(name)
if func:
return func(*args, **kwargs)
def get_custom_list_value(config, arg, module): def get_custom_list_value(config, arg, module):
value_list = [] value_list = []
splitted_config = config.splitlines() splitted_config = config.splitlines()
if arg == 'inject_map': if arg == 'inject_map':
REGEX_INJECT = ('.*inject-map\s(?P<inject_map>\S+)' REGEX_INJECT = r'.*inject-map\s(?P<inject_map>\S+)\sexist-map\s(?P<exist_map>\S+)-*'
'\sexist-map\s(?P<exist_map>\S+)-*')
for line in splitted_config: for line in splitted_config:
value = [] value = []
inject_group = {} inject_group = {}
try: try:
match_inject = re.match(REGEX_INJECT, line, re.DOTALL) match_inject = re.match(REGEX_INJECT, line, re.DOTALL)
@ -392,7 +351,7 @@ def get_custom_list_value(config, arg, module):
value.append(inject_map) value.append(inject_map)
value.append(exist_map) value.append(exist_map)
except AttributeError: except AttributeError:
value = [] value = []
if value: if value:
copy_attributes = False copy_attributes = False
@ -401,8 +360,7 @@ def get_custom_list_value(config, arg, module):
inject_group['inject_map'], inject_group['inject_map'],
inject_group['exist_map'])) inject_group['exist_map']))
REGEX = re.compile(r'\s+{0}\s*$'.format( REGEX = re.compile(r'\s+{0}\s*$'.format(inject_map_command), re.M)
inject_map_command), re.M)
try: try:
if REGEX.search(config): if REGEX.search(config):
copy_attributes = True copy_attributes = True
@ -417,8 +375,7 @@ def get_custom_list_value(config, arg, module):
REGEX_NETWORK = re.compile(r'(?:network\s)(?P<value>.*)$') REGEX_NETWORK = re.compile(r'(?:network\s)(?P<value>.*)$')
for line in splitted_config: for line in splitted_config:
value = [] value = []
network_group = {}
if 'network' in line: if 'network' in line:
value = REGEX_NETWORK.search(line).group('value').split() value = REGEX_NETWORK.search(line).group('value').split()
@ -431,8 +388,7 @@ def get_custom_list_value(config, arg, module):
RED_REGEX = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format( RED_REGEX = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(
PARAM_TO_COMMAND_KEYMAP[arg]), re.M) PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
for line in splitted_config: for line in splitted_config:
value = [] value = []
redistribute_group = {}
if 'redistribute' in line: if 'redistribute' in line:
value = RED_REGEX.search(line).group('value').split() value = RED_REGEX.search(line).group('value').split()
if value: if value:
@ -472,8 +428,7 @@ def get_custom_string_value(config, arg, module):
if PARAM_TO_COMMAND_KEYMAP[arg] in config: if PARAM_TO_COMMAND_KEYMAP[arg] in config:
value = REGEX.search(config).group('value') value = REGEX.search(config).group('value')
else: else:
REGEX_DAMPENING = ('.*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)' REGEX_DAMPENING = r'.*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)'
'\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)')
try: try:
match_dampening = re.match(REGEX_DAMPENING, config, re.DOTALL) match_dampening = re.match(REGEX_DAMPENING, config, re.DOTALL)
dampening_group = match_dampening.groupdict() dampening_group = match_dampening.groupdict()
@ -513,49 +468,45 @@ def get_value(arg, config, module):
'redistribute' 'redistribute'
] ]
if arg in BOOL_PARAMS: if arg in custom:
REGEX = re.compile(r'\s+{0}\s*$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
value = False
try:
if REGEX.search(config):
value = True
except TypeError:
value = False
elif arg in custom:
value = get_custom_list_value(config, arg, module) value = get_custom_list_value(config, arg, module)
elif (arg.startswith('distance') or arg.startswith('dampening') or elif (arg.startswith('distance') or arg.startswith('dampening') or
arg.startswith('table_map')): arg.startswith('table_map')):
value = get_custom_string_value(config, arg, module) value = get_custom_string_value(config, arg, module)
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: 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(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
value = '' 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:
value = has_command.group('value')
return value return value
def get_existing(module, args): def get_existing(module, args, warnings):
existing = {} existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
try: asn_regex = re.compile(r'.*router\sbgp\s(?P<existing_asn>\d+).*', re.DOTALL)
asn_regex = '.*router\sbgp\s(?P<existing_asn>\d+).*' match_asn = asn_regex.match(str(netcfg))
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 = ''
if existing_asn: if match_asn:
existing_asn = match_asn.group('existing_asn')
parents = ["router bgp {0}".format(existing_asn)] parents = ["router bgp {0}".format(existing_asn)]
if module.params['vrf'] != 'default': if module.params['vrf'] != 'default':
parents.append('vrf {0}'.format(module.params['vrf'])) parents.append('vrf {0}'.format(module.params['vrf']))
parents.append('address-family {0} {1}'.format(module.params['afi'], parents.append('address-family {0} {1}'.format(module.params['afi'], module.params['safi']))
module.params['safi']))
config = netcfg.get_section(parents) config = netcfg.get_section(parents)
if config: if config:
@ -568,22 +519,18 @@ def get_existing(module, args):
existing['safi'] = module.params['safi'] existing['safi'] = module.params['safi']
existing['vrf'] = module.params['vrf'] existing['vrf'] = module.params['vrf']
else: else:
WARNINGS.append("The BGP process {0} didn't exist but the task" warnings.append("The BGP process {0} didn't exist but the task just created it.".format(module.params['asn']))
" just created it.".format(module.params['asn']))
return existing return existing
def apply_key_map(key_map, table): def apply_key_map(key_map, table):
new_dict = {} new_dict = {}
for key, value in table.items(): for key in table:
new_key = key_map.get(key) new_key = key_map.get(key)
if new_key: if new_key:
value = table.get(key) new_dict[new_key] = table.get(key)
if value:
new_dict[new_key] = value
else:
new_dict[new_key] = value
return new_dict return new_dict
@ -637,12 +584,10 @@ def default_existing(existing_value, key, value):
elif key == 'inject-map': elif key == 'inject-map':
for maps in existing_value: for maps in existing_value:
if len(maps) == 2: if len(maps) == 2:
commands.append('no inject-map {0} exist-map {1}'.format( commands.append('no inject-map {0} exist-map {1}'.format(maps[0], maps[1]))
maps[0], maps[1]))
elif len(maps) == 3: elif len(maps) == 3:
commands.append('no inject-map {0} exist-map {1} ' commands.append('no inject-map {0} exist-map {1} '
'copy-attributes'.format( 'copy-attributes'.format(maps[0], maps[1]))
maps[0], maps[1]))
else: else:
commands.append('no {0} {1}'.format(key, existing_value)) commands.append('no {0} {1}'.format(key, existing_value))
return commands return commands
@ -658,8 +603,7 @@ def get_network_command(existing, key, value):
if len(inet) == 1: if len(inet) == 1:
command = '{0} {1}'.format(key, inet[0]) command = '{0} {1}'.format(key, inet[0])
elif len(inet) == 2: elif len(inet) == 2:
command = '{0} {1} route-map {2}'.format(key, command = '{0} {1} route-map {2}'.format(key, inet[0], inet[1])
inet[0], inet[1])
commands.append(command) commands.append(command)
return commands return commands
@ -791,7 +735,7 @@ def state_present(module, existing, proposed, candidate):
candidate.add(commands, parents=parents) candidate.add(commands, parents=parents)
elif len(commands) > 1: elif len(commands) > 1:
parents.append('address-family {0} {1}'.format(module.params['afi'], parents.append('address-family {0} {1}'.format(module.params['afi'],
module.params['safi'])) module.params['safi']))
if addr_family_command in commands: if addr_family_command in commands:
commands.remove(addr_family_command) commands.remove(addr_family_command)
candidate.add(commands, parents=parents) candidate.add(commands, parents=parents)
@ -812,8 +756,8 @@ def main():
argument_spec = dict( argument_spec = dict(
asn=dict(required=True, type='str'), asn=dict(required=True, type='str'),
vrf=dict(required=False, type='str', default='default'), vrf=dict(required=False, type='str', default='default'),
safi=dict(required=True, type='str', choices=['unicast','multicast', 'evpn']), safi=dict(required=True, type='str', choices=['unicast', 'multicast', 'evpn']),
afi=dict(required=True, type='str', choices=['ipv4','ipv6', 'vpnv4', 'vpnv6', 'l2vpn']), afi=dict(required=True, type='str', choices=['ipv4', 'ipv6', 'vpnv4', 'vpnv6', 'l2vpn']),
additional_paths_install=dict(required=False, type='bool'), additional_paths_install=dict(required=False, type='bool'),
additional_paths_receive=dict(required=False, type='bool'), additional_paths_receive=dict(required=False, type='bool'),
additional_paths_selection=dict(required=False, type='str'), additional_paths_selection=dict(required=False, type='str'),
@ -841,27 +785,23 @@ def main():
suppress_inactive=dict(required=False, type='bool'), suppress_inactive=dict(required=False, type='bool'),
table_map=dict(required=False, type='str'), table_map=dict(required=False, type='str'),
table_map_filter=dict(required=False, type='bool'), table_map_filter=dict(required=False, type='bool'),
state=dict(choices=['present', 'absent'], default='present', state=dict(choices=['present', 'absent'], default='present', required=False),
required=False),
include_defaults=dict(default=True),
config=dict(),
save=dict(type='bool', default=False)
) )
argument_spec.update(nxos_argument_spec) argument_spec.update(nxos_argument_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(
required_together=[DAMPENING_PARAMS, argument_spec=argument_spec,
['distance_ibgp', required_together=[DAMPENING_PARAMS, ['distance_ibgp', 'distance_ebgp', 'distance_local']],
'distance_ebgp', supports_check_mode=True,
'distance_local']], )
supports_check_mode=True)
warnings = list() warnings = list()
check_args(module, warnings) check_args(module, warnings)
result = dict(changed=False, warnings=warnings)
state = module.params['state'] state = module.params['state']
if module.params['dampening_routemap']: if module.params['dampening_routemap']:
for param in DAMPENING_PARAMS: for param in DAMPENING_PARAMS:
if module.params[param]: if module.params[param]:
@ -878,52 +818,17 @@ def main():
module.fail_json(msg='table_map param is needed when using' module.fail_json(msg='table_map param is needed when using'
' table_map_filter filter.') ' table_map_filter filter.')
args = [ args = PARAM_TO_COMMAND_KEYMAP.keys()
"additional_paths_install", existing = get_existing(module, args, warnings)
"additional_paths_receive",
"additional_paths_selection",
"additional_paths_send",
"advertise_l2vpn_evpn",
"afi",
"asn",
"client_to_client",
"dampen_igp_metric",
"dampening_half_time",
"dampening_max_suppress_time",
"dampening_reuse_time",
"dampening_suppress_time",
"dampening_routemap",
"dampening_state",
"default_information_originate",
"default_metric",
"distance_ebgp",
"distance_ibgp",
"distance_local",
"inject_map",
"maximum_paths",
"maximum_paths_ibgp",
"networks",
"next_hop_route_map",
"redistribute",
"safi",
"suppress_inactive",
"table_map",
"table_map_filter",
"vrf"
]
existing = invoke('get_existing', module, args) if existing.get('asn') and state == 'present':
if existing.get('asn') != module.params['asn']:
if existing.get('asn'):
if (existing.get('asn') != module.params['asn'] and
state == 'present'):
module.fail_json(msg='Another BGP ASN already exists.', module.fail_json(msg='Another BGP ASN already exists.',
proposed_asn=module.params['asn'], proposed_asn=module.params['asn'],
existing_asn=existing.get('asn')) existing_asn=existing.get('asn'))
end_state = existing
proposed_args = dict((k, v) for k, v in module.params.items() proposed_args = dict((k, v) for k, v in module.params.items()
if v is not None and k in args) if v is not None and k in args)
if proposed_args.get('networks'): if proposed_args.get('networks'):
if proposed_args['networks'][0] == 'default': if proposed_args['networks'][0] == 'default':
@ -936,33 +841,25 @@ def main():
for key, value in proposed_args.items(): for key, value in proposed_args.items():
if key not in ['asn', 'vrf']: if key not in ['asn', 'vrf']:
if str(value).lower() == 'default': if str(value).lower() == 'default':
value = PARAM_TO_DEFAULT_KEYMAP.get(key) value = PARAM_TO_DEFAULT_KEYMAP.get(key, 'default')
if value is None: if existing.get(key) != value:
value = 'default'
if existing.get(key) or (not existing.get(key) and value):
proposed[key] = value proposed[key] = value
result = {} candidate = CustomNetworkConfig(indent=3)
if state == 'present' or (state == 'absent' and existing): if state == 'present':
candidate = CustomNetworkConfig(indent=3) state_present(module, existing, proposed, candidate)
invoke('state_%s' % state, module, existing, proposed, candidate) elif state == 'absent' and existing:
response = load_config(module, candidate) state_absent(module, existing, proposed, candidate)
result.update(response)
if candidate:
load_config(module, candidate)
result['changed'] = True
result['commands'] = candidate.items_text()
else: else:
result['updates'] = [] result['commands'] = []
if module._verbosity > 0:
end_state = invoke('get_existing', module, args)
result['end_state'] = end_state
result['existing'] = existing
result['proposed'] = proposed_args
if WARNINGS:
result['warnings'] = WARNINGS
module.exit_json(**result) module.exit_json(**result)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -489,7 +489,6 @@ lib/ansible/modules/network/nxos/_nxos_mtu.py
lib/ansible/modules/network/nxos/_nxos_template.py lib/ansible/modules/network/nxos/_nxos_template.py
lib/ansible/modules/network/nxos/nxos_aaa_server.py lib/ansible/modules/network/nxos/nxos_aaa_server.py
lib/ansible/modules/network/nxos/nxos_aaa_server_host.py lib/ansible/modules/network/nxos/nxos_aaa_server_host.py
lib/ansible/modules/network/nxos/nxos_bgp_af.py
lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py
lib/ansible/modules/network/nxos/nxos_bgp_neighbor_af.py lib/ansible/modules/network/nxos/nxos_bgp_neighbor_af.py
lib/ansible/modules/network/nxos/nxos_command.py lib/ansible/modules/network/nxos/nxos_command.py

View file

@ -0,0 +1,87 @@
# (c) 2016 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 <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests.mock import patch
from ansible.modules.network.nxos import nxos_bgp_af
from .nxos_module import TestNxosModule, load_fixture, set_module_args
class TestNxosBgpAfModule(TestNxosModule):
module = nxos_bgp_af
def setUp(self):
self.mock_load_config = patch('ansible.modules.network.nxos.nxos_bgp_af.load_config')
self.load_config = self.mock_load_config.start()
self.mock_get_config = patch('ansible.modules.network.nxos.nxos_bgp_af.get_config')
self.get_config = self.mock_get_config.start()
def tearDown(self):
self.mock_load_config.stop()
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_af(self):
set_module_args(dict(asn=65535, afi='ipv4', safi='unicast'))
self.execute_module(
changed=True,
commands=['router bgp 65535', 'address-family ipv4 unicast']
)
def test_nxos_bgp_af_vrf(self):
set_module_args(dict(asn=65535, vrf='test', afi='ipv4', safi='unicast'))
self.execute_module(
changed=True,
commands=['router bgp 65535', 'vrf test', 'address-family ipv4 unicast']
)
def test_nxos_bgp_af_dampening_routemap(self):
set_module_args(dict(asn=65535, afi='ipv4', safi='unicast',
dampening_routemap='route-map-a'))
self.execute_module(
changed=True,
commands=['router bgp 65535', 'address-family ipv4 unicast',
'dampening route-map route-map-a']
)
def test_nxos_bgp_af_dampening_manual(self):
set_module_args(dict(asn=65535, afi='ipv4', safi='unicast',
dampening_half_time=5, dampening_suppress_time=2000,
dampening_reuse_time=1900, dampening_max_suppress_time=10))
self.execute_module(
changed=True,
commands=['router bgp 65535', 'address-family ipv4 unicast',
'dampening 5 1900 2000 10']
)
def test_nxos_bgp_af_dampening_mix(self):
set_module_args(dict(asn=65535, afi='ipv4', safi='unicast',
dampening_routemap='route-map-a',
dampening_half_time=5, dampening_suppress_time=2000,
dampening_reuse_time=1900, dampening_max_suppress_time=10))
result = self.execute_module(failed=True)
self.assertEqual(result['msg'], 'dampening_routemap cannot be used with the dampening_half_time param')