gates / gates (push) Successful in 31s
- clear_hold: a fifth verb in the closed operator-action list; lifts a hold of any reason in the RUNNING controller (memory + settings.json together), forgets the update's held flag, drops a waiting put-back from the app-stop marker (copy kept). Refused on a bad name or no hold. No new event type. - --clear-restore-hold asks the running controller over <data dir>/ctl/hold.sock (unix, 0600, peer uid checked, no TCP); edits settings.json only when no controller answers, and says so (in-guest restart). - R-1340: the operator's HELD STOPPED mail is built from the hold record (restore kind, mixed cause, failed put-back step, database replay or not, kept copy) — never the option-C database story by default. - R-1341: the OOM WARN line once per new kill (container, start time, kill count); event unchanged.
47 lines
1.9 KiB
Go
47 lines
1.9 KiB
Go
package stacks
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
)
|
|
|
|
// D64 = B / R-1343: when the operator clears a hold, the ONE held flag kept outside the hold store — „the last update
|
|
// ended held" — goes too. Left set, the crash-loop observer and the nightly update leg kept skipping an app whose hold
|
|
// was gone, until the controller restarted.
|
|
// RED-PROOF (2026-10-29): with ForgetUpdateHeld's body reduced to `return false`, this fails on „a held update must be forgotten".
|
|
func TestD64_ForgetUpdateHeld(t *testing.T) {
|
|
m := &Manager{cfg: &config.Config{}, logger: log.New(io.Discard, "", 0), stacks: map[string]*Stack{
|
|
"kimai": {Name: "kimai", Deployed: true},
|
|
"running": {Name: "running", Deployed: true, Updating: true},
|
|
}}
|
|
m.finishUpdateKey("kimai", UpdatePhaseFailed, UpdateErrorKeyHeld, "a kimai leállítva marad")
|
|
m.markUpdateHeld("kimai")
|
|
m.markUpdateHeld("running")
|
|
|
|
if !m.ForgetUpdateHeld("kimai") {
|
|
t.Fatal("a held update must be forgotten")
|
|
}
|
|
st, _ := m.GetStack("kimai")
|
|
if st.updateHeld || st.UpdateErrorKey != "" || st.UpdateError != "" || st.UpdatePhase != "" {
|
|
t.Fatalf("held state left: held=%v key=%q err=%q phase=%q", st.updateHeld, st.UpdateErrorKey, st.UpdateError, st.UpdatePhase)
|
|
}
|
|
// The two readers of the flag (ObserveUnhealthy's skip, unhealthy.go; legCandidate's LegSkipHeld, unattended.go)
|
|
// read the stored stack — not called here, because ObserveUnhealthy reaches docker (R-650).
|
|
m.mu.RLock()
|
|
skipped := m.stacks["kimai"].updateHeld || m.stacks["kimai"].UpdateErrorKey == UpdateErrorKeyHeld
|
|
m.mu.RUnlock()
|
|
if skipped {
|
|
t.Fatal("the observer and the update leg still skip kimai")
|
|
}
|
|
// A running update is never touched; an app with nothing held reports false.
|
|
if m.ForgetUpdateHeld("running") {
|
|
t.Fatal("a running update must not be touched")
|
|
}
|
|
if m.ForgetUpdateHeld("kimai") || m.ForgetUpdateHeld("nosuch") {
|
|
t.Fatal("nothing held → false")
|
|
}
|
|
}
|