Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b892413
New validation for CSCwr51759
Harinadh-Saladi Jun 1, 2026
371cf75
Updated validations.md
Harinadh-Saladi Jun 1, 2026
b216729
Addressed the review comments
Harinadh-Saladi Jun 4, 2026
3b9654c
Addressed the review comments
Harinadh-Saladi Jun 5, 2026
ddde9ad
Updated check name in all the places
Harinadh-Saladi Jun 5, 2026
1774d91
Addressed review comments
Harinadh-Saladi Jun 5, 2026
643d553
Addressed new comments
Harinadh-Saladi Jun 12, 2026
2f9033d
Added new scenarios with respect to service graph missing interface d…
Harinadh-Saladi Jun 24, 2026
be39aa3
Addressed review comments
Harinadh-Saladi Jun 27, 2026
e81c3bd
Addressed Dev review comments
Harinadh-Saladi Jul 1, 2026
74acfe9
Addressed the review comments
Harinadh-Saladi Jul 2, 2026
c217115
Addressed dev comments on contract and interface names with multiple …
Harinadh-Saladi Jul 3, 2026
0da4087
Rebased the code with latest changes by resolving merge conflicts
Harinadh-Saladi Jul 3, 2026
df5f5cb
Modified variable names with meaningful variable names
Harinadh-Saladi Jul 3, 2026
de1e487
Updated general check name
Harinadh-Saladi Jul 3, 2026
24d00e8
Removed function name under bugs section and updated validations.md f…
Harinadh-Saladi Jul 3, 2026
697dee0
Addressed Dev comments and modified variable names with meaningful names
Harinadh-Saladi Jul 3, 2026
2538b2b
Addressed review comments
Harinadh-Saladi Jul 6, 2026
148d0ac
Removed the bug link added earlier for this check, as it's added as g…
Harinadh-Saladi Jul 6, 2026
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
254 changes: 252 additions & 2 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from textwrap import TextWrapper
from getpass import getpass
from collections import defaultdict, OrderedDict
from datetime import datetime
from datetime import datetime,timedelta
from argparse import ArgumentParser
from itertools import chain
import threading
Expand Down Expand Up @@ -6702,6 +6702,256 @@ def stale_dbgacEpgSummaryTask_check(tversion, **kwargs):
return Result(result=result, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)
Comment thread
Harinadh-Saladi marked this conversation as resolved.


@check_wrapper(check_title="Verifying Cluster Interface Configuration For Deprecated RsCIfAtt Object")
def verify_cluster_interface_config_for_rscifatt_use_check(tversion, cversion, **kwargs):
result = PASS
doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#verifying-cluster-interface-configuration-for-deprecated-rscifatt-object"

lif_dn_regex = r"uni/tn-(?P<tenant>[^/]+)/lDevVip-(?P<device>[^/]+)/lIf-(?P<lif>[^/]+)$"
ldeviflif_regex = r"^uni/tn-[^/]+/lDevIf-\[(?P<base>uni/tn-[^\]]+/lDevVip-[^\]]+)\]/lDevIfLIf-(?P<lif>[^/]+)$"
tn_regex = r"^uni/tn-([^/]+)/"

if not tversion:
return Result(result=MANUAL, msg=TVER_MISSING, doc_url=doc_url)
if tversion.older_than("6.0(3d)"):
Comment thread
Harinadh-Saladi marked this conversation as resolved.
return Result(result=NA, msg=VER_NOT_AFFECTED, doc_url=doc_url)

post_upgrade = bool(cversion and (cversion.same_as("6.0(3d)") or cversion.newer_than("6.0(3d)")))
if post_upgrade:
headers = ["Tenant", "Device Name", "Cluster Interface"]
recommended_action = "Please review the concrete interface attachments under the flagged device cluster interfaces and reattach them using the UI: Tenant → Services → L4-L7 → Devices → Cluster Interface → Concrete Interface → + → Select the respective interface from the drop-down list → Submit"
else:
headers = ["Tenant", "Device Name", "Cluster Interface", "Missing Concrete Interface", "vnsRsCIfAtt DN"]
recommended_action = "Please reattach concrete interfaces again using the UI (without deleting the existing attachment objects): Tenant → Services → L4-L7 → Devices → Cluster Interface → Concrete Interface → + → Select the respective interface from the drop-down list → Submit"

# ── Step 1: Collect deployed service-graph LIF DNs ──────────────────────

