Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,16 @@ def prepare(self):
if net.get('network_attach_group') not in net_grp_name_list:
del net['network_attach_group']

# If the switch is in the switch list and a hostname is used, add the management IP to switch_attach_overrides
for net in data_model['vxlan']['overlay']['networks']:
for override in net.get('switch_attach_overrides', []):
hostname = override.get('hostname')
if hostname and any(sw['name'] == hostname for sw in switches):
found_switch = next(item for item in switches if item['name'] == hostname)
if found_switch.get('management').get('management_ipv4_address'):
override['mgmt_ip_address'] = found_switch['management']['management_ipv4_address']
elif found_switch.get('management').get('management_ipv6_address'):
override['mgmt_ip_address'] = found_switch['management']['management_ipv6_address']

self.kwargs['results']['model_extended'] = data_model
return self.kwargs['results']
15 changes: 15 additions & 0 deletions plugins/action/dtc/prepare_msite_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,5 +364,20 @@ def run(self, tmp=None, task_vars=None):
if net.get('network_attach_group') not in net_grp_name_list:
del net['network_attach_group']

# If the switch is in a child fabric and a hostname is used, add the management IP to switch_attach_overrides
for net in data_model['vxlan']['multisite']['overlay']['networks']:
for override in net.get('switch_attach_overrides', []):
hostname = override.get('hostname')
if not hostname:
continue
for child_fabric in child_fabrics_data.keys():
for sw in child_fabrics_data[child_fabric]['switches']:
if sw.get('hostname') is None:
continue
fwd_pattern = f"^{re.escape(hostname)}$|^{re.escape(hostname)}\\..*$"
rev_pattern = f"^{re.escape(sw['hostname'])}$|^{re.escape(sw['hostname'])}\\..*$"
if re.search(fwd_pattern, sw['hostname']) or re.search(rev_pattern, hostname):
override['mgmt_ip_address'] = sw['mgmt_ip_address']

