Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -6410,6 +6410,118 @@ 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="Cleanup vnsRsCIfAtt usage in services")
def vns_rscifatt_cleanup_check(tversion, **kwargs):
Copy link
Copy Markdown

@muthu-ku muthu-ku Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the proper function name and title

result = PASS
headers = ["Tenant", "Device Name", "Cluster Interface", "Missing Concrete Interface", "vnsRsCIfAtt DN"]
data = []
recommended_action = (
"Mo vnsRsCIfAtt is deprecated >=6.0(3d). Before upgrade, under Services, add the missing concrete interface as vnsRsCIfAttN under the same cluster interface"
)
doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#cleanup-vnsrscifatt-usage-in-services"

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")
if not vnsRsCIfAtts:
return Result(result=PASS, msg="No user-configured vnsRsCIfAtt payload found.", doc_url=doc_url)

vnsRsCIfAttNs = icurl("class", "vnsRsCIfAttN.json?rsp-prop-include=config-only")

def get_target_dn(relation_attributes):
target_dn = relation_attributes["tDn"].strip() if "tDn" in relation_attributes else ""
if target_dn:
return target_dn
if "dn" not in relation_attributes:
return ""
relation_dn = relation_attributes["dn"]
target_dn_match = re.search(r"\[(.*)\]$", relation_dn)
return target_dn_match.group(1).strip() if target_dn_match else ""

def get_parent_dn(relation_dn):
relation_dn = relation_dn.strip()
if "/rscIfAtt-[" in relation_dn:
return relation_dn.split("/rscIfAtt-[", 1)[0]
if "/rscIfAttN-[" in relation_dn:
return relation_dn.split("/rscIfAttN-[", 1)[0]
return relation_dn.rsplit("/", 1)[0] if "/" in relation_dn else ""

def parse_relation_context(relation_dn):
tenant_name = ""
device_name = ""
logical_interface = ""
concrete_interface = ""

dn_match = re.search(
r"uni/tn-(?P<tenant>[^/]+)/lDevVip-(?P<device>[^/]+)/lIf-(?P<lif>[^/]+)/"
r"rscIfAtt-\[.*?/cIf-\[(?P<cif>[^\]]+)\]\]",
relation_dn,
)
if dn_match:
tenant_name = dn_match.group("tenant")
device_name = dn_match.group("device")
logical_interface = dn_match.group("lif")
concrete_interface = dn_match.group("cif")
return tenant_name, device_name, logical_interface, concrete_interface

old_relation_dn_by_key = {}
for vnsRsCIfAtt in vnsRsCIfAtts:
if "vnsRsCIfAtt" not in vnsRsCIfAtt:
continue
if "attributes" not in vnsRsCIfAtt["vnsRsCIfAtt"]:
continue
relation_attributes = vnsRsCIfAtt["vnsRsCIfAtt"]["attributes"]
if "dn" not in relation_attributes:
continue

relation_dn = relation_attributes["dn"].strip()
if not relation_dn:
continue
target_dn = get_target_dn(relation_attributes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dn is the full path of the relationship object, and tDn is the path of the target interface object it is attached to.

No need of check for tDN.

if not target_dn:
continue
relation_key = (get_parent_dn(relation_dn), target_dn)
old_relation_dn_by_key[relation_key] = relation_dn

if not old_relation_dn_by_key:
return Result(result=PASS, msg="No user-configured vnsRsCIfAtt payload found.", doc_url=doc_url)

new_relation_keys = set()
for vnsRsCIfAttN in vnsRsCIfAttNs:
if "vnsRsCIfAttN" not in vnsRsCIfAttN:
continue
if "attributes" not in vnsRsCIfAttN["vnsRsCIfAttN"]:
continue
relation_attributes = vnsRsCIfAttN["vnsRsCIfAttN"]["attributes"]
if "dn" not in relation_attributes:
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when mo is not configured, vnsRsCIfAttNs will be empty.. above if conditions not needed.


relation_dn = relation_attributes["dn"].strip()
if not relation_dn:
continue
target_dn = get_target_dn(relation_attributes)
if not target_dn:
continue
relation_key = (get_parent_dn(relation_dn), target_dn)
new_relation_keys.add(relation_key)

for relation_key in sorted(old_relation_dn_by_key.keys()):
if relation_key in new_relation_keys:
continue
missing_dn = old_relation_dn_by_key[relation_key]
tenant_name, device_name, logical_interface, concrete_interface = parse_relation_context(missing_dn)
data.append([tenant_name, device_name, logical_interface, concrete_interface, missing_dn])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is that fault case handled for post upgrade case..

fault : F1690
uni/tn-CSCwj49418/lDevVip-test/lIf-intf-cons/vnsConfIssue-lif-invalid-CIf
Configuration is invalid due to LIf has an invalid CIf


if data:
result = FAIL_O

return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)


