Skip to content

Commit 262eeac

Browse files
mtarkingmikewiebe
andauthored
3.1 Backwards Compatibility (#524)
* split msd vrf child fabric templates for 3.1 support * pass nd version * updates for 3.1 * networks for 3.1 * networks for 3.1 * networks for 3.1 * networks for 3.1 * check package import in filter * remove packaging step * Update manage_child_fabric_networks.py * Update manage_child_fabric_vrfs.py * Update manage_child_fabric_networks.py * Update manage_child_fabric_vrfs.py * review comments * Remove unused vars_common_isn.changes_detected_fabric_links --------- Co-authored-by: mwiebe <[email protected]>
1 parent 62000b2 commit 262eeac

File tree

20 files changed

+347
-41
lines changed

20 files changed

+347
-41
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
steps:
2626
- name: Check out code
2727
uses: actions/checkout@v4
28-
28+
2929
- name: Set up Python 3.10.14
3030
uses: actions/setup-python@v5
3131
with:

plugins/action/dtc/manage_child_fabric_networks.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,26 @@
2828
from ansible.plugins.action import ActionBase
2929
from ansible.template import Templar
3030
from ansible.errors import AnsibleFileNotFound
31+
from ansible_collections.cisco.nac_dc_vxlan.plugins.filter import version_compare
3132

3233

34+
import re
3335
import json
3436

3537
display = Display()
3638

3739
# Path to Jinja template files relative to create role
38-
MSD_CHILD_FABRIC_NETWORK_TEMPLATE = "/../common/templates/ndfc_networks/msd_fabric/child_fabric/msd_child_fabric_network.j2"
40+
MSD_CHILD_FABRIC_NETWORK_TEMPLATE_PATH = "/../common/templates/ndfc_networks/msd_fabric/child_fabric/"
41+
MSD_CHILD_FABRIC_NETWORK_TEMPLATE = "/msd_child_fabric_network.j2"
42+
43+
# Currently supported Network template config keys and their mapping to data model keys
44+
NETWORK_TEMPLATE_CONFIG_MAP = {
45+
'loopbackId': {'dm_key': 'dhcp_loopback_id', 'default': ''},
46+
'ENABLE_NETFLOW': {'dm_key': 'netflow_enable', 'default': False},
47+
'VLAN_NETFLOW_MONITOR': {'dm_key': 'vlan_netflow_monitor', 'default': ''},
48+
'trmEnabled': {'dm_key': 'trm_enable', 'default': False},
49+
'mcastGroup': {'dm_key': 'multicast_group_address', 'default': ''},
50+
}
3951

4052

4153
class ActionModule(ActionBase):
@@ -46,8 +58,21 @@ def run(self, tmp=None, task_vars=None):
4658
results['failed'] = False
4759
results['child_fabrics_changed'] = []
4860

61+
nd_version = self._task.args["nd_version"]
4962
msite_data = self._task.args["msite_data"]
5063

64+
# Extract major, minor, patch and patch letter from nd_version
65+
# that is set in nac_dc_vxlan.dtc.connectivity_check role
66+
# Example nd_version: "3.1.1l" or "3.2.2m"
67+
# where 3.1.1/3.2.2 are major.minor.patch respectively
68+
# and l/m are the patch letter respectively
69+
nd_major_minor_patch = None
70+
nd_patch_letter = None
71+
match = re.match(r'^(\d+\.\d+\.\d+)([a-z])?$', nd_version)
72+
if match:
73+
nd_major_minor_patch = match.group(1)
74+
nd_patch_letter = match.group(2)
75+
5176
networks = msite_data['overlay_attach_groups']['networks']
5277
network_attach_groups_dict = msite_data['overlay_attach_groups']['network_attach_groups']
5378

@@ -95,6 +120,7 @@ def run(self, tmp=None, task_vars=None):
95120
network_child_fabric = network_child_fabric[0]
96121

97122
# Need to clean these up and make them more dynamic
123+
# Check if fabric settings are properly enabled
98124
if network_child_fabric.get('netflow_enable'):
99125
if child_fabric_attributes['ENABLE_NETFLOW'] == 'false':
100126
error_msg = (
@@ -145,13 +171,21 @@ def run(self, tmp=None, task_vars=None):
145171
# Check for differences between the data model and the template config from NDFC for the
146172
# attributes that are configurable by the user in a child fabric.
147173
# Note: This excludes IPv6 related attributes at this time as they are not yet supported fully in the data model.
148-
if (
149-
(ndfc_net_template_config['loopbackId'] != network_child_fabric.get('dhcp_loopback_id', "")) or
150-
(ndfc_net_template_config['ENABLE_NETFLOW'] != str(network_child_fabric.get('netflow_enable', False)).lower()) or
151-
(ndfc_net_template_config['VLAN_NETFLOW_MONITOR'] != network_child_fabric.get('vlan_netflow_monitor', "")) or
152-
(ndfc_net_template_config['trmEnabled'] != str(network_child_fabric.get('trm_enable', False)).lower()) or
153-
(ndfc_net_template_config['mcastGroup'] != network_child_fabric.get('multicast_group_address'))
154-
):
174+
diff_found = False
175+
for template_key, map_info in NETWORK_TEMPLATE_CONFIG_MAP.items():
176+
dm_key = map_info['dm_key']
177+
default = map_info['default']
178+
template_value = ndfc_net_template_config.get(template_key, default)
179+
dm_value = network_child_fabric.get(dm_key, default)
180+
# Normalize boolean/string values for comparison
181+
if isinstance(default, bool):
182+
template_value = str(template_value).lower()
183+
dm_value = str(dm_value).lower()
184+
if template_value != dm_value:
185+
diff_found = True
186+
break
187+
188+
if diff_found:
155189
results['child_fabrics_changed'].append(child_fabric)
156190

157191
# Combine task_vars with local_vars for template rendering
@@ -165,10 +199,13 @@ def run(self, tmp=None, task_vars=None):
165199
},
166200
)
167201

