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 13 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
|
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). |
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") |
|
/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>
|
/azp run [GITHUB]-trident-pr-e2e |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
🟡 Changes recommended
The Nebraska preflight consumes the pending update, and the no-reboot rollback assertion cannot detect a reboot.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
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 reads the libvirt/Azure-assigned address; that address remains available while the guest reboots, and the QEMU implementation retries indefinitely until an address exists. Consequently, an erroneous reboot still passes this regression assertion. Suggestion: monitorSshCommandCombinedOutput(..., "true")for a bounded interval after the status and fail if SSH ever becomes unreachable.
// 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)
- Files reviewed: 30/31 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
The no-reboot assertion is ineffective, and Nebraska cleanup can leak resources or race subsequent proxies.
Review details
Suppressed comments (2)
tools/storm/aclagent/tests/rollback.go:135
- Issue: This does not verify that the VM stayed up after the no-op rollback. Evidence:
GetVmIPonly queries the assigned address; on QEMU it reads libvirt's DHCP lease (tools/storm/utils/vm/qemu/qemu.go:130-209), which normally remains present during a reboot and can therefore return the same IP after an erroneous reboot. Suggestion: capture the guest boot ID before the second request, then use bounded SSH polling to assert the VM remains reachable and the boot ID does not change throughout the no-reboot observation window.
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)
tools/storm/aclagent/proxies/nebraska.go:181
- Issue: Cancellation does not synchronously clean up the external Postgres resource. Evidence: the context only wakes this background goroutine; callers have no way to wait for
docker rmor environment restoration before the next scenario or process exit. A standalonerun-ab-updatecan therefore leave the container running, and sequential proxy tests can race when an older goroutine unsetsNEBRASKA_DB_URLafter a newer proxy sets it. Suggestion: expose an awaitedClose/Waitlifecycle forNebraskaProxyand have callers defer it; restore the environment only if it still contains this proxy's DB URL.
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")
- Files reviewed: 30/31 changed files
- Comments generated: 0 new
- Review effort level: Balanced
NebraskaProxy's ephemeral Postgres container was pulled unqualified (docker.io/library/postgres:16-alpine), which OneBranch-governed pipeline pools cannot reach. Cache container in maritimusdev.azurecr.io
There was a problem hiding this comment.
🔵 Needs a closer look
Five unresolved moderate issues affect pipeline configuration, image freshness, scenario sequencing, server teardown, and reboot validation.
Review details
Suppressed comments (5)
Previously missed (3) — in code that hasn't changed since the last review.
.pipelines/templates/e2e-template.yml:277
- Issue: The ACL image build ignores the parent pipeline's selected Image Customizer build. Evidence: this template exposes
micBuildType/micVersion, but the inclusion passes neither, unlike the servicing stage immediately above;dev/previewand pinned-version runs therefore silently use the ACL template'srelease/*.*.*defaults. Suggestion: forward both parent parameters.
tools/storm/aclagent/proxies/apiserver.go:238 - Issue: API-server shutdown races the next test case. Evidence: cancelling
RunABUpdateonly wakes this goroutine; it does not wait forShutdownto close the listener, whileRunRollbackimmediately binds the sameAPIServerPort. Scheduling can therefore make rollback fail intermittently withaddress already in use. Suggestion: expose/await synchronous server teardown (or explicitly close the returned listener) before the scenario returns and the next test starts.
tools/storm/aclagent/proxies/rp.go:50 - Issue: Failed expectations do not stop this stateful scenario. Evidence:
RunScenariorecordsPassed=falsebut continues to later steps, so the update sequence still patchesfinalizeafter its preceding stage-success expectation times out or reports failure. Suggestion: stop processing steps after the first failed report so dependent operations are not executed with failed preconditions.
Makefile:1228
- Issue: The base-image target does not depend on the new systemd override that the YAML embeds. Evidence: changing
files/trident-acl-agent-override.confleaves an existing qcow2 newer than every listed prerequisite, somake artifacts/trident-vm-acl-agent-testimage.qcow2reuses an image with stale agent settings. Suggestion: add the override file as an explicit prerequisite, as is done for other image-specific files in nearby targets.
$(VM_IMAGE_PATH_PREFIX)/baseimg-acl-agent.yaml \
$(VM_IMAGE_PATH_PREFIX)/files/id_rsa.pub \
artifacts/rpm-overrides
tools/storm/aclagent/tests/rollback.go:133
- Issue: This does not verify that the VM stayed reachable. Evidence:
GetVmIPonly queries the assigned address; Azure retains it during reboot, and QEMU reads the persistent DHCP lease (and retries until an address appears), so an unintended second reboot can still pass this assertion. Suggestion: observe direct SSH connectivity for a bounded no-reboot window and fail if any outage occurs.
if _, err := stormvm.GetVmIP(vmConfig); err != nil {
- Files reviewed: 30/31 changed files
- Comments generated: 0 new
- Review effort level: Balanced
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.