Skip to content

Commit cb2676a

Browse files
authored
feat: Create all result files as part of prepare (#283)
1 parent 18c42af commit cb2676a

2 files changed

Lines changed: 92 additions & 15 deletions

File tree

aci-preupgrade-validation-script.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,11 @@ class AciResult:
929929
"docUrl", "severity", "ruleStatus", "showValidation", "failureDetails",
930930
)
931931

932+
# ruleStatus
933+
IN_PROGRESS = "in-progress"
934+
PASS = "passed"
935+
FAIL = "failed"
936+
932937
def __init__(self, func_name, name, description):
933938
self.ruleId = func_name
934939
self.name = name
@@ -938,14 +943,18 @@ def __init__(self, func_name, name, description):
938943
self.recommended_action = ""
939944
self.docUrl = ""
940945
self.severity = "informational"
941-
self.ruleStatus = "passed" # passed|failed
946+
self.ruleStatus = AciResult.IN_PROGRESS
942947
self.showValidation = True
943948
self.failureDetails = {
944949
"failType": "",
945950
"data": [],
946951
"unformatted_data": [],
947952
}
948953

954+
@property
955+
def filename(self):
956+
return re.sub(r'[^a-zA-Z0-9_]+|\s+', '_', self.ruleId) + '.json'
957+
949958
@staticmethod
950959
def craftData(column, rows):
951960
if not (isinstance(rows, list) and isinstance(column, list)):
@@ -979,8 +988,9 @@ def updateWithResults(self, result, recommended_action, msg, doc_url, headers, d
979988
elif result in [MANUAL]:
980989
self.severity = "warning"
981990

991+
self.ruleStatus = AciResult.PASS
982992
if result not in [NA, PASS]:
983-
self.ruleStatus = "failed"
993+
self.ruleStatus = AciResult.FAIL
984994
self.failureDetails["failType"] = result
985995
self.failureDetails["data"] = self.craftData(headers, data)
986996
if unformatted_headers and unformatted_data:
@@ -996,12 +1006,11 @@ def buildResult(self):
9961006
return {slot: getattr(self, slot) for slot in self.__slots__}
9971007

9981008
def writeResult(self, path=JSON_DIR):
999-
filename = re.sub(r'[^a-zA-Z0-9_]+|\s+', '_', self.ruleId) + '.json'
10001009
if not os.path.isdir(path):
10011010
os.mkdir(path)
1002-
with open(os.path.join(path, filename), "w") as f:
1011+
with open(os.path.join(path, self.filename), "w") as f:
10031012
json.dump(self.buildResult(), f, indent=2)
1004-
return "{}/{}".format(path, filename)
1013+
return "{}/{}".format(path, self.filename)
10051014

10061015

10071016
class Result:
@@ -1034,6 +1043,12 @@ def check_wrapper(check_title):
10341043
def decorator(check_func):
10351044
@functools.wraps(check_func)
10361045
def wrapper(index, total_checks, *args, **kwargs):
1046+
# When init is True, we just initialize the result file and return
1047+
if kwargs.get("init") is True:
1048+
synth = AciResult(wrapper.__name__, check_title, "")
1049+
synth.writeResult()
1050+
return None
1051+
10371052
# Print `[Check 1/81] <title>...`
10381053
print_title(check_title, index, total_checks)
10391054

@@ -5302,6 +5317,7 @@ def apic_database_size_check(cversion, **kwargs):
53025317

53035318
# ---- Script Execution ----
53045319

5320+
53055321
def parse_args(args):
53065322
parser = ArgumentParser(description="ACI Pre-Upgrade Validation Script - %s" % SCRIPT_VERSION)
53075323
parser.add_argument("-t", "--tversion", action="store", type=str, help="Upgrade Target Version. Ex. 6.2(1a)")
@@ -5330,10 +5346,14 @@ def initialize():
53305346
logging.basicConfig(level=logging.DEBUG, filename=LOG_FILE, format=fmt, datefmt='%Y-%m-%d %H:%M:%S')
53315347

53325348

5333-
def prepare(api_only, arg_tversion, arg_cversion, total_checks):
5349+
def prepare(api_only, arg_tversion, arg_cversion, checks):
53345350
prints(' ==== %s%s, Script Version %s ====\n' % (ts, tz, SCRIPT_VERSION))
53355351
prints('!!!! Check https://github.com/datacenter/ACI-Pre-Upgrade-Validation-Script for Latest Release !!!!\n')
53365352

5353+
# Create empty result files for all checks
5354+
for idx, check in enumerate(checks):
5355+
check(idx + 1, len(checks), init=True)
5356+
53375357
username = password = None
53385358
if not api_only:
53395359
username, password = get_credentials()
@@ -5359,7 +5379,7 @@ def prepare(api_only, arg_tversion, arg_cversion, total_checks):
53595379
"tversion": str(tversion),
53605380
"sw_cversion": str(sw_cversion),
53615381
"api_only": api_only,
5362-
"total_checks": total_checks,
5382+
"total_checks": len(checks),
53635383
}
53645384
with open(META_FILE, "w") as f:
53655385
json.dump(metadata, f, indent=2)
@@ -5530,7 +5550,7 @@ def main(_args=None):
55305550
return
55315551

55325552
initialize()
5533-
inputs = prepare(args.api_only, args.tversion, args.cversion, len(checks))
5553+
inputs = prepare(args.api_only, args.tversion, args.cversion, checks)
55345554
run_checks(checks, inputs)
55355555
wrapup(args.no_cleanup)
55365556

tests/test_prepare.py

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
import importlib
33
import logging
44
import json
5+
import os
56

67
script = importlib.import_module("aci-preupgrade-validation-script")
78
AciVersion = script.AciVersion
9+
AciResult = script.AciResult
810