results['overlay_attach_groups'] = data_model['vxlan']['multisite']['overlay']
return results
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{# Auto-generated NDFC DC VXLAN EVPN VRFs config data structure for fabric {{ vxlan.fabric.name }} #}
{# Auto-generated NDFC DC VXLAN EVPN Networks config data structure for fabric {{ vxlan.fabric.name }} #}
{% set networks = [] %}
{% if data_model_extended.vxlan.overlay.networks is defined and data_model_extended.vxlan.overlay.networks %}
{% set networks = data_model_extended.vxlan.overlay.networks %}
Expand Down Expand Up @@ -68,6 +68,22 @@
{% endif %}
{% for attach in network_attach_groups_dict[net['network_attach_group']] %}
- ip_address: {{ attach['mgmt_ip_address'] }}
{% if net['switch_attach_overrides'] is defined %}
{% for switch_override in net['switch_attach_overrides'] %}
{% if switch_override['mgmt_ip_address'] == attach['mgmt_ip_address'] %}
{% if switch_override['vlan_id'] is defined and switch_override['vlan_id'] != net['vlan_id'] %}
vlan_id: {{ switch_override['vlan_id'] }}
{% endif %}
{% if switch_override['freeform_config'] is defined %}
freeform_config: |-
{{ switch_override['freeform_config'] | cisco.nac_dc_vxlan.dedent | indent(8, true) }}
{% endif %}
{% if ndfc_version | cisco.nac_dc_vxlan.version_compare('12.4.1', '>=') %}
svi_enabled: {{ switch_override['svi_enabled'] | default(defaults.vxlan.overlay.networks.svi_enabled) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% if attach['ports'] is defined %}
ports: {{ attach['ports'] }}
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
{% else %}
{% set networks = [] %}
{% endif %}
{% set net_overrides = {} %}
{% for _n in runtime_mcfg_data_model.overlay_attach_groups.networks | default([], true) %}
{% set _ = net_overrides.update({_n['name']: _n.get('switch_attach_overrides') or []}) %}
{% endfor %}
{% for net in networks %}
- net_name: {{ net['name'] }}
{# ------------------------------------------------------ #}
Expand Down Expand Up @@ -74,6 +78,20 @@
{% endif %}
{% for attach in network_attach_groups_dict[net['network_attach_group']] %}
- ip_address: {{ attach['mgmt_ip_address'] }}
{% for switch_override in net_overrides.get(net['name'], []) %}
{% if switch_override['mgmt_ip_address'] == attach['mgmt_ip_address'] %}
{% if switch_override['vlan_id'] is defined and switch_override['vlan_id'] != net['vlan_id'] %}
vlan_id: {{ switch_override['vlan_id'] }}
{% endif %}
{% if switch_override['freeform_config'] is defined %}
freeform_config: |-
{{ switch_override['freeform_config'] | cisco.nac_dc_vxlan.dedent | indent(8, true) }}
{% endif %}
{% if ndfc_version | cisco.nac_dc_vxlan.version_compare('12.4.1', '>=') %}
svi_enabled: {{ switch_override['svi_enabled'] | default(defaults.vxlan.overlay.networks.svi_enabled) }}
{% endif %}
{% endif %}
{% endfor %}
{% if attach['ports'] is defined %}
ports: {{ attach['ports'] }}
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
{% else %}
{% set networks = [] %}
{% endif %}
{% set net_overrides = {} %}
{% for _n in runtime_msd_data_model.overlay_attach_groups.networks | default([], true) %}
{% set _ = net_overrides.update({_n['name']: _n.get('switch_attach_overrides') or []}) %}
{% endfor %}
{% for net in networks %}
- net_name: {{ net['name'] }}
{# ------------------------------------------------------ #}
Expand Down Expand Up @@ -74,6 +78,20 @@
{% endif %}
{% for attach in network_attach_groups_dict[net['network_attach_group']] %}
- ip_address: {{ attach['mgmt_ip_address'] }}
{% for switch_override in net_overrides.get(net['name'], []) %}
{% if switch_override['mgmt_ip_address'] == attach['mgmt_ip_address'] %}
{% if switch_override['vlan_id'] is defined and switch_override['vlan_id'] != net['vlan_id'] %}
vlan_id: {{ switch_override['vlan_id'] }}
{% endif %}
{% if switch_override['freeform_config'] is defined %}
freeform_config: |-
{{ switch_override['freeform_config'] | cisco.nac_dc_vxlan.dedent | indent(8, true) }}
{% endif %}
{% if ndfc_version | cisco.nac_dc_vxlan.version_compare('12.4.1', '>=') %}
svi_enabled: {{ switch_override['svi_enabled'] | default(defaults.vxlan.overlay.networks.svi_enabled) }}
{% endif %}
{% endif %}
{% endfor %}
{% if attach['ports'] is defined %}
ports: {{ attach['ports'] }}
{% endif %}
Expand Down
2 changes: 2 additions & 0 deletions roles/validate/files/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ factory_defaults:
trm_enable: false
# Staging trmv6 attribute for later usage after low-level collection adds support.
# trmv6_enable: false
svi_enabled: true
xconnect: false
vrf_attach_groups:
switches: []
Expand Down Expand Up @@ -524,6 +525,7 @@ factory_defaults:
route_target_both: false
route_tag: 12345
trm_enable: false
svi_enabled: true
isn:
auth_proto: MD5
sub_int_range: 2-511
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def match(cls, data_model):
if sm_networks and network_attach_groups:
results = cls.cross_reference_switches(network_attach_groups, switches, 'network', results)
results = cls.cross_reference_vpc_peers(network_attach_groups, vpc_peers, 'network', results)
if sm_networks:
results = cls.cross_reference_switch_overrides(sm_networks, switches, 'network', results)

return results

Expand Down Expand Up @@ -179,3 +181,24 @@ def cross_reference_vpc_peers(cls, attach_groups, vpc_peers, target, results):
)

return results

@classmethod
def cross_reference_switch_overrides(cls, attach_overrides, switches, target, results):
if not attach_overrides:
return results

for attach_override in attach_overrides:
for override in attach_override.get('switch_attach_overrides', []):
hostname = override.get('hostname')
if not hostname:
continue

if not any(s.get('name') == hostname for s in switches):
if not any(s.get('management', {}).get('management_ipv4_address') == hostname for s in switches):
if not any(s.get('management', {}).get('management_ipv6_address') == hostname for s in switches):
results.append(
f"switch: {hostname}, defined under {target} {attach_override.get('name')}.switch_attach_overrides"
f" does not match any switch in the topology."
)

return results
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
class Rule:
id = "405"
description = "Verify Network vlan_id override attribute are the same on vPC Peers"
severity = "HIGH"

@classmethod
def match(cls, data_model):
results = []
networks = cls.safeget(data_model, ['vxlan', 'overlay', 'networks'])
vpc_peers = cls.safeget(data_model, ['vxlan', 'topology', 'vpc_peers'])

# Build a mapping of vPC peers: hostname -> peer_hostname
vpc_peer_mapping = {}
if vpc_peers:
for vpc_pair in vpc_peers:
peer1 = vpc_pair.get('peer1')
peer2 = vpc_pair.get('peer2')
if peer1 and peer2:
vpc_peer_mapping[peer1] = peer2
vpc_peer_mapping[peer2] = peer1

# Check if vpc_peer_mapping is not empty and networks are defined
if vpc_peer_mapping and networks:
for network in networks:
attach_overrides = cls.safeget(network, ['switch_attach_overrides'])
if attach_overrides:
vlan_overrides = {o['hostname']: o.get('vlan_id') for o in attach_overrides}
for override in attach_overrides:
switch = override['hostname']
vlan_override = cls.safeget(override, ['vlan_id'])
if vlan_override:
if switch in vpc_peer_mapping:
peer = vpc_peer_mapping[switch]
peer_override = vlan_overrides.get(peer)
peer_vlan_id = peer_override if peer_override is not None else cls.safeget(network, ['vlan_id'])

if vlan_override != peer_vlan_id:
results.append(
f"Networks.{network['name']}: switches {switch} and {peer} "
f"are vPC peers but have different vlan_id: "
f"{vlan_override} != {peer_vlan_id}"
)

return results

@classmethod
def data_model_key_check(cls, tested_object, keys):
dm_key_dict = {'keys_found': [], 'keys_not_found': [], 'keys_data': [], 'keys_no_data': []}
for key in keys:
if tested_object and key in tested_object:
dm_key_dict['keys_found'].append(key)
tested_object = tested_object[key]
if tested_object:
dm_key_dict['keys_data'].append(key)
else:
dm_key_dict['keys_no_data'].append(key)
else:
dm_key_dict['keys_not_found'].append(key)
return dm_key_dict

@classmethod
def safeget(cls, dict, keys):
# Utility function to safely get nested dictionary values
for key in keys:
if dict is None:
return None
if key in dict:
dict = dict[key]
else:
return None

return dict
Loading