Repository navigation
System updates drain borrowable roles; make reboot_strategy live declaration #16036
Description
Parent: SLM method-adoption umbrella.
Method
Apply the update without dropping the workload. The reference stack achieves this in the kernel;
the portable half is the policy — a node being updated hands off its work first, so an update
never costs an in-flight run. In our field that matters more than it does for a web host: an
interrupted node is an interrupted agent workflow.
Already-exists audit
Both halves exist and are not connected:
models/manifest.py:26declaresRebootStrategyand:138-142ManifestSystemUpdates
(policy,reboot_strategy) — so every role declares a reboot strategy.api/updates.pypasses"auto_reboot": "false"(:163and:949) to the update playbook. The
declaredreboot_strategyis therefore never read by the update path — it is dead
declaration.services/blue_green.pyalready implements role borrowing,post_deploy_monitor_durationand
health-gatedauto_rollback— the drain-and-hand-off machinery exists, built for deployments.- Nothing in
api/updates.pyreferencesblue_green(grep: no hits).
Change
- Have the system-update path consume the manifest's
reboot_strategyinstead of a hardcoded
auto_reboot: "false". - For a node whose roles are borrowable, drain via
blue_greenrole borrowing before applying
updates that require a restart, and return the roles afterwards — the same path deployments
already use. - A role that cannot be borrowed says so, and its node's update is scheduled rather than silently
applied under load.
Acceptance criteria
-
api/updates.pyreadsManifestSystemUpdates.reboot_strategyrather than hardcodingauto_reboot - An update requiring a restart drains the node's borrowable roles first and restores them after
- A non-borrowable role produces an explicit scheduled-window outcome, not a silent in-place restart
- In-flight work is not dropped by a system update on a borrowable node — demonstrated by a test
-
RebootStrategyvalues are all reachable; none remains dead declaration
Implementation Order
Wave: 2
Depends on: none
Concrete shape from oVirt (added after further research)
This is our own lineage — the SLM's Ansible deployment approach came from oVirt's downstream — so
the reference shape is worth following closely rather than reinventing.
Per-host cycle. Upgrading a host in Up status sets it to maintenance mode, which triggers
migration of its workloads to other hosts in the cluster; the packages are then installed through
the Ansible-driven host deploy; the host is brought back up. If workloads are still running when
the operator triggers it, they are warned before it proceeds.
Update availability is host state. GET /hosts/{id} returns
<updates_available>true</updates_available>, and the upgrade is an action on the same resource
(POST /hosts/{id}/upgrade). Our equivalent lives in side tables — models/database.py:142 Node
carries no updates-available field; UpdateInfo (:383) and UpdateJob (:411) hold it instead.
Worth aligning while this path is being touched, so "which nodes need attention" is a node-state
read.
Rolling fleet upgrade composes from the per-host cycle. Host by host — maintenance → upgrade →
activate — with per-host status visible so the operator can see how far the fleet has got.
grep -niE 'rolling|serial' autobot-slm-backend/api/updates.py autobot-slm-backend/services/deployment.py
→ no hits. We have no rolling fleet update; services/blue_green.py has the hand-off machinery
but it is wired only to deployments.
The control plane's own update is a separate mode. oVirt has a distinct global maintenance mode
for operations requiring the engine itself to stop — it does not pretend the control plane's update
is the same problem as a managed host's. Our equivalent is the self-update path
(services/local_admin_socket.py, api/code_sync.py), which should stay distinct from this work.
What does not transfer: oVirt's drain step is VM live migration. Handing off a role is not the
same problem — adopting the method means solving the hand-off for our workloads, not copying theirs.
Additional acceptance criteria
- The drain → update → return cycle is one operation on the node resource, not a sequence the caller assembles
- A rolling fleet update applies that cycle node by node with per-node status visible throughout
- Triggering an update on a node with in-flight work warns before proceeding
- Update availability is readable from node state, not only from the update side-tables
- The SLM's own self-update path remains distinct from managed-node updates
Activity
mrveiss commentedon Sep 27, 2026
Call-site evidence for the sibling gap, and why it is a sibling rather than a duplicate
Filed #17661 for
MaintenanceWindow/NodeStatus.MAINTENANCE/ drain. It is the same class asthis issue from a different end, so recording the boundary here rather than re-opening the question
later.
This issue's gap:
ManifestSystemUpdates.reboot_strategydeclared per role and never read,because
api/updates.py:163,949hardcodesauto_reboot: "false";services/blue_green.py's roleborrowing never called by the update path.
#17661's gap, verified by following the chain rather than grepping the routes:
MaintenanceWindowis referenced only by its own model definition (models/database.py:507-510)and its own CRUD router (
api/maintenance.py), acrossautobot-slm-backend,autobot-backendand
autobot_shared, excluding tests and migrations — so no shared helper can be enforcing it.NodeStatus.MAINTENANCE(status_enums.py:45) is read at exactly three sites, all of them thedrain/undrain/window-activation code itself:
api/maintenance.py:398,api/nodes.py:2113,2119,api/nodes.py:2147.api/nodes.py:2211-2239->_execute_reboot_playbook(:2182-2194) ->executor.execute_playbook("reboot-node.yml", limit=[node.node_id]). Its only gate isNodeStatus.OFFLINEat:2232.Practical consequence for whoever takes either: both land in
api/updates.py, so under thesame-file rule they are one PR and one owner. If you take this issue, take #17661 with it, and read
reboot_strategyand the window/drain check from one place rather than adding a second competinggate.
Both are cross-linked from the umbrella #17656, whose analysis is in
docs/research/self-hosted-deployment-control-plane.md.docs(research): node-plane comparison findings (#17656)