Skip to content

Commit 9f08c1f

Browse files
committed
Improve results.add_log() to make sure added logs survive reboots
1 parent 74c7a4f commit 9f08c1f

File tree

4 files changed

+62
-38
lines changed

4 files changed

+62
-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: 52 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,43 @@ def _count_yaml_results(path):
7274
return counter
7375

7476

77+
def _get_global_logs_path():
78+
"""Get the path for the logs persistence 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+
"""Append logs to the persistence file (survives reboots). Only for TMT and plain reporting."""
88+
if have_atex_api():
89+
return # ATEX handles logs itself via partial results
90+
logs_file = _get_global_logs_path()
91+
# read existing logs
92+
if logs_file.exists():
93+
with open(logs_file) as f:
94+
existing = json.load(f)
95+
else:
96+
existing = []
97+
# append new logs and write back
98+
existing.extend(new_logs)
99+
with open(logs_file, 'w') as f:
100+
json.dump(existing, f)
101+
102+
103+
def _read_from_global_logs():
104+
"""Read all logs from the persistence file. Only for TMT and plain reporting."""
105+
if have_atex_api():
106+
return [] # ATEX handles logs itself via partial results
107+
logs_file = _get_global_logs_path()
108+
if logs_file.exists():
109+
with open(logs_file) as f:
110+
return json.load(f)
111+
return []
112+
113+
75114
def report_atex(status, name=None, note=None, logs=None, *, partial=False):
76115
report_plain(status, name, note, logs)
77116

@@ -159,7 +198,9 @@ def report_tmt(status, name=None, note=None, logs=None, *, add_output=True):
159198
for log in logs:
160199
log = Path(log)
161200
dstfile = dst / log.name
162-
shutil.copyfile(log, dstfile)
201+
# Only copy if not already present (add_log() may have already copied it)
202+
if not dstfile.exists():
203+
shutil.copyfile(log, dstfile)
163204
log_entries.append(str(dstfile.relative_to(test_data)))
164205
# add an empty log if none are present, to work around Testing Farm
165206
# and its Oculus result viewer expecting at least something
@@ -231,8 +272,8 @@ def add_log(*logs):
231272
232273
The log file(s) will be processed immediately:
233274
- For ATEX: uploaded incrementally using report_atex() with partial=True
234-
- For TMT: copied to the TMT data directory and stored in global_logs for later upload
235-
- For plain: stored in global_logs for later upload
275+
- For TMT: copied to the TMT data directory and stored in global logs file to survive reboots
276+
- For plain: stored in global logs file to survive reboots
236277
237278
This allows logs to be added incrementally throughout the test,
238279
and ensures they're available even if the test later fails with a traceback.
@@ -249,13 +290,15 @@ def add_log(*logs):
249290
report_atex(status='error', logs=logs, partial=True)
250291
elif have_tmt_api():
251292
test_data = Path(os.environ['TMT_TEST_DATA'])
293+
new_logs = []
252294
for log in logs:
253295
log = Path(log)
254296
dstfile = test_data / log.name
255297
shutil.copyfile(log, dstfile)
256-
global_logs.append(str(dstfile.relative_to(test_data)))
298+
new_logs.append(str(dstfile.relative_to(test_data)))
299+
_store_to_global_logs(new_logs)
257300
else:
258-
global_logs.extend(str(log) for log in logs)
301+
_store_to_global_logs([str(log) for log in logs])
259302

260303

261304
def report_and_exit(status=None, note=None, logs=None):
@@ -279,8 +322,9 @@ def report_and_exit(status=None, note=None, logs=None):
279322
else:
280323
status = 'pass'
281324

282-
# combine accumulated logs with any directly passed logs
283-
all_logs = (global_logs + logs) if logs else global_logs
325+
# read logs from global logs file (survives reboots) and combine with directly passed logs
326+
stored_logs = _read_from_global_logs()
327+
all_logs = (stored_logs + logs) if logs else stored_logs
284328

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

0 commit comments

Comments
 (0)