202+
# Attempt to find and read the template file
168203
role_path = task_vars.get('role_path')
169-
template_path = role_path + MSD_CHILD_FABRIC_NETWORK_TEMPLATE
204+
version = '3.2'
205+
if version_compare.version_compare(nd_major_minor_patch, '3.1.1', '<='):
206+
version = '3.1'
207+
template_path = f"{role_path}{MSD_CHILD_FABRIC_NETWORK_TEMPLATE_PATH}{version}{MSD_CHILD_FABRIC_NETWORK_TEMPLATE}"
170208

171-
# Attempt to find and read the template file
172209
try:
173210
template_full_path = self._find_needle('templates', template_path)
174211
with open(template_full_path, 'r') as template_file:

plugins/action/dtc/manage_child_fabric_vrfs.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,37 @@
2828
from ansible.plugins.action import ActionBase
2929
from ansible.template import Templar
3030
from ansible.errors import AnsibleFileNotFound
31+
from ansible_collections.cisco.nac_dc_vxlan.plugins.filter import version_compare
3132

3233

34+
import re
3335
import json
3436

3537
display = Display()
3638

3739
# Path to Jinja template files relative to create role
38-
MSD_CHILD_FABRIC_VRF_TEMPLATE = "/../common/templates/ndfc_vrfs/msd_fabric/child_fabric/msd_child_fabric_vrf.j2"
40+
MSD_CHILD_FABRIC_VRF_TEMPLATE_PATH = "/../common/templates/ndfc_vrfs/msd_fabric/child_fabric/"
41+
MSD_CHILD_FABRIC_VRF_TEMPLATE = "/msd_child_fabric_vrf.j2"
42+
43+
# Currently supported VRF template config keys and their mapping to data model keys
44+
VRF_TEMPLATE_CONFIG_MAP = {
45+
'advertiseHostRouteFlag': {'dm_key': 'adv_host_routes', 'default': ''},
46+
'advertiseDefaultRouteFlag': {'dm_key': 'adv_default_routes', 'default': ''},
47+
'configureStaticDefaultRouteFlag': {'dm_key': 'config_static_default_route', 'default': ''},
48+
'bgpPassword': {'dm_key': 'bgp_password', 'default': ''},
49+
'bgpPasswordKeyType': {'dm_key': 'bgp_password_key_type', 'default': ''},
50+
'ENABLE_NETFLOW': {'dm_key': 'netflow_enable', 'default': False},
51+
'NETFLOW_MONITOR': {'dm_key': 'netflow_monitor', 'default': ''},
52+
'trmEnabled': {'dm_key': 'trm_enable', 'default': False},
53+
'loopbackNumber': {'dm_key': 'rp_loopback_id', 'default': ''},
54+
'rpAddress': {'dm_key': 'rp_address', 'default': ''},
55+
'isRPAbsent': {'dm_key': 'no_rp', 'default': False},
56+
'isRPExternal': {'dm_key': 'rp_external', 'default': False},
57+
'L3VniMcastGroup': {'dm_key': 'underlay_mcast_ip', 'default': ''},
58+
'multicastGroup': {'dm_key': 'overlay_multicast_group', 'default': ''},
59+
'routeTargetImportMvpn': {'dm_key': 'import_mvpn_rt', 'default': ''},
60+
'routeTargetExportMvpn': {'dm_key': 'export_mvpn_rt', 'default': ''}
61+
}
3962

