diff --git a/playbooks/roles/dcnm_vpc_pair/dcnm_hosts.yaml b/playbooks/roles/dcnm_vpc_pair/dcnm_hosts.yaml new file mode 100644 index 000000000..32291be5e --- /dev/null +++ b/playbooks/roles/dcnm_vpc_pair/dcnm_hosts.yaml @@ -0,0 +1,18 @@ +all: + vars: + ansible_user: "admin" + ansible_password: "password-secret" + ansible_python_interpreter: python + ansible_httpapi_validate_certs: False + ansible_httpapi_use_ssl: True + children: + dcnm: + vars: + ansible_it_fabric: fabric-stage + ansible_connection: ansible.netcommon.httpapi + ansible_network_os: cisco.dcnm.dcnm + ansible_httpapi_validate_certs: no + hosts: + nac-ndfc1: + ansible_host: 10.10.5.1 + diff --git a/playbooks/roles/dcnm_vpc_pair/dcnm_tests.yaml b/playbooks/roles/dcnm_vpc_pair/dcnm_tests.yaml new file mode 100644 index 000000000..cee0d0648 --- /dev/null +++ b/playbooks/roles/dcnm_vpc_pair/dcnm_tests.yaml @@ -0,0 +1,78 @@ +--- +# This playbook can be used to execute integration tests for +# the role located in: +# +# tests/integration/targets/dcnm_image_policy +# +# Modify the hosts and vars sections with details for your testing +# setup and uncomment the testcase you want to run. +# +- hosts: dcnm + gather_facts: no + connection: ansible.netcommon.httpapi + + vars: + switch_username: admin + switch_password: "password-secret" + ansible_it_fabric: fabric-stage + ansible_switch1: 192.168.1.1 + ansible_switch2: 192.168.1.2 + ansible_peer1_ip: 192.168.1.1 + ansible_peer2_ip: 192.168.1.2 + ansible_vxlan_vpc_domain_id: 1000 + + config: + - peerOneId: "{{ ansible_switch1 }}" + peerTwoId: "{{ ansible_switch2 }}" + templateName: "vpc_pair" # Using the correct template name + profile: + # Required fields for VPC template + ADMIN_STATE: true + ALLOWED_VLANS: "all" + DOMAIN_ID: "{{ ansible_vxlan_vpc_domain_id }}" + FABRIC_NAME: "{{ ansible_it_fabric }}" + KEEP_ALIVE_HOLD_TIMEOUT: 3 + KEEP_ALIVE_VRF: "management" + PC_MODE: "active" + PEER1_KEEP_ALIVE_LOCAL_IP: "{{ ansible_peer1_ip }}" + PEER1_MEMBER_INTERFACES: "eth1/1" + PEER1_PCID: 1 + PEER2_KEEP_ALIVE_LOCAL_IP: "{{ ansible_peer2_ip }}" + PEER2_MEMBER_INTERFACES: "eth1/1" + PEER2_PCID: 2 + + # Additional required fields + peer1Ip: "{{ ansible_peer1_ip }}" + peer2Ip: "{{ ansible_peer2_ip }}" + vpcDomainId: "{{ ansible_vxlan_vpc_domain_id }}" + adminState: true + keepAliveVrf: "management" + keepAliveHoldTimeout: 3 + keepAliveLocalIp: "{{ ansible_peer1_ip }}" + keepAliveRemoteIp: "{{ ansible_peer2_ip }}" + + # Template specific fields + templateName: "vpc_pair" + templatePropId: "" + templatePropName: "vpc_pair" + templatePropDescription: "VPC Template" + templatePropDataType: "JSON" + templatePropDefaultValue: "" + templatePropDisplayName: "VPC Configuration" + templatePropIsMandatory: true + templatePropIsMultiSelect: false + templatePropIsPassword: false + templatePropIsReadOnly: false + templatePropIsRequired: true + templatePropIsSecure: false + templatePropIsSortable: false + templatePropIsVisible: true + templatePropOptions: [] + templatePropRange: [] + templatePropValue: "" + templatePropValueType: "STRING" + templateIPAddress: "{{ ansible_peer1_ip }}" + + + roles: + - dcnm_vpc_pair diff --git a/plugins/module_utils/network/dcnm/dcnm.py b/plugins/module_utils/network/dcnm/dcnm.py index 293eb7631..7459a1fb6 100644 --- a/plugins/module_utils/network/dcnm/dcnm.py +++ b/plugins/module_utils/network/dcnm/dcnm.py @@ -48,6 +48,7 @@ "long": "int", "ipV4Address": "ipv4", "ipV6Address": "ipv6", + "ipAddress": "ipv4", "interfaceRange": "list", "boolean": "bool", "enum": "str", 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 d99aac632..fd3d9be44 100644 --- a/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py +++ b/plugins/module_utils/network/dcnm/dcnm_vpc_pair_utils.py @@ -389,40 +389,43 @@ def dcnm_vpc_pair_compare_vpc_pair_objects(self, wobj, hobj): mismatch_reasons = [] for key in wobj: - if "_defaulted" in key: continue - if str(hobj.get(key, None)).lower() != str(wobj[key]).lower(): - if key == "nvPairs": - continue - - # We found an object that matched all other key values, but differs in one of the params. - mismatch_reasons.append( - {key.upper() + "_MISMATCH": [wobj[key], hobj.get(key, None)]} - ) + # Special handling for useVirtualPeerlink to treat None and False as equivalent + if key == "useVirtualPeerlink": + # Cast both values to boolean to treat None and False equivalently + hval = bool(hobj.get(key, False)) + wval = bool(wobj[key]) + if hval != wval: + mismatch_reasons.append( + {key.upper() + "_MISMATCH": [wobj[key], hobj.get(key, None)]} + ) + elif key != "nvPairs": # Skip nvPairs here as it's handled separately + if str(hobj.get(key, False)).lower() != str(wobj[key]).lower(): + mismatch_reasons.append( + {key.upper() + "_MISMATCH": [wobj[key], hobj.get(key, None)]} + ) if wobj.get("nvPairs", None) is not None: - for key in wobj["nvPairs"]: - if "_defaulted" in key: continue - if ( - str(hobj["nvPairs"].get(key, None)).lower() - != str(wobj["nvPairs"][key]).lower() - ): - # We found an object that matched all other key values, but differs in one of the params. - mismatch_reasons.append( - { - key.upper() - + "_MISMATCH": [ - wobj["nvPairs"][key], - hobj["nvPairs"].get(key, None), - ] - } - ) + # Special handling for useVirtualPeerlink to treat None and False as equivalent + if key == "useVirtualPeerlink": + # Apply same boolean logic to useVirtualPeerlink in nvPairs + hval = bool(hobj["nvPairs"].get(key, False)) + wval = bool(wobj["nvPairs"][key]) + if hval != wval: + mismatch_reasons.append( + {key.upper() + "_MISMATCH": [wval, hval]} + ) + else: + if str(hobj["nvPairs"].get(key, False)).lower() != str(wobj["nvPairs"][key]).lower(): + mismatch_reasons.append( + {key.upper() + "_MISMATCH": [wobj["nvPairs"][key], hobj["nvPairs"].get(key, None)]} + ) if mismatch_reasons != []: return "DCNM_VPC_PAIR_MERGE", mismatch_reasons, hobj diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_data.json b/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_data.json index dcbc14d9b..fd34dc033 100644 --- a/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_data.json +++ b/tests/unit/modules/dcnm/fixtures/dcnm_vpc_pair/dcnm_vpc_pair_data.json @@ -59,7 +59,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None" + "useVirtualPeerLink": false }, "vpc_pair_want": @@ -67,7 +67,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "nvPairs": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -110,7 +110,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "nvPairs": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -205,7 +205,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None" + "useVirtualPeerLink": false }, "vpc_pair_query_cfg_00003_11":[ @@ -246,7 +246,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "nvPairs": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -289,7 +289,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None" + "useVirtualPeerLink": false }, "vpc_pair_vpc_info_00005": @@ -298,7 +298,7 @@ "peerOneId": "10.122.84.174", "peerTwoId": "10.122.84.175", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "profile": { "ADMIN_STATE": true, "ALLOWED_VLANS": "", @@ -339,7 +339,7 @@ "peerOneId": "10.122.85.174", "peerTwoId": "10.122.85.175", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "profile": { "ADMIN_STATE": true, "ALLOWED_VLANS": "", @@ -380,7 +380,7 @@ "peerOneId": "10.122.86.174", "peerTwoId": "10.122.86.175", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "profile": { "ADMIN_STATE": true, "ALLOWED_VLANS": "", @@ -497,7 +497,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "nvPairs": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -538,7 +538,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "nvPairs": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -579,7 +579,7 @@ "peerOneId": "SAL1820SDPQ", "peerTwoId": "SAL1819S6K4", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "profile": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -620,7 +620,7 @@ "peerOneId": "SAL1820SDPR", "peerTwoId": "SAL1819S6K5", "templateName": "vpc_pair", - "useVirtualPeerLink": "None", + "useVirtualPeerLink": false, "profile": { "PEER2_MEMBER_INTERFACES_defaulted": false, "PEER1_MEMBER_INTERFACES_defaulted": false, @@ -1001,7 +1001,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink": null + "useVirtualPeerLink": false }, "vpc_pair_del_list_1": [ @@ -1025,7 +1025,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", @@ -1062,7 +1062,7 @@ "peerOneId": "SAL1820SDPQ", "peerTwoId": "SAL1819S6K4", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", @@ -1099,7 +1099,7 @@ "peerOneId": "SAL1820SDPR", "peerTwoId": "SAL1819S6K5", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", @@ -1138,7 +1138,7 @@ "peerOneId": "SAL1820SDPP", "peerTwoId": "SAL1819S6K3", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", @@ -1175,7 +1175,7 @@ "peerOneId": "SAL1820SDPQ", "peerTwoId": "SAL1819S6K4", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", @@ -1212,7 +1212,7 @@ "peerOneId": "SAL1820SDPR", "peerTwoId": "SAL1819S6K5", "templateName": "vpc_pair", - "useVirtualPeerLink":null, + "useVirtualPeerLink": false, "nvPairs": { "ADMIN_STATE": true, "ALLOWED_VLANS": "all", 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 dfeeb9d20..47693caeb 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 @@ -1208,7 +1208,7 @@ "recommendationReason": "Switches have same role and support Virtual Fabric Peering", "recommended": "True", "serialNumber": "FDO24020JMT", - "useVirtualPeerlink": "False", + "useVirtualPeerlink": false, "uuid": "None", "vdcId": 0, "vdcName": "None"