carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "context"
5 "errors"
6 "github.com/carlosframework/platform/internal/registry"
7 "net/http"
8 "net/http/httptest"
9 "testing"
10 "time"
11)
12
13func TestSeedExclusionWaitsForExistingPreparation(t *testing.T) {
14 t.Parallel()
15 for _, kind := range []string{"request", "timer", "stop"} {
16 t.Run(kind, func(t *testing.T) {
17 a := &Service{entries: map[string]*actEntry{}}
18 pending := make(chan struct{})
19 switch kind {
20 case "request":
21 a.entries["b.example"] = &actEntry{state: actWaking, ready: pending, wakeCancel: func() { t.Error("canceled existing wake") }}
22 case "timer":
23 a.tokenPreparations = map[string]chan struct{}{"b.example": pending}
24 case "stop":
25 a.entries["b.example"] = &actEntry{state: actStopping, stopped: pending}
26 }
27 ctx, cancel := context.WithTimeout(t.Context(), 30*time.Millisecond)
28 defer cancel()
29 if _, err := a.AcquireSeedExclusion(ctx, "b.example", nil); !errors.Is(err, context.DeadlineExceeded) {
30 t.Fatalf("did not wait: %v", err)
31 }
32 if a.fences["b.example"] != 0 {
33 t.Fatal("fenced the existing preparation")
34 }
35 a.mu.Lock()
36 delete(a.entries, "b.example")
37 delete(a.tokenPreparations, "b.example")
38 close(pending)
39 a.mu.Unlock()
40 token, err := a.AcquireSeedExclusion(t.Context(), "b.example", nil)
41 if err != nil {
42 t.Fatal(err)
43 }
44 if err := token.Check("b.example"); err != nil {
45 t.Fatal(err)
46 }
47 for _, serve := range []func(http.ResponseWriter, *http.Request, registry.Route, http.Handler) bool{a.ServeHTTP, a.ServeScheduled} {
48 response := httptest.NewRecorder()
49 called := false
50 handled := serve(response, httptest.NewRequest("GET", "http://b.example/", nil), registry.Route{Host: "b.example", Kind: "instance", Hibernate: true}, http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called = true }))
51 if !handled || response.Code != 503 || called {
52 t.Fatalf("request escaped exclusion: %v %d %v", handled, response.Code, called)
53 }
54 }
55 if _, err := a.PrepareScheduledToken("b.example", func() (string, error) { t.Fatal("timer entered seed"); return "", nil }); !errors.Is(err, ErrFenced) {
56 t.Fatal(err)
57 }
58 if _, err := a.PrepareScheduledToken("a.example", func() (string, error) { return "ok", nil }); err != nil {
59 t.Fatal("other host blocked", err)
60 }
61 if _, err := a.AcquireSeedExclusion(t.Context(), "b.example", nil); !errors.Is(err, ErrFenceHeld) {
62 t.Fatal("duplicate accepted", err)
63 }
64 again, err := a.AcquireSeedExclusion(t.Context(), "b.example", token)
65 if err != nil || again != token {
66 t.Fatal("unsettled owner could not resume", err)
67 }
68 if err := token.Release(); err != nil {
69 t.Fatal(err)
70 }
71 if err := token.Check("b.example"); !errors.Is(err, ErrFenceHeld) {
72 t.Fatal("stale token accepted", err)
73 }
74 })
75 }
76}
77
78func TestSeedExclusionRefusesForeignFenceAndToken(t *testing.T) {
79 a := &Service{fences: map[string]uint64{"b.example": 7}}
80 if _, err := a.AcquireSeedExclusion(t.Context(), "b.example", nil); !errors.Is(err, ErrFenceHeld) {
81 t.Fatal(err)
82 }
83 b := &Service{}
84 tok, err := b.AcquireSeedExclusion(t.Context(), "b.example", nil)
85 if err != nil {
86 t.Fatal(err)
87 }
88 if _, err := a.AcquireSeedExclusion(t.Context(), "b.example", tok); !errors.Is(err, ErrFenceHeld) {
89 t.Fatal(err)
90 }
91 if err := tok.Check("a.example"); !errors.Is(err, ErrFenceHeld) {
92 t.Fatal(err)
93 }
94}
95