4063

4164
class ActionModule(ActionBase):
@@ -46,8 +69,21 @@ def run(self, tmp=None, task_vars=None):
4669
results['failed'] = False
4770
results['child_fabrics_changed'] = []
4871

72+
nd_version = self._task.args["nd_version"]
4973
msite_data = self._task.args["msite_data"]
5074

75+
# Extract major, minor, patch and patch letter from nd_version
76+
# that is set in nac_dc_vxlan.dtc.connectivity_check role
77+
# Example nd_version: "3.1.1l" or "3.2.2m"
78+
# where 3.1.1/3.2.2 are major.minor.patch respectively
79+
# and l/m are the patch letter respectively
80+
nd_major_minor_patch = None
81+
nd_patch_letter = None
82+
match = re.match(r'^(\d+\.\d+\.\d+)([a-z])?$', nd_version)
83+
if match:
84+
nd_major_minor_patch = match.group(1)
85+
nd_patch_letter = match.group(2)
86+
5187
vrfs = msite_data['overlay_attach_groups']['vrfs']
5288
vrf_attach_groups_dict = msite_data['overlay_attach_groups']['vrf_attach_groups']
5389

@@ -95,6 +131,7 @@ def run(self, tmp=None, task_vars=None):
95131
vrf_child_fabric = vrf_child_fabric[0]
96132

