Skip to content

Commit c475d35

Browse files
authored
Fix for VRF_Lite Issue & DOT1Q_ID Auto Allocation Issue #210 (#467)
* Reserving DOT1Q ID for the VRF Lite Object through NDFC Resource Manager * Pylint Fix * Fixes for Nonetype Error, Multiple VRF-Lite Objects Attachment Failure, Diff Incorrectly Populated for Idempotence * DOT1Q Orphaned Resources Cleanup Fix --------- Co-authored-by: = <=>
1 parent 0d0be72 commit c475d35

File tree

1 file changed

+122
-46
lines changed

1 file changed

+122
-46
lines changed

plugins/modules/dcnm_vrf.py

Lines changed: 122 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -835,14 +835,17 @@ def to_bool(self, key, dict_with_key):
835835

836836
# pylint: enable=inconsistent-return-statements
837837
@staticmethod
838-
def compare_properties(dict1, dict2, property_list):
838+
def compare_properties(dict1, dict2, property_list, skip_prop=None):
839839
"""
840-
Given two dictionaries and a list of keys:
840+
Given two dictionaries, a list of keys and keys that can be
841+
skipped, compare the values of the keys in both dictionaries:
841842
842843
- Return True if all property values match.
843844
- Return False otherwise
844845
"""
845846
for prop in property_list:
847+
if skip_prop and prop in skip_prop:
848+
continue
846849
if dict1.get(prop) != dict2.get(prop):
847850
return False
848851
return True
@@ -954,8 +957,11 @@ def diff_for_attach_deploy(self, want_a, have_a, replace=False):
954957
continue
955958
found = True
956959
interface_match = True
960+
skip_prop = []
961+
if not wlite["DOT1Q_ID"]:
962+
skip_prop.append("DOT1Q_ID")
957963
if not self.compare_properties(
958-
wlite, hlite, self.vrf_lite_properties
964+
wlite, hlite, self.vrf_lite_properties, skip_prop
959965
):
960966
found = False
961967
break
@@ -1216,14 +1222,16 @@ def update_attach_params_extension_values(self, attach) -> dict:
12161222
else:
12171223
extension_values["VRF_LITE_CONN"] = copy.deepcopy(vrf_lite_connections)
12181224

1219-
extension_values["VRF_LITE_CONN"] = json.dumps(
1220-
extension_values["VRF_LITE_CONN"]
1221-
)
1222-
1223-
msg = "Returning extension_values: "
1225+
msg = "Building extension_values: "
12241226
msg += f"{json.dumps(extension_values, indent=4, sort_keys=True)}"
12251227
self.log.debug(msg)
12261228

1229+
extension_values["VRF_LITE_CONN"] = json.dumps(
1230+
extension_values["VRF_LITE_CONN"]
1231+
)
1232+
msg = "Returning extension_values: "
1233+
msg += f"{json.dumps(extension_values, indent=4, sort_keys=True)}"
1234+
self.log.debug(msg)
12271235
return copy.deepcopy(extension_values)
12281236

12291237
def update_attach_params(self, attach, vrf_name, deploy, vlan_id) -> dict:
@@ -1393,10 +1401,13 @@ def diff_for_create(self, want, have):
13931401
# remove it here (as we did with the other params that are
13941402
# compared in the call to self.dict_values_differ())
13951403
vlan_id_want = str(json_to_dict_want.get("vrfVlanId", ""))
1404+
vrfSegmentId_want = json_to_dict_want.get("vrfSegmentId")
13961405

13971406
skip_keys = []
13981407
if vlan_id_want == "0":
13991408
skip_keys = ["vrfVlanId"]
1409+
if vrfSegmentId_want is None:
1410+
skip_keys.append("vrfSegmentId")
14001411
templates_differ = self.dict_values_differ(
14011412
json_to_dict_want, json_to_dict_have, skip_keys=skip_keys
14021413
)
@@ -3093,6 +3104,54 @@ def get_extension_values_from_lite_objects(self, lite: list[dict]) -> list:
30933104

30943105
return extension_values_list
30953106

