//go:build linux package hostfiles import ( "bytes" "context" "encoding/json" "errors" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/release" ) // inheritOverCap writes, under pfx, the manifest a writer from before the // path cap could have left: a healthy published file beside an entry whose // path is past MaxPathBytes. It returns the manifest's bytes. func inheritOverCap(t *testing.T, s release.Store) []byte { t.Helper() ctx := context.Background() p := open(t, s) stage(t, p, "healthy.txt", "fine") commit(t, p) m, _, err := Load(ctx, s, pfx) if err != nil { t.Fatal(err) } m.Seq++ m.Entries[pathOfBytes(MaxPathBytes+1)] = Entry{Seq: m.Seq, Skipped: "too-large"} body, err := json.Marshal(m) if err != nil { t.Fatal(err) } _, etag, err := s.Get(ctx, ManifestKey(pfx)) if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, ManifestKey(pfx), body, etag); err != nil { t.Fatal(err) } return body } // Fix 5 (Astra confirming P2): a manifest inherited with a path past the // cap is refused by Open, terminally, and by every reader, and nothing is // committed over it. func TestAnInheritedManifestPastTheCapIsRefusedByOpenAndTheReaders(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) before := inheritOverCap(t, s) if p, err := Open(ctx, s, pfx); !errors.Is(err, ErrTooDeep) || p != nil { t.Fatalf("Open of an inherited over-cap manifest: %v", err) } if _, _, err := Restore(ctx, s, pfx, t.TempDir(), nil); !errors.Is(err, ErrTooDeep) { t.Fatalf("Restore: %v", err) } m, _, err := Load(ctx, s, pfx) if err != nil { t.Fatal(err) } if err := ValidateManifest(m, []string{pfx}); !errors.Is(err, ErrTooDeep) { t.Fatalf("ValidateManifest: %v", err) } const to = "instances/shop.test/abc/files/g9/" if _, err := CopyManifest(ctx, s, pfx, to, 0, nil); !errors.Is(err, ErrTooDeep) { t.Fatalf("CopyManifest: %v", err) } if _, _, err := s.Get(ctx, ManifestKey(to)); !errors.Is(err, os.ErrNotExist) { t.Fatalf("CopyManifest wrote its target: %v", err) } after, _, err := s.Get(ctx, ManifestKey(pfx)) if err != nil || !bytes.Equal(before, after) { t.Fatalf("the inherited manifest was rewritten: %v", err) } } // Run over a publisher holding such an entry (one continued before Open // refused them) stops on its first commit with one line, never retrying // at every tick, and commits nothing. func TestRunStopsOnAManifestItCannotCommit(t *testing.T) { logs := captureLog(t) s, dir := release.NewDirStore(t.TempDir()), t.TempDir() p := open(t, s) stage(t, p, "healthy.txt", "fine") commit(t, p) before, _, err := s.Get(context.Background(), ManifestKey(pfx)) if err != nil { t.Fatal(err) } p.mu.Lock() p.next++ p.man.Entries[pathOfBytes(MaxPathBytes+1)] = Entry{Seq: p.next, Skipped: "too-large"} p.mu.Unlock() write(t, dir, "healthy.txt", "changed") // the first scan stages it, and Commit is attempted ctx, cancel := context.WithCancel(context.Background()) defer cancel() done := make(chan error, 1) go func() { done <- run(ctx, p, dir, polled, nil) }() select { case err := <-done: if !errors.Is(err, ErrTooDeep) { t.Fatalf("run ended with %v, want ErrTooDeep", err) } case <-time.After(3 * time.Second): t.Fatalf("run kept going on a manifest no commit can write (%d commit lines)", logs.count("commit")) } if got := logs.count("cannot commit"); got != 1 { t.Fatalf("logged %d times, want once", got) } after, _, err := s.Get(context.Background(), ManifestKey(pfx)) if err != nil || !bytes.Equal(before, after) { t.Fatalf("something was committed: %v", err) } } // RestorableWithinCap is the readers' rule, exported for the checks made // before work that needs a manifest restored (#236): an entry of any kind // past the cap, live, tombstoned or skipped, is ErrTooDeep, and every // reader refuses the same manifest. func TestRestorableWithinCapIsTheReadersRule(t *testing.T) { sha := strings.Repeat("a", 64) for name, e := range map[string]Entry{ "live": {Object: pfx + objectKey(1, sha), SHA256: sha, Seq: 1, Size: 1}, "tombstone": {Seq: 1, Deleted: true}, "skipped": {Seq: 1, Skipped: "too-large"}, } { past := Manifest{V: ManifestVersion, Seq: 1, Entries: map[string]Entry{pathOfBytes(MaxPathBytes + 1): e}, Platform: map[string]Entry{}} if err := RestorableWithinCap(past); !errors.Is(err, ErrTooDeep) { t.Fatalf("%s past the cap: %v", name, err) } if err := ValidateManifest(past, []string{pfx}); !errors.Is(err, ErrTooDeep) { t.Fatalf("%s past the cap, ValidateManifest: %v", name, err) } at := Manifest{V: ManifestVersion, Seq: 1, Entries: map[string]Entry{pathOfBytes(MaxPathBytes): e}, Platform: map[string]Entry{}} if err := RestorableWithinCap(at); err != nil { t.Fatalf("%s at the cap: %v", name, err) } } }