1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

nxos_vxlan_vtep_vni fixes (#25913)

* nxos_vxlan_vtep_vni first pass
* nxos_vxlan_vtep_vni tests
This commit is contained in:
Nathaniel Case 2017-06-28 19:38:31 -04:00 committed by GitHub
parent 69de9d86bd
commit eea18e9875
4 changed files with 183 additions and 230 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,129 +30,87 @@ extends_documentation_fragment: nxos
version_added: "2.2" version_added: "2.2"
short_description: Creates a Virtual Network Identifier member (VNI) short_description: Creates a Virtual Network Identifier member (VNI)
description: description:
- Creates a Virtual Network Identifier member (VNI) for an NVE - Creates a Virtual Network Identifier member (VNI) for an NVE
overlay interface. overlay interface.
author: Gabriele Gerbino (@GGabriele) author: Gabriele Gerbino (@GGabriele)
notes: notes:
- default, where supported, restores params default value. - default, where supported, restores params default value.
options: options:
interface: interface:
description: description:
- Interface name for the VXLAN Network Virtualization Endpoint. - Interface name for the VXLAN Network Virtualization Endpoint.
required: true required: true
vni: vni:
description: description:
- ID of the Virtual Network Identifier. - ID of the Virtual Network Identifier.
required: true required: true
assoc_vrf: assoc_vrf:
description: description:
- This attribute is used to identify and separate processing VNIs - This attribute is used to identify and separate processing VNIs
that are associated with a VRF and used for routing. The VRF that are associated with a VRF and used for routing. The VRF
and VNI specified with this command must match the configuration and VNI specified with this command must match the configuration
of the VNI under the VRF. of the VNI under the VRF.
required: false required: false
choices: ['true','false'] choices: ['true','false']
default: null default: null
ingress_replication: ingress_replication:
description: description:
- Specifies mechanism for host reachability advertisement. - Specifies mechanism for host reachability advertisement.
required: false required: false
choices: ['bgp','static'] choices: ['bgp','static']
default: null default: null
multicast_group: multicast_group:
description: description:
- The multicast group (range) of the VNI. Valid values are - The multicast group (range) of the VNI. Valid values are
string and keyword 'default'. string and keyword 'default'.
required: false required: false
default: null default: null
peer_list: peer_list:
description: description:
- Set the ingress-replication static peer list. Valid values - Set the ingress-replication static peer list. Valid values
are an array, a space-separated string of ip addresses, are an array, a space-separated string of ip addresses,
or the keyword 'default'. or the keyword 'default'.
required: false required: false
default: null default: null
suppress_arp: suppress_arp:
description: description:
- Suppress arp under layer 2 VNI. - Suppress arp under layer 2 VNI.
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']
include_defaults:
description:
- Specify to use or not the complete running configuration
for module operations.
required: false
default: true
choices: ['true','true']
config:
description:
- Configuration string to be used for module operations. If not
specified, the module will use the current running configuration.
required: false
default: null
save:
description:
- Specify to save the running configuration after
module operations.
required: false
default: false
choices: ['true','false']
''' '''
EXAMPLES = ''' EXAMPLES = '''
- nxos_vxlan_vtep_vni: - nxos_vxlan_vtep_vni:
interface: nve1 interface: nve1
vni: 6000 vni: 6000
ingress_replication: default ingress_replication: default
username: "{{ un }}"
password: "{{ pwd }}"
host: "{{ inventory_hostname }}"
''' '''
RETURN = ''' RETURN = '''
proposed: commands:
description: k/v pairs of parameters passed into module
returned: verbose mode
type: dict
sample: {"ingress_replication": "default", "interface": "nve1", "vni": "6000"}
existing:
description: k/v pairs of existing configuration
returned: verbose mode
type: dict
sample: {}
end_state:
description: k/v pairs of configuration after module execution
returned: verbose mode
type: dict
sample: {"assoc_vrf": false, "ingress_replication": "", "interface": "nve1",
"multicast_group": "", "peer_list": [],
"suppress_arp": false, "vni": "6000"}
updates:
description: commands sent to the device description: commands sent to the device
returned: always returned: always
type: list type: list
sample: ["interface nve1", "member vni 6000"] sample: ["interface nve1", "member vni 6000"]
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
BOOL_PARAMS = ['suppress_arp'] BOOL_PARAMS = [
'assoc-vrf',
'suppress_arp',
]
PARAM_TO_COMMAND_KEYMAP = { PARAM_TO_COMMAND_KEYMAP = {
'assoc_vrf': 'associate-vrf', 'assoc_vrf': 'associate-vrf',
'interface': 'interface', 'interface': 'interface',
@ -160,70 +120,45 @@ PARAM_TO_COMMAND_KEYMAP = {
'peer_list': 'peer-ip', 'peer_list': 'peer-ip',
'suppress_arp': 'suppress-arp' 'suppress_arp': 'suppress-arp'
} }
PARAM_TO_DEFAULT_KEYMAP = {}
WARNINGS = []
def invoke(name, *args, **kwargs):
func = globals().get(name)
if func:
return func(*args, **kwargs)
def get_value(arg, config, module): def get_value(arg, config, module):
command = PARAM_TO_COMMAND_KEYMAP[arg]
command_val_re = re.compile(r'(?:{0}\s)(?P<value>.*)$'.format(command), re.M)
if arg in BOOL_PARAMS: if arg in BOOL_PARAMS:
REGEX = re.compile(r'\s+{0}\s*$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M) command_re = re.compile(r'\s+{0}\s*$'.format(command), re.M)
value = False value = False
try: if command_re.search(config):
if REGEX.search(config): value = True
value = True elif arg == 'peer_list':
except TypeError: has_command_val = command_val_re.findall(config, re.M)
value = False value = []
if has_command_val:
value = has_command_val
else: else:
REGEX = 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: has_command_val = command_val_re.search(config, re.M)
value = REGEX.search(config).group('value') if has_command_val:
value = has_command_val.group('value')
return value return value
def check_interface(module, netcfg): def check_interface(module, netcfg):
config = str(netcfg) config = str(netcfg)
REGEX = re.compile(r'(?:interface nve)(?P<value>.*)$', re.M) has_interface = re.search(r'(?:interface nve)(?P<value>.*)$', config, re.M)
value = '' value = ''
if 'interface nve' in config: if has_interface:
value = 'nve{0}'.format(REGEX.search(config).group('value')) value = 'nve{0}'.format(has_interface.group('value'))
return value return value
def get_custom_value(arg, config, module):
splitted_config = config.splitlines()
if arg == 'assoc_vrf':
value = False
if 'associate-vrf' in config:
value = True
elif arg == 'peer_list':
value = []
REGEX = re.compile(r'(?:peer-ip\s)(?P<peer_value>.*)$', re.M)
for line in splitted_config:
peer_value = ''
if PARAM_TO_COMMAND_KEYMAP[arg] in line:
peer_value = REGEX.search(line).group('peer_value')
if peer_value:
value.append(peer_value)
return value
def get_existing(module, args): def get_existing(module, args):
existing = {} existing = {}
netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
custom = [
'assoc_vrf',
'peer_list'
]
interface_exist = check_interface(module, netcfg) interface_exist = check_interface(module, netcfg)
if interface_exist: if interface_exist:
parents = ['interface {0}'.format(interface_exist)] parents = ['interface {0}'.format(interface_exist)]
@ -242,10 +177,7 @@ def get_existing(module, args):
if config: if config:
for arg in args: for arg in args:
if arg not in ['interface', 'vni']: if arg not in ['interface', 'vni']:
if arg in custom: existing[arg] = get_value(arg, config, module)
existing[arg] = get_custom_value(arg, config, module)
else:
existing[arg] = get_value(arg, config, module)
existing['interface'] = interface_exist existing['interface'] = interface_exist
existing['vni'] = module.params['vni'] existing['vni'] = module.params['vni']
@ -257,11 +189,7 @@ def apply_key_map(key_map, table):
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 new_key: if new_key:
value = table.get(key) new_dict[new_key] = value
if value:
new_dict[new_key] = value
else:
new_dict[new_key] = value
return new_dict return new_dict
@ -273,11 +201,9 @@ def state_present(module, existing, proposed, candidate):
for key, value in proposed_commands.items(): for key, value in proposed_commands.items():
if key == 'associate-vrf': if key == 'associate-vrf':
command = 'member vni {0} {1}'.format(module.params['vni'], key) command = 'member vni {0} {1}'.format(module.params['vni'], key)
if not value:
if value: command = 'no {0}'.format(command)
commands.append(command) commands.append(command)
else:
commands.append('no {0}'.format(command))
elif key == 'peer-ip' and value != 'default': elif key == 'peer-ip' and value != 'default':
for peer in value: for peer in value:
@ -289,10 +215,8 @@ def state_present(module, existing, proposed, candidate):
elif value is True: elif value is True:
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': elif value == 'default':
if existing_commands.get(key): if existing_commands.get(key):
existing_value = existing_commands.get(key) existing_value = existing_commands.get(key)
@ -345,23 +269,17 @@ def main():
multicast_group=dict(required=False, type='str'), multicast_group=dict(required=False, type='str'),
peer_list=dict(required=False, type='list'), peer_list=dict(required=False, type='list'),
suppress_arp=dict(required=False, type='bool'), suppress_arp=dict(required=False, type='bool'),
ingress_replication=dict(required=False, type='str', ingress_replication=dict(required=False, type='str', choices=['bgp', 'static', 'default']),
choices=['bgp', 'static', 'default']), state=dict(choices=['present', 'absent'], default='present', required=False),
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) 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}
if module.params['assoc_vrf']: if module.params['assoc_vrf']:
mutually_exclusive_params = ['multicast_group', mutually_exclusive_params = ['multicast_group',
@ -384,74 +302,46 @@ def main():
module.params['peer_list'] = stripped_peer_list module.params['peer_list'] = stripped_peer_list
state = module.params['state'] state = module.params['state']
args = [ args = PARAM_TO_COMMAND_KEYMAP.keys()
'assoc_vrf', existing, interface_exist = get_existing(module, args)
'interface',
'vni', if state == 'present':
'ingress_replication', if not interface_exist:
'multicast_group', module.fail_json(msg="The proposed NVE interface does not exist. Use nxos_interface to create it first.")
'peer_list', elif interface_exist != module.params['interface']:
'suppress_arp' module.fail_json(msg='Only 1 NVE interface is allowed on the switch.')
] elif state == 'absent':
if interface_exist != module.params['interface']:
module.exit_json(**result)
elif existing and existing['vni'] != module.params['vni']:
module.fail_json(
msg="ERROR: VNI delete failed: Could not find vni node for {0}".format(module.params['vni']),
existing_vni=existing['vni']
)
existing, interface_exist = invoke('get_existing', module, args)
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)
proposed = {} proposed = {}
for key, value in proposed_args.items(): for key, value in proposed_args.items():
if key != 'interface': if key != 'interface' and existing.get(key) != value:
if str(value).lower() == 'default': proposed[key] = value
value = PARAM_TO_DEFAULT_KEYMAP.get(key)
if value is None:
value = 'default'
if existing.get(key) or (not existing.get(key) and value):
proposed[key] = value
result = {} candidate = CustomNetworkConfig(indent=3)
if state == 'present' or (state == 'absent' and existing): if state == 'present':
if not interface_exist: state_present(module, existing, proposed, candidate)
WARNINGS.append("The proposed NVE interface does not exist. " elif state == 'absent':
"Use nxos_interface to create it first.") state_absent(module, existing, proposed, candidate)
elif interface_exist != module.params['interface']:
module.fail_json(msg='Only 1 NVE interface is allowed on '
'the switch.')
elif (existing and state == 'absent' and
existing['vni'] != module.params['vni']):
module.fail_json(msg="ERROR: VNI delete failed: Could not find"
" vni node for {0}".format(
module.params['vni']),
existing_vni=existing['vni'])
else:
candidate = CustomNetworkConfig(indent=3)
invoke('state_%s' % state, module, existing, proposed, candidate)
result['changed'] = False
for k, v in proposed.items():
if k in existing:
if existing[k] != proposed[k] or state == 'absent':
result['changed'] = True
if k not in existing and state == 'present':
result['changed'] = True
if module.check_mode:
module.exit_json(commands=candidate)
else:
load_config(module, candidate)
else:
result['updates'] = []
if module._verbosity > 0: if candidate:
end_state, interface_exist = invoke('get_existing', module, args) candidate = candidate.items_text()
result['end_state'] = end_state result['changed'] = True
result['existing'] = existing result['commands'] = candidate
result['proposed'] = proposed_args if not module.check_mode:
load_config(module, candidate)
if WARNINGS:
result['warnings'] = WARNINGS
module.exit_json(**result) module.exit_json(**result)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -395,7 +395,6 @@ lib/ansible/modules/network/nxos/nxos_vrrp.py
lib/ansible/modules/network/nxos/nxos_vtp_domain.py lib/ansible/modules/network/nxos/nxos_vtp_domain.py
lib/ansible/modules/network/nxos/nxos_vtp_password.py lib/ansible/modules/network/nxos/nxos_vtp_password.py
lib/ansible/modules/network/nxos/nxos_vtp_version.py lib/ansible/modules/network/nxos/nxos_vtp_version.py
lib/ansible/modules/network/nxos/nxos_vxlan_vtep_vni.py
lib/ansible/modules/net_tools/omapi_host.py lib/ansible/modules/net_tools/omapi_host.py
lib/ansible/modules/network/openswitch/_ops_template.py lib/ansible/modules/network/openswitch/_ops_template.py
lib/ansible/modules/network/openswitch/ops_command.py lib/ansible/modules/network/openswitch/ops_command.py

View file

@ -0,0 +1,2 @@
interface nve1
member vni 6000

View file

@ -0,0 +1,62 @@
# (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_vxlan_vtep_vni
from .nxos_module import TestNxosModule, load_fixture, set_module_args
class TestNxosVxlanVtepVniModule(TestNxosModule):
module = nxos_vxlan_vtep_vni
def setUp(self):
self.mock_load_config = patch('ansible.modules.network.nxos.nxos_vxlan_vtep_vni.load_config')
self.load_config = self.mock_load_config.start()
self.mock_get_config = patch('ansible.modules.network.nxos.nxos_vxlan_vtep_vni.get_config')
self.get_config = self.mock_get_config.start()
def tearDown(self):
self.mock_get_config.stop()
self.mock_load_config.stop()
def load_fixtures(self, commands=None, device=''):
self.get_config.return_value = load_fixture('nxos_vxlan_vtep_vni', 'config.cfg')
self.load_config.return_value = None
def test_nxos_vxlan_vtep_vni_present_no_change(self):
set_module_args(dict(interface='nve1', vni=6000))
self.execute_module(changed=False, commands=[])
def test_nxos_vxlan_vtep_vni(self):
set_module_args(dict(interface='nve1', vni=5000))
self.execute_module(changed=True, commands=['interface nve1', 'member vni 5000'])
def test_nxos_vxlan_vtep_vni_absent(self):
set_module_args(dict(interface='nve1', vni=6000, state='absent'))
self.execute_module(changed=True, commands=['interface nve1', 'no member vni 6000'])
def test_nxos_vxlan_vtep_vni_absent_no_change(self):
set_module_args(dict(interface='nve2', vni=6000, state='absent'))
self.execute_module(changed=False, commands=[])