Skip to content

Commit d61cc62

Browse files
committed
Improve results.add_log() to make sure added logs survive reboots
1 parent cacd2f3 commit d61cc62

File tree

4 files changed

+66
-38
lines changed

4 files changed

+66
-38
lines changed

hardening/host-os/oscap/test.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def do_one_remediation(ds, profile, arf_results):
2525
proc = util.subprocess_run(cmd)
2626
if proc.returncode not in [0,2]:
2727
raise RuntimeError(f"remediation oscap failed with {proc.returncode}")
28+
results.add_log(arf_results)
2829

2930

3031
if util.get_reboot_count() == 0:
@@ -40,7 +41,7 @@ def do_one_remediation(ds, profile, arf_results):
4041

4142
oscap.unselect_rules(util.get_datastream(), remediation_ds, remediation.excludes())
4243

43-
do_one_remediation(remediation_ds, profile, tmpdir / 'remediation-arf.xml')
44+
do_one_remediation(remediation_ds, profile, 'remediation-arf.xml')
4445

4546
util.reboot()
4647

@@ -49,7 +50,7 @@ def do_one_remediation(ds, profile, arf_results):
4950
elif util.get_reboot_count() == 1:
5051
util.log("second boot, doing second remediation")
5152

52-
do_one_remediation(remediation_ds, profile, tmpdir / 'remediation2-arf.xml')
53+
do_one_remediation(remediation_ds, profile, 'remediation2-arf.xml')
5354

5455
util.reboot()
5556

@@ -71,9 +72,4 @@ def do_one_remediation(ds, profile, arf_results):
7172
pack = util.RpmPack()
7273
pack.uninstall()
7374

74-
shutil.move(tmpdir / 'remediation-arf.xml', '.')
75-
shutil.move(tmpdir / 'remediation2-arf.xml', '.')
76-
77-
results.report_and_exit(logs=[
78-
'report.html', 'remediation-arf.xml', 'remediation2-arf.xml', 'scan-arf.xml',
79-
])
75+
results.report_and_exit(logs=['report.html', 'scan-arf.xml'])

hardening/oscap/old-new/test.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def remediate(datastream, arf_results, arf_results2):
4242
proc = g.ssh(' '.join(cmd))
4343
if proc.returncode not in [0,2]:
4444
raise RuntimeError(f"remediation oscap failed with {proc.returncode}")
45+
g.copy_from(arf_output)
46+
results.add_log(arf_output)
4547
g.soft_reboot()
4648

4749
# remediate using old content,
@@ -61,17 +63,5 @@ def remediate(datastream, arf_results, arf_results2):
6163

6264
g.copy_from('report.html')
6365
g.copy_from('scan-arf.xml')
64-
g.copy_from('remediation-arf-old.xml')
65-
g.copy_from('remediation-arf-old2.xml')
66-
g.copy_from('remediation-arf-new.xml')
67-
g.copy_from('remediation-arf-new2.xml')
6866

69-
logs = [
70-
'report.html',
71-
'scan-arf.xml',
72-
'remediation-arf-old.xml',
73-
'remediation-arf-old2.xml',
74-
'remediation-arf-new.xml',
75-
'remediation-arf-new2.xml',
76-
]
77-
results.report_and_exit(logs=logs)
67+
results.report_and_exit(logs=['report.html', 'scan-arf.xml'])

hardening/oscap/test.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
proc = g.ssh(' '.join(cmd))
4343
if proc.returncode not in [0,2]:
4444
raise RuntimeError(f"remediation oscap failed with {proc.returncode}")
45+
g.copy_from(arf_results)
46+
results.add_log(arf_results)
4547
g.soft_reboot()
4648

4749
# copy the original DS to the guest
@@ -56,14 +58,6 @@
5658
raise RuntimeError(f"post-reboot oscap failed unexpectedly with {proc.returncode}")
5759

5860
g.copy_from('report.html')
59-
g.copy_from('remediation-arf.xml')
60-
g.copy_from('remediation2-arf.xml')
6161
g.copy_from('scan-arf.xml')
6262

63-
logs = [
64-
'report.html',
65-
'remediation-arf.xml',
66-
'remediation2-arf.xml',
67-
'scan-arf.xml',
68-
]
69-
results.report_and_exit(logs=logs)
63+
results.report_and_exit(logs=['report.html', 'scan-arf.xml'])

lib/results.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def total(self):
3232

3333

