Repository navigation
test(agent): run the service interrupt against a real agent in the operator-agent suite - #684
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configurationConfiguration used: Repository: NVIDIA/nodewright/.coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: NVIDIA/nodewright/.coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 12 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughAdds a Chainsaw scenario that runs the service interrupt with a systemd unit installed by the test resource. The test checks the real and stale service markers, interrupt completion markers, resource status, node conditions, and stage log messages. The contributor documentation describes the scenario and explains why reboot remains unit-tested and Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Other Merge Risk: ⚪ Minimal · up to The service-restart scenario has no identified merge-blocking issue; it is ready for normal checks. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🧪 Generate unit tests (beta)
🛠️ Fix failing CI checks 💡
Comment |
f0d9e26 to
945ae14
Compare
Coverage Report for CI Build 36481834386Coverage increased (+0.06%) to 82.462%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions15 previously-covered lines in 3 files lost coverage.
Coverage Stats
💛 - Coveralls |
lockwobr
left a comment
There was a problem hiding this comment.
Running a real service interrupt against both agents is a good addition, and the gating (set -e on every block, exact-count marker) looks right. Inline are the few things that affect what the scenario actually proves. Everything below is a nit, and fine as follow-up tickets rather than changes here:
- The
Could not find fileguard doesn't see the interrupt logs (chainsaw-test.yaml:77). They go to$log/interrupts/, which$log/*.logdoesn't match, and the apply log has been rotated away by the time the guard runs. Asystemctlthat prints errors but exits 0 wouldn't be caught. - The marker comment (
chainsaw-test.yaml:57-59) implies the two agents behave the same. Python writesservice_restart_N.completebefore running the command, and Go writes it after success. Both produce the same layout once the interrupt finishes, but a Python pod killed mid-restart leaves a marker for a restart that never completed. Worth saying so, since the comment frames this as the handoff contract. apply.shhas noset -e(nodewright.yaml:47). A failed unit write still exits 0 afterecho "unit installed". The failure then shows up later as aUnit not foundinterrupt error or an assert timeout, which points away from the real cause.- The unit-file check (
chainsaw-test.yaml:71) is implied by the marker check. The marker can only exist if this run installed and restarted the unit. - The marker poll's 30 retries (
chainsaw-test.yaml:53).assert.yamlhas already waited forcomplete, andsystemctl restarton a oneshot unit is synchronous, so about 10 would do, like the sibling checks. - Pre-existing and not specific to this PR: the Python agent names step logs with one-second resolution (
%Y-%m-%d-%H%M%S) and opens them with'w'. Every stage here runs the sameshellscript_run.sh, so two stages starting in the same second would share a file, and the second would truncate the first. Init-container startup makes that rare, but it could flake any scenario that greps stage logs, including this one'spost interrupt ran. That's an agent ticket rather than a test one.
945ae14 to
0306161
Compare
|
The nits, in the order you listed them:
Inline threads are answered individually. The two-definition unit is your design from the |
…erator-agent suite
The only interrupt type the suite had ever run against a real agent was noop.
The main chainsaw suite's reboot cases run against the agentless image and
cover the operator's side of an interrupt only, so the agent's own interrupt
command path -- the daemon-reload, the per-service restart, the one completion
marker per operation that a retry resumes from -- had no end-to-end coverage
on either agent going into the Go cutover.
The service interrupt is the one type the suite can execute for real. Kind
nodes run systemd as PID 1, and the package pod mounts the host root with
HostToContainer propagation, so systemctl inside the agent's chroot reaches
the real manager. A reboot would terminate the kind node container, so that
path stays unit-tested.
The package's apply.sh installs the unit in two definitions with no reload
between them. The first is loaded and started (RemainAfterExit keeps it in
memory) and appends to a stale marker; the second, written over the file
without a daemon-reload, appends to the real marker. The interrupt runs
daemon-reload as operation 0 and the restart as operation 1, so exactly one
real-marker line proves the interrupt ran the real service manager exactly
once, and exactly one stale line proves the daemon-reload reached the
manager: a restart without an effective reload would have re-run the stale
definition instead. The stale line is also the proof apply.sh ran, whose own
log both agents have rotated away by the time the package completes (5 logs
kept, 6 written). This is a single clean pass; the resume-from-marker path a
retry would take is not exercised.
Both service_restart_{0,1}.complete markers pin the per-operation layout both
agents share once an interrupt finishes; the scenario records that Python
writes a marker before running its operation and Go after it succeeds.
post-interrupt and post-interrupt-check are asserted the way post_interrupt/
asserts them for noop, and every stage the package traverses has a script,
so the "Could not find file" guard fires only on a real defect. The unit,
the markers and the manager state are removed in finally as well as before
the run, so a reused cluster is left as it was found.
Closes #679
Signed-off-by: Riley Rice <rrice@nvidia.com>
0306161 to
dc29ca4
Compare
Description
Closes #679.
Adds
k8s-tests/operator-agent/service_restart/, which runs theserviceinterrupt against a real agent. Until now the only interrupt type ever run against a real agent in this suite wasnoop; the main chainsaw suite'srebootcases use the agentless image and cover the operator's side only. So the agent's own interrupt command path —systemctl daemon-reload, the per-servicerestart, and the one completion marker per operation that a retry resumes from — had no end-to-end coverage on either agent going into #222.Why
serviceand notreboot. Kind nodes run systemd as PID 1, andjob_builder.gomounts the host root withMountPropagation: HostToContainer, sosystemctlinside the agent's chroot reaches the real manager.rebootwould terminate the kind node container. That path stays unit-tested (#678); the scenario and the doc say so explicitly rather than let a green run imply more than it proves.What it asserts, all gated with
set -e:apply.shinstalls the unit twice. The first definition (Type=oneshot,RemainAfterExit=yes) is loaded and started, and itsExecStartappends a line to a stale marker; the file is then overwritten with the real definition, whoseExecStartappends to the real marker, without adaemon-reload. The interrupt runsdaemon-reloadas operation 0 and therestartas operation 1, so:grep -c . … = 1) proves the interrupt ran the real service manager exactly once.daemon-reloadnever took effect would have re-run the stale in-memory definition, leaving two stale lines and no real one. The stale line is also the proofapply.shran, whose own log both agents rotate away by completion (5 logs kept, this package writes 6).service_restart_0.completeandservice_restart_1.completeexist — the per-operation layout both agents share once an interrupt finishes. The comment records that Python writes a marker before running its operation and Go after it succeeds, so a Python pod killed mid-restart can leave a marker for a restart that never completed; this scenario is a single clean pass and asserts the layout.post-interruptandpost-interrupt-checkran, asserted the waypost_interrupt/asserts them fornoop:post interrupt ranandpost interrupt checkedin the logs,post-interrupt-check_ALL_CHECKEDunder the flags dir."Could not find file"inverted guard from ci(agent): run the operator-agent suite against the Go agent too #605, made meaningful by supplying every stage the package traverses. It sees only the survivingshellscript_run.shlogs, not the interrupt's own log under$log/interrupts/.Cleanup runs both before the run and in
finally: stop the unit, remove the file,daemon-reload,reset-failed, drop both markers. A reused local cluster is left as it was found. Runs against both agents through the existing path routing.restartAllServices(service procps force-reload) is deliberately not included here; it is worth a second scenario, and ifservice/procps turn out to be absent on the kind node image that is a finding to record rather than shim around.Checklist
git commit -s -S.docs/contributing/ci-test-pools.md's operator-agent coverage paragraph now names theserviceinterrupt and whyrebootis not covered here.Verification
Locally: YAML parses, the CR validates against the CRD schema (including the
interrupt.servicesfield),assert.yamlis structurally identical tointerrupt/'s, license headers clean. The e2e itself needs a cluster and runs in CI against both agents. The first CI run settled the one open question: chrootedsystemctlon the kind node works on both agents (the marker was written exactly once and both completion markers were present). That run failed only on a log assertion forapply.sh, whose log had been reaped by the 5-file retention; the stale marker now covers that. The two-definition unit from review depends on systemd keeping theRemainAfterExitunit in memory and using that stale definition on a restart without a reload, which I could not run locally either; the next CI run is the proof. AI assistance: produced with Claude Code.🤖 Generated with Claude Code