# ---- Script Execution ----


Expand Down Expand Up @@ -6581,6 +6693,7 @@ class CheckManager:
rogue_ep_coop_exception_mac_check,
n9k_c9408_model_lem_count_check,
inband_management_policy_misconfig_check,
vns_rscifatt_cleanup_check,
]
ssh_checks = [
# General
Expand Down
18 changes: 17 additions & 1 deletion docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ Items | Defect | This Script
[N9K-C9408 with more than 5 N9K-X9400-16W LEMs][d31] | CSCws82819 | :white_check_mark: | :no_entry_sign:
[Multi-Pod Modular Spine Bootscript File][d32] | CSCwr66848 | :white_check_mark: | :no_entry_sign:
[Inband Management Policy Misconfiguration][d33]| CSCwd40071 | :white_check_mark: | :no_entry_sign:
[Cleanup vnsRsCIfAtt usage in services][d34] | CSCwr51759 | :white_check_mark: | :no_entry_sign:

[d1]: #ep-announce-compatibility
[d2]: #eventmgr-db-size-defect-susceptibility
Expand Down Expand Up @@ -237,6 +238,7 @@ Items | Defect | This Script
[d31]: #n9k-c9408-with-more-than-5-n9k-x9400-16w-lems
[d32]: #multi-pod-modular-spine-bootscript-file
[d33]: #inband-management-policy-misconfiguration
[d34]: #cleanup-vnsrscifatt-usage-in-services

## General Check Details

Expand Down Expand Up @@ -2797,6 +2799,19 @@ 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.

### Cleanup vnsRsCIfAtt usage in services

Due to [CSCwr51759][70], when targeting 6.0(3)+, having only `vnsRsCIfAtt` without the corresponding `vnsRsCIfAttN` under the same `vnsLIf` can leave service graph interface attachment in an inconsistent state.

Impact:

If any `vnsRsCIfAtt` relation exists without a matching `vnsRsCIfAttN` for the same concrete interface target (`tDn`), the upgrade is outage-risky and should be treated as affected.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update the document as per impact and requirement.


Suggestion:

Before the upgrade, add the missing `vnsRsCIfAttN` relation under the same cluster interface (`vnsLIf`) with the same concrete interface target (`tDn`).



[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
Expand Down Expand Up @@ -2867,4 +2882,5 @@ 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
[69]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCws84232
[70]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwr51759
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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 = "vns_rscifatt_cleanup_check"

# icurl queries
vnsRsCIfAtt_api = "vnsRsCIfAtt.json?rsp-prop-include=config-only"
vnsRsCIfAttN_api = "vnsRsCIfAttN.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,
[],
),
# No user-configured vnsRsCIfAtt payload
(
{
vnsRsCIfAtt_api: read_data(dir, "vnsRsCIfAtt_empty.json"),
},
"6.1(5e)",
script.PASS,
[],
),
# 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]]",
]
],
),
],
)
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
18 changes: 18 additions & 0 deletions tests/checks/vns_rscifatt_cleanup_check/vnsRsCIfAttN_match.json
Original file line number Diff line number Diff line change
@@ -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]"
}
}
}
]
Original file line number Diff line number Diff line change
@@ -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]"
}
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
18 changes: 18 additions & 0 deletions tests/checks/vns_rscifatt_cleanup_check/vnsRsCIfAtt_match.json
Original file line number Diff line number Diff line change
@@ -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]"
}
}
}
]
Loading