# Build (contract_name, graph_name) keys from applied vnsGraphInst objects
graph_keys = set()
for entry in icurl("class", "vnsGraphInst.json?rsp-prop-include=config-only") or []:
try:
contract_dn = entry["vnsGraphInst"]["attributes"]["ctrctDn"].strip()
graph_dn = entry["vnsGraphInst"]["attributes"]["graphDn"].strip()
except (KeyError, TypeError, AttributeError):
continue
contract_match = re.search(r"/brc-([^/]+)$", contract_dn)
graph_match = re.search(r"/AbsGraph-([^/]+)$", graph_dn)
if contract_match and graph_match:
graph_keys.add((contract_match.group(1), graph_match.group(1)))

lif_dns = set()
lif_source_tenants = {} # lif_dn -> set(contract tenants) for implicit-object detection
dev_source_tenants = {} # device-prefix DN -> set(contract tenants)

ldev_ctx_query = (
"vnsLDevCtx.json?rsp-prop-include=config-only"
"&rsp-subtree=full"
"&rsp-subtree-class=vnsLIfCtx,vnsRsLIfCtxToLIf"
"&rsp-subtree-include=required"
)
for entry in icurl("class", ldev_ctx_query) or []:
try:
ldev_ctx_mo = entry["vnsLDevCtx"]
ldev_ctx_attrs = ldev_ctx_mo["attributes"]
contract = ldev_ctx_attrs["ctrctNameOrLbl"].strip()
graph = ldev_ctx_attrs["graphNameOrLbl"].strip()
ctx_dn = ldev_ctx_attrs["dn"].strip()
except (KeyError, TypeError, AttributeError):
continue

ctx_tenant_match = re.search(tn_regex, ctx_dn)
ctx_tenant = ctx_tenant_match.group(1) if ctx_tenant_match else ""

# Filter to contexts matching an active graph (fall back to all if no graphs found)
if graph_keys:
contract_parts = [part for part in contract.split("-") if part]
contract_without_prefix = "-".join(contract_parts[1:]) if len(contract_parts) > 1 else contract
graph_base_name = graph[:-9] if graph.endswith("-imported") else graph
if not any(
(contract_name, graph_name) in graph_keys
for contract_name in (contract, contract_without_prefix)
for graph_name in (graph, graph_base_name)
):
continue
elif not contract or not graph:
continue # Fallback mode: skip incomplete contexts

# DFS through vnsLIfCtx children to collect vnsRsLIfCtxToLIf references
stack = [ldev_ctx_mo]
while stack:
current_ctx = stack.pop()
for child in current_ctx.get("children", []) or []:
if child.get("vnsLIfCtx"):
stack.append(child["vnsLIfCtx"])
lif_relation = child.get("vnsRsLIfCtxToLIf")
if not lif_relation:
continue
try:
target_class = lif_relation["attributes"].get("tCl", "")
target_dn = lif_relation["attributes"]["tDn"].strip()
except (KeyError, TypeError, AttributeError):
continue
if not target_dn:
continue

if target_class == "vnsLIf" or re.search(lif_dn_regex, target_dn):
lif_dns.add(target_dn)
lif_dn_match = re.search(lif_dn_regex, target_dn)
if lif_dn_match and ctx_tenant:
dev_dn = "uni/tn-{}/lDevVip-{}".format(lif_dn_match.group("tenant"), lif_dn_match.group("device"))
lif_source_tenants.setdefault(target_dn, set()).add(ctx_tenant)
dev_source_tenants.setdefault(dev_dn, set()).add(ctx_tenant)
elif target_class == "vnsLDevIfLIf" or ("/lDevIf-[" in target_dn and "/lDevIfLIf-" in target_dn):
ldeviflif_match = re.search(ldeviflif_regex, target_dn)
if ldeviflif_match:
converted = "{}/lIf-{}".format(ldeviflif_match.group("base"), ldeviflif_match.group("lif"))
lif_dns.add(converted)
if ctx_tenant:
lif_source_tenants.setdefault(converted, set()).add(ctx_tenant)
dev_source_tenants.setdefault(ldeviflif_match.group("base"), set()).add(ctx_tenant)

# Expand to all LIFs under the same device clusters
dev_prefixes = set()
for lif_dn in lif_dns:
lif_dn_match = re.search(lif_dn_regex, lif_dn)
if lif_dn_match:
dev_prefixes.add("uni/tn-{}/lDevVip-{}".format(lif_dn_match.group("tenant"), lif_dn_match.group("device")))

if dev_prefixes:
for entry in icurl("class", "vnsLIf.json?rsp-prop-include=config-only") or []:
try:
lif_dn = entry["vnsLIf"]["attributes"]["dn"].strip()
except (KeyError, TypeError, AttributeError):
continue
lif_dn_match = re.search(lif_dn_regex, lif_dn)
if not lif_dn_match:
continue
dev_dn = "uni/tn-{}/lDevVip-{}".format(lif_dn_match.group("tenant"), lif_dn_match.group("device"))
if dev_dn in dev_prefixes:
lif_dns.add(lif_dn)
if dev_source_tenants.get(dev_dn):
lif_source_tenants.setdefault(lif_dn, set()).update(dev_source_tenants[dev_dn])

