carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build unix
2
3package hostbox
4
5import (
6 "context"
7 "errors"
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/hostwrite"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19func workFixture(t *testing.T) (WorkSource, release.Store, hostclaim.Observed) {
20 t.Helper()
21 ctx := context.Background()
22 dir := t.TempDir()
23 locks := filepath.Join(dir, "locks")
24 for _, p := range []string{locks, filepath.Join(dir, "hostguard")} {
25 if err := os.Mkdir(p, 0770); err != nil {
26 t.Fatal(err)
27 }
28 }
29 store := release.NewDirStore(t.TempDir())
30 obs, err := hostclaim.Read(ctx, store, "shop.example")
31 if err != nil {
32 t.Fatal(err)
33 }
34 obs, err = hostclaim.Update(ctx, store, obs, hostclaim.Adopt(hostclaim.NewHolder{Family: "instance", Key: "apps/acct/app/instances/shop.example.json", Account: "acct", App: "app", Host: "shop.example", Region: "ie"}, "box", nil, nil, "adopt", func() string { return "id" }))
35 if err != nil {
36 t.Fatal(err)
37 }
38 source := WorkSource{Guard: hostguard.Guard{DataDir: dir, LockDir: locks, Claims: hostguard.StoreReader{Store: store}}, Box: "box", Region: "ie", Now: func() time.Time { return time.Unix(100, 0) }}
39 source.Policy = func(_ context.Context, c *hostclaim.Claim, s Selection, _ hostwrite.Identity) error {
40 return Serving(c, s)
41 }
42 source.Local = func(context.Context, *hostclaim.Claim, Selection) error { return nil }
43 if err := source.Guard.Activate(ctx, "shop.example", "box", nil); err != nil {
44 t.Fatal(err)
45 }
46 return source, store, obs
47}
48
49func TestSelectedWorkRejectsChangesAndNeverRecaptures(t *testing.T) {
50 for _, mode := range []string{"phase", "executor", "round-trip", "lease", "held"} {
51 t.Run(mode, func(t *testing.T) {
52 source, store, obs := workFixture(t)
53 localLost, heldChanged := false, false
54 source.Local = func(context.Context, *hostclaim.Claim, Selection) error {
55 if localLost {
56 return ErrUnsupported
57 }
58 return nil
59 }
60 source.Policy = func(_ context.Context, c *hostclaim.Claim, s Selection, i hostwrite.Identity) error {
61 if heldChanged || i.Config != "selected" {
62 return ErrChanged
63 }
64 return Serving(c, s)
65 }
66 ctx := context.Background()
67 work, err := source.Select(ctx, "shop.example", "config")
68 if err != nil {
69 t.Fatal(err)
70 }
71 writes := 0
72 if err := work.With(ctx, hostwrite.Identity{Config: "selected"}, func() error { writes++; return nil }); err != nil {
73 t.Fatal(err)
74 }
75 switch mode {
76 case "phase":
77 _, err = hostclaim.Update(ctx, store, obs, hostclaim.BeginDelete("delete", "test", "etag", time.Unix(1, 0), []string{"ie"}, []string{"box"}, ""))
78 case "executor":
79 _, err = hostclaim.Update(ctx, store, obs, func(c *hostclaim.Claim) error { c.Op.Exec = "other"; c.Op.Participants = []string{"other"}; return nil })
80 case "round-trip":
81 _, err = hostclaim.Update(ctx, store, obs, hostclaim.AdmitWriter(hostclaim.Admission{Box: "box", Mode: "cold", NewGeneration: "next", Region: "ie", PredecessorRegion: "ie"}))
82 if err == nil {
83 err = source.Guard.Activate(ctx, "shop.example", "box", nil)
84 }
85 case "lease":
86 localLost = true
87 case "held":
88 heldChanged = true
89 }
90 if err != nil {
91 t.Fatal(err)
92 }
93 if err := work.With(ctx, hostwrite.Identity{Config: "selected"}, func() error { writes++; return nil }); err == nil {
94 t.Fatal("stale write admitted")
95 }
96 if writes != 1 {
97 t.Fatal(writes)
98 }
99 })
100 }
101}
102
103func TestSelectedWorkNeedsLocalRecordAndProviders(t *testing.T) {
104 source, _, _ := workFixture(t)
105 source.Local = nil
106 if _, err := source.Select(context.Background(), "shop.example", "env"); !errors.Is(err, ErrUnsupported) {
107 t.Fatal(err)
108 }
109 source.Local = func(context.Context, *hostclaim.Claim, Selection) error { return nil }
110 if err := os.Remove(filepath.Join(source.Guard.DataDir, "hostguard", "shop.example.json")); err != nil {
111 t.Fatal(err)
112 }
113 if _, err := source.Select(context.Background(), "shop.example", "env"); !errors.Is(err, hostguard.ErrNoRecord) {
114 t.Fatal(err)
115 }
116}
117
118// Every execution action waits out quiet_after[region], a relocation
119// target's probe spawn among them (§5.8: like a push).
120func TestSelectedWorkExecutionWaitsOutTheQuietWindow(t *testing.T) {
121 source, store, obs := workFixture(t)
122 source.Policy = func(context.Context, *hostclaim.Claim, Selection, hostwrite.Identity) error { return nil }
123 ctx := context.Background()
124 quiet := time.Unix(200, 0)
125 if _, err := hostclaim.Update(ctx, store, obs, func(c *hostclaim.Claim) error {
126 c.QuietAfter = map[string]time.Time{"ie": quiet}
127 return nil
128 }); err != nil {
129 t.Fatal(err)
130 }
131 run := func(now time.Time, action string) error {
132 at := source
133 at.Now = func() time.Time { return now }
134 work, err := at.Select(ctx, "shop.example", action)
135 if err != nil {
136 return err
137 }
138 return work.With(ctx, hostwrite.Identity{}, func() error { return nil })
139 }
140 for _, action := range []string{"probe", "spawn", "install", "restore", "seed", "writer"} {
141 if err := run(quiet, action); !errors.Is(err, ErrChanged) {
142 t.Fatalf("%s inside the quiet window: %v", action, err)
143 }
144 if err := run(quiet.Add(time.Second), action); err != nil {
145 t.Fatalf("%s after the quiet window: %v", action, err)
146 }
147 }
148 if err := run(quiet, "config"); err != nil {
149 t.Fatalf("a non-execution action waited: %v", err)
150 }
151}
152
153// A relocation probe is an execution action: the region's quiet-after
154// window refuses it like spawn (claim Plan 11).
155func TestPlan11QuietAfterRefusesTheProbeAction(t *testing.T) {
156 source, store, obs := workFixture(t)
157 ctx := context.Background()
158 work, err := source.Select(ctx, "shop.example", "probe")
159 if err != nil {
160 t.Fatal(err)
161 }
162 if _, err := hostclaim.Update(ctx, store, obs, func(c *hostclaim.Claim) error {
163 c.QuietAfter = map[string]time.Time{"ie": time.Unix(200, 0)}
164 return nil
165 }); err != nil {
166 t.Fatal(err)
167 }
168 ran := false
169 if err := work.With(ctx, hostwrite.Identity{}, func() error { ran = true; return nil }); !errors.Is(err, ErrChanged) || ran {
170 t.Fatalf("probe ran inside quiet-after: %v %v", err, ran)
171 }
172}
173