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>
133 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|