Skip to content
Merged
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
85ff014
synth class + write back each function
monrog2 Jun 25, 2025
3a2bb9f
add inspect to pull func name
monrog2 Jun 25, 2025
8671373
fix pytest from non str objects
monrog2 Jun 25, 2025
40c7485
py2
monrog2 Jun 25, 2025
d3ad348
args for input + global cleanup
monrog2 Jun 26, 2025
003e606
cleanup global
monrog2 Jun 26, 2025
e65ffbe
inspect cleanup
monrog2 Jun 26, 2025
46e1996
NA PASS logic in synth class
monrog2 Jun 26, 2025
8be4138
update args
monrog2 Jun 26, 2025
44e8713
tk review cleanup
monrog2 Jun 26, 2025
bca7298
cleanup
monrog2 Jun 26, 2025
41f9d72
synth pytest
monrog2 Jun 27, 2025
581825b
synth pytest
monrog2 Jun 27, 2025
d531743
fix args
monrog2 Jun 27, 2025
dac2901
instantiate uname pw for api-only
monrog2 Jun 27, 2025
764c6b2
--puv flag and live result cleanup logic
monrog2 Jun 27, 2025
8ecbb36
fix exeception writing
monrog2 Jun 27, 2025
77e6e27
Puv pytest (#258)
takishida Jul 1, 2025
78f9410
feat: Support QA version and AciVersion instance as input in AciVersi…
takishida Jul 2, 2025
4da893f
failureDetails.data + pytest
monrog2 Jul 2, 2025
d7098af
-c / -d args into puv (#265)
monrog2 Jul 11, 2025
3cfed10
merge v2.6.0 master
monrog2 Jul 11, 2025
654c2f1
Update synthMaintP with the latest schema with ruleId
takishida Jul 12, 2025
cebba59
Add decorator `@check_wrapper` for all check functions
takishida Jul 13, 2025
df586d9
rename synth class to AciResult + breakdown --puv into --api-only and…
monrog2 Jul 14, 2025
385fcb5
same updates
monrog2 Jul 14, 2025
c46c9c1
Add --version and --total-checks
takishida Jul 15, 2025
68be98a
Do not touch log folders with some options like --version
takishida Jul 15, 2025
d9efebb
arg verbiage update
monrog2 Jul 22, 2025
fbf65e7
ValueError when col and row len do not match + pytest + offending che…
monrog2 Jul 22, 2025
3c55922
fix more checks with mismatched col row length
monrog2 Jul 22, 2025
df5403e
header cleanup + cimc logic update for QA
monrog2 Jul 22, 2025
b509083
cleanup my testing
monrog2 Jul 22, 2025
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: 94 additions & 19 deletions aci-preupgrade-validation-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from getpass import getpass
from collections import defaultdict
from datetime import datetime
import inspect
Comment thread
monrog2 marked this conversation as resolved.
Outdated
import warnings
import time
import pexpect
Expand Down Expand Up @@ -64,6 +65,66 @@
warnings.simplefilter(action='ignore', category=FutureWarning)


class syntheticMaintPValidate:
def __init__(self, func, name, description):
self.function_name = func
self.name = name
self.description = description
self.reason = ""
self.criticality = "critical"
self.passed = True
self.recommended_action = ""
self.sub_reason = ""
self.showValidation = True
self.failureDetails = self.initFailureDetails()

def initFailureDetails(self):
failure_details = {}
failure_details["fail_type"] = ""
failure_details["header"] = ""
failure_details["footer"] = ""
failure_details["column"] = []
failure_details["row"] = []
return failure_details

def updateFailureDetails(self, result, recommended_action, reason, header, footer, column, row):
Comment thread
monrog2 marked this conversation as resolved.
Outdated
self.recommended_action = recommended_action
self.reason = reason
self.passed = False
self.failureDetails["fail_type"] = result
self.failureDetails["header"] = header
self.failureDetails["footer"] = footer
self.failureDetails["column"] = column
self.failureDetails["row"] = row

def buildResult(self):
result = {
"syntheticMaintPValidate": {
"attributes": {
"name": self.name,
"description": self.description,
"reason": self.reason,
"criticality": self.criticality,
Comment thread
monrog2 marked this conversation as resolved.
Outdated
"passed": self.passed,
"recommended_action": self.recommended_action,
"sub_reason": self.sub_reason,
"showValidation": self.showValidation,
Comment thread
monrog2 marked this conversation as resolved.
Outdated
"failureDetails": self.failureDetails
}
}
}
return result

def writeResult(self):
cleaned_name = re.sub(r'[^a-zA-Z0-9_]+|\s+', '_', self.name)
filename = cleaned_name + '.json'
path = "cx-preupgrade-validation-results"
if not os.path.isdir(path):
os.mkdir(path)
with open(os.path.join(path, filename), "w") as f:
json.dump(self.buildResult(), f, indent=4)


class OldVerClassNotFound(Exception):
""" Later versions of ACI can have class properties not found in older versions """
pass
Expand Down Expand Up @@ -1031,7 +1092,15 @@ def print_result(title, result, msg='',
unformatted_headers=None, unformatted_data=None,
recommended_action='',
doc_url='',
adjust_title=False):
adjust_title=False,
func="test"):
synth = syntheticMaintPValidate(func, title, "")
if result in [FAIL_O, FAIL_UF, ERROR, MANUAL, POST]:
Comment thread
monrog2 marked this conversation as resolved.
Outdated
# TODO: deal with unformatted data and headers
synth.updateFailureDetails(
result=result, recommended_action=recommended_action, reason=msg, header="", footer=doc_url, column=headers, row=data
)
synth.writeResult()
padding = 120 - len(title) - len(msg)
if adjust_title: padding += len(title) + 18
output = '{}{:>{}}'.format(msg, result, padding)
Expand Down Expand Up @@ -1070,7 +1139,11 @@ def _icurl(apitype, query, page=0, page_size=100000):
pre = '&' if '?' in query else '?'
query += '{}page={}&page-size={}'.format(pre, page, page_size)
uri = 'http://127.0.0.1:7777/api/{}/{}'.format(apitype, query)
cmd = ['icurl', '-gs', uri]
Comment thread
monrog2 marked this conversation as resolved.
if TOKEN:
cookie = "APIC-cookie={}".format(TOKEN)
cmd = ['curl', '-b', cookie, '-gs', uri]
else:
cmd = ['icurl', '-gs', uri]
logging.info('cmd = ' + ' '.join(cmd))
response = subprocess.check_output(cmd)
logging.debug('response: ' + str(response))
Expand Down Expand Up @@ -1948,7 +2021,7 @@ def switch_ssd_check(index, total_checks, **kwargs):
print_result(title, result, msg, headers, data, unformatted_headers, unformatted_data)
return result


# Connection based check
def apic_ssd_check(index, total_checks, cversion, **kwargs):
title = 'APIC SSD Health'
result = FAIL_UF
Expand Down Expand Up @@ -2596,7 +2669,7 @@ def lldp_with_infra_vlan_mismatch_check(index, total_checks, **kwargs):
print_result(title, result, msg, headers, data, unformatted_headers, unformatted_data)
return result


# Connection based check
def apic_version_md5_check(index, total_checks, tversion, username, password, **kwargs):
title = 'APIC Target version image and MD5 hash'
result = FAIL_UF
Expand All @@ -2616,7 +2689,7 @@ def apic_version_md5_check(index, total_checks, tversion, username, password, **
desc = fm_mo["firmwareFirmware"]['attributes']["description"]
md5 = fm_mo["firmwareFirmware"]['attributes']["checksum"]
if "Image signing verification failed" in desc:
data.append(["All", tversion, md5,
data.append(["All", str(tversion), md5,
'Target image is corrupted', 'Delete and Upload Again'])
image_validaton = False

Expand All @@ -2643,7 +2716,7 @@ def apic_version_md5_check(index, total_checks, tversion, username, password, **
c.log = LOG_FILE
c.connect()
except Exception as e:
data.append([apic_name, '-', '-', e, '-'])
data.append([apic_name, '-', '-', json.dumps(e.__dict__), '-'])
print_result(node_title, ERROR)
has_error = True
continue
Expand All @@ -2653,7 +2726,7 @@ def apic_version_md5_check(index, total_checks, tversion, username, password, **
tversion.dot_version)
except Exception as e:
data.append([apic_name, '-', '-',
'ls command via ssh failed due to:{}'.format(e), '-'])
'ls command via ssh failed due to:{}'.format(json.dumps(e.__dict__)), '-'])
print_result(node_title, ERROR)
has_error = True
continue
Expand All @@ -2667,7 +2740,7 @@ def apic_version_md5_check(index, total_checks, tversion, username, password, **
tversion.dot_version)
except Exception as e:
data.append([apic_name, str(tversion), '-',
'failed to check md5sum via ssh due to:{}'.format(e), '-'])
'failed to check md5sum via ssh due to:{}'.format(json.dumps(e.__dict__)), '-'])
print_result(node_title, ERROR)
has_error = True
continue
Expand Down Expand Up @@ -2701,7 +2774,7 @@ def apic_version_md5_check(index, total_checks, tversion, username, password, **
print_result(title, result, msg, headers, data, adjust_title=True)
return result


# Connection Based Check
def standby_apic_disk_space_check(index, total_checks, **kwargs):
title = 'Standby APIC Disk Space Usage'
result = FAIL_UF
Expand Down Expand Up @@ -4616,8 +4689,8 @@ def fc_ex_model_check(index, total_checks, tversion, **kwargs):
data.append([node_dn, model])
if data:
result = FAIL_O

print_result(title, result, msg, headers, data, recommended_action=recommended_action, doc_url=doc_url)
# func=inspect.currentframe().f_code.co_name
Comment thread
monrog2 marked this conversation as resolved.
Outdated
print_result(title, result, msg, headers, data, recommended_action=recommended_action, doc_url=doc_url)
return result


Expand Down Expand Up @@ -4953,9 +5026,9 @@ def aes_encryption_check(index, total_checks, tversion, **kwargs):

cryptkeys = icurl("mo", "uni/exportcryptkey.json")
if not cryptkeys:
data = [[tversion, "Object Not Found", impact]]
data = [[str(tversion), "Object Not Found", impact]]
elif cryptkeys[0]["pkiExportEncryptionKey"]["attributes"]["strongEncryptionEnabled"] != "yes":
data = [[tversion, "Disabled", impact]]
data = [[str(tversion), "Disabled", impact]]
else:
result = PASS

Expand Down Expand Up @@ -5007,12 +5080,12 @@ def service_bd_forceful_routing_check(index, total_checks, cversion, tversion, *
print_result(title, result, msg, headers, data, unformatted_headers, unformatted_data, recommended_action, doc_url)
return result


# Connection Base Check
def observer_db_size_check(index, total_checks, username, password, **kwargs):
title = 'Observer Database Size'
result = PASS
msg = ''
headers = ["Node" , "File Location", "Size (GB)"]
headers = ["Node", "File Location", "Size (GB)"]
data = []
recommended_action = 'Contact TAC to analyze and truncate large DB files'
doc_url = 'https://datacenter.github.io/ACI-Pre-Upgrade-Validation-Script/validations#observer-database-size'
Expand All @@ -5029,7 +5102,7 @@ def observer_db_size_check(index, total_checks, username, password, **kwargs):
prints('')
for apic in controllers:
attr = apic['topSystem']['attributes']
node_title = 'Checking %s...' % attr['name']
node_title = 'Checking %s...' % attr['name']
print_title(node_title)
try:
c = Connection(attr['address'])
Expand All @@ -5038,7 +5111,7 @@ def observer_db_size_check(index, total_checks, username, password, **kwargs):
c.log = LOG_FILE
c.connect()
except Exception as e:
data.append([attr['id'], attr['name'], e])
data.append([attr['id'], attr['name'], json.dumps(e.__dict__)])
print_result(node_title, ERROR)
has_error = True
continue
Expand All @@ -5058,9 +5131,9 @@ def observer_db_size_check(index, total_checks, username, password, **kwargs):
file_size = size_match.group("size")
file_name = "/data2/dbstats/" + size_match.group("file")
data.append([attr['id'], file_name, file_size])
print_result(node_title, DONE)
print_result(node_title, DONE)
except Exception as e:
data.append([attr['id'], attr['name'], e])
data.append([attr['id'], attr['name'], json.dumps(e.__dict__)])
print_result(node_title, ERROR)
has_error = True
continue
Expand Down Expand Up @@ -5105,6 +5178,8 @@ def ave_eol_check(index, total_checks, tversion, **kwargs):
prints(' ==== %s%s, Script Version %s ====\n' % (ts, tz, SCRIPT_VERSION))
prints('!!!! Check https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script for Latest Release !!!!\n')
prints('To use a non-default Login Domain, enter apic#DOMAIN\\\\USERNAME')
global TOKEN
TOKEN = os.getenv('webtoken')
Comment thread
monrog2 marked this conversation as resolved.
Outdated
username, password = get_credentials()
try:
cversion = get_current_version()
Expand Down