if not lif_dns:
return Result(result=PASS, msg="No deployed service graph interfaces found.", doc_url=doc_url)

# ── Step 2: Per-LIF subtree check for missing vnsRsCIfAttN ──────────────

sg_data = []
has_implicit_objects = False
for lif_dn in sorted(lif_dns):
lif_query = "{}.json?query-target=subtree&target-subtree-class=vnsRsCIfAttN&rsp-prop-include=config-only".format(lif_dn)
lif_subtree = icurl("mo", lif_query) or []
if any(isinstance(subtree_mo, dict) and subtree_mo.get("vnsRsCIfAttN") for subtree_mo in lif_subtree):
continue
lif_dn_match = re.search(lif_dn_regex, lif_dn)
lif_name = lif_dn_match.group("lif") if lif_dn_match else ""
lif_parts = [part for part in lif_name.split("-") if part]
missing_cif = lif_parts[1] if len(lif_parts) > 1 else lif_name
sg_data.append([
lif_dn_match.group("tenant") if lif_dn_match else "",
lif_dn_match.group("device") if lif_dn_match else "",
lif_name,
missing_cif,
lif_dn,
])
if post_upgrade and lif_dn_match and lif_dn_match.group("tenant") == "common":
if any(t and t != "common" for t in lif_source_tenants.get(lif_dn, set())):
has_implicit_objects = True

# ── Step 3: Return results ───────────────────────────────────────────────

if post_upgrade:
if sg_data:
sg_data.sort(key=lambda r: r[-1])
msg = "Graph is rendered with implicit objects" if has_implicit_objects else "vnsRsCIfAttN is missing under deployed L4-L7 cluster interfaces."
return Result(result=FAIL_O, msg=msg, headers=headers, data=[[r[0], r[1], r[2]] for r in sg_data], recommended_action=recommended_action, doc_url=doc_url)
return Result(result=PASS, msg="All deployed service graph interfaces have vnsRsCIfAttN.", doc_url=doc_url)

# Pre-upgrade: fetch both relation object classes for further checks
vnsRsCIfAtts = icurl("class", "vnsRsCIfAtt.json?rsp-prop-include=config-only") or []
vnsRsCIfAttNs = icurl("class", "vnsRsCIfAttN.json?rsp-prop-include=config-only") or []

if sg_data:
sg_data.sort(key=lambda r: r[-1])
msg = "vnsLIf has neither vnsRsCIfAtt nor vnsRsCIfAttN. Missing concrete interface mapping can cause service graph inconsistency." if not vnsRsCIfAtts and not vnsRsCIfAttNs else ""
return Result(result=FAIL_O, msg=msg, headers=headers, data=sg_data, recommended_action=recommended_action, doc_url=doc_url)

if not vnsRsCIfAtts and not vnsRsCIfAttNs:
return Result(result=FAIL_O, msg="Both vnsRsCIfAtt and vnsRsCIfAttN are missing. Reattach concrete interface mappings before upgrade.", headers=headers, data=[], recommended_action=recommended_action, doc_url=doc_url)

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

# Build new-object lookup sets in a single pass over vnsRsCIfAttNs
new_lif_dns = set() # LIF DNs covered by vnsRsCIfAttN (for coverage check)
new_dn_keys = set() # New DNs rewritten as old-style keys (for consistency check)
for relation_mo in vnsRsCIfAttNs:
try:
relation_dn = relation_mo["vnsRsCIfAttN"]["attributes"]["dn"].strip()
except (KeyError, TypeError, AttributeError):
continue
Comment thread
Harinadh-Saladi marked this conversation as resolved.
if not relation_dn:
continue
lif_parent_match = re.search(r"^(uni/tn-[^/]+/lDevVip-[^/]+/lIf-[^/]+)/rscIfAttN-\[", relation_dn)
if lif_parent_match:
new_lif_dns.add(lif_parent_match.group(1))
new_dn_keys.add(relation_dn.replace("/rscIfAttN-[", "/rscIfAtt-[", 1))