3434
global_counts = Counter()
35-
global_logs = []
35+
36+
# file for storing global logs across reboots (only for TMT and plain reporting)
37+
GLOBAL_LOGS_FILE = '_global_logs.json'
3638

3739

3840
def have_atex_api():
@@ -72,6 +74,46 @@ def _count_yaml_results(path):
7274
return counter
7375

7476

77+
def _get_global_logs_path():
78+
"""Get the path of the global log storage file. Only for TMT and plain reporting."""
79+
if have_tmt_api():
80+
return Path(os.environ['TMT_TEST_DATA']) / GLOBAL_LOGS_FILE
81+
else:
82+
# plain mode: use current working directory
83+
return Path(GLOBAL_LOGS_FILE)
84+
85+
86+
def _store_to_global_logs(new_logs):
87+
"""
88+
Append logs to the global log storage file (survives reboots).
89+
Only for TMT and plain reporting.
90+
"""
91+
if have_atex_api():
92+
return # ATEX handles logs itself via partial results
93+
logs_file = _get_global_logs_path()
94+
# read existing logs
95+
if logs_file.exists():
96+
with open(logs_file) as f:
97+
existing = json.load(f)
98+
else:
99+
existing = []
100+
# append new logs and write back
101+
existing.extend(new_logs)
102+
with open(logs_file, 'w') as f:
103+
json.dump(existing, f)
104+
105+
106+
def _read_from_global_logs():
107+
"""Read all logs from the global log storage file. Only for TMT and plain reporting."""
108+
if have_atex_api():
109+
return [] # ATEX handles logs itself via partial results
110+
logs_file = _get_global_logs_path()
111+
if logs_file.exists():
112+
with open(logs_file) as f:
113+
return json.load(f)
114+
return []
115+
116+
75117
def report_atex(status, name=None, note=None, logs=None, *, partial=False):
76118
if not partial:
77119
report_plain(status, name, note, logs)
@@ -160,7 +202,9 @@ def report_tmt(status, name=None, note=None, logs=None, *, add_output=True):
160202
for log in logs:
161203
log = Path(log)
162204
dstfile = dst / log.name
163-
shutil.copyfile(log, dstfile)
205+
# Only copy if not already present (add_log() may have already copied it)
206+
if not dstfile.exists():
207+
shutil.copyfile(log, dstfile)
164208
log_entries.append(str(dstfile.relative_to(test_data)))
165209
# add an empty log if none are present, to work around Testing Farm
166210
# and its Oculus result viewer expecting at least something
@@ -232,8 +276,9 @@ def add_log(*logs):
232276
233277
The log file(s) will be processed immediately:
234278
- For ATEX: uploaded incrementally using report_atex() with partial=True
235-
- For TMT: copied to the TMT data directory and stored in global_logs for later upload
236-
- For plain: stored in global_logs for later upload
279+
- For TMT: copied to the TMT data directory and stored in global log storage
280+
file to survive reboots
281+
- For plain: stored in global log storage file to survive reboots
237282
238283
This allows logs to be added incrementally throughout the test,
239284
and ensures they're available even if the test later fails with a traceback.
@@ -250,13 +295,15 @@ def add_log(*logs):
250295
report_atex(status='error', logs=logs, partial=True)
251296
elif have_tmt_api():
252297
test_data = Path(os.environ['TMT_TEST_DATA'])
298+
new_logs = []
253299
for log in logs:
254300
log = Path(log)
255301
dstfile = test_data / log.name
256302
shutil.copyfile(log, dstfile)
257-
global_logs.append(str(dstfile.relative_to(test_data)))
303+
new_logs.append(str(dstfile.relative_to(test_data)))
304+
_store_to_global_logs(new_logs)
258305
else:
259-
global_logs.extend(str(log) for log in logs)
306+
_store_to_global_logs([str(log) for log in logs])
260307

261308

262309
def report_and_exit(status=None, note=None, logs=None):
@@ -280,8 +327,9 @@ def report_and_exit(status=None, note=None, logs=None):
280327
else:
281328
status = 'pass'
282329

283-
# combine accumulated logs with any directly passed logs
284-
all_logs = (global_logs + logs) if logs else global_logs
330+
# read logs from global log storage file and combine with directly passed logs
331+
stored_logs = _read_from_global_logs()
332+
all_logs = (stored_logs + logs) if logs else stored_logs
285333

286334
# report and pass the status through the waiving logic, use combined logs or None if empty
287335
status = report(status=status, note=note, logs=(all_logs or None))

0 commit comments

Comments
 (0)