911

1012
@pytest.fixture(autouse=True)
@@ -133,7 +135,7 @@ def _mock_get_target_version(arg_tversion):
133135
"ave_eol_check",
134136
{"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]},
135137
),
136-
# veresions are switch syntax
138+
# versions are switch syntax
137139
# The version `get_target_version()` is ignored.
138140
(
139141
{
@@ -147,7 +149,7 @@ def _mock_get_target_version(arg_tversion):
147149
"ave_eol_check",
148150
{"username": "admin", "password": "mypassword", "cversion": AciVersion("6.0(4d)"), "tversion": AciVersion("6.1(4a)"), "sw_cversion": AciVersion("6.0(9d)"), "vpc_node_ids": ["101", "102"]},
149151
),
150-
# veresions are switch or APIC syntax
152+
# versions are switch or APIC syntax
151153
# The version `get_target_version()` is ignored.
152154
(
153155
{
@@ -164,15 +166,31 @@ def _mock_get_target_version(arg_tversion):
164166
],
165167
)
166168
def test_prepare(mock_icurl, api_only, arg_tversion, arg_cversion, debug_function, expected_result):
169+
script.initialize()
167170
checks = script.get_checks(api_only, debug_function)
168-
inputs = script.prepare(api_only, arg_tversion, arg_cversion, len(checks))
171+
inputs = script.prepare(api_only, arg_tversion, arg_cversion, checks)
169172
for key, value in expected_result.items():
170-
if "version" in key:
173+
if "version" in key: # cversion or tversion
171174
assert isinstance(inputs[key], AciVersion)
172175
assert str(inputs[key]) == str(value)
173176
else:
174177
assert inputs[key] == value
175178

179+
result_files = os.listdir(script.JSON_DIR)
180+
# Result files should be created for all checks
181+
assert len(result_files) == len(checks)
182+
for check in checks:
183+
# Rule name is known only through the wrapper `check_wrapper`.
184+
# Rule name content should be checked via another unit test.
185+
# Use AciResult class here just to get the filename from `check.__name__`.
186+
ar = AciResult(check.__name__, "unknown_name", "")
187+
file_path = os.path.join(script.JSON_DIR, ar.filename)
188+
assert os.path.exists(file_path), "Missing result file: {}".format(file_path)
189+
with open(file_path, "r") as f:
190+
result = json.load(f)
191+
assert result["ruleId"] == check.__name__
192+
assert result["ruleStatus"] == AciResult.IN_PROGRESS
193+
176194
with open(script.META_FILE, "r") as f:
177195
meta = json.load(f)
178196
assert meta["name"] == "PreupgradeCheck"
@@ -191,13 +209,13 @@ def test_prepare(mock_icurl, api_only, arg_tversion, arg_cversion, debug_functio
191209
def test_tversion_invald():
192210
with pytest.raises(SystemExit):
193211
with pytest.raises(ValueError):
194-
script.prepare(False, "invalid_version", "6.0(1a)", 1)
212+
script.prepare(False, "invalid_version", "6.0(1a)", [])
195213

196214

197215
def test_cversion_invald():
198216
with pytest.raises(SystemExit):
199217
with pytest.raises(ValueError):
200-
script.prepare(False, "6.0(1a)", "invalid_version", 1)
218+
script.prepare(False, "6.0(1a)", "invalid_version", [])
201219

202220

203221
@pytest.mark.parametrize(
@@ -264,7 +282,46 @@ def test_prepare_exception(capsys, caplog, mock_icurl, api_only, arg_tversion, a
264282
with pytest.raises(SystemExit):
265283
with pytest.raises(Exception):
266284
checks = script.get_checks(api_only, debug_function)
267-
script.prepare(api_only, arg_tversion, arg_cversion, len(checks))
285+
script.prepare(api_only, arg_tversion, arg_cversion, checks)
268286
captured = capsys.readouterr()
269287
print(captured.out)
270288
assert captured.out.endswith(expected_result)
289+
290+
291+
# Unit test focusing only on the result file creation
292+
def test_prepare_initial_result_files(mock_icurl, icurl_outputs):
293+
# Provide required API outputs used inside prepare()
294+
icurl_outputs.update({
295+
"firmwareCtrlrRunning.json": outputs["cversion"],
296+
"firmwareRunning.json": outputs["switch_version"],
297+
"fabricNodePEp.json": outputs["vpc_nodes"],
298+
})
299+
300+
# Create two simple checks with known titles
301+
@script.check_wrapper(check_title="Prepare Check A")
302+
def prep_check_a(**kwargs):
303+
return script.Result(result=script.PASS)
304+
305+
@script.check_wrapper(check_title="Prepare Check B")
306+
def prep_check_b(**kwargs):
307+
return script.Result(result=script.PASS)
308+
309+
checks = [prep_check_a, prep_check_b]
310+
311+
# Run prepare which should only initialize result files
312+
script.prepare(api_only=False, arg_tversion=None, arg_cversion=None, checks=checks)
313+
314+
# Verify result files and contents
315+
expected = {
316+
"prep_check_a": "Prepare Check A",
317+
"prep_check_b": "Prepare Check B",
318+
}
319+
for func_name, title in expected.items():
320+
ar = AciResult(func_name, title, "")
321+
file_path = os.path.join(script.JSON_DIR, ar.filename)
322+
assert os.path.exists(file_path), "Missing result file: {}".format(file_path)
323+
with open(file_path, "r") as f:
324+
data = json.load(f)
325+
assert data["ruleId"] == func_name
326+
assert data["name"] == title
327+
assert data["ruleStatus"] == AciResult.IN_PROGRESS

0 commit comments

Comments
 (0)