trident-acl-agent: add storm trident-acl-agent E2E test harness, test images, and pipeline - #731
trident-acl-agent: add storm trident-acl-agent E2E test harness, test images, and pipeline#731bfjelds (bfjelds) wants to merge 11 commits into
Conversation
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
bafab91 to
9b47495
Compare
9b47495 to
b94993f
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new Storm E2E scenario to validate trident-acl-agent end-to-end against a real tridentd on a VM (with fake Kubernetes apiserver + Nebraska/image server), along with the VM test images, CI pipeline stage wiring, and developer docs to run the suite locally.
Changes:
- Introduces
storm-trident run aclagentscenario with test cases for deploy, update (stage/finalize/reboot/commit), rollback, log collection, and cleanup. - Adds fake in-process infrastructure (apiserver, Omaha/Nebraska proxy, image server) used by the scenario to drive and observe Node annotation state transitions.
- Adds new ACL-agent VM image configs + Makefile target(s), plus a new CI stage to build images and run the scenario.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/storm/utils/vm/qemu/qemu.go | Adds configurable qcow2 image selection regex for QEMU VM deployment. |
| tools/storm/aclagent/utils/config/config.go | Defines Storm scenario configuration/flags for the ACL-agent suite. |
| tools/storm/aclagent/trident.go | Registers the new aclagent Storm scenario and test case sequencing. |
| tools/storm/aclagent/tests/vm.go | VM deploy/check/cleanup wrappers for QEMU/Azure platforms. |
| tools/storm/aclagent/tests/update.go | Implements the A/B update E2E flow, including fake services and post-reboot commit validation. |
| tools/storm/aclagent/tests/rollback.go | Implements rollback E2E flow and regression coverage for no-op rollback. |
| tools/storm/aclagent/tests/logs.go | Hooks into existing VM log-fetch helper for the scenario. |
| tools/storm/aclagent/README.md | Scenario usage and behavior documentation. |
| tools/storm/aclagent/proxies/scenario.go | Scenario YAML schema + validation for driving test steps. |
| tools/storm/aclagent/proxies/rp.go | “RP client” to patch requests / poll status via the fake apiserver. |
| tools/storm/aclagent/proxies/nebraska.go | Fake Nebraska/Omaha endpoint for update availability + package metadata. |
| tools/storm/aclagent/proxies/kubelet.go | Kubelet-ish helper for simulated ready/reboot signaling (currently unused by the main flow). |
| tools/storm/aclagent/proxies/imageserver.go | Simple HTTP server to serve a real update artifact (e.g. .cosi). |
| tools/storm/aclagent/proxies/constants.go | Shared constants for annotations/labels and marker defaults. |
| tools/storm/aclagent/proxies/apiserver.go | Minimal fake Kubernetes apiserver with LIST/WATCH/PATCH support for Node. |
| tools/go.mod | Adds Kubernetes API dependencies required by the fake apiserver/client types. |
| tools/go.sum | Updates module sums to reflect new Kubernetes-related dependencies. |
| tools/cmd/storm-trident/main.go | Registers the aclagent scenario with the storm-trident binary. |
| tests/images/trident-vm-testimage/README.md | Documents the new ACL-agent VM image variant. |
| tests/images/trident-vm-testimage/base/updateimg-acl-agent.yaml | MIC config for the ACL-agent update image (.cosi) that enables the agent. |
| tests/images/trident-vm-testimage/base/baseimg-acl-agent.yaml | MIC config for the ACL-agent base qcow2 image (agent installed, not enabled). |
| tests/images/testimages.py | Wires the ACL-agent update image into the test-image build plumbing. |
| Makefile | Adds a new qcow2 build target for the ACL-agent base test image. |
| docs/Development/Testing/TridentAclAgent-Tests.md | Adds developer documentation for building/running the ACL-agent Storm scenario. |
| docs/Development/Testing/Testing.md | Links to the new ACL-agent testing documentation. |
| .pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml | Adds CI stages/jobs to build images and run the ACL-agent Storm scenario. |
| .pipelines/templates/e2e-template.yml | Wires the new ACL-agent test stage into the E2E template. |
Suppressed comments (1)
tools/storm/aclagent/proxies/scenario.go:81
Validate()currently countsassert-failure-reasonas a valid mutually-exclusive step kind, but that step kind is not runnable. If removingassert-failure-reason, update the validation error accordingly so invalid scenarios fail fast with a correct message.
if step.AssertFailureReason != "" {
kinds++
}
if kinds != 1 {
return fmt.Errorf("scenario step %d must set exactly one of patch/expect/assert-failure-reason", index)
e7f33e9 to
b26b5a0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (8)
docs/Development/Testing/TridentAclAgent-Tests.md:176
- The "Reboot Choice" section describes shim-based reboot interception, but the harness code treats finalize as a real reboot and waits for SSH to go down/up. The docs should describe the real reboot flow, or the implementation should be updated to actually use a reboot shim.
## Reboot Choice
This scenario uses shim-based reboot interception rather than a full VM
reboot: a `reboot`/`systemctl reboot` shim on `PATH` inside the VM signals the
scenario's controller and exits the agent process instead of actually
rebooting. The scenario then restarts `trident-acl-agent` fresh, exercising
its post-reboot resume logic without tearing down the SSH session or the
in-process fake services. This is less realistic than a full reboot, but it
keeps the test deterministic and fast.
tests/images/trident-vm-testimage/README.md:21
- This section says the ACL-agent image "enables
trident-acl-agent.service" and that the scenario writes "localhost" tunnel endpoints, but the base qcow2 config (baseimg-acl-agent.yaml) leaves the service disabled, and the storm harness configures endpoints via--host-endpoint-ip(not localhost reverse tunnels). Updating the README will keep the image docs consistent with the scenario and image configs.
For both, a set of corresponding update images is available. The ACL-agent
variant reuses the servicing-style VM image layout but additionally installs
`trident-acl-agent` and enables `trident-acl-agent.service` so storm ACL-agent
scenarios can drive a real in-guest agent talking to `tridentd`. The image does
not preseed /etc/trident/trident-acl-agent.conf with runner-specific tunnel
ports; the test scenario should SSH in after boot and write the real localhost
proxy endpoints for Nebraska and the Kubernetes API server.
tools/storm/aclagent/README.md:89
- The "Reboot choice" section says the scenario uses a reboot shim to avoid a full reboot, but
run-ab-update/run-rollbackexplicitly wait for SSH to drop and come back, and describe a realsystemctl reboottriggered by the agent. The docs should match the harness's real-reboot behavior.
## Reboot choice
This scenario keeps the shim-based reboot interception from the old tester.
That is less realistic than a full VM reboot, but it keeps the test deterministic
and lets the storm runner hold the reverse SSH tunnels and in-process fake services
steady while the agent drives the finalize path.
tools/storm/aclagent/proxies/apiserver.go:285
handlePatchcallsDecoder.DisallowUnknownFields(), but the body is decoded intomap[string]any, so unknown fields are never rejected. This can hide mismatches between what the harness expects and what the agent actually patches.
Decode directly into metadataPatch (with DisallowUnknownFields) before applying the patch.
defer r.Body.Close()
body := json.NewDecoder(r.Body)
body.DisallowUnknownFields()
var raw map[string]any
if err := body.Decode(&raw); err != nil {
http.Error(w, fmt.Sprintf("invalid patch body: %v", err), http.StatusBadRequest)
return
}
tools/storm/aclagent/README.md:46
- This README describes
prepareVmForAclAgentas usinglocalhost:<port>via reverse-SSH-forwarded tunnels and runningenable --now, but the harness configures the agent to talk tohttp://<host-endpoint-ip>:<port>and does an explicit restart (to reconnect between test cases). Keeping the README aligned with the actual harness behavior will reduce confusion when running locally.
`prepareVmForAclAgent` writes `/etc/trident/trident-acl-agent.conf` pointing
at the `localhost:<port>` endpoints storm reverse-SSH-forwards into the VM,
then runs `systemctl enable --now trident-acl-agent.service`. Before that
runs, the service simply isn't started -- no crash-looping, no log noise.
docs/Development/Testing/TridentAclAgent-Tests.md:153
- The "Test Cases" list is out of sync with the current harness: config/service setup happens in
run-ab-update/run-rollback(notcheck-deployment), the scenario includesrun-rollback, and the harness performs a real reboot (SSH goes down/up) rather than a shim-based simulated reboot.
1. **deploy-vm** — Copies the base qcow2 image and creates a QEMU VM
2. **check-deployment** — Verifies the VM booted and is accessible via SSH;
writes `/etc/trident/trident-acl-agent.conf` pointing at the
`localhost:<port>` endpoints storm reverse-SSH-forwards into the VM, then
runs `systemctl enable --now trident-acl-agent.service`
3. **run-ab-update** — Starts the fake apiserver and fake Nebraska/Omaha
endpoints in-process, seeds bootstrap node labels, patches the desired
update-image label, and waits for `trident-acl-agent` to drive a real
Trident A/B update to completion (including the shim-based simulated
reboot)
docs/Development/Testing/TridentAclAgent-Tests.md:159
- The flags table lists
--artifacts-dirdefault as/tmp, but the scenario'sTestConfigsetsArtifactsDirdefault to.. This mismatch can cause local runs to look in the wrong directory by default.
| Flag | Description | Default |
|------|-------------|---------|
| `--artifacts-dir` | Directory containing VM images | `/tmp` |
| `--output-path` | Output directory for logs | `./output` |
| `--platform` | `qemu` or `azure` | `qemu` |
| `--ssh-private-key-path` | Path to SSH private key | `~/.ssh/id_rsa` |
| `--api-server-port` | Port for the fake Kubernetes API server | `18080` |
tools/storm/aclagent/proxies/rp.go:63
ScenarioStepsupportsassert-failure-reason(andScenario.Validateallows it), butRPClient.runStepnever handlesAssertFailureReason. Any scenario YAML that usesassert-failure-reasonwill currently fail with "no recognized action" instead of performing the assertion.
Either implement assert-failure-reason handling in the runner, or remove the field (and its validation path) until it’s supported.
func (c *RPClient) runStep(ctx context.Context, index int, step ScenarioStep) (*StepReport, error) {
switch {
case step.Patch != nil:
if err := c.patchNodeRequest(ctx, step.Patch); err != nil {
return nil, err
}
return &StepReport{Index: index, Kind: "patch", Passed: true, Message: "patched fake Node request annotation"}, nil
case step.Expect != nil:
return c.expectStatus(ctx, index, step.Expect)
default:
return nil, fmt.Errorf("step %d had no recognized action", index)
}
- use grep -qF for literal IP:port config checks (avoid regex-dot false match) - waitForVmRebootAndSshBack now fails if SSH never goes down, instead of silently passing when no reboot occurred - remove unimplemented assert-failure-reason scenario field - fix ExpectTimeout step message to not claim a timeout when a match actually occurred (the real failure case) - correct stale docs describing shim-based reboot interception; scenario does a real VM reboot + SSH reachability wait - correct trident-vm-testimage README: only the update image enables trident-acl-agent.service by default, base image leaves it disabled Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c06585d-a82d-475f-a802-83fdfa012d86
- use grep -qF for literal IP:port config checks (avoid regex-dot false match) - waitForVmRebootAndSshBack now fails if SSH never goes down, instead of silently passing when no reboot occurred - remove unimplemented assert-failure-reason scenario field - fix ExpectTimeout step message to not claim a timeout when a match actually occurred (the real failure case) - correct stale docs describing shim-based reboot interception; scenario does a real VM reboot + SSH reachability wait - correct trident-vm-testimage README: only the update image enables trident-acl-agent.service by default, base image leaves it disabled Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c06585d-a82d-475f-a802-83fdfa012d86
1eeda78 to
5a5ecce
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (5)
tools/storm/aclagent/utils/config/config.go:4
ArtifactsDirdefault is"."here, but other storm scenarios default it to"/tmp"(e.g. tools/storm/servicing/utils/config/config.go:4 and tools/storm/rollback/utils/config/config.go:4). Keeping this consistent avoids surprising behavior when running different scenarios locally and matches the docs’ stated default.
ArtifactsDir string `help:"Directory containing artifacts for the VM" default:"."`
tools/storm/utils/vm/qemu/qemu.go:24
ImagePatternis compiled as a regex (tools/storm/utils/file/file.go:13) and the default pattern contains an unescaped.beforeqcow2, so it will also match names like...testimageXqcow2and can accidentally match unexpected files (or trigger the "multiple files found" error). Escaping the dot makes the default behave as intended.
SecureBoot bool `help:"Enable secure boot for the VM" default:"false"`
SerialLog string `help:"Path to the serial log file" default:"/tmp/trident-vm-verity-test.log"`
ImagePattern string `help:"Regex pattern used to find the base VM image (.qcow2) in the artifacts directory" default:"^trident-vm-.*-testimage.qcow2$"`
tools/storm/aclagent/README.md:46
- This describes
prepareVmForAclAgentas usinglocalhost:<port>via reverse-SSH tunnels and runningenable --now, but the implementation writes host-IP endpoints (see tools/storm/aclagent/tests/update.go:262-277) and doessystemctl enable+systemctl restart(update.go:360-366). Updating this keeps the doc aligned with the actual harness behavior (and the rationale about tunnels not surviving reboot).
`prepareVmForAclAgent` writes `/etc/trident/trident-acl-agent.conf` pointing
at the `localhost:<port>` endpoints storm reverse-SSH-forwards into the VM,
then runs `systemctl enable --now trident-acl-agent.service`. Before that
runs, the service simply isn't started -- no crash-looping, no log noise.
tools/storm/aclagent/README.md:89
- This section still says the scenario uses shim-based reboot interception and depends on reverse SSH tunnels, but the tests wait for a real reboot by polling SSH reachability (tools/storm/aclagent/tests/update.go:403-429) and explicitly avoid tunnels for the fake endpoints (update.go:124-127). This mismatch will confuse anyone debugging reboot-related failures.
This scenario keeps the shim-based reboot interception from the old tester.
That is less realistic than a full VM reboot, but it keeps the test deterministic
and lets the storm runner hold the reverse SSH tunnels and in-process fake services
steady while the agent drives the finalize path.
docs/Development/Testing/TridentAclAgent-Tests.md:142
- The
check-deploymentdescription says the config points atlocalhost:<port>via reverse-SSH forwarding and that it runsenable --now, but the harness uses a host-reachable IP/port (tests/update.go:262-277) and performssystemctl enable+systemctl restart(update.go:360-366). Aligning this documentation matters because it affects how people reason about connectivity across the real reboot.
1. **deploy-vm** — Copies the base qcow2 image and creates a QEMU VM
2. **check-deployment** — Verifies the VM booted and is accessible via SSH;
writes `/etc/trident/trident-acl-agent.conf` pointing at the
`localhost:<port>` endpoints storm reverse-SSH-forwards into the VM, then
runs `systemctl enable --now trident-acl-agent.service`
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (10)
tools/storm/aclagent/proxies/rp.go:83
- Issue:
decodeStatus()errors are ignored, so a malformedacl.azure.com/update-statusannotation will be silently treated as “no status yet” and the step will just time out, hiding the real failure.
Evidence: status, _ := decodeStatus(node) drops the error.
Suggestion: handle the error explicitly and fail the step with the decode error + raw annotation value so the scenario report is self-diagnosing.
node, err := c.getNode(ctx)
if err != nil {
return nil, err
}
status, _ := decodeStatus(node)
tools/storm/aclagent/tests/update.go:128
- Issue: The fake apiserver binds to
0.0.0.0, which exposes the unauthenticated test endpoint on all host interfaces. This is avoidable (and can be surprising on developer machines) since the VM only needs it reachable viaHostEndpointIP.
Evidence: ListenAndServe(..., "0.0.0.0:<port>").
Suggestion: bind to testConfig.HostEndpointIP (or another explicit listen address) so the server is only exposed where needed.
// Bind on all interfaces (not 127.0.0.1) so the VM can reach the fake
// apiserver directly over the libvirt NAT network at testConfig.HostEndpointIP,
// instead of relying on reverse SSH tunnels. Tunnels don't survive a real
// VM reboot; a real host IP does.
if _, err := apiServer.ListenAndServe(ctx, fmt.Sprintf("0.0.0.0:%d", testConfig.APIServerPort)); err != nil {
tools/storm/aclagent/tests/update.go:158
- Issue: The fake image server binds to
0.0.0.0, exposing a local artifact over unauthenticated HTTP on all host interfaces.
Evidence: imageServer.ListenAndServe(..., "0.0.0.0:<port>").
Suggestion: bind to testConfig.HostEndpointIP so it’s only reachable on the interface the VM uses.
return fmt.Errorf("failed to hash image %s: %w", imagePath, err)
}
imageServer := &stormproxies.ImageServer{ImagePath: imagePath}
if _, err := imageServer.ListenAndServe(ctx, fmt.Sprintf("0.0.0.0:%d", testConfig.ImageServerPort)); err != nil {
return fmt.Errorf("failed to start fake image server: %w", err)
tools/storm/aclagent/tests/update.go:173
- Issue: The fake Nebraska endpoint binds to
0.0.0.0, exposing an unauthenticated update server mock on all host interfaces.
Evidence: nebraska.ListenAndServe(..., "0.0.0.0:<port>").
Suggestion: bind to testConfig.HostEndpointIP so it’s only reachable on the interface the VM uses.
SHA384: nebraskaSHA384,
PackageName: nebraskaPackageName,
}}
if _, err := nebraska.ListenAndServe(ctx, fmt.Sprintf("0.0.0.0:%d", testConfig.NebraskaPort)); err != nil {
return fmt.Errorf("failed to start fake Nebraska endpoint: %w", err)
tools/storm/aclagent/tests/rollback.go:42
- Issue: The fake apiserver in rollback binds to
0.0.0.0, exposing an unauthenticated test endpoint on all host interfaces.
Evidence: ListenAndServe(..., "0.0.0.0:<port>").
Suggestion: bind to testConfig.HostEndpointIP (or another explicit listen address) to limit exposure to the interface the VM uses.
nodeStore := stormproxies.NewNodeStore(stormproxies.NewSeedNode(testConfig.NodeName, map[string]string{}))
apiServer := stormproxies.NewAPIServer(testConfig.NodeName, nodeStore)
if _, err := apiServer.ListenAndServe(ctx, fmt.Sprintf("0.0.0.0:%d", testConfig.APIServerPort)); err != nil {
return fmt.Errorf("failed to start fake apiserver: %w", err)
}
tools/storm/aclagent/trident.go:72
- Issue: On error paths,
runTestCasecallstc.FailFromError(err)but then continues and unconditionally logs the test as “passed”. That makes logs misleading and can mask follow-on errors after a known failure.
Evidence: after tc.FailFromError(err) the function falls through to logrus.Infof("test case '%s' passed"...).
Suggestion: return immediately after FailFromError, and only log “passed” when there was no error.
if testCaseSpecificConfig.OutputPath != "" {
testCaseSpecificConfig.OutputPath = filepath.Join(testCaseSpecificConfig.OutputPath, tc.Name())
if err := os.MkdirAll(testCaseSpecificConfig.OutputPath, 0o755); err != nil {
tc.FailFromError(err)
}
docs/Development/Testing/TridentAclAgent-Tests.md:142
- Issue: The doc says
check-deploymentwrites config pointing atlocalhost:<port>via reverse SSH tunnels, but the harness now configures the VM to talk directly to the test runner atHostEndpointIP:<port>(and avoids tunnels specifically because they don’t survive a real reboot).
Evidence: RunABUpdate binds the fake apiserver on 0.0.0.0 and advertises http://<HostEndpointIP>:<port> to the VM.
Suggestion: update this step description to match the actual networking model (host-reachable IP/port, no reverse tunnels).
2. **check-deployment** — Verifies the VM booted and is accessible via SSH;
writes `/etc/trident/trident-acl-agent.conf` pointing at the
`localhost:<port>` endpoints storm reverse-SSH-forwards into the VM, then
runs `systemctl enable --now trident-acl-agent.service`
docs/Development/Testing/TridentAclAgent-Tests.md:157
- Issue: Documented default for
--artifacts-dirdoesn’t match the scenario’s flag default.
Evidence: docs list /tmp, but TestConfig.ArtifactsDir defaults to ..
Suggestion: update the doc table default (or the flag default) so they stay consistent.
| `--artifacts-dir` | Directory containing VM images | `/tmp` |
tools/storm/aclagent/README.md:46
- Issue: This README says the scenario uses
localhost:<port>endpoints via reverse SSH forwarding, but the harness configures the VM to reach the fake services directly onHostEndpointIP:<port>(and intentionally avoids tunnels for reboot survival).
Evidence: prepareVmForAclAgent writes endpoints using HostEndpointIP.
Suggestion: update the README wording to match the actual networking model.
`prepareVmForAclAgent` writes `/etc/trident/trident-acl-agent.conf` pointing
at the `localhost:<port>` endpoints storm reverse-SSH-forwards into the VM,
then runs `systemctl enable --now trident-acl-agent.service`. Before that
runs, the service simply isn't started -- no crash-looping, no log noise.
tests/images/trident-vm-testimage/README.md:25
- Issue: This README says the scenario should write “localhost proxy endpoints”, but the harness configures the VM to talk to the test runner over a host-reachable IP (
HostEndpointIP) rather than SSH-tunneled localhost.
Evidence: prepareVmForAclAgent uses HostEndpointIP for the apiserver/Nebraska endpoints.
Suggestion: update this text to describe writing <host-endpoint-ip>:<port> endpoints (reachable from the VM) instead of localhost.
into it there is no test harness left to `systemctl enable --now` it. Neither
image preseeds /etc/trident/trident-acl-agent.conf with runner-specific
tunnel ports; the test scenario should SSH in after boot and write the real
localhost proxy endpoints for Nebraska and the Kubernetes API server.
- use grep -qF for literal IP:port config checks (avoid regex-dot false match) - waitForVmRebootAndSshBack now fails if SSH never goes down, instead of silently passing when no reboot occurred - remove unimplemented assert-failure-reason scenario field - fix ExpectTimeout step message to not claim a timeout when a match actually occurred (the real failure case) - correct stale docs describing shim-based reboot interception; scenario does a real VM reboot + SSH reachability wait - correct trident-vm-testimage README: only the update image enables trident-acl-agent.service by default, base image leaves it disabled Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c06585d-a82d-475f-a802-83fdfa012d86
5a5ecce to
5525d5d
Compare
- use grep -qF for literal IP:port config checks (avoid regex-dot false match) - waitForVmRebootAndSshBack now fails if SSH never goes down, instead of silently passing when no reboot occurred - remove unimplemented assert-failure-reason scenario field - fix ExpectTimeout step message to not claim a timeout when a match actually occurred (the real failure case) - correct stale docs describing shim-based reboot interception; scenario does a real VM reboot + SSH reachability wait - correct trident-vm-testimage README: only the update image enables trident-acl-agent.service by default, base image leaves it disabled Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c06585d-a82d-475f-a802-83fdfa012d86
5525d5d to
efae982
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
tools/storm/aclagent/proxies/nebraska.go:154
- Before overwriting
NEBRASKA_DB_URL, capture any existing value so the shutdown hook can restore it (instead of always unsetting). This prevents tests/tools that already rely onNEBRASKA_DB_URLin the same process from breaking after this proxy runs.
if err := os.Setenv("NEBRASKA_DB_URL", dbURL); err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to set NEBRASKA_DB_URL: %w", err)
}
tools/storm/aclagent/proxies/nebraska.go:182
- The shutdown hook currently unsets
NEBRASKA_DB_URLunconditionally, which can delete a caller-provided value. Restore the previous value when one existed; otherwise unset.
// Unset so it doesn't leak into other tests/scenarios running in
// this same process after this proxy has shut down.
_ = os.Unsetenv("NEBRASKA_DB_URL")
}()
tools/storm/aclagent/proxies/nebraska.go:78
ListenAndServetemporarily overridesNEBRASKA_DB_URLfor the embedded Nebraska API, but the shutdown path currently unsets it unconditionally. To avoid clobbering a caller-providedNEBRASKA_DB_URL, store the prior value on the proxy so it can be restored on shutdown.
containerID string
dbURL string
api *api.API
handler *omaha.Handler
appID string
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
tools/storm/aclagent/proxies/nebraska.go:160
- In NebraskaProxy.ListenAndServe, NEBRASKA_DB_URL is set globally but not unset on the early-return error paths after Setenv succeeds (e.g., api.NewWithMigrations/seed/net.Listen failures). That environment leak can affect subsequent tests/scenarios running in the same process, because later Nebraska instances will read the stale DB URL.
Unset NEBRASKA_DB_URL alongside container teardown on those error paths (the ctx-cancel goroutine only runs on the success path).
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
tools/storm/aclagent/proxies/nebraska.go:160
NEBRASKA_DB_URLis set globally, but on error paths afteros.Setenv(e.g. DB init/seed/listen failures) it is never unset. That can leak into later tests/scenarios running in the same process and point them at a stale/removed container, making failures flaky and hard to diagnose.
Unset NEBRASKA_DB_URL before returning on these error paths (it is already unset on the happy-path shutdown goroutine).
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated no new comments.
Suppressed comments (4)
Previously missed (3) — in code that hasn't changed since the last review.
tools/storm/aclagent/proxies/nebraska.go:182
- The shutdown goroutine always unsets
NEBRASKA_DB_URL, which will wipe any pre-existing value a developer might have had in their environment (and it won’t match the restore logic needed for the failure paths). Prefer restoring the prior value captured beforeos.Setenv(or unsetting only if it wasn’t previously set).
go func() {
<-ctx.Done()
_ = server.Shutdown(context.Background())
stopEphemeralPostgres(containerID)
// Unset so it doesn't leak into other tests/scenarios running in
// this same process after this proxy has shut down.
_ = os.Unsetenv("NEBRASKA_DB_URL")
}()
tools/storm/aclagent/tests/update.go:510
waitForServiceActiveonly trackslastErr. Ifsystemctl is-activereturns a non-"active" state with exit code 0 (common),lastErrends up nil and the final error message becomes misleading. Track and report the last observedis-activeoutput as well (e.g. "activating", "failed"), so timeouts are diagnosable without reading the embedded journal.
func waitForServiceActive(cfg stormvmconfig.VMConfig, vmIP, service string, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
var lastErr error
for time.Now().Before(deadline) {
out, err := stormssh.SshCommandCombinedOutput(cfg, vmIP, fmt.Sprintf("sudo systemctl is-active %s", service))
if err == nil && strings.TrimSpace(out) == "active" {
return nil
}
lastErr = err
time.Sleep(2 * time.Second)
.pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml:135
- The template defines a
verboseLoggingparameter, but the bash step hard-codes--verboseunconditionally (FLAGS="-a --verbose"), so the parameter currently has no effect. Wire--verboseto the parameter (matching the pattern used by other storm pipeline stages) so callers can disable noisy logs when desired.
- bash: |
set -eux
FLAGS="-a --verbose"
sudo ./bin/storm-trident run aclagent $FLAGS \
tools/storm/aclagent/proxies/nebraska.go:154
ListenAndServesetsNEBRASKA_DB_URLbut several early-return error paths after that (api.NewWithMigrations,p.seed,net.Listen) don’t restore/unset it, so a failed proxy startup can leak a stale DB URL into later tests/scenarios running in the same process (and it can also clobber an existingNEBRASKA_DB_URL). Capture the prior value and restore it on every failure after the env var is modified.
// api.New only reads NEBRASKA_DB_URL from the environment - there is no
// functional option to set a custom DSN - so this is the only way to
// point it at our ephemeral container. Safe here because exactly one
// NebraskaProxy is ever instantiated per storm test process.
if err := os.Setenv("NEBRASKA_DB_URL", dbURL); err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to set NEBRASKA_DB_URL: %w", err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
tools/storm/aclagent/proxies/nebraska.go:160
ListenAndServesetsNEBRASKA_DB_URLbefore initializing/seeding Nebraska, but on the early error paths (api.NewWithMigrations,seed,net.Listen) it returns without unsetting the env var. That leaks process-global state into subsequent storm scenarios/tests running in the same process and can cause very confusing cross-test failures.
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tools/storm/aclagent/proxies/nebraska.go:160
ListenAndServesetsNEBRASKA_DB_URLglobally, but on early-return error paths after theSetenvsucceeds it never unsets it. That can leak a stale DSN into later storm scenarios/tests running in the same process (and make subsequent NebraskaProxy initializations behave unpredictably). Unset the env var before returning on these error paths (you can keep the existing unset in the ctx-cancel shutdown goroutine for the normal success path).
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
tools/storm/aclagent/proxies/nebraska.go:160
NEBRASKA_DB_URLis set to point Nebraska's API at the ephemeral Postgres container, but on later failure paths (api.NewWithMigrations,seed, ornet.Listen) the function returns without unsetting the env var. That can leak the ephemeral DSN into subsequent tests/scenarios running in the same process and cause confusing cross-test behavior.
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
docs/Development/Testing/TridentAclAgent-Tests.md:184
- The
--image-pathdefault description says the runner picks the first*.cosiunder--artifacts-dir, but the implementation usesstormfile.FindFile(...)which errors if there are multiple matches. Also,QemuConfignow supports--image-patternfor selecting the base qcow2 image, but this flag isn’t listed here, making it harder to resolve the common "multiple files found" error when an artifacts directory contains more than one*-testimage.qcow2.
| `--image-path` | Real `.cosi` update image to serve during staging | first `*.cosi` found under `--artifacts-dir` |
| `--verbose` | Enable verbose logging | `false` |
| `--test-case-to-run` | Run a specific test case only | `all` |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated no new comments.
Suppressed comments (4)
tools/storm/aclagent/proxies/nebraska.go:160
- In NebraskaProxy.ListenAndServe, if api.NewWithMigrations fails after setting NEBRASKA_DB_URL, the function returns without unsetting the env var. This can leak a stale DB URL into later tests/scenarios running in the same process and cause confusing follow-on failures.
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
tools/storm/aclagent/proxies/nebraska.go:167
- In NebraskaProxy.ListenAndServe, if seeding fails after NEBRASKA_DB_URL is set, the env var is left set even though the ephemeral Postgres container is torn down. Unset NEBRASKA_DB_URL on this error path to avoid leaking a dead DSN into later scenarios.
if err := p.seed(); err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to seed Nebraska scenario: %w", err)
}
.pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml:131
- The template defines a verboseLogging parameter, but the script always forces "--verbose" via FLAGS. This makes the parameter ineffective and prevents running the stage with non-verbose output.
- bash: |
set -eux
FLAGS="-a --verbose"
tools/storm/aclagent/proxies/nebraska.go:173
- In NebraskaProxy.ListenAndServe, if net.Listen fails after NEBRASKA_DB_URL is set, the env var remains set even though the proxy is aborting. Unset NEBRASKA_DB_URL on this error path to avoid cross-test contamination.
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to listen on %s: %w", listenAddr, err)
}
|
/azp run [GITHUB]-trident-pr-e2e |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml:131
- The
verboseLoggingtemplate parameter is declared but ignored: the bash step hard-codesFLAGS="-a --verbose", forcing verbose output even whenverboseLogging: falseand making the parameter dead. Wire the parameter into the flag construction (or remove the parameter entirely).
- bash: |
set -eux
FLAGS="-a --verbose"
|
/azp run [GITHUB]-trident-pr-e2e |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated no new comments.
Suppressed comments (2)
.pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml:131
- The bash step hard-codes
FLAGS="-a --verbose", so theverboseLoggingparameter declared at the top of the template is unused. This makes verbose logs mandatory even when callers setverboseLogging: falseand makes the parameter misleading/dead.
set -eux
FLAGS="-a --verbose"
tools/storm/aclagent/proxies/nebraska.go:173
ListenAndServesetsNEBRASKA_DB_URL, but on the error paths after that (failed migrations init, seed failure, or listen failure) it returns without unsetting the env var. If the storm process runs additional scenarios/tests afterwards, they may unexpectedly pick up a stale DB URL pointing at a stopped container.
a, err := api.NewWithMigrations(api.OptionInitDB)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to initialize Nebraska API against ephemeral Postgres: %w", err)
}
p.api = a
p.handler = omaha.NewHandler(a)
if err := p.seed(); err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to seed Nebraska scenario: %w", err)
}
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
stopEphemeralPostgres(containerID)
return nil, fmt.Errorf("failed to listen on %s: %w", listenAddr, err)
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated 4 comments.
Suppressed comments (2)
tools/storm/aclagent/tests/rollback.go:133
- Issue: This does not establish that the no-op rollback avoided a reboot. Evidence:
GetVmIPonly queries the VM provider for an address; QEMU uses the libvirt DHCP lease, which can remain present while the guest reboots, and its address helper retries until an IP exists rather than reporting temporary unreachability. A single check can also run before a delayed reboot starts. Suggestion: capture the guest boot ID before submitting the second rollback and verify it remains unchanged after a bounded no-reboot observation window (or continuously probe SSH and fail on any outage).
if _, err := stormvm.GetVmIP(vmConfig); err != nil {
tools/storm/aclagent/tests/rollback.go:119
- Issue: This regression expectation does not prove that
servicing_kindidentified an empty rollback chain. Evidence: it accepts anyOperationFailedresult, including unrelated tridentd, connection, or agent failures;RPClientcurrently discards the status message and only matches operation/id/code. Suggestion: expose and assert the specific no-active-servicing reason (or another dedicated discriminator) for this step.
{Expect: &stormproxies.ExpectStep{OperationID: "dddddddd-4444-4444-4444-444444444444", Operation: "rollback", Code: "OperationFailed", Timeout: 60 * time.Second}},
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated no new comments.
Suppressed comments (4)
tools/storm/aclagent/tests/rollback.go:87
- Issue: This post-reboot call can restart the agent while its automatically started instance is executing the pending commit. Evidence: the base root was enabled earlier in this same test, the fake API server remains the same across this reboot, and
prepareVmForAclAgentunconditionally runssystemctl restart trident-acl-agent.service; that function’s own comment notes that interrupting a commit can leave the retry failing with “Servicing is active.” Suggestion: do not re-run preparation here; let the boot-started agent reconnect and complete, then poll the commit annotation.
// Restart the agent so it reconnects to this test case's fresh fake
// apiserver instance (see prepareVmForAclAgent's doc comment in
// update.go). /var/lib/kubelet is its own dedicated ext4 partition, so
// the fake kubeconfig itself already persists across the rollback
// reboot's root swap and doesn't need to be re-delivered.
if err := prepareVmForAclAgent(vmConfig.VMConfig, vmIP, testConfig); err != nil {
return fmt.Errorf("failed to reconfigure ACL agent on post-rollback-reboot root: %w", err)
tools/storm/aclagent/tests/rollback.go:135
- Issue: This does not verify that the no-op rollback avoided a reboot. Evidence:
GetVmIPonly queries libvirt/Azure for the assigned address; a DHCP/public IP generally remains available while the guest is rebooting, and no SSH connection is attempted. Suggestion: poll SSH for a short grace period and fail if it becomes unreachable, so the regression test actually detects an unexpected reboot.
// A no-op rollback must not trigger another reboot: the VM should
// still be reachable immediately, with no reboot wait needed.
if _, err := stormvm.GetVmIP(vmConfig); err != nil {
collectAclArtifactsBestEffort(vmConfig.VMConfig, vmIP, testConfig.OutputPath)
return fmt.Errorf("VM appears to have rebooted (or become unreachable) after a no-op rollback, which should not trigger a reboot: %w", err)
.pipelines/templates/stages/testing_acl_agent/trident-acl-agent-test.yml:135
- Issue: Failed runs can leave the libvirt VM behind on the shared runner. Evidence: the scenario explicitly skips
cleanup-vmafter a dependency failure, whileCleanuponly destroys the VM whenForceCleanupis true; this pipeline does not pass that flag. Suggestion: enable forced cleanup as the existing servicing and rollback pipelines do.
sudo ./bin/storm-trident run aclagent $FLAGS \
--output-path $(ob_outputDirectory) \
--artifacts-dir $(Build.ArtifactStagingDirectory) \
--ssh-private-key-path ~/.ssh/id_rsa
tools/storm/aclagent/proxies/nebraska.go:151
- Issue:
NEBRASKA_DB_URLis process-global but is cleared asynchronously by an older proxy’s shutdown goroutine. Evidence: the newly added test starts several proxies in sequential subtests; after one cleanup signals cancellation, its goroutine can run after the next proxy sets its URL and unset the new value. Startup failures afterSetenvalso leave the dead URL installed. Suggestion: save the previous value, set the URL only aroundapi.NewWithMigrations, restore it immediately afterward on every path, and remove the delayed unconditionalUnsetenv.
// api.New only reads NEBRASKA_DB_URL from the environment - there is no
// functional option to set a custom DSN - so this is the only way to
// point it at our ephemeral container. Safe here because exactly one
// NebraskaProxy is ever instantiated per storm test process.
if err := os.Setenv("NEBRASKA_DB_URL", dbURL); err != nil {
|
/azp run [GITHUB]-trident-pr-e2e |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
…and pipeline
Adds the storm-trident E2E scenario ("aclagent") that validates
trident-acl-agent end-to-end against real tridentd (while mocking
kubelet/Nebraska) on a VM, testing stage/finalize/rollback/commit
through real reboots, plus the storm code, pipeline wiring, test images,
and development documentation to run these tests locally.
Storm code (tools/storm/aclagent/):
- proxies/: fake apiserver serving the Node annotation protocol
(including a real K8s-compatible watch stream), a fake Nebraska/Omaha
endpoint backed by the real github.com/flatcar/nebraska server package
(not a hand-rolled mock), a fake image server for serving update COSIs,
a minimal kubelet shim, and an RP client that drives scenarios and
polls status annotations.
- tests/: run-ab-update (stage/finalize/commit through a real reboot),
run-rollback (rollback stage/finalize/commit through a real reboot,
plus a regression test that a second rollback against an empty
rollback chain is a servicing_kind no-op rather than a false success),
plus VM lifecycle and log-collection helpers.
- README.md documents local usage.
Registered in tools/cmd/storm-trident/main.go alongside storm-trident's
other scenarios.
Test images (tests/images/): baseimg-acl-agent.yaml/
updateimg-acl-agent.yaml VM image configs for the base and post-update
ACL test images, wired into testimages.py.
Pipeline: trident-acl-agent-test.yml stage wired into e2e-template.yml.
Docs: docs/Development/Testing/TridentAclAgent-Tests.md documents the
suite; Testing.md links to it.
Harness evolution as trident-acl-agent's config/annotation surface
changed (all folded into this one commit, tree matches the final state
of PR #731's original commit history):
- goal_source renamed to "annotations"; --validate-connection checked
before/after config delivery.
- Nebraska mock rebuilt on the real flatcar/nebraska server package,
modeling event/in-progress instance state instead of approximating it.
- Config/kubelet persistence made durable across the A/B reboot; SSH
stability required before post-reboot reconfigure.
- Commit status read from its own annotation key rather than conflated
with the operation status.
- trident-acl-agent's static TOML config (then TRIDENT_ACL_AGENT_* env
vars) progressively emptied out as Nebraska endpoint/app_id/track
moved to per-request overrides on the update-request annotation
(server/appId/track fields) instead: prepareVmForAclAgent no longer
writes any config to the VM at all, and expectValidateConnection can
inject one-off TRIDENT_ACL_AGENT_NEBRASKA_* env vars to exercise both
the success and failure paths of that override mechanism directly.
- kubernetes.node_name derived from the VM image's own Image Customizer
hostname instead of a config override, matching trident-acl-agent's
hostname-based default.
- Assorted fixes: CI flag passthrough, Copilot-flagged review issues,
Makefile go-tools dependency restoration, logrus formatting, and
removal of a dead trident-acl-agent.conf collection step once the
config file stopped existing.
Verified: all 6 storm aclagent test cases (deploy-vm, check-deployment,
run-ab-update, run-rollback, collect-logs, cleanup-vm) pass end-to-end
against a real QEMU VM with real A/B update and rollback reboot cycles.
…p-in trident-acl-agent ' s compiled-in defaults changed to the generic acl.microsoft.com/VERSION_ID, so this scenario now explicitly overrides them to acl.azure.com/IMAGE_VERSION (the values it has always exercised) via a drop-in written before the service is enabled/restarted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…images A drop-in written under /etc/systemd/system at runtime lives only on the currently-active root and does not survive this usr-verity image ' s A/B swap - the previous commit ' s runtime SSH write only ever reached the base image ' s root, so the update image booted back to the agent ' s generic acl.microsoft.com/VERSION_ID defaults and never emitted the acl.azure.com/IMAGE_VERSION status this scenario expects, failing post-reboot commit. Bake the same override.conf into both baseimg-acl-agent.yaml and updateimg-acl-agent.yaml via additionalFiles instead, so each image ' s own root already has it. prepareVmForAclAgent no longer writes it at runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
trident-acl-agent now rejects non-UUID operation_id values in UpdateRequest per the formal schema (validate() added in PR 730). The storm aclagent test scenarios used human-readable placeholder operation IDs (stage-op, finalize-op, rollback-op, rollback-op-2), which are no longer accepted and caused run-ab-update/run-rollback to fail with InvalidRequest instead of Success. Replace them with fixed UUID-format placeholders, matching what real callers (AKS ACL Update Service) actually send. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Go fixes: - apiserver.go: decode handlePatch into typed struct so DisallowUnknownFields is effective; fix handleWatch double-header write after WriteHeader(200) has already been sent - rp.go: propagate expectStatus JSON decode errors instead of swallowing them - trident.go: return immediately after FailFromError so a failed test case is not also logged as passed - vm.go: DeployVM/CleanupVM now error on an unrecognized VM platform instead of silently returning nil - nebraska.go: unset NEBRASKA_DB_URL on shutdown so it does not leak into the next test case in the same process - config.go: correct ImagePath help text to match FindFile actual behavior (errors on multiple matches, not first-match) - update.go/rollback.go: guard MkdirAll(OutputPath) against an empty path; bind fake servers to HostEndpointIP instead of 0.0.0.0; fix shell != portability; correct misleading comment about why the agent needs reconnecting after reboot Docs: - Rewrite README.md, trident-vm-testimage/README.md, and TridentAclAgent-Tests.md to describe the current annotation-driven design (fake kubeconfig + per-request Nebraska fields) instead of the earlier label-driven/config-file/reverse-SSH design, and fix the --artifacts-dir default and missing run-rollback test case. Verified: full storm aclagent E2E suite (6/6 PASS) after these changes, including the HostEndpointIP bind change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 98412869-e5c8-4f3f-a97c-fe870db70701
- rollback.go: bind the fake apiserver to HostEndpointIP instead of 0.0.0.0, matching run-ab-update and avoiding unintended exposure - qemu/qemu.go: escape the literal dot in the default ImagePattern regex (.qcow2 -> \.qcow2) so it does not match arbitrary characters - docs/Development/Testing/Testing.md: fix stale "label-driven" wording for the ACL agent test link (protocol is annotation-driven) Verified: full storm aclagent E2E suite (6/6 PASS) after these changes, including the rollback HostEndpointIP bind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 98412869-e5c8-4f3f-a97c-fe870db70701
sha384File's doc comment ran directly into logScenarioTimeline's with no blank line between them, so godoc associated the whole merged block with logScenarioTimeline and left sha384File undocumented. Split them into separate comment blocks, each immediately above its own function. Verified: gofmt -l (clean), go vet ./storm/... (clean), storm-trident rebuilds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 98412869-e5c8-4f3f-a97c-fe870db70701
- tests/update.go: use \ systemctl start\ instead of \restart\ for tridentd.service in prepareVmForAclAgent. tridentd is socket-activated and holds no per-test-case state to refresh, so restarting it is unnecessary and, on the post-reboot path, risks killing an in-progress gRPC call (e.g. a commit already underway). - docs/.../TridentAclAgent-Tests.md: fix stale \Go 1.24+\ prerequisite to match tools/go.mod's actual \go 1.25.0\ requirement.
Docusaurus slugifies headings by dropping '/' rather than converting it to a hyphen, so "## Pre/post-reboot state and the watchdog" generates the anchor #prepost-reboot-state-and-the-watchdog, not #pre-post-reboot-state-and-the-watchdog. Update the manual link to match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
tools/storm/aclagent/tests/rollback.go:135
- Issue: This does not verify that the no-op rollback avoided a reboot. Evidence:
GetVmIPonly queries libvirt/Azure for an assigned address; for QEMU it reads the DHCP lease, which normally remains present while the guest is rebooting, so this check can succeed throughout an unintended reboot. Suggestion: observe SSH/boot identity for a bounded grace period (for example, read/proc/sys/kernel/random/boot_idbefore the request and assert the same ID after continuous SSH availability) instead of querying the lease.
if _, err := stormvm.GetVmIP(vmConfig); err != nil {
collectAclArtifactsBestEffort(vmConfig.VMConfig, vmIP, testConfig.OutputPath)
return fmt.Errorf("VM appears to have rebooted (or become unreachable) after a no-op rollback, which should not trigger a reboot: %w", err)
| $(VM_IMAGE_PATH_PREFIX)/baseimg-acl-agent.yaml \ | ||
| $(VM_IMAGE_PATH_PREFIX)/files/id_rsa.pub \ | ||
| artifacts/rpm-overrides |
| go func() { | ||
| <-ctx.Done() | ||
| _ = server.Shutdown(context.Background()) | ||
| stopEphemeralPostgres(containerID) | ||
| // Unset so it doesn't leak into other tests/scenarios running in | ||
| // this same process after this proxy has shut down. | ||
| _ = os.Unsetenv("NEBRASKA_DB_URL") |
Summary
Adds the storm-trident E2E scenario that validates
trident-acl-agentend-to-end against real tridentd (while mocking kubelet/Nebraska) on a VM, testing update, rollback, reboot, and commit.Includes storm code, pipeline, test images, and development documentation on how to run these tests locally.
Context
This is the last step in enabling trident-acl-agent to run updates and rollbacks. Related PRs:
Validation
PR details
Storm code
proxies/— fake apiserver (serves the Node annotation protocol the agent watches/patches, including a real K8s-compatible watch stream), fake Nebraska/image servers, a minimal kubelet API shim, and an RP client used to drive scenarios and poll status annotations.tests/—update.go(stage/finalize/commit against a real reboot),rollback.go(rollback stage/finalize/commit against a real reboot, plus a regression test that a second rollback against an empty rollback chain is detected as a no-op viaservicing_kindrather than reporting a false Success and rebooting again),vm.go/logs.go(VM lifecycle and log collection helpers).trident.go,utils/config— scenario wiring and config plumbing specific to the aclagent suite.README.md— usage instructions for running the suite locally.Registered in
tools/cmd/storm-trident/main.goalongside storm-trident's other scenarios.Test images (
tests/images)baseimg-acl-agent.yaml/updateimg-acl-agent.yaml— VM image configurations for the base and post-update ACL test images.testimages.py— wires the update image into the existing COSI-based image build path (the base qcow2 is built via the Makefile target below, nottestimages.py, since the qcow2 tooling differs from COSI).Build tooling
New
artifacts/trident-vm-acl-agent-testimage.qcow2Makefile target for the base image.Pipeline
New
trident-acl-agent-test.ymlstage wired intoe2e-template.yml, running the aclagent storm scenario in CI.Docs
docs/Development/Testing/TridentAclAgent-Tests.mddocuments the suite;Testing.mdlinks to it.