carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/ed25519"
6 "encoding/json"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostops"
12 "github.com/carlosframework/platform/internal/provision"
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17func TestSidecarWakeOldIdentityCannotWakeReplacement(t *testing.T) {
18 t.Setenv("CARLOS_HOST_CLAIM", "")
19 ctx := context.Background()
20 s := release.NewDirStore(t.TempDir())
21 host := provision.SidecarHost("bot", "app", "acct")
22 at := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
23 inc, err := hostclaim.Incarnation("acct", "app", host, at)
24 if err != nil {
25 t.Fatal(err)
26 }
27 _, err = hostclaim.Update(ctx, s, hostclaim.Observed{Host: host, Absent: true}, func(c *hostclaim.Claim) error {
28 c.Holder = &hostclaim.Holder{Family: "sidecar", Key: provision.SidecarRecordKey("acct", "app", "bot"), Account: "acct", App: "app", Name: "bot", CreatedAt: at, Incarnation: inc, Location: "instances/" + host + "/" + inc, Stamp: 1, Session: 1, WriterBox: "box"}
29 c.Op = hostclaim.Op{Kind: hostclaim.KindServing, Exec: "box"}
30 c.Touched.Complete = true
31 return nil
32 })
33 if err != nil {
34 t.Fatal(err)
35 }
36 owner := hostops.RegistrationOwner{Console: "console", Enrollment: "enrollment", Controller: "request"}
37 reg := hostops.Registration{Kind: "wake", Host: host, Owner: owner, AppID: "new-app", Epoch: 1, Incarnation: inc, CreatedAt: at}
38 key := release.SidecarWakeKey("acct", "app", "bot")
39 effect := hostops.Effect{ID: "new-request", OperationID: "new-request", AppID: "new-app", Resource: "control-object:" + key, Action: "wake", Owner: owner, State: "complete", ResultReference: "new-request", Inputs: map[string]string{"key": key, "incarnation": inc, "request_id": "new-request", "admission_epoch": "1", "created_at": at.Format(time.RFC3339Nano)}}
40 a := hostops.AppAdmission{V: 1, AppID: "new-app", Revision: 1, Epoch: 1, State: "open", Registrations: map[string]hostops.Registration{"new-request": reg}, Effects: map[string]hostops.Effect{"new-request": effect}}
41 raw, _ := json.Marshal(a)
42 if _, err = s.Put(ctx, "apps/acct/app/admission.json", raw, ""); err != nil {
43 t.Fatal(err)
44 }
45 rt := registry.Route{Host: host, Kind: "sidecar", Slug: "bot", App: "app", Account: "acct"}
46 mem := newSidecarWakeMemory(true)
47 sidecarWakeChanges(ctx, nil, s, mem)
48 if err = release.TouchSidecarWake(ctx, s, "acct", "app", "bot", "operator", "old", release.SidecarWakeIdentity{AppID: "old-app", Incarnation: inc, RequestID: "old-request"}); err != nil {
49 t.Fatal(err)
50 }
51 moved, _ := sidecarWakeChanges(ctx, []registry.Route{rt}, s, mem)
52 if len(moved) != 0 {
53 t.Fatal("delayed old app request woke replacement")
54 }
55 if err = release.TouchSidecarWake(ctx, s, "acct", "app", "bot", "operator", "new", release.SidecarWakeIdentity{AppID: "new-app", Incarnation: inc, RequestID: "new-request"}); err != nil {
56 t.Fatal(err)
57 }
58 moved, errs := sidecarWakeChanges(ctx, []registry.Route{rt}, s, mem)
59 if len(errs) != 0 || len(moved) != 1 {
60 t.Fatalf("new request: moved=%d errors=%v", len(moved), errs)
61 }
62 captured := mem.identities[host]
63 pub, _, err := ed25519.GenerateKey(nil)
64 if err != nil {
65 t.Fatal(err)
66 }
67 w, err := hostops.NewControlWriter(s, pub, time.Now)
68 if err != nil {
69 t.Fatal(err)
70 }
71 // Registration cleanup is the writer's settle, not a raw admission edit.
72 if err = w.SettleRegistration(ctx, "acct", "app", "new-app", "new-request", time.Now()); err != nil {
73 t.Fatal(err)
74 }
75 if cleaned, err := hostops.ReadAdmission(ctx, s, "acct", "app", "new-app"); err != nil || len(cleaned.Value.Registrations) != 0 {
76 t.Fatalf("registration not settled: %v %+v", err, cleaned.Value.Registrations)
77 }
78 delayed := newSidecarWakeMemory(true)
79 sidecarWakeChanges(ctx, nil, s, delayed)
80 moved, errs = sidecarWakeChanges(ctx, []registry.Route{rt}, s, delayed)
81 if len(errs) != 0 || len(moved) != 1 {
82 t.Fatalf("completed wake lost after registration cleanup: moved=%d errors=%v", len(moved), errs)
83 }
84 obs, err := hostclaim.Read(ctx, s, host)
85 if err != nil {
86 t.Fatal(err)
87 }
88 _, err = hostclaim.Update(ctx, s, obs, func(c *hostclaim.Claim) error {
89 c.Holder.CreatedAt = c.Holder.CreatedAt.Add(time.Hour)
90 c.Holder.Incarnation, _ = hostclaim.Incarnation(c.Holder.Account, c.Holder.App, c.Host, c.Holder.CreatedAt)
91 c.Holder.Location = "instances/" + host + "/" + c.Holder.Incarnation
92 return nil
93 })
94 if err != nil {
95 t.Fatal(err)
96 }
97 if err = verifySidecarWake(ctx, s, rt, captured); err == nil {
98 t.Fatal("queued request survived replacement before dispatch")
99 }
100}
101
102func TestSidecarWakeFlagOffIgnoresEnvironment(t *testing.T) {
103 t.Setenv("CARLOS_HOST_CLAIM", "1")
104 ctx := t.Context()
105 s := release.NewDirStore(t.TempDir())
106 mem := newSidecarWakeMemory(false)
107 sidecarWakeChanges(ctx, nil, s, mem)
108 rt := registry.Route{Host: provision.SidecarHost("bot", "app", "acct"), Kind: "sidecar", Slug: "bot", App: "app", Account: "acct"}
109 if err := release.TouchSidecarWake(ctx, s, "acct", "app", "bot", "operator", "wake"); err != nil {
110 t.Fatal(err)
111 }
112 woke := make(chan registry.Route, 1)
113 moved, errs := sidecarWakeLeg(ctx, []registry.Route{rt}, s, mem, func(r registry.Route) error { woke <- r; return nil })
114 if len(moved) != 1 || len(errs) != 0 {
115 t.Fatalf("flag-off legacy wake: moved=%v errors=%v", moved, errs)
116 }
117 select {
118 case got := <-woke:
119 if got.Host != rt.Host || got.WakeIncarnation != "" {
120 t.Fatalf("legacy wake changed: %+v", got)
121 }
122 case <-time.After(time.Second):
123 t.Fatal("legacy dispatch did not wake")
124 }
125}
126