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 unix
2
3package hostbox
4
5import (
6 "context"
7 "encoding/json"
8 "errors"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/genselect"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 "github.com/carlosframework/platform/internal/lease/leasetest"
16 "github.com/superfly/ltx"
17)
18
19// restorePlan is a restore plan whose one selected file is the snapshot
20// [1, max] of generation gen ("" is the location's own files) under
21// location: what Select hands a loss admission.
22func restorePlan(location, gen string, max uint64) *genselect.Plan {
23 base := strings.TrimSuffix(location, "/") + "/"
24 if gen != "" && gen != hostclaim.RootGeneration {
25 base += "g-" + gen + "/"
26 }
27 f := genselect.File{Key: base + "0009/" + ltx.FormatFilename(1, ltx.TXID(max)), VersionID: "v1", Level: 9, MinTXID: 1, MaxTXID: max, Size: 100}
28 g := gen
29 if g == "" {
30 g = hostclaim.RootGeneration
31 }
32 return &genselect.Plan{Generation: g, Files: []genselect.File{f}, Root: &f, MaxTXID: max}
33}
34
35// moves Task 4 (platform-1e's ruling on #236): a loss admission's restore
36// point is consistent with its own plan: the admission's RemoteMaxTXID is
37// the plan's maximum, which genselect.GenerationMax reads from the plan's
38// selected files of its own generation, never a fresh listing. A plan that
39// carries only a maximum, or whose files do not reach it, is refused before
40// the loss sources run and before the claim CAS; no bucket is read, so a
41// writer with none still admits a consistent plan.
42func TestLossRestorePointIsTheGenerationMax(t *testing.T) {
43 for _, path := range []string{"declared", "fenced"} {
44 for _, mode := range []string{"only a maximum", "files short of it", "another input", "consistent, no bucket"} {
45 t.Run(path+"/"+mode, func(t *testing.T) {
46 _, store, o := workFixture(t)
47 o, err := hostclaim.Update(t.Context(), store, o, func(c *hostclaim.Claim) error {
48 c.Op = hostclaim.Op{Kind: hostclaim.KindRestoring, ID: "op", Phase: hostclaim.PhaseProbing, Exec: "box", Participants: []string{"box"}, Intent: map[string]string{"target": "target"}}
49 return nil
50 })
51 if err != nil {
52 t.Fatal(err)
53 }
54 w := Writer{Claims: store, Box: "box", Region: "ie"}
55 in := hostclaim.Admission{Box: "box", Mode: "cold", Region: "ie", RemoteMaxTXID: 3}
56 baseline := restorePlan(o.Claim.Holder.Location, "src", 3)
57 switch mode {
58 case "only a maximum":
59 baseline = &genselect.Plan{Generation: "src", MaxTXID: 3}
60 case "files short of it":
61 baseline = restorePlan(o.Claim.Holder.Location, "src", 2)
62 baseline.MaxTXID = 3
63 case "another input":
64 in.RemoteMaxTXID = 4
65 }
66 err = w.restorePoint(t.Context(), o, in, baseline)
67 if mode == "consistent, no bucket" {
68 if err != nil {
69 t.Fatalf("a consistent plan was refused: %v", err)
70 }
71 return
72 }
73 if !errors.Is(err, ErrChanged) {
74 t.Fatalf("refused with %v", err)
75 }
76 sources := 0
77 w.Loss = &LossAdmission{PublicKey: make([]byte, 32), Owner: &plan12LossOwner{}, Sources: func(context.Context, hostclaim.Observed, hostclaim.LossRecord, hostclaim.Admission, *genselect.Plan) ([]hostclaim.ValidatedWriteSource, error) {
78 sources++
79 return nil, nil
80 }}
81 if path == "declared" {
82 _, err = w.admitLoss(t.Context(), o, in, baseline, "loss_1")
83 } else {
84 _, err = w.admitFencedLoss(t.Context(), o, in, baseline, hostclaim.LossRecord{})
85 }
86 if !errors.Is(err, ErrChanged) || sources != 0 {
87 t.Fatalf("%s: %v, the loss sources ran %d times", path, err, sources)
88 }
89 })
90 }
91 }
92}
93
94// Fix round 1, item 2: the restore point honours the authorized pin, as
95// Select does. An ordinary selection whose only usable root is the marker's
96// pinned snapshot, surviving only as a historical version (its current
97// version a retention tombstone), is accepted at the pin's maximum.
98func TestLossRestorePointHonoursThePinnedRoot(t *testing.T) {
99 _, store, o := workFixture(t)
100 o, err := hostclaim.Update(t.Context(), store, o, func(c *hostclaim.Claim) error {
101 c.Holder.Generation = "src"
102 c.Op = hostclaim.Op{Kind: hostclaim.KindRestoring, ID: "op", Phase: hostclaim.PhaseProbing, Exec: "box", Participants: []string{"box"}, Intent: map[string]string{"target": "target"}}
103 return nil
104 })
105 if err != nil {
106 t.Fatal(err)
107 }
108 h := o.Claim.Holder
109 data := leasetest.NewVersionedBucket()
110 data.AddBucket("data")
111 header, err := (&ltx.Header{Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, PageSize: 4096, MinTXID: 1, MaxTXID: 5}).MarshalBinary()
112 if err != nil {
113 t.Fatal(err)
114 }
115 root := strings.TrimSuffix(h.Location, "/") + "/g-src/0009/" + ltx.FormatFilename(1, 5)
116 version := data.Put("data", root, header)
117 marker, err := json.Marshal(genselect.Marker{Op: "src", Incarnation: h.Incarnation, Host: o.Host, Box: "box", InstalledAt: time.Unix(1, 0).UTC(),
118 Pin: genselect.Pin{SnapshotKey: root, SnapshotVersion: version, SnapshotTXID: 5}})
119 if err != nil {
120 t.Fatal(err)
121 }
122 data.Put("data", strings.TrimSuffix(h.Location, "/")+"/generation.json", marker)
123 data.BareDelete("data", root)
124 w := Writer{Claims: store, Box: "box", Region: "ie", Bucket: "data", Data: data}
125 plan, err := w.Baseline(t.Context(), o)
126 if err != nil {
127 t.Fatal(err)
128 }
129 if plan.Root == nil || plan.Root.VersionID != version || plan.MaxTXID != 5 {
130 t.Fatalf("the selection is not the pinned historical root: %+v", plan)
131 }
132 if err := w.restorePoint(t.Context(), o, hostclaim.Admission{RemoteMaxTXID: plan.MaxTXID}, plan); err != nil {
133 t.Fatalf("a valid pinned restore point was refused: %v", err)
134 }
135}
136