# Secondary coverage check: any deployed LIF not yet covered by a vnsRsCIfAttN
data = []
for lif_dn in sorted(lif_dns):
if lif_dn in new_lif_dns:
continue
lif_dn_match = re.search(lif_dn_regex, lif_dn)
lif_name = lif_dn_match.group("lif") if lif_dn_match else ""
lif_parts = [part for part in lif_name.split("-") if part]
missing_cif = lif_parts[1] if len(lif_parts) > 1 else lif_name
data.append([
lif_dn_match.group("tenant") if lif_dn_match else "",
lif_dn_match.group("device") if lif_dn_match else "",
lif_name,
missing_cif,
lif_dn,
])
if data:
return Result(result=FAIL_O, headers=headers, data=data, recommended_action=recommended_action, doc_url=doc_url)

# Consistency check: each old vnsRsCIfAtt must have a matching new vnsRsCIfAttN
data = []
for old_relation_mo in vnsRsCIfAtts:
try:
old_dn = old_relation_mo["vnsRsCIfAtt"]["attributes"]["dn"].strip()
except (KeyError, TypeError, AttributeError):
continue
if not old_dn:
continue
old_lif_match = re.search(r"^(uni/tn-[^/]+/lDevVip-[^/]+/lIf-[^/]+)/", old_dn)
old_lif = old_lif_match.group(1) if old_lif_match else ""
if lif_dns and old_lif and old_lif not in lif_dns:
continue
if old_dn.replace("/rscIfAttN-[", "/rscIfAtt-[", 1) in new_dn_keys:
continue
old_relation_match = re.search(
r"uni/tn-(?P<tenant>[^/]+)/lDevVip-(?P<device>[^/]+)/lIf-(?P<lif>[^/]+)/"
r"rscIfAtt-\[.*?/cIf-\[(?P<cif>[^\]]+)\]\]",
old_dn,
)
data.append([
old_relation_match.group("tenant") if old_relation_match else "",
old_relation_match.group("device") if old_relation_match else "",
old_relation_match.group("lif") if old_relation_match else "",
old_relation_match.group("cif") if old_relation_match else "",
old_dn,
])

data.sort(key=lambda r: r[-1])
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 @@ -6794,6 +7044,7 @@ class CheckManager:
fabric_link_redundancy_check,
apic_downgrade_compat_warning_check,
svccore_excessive_data_check,
verify_cluster_interface_config_for_rscifatt_use_check,

# Faults
apic_disk_space_faults_check,
Expand Down Expand Up @@ -6877,7 +7128,6 @@ class CheckManager:
wred_affected_model_check,
n9k_c93180yc_fx3_switch_memory_check,
stale_dbgacEpgSummaryTask_check,

]
ssh_checks = [
# General
Expand Down
23 changes: 21 additions & 2 deletions docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
[Verifying Cluster Interface Configuration For Deprecated RsCIfAtt Object][g22] | :white_check_mark: | :no_entry_sign:

[g1]: #compatibility-target-aci-version
[g2]: #compatibility-cimc-version
Expand All @@ -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]: #verifying-cluster-interface-configuration-for-deprecated-rscifatt-object

### Fault Checks
Items | Faults | This Script | APIC built-in
Expand Down Expand Up @@ -2782,7 +2784,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.


Comment thread
Harinadh-Saladi marked this conversation as resolved.
### 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.
Expand Down Expand Up @@ -2845,6 +2846,25 @@ Affected versions: 6.1(5e) and below, or 6.2(1g).

Contact Cisco TAC for next steps. For more details, refer to the workaround in [CSCwt69100][75].

### Verifying Cluster Interface Configuration For Deprecated RsCIfAtt Object

For upgrades from versions 6.0(2) or below to 6.0(3) or above:

The flagged device cluster interfaces have one or more concrete interface attachments that use the deprecated relation object vnsRsCIfAtt. This relation will be deleted when upgrading to version 6.0(3) or later. To avoid issues, the affected cluster interfaces must be migrated to the newer relation object, vnsRsCIfAttN.

To perform the migration, reattach the flagged concrete interface attachments (without deleting the existing attachment objects) using the UI by following these steps:

Tenant → Services → L4-L7 → Devices → Cluster Interface → Concrete Interface → +

For upgrades with current version as 6.0(3) or above:

The flagged device cluster interfaces have one or more concrete interface attachments that may be missing a valid relation object (vnsRsCIfAttN). This could be a symptom of removal of the deprecated relation object during an upgrade to version 6.0(3) or later.

It is highly recommended to review the concrete interface attachments under the flagged device cluster interfaces. To ensure that the required relation object is created, reattach the flagged concrete interface attachments (without deleting the existing attachment objects) using the UI by following these steps:

Tenant → Services → L4-L7 → Devices → Cluster Interface → Concrete Interface → +



[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 @@ -2923,4 +2943,3 @@ Contact Cisco TAC for next steps. For more details, refer to the workaround in [
[74]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwm42741
[75]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwt69100
[76]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwt38698

Loading
Loading