Files
VeillaxandClaude Sonnet 5.5 2b7cb5dd35
CI / test (push) Successful in 5m21s
Release / release (push) Successful in 6m9s
Show a progress bar and live log while Hammerhead, edges and sites update
The updater reports each step (checking, downloading with percent, verifying, installing, restarting).
Edges and sites send that progress and their log with their reports; Hammerhead serves it to a dialog
that opens when an update starts, and keeps its own last update log on disk across the restart.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 18:05:42 -06:00

133 lines
4.4 KiB
Go

package e2e
import (
"context"
"strings"
"sync/atomic"
"testing"
"time"
"git.veillax.com/Veillax/bamboo"
"git.veillax.com/Veillax/blacktip"
"git.veillax.com/Veillax/reef/model"
)
type edgeRow struct {
ID, Version, UpdateMsg string
AutoUpdate bool
UpdatePending bool
Online bool
}
func (w *world) edgeRow() edgeRow {
var edges []edgeRow
w.admin.must("GET", "/api/edges", nil, &edges)
for _, e := range edges {
if e.ID == w.edge {
return e
}
}
w.t.Fatal("edge not listed")
return edgeRow{}
}
func (w *world) siteRow() model.Site {
var st struct{ Sites []model.Site }
w.admin.must("GET", "/api/orgs/"+w.org+"/state", nil, &st)
for _, s := range st.Sites {
if s.ID == w.site {
return s
}
}
w.t.Fatal("site not listed")
return model.Site{}
}
func waitFor(t *testing.T, what string, ok func() bool) {
t.Helper()
for i := 0; i < 150; i++ {
if ok() {
return
}
time.Sleep(100 * time.Millisecond)
}
t.Fatalf("timed out waiting for %s", what)
}
func TestEdgesAndSitesReportVersionsAndUpdateOnRequest(t *testing.T) {
var edgeCalls, siteCalls atomic.Int32
siteCfgHook = func(c *bamboo.Config) {
c.Version, c.AutoUpdate = "v0.1.0", true
c.UpdateNow = func(ctx context.Context, progress func(string, int, string)) (string, error) {
siteCalls.Add(1)
progress("checking", 10, "Checking for the newest release")
progress("done", 100, "Already on the newest release")
return "", nil
}
}
defer func() { siteCfgHook = nil }()
w := setup(t, blacktip.TraefikConfig{}, func(c *blacktip.Config) {
c.Version, c.AutoUpdate = "v0.1.0", true
c.UpdateNow = func(ctx context.Context, progress func(string, int, string)) (string, error) {
edgeCalls.Add(1)
progress("downloading", 40, "Downloading blacktip_v0.2.0_linux_amd64.tar.gz")
progress("downloading", 70, "Downloaded 28.0 of 40.0 MB (70%)")
progress("restarting", 100, "Installed v0.2.0; restarting into it")
return "v0.2.0", nil
}
})
waitFor(t, "the edge to report its version", func() bool { e := w.edgeRow(); return e.Version == "v0.1.0" && e.AutoUpdate })
waitFor(t, "the site to report its version", func() bool { s := w.siteRow(); return s.Version == "v0.1.0" && s.AutoUpdate })
w.admin.must("POST", "/api/edges/"+w.edge+"/update", nil, nil)
waitFor(t, "the edge to answer the update request", func() bool {
e := w.edgeRow()
return !e.UpdatePending && e.UpdateMsg == "updated to v0.2.0, restarting"
})
if edgeCalls.Load() != 1 {
t.Fatalf("the edge ran its update %d times, want 1", edgeCalls.Load())
}
var log struct {
Running bool
Stage string
Pct int
Lines []string
}
w.admin.must("GET", "/api/edges/"+w.edge+"/update/log", nil, &log)
text := strings.Join(log.Lines, "; ")
if log.Running || log.Stage != "restarting" || log.Pct != 100 || !strings.Contains(text, "Update requested") || !strings.Contains(text, "Downloaded 28.0 of 40.0 MB") || !strings.Contains(text, "restarting into it") {
t.Fatalf("edge update log: %+v (%s)", log, text)
}
w.admin.must("POST", "/api/orgs/"+w.org+"/sites/"+w.site+"/update", nil, nil)
waitFor(t, "the site to answer the update request", func() bool {
s := w.siteRow()
return s.UpdateID == "" && s.UpdateMsg == "already on the newest release"
})
if siteCalls.Load() != 1 {
t.Fatalf("the site ran its update %d times, want 1", siteCalls.Load())
}
w.admin.must("GET", "/api/orgs/"+w.org+"/sites/"+w.site+"/update/log", nil, &log)
if log.Running || log.Stage != "done" || !strings.Contains(strings.Join(log.Lines, "; "), "Already on the newest release") {
t.Fatalf("site update log: %+v", log)
}
// asking again works and is a fresh request, not a leftover answer
w.admin.must("POST", "/api/edges/"+w.edge+"/update", nil, nil)
waitFor(t, "a second update request to be answered", func() bool { return edgeCalls.Load() == 2 && !w.edgeRow().UpdatePending })
}
func TestUpdatesNeedAutoUpdateOnTheMachine(t *testing.T) {
w := setup(t, blacktip.TraefikConfig{}) // no version, auto-update off
waitFor(t, "the edge to be online", func() bool { return w.edgeRow().Online })
code, body := w.admin.do("POST", "/api/edges/"+w.edge+"/update", nil, nil)
if code != 400 || !strings.Contains(body, "BLACKTIP_AUTO_UPDATE") {
t.Fatalf("update on an edge without auto-update: %d %s", code, body)
}
code, body = w.admin.do("POST", "/api/orgs/"+w.org+"/sites/"+w.local+"/update", nil, nil)
if code != 400 {
t.Fatalf("update on a local site: %d %s", code, body)
}
}