carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestPlan11AutomaticEvidenceRetainsAndStrictDecodes(t *testing.T) {
14 ctx := context.Background()
15 s := release.NewDirStore(t.TempDir())
16 o := servingClaim(t, ctx, s)
17 fence := FenceRef{Enrollment: strings.Repeat("a", 32), Key: "control/box-fences/" + strings.Repeat("a", 32) + ".json", SHA256: strings.Repeat("b", 64)}
18 loss := AutomaticLoss{Box: "box-a", Enrollment: fence.Enrollment, Fence: fence, ObservedAt: "2026-10-09T10:00:00Z", Op: "reloc", Kind: KindRelocating, Phase: "releasing", Role: "source", Incarnation: o.Claim.Holder.Incarnation, Stamp: 1, Timers: TimersUnknown}
19 loss.ID = automaticLossID(loss.Op, loss.Box, fence)
20 o, err := Update(ctx, s, o, func(c *Claim) error {
21 c.EvidenceRefs = []AutomaticLoss{loss}
22 c.LossRefs = []string{loss.ID}
23 return nil
24 })
25 if err != nil {
26 t.Fatal(err)
27 }
28 o, err = Update(ctx, s, o, func(c *Claim) error {
29 c.Op = Op{Kind: KindParking, ID: "retire"}
30 return FreeAfterPark("retire")(c)
31 })
32 if err != nil {
33 t.Fatal(err)
34 }
35 read, err := Read(ctx, s, testHost)
36 if err != nil {
37 t.Fatal(err)
38 }
39 if len(read.Claim.EvidenceRefs) != 1 || read.Claim.EvidenceRefs[0].ID != loss.ID {
40 t.Fatal("free discarded loss")
41 }
42 nh := newHolder()
43 nh.CreatedAt = nh.CreatedAt.Add(time.Hour)
44 successorRecord, _ := json.Marshal(map[string]any{"host": nh.Host, "account": nh.Account, "app": nh.App, "region": nh.RecordRegion, "created_at": nh.CreatedAt})
45 o, err = Update(ctx, s, o, BeginCreate(nh, successorRecord, "successor", "console", nh.CreatedAt))
46 if err != nil || len(o.Claim.EvidenceRefs) != 1 || o.Claim.EvidenceRefs[0].Incarnation != loss.Incarnation {
47 t.Fatal("successor discarded prior loss", err)
48 }
49 read = o
50 for name, mutate := range map[string]func(*AutomaticLoss){
51 "time": func(l *AutomaticLoss) { l.ObservedAt = "bad" },
52 "identity": func(l *AutomaticLoss) { l.ID = "auto-" + strings.Repeat("c", 32) },
53 "enrollment": func(l *AutomaticLoss) { l.Enrollment = strings.Repeat("c", 32) },
54 "role": func(l *AutomaticLoss) { l.Role = "observer" },
55 } {
56 _, err := Update(ctx, s, o, func(c *Claim) error { mutate(&c.EvidenceRefs[0]); return nil })
57 if err == nil {
58 t.Fatal("malformed loss accepted", name)
59 }
60 }
61 raw, _ := json.Marshal(read.Claim)
62 raw = []byte(strings.Replace(string(raw), `"observed_at":`, `"unknown":true,"observed_at":`, 1))
63 key, _ := Key(testHost)
64 if _, err := s.Put(ctx, key, raw, read.ETag); err != nil {
65 t.Fatal(err)
66 }
67 if _, err := Read(ctx, s, testHost); err == nil {
68 t.Fatal("unknown evidence field accepted")
69 }
70}
71
72func TestPlan11RelocationReleaseIsStopOnly(t *testing.T) {
73 ctx := context.Background()
74 s := release.NewDirStore(t.TempDir())
75 o := servingClaim(t, ctx, s)
76 ob := Obligation{ID: "relocation-release", Op: "reloc", Kind: "relocation-release", Region: "ie", Box: "box-a", Owner: "region:ie", Retirement: "installation-1"}
77 next, err := Update(ctx, s, o, func(c *Claim) error { c.Obligations = []Obligation{ob}; return nil })
78 if err != nil {
79 t.Fatal(err)
80 }
81 for name, mut := range map[string]func(*Obligation){
82 "park": func(o *Obligation) {
83 o.Park = &ParkScope{Location: "instances/" + testHost, Incarnation: next.Claim.Holder.Incarnation, EvidenceAt: time.Now(), RequestLimit: time.Second}
84 },
85 "owner": func(o *Obligation) { o.Owner = "box:box-a" },
86 "box": func(o *Obligation) { o.Box = "" },
87 "generation": func(o *Obligation) { o.Generation = "g" },
88 } {
89 _, err := Update(ctx, s, next, func(c *Claim) error { mut(&c.Obligations[0]); return nil })
90 if err == nil {
91 t.Fatal("invalid release accepted", name)
92 }
93 }
94}
95
96func TestPlan11QueueReleaseIsInstallationBound(t *testing.T) {
97 ctx := context.Background()
98 s := release.NewDirStore(t.TempDir())
99 o := servingClaim(t, ctx, s)
100 o, err := Update(ctx, s, o, func(c *Claim) error {
101 c.Op = Op{Kind: KindRelocating, ID: "reloc", Phase: "releasing", Exec: "box-a", Intent: map[string]string{"source_box": "box-a", "region": "ie"}}
102 return QueueRelocationRelease("reloc", "box-a", "ie", "install-1")(c)
103 })
104 if err != nil {
105 t.Fatal(err)
106 }
107 if len(o.Claim.Obligations) != 1 || o.Claim.Obligations[0].Owner != "region:ie" {
108 t.Fatal("no stop-only chore")
109 }
110 if err := QueueRelocationRelease("reloc", "box-a", "ie", "install-1")(o.Claim); err != nil {
111 t.Fatal(err)
112 }
113 if len(o.Claim.Obligations) != 1 {
114 t.Fatal("retry duplicated release")
115 }
116 if err := QueueRelocationRelease("reloc", "box-a", "ie", "different-install")(o.Claim); err == nil {
117 t.Fatal("rebound release")
118 }
119 if err := QueueRelocationRelease("reloc", "box-b", "ie", "install-1")(o.Claim); err == nil {
120 t.Fatal("wrong executor")
121 }
122}
123