Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
0fd472a
Addressed review comments
Harinadh-Saladi Jul 31, 2026
2812c3f
Removed extra lines
Harinadh-Saladi Jul 31, 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
276 changes: 273 additions & 3 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,276 @@ 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="Vnsrscifatt Deprecation Check")
def vnsrscifatt_deprecation_check(tversion, cversion, **kwargs):
result = PASS
doc_url = "https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations/#vnsrscifatt-deprecation-check"

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_cifatt_delete = bool(cversion and (cversion.same_as("6.0(3d)") or cversion.newer_than("6.0(3d)")))
if post_cifatt_delete:
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"
Comment thread
Harinadh-Saladi marked this conversation as resolved.

# ── 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"
)
Comment on lines +6746 to +6751

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is a switch object? and we are pulling subtree.

have we been able to test this on a scale fabric and what are the implications of the response time?

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
Comment thread
Harinadh-Saladi marked this conversation as resolved.
elif not contract or not graph:
continue # Fallback mode: skip incomplete contexts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

# Fallback mode: skip incomplete contexts

Can this logic be applied directly within the API call? I see that rsp-subtree-include=required is being used above to so some first level 'if filtering, is there another point where similar could be applied?


# 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 not lif_dns:
return Result(result=PASS, msg="No deployed service graph interfaces found.", doc_url=doc_url)

# Fetch all LIFs with relation children once and reuse this collection in Step 2/3.
vns_lif_with_rel_query = (
"vnsLIf.json?rsp-prop-include=config-only"
"&rsp-subtree=children"
"&rsp-subtree-class=vnsRsCIfAtt,vnsRsCIfAttN"
)
vnsLIfs = icurl("class", vns_lif_with_rel_query) or []

vnsRsCIfAtts = []
vnsRsCIfAttNs = []
lifs_with_new_relation = set()

for entry in vnsLIfs:
try:
lif_mo = entry["vnsLIf"]
lif_attrs = lif_mo["attributes"]
lif_dn = lif_attrs["dn"].strip()
except (KeyError, TypeError, AttributeError):
continue

for child in lif_mo.get("children", []) or []:
if isinstance(child, dict) and child.get("vnsRsCIfAtt"):
vnsRsCIfAtts.append(child)
if isinstance(child, dict) and child.get("vnsRsCIfAttN"):
vnsRsCIfAttNs.append(child)
lifs_with_new_relation.add(lif_dn)

if not dev_prefixes:
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])

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

sg_data = []
has_implicit_objects = False
for lif_dn in sorted(lif_dns):
if lif_dn in lifs_with_new_relation:
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_cifatt_delete 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_cifatt_delete:
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Blocking] Please do not return PASS solely because every deployed LIF has vnsRsCIfAttN. For current versions 6.2(3)+, the CSCwr51759 release-note content recommends removing deprecated vnsRsCIfAtt objects when a matching vnsRsCIfAttN exists, but this branch never queries the old class, so that state silently passes. Please add explicit handling and regression coverage for it. Before deciding the exact result and recommended action, we need Development signoff on whether these old objects must be cleaned up on 6.2(3)+, or whether the risk of leaving them is minimal enough to allow the check to pass; please document the decision and its authoritative source.


# Pre-upgrade: reuse relation objects collected from vnsLIf subtrees.

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,7 +7064,7 @@ class CheckManager:
fabric_link_redundancy_check,
apic_downgrade_compat_warning_check,
svccore_excessive_data_check,

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

vnsrscifatt_deprecation_check,
]
ssh_checks = [
# General
Expand Down
17 changes: 16 additions & 1 deletion docs/docs/validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Items | This Script
[Supported Hardware Compatibility][g20] | :white_check_mark: | :no_entry_sign:
[Svccore Excessive Data Check][g21] | :white_check_mark: | :no_entry_sign:


[g1]: #compatibility-target-aci-version
[g2]: #compatibility-cimc-version
[g3]: #compatibility-switch-hardware
Expand Down Expand Up @@ -206,6 +207,8 @@ Items | Defect | This Script
[WRED with Affected FM Models][d35] | CSCwt50713 | :white_check_mark: | :no_entry_sign:
[N9K-C93180YC-FX3 Switch Memory Less Than 32GB][d36] | CSCwm42741 | :white_check_mark: | :no_entry_sign:
[Stale dbgacEpgSummaryTask Objects][d37] | CSCwt69100 | :white_check_mark: | :no_entry_sign:
[Vnsrscifatt Deprecation Check][d38] | CSCwr51759 | :white_check_mark: | :no_entry_sign:


[d1]: #ep-announce-compatibility
[d2]: #eventmgr-db-size-defect-susceptibility
Expand Down Expand Up @@ -244,6 +247,7 @@ Items | Defect | This Script
[d35]: #wred-with-affected-fm-models
[d36]: #n9k-c93180yc-fx3-switch-memory-less-than-32gb
[d37]: #stale-dbgacepgsummarytask-objects
[d38]: #vnsrscifatt-deprecation-check

## General Check Details

Expand Down Expand Up @@ -2782,7 +2786,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 +2848,17 @@ 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].

### Vnsrscifatt Deprecation Check

Due to [CSCwr51759][77], After upgrading ACI to 6.0(3d) or later release, one or more L4-L7 service graph device cluster interfaces are missing their concrete interface attachment, causing the service graph to fail to render and resulting in a traffic outage for PBR/L4-L7 redirected traffic.

This occurs when a deployed service graph's cluster interface (vnsLIf) concrete interface mapping is defined using the deprecated relation object vnsRsCIfAtt, and the object was never migrated to its replacement, vnsRsCIfAttN, prior to upgrading to 6.0(3d) or later.
Because vnsRsCIfAtt is deleted during the upgrade to 6.0(3d)+, any cluster interface still relying solely on it loses its concrete interface mapping, and no equivalent vnsRsCIfAttN object exists to take its place.

Before upgrading (current version older than 6.0(3d)): Reattach the concrete interface via the APIC GUI without deleting the existing attachment object — Tenant → Services → L4-L7 → Devices → Cluster Interface → Concrete Interface → + → select the interface → Submit. This creates the new vnsRsCIfAttN object alongside the old one so the mapping survives the upgrade.
After upgrading (current version 6.0(3d)) or later, verify all concrete device interface attachments to ensure there are none missing, Reattach the concrete interface using the same UI path to recreate the missing vnsRsCIfAttN object.

Starting with ACI 6.0(3d), the object model for L4-L7 service graph concrete interface attachment changed: the legacy relation object vnsRsCIfAtt (under vnsLIf) was deprecated in favor of vnsRsCIfAttN. vnsRsCIfAtt objects are removed by the switchover/upgrade to 6.0(3d) or later, but this removal is not paired with an automatic creation of the equivalent vnsRsCIfAttN object for cluster interfaces that had never been re-attached under the new object.

[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 +2937,5 @@ 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
[77]: https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwr51759

Loading
Loading