mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-05-03 15:51:30 -07:00
This completes the refactor of the nxos modules to use the persistent connection. It also updates all of the nxos modules to use the new connection module and preserves use of nxapi as well.
380 lines
12 KiB
Python
380 lines
12 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# 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/>.
|
|
#
|
|
|
|
ANSIBLE_METADATA = {'status': ['preview'],
|
|
'supported_by': 'community',
|
|
'version': '1.0'}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: nxos_vpc_interface
|
|
version_added: "2.2"
|
|
short_description: Manages interface VPC configuration
|
|
description:
|
|
- Manages interface VPC configuration
|
|
author:
|
|
- Jason Edelman (@jedelman8)
|
|
- Gabriele Gerbino (@GGabriele)
|
|
notes:
|
|
- Either vpc or peer_link param is required, but not both.
|
|
- C(state=absent) removes whatever VPC config is on a port-channel
|
|
if one exists.
|
|
- Re-assigning a vpc or peerlink from one portchannel to another is not
|
|
supported. The module will force the user to unconfigure an existing
|
|
vpc/pl before configuring the same value on a new portchannel
|
|
options:
|
|
portchannel:
|
|
description:
|
|
- Group number of the portchannel that will be configured.
|
|
required: true
|
|
vpc:
|
|
description:
|
|
- VPC group/id that will be configured on associated portchannel.
|
|
required: false
|
|
default: null
|
|
peer_link:
|
|
description:
|
|
- Set to true/false for peer link config on associated portchannel.
|
|
required: false
|
|
default: null
|
|
state:
|
|
description:
|
|
- Manages desired state of the resource.
|
|
required: true
|
|
choices: ['present','absent']
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- nxos_vpc_portchannel:
|
|
portchannel: 10
|
|
vpc: 100
|
|
username: "{{ un }}"
|
|
password: "{{ pwd }}"
|
|
host: "{{ inventory_hostname }}"
|
|
'''
|
|
|
|
RETURN = '''
|
|
proposed:
|
|
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
|
|
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
|
|
returned: always
|
|
type: list
|
|
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 nxos_argument_spec, check_args
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.netcfg import CustomNetworkConfig
|
|
|
|
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):
|
|
flat_command_list = []
|
|
for command in command_lists:
|
|
if isinstance(command, list):
|
|
flat_command_list.extend(command)
|
|
else:
|
|
flat_command_list.append(command)
|
|
return flat_command_list
|
|
|
|
|
|
def get_portchannel_list(module):
|
|
command = 'show port-channel summary'
|
|
portchannels = []
|
|
pc_list = []
|
|
|
|
body = execute_show_command(command, module)
|
|
|
|
try:
|
|
pc_list = body[0]['TABLE_channel']['ROW_channel']
|
|
except (KeyError, AttributeError):
|
|
return portchannels
|
|
|
|
if pc_list:
|
|
if isinstance(pc_list, dict):
|
|
pc_list = [pc_list]
|
|
|
|
for pc in pc_list:
|
|
portchannels.append(pc['group'])
|
|
|
|
return portchannels
|
|
|
|
|
|
def get_existing_portchannel_to_vpc_mappings(module):
|
|
command = 'show vpc brief'
|
|
pc_vpc_mapping = {}
|
|
|
|
body = execute_show_command(command, module)
|
|
|
|
try:
|
|
vpc_table = body[0]['TABLE_vpc']['ROW_vpc']
|
|
except (KeyError, AttributeError, TypeError):
|
|
vpc_table = None
|
|
|
|
if vpc_table:
|
|
if isinstance(vpc_table, dict):
|
|
vpc_table = [vpc_table]
|
|
|
|
for vpc in vpc_table:
|
|
pc_vpc_mapping[str(vpc['vpc-id'])] = str(vpc['vpc-ifindex'])
|
|
|
|
return pc_vpc_mapping
|
|
|
|
|
|
def peer_link_exists(module):
|
|
found = False
|
|
run = get_vpc_running_config(module)
|
|
|
|
vpc_list = run.split('\n')
|
|
for each in vpc_list:
|
|
if 'peer-link' in each:
|
|
found = True
|
|
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):
|
|
command = 'show vpc brief'
|
|
peer_link = None
|
|
body = execute_show_command(command, module)
|
|
try:
|
|
peer_link = body[0]['TABLE_peerlink']['ROW_peerlink']['peerlink-ifindex']
|
|
except (KeyError, AttributeError):
|
|
return peer_link
|
|
|
|
return peer_link
|
|
|
|
|
|
def get_portchannel_vpc_config(module, portchannel):
|
|
command = 'show vpc brief'
|
|
peer_link_pc = None
|
|
peer_link = False
|
|
vpc = ""
|
|
pc = ""
|
|
config = {}
|
|
|
|
body = execute_show_command(command, module)
|
|
|
|
try:
|
|
table = body[0]['TABLE_peerlink']['ROW_peerlink']
|
|
except (KeyError, AttributeError, TypeError):
|
|
table = {}
|
|
|
|
if table:
|
|
peer_link_pc = table.get('peerlink-ifindex', None)
|
|
|
|
if peer_link_pc:
|
|
plpc = str(peer_link_pc[2:])
|
|
if portchannel == plpc:
|
|
config['portchannel'] = portchannel
|
|
config['peer-link'] = True
|
|
config['vpc'] = vpc
|
|
|
|
mapping = get_existing_portchannel_to_vpc_mappings(module)
|
|
|
|
for existing_vpc, port_channel in mapping.items():
|
|
port_ch = str(port_channel[2:])
|
|
if port_ch == portchannel:
|
|
pc = port_ch
|
|
vpc = str(existing_vpc)
|
|
|
|
config['portchannel'] = pc
|
|
config['peer-link'] = peer_link
|
|
config['vpc'] = vpc
|
|
|
|
return config
|
|
|
|
|
|
def get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing):
|
|
commands = []
|
|
|
|
if delta.get('peer-link') is False and existing.get('peer-link') is True:
|
|
command = 'no vpc peer-link'
|
|
commands.append('no vpc peer-link')
|
|
commands.insert(0, 'interface port-channel{0}'.format(portchannel))
|
|
|
|
elif delta.get('peer-link') or not existing.get('vpc'):
|
|
command = 'vpc {0}'.format(config_value)
|
|
commands.append(command)
|
|
commands.insert(0, 'interface port-channel{0}'.format(portchannel))
|
|
|
|
return commands
|
|
|
|
|
|
def main():
|
|
argument_spec = dict(
|
|
portchannel=dict(required=True, type='str'),
|
|
vpc=dict(required=False, type='str'),
|
|
peer_link=dict(required=False, type='bool'),
|
|
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)
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec,
|
|
mutually_exclusive=[['vpc', 'peer_link']],
|
|
supports_check_mode=True)
|
|
|
|
warnings = list()
|
|
check_args(module, warnings)
|
|
|
|
|
|
portchannel = module.params['portchannel']
|
|
vpc = module.params['vpc']
|
|
peer_link = module.params['peer_link']
|
|
state = module.params['state']
|
|
|
|
changed = False
|
|
args = {'portchannel': portchannel, 'vpc': vpc, 'peer-link': peer_link}
|
|
active_peer_link = None
|
|
|
|
if portchannel not in get_portchannel_list(module):
|
|
module.fail_json(msg="The portchannel you are trying to make a"
|
|
" VPC or PL is not created yet. "
|
|
"Create it first!")
|
|
if vpc:
|
|
mapping = get_existing_portchannel_to_vpc_mappings(module)
|
|
|
|
if vpc in mapping and portchannel != mapping[vpc].strip('Po'):
|
|
module.fail_json(msg="This vpc is already configured on "
|
|
"another portchannel. Remove it first "
|
|
"before trying to assign it here. ",
|
|
existing_portchannel=mapping[vpc])
|
|
|
|
for vpcid, existing_pc in mapping.items():
|
|
if portchannel == existing_pc.strip('Po') and vpcid != vpc:
|
|
module.fail_json(msg="This portchannel already has another"
|
|
" VPC configured. Remove it first "
|
|
"before assigning this one",
|
|
existing_vpc=vpcid)
|
|
|
|
if peer_link_exists(module):
|
|
active_peer_link = get_active_vpc_peer_link(module)
|
|
if active_peer_link[-2:] == portchannel:
|
|
module.fail_json(msg="That port channel is the current "
|
|
"PEER LINK. Remove it if you want it"
|
|
" to be a VPC")
|
|
config_value = vpc
|
|
|
|
elif peer_link is not None:
|
|
if peer_link_exists(module):
|
|
active_peer_link = get_active_vpc_peer_link(module)[2::]
|
|
if active_peer_link != portchannel:
|
|
if peer_link:
|
|
module.fail_json(msg="A peer link already exists on"
|
|
" the device. Remove it first",
|
|
current_peer_link='Po{0}'.format(
|
|
active_peer_link))
|
|
config_value = 'peer-link'
|
|
|
|
|
|
proposed = dict((k, v) for k, v in args.items() if v is not None)
|
|
existing = get_portchannel_vpc_config(module, portchannel)
|
|
end_state = existing
|
|
commands = []
|
|
|
|
if state == 'present':
|
|
delta = dict(set(proposed.items()).difference(existing.items()))
|
|
if delta:
|
|
command = get_commands_to_config_vpc_interface(
|
|
portchannel,
|
|
delta,
|
|
config_value,
|
|
existing
|
|
)
|
|
commands.append(command)
|
|
|
|
elif state == 'absent':
|
|
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)])
|
|
|
|
cmds = flatten_list(commands)
|
|
if cmds:
|
|
if module.check_mode:
|
|
module.exit_json(changed=True, commands=cmds)
|
|
else:
|
|
changed = True
|
|
load_config(module, cmds)
|
|
if module.params['transport'] == 'cli':
|
|
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:
|
|
cmds.pop(0)
|
|
|
|
results = {}
|
|
results['proposed'] = proposed
|
|
results['existing'] = existing
|
|
results['end_state'] = end_state
|
|
results['updates'] = cmds
|
|
results['changed'] = changed
|
|
results['warnings'] = warnings
|
|
|
|
module.exit_json(**results)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|