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 linux
2
3package hostfiles
4
5import (
6 "bytes"
7 "context"
8 "encoding/json"
9 "errors"
10 "os"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/carlosframework/platform/internal/release"
16)
17
18// inheritOverCap writes, under pfx, the manifest a writer from before the
19// path cap could have left: a healthy published file beside an entry whose
20// path is past MaxPathBytes. It returns the manifest's bytes.
21func inheritOverCap(t *testing.T, s release.Store) []byte {
22 t.Helper()
23 ctx := context.Background()
24 p := open(t, s)
25 stage(t, p, "healthy.txt", "fine")
26 commit(t, p)
27 m, _, err := Load(ctx, s, pfx)
28 if err != nil {
29 t.Fatal(err)
30 }
31 m.Seq++
32 m.Entries[pathOfBytes(MaxPathBytes+1)] = Entry{Seq: m.Seq, Skipped: "too-large"}
33 body, err := json.Marshal(m)
34 if err != nil {
35 t.Fatal(err)
36 }
37 _, etag, err := s.Get(ctx, ManifestKey(pfx))
38 if err != nil {
39 t.Fatal(err)
40 }
41 if _, err := s.Put(ctx, ManifestKey(pfx), body, etag); err != nil {
42 t.Fatal(err)
43 }
44 return body
45}
46
47// Fix 5 (Astra confirming P2): a manifest inherited with a path past the
48// cap is refused by Open, terminally, and by every reader, and nothing is
49// committed over it.
50func TestAnInheritedManifestPastTheCapIsRefusedByOpenAndTheReaders(t *testing.T) {
51 ctx := context.Background()
52 s := release.NewDirStore(t.TempDir())
53 before := inheritOverCap(t, s)
54 if p, err := Open(ctx, s, pfx); !errors.Is(err, ErrTooDeep) || p != nil {
55 t.Fatalf("Open of an inherited over-cap manifest: %v", err)
56 }
57 if _, _, err := Restore(ctx, s, pfx, t.TempDir(), nil); !errors.Is(err, ErrTooDeep) {
58 t.Fatalf("Restore: %v", err)
59 }
60 m, _, err := Load(ctx, s, pfx)
61 if err != nil {
62 t.Fatal(err)
63 }
64 if err := ValidateManifest(m, []string{pfx}); !errors.Is(err, ErrTooDeep) {
65 t.Fatalf("ValidateManifest: %v", err)
66 }
67 const to = "instances/shop.test/abc/files/g9/"
68 if _, err := CopyManifest(ctx, s, pfx, to, 0, nil); !errors.Is(err, ErrTooDeep) {
69 t.Fatalf("CopyManifest: %v", err)
70 }
71 if _, _, err := s.Get(ctx, ManifestKey(to)); !errors.Is(err, os.ErrNotExist) {
72 t.Fatalf("CopyManifest wrote its target: %v", err)
73 }
74 after, _, err := s.Get(ctx, ManifestKey(pfx))
75 if err != nil || !bytes.Equal(before, after) {
76 t.Fatalf("the inherited manifest was rewritten: %v", err)
77 }
78}
79
80// Run over a publisher holding such an entry (one continued before Open
81// refused them) stops on its first commit with one line, never retrying
82// at every tick, and commits nothing.
83func TestRunStopsOnAManifestItCannotCommit(t *testing.T) {
84 logs := captureLog(t)
85 s, dir := release.NewDirStore(t.TempDir()), t.TempDir()
86 p := open(t, s)
87 stage(t, p, "healthy.txt", "fine")
88 commit(t, p)
89 before, _, err := s.Get(context.Background(), ManifestKey(pfx))
90 if err != nil {
91 t.Fatal(err)
92 }
93 p.mu.Lock()
94 p.next++
95 p.man.Entries[pathOfBytes(MaxPathBytes+1)] = Entry{Seq: p.next, Skipped: "too-large"}
96 p.mu.Unlock()
97 write(t, dir, "healthy.txt", "changed") // the first scan stages it, and Commit is attempted
98 ctx, cancel := context.WithCancel(context.Background())
99 defer cancel()
100 done := make(chan error, 1)
101 go func() { done <- run(ctx, p, dir, polled, nil) }()
102 select {
103 case err := <-done:
104 if !errors.Is(err, ErrTooDeep) {
105 t.Fatalf("run ended with %v, want ErrTooDeep", err)
106 }
107 case <-time.After(3 * time.Second):
108 t.Fatalf("run kept going on a manifest no commit can write (%d commit lines)", logs.count("commit"))
109 }
110 if got := logs.count("cannot commit"); got != 1 {
111 t.Fatalf("logged %d times, want once", got)
112 }
113 after, _, err := s.Get(context.Background(), ManifestKey(pfx))
114 if err != nil || !bytes.Equal(before, after) {
115 t.Fatalf("something was committed: %v", err)
116 }
117}
118
119// RestorableWithinCap is the readers' rule, exported for the checks made
120// before work that needs a manifest restored (#236): an entry of any kind
121// past the cap, live, tombstoned or skipped, is ErrTooDeep, and every
122// reader refuses the same manifest.
123func TestRestorableWithinCapIsTheReadersRule(t *testing.T) {
124 sha := strings.Repeat("a", 64)
125 for name, e := range map[string]Entry{
126 "live": {Object: pfx + objectKey(1, sha), SHA256: sha, Seq: 1, Size: 1},
127 "tombstone": {Seq: 1, Deleted: true},
128 "skipped": {Seq: 1, Skipped: "too-large"},
129 } {
130 past := Manifest{V: ManifestVersion, Seq: 1, Entries: map[string]Entry{pathOfBytes(MaxPathBytes + 1): e}, Platform: map[string]Entry{}}
131 if err := RestorableWithinCap(past); !errors.Is(err, ErrTooDeep) {
132 t.Fatalf("%s past the cap: %v", name, err)
133 }
134 if err := ValidateManifest(past, []string{pfx}); !errors.Is(err, ErrTooDeep) {
135 t.Fatalf("%s past the cap, ValidateManifest: %v", name, err)
136 }
137 at := Manifest{V: ManifestVersion, Seq: 1, Entries: map[string]Entry{pathOfBytes(MaxPathBytes): e}, Platform: map[string]Entry{}}
138 if err := RestorableWithinCap(at); err != nil {
139 t.Fatalf("%s at the cap: %v", name, err)
140 }
141 }
142}
143