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 "crypto/sha256"
8 "encoding/hex"
9 "encoding/json"
10 "github.com/carlosframework/platform/internal/genselect"
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/termfence"
13 "testing"
14 "time"
15)
16
17type plan12FenceLossAuthority struct{}
18
19func (plan12FenceLossAuthority) ValidateFence(context.Context, termfence.Record) (hostclaim.FenceBinding, error) {
20 return hostclaim.FenceBinding{Region: "uk", RosterRevision: 1, RosterETag: "roster"}, nil
21}
22func TestPlan12FencedTargetLossMaximum(t *testing.T) {
23 for _, mode := range []string{"ok", "missing-validator", "wrong-source", "bypass"} {
24 t.Run(mode, func(t *testing.T) {
25 _, s, o := workFixture(t)
26 now := time.Now().UTC()
27 record := termfence.Record{V: 1, Box: "target", Enrollment: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", State: "terminated", ObservedAt: now.Format(time.RFC3339Nano)}
28 raw, _ := json.Marshal(record)
29 sum := sha256.Sum256(raw)
30 ref := hostclaim.FenceRef{Enrollment: record.Enrollment, Key: "control/box-fences/" + record.Enrollment + ".json", SHA256: hex.EncodeToString(sum[:])}
31 if _, err := s.Put(t.Context(), ref.Key, raw, ""); err != nil {
32 t.Fatal(err)
33 }
34 var err error
35 o, err = hostclaim.Update(t.Context(), s, o, func(c *hostclaim.Claim) error {
36 c.Op = hostclaim.Op{Kind: hostclaim.KindLanding, ID: "move", Phase: hostclaim.PhaseProbing, Exec: "target", Participants: []string{"box", "target"}, Intent: map[string]string{"source": "box", "target": "target", "to": "uk", "target_enrollment": record.Enrollment}, Journal: map[string]string{hostclaim.JournalSourceStamp: "1", hostclaim.JournalSrcGen: "src"}}
37 c.Holder.Session, c.Holder.Stamp, c.Holder.WriterBox, c.Holder.Generation = 2, 2, "target", "target-gen"
38 c.Holder.Admissions = []hostclaim.AdmissionRecord{{Session: 2, Box: "target", Generation: "target-gen", Region: "uk", Mode: "cold"}}
39 return nil
40 })
41 if err != nil {
42 t.Fatal(err)
43 }
44 fence, err := hostclaim.VerifyMoveTargetFence(t.Context(), s, o, ref, plan12FenceLossAuthority{})
45 if err != nil {
46 t.Fatal(err)
47 }
48
49 // Admission consumes a previously grant-released claim. The grant itself
50 // is exercised in hostops.TestPlan12RecoveryFenceGrant.
51 o, err = hostclaim.Update(t.Context(), s, o, func(c *hostclaim.Claim) error {
52 raw, _ := json.Marshal(ref)
53 sum := sha256.Sum256([]byte(c.Op.ID + "\x00target\x00" + ref.Enrollment + "\x00" + ref.SHA256))
54 id := "auto-" + hex.EncodeToString(sum[:16])
55 loss := hostclaim.AutomaticLoss{ID: id, Box: "target", Enrollment: ref.Enrollment, Fence: ref, ObservedAt: fence.ObservedAt().Format(time.RFC3339Nano), Op: c.Op.ID, Kind: c.Op.Kind, Phase: c.Op.Phase, Role: "target", Incarnation: c.Holder.Incarnation, Stamp: c.Holder.Stamp, Session: c.Holder.Session, Generation: c.Holder.Generation, Timers: hostclaim.TimersUnknown}
56 if len(c.Holder.Admissions) > 0 {
57 a := c.Holder.Admissions[len(c.Holder.Admissions)-1]
58 loss.LastAdmission = &a
59 }
60 c.Op.Kind, c.Op.Phase, c.Op.Exec = hostclaim.KindRestoring, hostclaim.PhaseReleased, "none"
61 c.Op.Intent["rollback"] = "rb_abcdefghijklmnopqrstuvwxyz"
62 c.Op.Participants = []string{c.Op.Intent["source"]}
63 c.Op.Journal["fence.target"], c.Op.Journal["fenced_target_released"] = string(raw), string(raw)
64 c.Lost, c.LossRefs, c.EvidenceRefs = []string{"target"}, []string{id}, []hostclaim.AutomaticLoss{loss}
65 return nil
66 })
67 if err != nil {
68 t.Fatal(err)
69 }
70 o, err = hostclaim.Update(t.Context(), s, o, hostclaim.ReturnToSource("move", "box", false, hostclaim.ReturnFence{}))
71 if err != nil {
72 t.Fatal(err)
73 }
74 o, err = hostclaim.Update(t.Context(), s, o, hostclaim.SetRestoringPhase("move", hostclaim.PhaseClaimed, hostclaim.PhaseProbing))
75 if err != nil {
76 t.Fatal(err)
77 }
78 in := hostclaim.Admission{ObservedOpID: "move", ObservedPhase: o.Claim.Op.Phase, ObservedSession: 2, ObservedGeneration: o.Claim.Holder.Generation, Box: "box", Mode: "cold", NewGeneration: "return", Region: "ie", PredecessorRegion: "uk", RemoteMaxTXID: 3}
79 w := Writer{Claims: s, Box: "box", Region: "ie", AdmissionEvidence: func(_ context.Context, _ hostclaim.Observed, a hostclaim.Admission, p *genselect.Plan) error {
80 if a.RemoteMaxTXID != 7 || p.MaxTXID != 3 {
81 t.Fatalf("loss maximum %d restore maximum %d", a.RemoteMaxTXID, p.MaxTXID)
82 }
83 return nil
84 }, Loss: &LossAdmission{Sources: func(_ context.Context, _ hostclaim.Observed, l hostclaim.LossRecord, _ hostclaim.Admission, _ *genselect.Plan) ([]hostclaim.ValidatedWriteSource, error) {
85 if len(l.WriteSources) != 1 {
86 t.Fatalf("frozen sources: %+v", l)
87 }
88 source := l.WriteSources[0]
89 if mode == "wrong-source" {
90 source.Generation = "other"
91 }
92 return []hostclaim.ValidatedWriteSource{{Reason: source.Reason, Box: source.Box, Session: source.Session, Generation: source.Generation, Region: source.Region, Baseline: source.Baseline, RemoteMaxTXID: 7}}, nil
93 }}}
94 if mode == "missing-validator" {
95 w.Loss = nil
96 }
97 var got hostclaim.Observed
98 if mode == "bypass" {
99 got, err = hostclaim.Update(t.Context(), s, o, hostclaim.AdmitWriter(in))
100 } else {
101 got, err = w.Admit(t.Context(), o, in, restorePlan(o.Claim.Holder.Location, "", 3))
102 }
103 if mode != "ok" {
104 if err == nil {
105 t.Fatal("accepted", mode)
106 }
107 return
108 }
109 if err != nil {
110 t.Fatal(err)
111 }
112 a := got.Claim.Holder.Admissions[len(got.Claim.Holder.Admissions)-1]
113 if a.WritesLost == nil || a.WritesLost.RemoteMaxTXID != 7 || a.WritesLost.Generation != "target-gen" {
114 t.Fatalf("lost target maximum: %+v", a)
115 }
116 })
117 }
118}
119