carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "sync"
7 "sync/atomic"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/hostwrite"
11)
12
13type pausedReleaseWork struct {
14 pauseAt int
15 calls atomic.Int64
16 valid atomic.Bool
17 once sync.Once
18 entered chan struct{}
19 resume chan struct{}
20}
21
22func newPausedReleaseWork(pauseAt int) *pausedReleaseWork {
23 w := &pausedReleaseWork{pauseAt: pauseAt, entered: make(chan struct{}), resume: make(chan struct{})}
24 w.valid.Store(true)
25 return w
26}
27
28func (w *pausedReleaseWork) With(_ context.Context, id hostwrite.Identity, fn func() error) error {
29 if id.Release == "" {
30 return errors.New("missing release identity")
31 }
32 call := int(w.calls.Add(1))
33 if call == w.pauseAt {
34 w.once.Do(func() { close(w.entered) })
35 <-w.resume
36 }
37 if !w.valid.Load() {
38 return errors.New("release selection changed")
39 }
40 return fn()
41}
42
43type oneReleaseSelector struct{ work hostwrite.Work }
44
45func (s oneReleaseSelector) Select(context.Context, string, string) (hostwrite.Work, error) {
46 return s.work, nil
47}
48
49func TestPassStaleSelectionBeforePointLiveMakesNoHostEffect(t *testing.T) {
50 s := NewDirStore(t.TempDir())
51 priv, pub := testKeys(t)
52 opt := t.TempDir()
53 reg := testRegistry(t)
54 t.Cleanup(func() { reg.Close() })
55 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build"))
56 if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
57 t.Fatal(err)
58 }
59 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
60 w := newPausedReleaseWork(1)
61 restarts := atomic.Int64{}
62 done := make(chan error, 1)
63 go func() {
64 done <- Pass(t.Context(), reg, s, pub, opt, func(string) error { restarts.Add(1); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{work: w}}))
65 }()
66 <-w.entered
67 w.valid.Store(false)
68 close(w.resume)
69 if err := <-done; err != nil {
70 t.Fatal(err)
71 }
72 if live, err := LiveVersion(opt, "app.example"); err != nil || live != "" {
73 t.Fatalf("live = %q, %v", live, err)
74 }
75 rt, err := reg.Get("app.example")
76 if err != nil || rt.Version != "" || restarts.Load() != 0 {
77 t.Fatalf("route=%+v err=%v restarts=%d", rt, err, restarts.Load())
78 }
79}
80
81func TestPassRollbackReusesOriginalSelection(t *testing.T) {
82 s := NewDirStore(t.TempDir())
83 priv, pub := testKeys(t)
84 opt := t.TempDir()
85 reg := testRegistry(t)
86 t.Cleanup(func() { reg.Close() })
87 for _, version := range []string{"v1", "v2"} {
88 shipRelease(t, s, "bab", "myapp", version, adoptPlatform, []byte(version))
89 }
90 if err := StageArtifact(opt, "bab", "v1", "myapp", []byte("v1")); err != nil {
91 t.Fatal(err)
92 }
93 if err := PointLive(opt, "app.example", "bab", "v1", "myapp"); err != nil {
94 t.Fatal(err)
95 }
96 if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v2"); err != nil {
97 t.Fatal(err)
98 }
99 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
100 w := newPausedReleaseWork(3)
101 done := make(chan error, 1)
102 go func() {
103 done <- Pass(t.Context(), reg, s, pub, opt, func(string) error { return errors.New("unexpected legacy restart") }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{work: w}}), WithSelectedRestarts(func(context.Context, hostwrite.Selection) error { return errors.New("restart failed") }, nil))
104 }()
105 <-w.entered
106 w.valid.Store(false)
107 close(w.resume)
108 if err := <-done; err != nil {
109 t.Fatal(err)
110 }
111 if live, err := LiveVersion(opt, "app.example"); err != nil || live != "v2" {
112 t.Fatalf("stale rollback changed live link: %q, %v", live, err)
113 }
114}
115
116func TestPassEnabledMissingHostWriteProviderRefusesBeforeChannelRead(t *testing.T) {
117 reg := testRegistry(t)
118 t.Cleanup(func() { reg.Close() })
119 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
120 err := Pass(t.Context(), reg, nil, nil, t.TempDir(), nil, WithHostWrites(hostwrite.Config{Enabled: true}))
121 if !errors.Is(err, hostwrite.ErrUnavailable) {
122 t.Fatalf("Pass = %v", err)
123 }
124}
125
126func TestPassClaimRefusesHostnameOnlyRestart(t *testing.T) {
127 s := NewDirStore(t.TempDir())
128 priv, pub := testKeys(t)
129 reg := testRegistry(t)
130 t.Cleanup(func() { reg.Close() })
131 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build"))
132 if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
133 t.Fatal(err)
134 }
135 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
136 w := newPausedReleaseWork(0)
137 restarts := 0
138 if err := Pass(t.Context(), reg, s, pub, t.TempDir(), func(string) error { restarts++; return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}})); err != nil {
139 t.Fatal(err)
140 }
141 rt, err := reg.Get("app.example")
142 if err != nil {
143 t.Fatal(err)
144 }
145 if restarts != 0 || rt.Version != "" {
146 t.Fatalf("hostname-only restart accepted: restarts=%d version=%q", restarts, rt.Version)
147 }
148}
149
150func TestPassClaimSelectedRestartBoundary(t *testing.T) {
151 for _, stale := range []bool{false, true} {
152 t.Run(map[bool]string{false: "current", true: "successor"}[stale], func(t *testing.T) {
153 s := NewDirStore(t.TempDir())
154 priv, pub := testKeys(t)
155 reg := testRegistry(t)
156 t.Cleanup(func() { reg.Close() })
157 shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build"))
158 if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil {
159 t.Fatal(err)
160 }
161 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
162 w := newPausedReleaseWork(0)
163 effects := 0
164 restart := func(ctx context.Context, selected hostwrite.Selection) error {
165 if selected.Work != w || selected.Host != "app.example" || selected.Identity.Release == "" {
166 t.Fatalf("lost selection: %+v", selected)
167 }
168 if stale {
169 w.valid.Store(false)
170 }
171 return selected.With(ctx, func() error { effects++; return nil })
172 }
173 if err := Pass(t.Context(), reg, s, pub, t.TempDir(), func(string) error { t.Fatal("legacy callback"); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}}), WithSelectedRestarts(restart, nil)); err != nil {
174 t.Fatal(err)
175 }
176 rt, err := reg.Get("app.example")
177 if err != nil {
178 t.Fatal(err)
179 }
180 if stale && (effects != 0 || rt.Version != "") || !stale && (effects != 1 || rt.Version != "v1") {
181 t.Fatalf("effects=%d version=%s", effects, rt.Version)
182 }
183 })
184 }
185}
186
187func TestPassClaimRollbackStartRetainsSelection(t *testing.T) {
188 s := NewDirStore(t.TempDir())
189 priv, pub := testKeys(t)
190 reg := testRegistry(t)
191 t.Cleanup(func() { reg.Close() })
192 opt := t.TempDir()
193 for _, v := range []string{"v1", "v2"} {
194 shipRelease(t, s, "bab", "myapp", v, adoptPlatform, []byte(v))
195 }
196 if err := StageArtifact(opt, "bab", "v1", "myapp", []byte("v1")); err != nil {
197 t.Fatal(err)
198 }
199 if err := PointLive(opt, "app.example", "bab", "v1", "myapp"); err != nil {
200 t.Fatal(err)
201 }
202 if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v2"); err != nil {
203 t.Fatal(err)
204 }
205 addRoute(t, reg, "app.example", "myapp", "canary/x", false)
206 w := newPausedReleaseWork(0)
207 var first hostwrite.Selection
208 starts, callbacks := 0, 0
209 restart := func(ctx context.Context, s hostwrite.Selection) error {
210 first = s
211 return s.With(ctx, func() error { return errors.New("restart failed") })
212 }
213 start := func(ctx context.Context, s hostwrite.Selection) error {
214 callbacks++
215 if s.Work != first.Work || s.Identity != first.Identity || s.Host != first.Host {
216 t.Fatal("rollback recaptured authority")
217 }
218 w.valid.Store(false)
219 return s.With(ctx, func() error { starts++; return nil })
220 }
221 if err := Pass(t.Context(), reg, s, pub, opt, func(string) error { t.Fatal("legacy restart"); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}}), WithSelectedRestarts(restart, start), WithStartUnit(func(string) error { t.Fatal("legacy start"); return nil })); err != nil {
222 t.Fatal(err)
223 }
224 if callbacks != 1 || starts != 0 {
225 t.Fatalf("callbacks=%d starts=%d", callbacks, starts)
226 }
227 if live, err := LiveVersion(opt, "app.example"); err != nil || live != "v1" {
228 t.Fatalf("rollback=%s %v", live, err)
229 }
230}
231