meraki_switchport - Improve reliability (#54275)
* Rewrite much of the execution of meraki_switchport - Previous versions had problems with idempotency and allowed_vlans * Modified payload creation - Parameter map is used - propsed is created using .copy() - Much cleaner this way * Add whitespace for lint * Add bugfix snippet for changelog
This commit is contained in:
parent
4b0014166b
commit
d59eb9edab
3 changed files with 98 additions and 70 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- "meraki_switchport - improve reliability with native VLAN functionality."
|
|
@ -245,34 +245,32 @@ from ansible.module_utils.urls import fetch_url
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
|
||||||
|
|
||||||
# param_map isn't used but leaving in as it is a more efficient method for implementation later
|
param_map = {'access_policy_number': 'accessPolicyNumber',
|
||||||
param_map = {'accessPolicyNumber': 'access_policy_number',
|
'allowed_vlans': 'allowedVlans',
|
||||||
'allowedVlans': 'allowed_vlans',
|
|
||||||
'enabled': 'enabled',
|
'enabled': 'enabled',
|
||||||
'isolationEnabled': 'isolation_enabled',
|
'isolation_enabled': 'isolationEnabled',
|
||||||
'linkNegotiation': 'link_negotiation',
|
'link_negotiation': 'linkNegotiation',
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'number': 'number',
|
'number': 'number',
|
||||||
'poeEnabled': 'poe_enabled',
|
'poe_enabled': 'poeEnabled',
|
||||||
'rstpEnabled': 'rstp_enabled',
|
'rstp_enabled': 'rstpEnabled',
|
||||||
'stpGuard': 'stp_guard',
|
'stp_guard': 'stpGuard',
|
||||||
'tags': 'tags',
|
'tags': 'tags',
|
||||||
'type': 'type',
|
'type': 'type',
|
||||||
'vlan': 'vlan',
|
'vlan': 'vlan',
|
||||||
'voiceVlan': 'voice_vlan',
|
'voice_vlan': 'voiceVlan',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def list_to_csv(items):
|
def sort_vlans(meraki, vlans):
|
||||||
csv = ''
|
converted = set()
|
||||||
count = 0
|
for vlan in vlans:
|
||||||
for item in items:
|
converted.add(int(vlan))
|
||||||
if count == len(items) - 1:
|
vlans_sorted = sorted(converted)
|
||||||
csv = csv + str(item)
|
vlans_str = []
|
||||||
else:
|
for vlan in vlans_sorted:
|
||||||
csv = csv + str(item) + ','
|
vlans_str.append(str(vlan))
|
||||||
count += 1
|
return ','.join(vlans_str)
|
||||||
return csv
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -309,18 +307,13 @@ def main():
|
||||||
meraki = MerakiModule(module, function='switchport')
|
meraki = MerakiModule(module, function='switchport')
|
||||||
meraki.params['follow_redirects'] = 'all'
|
meraki.params['follow_redirects'] = 'all'
|
||||||
|
|
||||||
# argument checks
|
|
||||||
# if meraki.params['type'] == 'access':
|
|
||||||
# if meraki.params['allowed_vlans']:
|
|
||||||
# meraki.fail_json(msg='allowed_vlans is not allowed on access ports')
|
|
||||||
if meraki.params['type'] == 'trunk':
|
if meraki.params['type'] == 'trunk':
|
||||||
if not meraki.params['allowed_vlans']:
|
if not meraki.params['allowed_vlans']:
|
||||||
meraki.params['allowed_vlans'] = ['all'] # Backdoor way to set default without conflicting on access
|
meraki.params['allowed_vlans'] = ['all'] # Backdoor way to set default without conflicting on access
|
||||||
|
|
||||||
# TODO: Add support for serial in the construct_path() method
|
query_urls = {'switchport': '/devices/{serial}/switchPorts'}
|
||||||
query_urls = {'switchport': '/devices/serial/switchPorts'}
|
query_url = {'switchport': '/devices/{serial}/switchPorts/{number}'}
|
||||||
query_url = {'switchport': '/devices/serial/switchPorts/'}
|
update_url = {'switchport': '/devices/{serial}/switchPorts/{number}'}
|
||||||
update_url = {'switchport': '/devices/serial/switchPorts/'}
|
|
||||||
|
|
||||||
meraki.url_catalog['get_all'].update(query_urls)
|
meraki.url_catalog['get_all'].update(query_urls)
|
||||||
meraki.url_catalog['get_one'].update(query_url)
|
meraki.url_catalog['get_one'].update(query_url)
|
||||||
|
@ -341,64 +334,53 @@ def main():
|
||||||
# part where your module will do what it needs to do)
|
# part where your module will do what it needs to do)
|
||||||
if meraki.params['state'] == 'query':
|
if meraki.params['state'] == 'query':
|
||||||
if meraki.params['number']:
|
if meraki.params['number']:
|
||||||
path = meraki.construct_path('get_one') + meraki.params['number']
|
path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial'],
|
||||||
path = path.replace('serial', meraki.params['serial'])
|
'number': meraki.params['number'],
|
||||||
|
})
|
||||||
response = meraki.request(path, method='GET')
|
response = meraki.request(path, method='GET')
|
||||||
meraki.result['data'] = response
|
meraki.result['data'] = response
|
||||||
else:
|
else:
|
||||||
path = meraki.construct_path('get_all')
|
path = meraki.construct_path('get_all', custom={'serial': meraki.params['serial']})
|
||||||
path = path.replace('serial', meraki.params['serial'])
|
|
||||||
response = meraki.request(path, method='GET')
|
response = meraki.request(path, method='GET')
|
||||||
meraki.result['data'] = response
|
meraki.result['data'] = response
|
||||||
elif meraki.params['state'] == 'present':
|
elif meraki.params['state'] == 'present':
|
||||||
payload = dict()
|
payload = dict()
|
||||||
proposed = dict()
|
|
||||||
|
|
||||||
# if meraki.params['type'] == 'access':
|
for k, v in meraki.params.items():
|
||||||
payload['name'] = meraki.params['name']
|
try:
|
||||||
payload['tags'] = meraki.params['tags']
|
payload[param_map[k]] = v
|
||||||
payload['enabled'] = meraki.params['enabled']
|
except KeyError:
|
||||||
payload['poeEnabled'] = meraki.params['poe_enabled']
|
pass
|
||||||
payload['type'] = meraki.params['type']
|
|
||||||
payload['vlan'] = meraki.params['vlan']
|
allowed = set() # Use a set to remove duplicate items
|
||||||
payload['voiceVlan'] = meraki.params['voice_vlan']
|
if meraki.params['allowed_vlans'][0] == 'all':
|
||||||
payload['isolationEnabled'] = meraki.params['isolation_enabled']
|
allowed.add('all')
|
||||||
payload['rstpEnabled'] = meraki.params['rstp_enabled']
|
else:
|
||||||
payload['stpGuard'] = meraki.params['stp_guard']
|
for vlan in meraki.params['allowed_vlans']:
|
||||||
payload['accessPolicyNumber'] = meraki.params['access_policy_number']
|
allowed.add(str(vlan))
|
||||||
payload['linkNegotiation'] = meraki.params['link_negotiation']
|
if meraki.params['vlan'] is not None:
|
||||||
payload['allowedVlans'] = list_to_csv(meraki.params['allowed_vlans'])
|
allowed.add(str(meraki.params['vlan']))
|
||||||
proposed['name'] = meraki.params['name']
|
if len(allowed) > 1: # Convert from list to comma separated
|
||||||
proposed['tags'] = meraki.params['tags']
|
payload['allowedVlans'] = sort_vlans(meraki, allowed)
|
||||||
proposed['enabled'] = meraki.params['enabled']
|
else:
|
||||||
proposed['poeEnabled'] = meraki.params['poe_enabled']
|
payload['allowedVlans'] = next(iter(allowed))
|
||||||
proposed['type'] = meraki.params['type']
|
|
||||||
proposed['vlan'] = meraki.params['vlan']
|
|
||||||
proposed['voiceVlan'] = meraki.params['voice_vlan']
|
|
||||||
proposed['isolationEnabled'] = meraki.params['isolation_enabled']
|
|
||||||
proposed['rstpEnabled'] = meraki.params['rstp_enabled']
|
|
||||||
proposed['stpGuard'] = meraki.params['stp_guard']
|
|
||||||
proposed['accessPolicyNumber'] = meraki.params['access_policy_number']
|
|
||||||
proposed['linkNegotiation'] = meraki.params['link_negotiation']
|
|
||||||
proposed['allowedVlans'] = list_to_csv(meraki.params['allowed_vlans'])
|
|
||||||
|
|
||||||
# Exceptions need to be made for idempotency check based on how Meraki returns
|
# Exceptions need to be made for idempotency check based on how Meraki returns
|
||||||
if meraki.params['type'] == 'trunk':
|
if meraki.params['type'] == 'access':
|
||||||
if meraki.params['vlan'] and meraki.params['allowed_vlans'] != 'all':
|
|
||||||
proposed['allowedVlans'] = str(meraki.params['vlan']) + ',' + proposed['allowedVlans']
|
|
||||||
else:
|
|
||||||
if not meraki.params['vlan']: # VLAN needs to be specified in access ports, but can't default to it
|
if not meraki.params['vlan']: # VLAN needs to be specified in access ports, but can't default to it
|
||||||
payload['vlan'] = 1
|
payload['vlan'] = 1
|
||||||
proposed['vlan'] = 1
|
|
||||||
|
|
||||||
query_path = meraki.construct_path('get_one') + meraki.params['number']
|
proposed = payload.copy()
|
||||||
query_path = query_path.replace('serial', meraki.params['serial'])
|
query_path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial'],
|
||||||
|
'number': meraki.params['number'],
|
||||||
|
})
|
||||||
original = meraki.request(query_path, method='GET')
|
original = meraki.request(query_path, method='GET')
|
||||||
if meraki.params['type'] == 'trunk':
|
if meraki.params['type'] == 'trunk':
|
||||||
proposed['voiceVlan'] = original['voiceVlan'] # API shouldn't include voice VLAN on a trunk port
|
proposed['voiceVlan'] = original['voiceVlan'] # API shouldn't include voice VLAN on a trunk port
|
||||||
if meraki.is_update_required(original, proposed, optional_ignore=('number')):
|
if meraki.is_update_required(original, proposed, optional_ignore=('number')):
|
||||||
path = meraki.construct_path('update') + meraki.params['number']
|
path = meraki.construct_path('update', custom={'serial': meraki.params['serial'],
|
||||||
path = path.replace('serial', meraki.params['serial'])
|
'number': meraki.params['number'],
|
||||||
|
})
|
||||||
response = meraki.request(path, method='PUT', payload=json.dumps(payload))
|
response = meraki.request(path, method='PUT', payload=json.dumps(payload))
|
||||||
meraki.result['data'] = response
|
meraki.result['data'] = response
|
||||||
meraki.result['changed'] = True
|
meraki.result['changed'] = True
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
auth_key: '{{ auth_key }}'
|
auth_key: '{{ auth_key }}'
|
||||||
use_https: false
|
use_https: false
|
||||||
state: query
|
state: query
|
||||||
serial: Q2HP-2C6E-
|
serial: Q2HP-2C6E-GTLD
|
||||||
output_level: debug
|
output_level: debug
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
register: http
|
register: http
|
||||||
|
@ -132,6 +132,48 @@
|
||||||
that:
|
that:
|
||||||
- update_access_port.data.vlan == 10
|
- update_access_port.data.vlan == 10
|
||||||
|
|
||||||
|
- name: Configure port as trunk
|
||||||
|
meraki_switchport:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
state: present
|
||||||
|
serial: Q2HP-2C6E-GTLD
|
||||||
|
number: 8
|
||||||
|
enabled: true
|
||||||
|
name: Test Port
|
||||||
|
type: trunk
|
||||||
|
vlan: 10
|
||||||
|
allowed_vlans: 10, 100, 200
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Convert trunk port to access
|
||||||
|
meraki_switchport:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
state: present
|
||||||
|
serial: Q2HP-2C6E-GTLD
|
||||||
|
number: 8
|
||||||
|
enabled: true
|
||||||
|
name: Test Port
|
||||||
|
type: access
|
||||||
|
vlan: 10
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Test converted port for idempotency
|
||||||
|
meraki_switchport:
|
||||||
|
auth_key: '{{auth_key}}'
|
||||||
|
state: present
|
||||||
|
serial: Q2HP-2C6E-GTLD
|
||||||
|
number: 8
|
||||||
|
enabled: true
|
||||||
|
name: Test Port
|
||||||
|
type: access
|
||||||
|
vlan: 10
|
||||||
|
delegate_to: localhost
|
||||||
|
register: convert_idempotent
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- convert_idempotent.changed == False
|
||||||
|
|
||||||
- name: Configure access port with voice VLAN
|
- name: Configure access port with voice VLAN
|
||||||
meraki_switchport:
|
meraki_switchport:
|
||||||
auth_key: '{{auth_key}}'
|
auth_key: '{{auth_key}}'
|
||||||
|
@ -188,6 +230,7 @@
|
||||||
tags: server
|
tags: server
|
||||||
type: trunk
|
type: trunk
|
||||||
allowed_vlans: all
|
allowed_vlans: all
|
||||||
|
vlan: 8
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
register: update_trunk
|
register: update_trunk
|
||||||
|
|
||||||
|
@ -210,6 +253,7 @@
|
||||||
name: Server port
|
name: Server port
|
||||||
tags: server
|
tags: server
|
||||||
type: trunk
|
type: trunk
|
||||||
|
vlan: 8
|
||||||
allowed_vlans:
|
allowed_vlans:
|
||||||
- 10
|
- 10
|
||||||
- 15
|
- 15
|
||||||
|
@ -224,7 +268,7 @@
|
||||||
that:
|
that:
|
||||||
- update_trunk.data.tags == 'server'
|
- update_trunk.data.tags == 'server'
|
||||||
- update_trunk.data.type == 'trunk'
|
- update_trunk.data.type == 'trunk'
|
||||||
- update_trunk.data.allowedVlans == '10,15,20'
|
- update_trunk.data.allowedVlans == '8,10,15,20'
|
||||||
|
|
||||||
- name: Configure trunk port with specific VLANs and native VLAN
|
- name: Configure trunk port with specific VLANs and native VLAN
|
||||||
meraki_switchport:
|
meraki_switchport:
|
||||||
|
|
Loading…
Reference in a new issue