Skip to content

Commit 56ddfdd

Browse files
committed
contest: remove: add support for env checks
Driver tests seem to fail quite a lot due to other tests not cleaning up after themselves. Add a hook point and a script for checking that the env is sane. The expectation is that we'll run contest/scripts/env_check.py in each netns before the test (as part of setup), and then after each test. If post-test check fails VM is destroyed and new one gets started. Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 1bce351 commit 56ddfdd

File tree

3 files changed

+140
-4
lines changed

3 files changed

+140
-4
lines changed

contest/remote/lib/vm.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,11 @@ def _set_env(self):
149149
self.cmd("export LD_LIBRARY_PATH=" + self.config.get('vm', 'ld_paths') + ':$LD_LIBRARY_PATH')
150150
self.drain_to_prompt()
151151

152-
if self.config.get('vm', 'setup', fallback=None):
153-
self.cmd(self.config.get('vm', 'setup'))
152+
setup_scripts = self.config.get('vm', 'setup', fallback='').split(',')
153+
for setup in setup_scripts:
154+
if not setup:
155+
continue
156+
self.cmd(setup)
154157
self.drain_to_prompt()
155158

156159
exports = self.config.get('vm', 'exports', fallback=None)

contest/remote/vmksft-p.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,20 @@ def _vm_thread(config, results_path, thr_id, hard_stop, in_queue, out_queue):
222222

223223
print(f"INFO: thr-{thr_id} {prog} >> nested tests: {len(nested_tests)}")
224224

225-
if not is_retry and result == 'fail':
225+
can_retry = not is_retry
226+
227+
post_check = config.get('ksft', 'post_check', fallback=None)
228+
if post_check and not vm.fail_state:
229+
vm.cmd(post_check)
230+
vm.drain_to_prompt()
231+
pc = vm.bash_prev_retcode()
232+
if pc != 0:
233+
vm.fail_state = "env-check-fail"
234+
if result == 'pass':
235+
result = 'fail'
236+
can_retry = False # Don't waste time, the test is buggy
237+
238+
if can_retry and result == 'fail':
226239
in_queue.put(outcome)
227240
else:
228241
out_queue.put(outcome)
@@ -232,7 +245,7 @@ def _vm_thread(config, results_path, thr_id, hard_stop, in_queue, out_queue):
232245
"found": indicators, "vm_state": vm.fail_state})
233246

234247
if vm.fail_state:
235-
print(f"INFO: thr-{thr_id} VM kernel crashed, destroying it")
248+
print(f"INFO: thr-{thr_id} VM {vm.fail_state}, destroying it")
236249
vm.stop()
237250
vm.dump_log(results_path + f'/vm-stop-thr{thr_id}-{vm_id}')
238251
vm = None

contest/scripts/env_check.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Collect system state info. Save it to a JSON file,
4+
if file already exists, compare it first and report deltas.
5+
"""
6+
7+
import json
8+
import os
9+
import subprocess
10+
import sys
11+
12+
13+
def run_cmd_text(cmd):
14+
"""Execute a shell command and return its output as text."""
15+
result = subprocess.run(cmd, shell=True, check=False,
16+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
17+
universal_newlines=True)
18+
return result.stdout
19+
20+
21+
def run_cmd_json(cmd):
22+
"""Execute a shell command and return its output parsed as JSON."""
23+
result = subprocess.run(cmd, shell=True, check=False,
24+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
25+
universal_newlines=True)
26+
if result.returncode != 0:
27+
return {"error": result.stderr.strip()}
28+
29+
ret = json.loads(result.stdout)
30+
# "decapsulate" the one element arrays that ip and ethtool like return
31+
if isinstance(ret, list) and len(ret) == 1:
32+
ret = ret[0]
33+
return ret
34+
35+
36+
def collect_system_state():
37+
"""Collect network interface information."""
38+
state = {
39+
"links": {},
40+
"chans": {},
41+
"feat": {},
42+
"rings": {},
43+
"rss": {},
44+
"ntuple": {},
45+
}
46+
47+
interfaces = run_cmd_json("ip -j -d link show")
48+
49+
for iface in interfaces:
50+
ifname = iface['ifname']
51+
52+
state["links"][ifname] = iface
53+
54+
state["chans"][ifname] = run_cmd_json(f"ethtool -j -l {ifname}")
55+
state["feat" ][ifname] = run_cmd_json(f"ethtool -j -k {ifname}")
56+
state["rings"][ifname] = run_cmd_json(f"ethtool -j -g {ifname}")
57+
state["rss" ][ifname] = run_cmd_json(f"ethtool -j -x {ifname}")
58+
if "rss-hash-key" in state["rss"][ifname]:
59+
del state["rss"][ifname]["rss-hash-key"]
60+
state["ntuple"][ifname] = run_cmd_text(f"ethtool -n {ifname}")
61+
62+
return state
63+
64+
65+
def compare_states(current, saved, path=""):
66+
"""Compare current system state with saved state."""
67+
68+
ret = 0
69+
70+
if isinstance(current, dict) and isinstance(saved, dict):
71+
for k in current.keys() | saved.keys():
72+
if k in current and k in saved:
73+
ret |= compare_states(current[k], saved[k], path=f"{path}.{k}")
74+
else:
75+
print(f"Saved {path}.{k}:", saved.get(k))
76+
print(f"Current {path}.{k}:", current.get(k))
77+
ret = 1
78+
else:
79+
if current != saved:
80+
print(f"Saved {path}:", saved)
81+
print(f"Current {path}:", current)
82+
ret = 1
83+
84+
return ret
85+
86+
87+
def main():
88+
"""Main function to collect and compare network interface states."""
89+
output_file = "/tmp/nipa-env-state.json"
90+
if len(sys.argv) > 1:
91+
output_file = sys.argv[1]
92+
93+
# Collect current system state
94+
current_state = collect_system_state()
95+
exit_code = 0
96+
97+
# Check if the file already exists
98+
if os.path.exists(output_file):
99+
print("Comparing to existing state file: ", end="")
100+
try:
101+
with open(output_file, 'r', encoding='utf-8') as f:
102+
saved_state = json.load(f)
103+
104+
# Compare states
105+
exit_code = compare_states(current_state, saved_state)
106+
if exit_code == 0:
107+
print("no differences detected.")
108+
except (json.JSONDecodeError, IOError, OSError) as e:
109+
print("Error loading or comparing:")
110+
print(e)
111+
# Save current state to file
112+
with open(output_file, 'w', encoding='utf-8') as f:
113+
json.dump(current_state, f, indent=2)
114+
print(f"Current system state saved to {output_file}")
115+
116+
sys.exit(exit_code)
117+
118+
119+
if __name__ == "__main__":
120+
main()

0 commit comments

Comments
 (0)