carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "io"
8 "net/http"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/dbdoor"
14 "github.com/carlosframework/platform/internal/genselect"
15 "github.com/carlosframework/platform/internal/hostclaim"
16 "github.com/superfly/ltx"
17)
18
19func doorLTXHeader(t *testing.T, lo, hi uint64) []byte {
20 t.Helper()
21 h := ltx.Header{Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, PageSize: 4096, MinTXID: ltx.TXID(lo), MaxTXID: ltx.TXID(hi)}
22 b, err := h.MarshalBinary()
23 if err != nil {
24 t.Fatal(err)
25 }
26 return b
27}
28
29func TestDoorPlanHTTPGenerationSelection(t *testing.T) {
30 for _, mode := range []string{"origin", "published", "unfinished", "unfinished-origin", "gap", "missing-pin", "partial-list", "disagreement", "bad-header", "holder-change", "op-change"} {
31 t.Run(mode, func(t *testing.T) {
32 fx := dbFixture(t)
33 model := enableDoorPlan(t, fx)
34 fx.srv.cfg.HomeRegion = "ie"
35 obs, err := hostclaim.Read(t.Context(), fx.rel, dbTestHost)
36 if err != nil {
37 t.Fatal(err)
38 }
39 c := cloneDoorClaim(obs.Claim)
40 loc := c.Holder.Location
41 gen := "live"
42 if mode == "origin" || mode == "unfinished-origin" {
43 gen = ""
44 }
45 c.Holder.Generation = gen
46 base := loc
47 if gen != "" {
48 base += "/g-" + gen
49 }
50 put := func(level int, lo, hi uint64) genselect.File {
51 key := dbVersionKey(base, level, lo, hi)
52 b := doorLTXHeader(t, lo, hi)
53 id := model.Put("data-test", key, b)
54 return genselect.File{Key: key, VersionID: id, Level: level, MinTXID: lo, MaxTXID: hi, Size: int64(len(b))}
55 }
56 root := put(9, 1, 11)
57 cont := put(1, 10, 13)
58 put(0, 11, 11)
59 put(0, 12, 12)
60 if gen != "" {
61 marker := genselect.Marker{Op: gen, Host: dbTestHost, Incarnation: c.Holder.Incarnation, Box: "box", InstalledAt: time.Unix(1, 0).UTC(), Pin: genselect.Pin{SnapshotKey: root.Key, SnapshotVersion: root.VersionID, SnapshotTXID: 11}}
62 raw, err := json.Marshal(marker)
63 if err != nil {
64 t.Fatal(err)
65 }
66 model.Put("data-test", loc+"/generation.json", raw)
67 model.Put("data-test", root.Key, []byte("late duplicate must never be selected"))
68 model.Put("data-test", dbVersionKey(loc, 9, 1, 99), doorLTXHeader(t, 1, 99))
69 }
70 switch mode {
71 case "unfinished", "unfinished-origin":
72 predecessor := gen
73 if predecessor == "" {
74 predecessor = hostclaim.RootGeneration
75 }
76 c.Holder.Generation = "next"
77 c.Holder.Admissions = []hostclaim.AdmissionRecord{{Generation: "next", Predecessor: predecessor, Region: "ie", PredecessorRegion: "ie", Mode: "cold"}}
78 case "gap":
79 model.BareDelete("data-test", cont.Key)
80 model.BareDelete("data-test", dbVersionKey(base, 0, 12, 12))
81 put(0, 13, 13)
82 case "missing-pin":
83 if err := model.DeleteVersion(t.Context(), "data-test", root.Key, root.VersionID); err != nil {
84 t.Fatal(err)
85 }
86 case "partial-list":
87 model.PageSize, model.FailListAfterPages = 1, 1
88 case "disagreement":
89 c.Holder.Generation = "unknown"
90 case "bad-header":
91 model.Put("data-test", dbVersionKey(base, 9, 1, 20), []byte("broken successor"))
92 }
93 writeDoorClaim(t, fx.srv, dbRequest{host: dbTestHost}, c)
94 if mode == "holder-change" || mode == "op-change" {
95 fx.srv.cfg.DBVersions = doorOpenHook{VersionedStore: model, open: func(ctx context.Context, bucket, key, version string) (io.ReadCloser, error) {
96 rc, err := model.OpenVersion(ctx, bucket, key, version)
97 next := cloneDoorClaim(c)
98 if mode == "holder-change" {
99 next.Holder.Session++
100 } else {
101 next.Op.Exec = "changed"
102 }
103 writeDoorClaim(t, fx.srv, dbRequest{host: dbTestHost}, next)
104 return rc, err
105 }}
106 }
107 status, h, body := rawReq(t, "GET", fx.url(dbdoor.ListPath("bab", "titogo", dbTestHost, 9)), fx.member, "")
108 if mode != "origin" && mode != "published" && mode != "unfinished" && mode != "unfinished-origin" {
109 if status != 409 || h.Get("Retry-After") != "1" || strings.Contains(body, "files") || strings.Contains(body, "instances/") || strings.Contains(body, "genselect:") {
110 t.Fatalf("refusal: %d %v %s", status, h, body)
111 }
112 return
113 }
114 if status != 200 {
115 t.Fatalf("list: %d %s", status, body)
116 }
117 var list dbdoor.ListResponse
118 if err := json.Unmarshal([]byte(body), &list); err != nil {
119 t.Fatal(err)
120 }
121 if list.Protocol != 2 || len(list.Files) != 2 {
122 t.Fatalf("list = %+v", list)
123 }
124 for i, want := range []genselect.File{root, cont} {
125 f := list.Files[i]
126 handle, err := verifyDBHandle(fx.srv.dbHandleKey, f.Handle, time.Now())
127 if err != nil || handle.Key != want.Key || handle.VersionID != want.VersionID || handle.Size != want.Size || f.Size != want.Size || f.Level != want.Level || f.MinTXID != want.MinTXID || f.MaxTXID != want.MaxTXID {
128 t.Fatalf("file %d: %+v %+v %v; want %+v", i, f, handle, err, want)
129 }
130 model.Put("data-test", want.Key, []byte("overwritten after listing"))
131 req, err := http.NewRequest("GET", fx.url(dbdoor.GetPath("bab", "titogo", dbTestHost, f.Level, f.MinTXID, f.MaxTXID)), nil)
132 if err != nil {
133 t.Fatal(err)
134 }
135 req.Header.Set("Authorization", "Bearer "+fx.member)
136 req.Header.Set(dbdoor.HandleHeader, f.Handle)
137 req.Header.Set("Range", "bytes=2-7")
138 res, err := http.DefaultClient.Do(req)
139 if err != nil {
140 t.Fatal(err)
141 }
142 got, err := io.ReadAll(res.Body)
143 res.Body.Close()
144 if err != nil || res.StatusCode != 206 || !bytes.Equal(got, doorLTXHeader(t, want.MinTXID, want.MaxTXID)[2:8]) || res.Header.Get(dbdoor.HandleHeader) != f.Handle {
145 t.Fatalf("get: %d %x %v", res.StatusCode, got, err)
146 }
147 if err := model.DeleteVersion(t.Context(), "data-test", want.Key, want.VersionID); err != nil {
148 t.Fatal(err)
149 }
150 res, err = http.DefaultClient.Do(req)
151 if err != nil {
152 t.Fatal(err)
153 }
154 res.Body.Close()
155 if res.StatusCode != 404 {
156 t.Fatalf("missing version fell back: %d", res.StatusCode)
157 }
158 }
159 if fx.opens.Load() != 0 {
160 t.Fatal("opened unversioned replica")
161 }
162 })
163 }
164}
165