97133
# Need to clean these up and make them more dynamic
134+
# Check if fabric settings are properly enabled
98135
if vrf_child_fabric.get('netflow_enable'):
99136
if child_fabric_attributes['ENABLE_NETFLOW'] == 'false':
100137
error_msg = (
@@ -145,24 +182,21 @@ def run(self, tmp=None, task_vars=None):
145182
# Check for differences between the data model and the template config from NDFC for the
146183
# attributes that are configurable by the user in a child fabric.
147184
# Note: This excludes IPv6 related attributes at this time as they are not yet supported fully in the data model.
148-
if (
149-
(ndfc_vrf_template_config['advertiseHostRouteFlag'] != str(vrf_child_fabric.get('adv_host_routes', '')).lower()) or
150-
(ndfc_vrf_template_config['advertiseDefaultRouteFlag'] != str(vrf_child_fabric.get('adv_default_routes', '')).lower()) or
151-
(ndfc_vrf_template_config['configureStaticDefaultRouteFlag'] != str(vrf_child_fabric.get('config_static_default_route', '')).lower()) or # noqa: E501
152-
(ndfc_vrf_template_config['bgpPassword'] != vrf_child_fabric.get('bgp_password', '')) or
153-
(ndfc_vrf_template_config.get('bgpPasswordKeyType', '') != vrf_child_fabric.get('bgp_password_key_type', '')) or
154-
(ndfc_vrf_template_config['ENABLE_NETFLOW'] != str(vrf_child_fabric.get('netflow_enable', False)).lower()) or
155-
(ndfc_vrf_template_config['NETFLOW_MONITOR'] != vrf_child_fabric.get('netflow_monitor', '')) or
156-
(ndfc_vrf_template_config['trmEnabled'] != str(vrf_child_fabric.get('trm_enable', False)).lower()) or
157-
(ndfc_vrf_template_config.get('loopbackNumber', '') != vrf_child_fabric.get('rp_loopback_id', '')) or
158-
(ndfc_vrf_template_config.get('rpAddress', '') != vrf_child_fabric.get('rp_address', '')) or
159-
(ndfc_vrf_template_config['isRPAbsent'] != str(vrf_child_fabric.get('no_rp', False)).lower()) or
160-
(ndfc_vrf_template_config['isRPExternal'] != str(vrf_child_fabric.get('rp_external', False)).lower()) or
161-
(ndfc_vrf_template_config.get('L3VniMcastGroup', '') != vrf_child_fabric.get('underlay_mcast_ip', '')) or
162-
(ndfc_vrf_template_config['multicastGroup'] != vrf_child_fabric.get('overlay_multicast_group', '')) or
163-
(ndfc_vrf_template_config.get('routeTargetImportMvpn', '') != vrf_child_fabric.get('import_mvpn_rt', '')) or
164-
(ndfc_vrf_template_config.get('routeTargetExportMvpn', '') != vrf_child_fabric.get('export_mvpn_rt', ''))
165-
):
185+
diff_found = False
186+
for template_key, map_info in VRF_TEMPLATE_CONFIG_MAP.items():
187+
dm_key = map_info['dm_key']
188+
default = map_info['default']
189+
template_value = ndfc_vrf_template_config.get(template_key, default)
190+
dm_value = vrf_child_fabric.get(dm_key, default)
191+
# Normalize boolean/string values for comparison
192+
if isinstance(default, bool):
193+
template_value = str(template_value).lower()
194+
dm_value = str(dm_value).lower()
195+
if template_value != dm_value:
196+
diff_found = True
197+
break
198+
199+
if diff_found:
166200
results['child_fabrics_changed'].append(child_fabric)
167201

168202
# Combine task_vars with local_vars for template rendering
@@ -179,7 +213,11 @@ def run(self, tmp=None, task_vars=None):
179213

180214
# Attempt to find and read the template file
181215
role_path = task_vars.get('role_path')
182-
template_path = role_path + MSD_CHILD_FABRIC_VRF_TEMPLATE
216+
version = '3.2'
217+
if version_compare.version_compare(nd_major_minor_patch, '3.1.1', '<='):
218+
version = '3.1'
219+
template_path = f"{role_path}{MSD_CHILD_FABRIC_VRF_TEMPLATE_PATH}{version}{MSD_CHILD_FABRIC_VRF_TEMPLATE}"
220+
183221
try:
184222
template_full_path = self._find_needle('templates', template_path)
185223
with open(template_full_path, 'r') as template_file:

plugins/filter/version_compare.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,38 @@
6565
from jinja2.runtime import Undefined
6666
from jinja2.exceptions import UndefinedError
6767

68-
from packaging.version import Version
68+
try:
69+
from packaging.version import Version
70+
except ImportError as imp_exc:
71+
PACKAGING_LIBRARY_IMPORT_ERROR = imp_exc
72+
else:
73+
PACKAGING_LIBRARY_IMPORT_ERROR = None
6974

7075
from ansible.module_utils.six import string_types
71-
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
76+
from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleFilterTypeError
7277
from ansible.module_utils.common.text.converters import to_native
7378

7479

7580
SUPPORTED_COMPARISON_OPERATORS = ['==', '!=', '>', '>=', '<', '<=']
7681

7782

7883
def version_compare(version1, version2, op):
84+
"""
85+
Compare two version strings using the specified operator.
86+
Args:
87+
version1 (str): The first version string to compare.
88+
version2 (str): The second version string to compare.
89+
op (str): The comparison operator as a string. Supported: '==', '!=', '>', '>=', '<', '<='.
90+
Returns:
91+
bool: The result of the comparison.
92+
Raises:
93+
AnsibleError: If the 'packaging' library is not installed.
94+
AnsibleFilterTypeError: If the version arguments are not strings.
95+
AnsibleFilterError: If the operator is unsupported or version parsing fails.
96+
"""
97+
if PACKAGING_LIBRARY_IMPORT_ERROR:
98+
raise AnsibleError('packaging must be installed to use this filter plugin') from PACKAGING_LIBRARY_IMPORT_ERROR
99+
79100
if not isinstance(version1, (string_types, Undefined)):
80101
raise AnsibleFilterTypeError(f"Can only check string versions, however version1 is: {type(version1)}")
81102

roles/dtc/common/templates/ndfc_networks/msd_fabric/child_fabric/3.1/.gitkeep

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{# Auto-generated NDFC VXLAN EVPN MSD Child Fabric Network config data structure for fabric {{ fabric_name }} #}
2+
{
3+
"fabric": "{{ fabric_name }}",
4+
"networkName": "{{ network_name }}",
5+
"displayName": "{{ network_name }}",
6+
"networkId": "{{ ndfc.segmentId }}",
7+
"networkTemplate": "Default_Network_Universal",
8+
"networkExtensionTemplate": "Default_Network_Extension_Universal",
9+
"networkTemplateConfig": "{{ {
10+
"vrfName": ndfc.vrfName,
11+
"vlanId": ndfc.vlanId,
12+
"vlanName": ndfc.vlanName,
13+
"segmentId": ndfc.segmentId,
14+
"intfDescription": ndfc.intfDescription,
15+
"gatewayIpAddress": ndfc.gatewayIpAddress,
16+
"gatewayIpV6Address": ndfc.gatewayIpV6Address,
17+
"mtu": ndfc.mtu,
18+
"isLayer2Only": ndfc.isLayer2Only,
19+
"suppressArp": ndfc.suppressArp,
20+
"mcastGroup": dm.multicast_group_address | default(ndfc.mcastGroup),
21+
"tag": ndfc.tag,
22+
"secondaryGW1": ndfc.secondaryGW1,
23+
"secondaryGW2": ndfc.secondaryGW2,
24+
"secondaryGW3": ndfc.secondaryGW3,
25+
"secondaryGW4": ndfc.secondaryGW4,
26+
"loopbackId": dm.dhcp_loopback_id | default(ndfc.loopbackId),
27+
"dhcpServerAddr1": dm['dhcp_servers'][0]['ip_address'] if dm['dhcp_servers'][0]['ip_address'] is defined else "",
28+
"vrfDhcp": dm['dhcp_servers'][0]['vrf'] if dm['dhcp_servers'][0]['ip_address'] is defined else ndfc.vrfDhcp,
29+
"dhcpServerAddr2": dm['dhcp_servers'][1]['ip_address'] if dm['dhcp_servers'][1]['ip_address'] is defined else ndfc.dhcpServerAddr2,
30+
"vrfDhcp2": dm['dhcp_servers'][1]['vrf'] if dm['dhcp_servers'][1]['ip_address'] is defined else "",
31+
"dhcpServerAddr3": dm['dhcp_servers'][2]['ip_address'] if dm['dhcp_servers'][2]['ip_address'] is defined else ndfc.dhcpServerAddr3,
32+
"vrfDhcp3": dm['dhcp_servers'][2]['vrf'] if dm['dhcp_servers'][2]['vrf'] is defined else "",
33+
"ENABLE_NETFLOW": dm.netflow_enable if dm.netflow_enable is defined else ndfc.ENABLE_NETFLOW,
34+
"SVI_NETFLOW_MONITOR": ndfc.SVI_NETFLOW_MONITOR,
35+
"VLAN_NETFLOW_MONITOR": dm.vlan_netflow_monitor if (dm.netflow_enable is defined and dm.netflow_enable) else ndfc.VLAN_NETFLOW_MONITOR,
36+
"enableIR": ndfc.enableIR,
37+
"trmEnabled": dm.trm_enable if dm.trm_enable is defined else ndfc.trmEnabled,
38+
"igmpVersion": ndfc.igmpVersion,
39+
"rtBothAuto": ndfc.rtBothAuto,
40+
"enableL3OnBorder": ndfc.enableL3OnBorder,
41+
"nveId": ndfc.nveId,
42+
"type": "Normal",
43+
} }}",
44+
"vrf": "{{ ndfc.vrfName }}",
45+
"type": "Normal",
46+
"hierarchicalKey": "{{ fabric_name }}"
47+
}

roles/dtc/common/templates/ndfc_networks/msd_fabric/child_fabric/3.2/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)