Repository navigation
Keep the last-good service assets until an upgrade is judged - #387
Merged
Merged
Conversation
Philip Lombardi (plombardi89)
deployed
to
e2e-testing
October 20, 2026 20:17 — with
GitHub Actions
Active
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 20, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused changes preserve rollback assets and startup ordering, with regression coverage and no unresolved findings.
No open issues 🎉
What changed in this PR
Protects AgentUpgrade rollback by preserving last-good service assets until the upgrade is judged.
Changes:
- Keeps existing units and recovery scripts while an upgrade signal exists, writing only missing assets.
- Converges held assets after judgment, before opening the startup gate.
- Adds regression tests and documents the behavior.
| File | Description |
|---|---|
| pkg/daemon/lifecycle.go | Preserves existing assets during pending upgrades. |
| pkg/daemon/lifecycle_test.go | Tests preservation, missing assets, and normal convergence. |
| pkg/daemon/host_agent_activation.go | Retains unconditional writes for direct activation. |
| pkg/daemon/daemon.go | Converges held assets before opening the startup gate. |
| pkg/daemon/daemon_test.go | Tests gate ordering, recovery, retries, and convergence failures. |
| docs/usage/operations.md | Documents last-good asset preservation. |
Review effort: Balanced
hbc (bcho)
approved these changes
Oct 20, 2026
At every start, the daemon rewrote the agent's systemd units and its recovery script from its own binary, an upgraded candidate included, before its upgrade was judged. Recovery runs the last-good binary's recover-agent-upgrade, but through that recovery unit and script, and systemd restarts the last-good binary through the agent's unit. So a candidate whose assets were broken broke its own rollback, and could leave the node on the failed version. While an AgentUpgrade's signal exists, the daemon may be the upgrade's candidate, not yet judged, or recovery is under way, and the assets on disk are the last-good version's. At startup, the daemon now keeps them then, writing only missing ones. Once it has published the upgrade's result and the signal is cleared, it writes its own, before it opens the startup gate. A failed upgrade keeps its signal, so the last-good version's assets stay. Failing to write them after a successful upgrade is only logged: the last-good version's still work, and the next start writes them again. Without a signal, the daemon converges them at startup as before, and install and direct activation still write them all. The startup gate's loop moved into openGateAfterAgentUpgradeJudgment, unchanged but for that step, so it can be tested.
Philip Lombardi (plombardi89)
force-pushed
the
phlombar/upgrade-keep-last-good-assets
branch
from
October 20, 2026 20:32
9b4698c to
f4c6ad3
Compare
Philip Lombardi (plombardi89)
deployed
to
e2e-testing
October 20, 2026 20:32 — with
GitHub Actions
Active
hbc (bcho)
approved these changes
Oct 20, 2026
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 20, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 21, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 21, 2026
#387 has a host's daemon keep the last-good version's service assets while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 21, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 21, 2026
Brings in #386, which resets the agent unit's start limit before an upgrade's restart, and #387, which keeps the last-good service assets while an AgentUpgrade is pending and writes them once it's judged. - ensureDaemonServiceAssets, #387's, and ensureAgentUpgradeServiceAssets both go through ensureServiceAssetsUnderHostRoot, which resolves the host root once, as this branch does, rather than main's fixed /usr/local paths and recoveryScriptPath, which this branch removed. - The daemon installs its host binary, then converges the assets, holding them back while an upgrade is pending, as before removing the legacy seed. That removal is safe while they're held: units that name the seed exist only on a host not yet moved off /usr/local, where hostroot.LegacyReleased has it remove nothing. - #387's openGateAfterAgentUpgradeJudgment keeps the startup gate's loop, and its last step, now proceed, is this branch's: once the upgrade is judged and the held assets are written, reconcile the host root, then open the gate, or hold it until the daemon is replaced.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 22, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 22, 2026
while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 22, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
Philip Lombardi (plombardi89)
deleted the
phlombar/upgrade-keep-last-good-assets
branch
October 23, 2026 16:52
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 23, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 23, 2026
while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 23, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 24, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 24, 2026
while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 24, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
Each says what follows once it merges: a rebase, and the same change in the container's executor or convergence.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
while an AgentUpgrade's signal exists, writing its own once the upgrade is judged, as recovery, and the restart into the last-good version, run through them. The container's convergence now does the same with its units: while the signal exists, it writes only missing ones, and reports that it held them back. It still copies the agent's binary into the machine, as judging the upgrade needs it there, and still removes old images, which keeps the selected and last-good ones. Run chooses, once for its mode, what the startup gate runs once the upgrade is judged: on a host, #387's rewrite of the assets ensureDaemonServiceAssets held back, which ensureHostInstall now returns; in the container, the convergence's writeUnits, if it held the units back. openGateAfterAgentUpgradeJudgment is #387's, unchanged. The signal check #387 inlined is agentUpgradePending, which both use.
Philip Lombardi (plombardi89)
added a commit
that referenced
this pull request
Oct 27, 2026
DESIGN.md's upgrade steps now say the agent resets its unit's start-limit count before an upgrade's restart, and that the candidate keeps the last-good version's units until the upgrade is judged, as #386 and #387 have a host's daemon do. Where the container runs as kernel_t and may not reset the count, a sixth start in five minutes still fails. TODO.md: both items are done, with #386 and #387 merged into main and ported to the container.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
At every start, the daemon rewrites the agent's service assets from its own binary, and reloads systemd:
/usr/local/lib/aks-flex-node/aks-flex-node-recovery.sh;An upgraded candidate does this too, before its upgrade is judged.
Recovery runs the last-good binary's
recover-agent-upgrade, but through the recovery unit and script the candidate just wrote. And after a failed upgrade, systemd restarts the last-good binary through the agent's unit, which the candidate also wrote. So a candidate whose assets are broken breaks its own rollback, and can leave the node on the failed version.This came up in review of the ACL branch (#370), which mirrors this flow for its container units.
Fix
The daemon now keeps the last-good version's assets until the upgrade is judged:
ensureDaemonServiceAssets), the daemon keeps the assets on disk and writes only missing ones. In that state the daemon may be the upgrade's candidate, not yet judged, or recovery is under way. Either way, the assets on disk are the last-good version's.InstallServicestill writes them all;agent-upgrade), which creates no signal, still writes them all.For testing, the startup gate's loop in
daemon.Runmoved intoopenGateAfterAgentUpgradeJudgment. It's unchanged apart from the new step.Testing
golangci-lint, and the docs checks pass.main's e2eagent-upgrade, including the broken candidate's rollback, runs on this PR.