d698ce343b
gates / gates (push) Successful in 14s
R-487: the local backup lists are keyed on the drives, not on what is deployed — a removed app whose unit was kept is listed with the restore that reinstalls it, the picker answers for it, and the restore opens the unit where it sits. R-491: a removal clears the app's update hold. R-490: /api/system/info reaches the API router and reads the default storage path. R-489: volumes_removed is the real before/after difference, [] when none. R-476: a Tier-2 copy is dated by its data, not its manifest. R-456: the boot-orphan rule is pinned. Every fix red-proofed.
34 lines
1.6 KiB
Go
34 lines
1.6 KiB
Go
package bootrecon
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// R-456 — written down in 02-controller-module-map.md and pinned here: a stack with one member
|
|
// REMOVED (absent, not dead) and the rest running reads as running, so the boot sweep does not repair
|
|
// it; only a present-but-dead supervised member makes it degraded. Measured 2026-09-02 on demo-hp
|
|
// (`docker rm -f bookstack` with bookstack-db running: not selected; both gone: repaired in 6.3 s).
|
|
func TestR456_AbsentMemberIsNotABootOrphan(t *testing.T) {
|
|
present := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}}
|
|
state := stacks.AggregateStateForTest(present)
|
|
if state != stacks.StateRunning {
|
|
t.Fatalf("one running member, one absent: state %q, want running (an absent member is not a dead one)", state)
|
|
}
|
|
s := stacks.Stack{Name: "bookstack", Deployed: true, State: state, Containers: present,
|
|
AppConfig: &stacks.AppConfig{Deployed: true, DesiredState: stacks.DesiredStateRunning}}
|
|
if isBootOrphan(s) {
|
|
t.Error("a partly-dead stack must NOT be a boot orphan — repairing half a stack beside its live database is not obviously safe (R-456)")
|
|
}
|
|
// Control: the same member present but DEAD is degraded, and degraded IS an orphan.
|
|
dead := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}, {Name: "bookstack", State: "exited"}}
|
|
if st := stacks.AggregateStateForTest(dead); st != stacks.StateDegraded {
|
|
t.Fatalf("control: a present dead member must read degraded, got %q", st)
|
|
}
|
|
s.State, s.Containers = stacks.StateDegraded, dead
|
|
if !isBootOrphan(s) {
|
|
t.Error("control: a degraded stack IS a boot orphan")
|
|
}
|
|
}
|