diff --git a/aci-preupgrade-validation-script.py b/aci-preupgrade-validation-script.py index da1b4b9e..23c6a525 100644 --- a/aci-preupgrade-validation-script.py +++ b/aci-preupgrade-validation-script.py @@ -6410,6 +6410,107 @@ def svccore_excessive_data_check(**kwargs): return Result(result=ERROR, msg="Error occurred while fetching svccore object counts: {}".format(str(e)), doc_url=doc_url) +@check_wrapper(check_title="Check missing vnsRsCIfAttN") +def vnsrscifattn_missing_check(tversion, **kwargs): + result = PASS + headers = ["Tenant", "Device Name", "Cluster Interface", "Missing Concrete Interface", "vnsRsCIfAtt DN"] + manual_headers = ["Tenant", "Device Name", "Cluster Interface", "Missing Concrete Interface", "vnsLIf DN"] + data = [] + recommended_action = ( + "From 6.0(3) release, Mo vnsRsCIfAtt is deprecated. Before upgrade, reattach any missing concrete interface mapping under the same L4-L7 device cluster interface so the corresponding vnsRsCIfAttN relation exists." + ) + doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#check-missing-vnsrscifattn" + + if not tversion: + return Result(result=MANUAL, msg=TVER_MISSING, doc_url=doc_url) + + if tversion.older_than("6.0(3d)"): + return Result(result=NA, msg=VER_NOT_AFFECTED, doc_url=doc_url) + + vnsRsCIfAtts = icurl("class", "vnsRsCIfAtt.json?rsp-prop-include=config-only") + vnsRsCIfAttNs = icurl("class", "vnsRsCIfAttN.json?rsp-prop-include=config-only") + + if not vnsRsCIfAtts and not vnsRsCIfAttNs: + vnsLIfs = icurl("class", "vnsLIf.json?rsp-prop-include=config-only") + for vnsLIf in vnsLIfs: + try: + lif_dn = vnsLIf["vnsLIf"]["attributes"]["dn"].strip() + except (KeyError, TypeError, AttributeError): + continue + if not lif_dn: + continue + + match = re.search( + r"uni/tn-(?P[^/]+)/lDevVip-(?P[^/]+)/lIf-(?P[^/]+)$", + lif_dn, + ) + data.append([ + match.group("tenant") if match else "", + match.group("device") if match else "", + match.group("lif") if match else "", + "N/A", + lif_dn, + ]) + + data.sort(key=lambda row: row[-1]) + + if data: + msg = "vnsLIf has neither vnsRsCIfAtt nor vnsRsCIfAttN. Manual verification required to avoid service graph inconsistency." + manual_action = ( + "Under each impacted L4-L7 device cluster interface, manually verify and reattach concrete interfaces as needed." + ) + return Result(result=MANUAL, msg=msg, headers=manual_headers, data=data, recommended_action=manual_action, doc_url=doc_url) + + msg = "Both vnsRsCIfAtt and vnsRsCIfAttN are missing. Manual verification required for service graph interface attachments." + manual_action = ( + "Validate L4-L7 device cluster interfaces and reattach concrete interfaces where needed." + ) + return Result(result=MANUAL, msg=msg, recommended_action=manual_action, doc_url=doc_url) + + if not vnsRsCIfAtts: + return Result(result=PASS, msg="No user-configured vnsRsCIfAtt payload found.", doc_url=doc_url) + + new_dn_keys = set() + for new_mo in vnsRsCIfAttNs: + try: + dn = new_mo["vnsRsCIfAttN"]["attributes"]["dn"].strip() + except (KeyError, TypeError, AttributeError): + continue + if dn: + new_dn_keys.add(dn.replace("/rscIfAttN-[", "/rscIfAtt-[", 1)) + + for old_mo in vnsRsCIfAtts: + try: + old_dn = old_mo["vnsRsCIfAtt"]["attributes"]["dn"].strip() + except (KeyError, TypeError, AttributeError): + continue + if not old_dn: + continue + + if old_dn.replace("/rscIfAttN-[", "/rscIfAtt-[", 1) in new_dn_keys: + continue + + match = re.search( + r"uni/tn-(?P[^/]+)/lDevVip-(?P[^/]+)/lIf-(?P[^/]+)/" + r"rscIfAtt-\[.*?/cIf-\[(?P[^\]]+)\]\]", + old_dn, + ) + data.append([ + match.group("tenant") if match else "", + match.group("device") if match else "", + match.group("lif") if match else "", + match.group("cif") if match else "", + old_dn, + ]) + + data.sort(key=lambda row: row[-1]) + + if data: + result = FAIL_O + + return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url) + + # ---- Script Execution ---- @@ -6502,6 +6603,7 @@ class CheckManager: fabric_link_redundancy_check, apic_downgrade_compat_warning_check, svccore_excessive_data_check, + vnsrscifattn_missing_check, # Faults apic_disk_space_faults_check, diff --git a/docs/docs/validations.md b/docs/docs/validations.md index 82f22119..c8a7a93d 100644 --- a/docs/docs/validations.md +++ b/docs/docs/validations.md @@ -39,6 +39,7 @@ Items | This Script [APIC downgrade compatibility when crossing 6.2 release][g19]| :white_check_mark: | :no_entry_sign: [Supported Hardware Compatibility][g20] | :white_check_mark: | :no_entry_sign: [Svccore Excessive Data Check][g21] | :white_check_mark: | :no_entry_sign: +[Check missing vnsRsCIfAttN][g22] | :white_check_mark: | :no_entry_sign: [g1]: #compatibility-target-aci-version [g2]: #compatibility-cimc-version @@ -61,6 +62,7 @@ Items | This Script [g19]: #apic-downgrade-compatibility-when-crossing-62-release [g20]: #supported-hardware-compatibility [g21]: #svccore-excessive-data-check +[g22]: #check-missing-vnsrscifattn ### Fault Checks Items | Faults | This Script | APIC built-in @@ -2769,7 +2771,6 @@ This issue happens only when the target version is specifically 6.1(4h). To avoid this issue, change the target version to another version. Or verify that the `bootscript` file exists in the bootflash of each modular spine switch prior to upgrading to 6.1(4h). If the file is missing, you have to do clean reboot on the impacted spine to ensure that `/bootflash/bootscript` gets created again. In case you already upgraded your spine and you are experiencing the traffic impact due to this issue, clean reboot of the spine will restore the traffic. - ### Inband Management Policy Misconfiguration Due to the defect [CSCwh80837][67], starting from version 6.0(4c), mgmtRsInBStNode policy get modified in leaf/spine during Apic upgrade. @@ -2797,6 +2798,13 @@ Administrators may be unable to access or operate the APIC GUI, potentially impa This check will verify the count of the `svccoreCtrlr` Managed Object and raise and alarm with the bug if object count found more than 240. Remove the content or objects of `svccoreCtrlr` or `svccoreNode`. Contact Cisco TAC or upgrade to a release containing the fix for CSCws84232 before proceeding with an upgrade. +### Check missing vnsRsCIfAttN + +When upgrading to 6.0(3) and above, 'vnsRsCIfAtt' is deleted without creating 'vnsRsCIfAttN' under 'vnsLIf'. This leaves the service graph interface attachment in an inconsistent state. + +For all impacted DNs in this check, reattach the Concrete interfaces associated with the cluster interface under Devices in the Services > L4-L7 tab. + +Tenant --> Services --> L4-L7 --> Devices (Device_name) --> cluster interface --> Concrete interfaces [0]: https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script [1]: https://www.cisco.com/c/dam/en/us/td/docs/Website/datacenter/apicmatrix/index.html @@ -2867,4 +2875,4 @@ This check will verify the count of the `svccoreCtrlr` Managed Object and raise [66]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwr66848 [67]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwh80837 [68]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwd40071 -[69]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCws84232 \ No newline at end of file +[69]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCws84232 diff --git a/tests/checks/vnsrscifattn_missing_check/test_vnsrscifattn_missing_check.py b/tests/checks/vnsrscifattn_missing_check/test_vnsrscifattn_missing_check.py new file mode 100644 index 00000000..42014221 --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/test_vnsrscifattn_missing_check.py @@ -0,0 +1,131 @@ +import os +import pytest +import importlib +from helpers.utils import read_data + +script = importlib.import_module("aci-preupgrade-validation-script") + +dir = os.path.dirname(os.path.abspath(__file__)) + +test_function = "vnsrscifattn_missing_check" + +# icurl queries +vnsRsCIfAtt_api = "vnsRsCIfAtt.json?rsp-prop-include=config-only" +vnsRsCIfAttN_api = "vnsRsCIfAttN.json?rsp-prop-include=config-only" +vnsLIf_api = "vnsLIf.json?rsp-prop-include=config-only" + +@pytest.mark.parametrize( + "icurl_outputs, tversion, expected_result, expected_data", + [ + # Target version missing + ( + {}, + None, + script.MANUAL, + [], + ), + # Target version is not affected (< 6.0(3d)) + ( + {}, + "6.0(2h)", + script.NA, + [], + ), + # Both vnsRsCIfAtt and vnsRsCIfAttN are missing (no vnsLIf rows) + ( + { + vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_empty.json"), + vnsRsCIfAttN_api: read_data(dir, "vnsRsCIfAtt_empty.json"), + vnsLIf_api: [], + }, + "6.1(5e)", + script.MANUAL, + [], + ), + # Both vnsRsCIfAtt and vnsRsCIfAttN are missing while vnsLIf exists + ( + { + vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_empty.json"), + vnsRsCIfAttN_api: read_data(dir, "vnsRsCIfAtt_empty.json"), + vnsLIf_api: read_data(dir, "vnsLIf_only.json"), + }, + "6.1(5e)", + script.MANUAL, + [ + [ + "CSCwj49418", + "test", + "intf-cons", + "N/A", + "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons", + ], + [ + "CSCwj49418", + "test", + "intf-prov", + "N/A", + "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov", + ], + ], + ), + # All vnsRsCIfAtt relations have matching vnsRsCIfAttN relations + ( + { + vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_match.json"), + vnsRsCIfAttN_api: read_data(dir, "vnsRsCIfAttN_match.json"), + }, + "6.1(5e)", + script.PASS, + [], + ), + # One vnsRsCIfAtt relation (cons) missing in vnsRsCIfAttN + ( + { + vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_match.json"), + vnsRsCIfAttN_api: read_data(dir, "vnsRsCIfAttN_missing_cons.json"), + }, + "6.1(5e)", + script.FAIL_O, + [ + [ + "CSCwj49418", + "test", + "intf-cons", + "cons", + "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons/rscIfAtt-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]]", + ] + ], + ), + # vnsRsCIfAttN is empty; all vnsRsCIfAtt relations are missing + ( + { + vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_match.json"), + vnsRsCIfAttN_api: [], + }, + "6.1(5e)", + script.FAIL_O, + [ + [ + "CSCwj49418", + "test", + "intf-cons", + "cons", + "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons/rscIfAtt-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]]", + ], + [ + "CSCwj49418", + "test", + "intf-prov", + "prov", + "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov/rscIfAtt-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]]", + ], + ], + ), + ], +) +def test_logic(run_check, mock_icurl, tversion, expected_result, expected_data): + result = run_check( + tversion=script.AciVersion(tversion) if tversion else None, + ) + assert result.result == expected_result + assert result.data == expected_data diff --git a/tests/checks/vnsrscifattn_missing_check/vnsLIf_only.json b/tests/checks/vnsrscifattn_missing_check/vnsLIf_only.json new file mode 100644 index 00000000..0eb80a89 --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/vnsLIf_only.json @@ -0,0 +1,18 @@ +[ + { + "vnsLIf": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons", + "name": "intf-cons" + } + } + }, + { + "vnsLIf": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov", + "name": "intf-prov" + } + } + } +] diff --git a/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_match.json b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_match.json new file mode 100644 index 00000000..23447301 --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_match.json @@ -0,0 +1,18 @@ +[ + { + "vnsRsCIfAttN": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov/rscIfAttN-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]]", + "tDn": "uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]" + } + } + }, + { + "vnsRsCIfAttN": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons/rscIfAttN-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]]", + "tDn": "uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]" + } + } + } +] diff --git a/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_missing_cons.json b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_missing_cons.json new file mode 100644 index 00000000..25de6eb8 --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAttN_missing_cons.json @@ -0,0 +1,10 @@ +[ + { + "vnsRsCIfAttN": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov/rscIfAttN-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]]", + "tDn": "uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]" + } + } + } +] diff --git a/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_empty.json b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_empty.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_empty.json @@ -0,0 +1 @@ +[] diff --git a/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_match.json b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_match.json new file mode 100644 index 00000000..a9b06968 --- /dev/null +++ b/tests/checks/vnsrscifattn_missing_check/vnsRsCIfAtt_match.json @@ -0,0 +1,18 @@ +[ + { + "vnsRsCIfAtt": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-prov/rscIfAtt-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]]", + "tDn": "uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[prov]" + } + } + }, + { + "vnsRsCIfAtt": { + "attributes": { + "dn": "uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons/rscIfAtt-[uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]]", + "tDn": "uni/tn-CSCwj49418/lDevVip-test/cDev-cdev/cIf-[cons]" + } + } + } +]