carlos / platform Public

offline-move: neutral example hosts in place of tenant names

The branch's tests and plan named a tenant (its hosts, app and manifest).
They now use the app shop under shop.example.com: acme.shop.example.com
and its siblings, with the offline tests' own manifest, claim helper and
move door so the pre-existing fixtures on main stay as they were. Test
semantics are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 75ebf4ff25cee73f8b1a4ff8a8b077065647cca8 parent d739773
5 files changed, +160 −121
  • docs/superpowers/plans/2026-10-20-offline-operator-move.md +1 −1
  • internal/console/apiinstancemove_offline_test.go +75 −40
  • internal/moves/offline_test.go +2 −2
  • move_test.go +75 −71
  • offline_move_e2e_test.go +7 −7
diff --git a/docs/superpowers/plans/2026-10-20-offline-operator-move.md b/docs/superpowers/plans/2026-10-20-offline-operator-move.md
index ce49ded67..a8272b073 100644
--- a/docs/superpowers/plans/2026-10-20-offline-operator-move.md
+++ b/docs/superpowers/plans/2026-10-20-offline-operator-move.md
@@ -597,7 +597,7 @@ What it leaves behind that activation will meet: `last_move` with no `move` (fin
## Non-goals
-Self-serve and member access; automatic recovery or retries beyond a step's own ensures; a no-traffic boot proof on the target before `open` (the dev rehearsal is the boot proof; after `open` a boot failure is fixed forward); moving object store data (the bucket stays in its region; residency follows the instance's database only, F4); multi-box target regions; sidecars, seeds, runs, replicas, pools, fleets, steered, unit-backed and non-hibernating instances; rollback after `open` (a new move); timer run history; the restore-verify verdict; DNS (the flagship's instance hosts resolve through the wildcard and backhaul); live migration; Tito.
+Self-serve and member access; automatic recovery or retries beyond a step's own ensures; a no-traffic boot proof on the target before `open` (the dev rehearsal is the boot proof; after `open` a boot failure is fixed forward); moving object store data (the bucket stays in its region; residency follows the instance's database only, F4); multi-box target regions; sidecars, seeds, runs, replicas, pools, fleets, steered, unit-backed and non-hibernating instances; rollback after `open` (a new move); timer run history; the restore-verify verdict; DNS (the flagship's instance hosts resolve through the wildcard and backhaul); live migration; deployments other than the flagship.
## Risks
diff --git a/internal/console/apiinstancemove_offline_test.go b/internal/console/apiinstancemove_offline_test.go
index 6a991ff82..acf194a07 100644
--- a/internal/console/apiinstancemove_offline_test.go
+++ b/internal/console/apiinstancemove_offline_test.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/carlosframework/platform/internal/domains"
+ "github.com/carlosframework/platform/internal/hostclaim"
"github.com/carlosframework/platform/internal/hostops"
"github.com/carlosframework/platform/internal/moves"
"github.com/carlosframework/platform/internal/provision"
@@ -27,7 +28,38 @@ import (
// write them (moves.OfflineReportKey); the record moves only through
// provision's offline transitions.
-const offlineHost = "acme.go.tito.io"
+const offlineHost = "acme.shop.example.com"
+
+// offlineManifest is the enabled manifest of the app shop, whose instances
+// live under shop.example.com.
+func offlineManifest() provision.Manifest {
+ return provision.Manifest{Schema: 1, App: "shop", Domain: "shop.example.com",
+ Pool: "shop", Channel: "stable", Regions: []string{"ie"}, Seed: []string{"seed"}}
+}
+
+// offlineClaimFor is claimFor for an instance of the app shop: it writes the
+// host's serving claim for the record's own incarnation.
+func offlineClaimFor(t *testing.T, fx *consoleFixture, host, box, region string) {
+ t.Helper()
+ rec, err := provision.GetRecord(context.Background(), fx.rel, "bab", "shop", host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ inc, err := hostclaim.Incarnation("bab", "shop", host, rec.CreatedAt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = hostclaim.Update(context.Background(), fx.rel, hostclaim.Observed{Host: host, Absent: true}, func(c *hostclaim.Claim) error {
+ c.Holder = &hostclaim.Holder{Family: "instance", Key: provision.RecordKey("bab", "shop", host), Account: "bab", App: "shop",
+ Incarnation: inc, CreatedAt: rec.CreatedAt, Location: "instances/" + host, Stamp: 1}
+ c.Op = hostclaim.Op{Kind: hostclaim.KindServing, Exec: box}
+ c.Touched = hostclaim.Touched{Complete: true, Regions: []string{region}, Boxes: []string{box}}
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+}
// offlineBox is one box the fleet publishes: an edges file and a status
// snapshot.
@@ -68,12 +100,15 @@ func newOfflineWorldIn(t *testing.T, home, region string, boxes ...offlineBox) *
if err := fx.st.SetOperator("alice@keymail.dev", true); err != nil {
t.Fatal(err)
}
+ if _, err := fx.st.CreateAppIn(OpsAccountID, "shop", "paul@keymail.dev", time.Now()); err != nil {
+ t.Fatal(err)
+ }
fx.srv.cfg.HomeRegion = home
- m := titoManifest()
+ m := offlineManifest()
m.Regions = nil
m.Pool = ""
- enableInstances(t, fx, "titogo", m)
- u := instancesURL(fx, "bab", "titogo")
+ enableInstances(t, fx, "shop", m)
+ u := instancesURL(fx, "bab", "shop")
if c, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"`+offlineHost+`","region":"`+region+`"}`); c != http.StatusCreated {
t.Fatalf("create = %d %s", c, out)
}
@@ -214,7 +249,7 @@ func (w *offlineWorld) status() offlineView {
func (w *offlineWorld) record() *provision.Record {
w.t.Helper()
- rec, err := provision.GetRecord(context.Background(), w.fx.rel, "bab", "titogo", offlineHost)
+ rec, err := provision.GetRecord(context.Background(), w.fx.rel, "bab", "shop", offlineHost)
if err != nil {
w.t.Fatal(err)
}
@@ -225,7 +260,7 @@ func (w *offlineWorld) record() *provision.Record {
func (w *offlineWorld) edit(muts ...func(*provision.Record) error) {
w.t.Helper()
for _, m := range muts {
- if _, err := provision.SetRecordMove(context.Background(), w.fx.rel, "bab", "titogo", offlineHost, m); err != nil {
+ if _, err := provision.SetRecordMove(context.Background(), w.fx.rel, "bab", "shop", offlineHost, m); err != nil {
w.t.Fatal(err)
}
}
@@ -413,7 +448,7 @@ var offlineRoutes = []struct{ method, path, body string }{
func TestOfflineDoorsAreOperatorOnly(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
member := w.token
- router := serviceToken(t, w.fx.st, OpsAccountID, "router", "titogo", svcrole.Instance)
+ router := serviceToken(t, w.fx.st, OpsAccountID, "router", "shop", svcrole.Instance)
if err := w.fx.st.SetOperator("alice@keymail.dev", false); err != nil {
t.Fatal(err)
}
@@ -487,7 +522,7 @@ func TestOfflineStartRefusals(t *testing.T) {
other := func(w *offlineWorld) {
// A second instance, moving offline: started through its own door, so
// it holds the lock.
- v, code, out := w.startOther("beta.go.tito.io")
+ v, code, out := w.startOther("beta.shop.example.com")
if code != http.StatusAccepted {
t.Fatalf("beta start = %d %s (%+v)", code, out, v)
}
@@ -506,30 +541,30 @@ func TestOfflineStartRefusals(t *testing.T) {
{name: "a record acquisition", boxes: oneEach, body: body, code: 409, msg: msgO29,
setup: func(w *offlineWorld) { w.edit(func(r *provision.Record) error { r.Acquisition = "acq-1"; return nil }) }},
{name: "a host claim", boxes: oneEach, body: body, code: 409, msg: msgO29,
- setup: func(w *offlineWorld) { claimFor(t, w.fx, offlineHost, "ie-box", "ie") }},
+ setup: func(w *offlineWorld) { offlineClaimFor(t, w.fx, offlineHost, "ie-box", "ie") }},
{name: "a domains claim with an acquisition", boxes: oneEach, body: body, code: 409, msg: msgO29,
setup: func(w *offlineWorld) {
- c := domains.Claim{Schema: 1, Host: offlineHost, Account: "bab", App: "titogo", Acquisition: "acq-1"}
+ c := domains.Claim{Schema: 1, Host: offlineHost, Account: "bab", App: "shop", Acquisition: "acq-1"}
raw, _ := json.Marshal(c)
offlinePut(t, w.fx.rel, domains.ClaimKey(offlineHost), raw)
}},
{name: "an admission", boxes: oneEach, body: body, code: 409, msg: msgO29,
setup: func(w *offlineWorld) {
- key, err := hostops.AdmissionKey("bab", "titogo")
+ key, err := hostops.AdmissionKey("bab", "shop")
if err != nil {
t.Fatal(err)
}
offlinePut(t, w.fx.rel, key, []byte(`{}`))
}},
- {name: "a second move in flight", boxes: oneEach, body: body, code: 409, msg: "beta.go.tito.io is moving offline. move one instance at a time", setup: other},
+ {name: "a second move in flight", boxes: oneEach, body: body, code: 409, msg: "beta.shop.example.com is moving offline. move one instance at a time", setup: other},
{name: "no region", boxes: oneEach, body: `{"region":""}`, code: 400, msg: "invalid region"},
{name: "a bad region", boxes: oneEach, body: `{"region":"UK!"}`, code: 400, msg: "invalid region"},
{name: "the current region", boxes: oneEach, body: `{"region":"ie"}`, code: 409, msg: "the instance is already in ie"},
{name: "the manifest's regions", boxes: oneEach, body: body, code: 400, msg: "region uk is not one of this app's regions: ie",
setup: func(w *offlineWorld) {
- m := titoManifest()
+ m := offlineManifest()
m.Pool = ""
- enableInstances(t, w.fx, "titogo", m)
+ enableInstances(t, w.fx, "shop", m)
}},
{name: "an unreadable edges file", boxes: oneEach, body: body, code: 409, msg: "the fleet's edges files could not all be read. try again",
setup: func(w *offlineWorld) { offlinePut(t, w.fx.rel, edgesFilePrefix+"broken.json", []byte("{")) }},
@@ -917,8 +952,8 @@ func TestOfflineFinishDoor(t *testing.T) {
w.refused(http.MethodPost, "/finish", "", http.StatusConflict, msgO25(provision.OfflineOpen))
// A parked report whose inventory is not settled is not parked.
for name, retired := range map[string][]moves.RetiredPath{
- "an unknown action": {{Path: "/data/acme.go.tito.io", Action: "deleted"}},
- "left, no reason": {{Path: "/opt/carlos/live/acme.go.tito.io", Action: moves.RetiredLeft}},
+ "an unknown action": {{Path: "/data/acme.shop.example.com", Action: "deleted"}},
+ "left, no reason": {{Path: "/opt/carlos/live/acme.shop.example.com", Action: moves.RetiredLeft}},
"no path": {{Action: moves.RetiredParked}},
} {
w.report(moves.OfflineRoleSource, "ie-box", moves.OfflinePhaseParked, func(r *moves.OfflineReport) { r.Retired = retired })
@@ -931,12 +966,12 @@ func TestOfflineFinishDoor(t *testing.T) {
// status view shows it.
w.report(moves.OfflineRoleSource, "ie-box", moves.OfflinePhaseParked, func(r *moves.OfflineReport) {
r.Retired = []moves.RetiredPath{
- {Path: "/data/acme.go.tito.io", Action: moves.RetiredParked},
- {Path: "/data/acme.go.tito.io.env", Action: moves.RetiredAbsent},
- {Path: "/opt/carlos/live/acme.go.tito.io", Action: moves.RetiredLeft, Reason: "root-owned"},
+ {Path: "/data/acme.shop.example.com", Action: moves.RetiredParked},
+ {Path: "/data/acme.shop.example.com.env", Action: moves.RetiredAbsent},
+ {Path: "/opt/carlos/live/acme.shop.example.com", Action: moves.RetiredLeft, Reason: "root-owned"},
}
})
- if v := w.status(); v.Next != offlineVerbFinish || len(v.Left) != 1 || v.Left[0] != (offlineLeftView{Box: "ie-box", Path: "/opt/carlos/live/acme.go.tito.io", Reason: "root-owned"}) {
+ if v := w.status(); v.Next != offlineVerbFinish || len(v.Left) != 1 || v.Left[0] != (offlineLeftView{Box: "ie-box", Path: "/opt/carlos/live/acme.shop.example.com", Reason: "root-owned"}) {
t.Fatalf("status = next %q left %+v waiting %q", v.Next, v.Left, v.Waiting)
}
before := serialBytes(t, w.fx.rel)
@@ -1126,7 +1161,7 @@ func TestOfflineConflictMapsOntoRecordedSentences(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
w.start()
apply := func(mut func(*provision.Record) error) error {
- _, err := provision.SetRecordMove(context.Background(), w.fx.rel, "bab", "titogo", offlineHost, mut)
+ _, err := provision.SetRecordMove(context.Background(), w.fx.rel, "bab", "shop", offlineHost, mut)
return err
}
const other = "mv_ZZZZZZZZZZZZZZZZZZZZZZZZZZ"
@@ -1218,7 +1253,7 @@ func TestOfflineMovesOutOfAndIntoHome(t *testing.T) {
// startOther creates a second instance and starts it through its own door.
func (w *offlineWorld) startOther(host string) (offlineView, int, string) {
w.t.Helper()
- u := instancesURL(w.fx, "bab", "titogo")
+ u := instancesURL(w.fx, "bab", "shop")
if c, _, out := rawReq(w.t, http.MethodPost, u, w.token, `{"host":"`+host+`","region":"ie"}`); c != http.StatusCreated && c != http.StatusConflict {
w.t.Fatalf("create %s = %d %s", host, c, out)
}
@@ -1244,11 +1279,11 @@ func (w *offlineWorld) lock() (moves.OfflineActive, bool) {
// Two starts at once for different hosts: exactly one wins the lock.
func TestOfflineConcurrentStartsOneWins(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
- u := instancesURL(w.fx, "bab", "titogo")
- if c, _, out := rawReq(t, http.MethodPost, u, w.token, `{"host":"beta.go.tito.io","region":"ie"}`); c != http.StatusCreated {
+ u := instancesURL(w.fx, "bab", "shop")
+ if c, _, out := rawReq(t, http.MethodPost, u, w.token, `{"host":"beta.shop.example.com","region":"ie"}`); c != http.StatusCreated {
t.Fatalf("create = %d %s", c, out)
}
- hosts := []string{offlineHost, "beta.go.tito.io"}
+ hosts := []string{offlineHost, "beta.shop.example.com"}
type answer struct {
code int
out string
@@ -1287,7 +1322,7 @@ func TestOfflineConcurrentStartsOneWins(t *testing.T) {
if a, ok := w.lock(); !ok || a.Host != hosts[won] {
t.Fatalf("lock = %+v %v, want the winner %s", a, ok, hosts[won])
}
- loser, err := provision.GetRecord(context.Background(), w.fx.rel, "bab", "titogo", hosts[lost])
+ loser, err := provision.GetRecord(context.Background(), w.fx.rel, "bab", "shop", hosts[lost])
if err != nil || loser.Move != nil {
t.Fatalf("the loser's record = %+v %v", loser.Move, err)
}
@@ -1298,7 +1333,7 @@ func TestOfflineConcurrentStartsOneWins(t *testing.T) {
func TestOfflineLockLifecycle(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
w.toLanded()
- if a, ok := w.lock(); !ok || a.Host != offlineHost || a.Op != w.id || a.Account != "bab" || a.App != "titogo" {
+ if a, ok := w.lock(); !ok || a.Host != offlineHost || a.Op != w.id || a.Account != "bab" || a.App != "shop" {
t.Fatalf("lock = %+v %v", a, ok)
}
w.ok("/open", "", provision.OfflineOpen)
@@ -1341,39 +1376,39 @@ func TestOfflineStaleLockIsReplaced(t *testing.T) {
}
// A lock naming a host with no record.
w := newOfflineWorld(t, oneEach...)
- put(w, moves.OfflineActive{Host: "gone.go.tito.io", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "titogo", At: time.Now().Add(-time.Hour)})
+ put(w, moves.OfflineActive{Host: "gone.shop.example.com", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "shop", At: time.Now().Add(-time.Hour)})
w.start()
if a, _ := w.lock(); a.Host != offlineHost || a.Op != w.id {
t.Fatalf("lock = %+v", a)
}
// A lock naming a host whose record carries no such move (a failed release).
x := newOfflineWorld(t, oneEach...)
- if c, _, out := rawReq(t, http.MethodPost, instancesURL(x.fx, "bab", "titogo"), x.token, `{"host":"beta.go.tito.io","region":"ie"}`); c != http.StatusCreated {
+ if c, _, out := rawReq(t, http.MethodPost, instancesURL(x.fx, "bab", "shop"), x.token, `{"host":"beta.shop.example.com","region":"ie"}`); c != http.StatusCreated {
t.Fatalf("create = %d %s", c, out)
}
- put(x, moves.OfflineActive{Host: "beta.go.tito.io", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "titogo", At: time.Now().Add(-time.Hour)})
+ put(x, moves.OfflineActive{Host: "beta.shop.example.com", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "shop", At: time.Now().Add(-time.Hour)})
x.start()
// A lock whose record carries another move of that host is not this one's.
y := newOfflineWorld(t, oneEach...)
- put(y, moves.OfflineActive{Host: offlineHost, Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "titogo", At: time.Now().Add(-time.Hour)})
+ put(y, moves.OfflineActive{Host: offlineHost, Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "shop", At: time.Now().Add(-time.Hour)})
y.start()
// A young lock is believed even before its record carries the move: the
// start door takes the lock, then writes the record.
f := newOfflineWorld(t, oneEach...)
- if c, _, out := rawReq(t, http.MethodPost, instancesURL(f.fx, "bab", "titogo"), f.token, `{"host":"beta.go.tito.io","region":"ie"}`); c != http.StatusCreated {
+ if c, _, out := rawReq(t, http.MethodPost, instancesURL(f.fx, "bab", "shop"), f.token, `{"host":"beta.shop.example.com","region":"ie"}`); c != http.StatusCreated {
t.Fatalf("create = %d %s", c, out)
}
- put(f, moves.OfflineActive{Host: "beta.go.tito.io", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "titogo", At: time.Now()})
- f.refused(http.MethodPost, "", `{"region":"uk"}`, http.StatusConflict, "beta.go.tito.io is moving offline. move one instance at a time")
+ put(f, moves.OfflineActive{Host: "beta.shop.example.com", Op: "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa", Account: "bab", App: "shop", At: time.Now()})
+ f.refused(http.MethodPost, "", `{"region":"uk"}`, http.StatusConflict, "beta.shop.example.com is moving offline. move one instance at a time")
// An old lock is live while its record carries the move.
g := newOfflineWorld(t, oneEach...)
- if _, code, out := g.startOther("beta.go.tito.io"); code != http.StatusAccepted {
+ if _, code, out := g.startOther("beta.shop.example.com"); code != http.StatusAccepted {
t.Fatalf("beta start = %d %s", code, out)
}
a, _ := g.lock()
a.At = time.Now().Add(-24 * time.Hour)
put(g, a)
- g.refused(http.MethodPost, "", `{"region":"uk"}`, http.StatusConflict, "beta.go.tito.io is moving offline. move one instance at a time")
+ g.refused(http.MethodPost, "", `{"region":"uk"}`, http.StatusConflict, "beta.shop.example.com is moving offline. move one instance at a time")
// An unreadable lock is not guessed at.
z := newOfflineWorld(t, oneEach...)
offlinePut(t, z.fx.rel, moves.OfflineActiveKey, []byte("not json"))
@@ -1384,7 +1419,7 @@ func TestOfflineStaleLockIsReplaced(t *testing.T) {
// gate, not a listing of every record.
func TestOfflineStartIgnoresOtherRecords(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
- offlinePut(t, w.fx.rel, provision.RecordKey("bab", "titogo", "junk.go.tito.io"), []byte("{not a record"))
+ offlinePut(t, w.fx.rel, provision.RecordKey("bab", "shop", "junk.shop.example.com"), []byte("{not a record"))
w.start()
}
@@ -1396,10 +1431,10 @@ func TestOfflineStartKeepsTheLockWhenTheRecordWriteFails(t *testing.T) {
for _, landed := range []bool{true, false} {
t.Run(fmt.Sprintf("landed=%v", landed), func(t *testing.T) {
w := newOfflineWorld(t, oneEach...)
- if c, _, out := rawReq(t, http.MethodPost, instancesURL(w.fx, "bab", "titogo"), w.token, `{"host":"beta.go.tito.io","region":"ie"}`); c != http.StatusCreated {
+ if c, _, out := rawReq(t, http.MethodPost, instancesURL(w.fx, "bab", "shop"), w.token, `{"host":"beta.shop.example.com","region":"ie"}`); c != http.StatusCreated {
t.Fatalf("create = %d %s", c, out)
}
- w.fx.srv.cfg.Releases = &recordWriteFault{Store: w.fx.rel, key: provision.RecordKey("bab", "titogo", offlineHost), land: landed}
+ w.fx.srv.cfg.Releases = &recordWriteFault{Store: w.fx.rel, key: provision.RecordKey("bab", "shop", offlineHost), land: landed}
code, out := w.req(http.MethodPost, "", `{"region":"uk"}`)
if landed {
// The record carries the move: the start succeeded.
@@ -1417,7 +1452,7 @@ func TestOfflineStartKeepsTheLockWhenTheRecordWriteFails(t *testing.T) {
t.Fatalf("lock = %+v %v, want it kept", a, ok)
}
// A second host's start is refused whichever way the write went.
- bu := instancesURL(w.fx, "bab", "titogo") + "/beta.go.tito.io/move/offline"
+ bu := instancesURL(w.fx, "bab", "shop") + "/beta.shop.example.com/move/offline"
c, _, o := rawReq(t, http.MethodPost, bu, w.token, `{"region":"uk"}`)
if c != http.StatusConflict || errorText(o) != offlineHost+" is moving offline. move one instance at a time" {
t.Fatalf("beta start = %d %s", c, o)
diff --git a/internal/moves/offline_test.go b/internal/moves/offline_test.go
index 84a0e4050..50fbc9e13 100644
--- a/internal/moves/offline_test.go
+++ b/internal/moves/offline_test.go
@@ -443,7 +443,7 @@ func TestOfflineStateAndPhaseNamesAreDistinct(t *testing.T) {
}
func TestOfflineSealKeyAndValidation(t *testing.T) {
- const host, op = "acme.go.tito.io", "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa"
+ const host, op = "acme.go.example.com", "mv_aaaaaaaaaaaaaaaaaaaaaaaaaa"
if got := OfflineSealKey(host, op); got != "control/offline-moves/"+host+"/"+op+"/seal.json" {
t.Fatalf("seal key = %q", got)
}
@@ -466,7 +466,7 @@ func TestOfflineSealKeyAndValidation(t *testing.T) {
t.Fatal(err)
}
for name, edit := range map[string]func(*OfflineReport){
- "another host": func(r *OfflineReport) { r.Host = "other.go.tito.io" },
+ "another host": func(r *OfflineReport) { r.Host = "other.go.example.com" },
"another op": func(r *OfflineReport) { r.Op = "mv_bbbbbbbbbbbbbbbbbbbbbbbbbb" },
"a source role": func(r *OfflineReport) { r.Role = OfflineRoleSource },
"not sealed": func(r *OfflineReport) { r.Phase = OfflinePhaseStopped },
diff --git a/move_test.go b/move_test.go
index 3ae90a02d..e91be1486 100644
--- a/move_test.go
+++ b/move_test.go
@@ -1097,7 +1097,11 @@ func TestMoveStatusSaysWhyTheCutDidNotStart(t *testing.T) {
// ---- carlos move offline ----
-const tOfflinePth = tMovePth + "/offline"
+// tOffMovePth is the move door of the offline tests' instance, oHost of the
+// app shop.
+const tOffMovePth = "/api/cli/apps/bab/shop/instances/" + oHost + "/move"
+
+const tOfflinePth = tOffMovePth + "/offline"
// offPost is one scripted answer of a mutating door; the last one repeats.
type offPost struct {
@@ -1127,7 +1131,7 @@ func (c *fakeOfflineConsole) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(v)
}
body, _ := io.ReadAll(r.Body)
- c.log = append(c.log, r.Method+" "+strings.TrimPrefix(r.URL.Path, tMovePth)+" "+strings.TrimSpace(string(body)))
+ c.log = append(c.log, r.Method+" "+strings.TrimPrefix(r.URL.Path, tOffMovePth)+" "+strings.TrimSpace(string(body)))
if !strings.HasPrefix(r.URL.Path, tOfflinePth) {
http.NotFound(w, r)
return
@@ -1185,7 +1189,7 @@ func runOfflineAgainst(t *testing.T, c *fakeOfflineConsole, args ...string) (int
stdin: strings.NewReader(""),
}
args = append([]string{"offline"}, args...)
- args = append(args, "--host", tHost, "--account", "bab", "--app", "titogo")
+ args = append(args, "--host", oHost, "--account", "bab", "--app", "shop")
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
code := moveMain(ctx, args, &out, deps)
@@ -1194,7 +1198,7 @@ func runOfflineAgainst(t *testing.T, c *fakeOfflineConsole, args ...string) (int
// offView is the offline status view of a move in state, as T6 builds it.
func offView(state string, extra frame) frame {
- f := frame{"host": tHost, "state": state, "id": tMoveID, "from": "ie", "to": "uk",
+ f := frame{"host": oHost, "state": state, "id": tMoveID, "from": "ie", "to": "uk",
"sealer": "ie-box", "boxes": []string{"ie-box"}}
for k, v := range extra {
f[k] = v
@@ -1203,7 +1207,7 @@ func offView(state string, extra frame) frame {
}
func offDone(outcome string, offlineS float64) frame {
- return frame{"host": tHost, "state": "none",
+ return frame{"host": oHost, "state": "none",
"last": frame{"id": tMoveID, "outcome": outcome, "at": "2026-10-20T10:00:00Z", "offline_s": offlineS}}
}
@@ -1212,19 +1216,19 @@ func offReport(box, role, phase string, fresh bool) frame {
}
const (
- oHost = "acme.go.tito.io"
+ oHost = "acme.shop.example.com"
- oStarting = "Moving acme.go.tito.io from ie to uk."
- oOffline = "acme.go.tito.io is offline from now until the move opens or is aborted."
- oStopped = "Every box in ie has stopped acme.go.tito.io. Run carlos move offline seal --host acme.go.tito.io to copy it."
- oCopying = "Copying acme.go.tito.io to uk."
- oCopied = "Copied and checked. Run carlos move offline flip --host acme.go.tito.io to start it in uk."
+ oStarting = "Moving acme.shop.example.com from ie to uk."
+ oOffline = "acme.shop.example.com is offline from now until the move opens or is aborted."
+ oStopped = "Every box in ie has stopped acme.shop.example.com. Run carlos move offline seal --host acme.shop.example.com to copy it."
+ oCopying = "Copying acme.shop.example.com to uk."
+ oCopied = "Copied and checked. Run carlos move offline flip --host acme.shop.example.com to start it in uk."
oFlipped = "Stopped in ie. Starting in uk."
- oLanded = "uk holds everything acme.go.tito.io needs. Nothing is served yet. Run carlos move offline open --host acme.go.tito.io to serve it from uk."
- oServed = "acme.go.tito.io is served from uk. Run carlos move offline finish --host acme.go.tito.io once ie has cleaned up."
- oBack = "Moving acme.go.tito.io back to ie."
- oPutBack = "Every box has put acme.go.tito.io back in ie. Run carlos move offline finish --host acme.go.tito.io to end the move."
- oWaitBox = "Waiting for ie-box in ie to confirm it has stopped acme.go.tito.io. acme.go.tito.io is offline."
+ oLanded = "uk holds everything acme.shop.example.com needs. Nothing is served yet. Run carlos move offline open --host acme.shop.example.com to serve it from uk."
+ oServed = "acme.shop.example.com is served from uk. Run carlos move offline finish --host acme.shop.example.com once ie has cleaned up."
+ oBack = "Moving acme.shop.example.com back to ie."
+ oPutBack = "Every box has put acme.shop.example.com back in ie. Run carlos move offline finish --host acme.shop.example.com to end the move."
+ oWaitBox = "Waiting for ie-box in ie to confirm it has stopped acme.shop.example.com. acme.shop.example.com is offline."
)
func TestParseMoveArgsOffline(t *testing.T) {
@@ -1233,21 +1237,21 @@ func TestParseMoveArgsOffline(t *testing.T) {
ok bool
sub string
}{
- {[]string{"offline", "start", "--host", tHost, "--to", "uk"}, true, "start"},
- {[]string{"offline", "seal", "--host", tHost}, true, "seal"},
- {[]string{"offline", "flip", "--host", tHost}, true, "flip"},
- {[]string{"offline", "open", "--host", tHost}, true, "open"},
- {[]string{"offline", "finish", "--host", tHost}, true, "finish"},
- {[]string{"offline", "abort", "--host", tHost}, true, "abort"},
- {[]string{"offline", "status", "--host", tHost, "--app", "x", "--account", "y"}, true, "status"},
- {[]string{"offline", "start", "--host", tHost}, false, ""},
+ {[]string{"offline", "start", "--host", oHost, "--to", "uk"}, true, "start"},
+ {[]string{"offline", "seal", "--host", oHost}, true, "seal"},
+ {[]string{"offline", "flip", "--host", oHost}, true, "flip"},
+ {[]string{"offline", "open", "--host", oHost}, true, "open"},
+ {[]string{"offline", "finish", "--host", oHost}, true, "finish"},
+ {[]string{"offline", "abort", "--host", oHost}, true, "abort"},
+ {[]string{"offline", "status", "--host", oHost, "--app", "x", "--account", "y"}, true, "status"},
+ {[]string{"offline", "start", "--host", oHost}, false, ""},
{[]string{"offline", "start", "--to", "uk"}, false, ""},
- {[]string{"offline", "seal", "--host", tHost, "--to", "uk"}, false, ""},
- {[]string{"offline", "seal", "--host", tHost, "--no-wait"}, false, ""},
- {[]string{"offline", "seal", "--host", tHost, "extra"}, false, ""},
+ {[]string{"offline", "seal", "--host", oHost, "--to", "uk"}, false, ""},
+ {[]string{"offline", "seal", "--host", oHost, "--no-wait"}, false, ""},
+ {[]string{"offline", "seal", "--host", oHost, "extra"}, false, ""},
{[]string{"offline"}, false, ""},
- {[]string{"offline", "cut", "--host", tHost}, false, ""},
- {[]string{"offline", "--host", tHost}, false, ""},
+ {[]string{"offline", "cut", "--host", oHost}, false, ""},
+ {[]string{"offline", "--host", oHost}, false, ""},
} {
got, err := parseMoveArgs(c.args, io.Discard)
if (err == nil) != c.ok || (c.ok && (got.verb != "offline" || got.offline != c.sub)) {
@@ -1255,15 +1259,15 @@ func TestParseMoveArgsOffline(t *testing.T) {
}
}
var out bytes.Buffer
- if _, err := parseMoveArgs([]string{"offline", "cut", "--host", tHost}, &out); err == nil {
+ if _, err := parseMoveArgs([]string{"offline", "cut", "--host", oHost}, &out); err == nil {
t.Fatal("an unknown offline verb parsed")
}
wantLines(t, out.String(), "carlos move offline needs start, seal, flip, open, finish, abort or status.")
out.Reset()
- _, _ = parseMoveArgs([]string{"offline", "start", "--host", tHost}, &out)
+ _, _ = parseMoveArgs([]string{"offline", "start", "--host", oHost}, &out)
wantLines(t, out.String(), "carlos move needs --to <region>.")
// The Phase 1 verbs are untouched.
- if got, err := parseMoveArgs([]string{"abort", "--host", tHost}, io.Discard); err != nil || got.verb != "abort" || got.offline != "" {
+ if got, err := parseMoveArgs([]string{"abort", "--host", oHost}, io.Discard); err != nil || got.verb != "abort" || got.offline != "" {
t.Errorf("carlos move abort = %+v, %v", got, err)
}
}
@@ -1327,7 +1331,7 @@ func TestMoveOfflineRefusals(t *testing.T) {
}
func TestMoveOfflineSeal(t *testing.T) {
- proc := "ie-box still runs a process for acme.go.tito.io. wait for it to stop, then flip"
+ proc := "ie-box still runs a process for acme.shop.example.com. wait for it to stop, then flip"
c := &fakeOfflineConsole{
posts: map[string][]offPost{"/seal": {{202, offView("offline-sealing", nil)}}},
gets: []frame{
@@ -1356,11 +1360,11 @@ func TestMoveOfflineSealFails(t *testing.T) {
extra frame
say string
}{
- {"item", frame{"items": []frame{item("ok", ""), item("failed", "a file over 256 MiB")}}, "The move could not finish a step: a file over 256 MiB. Run carlos move offline status --host acme.go.tito.io for each item."},
- {"item without detail", frame{"items": []frame{item("failed", "")}}, "The move could not finish a step: files. Run carlos move offline status --host acme.go.tito.io for each item."},
+ {"item", frame{"items": []frame{item("ok", ""), item("failed", "a file over 256 MiB")}}, "The move could not finish a step: a file over 256 MiB. Run carlos move offline status --host acme.shop.example.com for each item."},
+ {"item without detail", frame{"items": []frame{item("failed", "")}}, "The move could not finish a step: files. Run carlos move offline status --host acme.shop.example.com for each item."},
{"failed flag and report", frame{"failed": true, "reports": []frame{{"box": "ie-box", "role": "sealer", "phase": "failed", "fresh": true,
"items": frame{"db": frame{"ok": true, "reason": ""}, "files": frame{"ok": false, "reason": "special file in the archive"}}}}},
- "The move could not finish a step: special file in the archive. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "The move could not finish a step: special file in the archive. Run carlos move offline status --host acme.shop.example.com for each item."},
} {
t.Run(c.name, func(t *testing.T) {
f := &fakeOfflineConsole{
@@ -1380,12 +1384,12 @@ func TestMoveOfflineFlip(t *testing.T) {
posts: map[string][]offPost{"/flip": {{202, offView("offline-landing", nil)}}},
gets: []frame{
offView("offline-landing", nil),
- offView("offline-landed", frame{"waiting": "this move is offline-landed. run carlos move offline status --host acme.go.tito.io to see what it waits for"}),
+ offView("offline-landed", frame{"waiting": "this move is offline-landed. run carlos move offline status --host acme.shop.example.com to see what it waits for"}),
offView("offline-landed", frame{"next": "open"}),
},
}
code, out := runOfflineAgainst(t, c, "flip")
- want := oFlipped + "\nthis move is offline-landed. run carlos move offline status --host acme.go.tito.io to see what it waits for\n" + oLanded + "\n"
+ want := oFlipped + "\nthis move is offline-landed. run carlos move offline status --host acme.shop.example.com to see what it waits for\n" + oLanded + "\n"
if code != 0 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant:\n%s", code, out, want)
}
@@ -1398,11 +1402,11 @@ func TestMoveOfflineFlipFails(t *testing.T) {
extra frame
say string
}{
- {"item", frame{"items": []frame{failedItem}}, "uk could not take acme.go.tito.io: the listing differs. Run carlos move offline abort --host acme.go.tito.io to move it back."},
+ {"item", frame{"items": []frame{failedItem}}, "uk could not take acme.shop.example.com: the listing differs. Run carlos move offline abort --host acme.shop.example.com to move it back."},
{"failed flag, check in the report", frame{"failed": true, "reports": []frame{{"box": "uk-box", "role": "target", "phase": "failed", "fresh": true,
"items": frame{"env-pin": frame{"ok": false, "reason": "the app settings differ"}}}}},
- "uk could not take acme.go.tito.io: the app settings differ. Run carlos move offline abort --host acme.go.tito.io to move it back."},
- {"failed flag only", frame{"failed": true}, "uk could not take acme.go.tito.io: failed. Run carlos move offline abort --host acme.go.tito.io to move it back."},
+ "uk could not take acme.shop.example.com: the app settings differ. Run carlos move offline abort --host acme.shop.example.com to move it back."},
+ {"failed flag only", frame{"failed": true}, "uk could not take acme.shop.example.com: failed. Run carlos move offline abort --host acme.shop.example.com to move it back."},
} {
t.Run(c.name, func(t *testing.T) {
f := &fakeOfflineConsole{
@@ -1434,7 +1438,7 @@ func TestMoveOfflineOpen(t *testing.T) {
}
code, out := runOfflineAgainst(t, c, "open")
// The served line waits for the target's opened report.
- want := "Waiting for ie-box to clean up acme.go.tito.io.\n" + oServed + "\n"
+ want := "Waiting for ie-box to clean up acme.shop.example.com.\n" + oServed + "\n"
if code != 0 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant:\n%s", code, out, want)
}
@@ -1443,7 +1447,7 @@ func TestMoveOfflineOpen(t *testing.T) {
posts: map[string][]offPost{"/open": {{202, offView("offline-open", nil)}}},
gets: []frame{offView("offline-open", frame{"next": "finish"})},
}
- if code, out := runOfflineAgainst(t, c, "open"); code != 0 || out != "Waiting for ie-box to clean up acme.go.tito.io.\n"+oServed+"\n" {
+ if code, out := runOfflineAgainst(t, c, "open"); code != 0 || out != "Waiting for ie-box to clean up acme.shop.example.com.\n"+oServed+"\n" {
t.Fatalf("exit %d, output:\n%s", code, out)
}
}
@@ -1465,7 +1469,7 @@ func TestMoveOfflineFinishPostsTwice(t *testing.T) {
},
}
code, out := runOfflineAgainst(t, c, "finish")
- want := "Waiting for ie-box to clean up acme.go.tito.io.\nMoved. acme.go.tito.io is served from uk. It was offline for 1800 seconds.\n"
+ want := "Waiting for ie-box to clean up acme.shop.example.com.\nMoved. acme.shop.example.com is served from uk. It was offline for 1800 seconds.\n"
if code != 0 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant:\n%s", code, out, want)
}
@@ -1484,7 +1488,7 @@ func TestMoveOfflineFinishWaitsBeforeThePost(t *testing.T) {
},
}
code, out := runOfflineAgainst(t, c, "finish")
- if code != 0 || !strings.HasSuffix(out, "It was offline for 12 seconds.\n") || !strings.HasPrefix(out, "Waiting for ie-box to clean up acme.go.tito.io.\n") {
+ if code != 0 || !strings.HasSuffix(out, "It was offline for 12 seconds.\n") || !strings.HasPrefix(out, "Waiting for ie-box to clean up acme.shop.example.com.\n") {
t.Fatalf("exit %d:\n%s", code, out)
}
reqs := c.requests()
@@ -1505,7 +1509,7 @@ func TestMoveOfflineFinishAfterAbort(t *testing.T) {
gets: []frame{offView("offline-reverting", frame{"next": "finish"})},
}
code, out := runOfflineAgainst(t, c, "finish")
- if want := "acme.go.tito.io is back in ie and serving. Nothing was lost.\n"; code != 3 || out != want {
+ if want := "acme.shop.example.com is back in ie and serving. Nothing was lost.\n"; code != 3 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant 3:\n%s", code, out, want)
}
if c.posted("/finish") != 1 {
@@ -1516,15 +1520,15 @@ func TestMoveOfflineFinishAfterAbort(t *testing.T) {
func TestMoveOfflineFinishRefusedAndOutcomeNotRecorded(t *testing.T) {
// Nothing in flight: the door's own sentence, exit 5.
c := &fakeOfflineConsole{
- posts: map[string][]offPost{"/finish": {{409, frame{"error": "acme.go.tito.io has no offline move in flight"}}}},
- gets: []frame{{"host": tHost, "state": "none"}},
+ posts: map[string][]offPost{"/finish": {{409, frame{"error": "acme.shop.example.com has no offline move in flight"}}}},
+ gets: []frame{{"host": oHost, "state": "none"}},
}
- if code, out := runOfflineAgainst(t, c, "finish"); code != 5 || out != "acme.go.tito.io has no offline move in flight\n" {
+ if code, out := runOfflineAgainst(t, c, "finish"); code != 5 || out != "acme.shop.example.com has no offline move in flight\n" {
t.Fatalf("exit %d:\n%s", code, out)
}
// An outcome that is another move's is not this move's.
c = &fakeOfflineConsole{
- posts: map[string][]offPost{"/finish": {{202, frame{"host": tHost, "state": "none", "last": frame{"id": "mv_other", "outcome": "done"}}}}},
+ posts: map[string][]offPost{"/finish": {{202, frame{"host": oHost, "state": "none", "last": frame{"id": "mv_other", "outcome": "done"}}}}},
gets: []frame{offView("offline-reverting", frame{"next": "finish"})},
}
if code, out := runOfflineAgainst(t, c, "finish"); code != 4 || out != "This move's outcome is not recorded.\n" {
@@ -1541,7 +1545,7 @@ func TestMoveOfflineAbort(t *testing.T) {
},
}
code, out := runOfflineAgainst(t, c, "abort")
- want := oBack + "\nWaiting for ie-box to clean up acme.go.tito.io.\n" + oPutBack + "\n"
+ want := oBack + "\nWaiting for ie-box to clean up acme.shop.example.com.\n" + oPutBack + "\n"
if code != 3 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant 3:\n%s", code, out, want)
}
@@ -1549,8 +1553,8 @@ func TestMoveOfflineAbort(t *testing.T) {
t.Error("abort ran finish by itself")
}
// Once open, the door refuses and the CLI says so as sent.
- c = &fakeOfflineConsole{posts: map[string][]offPost{"/abort": {{409, frame{"error": "acme.go.tito.io is already served from uk. move it back with a new move"}}}}}
- if code, out := runOfflineAgainst(t, c, "abort"); code != 5 || out != "acme.go.tito.io is already served from uk. move it back with a new move\n" {
+ c = &fakeOfflineConsole{posts: map[string][]offPost{"/abort": {{409, frame{"error": "acme.shop.example.com is already served from uk. move it back with a new move"}}}}}
+ if code, out := runOfflineAgainst(t, c, "abort"); code != 5 || out != "acme.shop.example.com is already served from uk. move it back with a new move\n" {
t.Fatalf("exit %d:\n%s", code, out)
}
}
@@ -1563,7 +1567,7 @@ func TestMoveOfflineOvertaken(t *testing.T) {
gets: []frame{offView("offline-reverting", nil)},
}
code, out := runOfflineAgainst(t, c, "seal")
- if want := oCopying + "\n" + oStarting + "\n" + oBack + "\nWaiting for ie-box to clean up acme.go.tito.io.\n"; code != 4 || out != want {
+ if want := oCopying + "\n" + oStarting + "\n" + oBack + "\nWaiting for ie-box to clean up acme.shop.example.com.\n"; code != 4 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant 4:\n%s", code, out, want)
}
}
@@ -1591,7 +1595,7 @@ func TestMoveOfflineStatus(t *testing.T) {
// A failed item is the operator's: exit 4.
c = &fakeOfflineConsole{gets: []frame{offView("offline-landing", frame{"items": []frame{{"id": "db", "owner": "landing", "status": "failed", "detail": "digest differs"}}})}}
code, out = runOfflineAgainst(t, c, "status")
- wantF := oStarting + "\nuk could not take acme.go.tito.io: digest differs. Run carlos move offline abort --host acme.go.tito.io to move it back.\nItem db is failed: digest differs.\n"
+ wantF := oStarting + "\nuk could not take acme.shop.example.com: digest differs. Run carlos move offline abort --host acme.shop.example.com to move it back.\nItem db is failed: digest differs.\n"
if code != 4 || out != wantF {
t.Fatalf("exit %d, output:\n%s\nwant 4:\n%s", code, out, wantF)
}
@@ -1603,12 +1607,12 @@ func TestMoveOfflineStatus(t *testing.T) {
}
// Nothing in flight.
- c = &fakeOfflineConsole{gets: []frame{{"host": tHost, "state": "none"}}}
- if code, out := runOfflineAgainst(t, c, "status"); code != 0 || out != "No move is in flight for acme.go.tito.io.\n" {
+ c = &fakeOfflineConsole{gets: []frame{{"host": oHost, "state": "none"}}}
+ if code, out := runOfflineAgainst(t, c, "status"); code != 0 || out != "No move is in flight for acme.shop.example.com.\n" {
t.Fatalf("exit %d:\n%s", code, out)
}
c = &fakeOfflineConsole{gets: []frame{offDone("done", 5)}}
- if code, out := runOfflineAgainst(t, c, "status"); code != 0 || out != "Move "+tMoveID+" of acme.go.tito.io is done.\n" {
+ if code, out := runOfflineAgainst(t, c, "status"); code != 0 || out != "Move "+tMoveID+" of acme.shop.example.com is done.\n" {
t.Fatalf("exit %d:\n%s", code, out)
}
}
@@ -1618,7 +1622,7 @@ func TestMoveOfflineWaitingAfterOpenAndAbort(t *testing.T) {
c := &fakeOfflineConsole{gets: []frame{offView("offline-finishing", frame{
"reports": []frame{offReport("ie-box", "source", "gone", true), offReport("ie-box", "sealer", "sealed", true)}})}}
code, out := runOfflineAgainst(t, c, "status")
- want := oStarting + "\n" + oServed + "\nWaiting for ie-box to clean up acme.go.tito.io.\n" +
+ want := oStarting + "\n" + oServed + "\nWaiting for ie-box to clean up acme.shop.example.com.\n" +
"ie-box is gone as the source.\nie-box is sealed as the sealer.\n"
if code != 0 || out != want {
t.Fatalf("exit %d, output:\n%s\nwant:\n%s", code, out, want)
@@ -1629,12 +1633,12 @@ func TestMoveOfflineNeedsNoPhase1AndRefusesRoot(t *testing.T) {
// The Phase 1 door is never read: the fake 404s everything outside
// /move/offline, and the verb still works (the other tests). Root is
// refused as for every carlos move.
- c := &fakeOfflineConsole{gets: []frame{{"host": tHost, "state": "none"}}}
+ c := &fakeOfflineConsole{gets: []frame{{"host": oHost, "state": "none"}}}
ts := httptest.NewServer(c)
defer ts.Close()
var out bytes.Buffer
deps := moveDeps{session: func(string) (*apiSession, error) { t.Error("signed in as root"); return nil, nil }, euid: func() int { return 0 }, poll: time.Millisecond}
- if code := moveMain(context.Background(), []string{"offline", "status", "--host", tHost, "--app", "titogo"}, &out, deps); code != 5 {
+ if code := moveMain(context.Background(), []string{"offline", "status", "--host", oHost, "--app", "shop"}, &out, deps); code != 5 {
t.Fatalf("exit %d:\n%s", code, out.String())
}
wantLines(t, out.String(), "Run carlos move as yourself, not as root. It uses your own credentials.")
@@ -1722,13 +1726,13 @@ func TestMoveOfflineStatusFailed(t *testing.T) {
say string
}{
{"failed seal, flag only", "offline-sealing", frame{"failed": true},
- "The move could not finish a step: failed. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "The move could not finish a step: failed. Run carlos move offline status --host acme.shop.example.com for each item."},
{"failed seal, sealer report", "offline-sealing", frame{"failed": true, "reports": []frame{sealerFailed}},
- "The move could not finish a step: special file in the archive. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "The move could not finish a step: special file in the archive. Run carlos move offline status --host acme.shop.example.com for each item."},
{"failed landing, target report", "offline-landed", frame{"failed": true, "reports": []frame{targetFailed}},
- "uk could not take acme.go.tito.io: failed. Run carlos move offline abort --host acme.go.tito.io to move it back."},
+ "uk could not take acme.shop.example.com: failed. Run carlos move offline abort --host acme.shop.example.com to move it back."},
{"failed target before the flip", "offline-stopping", frame{"failed": true, "reports": []frame{targetFailed}},
- "uk could not take acme.go.tito.io: failed. Run carlos move offline abort --host acme.go.tito.io to move it back."},
+ "uk could not take acme.shop.example.com: failed. Run carlos move offline abort --host acme.shop.example.com to move it back."},
} {
t.Run(c.name, func(t *testing.T) {
f := &fakeOfflineConsole{gets: []frame{offView(c.state, c.extra)}}
@@ -1747,13 +1751,13 @@ func TestMoveOfflineStatusFailed(t *testing.T) {
func TestMoveOfflineStatusShowsPathsLeft(t *testing.T) {
c := &fakeOfflineConsole{gets: []frame{offView("offline-finishing", frame{
"next": "finish",
- "left": []frame{{"box": "ie-box", "path": "/opt/carlos/live/acme.go.tito.io", "reason": "a root-owned directory"}},
+ "left": []frame{{"box": "ie-box", "path": "/opt/carlos/live/acme.shop.example.com", "reason": "a root-owned directory"}},
})}}
code, out := runOfflineAgainst(t, c, "status")
if code != 0 {
t.Fatalf("exit %d:\n%s", code, out)
}
- wantLines(t, out, "ie-box left /opt/carlos/live/acme.go.tito.io in place: a root-owned directory.")
+ wantLines(t, out, "ie-box left /opt/carlos/live/acme.shop.example.com in place: a root-owned directory.")
}
// A failed report seen after the open is not silent: status says it and exits
@@ -1772,13 +1776,13 @@ func TestMoveOfflineFailedReportAfterTheOpen(t *testing.T) {
}{
// Abort refuses once open, so a target failure there says MV-O9.
{"target, open", "offline-open", failedRep("uk-box", "target", "config"),
- "The move could not finish a step: the config check failed. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "The move could not finish a step: the config check failed. Run carlos move offline status --host acme.shop.example.com for each item."},
{"sealer, finishing", "offline-finishing", failedRep("ie-box", "sealer", "lease"),
- "The move could not finish a step: the lease check failed. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "The move could not finish a step: the lease check failed. Run carlos move offline status --host acme.shop.example.com for each item."},
{"source, open", "offline-open", failedRep("ie-box", "source", "warm-cache"),
- "ie-box could not clean up acme.go.tito.io: the warm-cache check failed. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "ie-box could not clean up acme.shop.example.com: the warm-cache check failed. Run carlos move offline status --host acme.shop.example.com for each item."},
{"source, reverting", "offline-reverting", failedRep("ie-box", "source", "warm-cache"),
- "ie-box could not clean up acme.go.tito.io: the warm-cache check failed. Run carlos move offline status --host acme.go.tito.io for each item."},
+ "ie-box could not clean up acme.shop.example.com: the warm-cache check failed. Run carlos move offline status --host acme.shop.example.com for each item."},
} {
t.Run(c.name, func(t *testing.T) {
f := &fakeOfflineConsole{gets: []frame{offView(c.state, frame{"reports": []frame{c.rep}})}}
diff --git a/offline_move_e2e_test.go b/offline_move_e2e_test.go
index a924edb49..fe8e07eb2 100644
--- a/offline_move_e2e_test.go
+++ b/offline_move_e2e_test.go
@@ -28,7 +28,7 @@ import (
// The boxes are played by reports written where the real boxes write them.
func TestMoveOfflineAgainstTheRealDoors(t *testing.T) {
const (
- host = "acme.go.tito.io"
+ host = "acme.shop.example.com"
addr = "paul@keymail.dev"
)
tmp := t.TempDir()
@@ -78,18 +78,18 @@ func TestMoveOfflineAgainstTheRealDoors(t *testing.T) {
if err := cst.AddMember(console.OpsAccountID, addr, console.RoleOwner, time.Now()); err != nil {
t.Fatal(err)
}
- if _, err := cst.CreateAppIn(console.OpsAccountID, "titogo", addr, time.Now()); err != nil {
+ if _, err := cst.CreateAppIn(console.OpsAccountID, "shop", addr, time.Now()); err != nil {
t.Fatal(err)
}
token, _ := e2eDeviceLogin(t, ts.URL, cst, addr, "offline@laptop")
ctx := context.Background()
// A hibernating instance in ie, and the two regions' boxes published.
- if err := provision.PutManifest(ctx, rel, release.OpsAccount, "titogo",
- provision.Manifest{Schema: 1, App: "titogo", Domain: "go.tito.io", Channel: "stable"}); err != nil {
+ if err := provision.PutManifest(ctx, rel, release.OpsAccount, "shop",
+ provision.Manifest{Schema: 1, App: "shop", Domain: "shop.example.com", Channel: "stable"}); err != nil {
t.Fatal(err)
}
- req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/cli/apps/"+release.OpsAccount+"/titogo/instances",
+ req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/cli/apps/"+release.OpsAccount+"/shop/instances",
strings.NewReader(`{"host":"`+host+`","region":"ie"}`))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -128,7 +128,7 @@ func TestMoveOfflineAgainstTheRealDoors(t *testing.T) {
euid: func() int { return 1000 },
poll: 20 * time.Millisecond,
}
- args = append(append([]string{"offline"}, args...), "--host", host, "--account", release.OpsAccount, "--app", "titogo")
+ args = append(append([]string{"offline"}, args...), "--host", host, "--account", release.OpsAccount, "--app", "shop")
code := moveMain(c, args, &out, deps)
return code, out.String()
}
@@ -164,7 +164,7 @@ func TestMoveOfflineAgainstTheRealDoors(t *testing.T) {
}()
var id string
for deadline := time.Now().Add(20 * time.Second); id == "" && time.Now().Before(deadline); time.Sleep(10 * time.Millisecond) {
- if rec, err := provision.GetRecord(ctx, rel, release.OpsAccount, "titogo", host); err == nil && rec.Move != nil {
+ if rec, err := provision.GetRecord(ctx, rel, release.OpsAccount, "shop", host); err == nil && rec.Move != nil {
id = rec.Move.ID
}
}