-
Notifications
You must be signed in to change notification settings - Fork 48
New validation for CSCwr51759 #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4.2.0-dev
Are you sure you want to change the base?
Changes from all commits
b892413
371cf75
b216729
3b9654c
ddde9ad
1774d91
643d553
2f9033d
be39aa3
e81c3bd
74acfe9
c217115
0da4087
df5f5cb
de1e487
24d00e8
697dee0
2538b2b
148d0ac
0fd472a
2812c3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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) | ||
|
|
||
|
|
||
| @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)"): | ||
|
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" | ||
|
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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Harinadh-Saladi marked this conversation as resolved.
|
||
| elif not contract or not graph: | ||
| continue # Fallback mode: skip incomplete contexts | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can this logic be applied directly within the API call? I see that |
||
|
|
||
| # 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Blocking] Please do not return |
||
|
|
||
| # 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 | ||
|
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 ---- | ||
|
|
||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.