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 "errors"
6 "fmt"
7 "io"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14type plan15BoundedStore struct {
15 *release.DirStore
16 oversized, conflict bool
17 read, closed, opens, gets int
18}
19
20func (s *plan15BoundedStore) Get(ctx context.Context, key string) ([]byte, string, error) {
21 if strings.Contains(key, "control/host-origins/") {
22 panic("unbounded node Get")
23 }
24 s.gets++
25 return s.DirStore.Get(ctx, key)
26}
27func (s *plan15BoundedStore) Put(ctx context.Context, key string, b []byte, tag string) (string, error) {
28 if s.conflict && strings.Contains(key, "control/host-origins/") {
29 return "", release.ErrCASConflict
30 }
31 return s.DirStore.Put(ctx, key, b, tag)
32}
33
34type plan15Reader struct {
35 io.Reader
36 store *plan15BoundedStore
37}
38
39func (r plan15Reader) Read(b []byte) (int, error) {
40 n, e := r.Reader.Read(b)
41 r.store.read += n
42 return n, e
43}
44func (r plan15Reader) Close() error { r.store.closed++; return nil }
45func (s *plan15BoundedStore) Open(ctx context.Context, key string) (io.ReadCloser, string, error) {
46 s.opens++
47 if s.oversized {
48 return plan15Reader{strings.NewReader(strings.Repeat("x", 1<<20)), s}, "oversized", nil
49 }
50 return s.DirStore.Open(ctx, key)
51}
52func TestPlan15BoundedReachableAndConflicting(t *testing.T) {
53 for _, prefixed := range []bool{false, true} {
54 for _, conflict := range []bool{false, true} {
55 t.Run(fmt.Sprintf("prefix=%v/conflict=%v", prefixed, conflict), func(t *testing.T) {
56 plan15Enable(t)
57 counter := &plan15BoundedStore{DirStore: release.NewDirStore(t.TempDir())}
58 var s release.Store = counter
59 if prefixed {
60 s = release.NewPrefixStore(release.NewPrefixStore(s, "outer"), "inner")
61 }
62 o := servingClaim(t, t.Context(), s)
63 if !conflict {
64 o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "real", "ie"))
65 }
66 counter.oversized, counter.conflict = true, conflict
67 counter.opens, counter.closed, counter.read = 0, 0, 0
68 var err error
69 if conflict {
70 _, err = Update(t.Context(), s, o, plan15Admit(o, "candidate", "ie"))
71 } else {
72 h := o.Claim.Holder
73 _, err = AdmittedOrigin(t.Context(), s, o, h.Incarnation, h.Location, "ie", "real")
74 }
75 if !errors.Is(err, ErrMalformed) || counter.read != 4097 || counter.closed != 1 || counter.opens != 1 {
76 t.Fatalf("err=%v bytes=%d closes=%d opens=%d", err, counter.read, counter.closed, counter.opens)
77 }
78 })
79 }
80 }
81}
82func TestPlan15CacheBindings(t *testing.T) {
83 plan15Enable(t)
84 s := &plan15BoundedStore{DirStore: release.NewDirStore(t.TempDir())}
85 o := servingClaim(t, t.Context(), s)
86 o = updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "first", "ie"))
87 h := o.Claim.Holder
88 first := OriginTuple{h.Incarnation, h.Location, "ie", "first"}
89 cache := NewOriginProofCache(s)
90 prove := func(obs Observed, set []OriginTuple, want bool) {
91 t.Helper()
92 result, err := cache.Proven(t.Context(), obs, set)
93 if err != nil || result[set[0]] != want {
94 t.Fatalf("%v %v", result, err)
95 }
96 }
97 s.opens, s.gets = 0, 0
98 prove(o, []OriginTuple{first, first}, true)
99 prove(o, []OriginTuple{first}, true)
100 if s.opens != int(o.Claim.Origins.Count) || s.gets != 2 {
101 t.Fatalf("cache opens=%d reads=%d", s.opens, s.gets)
102 }
103 different := first
104 different.Region = "uk"
105 prove(o, []OriginTuple{different}, false)
106 if s.opens != 2*int(o.Claim.Origins.Count) {
107 t.Fatal("tuple set reused")
108 }
109 other := &plan15BoundedStore{DirStore: release.NewDirStore(t.TempDir())}
110 key, _ := Key(o.Host)
111 body, _, err := s.Get(t.Context(), key)
112 if err != nil {
113 t.Fatal(err)
114 }
115 if _, err := other.Put(t.Context(), key, body, ""); err != nil {
116 t.Fatal(err)
117 }
118 if got, err := NewOriginProofCache(other).Proven(t.Context(), o, []OriginTuple{different}); got != nil || err == nil {
119 t.Fatal("foreign store reused")
120 }
121 prove(o, []OriginTuple{first}, true)
122 edited := o
123 c := *o.Claim
124 edited.Claim = &c
125 c.Origins = &OriginHead{Digest: strings.Repeat("f", 64), Count: o.Claim.Origins.Count}
126 if got, err := cache.Proven(t.Context(), edited, []OriginTuple{first}); got != nil || !errors.Is(err, ErrConflict) {
127 t.Fatal("edited head reused")
128 }
129 prove(o, []OriginTuple{first}, true)
130 newer := updateGenerationTest(t, t.Context(), s, o, plan15Admit(o, "second", "ie"))
131 if got, err := cache.Proven(t.Context(), o, []OriginTuple{first}); got != nil || !errors.Is(err, ErrConflict) {
132 t.Fatal("stale observation reused")
133 }
134 s.opens = 0
135 prove(newer, []OriginTuple{first}, true)
136 if s.opens != int(newer.Claim.Origins.Count) {
137 t.Fatal("changed head did not traverse")
138 }
139 // No partial positives escape a failed batch, including a corrupt tail.
140 if err := s.Delete(t.Context(), originKey(o.Host, o.Claim.Origins.Digest)); err != nil {
141 t.Fatal(err)
142 }
143 second := first
144 second.Generation = "second"
145 if got, err := cache.Proven(t.Context(), newer, []OriginTuple{first, second}); got != nil || err == nil {
146 t.Fatal("partial proof escaped")
147 }
148}
149