3107+
def get_vrf_lite_dot1q_id(self, serial_number: str, vrf_name: str, interface: str) -> int:
3108+
"""
3109+
# Summary
3110+
3111+
Given a switch serial, vrf name and ifname, return the dot1q ID
3112+
reserved for the vrf_lite extension on that switch.
3113+
3114+
## Raises
3115+
3116+
Calls fail_json if DNCM fails to reserve the dot1q ID.
3117+
"""
3118+
caller = inspect.stack()[1][3]
3119+
3120+
msg = "ENTERED. "
3121+
msg += f"caller: {caller}. "
3122+
msg += f"serial_number: {serial_number}"
3123+
msg += f"vrf name: {vrf_name}"
3124+
msg += f"interface: {interface}"
3125+
self.log.debug(msg)
3126+
3127+
dot1q_id = None
3128+
path = "/appcenter/cisco/ndfc/api/v1/lan-fabric"
3129+
path += "/rest/resource-manager/reserve-id"
3130+
verb = "POST"
3131+
payload = {"scopeType": "DeviceInterface",
3132+
"usageType": "TOP_DOWN_L3_DOT1Q",
3133+
"serialNumber": serial_number,
3134+
"ifName": interface,
3135+
"allocatedTo": vrf_name}
3136+
3137+
resp = dcnm_send(self.module, verb, path, json.dumps(payload))
3138+
if resp.get("RETURN_CODE") != 200:
3139+
msg = f"{self.class_name}.get_vrf_lite_dot1q_id: "
3140+
msg += f"caller: {caller}. "
3141+
msg += "Failed to get dot1q ID for vrf_lite extension on switch "
3142+
msg += f"{serial_number} for vrf {vrf_name} and interface {interface}. "
3143+
msg += f"Response: {resp}"
3144+
self.module.fail_json(msg=msg)
3145+
else:
3146+
msg = f"{self.class_name}.get_vrf_lite_dot1q_id: "
3147+
msg += f"caller: {caller}. "
3148+
msg += "Successfully got dot1q ID for vrf_lite extension on switch "
3149+
msg += f"{serial_number} for vrf {vrf_name} and interface {interface}. "
3150+
msg += f"Response: {resp}"
3151+
self.log.debug(msg)
3152+
dot1q_id = resp.get("DATA")
3153+
return dot1q_id
3154+
30963155
def update_vrf_attach_vrf_lite_extensions(self, vrf_attach, lite) -> dict:
30973156
"""
30983157
# Summary
@@ -3223,7 +3282,20 @@ def update_vrf_attach_vrf_lite_extensions(self, vrf_attach, lite) -> dict:
32233282
if item["user"]["dot1q"]:
32243283
nbr_dict["DOT1Q_ID"] = str(item["user"]["dot1q"])
32253284
else:
3226-
nbr_dict["DOT1Q_ID"] = str(item["switch"]["DOT1Q_ID"])
3285+
dot1q_vlan = self.get_vrf_lite_dot1q_id(
3286+
serial_number,
3287+
vrf_attach.get("vrfName"),
3288+
nbr_dict["IF_NAME"]
3289+
)
3290+
if dot1q_vlan is not None:
3291+
nbr_dict["DOT1Q_ID"] = str(dot1q_vlan)
3292+
else:
3293+
msg = f"{self.class_name}.{method_name}: "
3294+
msg += f"caller: {caller}. "
3295+
msg += "Failed to get dot1q ID for vrf_lite extension "
3296+
msg += f"on switch {serial_number} for vrf {vrf_attach.get('vrfName')} "
3297+
msg += f"and interface {nbr_dict['IF_NAME']}"
3298+
self.module.fail_json(msg=msg)
32273299

32283300
if item["user"]["ipv4_addr"]:
32293301
nbr_dict["IP_MASK"] = item["user"]["ipv4_addr"]
@@ -3269,9 +3341,9 @@ def update_vrf_attach_vrf_lite_extensions(self, vrf_attach, lite) -> dict:
32693341
ms_con["MULTISITE_CONN"] = []
32703342
extension_values["MULTISITE_CONN"] = json.dumps(ms_con)
32713343

3272-
extension_values["VRF_LITE_CONN"] = json.dumps(
3273-
extension_values["VRF_LITE_CONN"]
3274-
)
3344+
extension_values["VRF_LITE_CONN"] = json.dumps(
3345+
extension_values["VRF_LITE_CONN"]
3346+
)
32753347
vrf_attach["extensionValues"] = json.dumps(extension_values).replace(" ", "")
32763348
if vrf_attach.get("vrf_lite") is not None:
32773349
del vrf_attach["vrf_lite"]
@@ -3736,44 +3808,48 @@ def release_orphaned_resources(self, vrf_del_list, is_rollback=False):
37363808

37373809
path = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/"
37383810
path += f"resource-manager/fabric/{self.fabric}/"
3739-
path += "pools/TOP_DOWN_VRF_VLAN"
3740-
resp = dcnm_send(self.module, "GET", path)
3741-
self.result["response"].append(resp)
3742-
fail, self.result["changed"] = self.handle_response(resp, "deploy")
3743-
if fail:
3744-
if is_rollback:
3745-
self.failed_to_rollback = True
3746-
return
3747-
self.failure(resp)
3811+
resource_pool = ["TOP_DOWN_VRF_VLAN", "TOP_DOWN_L3_DOT1Q"]
3812+
for pool in resource_pool:
3813+
msg = f"Processing orphaned resources in pool:{pool}"
3814+
self.log.debug(msg)
3815+
req_path = path + f"pools/{pool}"
3816+
resp = dcnm_send(self.module, "GET", req_path)
3817+
self.result["response"].append(resp)
3818+
fail, self.result["changed"] = self.handle_response(resp, "deploy")
3819+
if fail:
3820+
if is_rollback:
3821+
self.failed_to_rollback = True
3822+
return
3823+
self.failure(resp)
37483824

3749-
delete_ids = []
3750-
for item in resp["DATA"]:
3751-
if "entityName" not in item:
3752-
continue
3753-
if item["entityName"] not in vrf_del_list:
3754-
continue
3755-
if item.get("allocatedFlag") is not False:
3756-
continue
3757-
if item.get("id") is None:
3758-
continue
3759-
# Resources with no ipAddress or switchName
3760-
# are invalid and of Fabric's scope and
3761-
# should not be attempted to be deleted here.
3762-
if not item.get("ipAddress"):
3763-
continue
3764-
if not item.get("switchName"):
3765-
continue
3825+
delete_ids = []
3826+
for item in resp["DATA"]:
3827+
if "entityName" not in item:
3828+
continue
3829+
if item["entityName"] not in vrf_del_list:
3830+
continue
3831+
if item.get("allocatedFlag") is not False:
3832+
continue
3833+
if item.get("id") is None:
3834+
continue
3835+
# Resources with no ipAddress or switchName
3836+
# are invalid and of Fabric's scope and
3837+
# should not be attempted to be deleted here.
3838+
if not item.get("ipAddress"):
3839+
continue
3840+
if not item.get("switchName"):
3841+
continue
37663842

3767-
msg = f"item {json.dumps(item, indent=4, sort_keys=True)}"
3768-
self.log.debug(msg)
3843+
msg = f"item {json.dumps(item, indent=4, sort_keys=True)}"
3844+
self.log.debug(msg)
37693845

3770-
delete_ids.append(item["id"])
3846+
delete_ids.append(item["id"])
37713847

3772-
if len(delete_ids) == 0:
3773-
return
3774-
msg = f"Releasing orphaned resources with IDs:{delete_ids}"
3775-
self.log.debug(msg)
3776-
self.release_resources_by_id(delete_ids)
3848+
if len(delete_ids) == 0:
3849+
return
3850+
msg = f"Releasing orphaned resources with IDs:{delete_ids}"
3851+
self.log.debug(msg)
3852+
self.release_resources_by_id(delete_ids)
37773853

37783854
def push_to_remote(self, is_rollback=False):
37793855
"""

0 commit comments

Comments
 (0)