From 671ab987cc189503ca7db59ecd9e7b62f1628e94 Mon Sep 17 00:00:00 2001 From: mwiebe Date: Wed, 13 Nov 2024 22:34:17 -0500 Subject: [PATCH 1/5] Enable useVirtualPeerlink for state merged --- .../network/dcnm/dcnm_vpc_pair_utils.py | 20 +++++++++++++++++++ plugins/modules/dcnm_vpc_pair.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py b/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py index f3f243901..874128578 100644 --- a/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py +++ b/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py @@ -22,6 +22,7 @@ "VPC_PAIR_DELETE_PATH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/vpcpair?serialNumber={}", "VPC_PAIR_DEPLOY_PATH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{}/config-deploy/{}?forceShowRun=false", "VPC_PAIR_CFG_SAVE_PATH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{}/config-save", + "VPC_PEER_LINK_GET_PATH": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/vpcpair/recommendation?serialNumber={}&useVirtualPeerlink=true", "FABRIC_ACCESS_MODE": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{}/accessmode", }, } @@ -209,6 +210,21 @@ def dcnm_vpc_pair_utils_get_vpc_pair_info_from_dcnm(self, swid): else: return [] + # Get useVirtualPeerlink information + path = self.paths["VPC_PEER_LINK_GET_PATH"] + path = path.format(peerOneId) + resp = dcnm_send(self.module, "GET", path) + + if ( + resp + and (resp["RETURN_CODE"] == 200) + and (resp["MESSAGE"] == "OK") + and resp["DATA"] + ): + useVirtualPeerlink = resp["DATA"][0].get("useVirtualPeerlink", None) + else: + return [] + # Get the Profile information now and combine both the first response data and the current one to # form the 'have' object. There is no direct call to get this combined information. path = self.paths["VPC_PAIR_GET_POLICY_PATH"] @@ -226,6 +242,7 @@ def dcnm_vpc_pair_utils_get_vpc_pair_info_from_dcnm(self, swid): resp["DATA"]["peerTwoId"] = peerTwoId resp["DATA"]["peerOneDbId"] = peerOneDbId resp["DATA"]["peerTwoDbId"] = peerTwoDbId + resp["DATA"]["useVirtualPeerlink"] = useVirtualPeerlink # Some of the fields in 'have' may be different than what is sent in CREATE/UPDATE/DELETE payloads to DCNM. Update these # fields,if any, so that all keys are consistent between 'want' and 'have'. This will be necessary for compare function to @@ -634,6 +651,9 @@ def dcnm_vpc_pair_utils_process_modify_payloads(self): path = self.paths["VPC_PAIR_UPDATE_PATH"] json_payload = json.dumps(elem) + # Sample json_payload + # '{"useVirtualPeerlink":true,"peerOneId":"FDO24020JMB","peerTwoId":"FDO24020JMT"}' + resp = dcnm_send(self.module, "PUT", path, json_payload) if resp != []: diff --git a/plugins/modules/dcnm_vpc_pair.py b/plugins/modules/dcnm_vpc_pair.py index 4376a0937..0130db341 100644 --- a/plugins/modules/dcnm_vpc_pair.py +++ b/plugins/modules/dcnm_vpc_pair.py @@ -909,7 +909,7 @@ def dcnm_vpc_pair_validate_input(self, cfg): "peerOneId": {"required": "True", "type": "ipv4"}, "peerTwoId": {"required": "True", "type": "ipv4"}, "templateName": {"type": "str"}, - "useVirtualPeerLink": {"type": "bool"}, + "useVirtualPeerlink": {"type": "bool"}, "profile": {"type": "dict"}, } From a3da494b7ab359beb50700cddef4b6c969f20479 Mon Sep 17 00:00:00 2001 From: mwiebe Date: Thu, 14 Nov 2024 16:34:46 -0500 Subject: [PATCH 2/5] Update unit tests --- .../network/dcnm/dcnm_vpc_pair_utils.py | 1 + .../dcnm_vpc_pair/dcnm_vpc_pair_response.json | 26 +++++++++++ tests/unit/modules/dcnm/test_dcnm_vpc_pair.py | 45 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py b/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py index 874128578..06d509054 100644 --- a/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py +++ b/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py @@ -213,6 +213,7 @@ def dcnm_vpc_pair_utils_get_vpc_pair_info_from_dcnm(self, swid): # Get useVirtualPeerlink information path = self.paths["VPC_PEER_LINK_GET_PATH"] path = path.format(peerOneId) + resp = dcnm_send(self.module, "GET", path) if ( diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_response.json b/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_response.json index 48a8de170..dfeeb9d20 100644 --- a/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_response.json +++ b/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_response.json @@ -1193,6 +1193,32 @@ } }, + "vpc_pair_info_virtual_peer_link_resp": + { + "DATA": [{ + "blockSelection": "False", + "currentPeer": "True", + "ethSwitchId": 178800, + "fabricName": "None", + "ipAddress": "172.31.217.103", + "lanId": 0, + "logicalName": "93180YC-FX3S-L1-S1", + "nxosVersion": "10.4(3)", + "platformType": "None", + "recommendationReason": "Switches have same role and support Virtual Fabric Peering", + "recommended": "True", + "serialNumber": "FDO24020JMT", + "useVirtualPeerlink": "False", + "uuid": "None", + "vdcId": 0, + "vdcName": "None" + }], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/vpcpair/recommendation?serialNumber=FDO24020JMB&useVirtualPeerlink=true", + "RETURN_CODE": 200 + }, + "vpc_pair_policy_resp_84": { "RETURN_CODE": 200, diff --git a/tests/unit/modules/dcnm/test_dcnm_vpc_pair.py b/tests/unit/modules/dcnm/test_dcnm_vpc_pair.py index 12796cfaa..ddb8dd6c7 100644 --- a/tests/unit/modules/dcnm/test_dcnm_vpc_pair.py +++ b/tests/unit/modules/dcnm/test_dcnm_vpc_pair.py @@ -185,8 +185,10 @@ def test_dcnm_vpc_pair_00003( if tc_id < 7: dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_00003")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_00003")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_00003")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_00003")) ) @@ -250,22 +252,28 @@ def test_dcnm_vpc_pair_00004( resp = load_data("dcnm_vpc_pair_response") dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_1")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_2")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_2")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_3")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_3")) ) @@ -341,12 +349,15 @@ def test_dcnm_vpc_pair_00005( if tc_id < 3: dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_2")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_3")) elif tc_id == 3: dcnm_send_side_effect.append([]) @@ -356,23 +367,29 @@ def test_dcnm_vpc_pair_00005( vpc_pair.vpc_pair_info[0]["peerOneId"] = "10.122.84.190" dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_2")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_3")) elif tc_id == 5: vpc_pair.vpc_pair_info[0]["peerTwoId"] = "10.122.84.190" dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_2")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_3")) update_delete_payloads_side_effect.append( @@ -722,14 +739,18 @@ def test_dcnm_vpc_pair_00010( resp = load_data("dcnm_vpc_pair_response") dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_1")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_2")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_2")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_3")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_3")) mock_dcnm_send = Mock(side_effect=dcnm_send_side_effect) @@ -778,6 +799,7 @@ def test_dcnm_vpc_pair_00010_2( resp = load_data("dcnm_vpc_pair_response") dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) mock_dcnm_send = Mock(side_effect=dcnm_send_side_effect) @@ -823,6 +845,7 @@ def test_dcnm_vpc_pair_00010_3( resp = load_data("dcnm_vpc_pair_response") dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_1")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_1")) mock_dcnm_send = Mock(side_effect=dcnm_send_side_effect) @@ -1912,10 +1935,13 @@ def test_dcnm_vpc_pair_merged_existing(self): # dcnm_send() invoked from module_utils/dcnm_vpc_pair_utils.py dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_84")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_85")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_86")) dcnm_send_side_effect.append(resp.get("vpc_pair_sync_status_in_sync")) dcnm_send_side_effect.append(resp.get("vpc_pair_sync_status_in_sync")) @@ -2042,10 +2068,13 @@ def test_dcnm_vpc_pair_delete_existing(self): # dcnm_send() invoked from module_utils/dcnm_vpc_pair_utils.py dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_84")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_85")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_86")) dcnm_send_side_effect.append(resp.get("vpc_pair_delete_succ_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_delete_succ_resp")) @@ -2260,17 +2289,21 @@ def test_dcnm_vpc_pair_override_existing_cfg(self): # dcnm_send() invoked from module_utils/dcnm_vpc_pair_utils.py dcnm_send_side_effect.append(resp.get("vpc_pair_null_have")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_85")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_86")) dcnm_send_side_effect.append(resp.get("vpc_pair_null_have")) dcnm_send_side_effect.append(resp.get("vpc_pair_null_have")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_85")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_86")) ) @@ -2389,21 +2422,27 @@ def test_dcnm_vpc_pair_override_existing_no_new_cfg(self): # dcnm_send() invoked from module_utils/dcnm_vpc_pair_utils.py dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_84")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_85")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_86")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_84")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_85")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_86")) ) @@ -2507,21 +2546,27 @@ def test_dcnm_vpc_pair_query(self): # dcnm_send() invoked from module_utils/dcnm_vpc_pair_utils.py dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_84")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_85")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append(resp.get("vpc_pair_policy_resp_86")) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_84")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_84")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_85")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_85")) ) dcnm_send_side_effect.append(resp.get("vpc_pair_info_resp_86")) + dcnm_send_side_effect.append(resp.get("vpc_pair_info_virtual_peer_link_resp")) dcnm_send_side_effect.append( copy.deepcopy(resp.get("vpc_pair_policy_resp_86")) ) From a6d91a8149674b856c46f5144f9f4aa5c012ea0b Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 15 Nov 2024 11:58:55 -1000 Subject: [PATCH 3/5] Fix for issue #343 1. module_utils/fabric/create.py Modify the payload for external fabric types to include EXT_FABRIC_TYPE key with value being the default value that the NDFC GUI uses to display the fabric type. 2. module_utils/fabric/fabric_types.py Add a mapping between external fabric types and the string NDFC uses when displaying the fabric type. --- plugins/module_utils/fabric/create.py | 42 +++++++++++++++++--- plugins/module_utils/fabric/fabric_types.py | 44 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index 8c404908f..d70f8342f 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -23,12 +23,15 @@ import json import logging -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricCreate -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ - FabricCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_types import \ - FabricTypes +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import ( + EpFabricCreate, +) +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import ( + FabricCommon, +) +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_types import ( + FabricTypes, +) class FabricCreateCommon(FabricCommon): @@ -112,6 +115,31 @@ def _set_fabric_create_endpoint(self, payload): self.path = self.ep_fabric_create.path self.verb = self.ep_fabric_create.verb + def _fixup_payload_ext_fabric_type(self, payload: dict) -> dict: + """ + # Summary + + If the payload contains an external fabric type (e.g ISN) + and does not contain the EXT_FABRIC_TYPE key, add this + key with the default value that NDFC GUI uses for displaying + the fabric type. + + # Raises + + None + """ + self.log.debug(f"ZZZ: payload {json.dumps(payload, indent=4, sort_keys=True)}") + fabric_type = payload.get("FABRIC_TYPE") + if fabric_type not in self.fabric_types.external_fabric_types: + return payload + if "EXT_FABRIC_TYPE" in payload: + return payload + value = self.fabric_types.fabric_type_to_ext_fabric_type_map.get(fabric_type) + if value is None: + return payload + payload["EXT_FABRIC_TYPE"] = value + return payload + def _send_payloads(self): """ - If ``check_mode`` is ``False``, send the payloads @@ -125,6 +153,8 @@ def _send_payloads(self): - This overrides the parent class method. """ for payload in self._payloads_to_commit: + payload = self._fixup_payload_ext_fabric_type(payload) + try: self._set_fabric_create_endpoint(payload) except ValueError as error: diff --git a/plugins/module_utils/fabric/fabric_types.py b/plugins/module_utils/fabric/fabric_types.py index f96dc41ce..74010c9b3 100644 --- a/plugins/module_utils/fabric/fabric_types.py +++ b/plugins/module_utils/fabric/fabric_types.py @@ -88,8 +88,26 @@ def _init_fabric_types(self) -> None: self._fabric_type_to_feature_name_map["VXLAN_EVPN"] = "vxlan" self._fabric_type_to_feature_name_map["VXLAN_EVPN_MSD"] = "vxlan" + # Map fabric type to the value that the controller GUI displays + # in the Fabric Type column at NDFC -> Manage -> Fabrics + # This is needed only for fabrics that use the External_Fabric + # template, e.g. ISN, and will be inserted into the POST request + # payload for external fabrics as (in the case of ISN fabric type): + # "EXT_FABRIC_TYPE": "Multi-Site External Network" + # + # Exposed via property fabric_type_to_ext_fabric_type_map + self._fabric_type_to_ext_fabric_type_map = {} + self._fabric_type_to_ext_fabric_type_map["ISN"] = "Multi-Site External Network" + self._valid_fabric_types = sorted(self._fabric_type_to_template_name_map.keys()) + # Adding self._external_fabric_types to be used in conjunction with + # self._fabric_type_to_ext_fabric_type_map. This is used in (at least) + # FabricCreateCommon() to determine if EXT_FABRIC_TYPE key needs to be + # added to a payload. + self._external_fabric_types = set() + self._external_fabric_types.add("ISN") + self._mandatory_parameters_all_fabrics = [] self._mandatory_parameters_all_fabrics.append("FABRIC_NAME") self._mandatory_parameters_all_fabrics.append("FABRIC_TYPE") @@ -128,6 +146,19 @@ def _init_properties(self) -> None: self._properties["template_name"] = None self._properties["valid_fabric_types"] = self._valid_fabric_types + @property + def external_fabric_types(self): + """ + # Summary + + set() containing all external fabric types e.g. ISN. + + # Raises + + None + """ + return self._external_fabric_types + @property def fabric_type(self): """ @@ -150,6 +181,19 @@ def fabric_type(self, value): raise ValueError(msg) self._properties["fabric_type"] = value + @property + def fabric_type_to_ext_fabric_type_map(self): + """ + # Summary + + Returns a dictionary, keyed on fabric_type (e.g. "ISN"), + whose value is a string that the NDFC GUI uses to describe the + external fabric type. See the Fabric Type column at + NDFC -> Manage -> Fabrics for an example of how this is used + by the NDFC GUI. + """ + return self._fabric_type_to_ext_fabric_type_map + @property def feature_name(self): """ From 2f7ea180b961b0191ce50252cb97f05abe1bbdac Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 15 Nov 2024 14:21:41 -1000 Subject: [PATCH 4/5] Add log message when payload is modified. 1. plugins/module_utils/fabric/create.py a. Change the method name _fixup_payload_ext_fabric_type() to _add_ext_fabric_type_to_payload() b. Add a log message in _add_ext_fabric_type_to_payload() when a payload has been modified. --- plugins/module_utils/fabric/create.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index d70f8342f..298fab3d7 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -115,7 +115,7 @@ def _set_fabric_create_endpoint(self, payload): self.path = self.ep_fabric_create.path self.verb = self.ep_fabric_create.verb - def _fixup_payload_ext_fabric_type(self, payload: dict) -> dict: + def _add_ext_fabric_type_to_payload(self, payload: dict) -> dict: """ # Summary @@ -128,7 +128,8 @@ def _fixup_payload_ext_fabric_type(self, payload: dict) -> dict: None """ - self.log.debug(f"ZZZ: payload {json.dumps(payload, indent=4, sort_keys=True)}") + method_name = inspect.stack()[0][3] + fabric_type = payload.get("FABRIC_TYPE") if fabric_type not in self.fabric_types.external_fabric_types: return payload @@ -138,6 +139,12 @@ def _fixup_payload_ext_fabric_type(self, payload: dict) -> dict: if value is None: return payload payload["EXT_FABRIC_TYPE"] = value + + msg = f"{self.class_name}.{method_name}: " + msg += "Added EXT_FABRIC_TYPE to payload. " + msg += f"fabric_type: {fabric_type}, " + msg += f"value: {value}" + self.log.debug(msg) return payload def _send_payloads(self): @@ -153,7 +160,7 @@ def _send_payloads(self): - This overrides the parent class method. """ for payload in self._payloads_to_commit: - payload = self._fixup_payload_ext_fabric_type(payload) + payload = self._add_ext_fabric_type_to_payload(payload) try: self._set_fabric_create_endpoint(payload) From 95c7f626df0758412ec54f73d6f2267111024a8b Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Fri, 15 Nov 2024 14:26:39 -1000 Subject: [PATCH 5/5] Update comment 1. module_utils.fabric/fabric_types.py Edit comment to mention that the private set is exposed via property external_fabric_types. --- plugins/module_utils/fabric/fabric_types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/module_utils/fabric/fabric_types.py b/plugins/module_utils/fabric/fabric_types.py index 74010c9b3..cb737b53a 100644 --- a/plugins/module_utils/fabric/fabric_types.py +++ b/plugins/module_utils/fabric/fabric_types.py @@ -101,10 +101,12 @@ def _init_fabric_types(self) -> None: self._valid_fabric_types = sorted(self._fabric_type_to_template_name_map.keys()) - # Adding self._external_fabric_types to be used in conjunction with + # self._external_fabric_types is used in conjunction with # self._fabric_type_to_ext_fabric_type_map. This is used in (at least) # FabricCreateCommon() to determine if EXT_FABRIC_TYPE key needs to be # added to a payload. + # + # Exposed via property external_fabric_types self._external_fabric_types = set() self._external_fabric_types.add("ISN")