Fixes nxos_vpc_interface (#25907)

* nxos_vpc_interface fix

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* nxos_vpc_interface unit test

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* fix state_present

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* add state_present unit test

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* make ansibot happy

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* teardown get_config

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>
pull/4420/head
Trishna Guha 2017-06-24 18:05:07 +05:30 committed by GitHub
parent b82cc8e7cc
commit 3482a6326b
5 changed files with 194 additions and 144 deletions

View File

@ -16,10 +16,10 @@
# 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,92 +28,57 @@ extends_documentation_fragment: nxos
version_added: "2.2" version_added: "2.2"
short_description: Manages interface VPC configuration short_description: Manages interface VPC configuration
description: description:
- Manages interface VPC configuration - Manages interface VPC configuration
author: author:
- Jason Edelman (@jedelman8) - Jason Edelman (@jedelman8)
- Gabriele Gerbino (@GGabriele) - Gabriele Gerbino (@GGabriele)
notes: notes:
- Either vpc or peer_link param is required, but not both. - Either vpc or peer_link param is required, but not both.
- C(state=absent) removes whatever VPC config is on a port-channel - C(state=absent) removes whatever VPC config is on a port-channel
if one exists. if one exists.
- Re-assigning a vpc or peerlink from one portchannel to another is not - Re-assigning a vpc or peerlink from one portchannel to another is not
supported. The module will force the user to unconfigure an existing supported. The module will force the user to unconfigure an existing
vpc/pl before configuring the same value on a new portchannel vpc/pl before configuring the same value on a new portchannel
options: options:
portchannel: portchannel:
description: description:
- Group number of the portchannel that will be configured. - Group number of the portchannel that will be configured.
required: true required: true
vpc: vpc:
description: description:
- VPC group/id that will be configured on associated portchannel. - VPC group/id that will be configured on associated portchannel.
required: false required: false
default: null default: null
peer_link: peer_link:
description: description:
- Set to true/false for peer link config on associated portchannel. - Set to true/false for peer link config on associated portchannel.
required: false required: false
default: null default: null
state: state:
description: description:
- Manages desired state of the resource. - Manages desired state of the resource.
required: true required: true
choices: ['present','absent'] choices: ['present','absent']
''' '''
EXAMPLES = ''' EXAMPLES = '''
- nxos_vpc_portchannel: - nxos_vpc_interface:
portchannel: 10 portchannel: 10
vpc: 100 vpc: 100
username: "{{ un }}"
password: "{{ pwd }}"
host: "{{ inventory_hostname }}"
''' '''
RETURN = ''' RETURN = '''
proposed: commands:
description: k/v pairs of parameters passed into module
returned: always
type: dict
sample: {"portchannel": "100", "vpc": "10"}
existing:
description: k/v pairs of existing configuration
returned: always
type: dict
sample: {}
end_state:
description: k/v pairs of configuration after module execution
returned: always
type: dict
sample: {"peer-link": false, "portchannel": "100", "vpc": "10"}
updates:
description: commands sent to the device description: commands sent to the device
returned: always returned: always
type: list type: list
sample: ["interface port-channel100", "vpc 10"] sample: ["interface port-channel100", "vpc 10"]
changed:
description: check to see if a change was made on the device
returned: always
type: boolean
sample: true
''' '''
from ansible.module_utils.nxos import get_config, load_config, run_commands from ansible.module_utils.nxos import get_config, load_config, run_commands
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
def execute_show_command(command, module, command_type='cli_show'):
if module.params['transport'] == 'cli':
command += ' | json'
cmds = [command]
body = run_commands(module, cmds)
elif module.params['transport'] == 'nxapi':
cmds = [command]
body = run_commands(module, cmds)
return body
def flatten_list(command_lists): def flatten_list(command_lists):
flat_command_list = [] flat_command_list = []
@ -126,15 +91,13 @@ def flatten_list(command_lists):
def get_portchannel_list(module): def get_portchannel_list(module):
command = 'show port-channel summary'
portchannels = [] portchannels = []
pc_list = [] pc_list = []
body = execute_show_command(command, module)
try: try:
pc_list = body[0]['TABLE_channel']['ROW_channel'] body = run_commands(module, ['show port-channel summary | json'])[0]
except (KeyError, AttributeError): pc_list = body['TABLE_channel']['ROW_channel']
except (KeyError, AttributeError, TypeError):
return portchannels return portchannels
if pc_list: if pc_list:
@ -148,13 +111,11 @@ def get_portchannel_list(module):
def get_existing_portchannel_to_vpc_mappings(module): def get_existing_portchannel_to_vpc_mappings(module):
command = 'show vpc brief'
pc_vpc_mapping = {} pc_vpc_mapping = {}
body = execute_show_command(command, module)
try: try:
vpc_table = body[0]['TABLE_vpc']['ROW_vpc'] body = run_commands(module, ['show vpc brief | json'])[0]
vpc_table = body['TABLE_vpc']['ROW_vpc']
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
vpc_table = None vpc_table = None
@ -170,7 +131,7 @@ def get_existing_portchannel_to_vpc_mappings(module):
def peer_link_exists(module): def peer_link_exists(module):
found = False found = False
run = get_vpc_running_config(module) run = get_config(module, flags=['section vpc'])
vpc_list = run.split('\n') vpc_list = run.split('\n')
for each in vpc_list: for each in vpc_list:
@ -179,38 +140,28 @@ def peer_link_exists(module):
return found return found
def get_vpc_running_config(module):
command = 'show running section vpc'
body = execute_show_command(command, module,
command_type='cli_show_ascii')[0]
return body
def get_active_vpc_peer_link(module): def get_active_vpc_peer_link(module):
command = 'show vpc brief'
peer_link = None peer_link = None
body = execute_show_command(command, module)
try: try:
peer_link = body[0]['TABLE_peerlink']['ROW_peerlink']['peerlink-ifindex'] body = run_commands(module, ['show vpc brief | json'])[0]
except (KeyError, AttributeError): peer_link = body['TABLE_peerlink']['ROW_peerlink']['peerlink-ifindex']
except (KeyError, AttributeError, TypeError):
return peer_link return peer_link
return peer_link return peer_link
def get_portchannel_vpc_config(module, portchannel): def get_portchannel_vpc_config(module, portchannel):
command = 'show vpc brief'
peer_link_pc = None peer_link_pc = None
peer_link = False peer_link = False
vpc = "" vpc = ""
pc = "" pc = ""
config = {} config = {}
body = execute_show_command(command, module)
try: try:
table = body[0]['TABLE_peerlink']['ROW_peerlink'] body = run_commands(module, ['show vpc brief | json'])[0]
table = body['TABLE_peerlink']['ROW_peerlink']
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
table = {} table = {}
@ -255,33 +206,58 @@ def get_commands_to_config_vpc_interface(portchannel, delta, config_value, exist
return commands return commands
def state_present(portchannel, delta, config_value, existing):
commands = []
command = get_commands_to_config_vpc_interface(
portchannel,
delta,
config_value,
existing
)
commands.append(command)
return commands
def state_absent(portchannel, existing):
commands = []
if existing.get('vpc'):
command = 'no vpc'
commands.append(command)
elif existing.get('peer-link'):
command = 'no vpc peer-link'
commands.append(command)
if commands:
commands.insert(0, 'interface port-channel{0}'.format(portchannel))
return commands
def main(): def main():
argument_spec = dict( argument_spec = dict(
portchannel=dict(required=True, type='str'), portchannel=dict(required=True, type='str'),
vpc=dict(required=False, type='str'), vpc=dict(required=False, type='str'),
peer_link=dict(required=False, type='bool'), peer_link=dict(required=False, type='bool'),
state=dict(choices=['absent', 'present'], default='present'), state=dict(choices=['absent', 'present'], default='present')
include_defaults=dict(default=False),
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,
mutually_exclusive=[['vpc', 'peer_link']], mutually_exclusive=[['vpc', 'peer_link']],
supports_check_mode=True) supports_check_mode=True)
warnings = list() warnings = list()
commands = []
check_args(module, warnings) check_args(module, warnings)
results = {'changed': False, 'warnings': warnings}
portchannel = module.params['portchannel'] portchannel = module.params['portchannel']
vpc = module.params['vpc'] vpc = module.params['vpc']
peer_link = module.params['peer_link'] peer_link = module.params['peer_link']
state = module.params['state'] state = module.params['state']
changed = False
args = {'portchannel': portchannel, 'vpc': vpc, 'peer-link': peer_link} args = {'portchannel': portchannel, 'vpc': vpc, 'peer-link': peer_link}
active_peer_link = None active_peer_link = None
@ -294,14 +270,14 @@ def main():
if vpc in mapping and portchannel != mapping[vpc].strip('Po'): if vpc in mapping and portchannel != mapping[vpc].strip('Po'):
module.fail_json(msg="This vpc is already configured on " module.fail_json(msg="This vpc is already configured on "
"another portchannel. Remove it first " "another portchannel. Remove it first "
"before trying to assign it here. ", "before trying to assign it here. ",
existing_portchannel=mapping[vpc]) existing_portchannel=mapping[vpc])
for vpcid, existing_pc in mapping.items(): for vpcid, existing_pc in mapping.items():
if portchannel == existing_pc.strip('Po') and vpcid != vpc: if portchannel == existing_pc.strip('Po') and vpcid != vpc:
module.fail_json(msg="This portchannel already has another" module.fail_json(msg="This portchannel already has another"
" VPC configured. Remove it first " " VPC configured. Remove it first "
"before assigning this one", "before assigning this one",
existing_vpc=vpcid) existing_vpc=vpcid)
@ -309,7 +285,7 @@ def main():
active_peer_link = get_active_vpc_peer_link(module) active_peer_link = get_active_vpc_peer_link(module)
if active_peer_link[-2:] == portchannel: if active_peer_link[-2:] == portchannel:
module.fail_json(msg="That port channel is the current " module.fail_json(msg="That port channel is the current "
"PEER LINK. Remove it if you want it" "PEER LINK. Remove it if you want it"
" to be a VPC") " to be a VPC")
config_value = vpc config_value = vpc
@ -319,64 +295,34 @@ def main():
if active_peer_link != portchannel: if active_peer_link != portchannel:
if peer_link: if peer_link:
module.fail_json(msg="A peer link already exists on" module.fail_json(msg="A peer link already exists on"
" the device. Remove it first", " the device. Remove it first",
current_peer_link='Po{0}'.format( current_peer_link='Po{0}'.format(active_peer_link))
active_peer_link))
config_value = 'peer-link' config_value = 'peer-link'
proposed = dict((k, v) for k, v in args.items() if v is not None) proposed = dict((k, v) for k, v in args.items() if v is not None)
existing = get_portchannel_vpc_config(module, portchannel) existing = get_portchannel_vpc_config(module, portchannel)
end_state = existing
commands = []
if state == 'present': if state == 'present':
delta = dict(set(proposed.items()).difference(existing.items())) delta = dict(set(proposed.items()).difference(existing.items()))
if delta: if delta:
command = get_commands_to_config_vpc_interface( commands = state_present(portchannel, delta, config_value, existing)
portchannel,
delta,
config_value,
existing
)
commands.append(command)
elif state == 'absent': elif state == 'absent' and existing:
if existing.get('vpc'): commands = state_absent(portchannel, existing)
command = ['no vpc']
commands.append(command)
elif existing.get('peer-link'):
command = ['no vpc peer-link']
commands.append(command)
if commands:
commands.insert(0, ['interface port-channel{0}'.format(portchannel)])
cmds = flatten_list(commands) cmds = flatten_list(commands)
if cmds: if cmds:
if module.check_mode: if module.check_mode:
module.exit_json(changed=True, commands=cmds) module.exit_json(changed=True, commands=cmds)
else: else:
changed = True
load_config(module, cmds) load_config(module, cmds)
if module.params['transport'] == 'cli': results['changed'] = True
output = ' '.join(output)
if 'error' in output.lower():
module.fail_json(msg=output.replace('\n', ''))
end_state = get_portchannel_vpc_config(module, portchannel)
if 'configure' in cmds: if 'configure' in cmds:
cmds.pop(0) cmds.pop(0)
results = {} results['commands'] = cmds
results['proposed'] = proposed
results['existing'] = existing
results['end_state'] = end_state
results['updates'] = cmds
results['changed'] = changed
results['warnings'] = warnings
module.exit_json(**results) module.exit_json(**results)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -389,7 +389,6 @@ lib/ansible/modules/network/nxos/nxos_system.py
lib/ansible/modules/network/nxos/nxos_udld.py lib/ansible/modules/network/nxos/nxos_udld.py
lib/ansible/modules/network/nxos/nxos_udld_interface.py lib/ansible/modules/network/nxos/nxos_udld_interface.py
lib/ansible/modules/network/nxos/nxos_user.py lib/ansible/modules/network/nxos/nxos_user.py
lib/ansible/modules/network/nxos/nxos_vpc_interface.py
lib/ansible/modules/network/nxos/nxos_vrf.py lib/ansible/modules/network/nxos/nxos_vrf.py
lib/ansible/modules/network/nxos/nxos_vrf_interface.py lib/ansible/modules/network/nxos/nxos_vrf_interface.py
lib/ansible/modules/network/nxos/nxos_vrrp.py lib/ansible/modules/network/nxos/nxos_vrrp.py

View File

@ -0,0 +1,22 @@
{
"TABLE_channel": {
"ROW_channel": [
{
"group": "10",
"port-channel": "port-channel10",
"layer": "R",
"status": "D",
"type": "Eth",
"prtcl": "NONE"
},
{
"group": "20",
"port-channel": "port-channel20",
"layer": "R",
"status": "D",
"type": "Eth",
"prtcl": "NONE"
}
]
}
}

View File

@ -0,0 +1,14 @@
{
"TABLE_vpc": {
"ROW_vpc": [
{
"vpc-id": 100,
"vpc-ifindex": "Po10",
"vpc-port-state": "Up",
"vpc-thru-peerlink": 19,
"vpc-consistency": "consistent",
"vpc-consistency-status": "SUCCESS"
}
]
}
}

View File

@ -0,0 +1,69 @@
# (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 os
from ansible.compat.tests.mock import patch
from ansible.modules.network.nxos import nxos_vpc_interface
from .nxos_module import TestNxosModule, load_fixture, set_module_args
class TestNxosVpcModule(TestNxosModule):
module = nxos_vpc_interface
def setUp(self):
self.mock_load_config = patch('ansible.modules.network.nxos.nxos_vpc_interface.load_config')
self.load_config = self.mock_load_config.start()
self.mock_get_config = patch('ansible.modules.network.nxos.nxos_vpc_interface.get_config')
self.get_config = self.mock_get_config.start()
self.mock_run_commands = patch('ansible.modules.network.nxos.nxos_vpc_interface.run_commands')
self.run_commands = self.mock_run_commands.start()
def tearDown(self):
self.mock_load_config.stop()
self.mock_get_config.stop()
self.mock_run_commands.stop()
def load_fixtures(self, commands=None):
def load_from_file(*args, **kwargs):
module, commands = args
output = list()
for command in commands:
filename = str(command).split(' | ')[0].replace(' ', '_')
filename = os.path.join('nxos_vpc_interface', filename)
output.append(load_fixture(filename))
return output
self.run_commands.side_effect = load_from_file
self.load_config.return_value = None
def test_nxos_vpc_interface_absent(self):
set_module_args(dict(portchannel=10, vpc=100, state='absent'))
result = self.execute_module(changed=True)
self.assertEqual(result['commands'], ['interface port-channel10', 'no vpc'])
def test_nxos_vpc_interface_present(self):
set_module_args(dict(portchannel=20, vpc=200, state='present'))
result = self.execute_module(changed=True)
self.assertEqual(result['commands'], ['interface port-channel20', 'vpc 200'])