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 "crypto/ed25519"
6 "errors"
7 "reflect"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestLossIdentityValidation(t *testing.T) {
14 for _, tc := range []struct {
15 name string
16 loss WritesLost
17 valid bool
18 }{
19 {"legacy", WritesLost{Box: "box"}, true},
20 {"claimed", WritesLost{Box: "box", Session: 2, Generation: "g2"}, true},
21 {"missing legacy box", WritesLost{}, false},
22 {"missing claimed box", WritesLost{Session: 2, Generation: "g2"}, false},
23 {"zero session with generation", WritesLost{Box: "box", Generation: "g2"}, false},
24 {"positive session without generation", WritesLost{Box: "box", Session: 2}, false},
25 } {
26 t.Run(tc.name, func(t *testing.T) {
27 for _, field := range []string{"primary", "additional", "source"} {
28 t.Run(field, func(t *testing.T) {
29 r := validLossRecord(t)
30 a := ArchivedAdmissionLoss{LossID: r.ID, AdmissionClaimGen: 8}
31 switch field {
32 case "primary":
33 a.WritesLost = &tc.loss
34 case "additional":
35 a.AdditionalWritesLost = []WritesLost{tc.loss}
36 case "source":
37 r.WriteSources[0].Box, r.WriteSources[0].Session, r.WriteSources[0].Generation = tc.loss.Box, tc.loss.Session, tc.loss.Generation
38 }
39 if field != "source" {
40 r.ArchivedAdmissionLosses = []ArchivedAdmissionLoss{a}
41 }
42 err := r.Validate()
43 if tc.valid && err != nil || !tc.valid && !errors.Is(err, ErrLossRecord) {
44 t.Fatalf("Validate() = %v, valid = %v", err, tc.valid)
45 }
46 })
47 }
48 })
49 }
50 legacy := WritesLost{Box: "box"}
51 if validWriteLosses(&legacy, []WritesLost{legacy}) || validWriteLosses(nil, []WritesLost{legacy, legacy}) {
52 t.Fatal("duplicate legacy identity accepted")
53 }
54}
55
56func TestAdmitWriterWithLossArchivesLegacyIdentity(t *testing.T) {
57 for _, field := range []string{"primary", "additional"} {
58 t.Run(field, func(t *testing.T) {
59 ctx := context.Background()
60 s := release.NewDirStore(t.TempDir())
61 pub, priv, err := ed25519.GenerateKey(nil)
62 if err != nil {
63 t.Fatal(err)
64 }
65 primary := WritesLost{Box: "target", Session: 4, Generation: "g-target", RemoteMaxTXID: 12}
66 additional := WritesLost{Box: "source", Session: 2, Generation: "g-source", RemoteMaxTXID: 10}
67 if field == "primary" {
68 primary.Session, primary.Generation = 0, ""
69 } else {
70 additional.Session, additional.Generation = 0, ""
71 }
72 obs := servingClaim(t, ctx, s)
73 obs = updateGenerationTest(t, ctx, s, obs, func(c *Claim) error {
74 c.Op = Op{Kind: KindReverting, ID: "return", Phase: "recover", Exec: "recovery", Participants: []string{"recovery"}}
75 c.Holder.WriterBox, c.Holder.Generation, c.Holder.Session = primary.Box, primary.Generation, primary.Session
76 c.LossRefs = []string{"loss_1"}
77 return nil
78 })
79 r := validLossRecord(t)
80 r.Host = obs.Host
81 r.HolderIncarnation, r.HolderStamp, r.HolderSession = obs.Claim.Holder.Incarnation, obs.Claim.Holder.Stamp, primary.Session
82 r.OpID, r.OpKind, r.OpPhase, r.Box = "return", KindReverting, "recover", primary.Box
83 r.WriteSources = []WriteSource{
84 {Reason: "target", Box: primary.Box, Generation: primary.Generation, Session: primary.Session, Region: "ie", Baseline: "target-snapshot"},
85 {Reason: "source-cut", Box: additional.Box, Generation: additional.Generation, Session: additional.Session, Region: "ie", Baseline: "source-seal"},
86 }
87 created, err := CreateLossRecord(ctx, s, release.RootSigner(priv), r)
88 if err != nil {
89 t.Fatal(err)
90 }
91 r.State, r.ResultClaimGen = "committed", obs.Claim.Gen
92 committed, err := ReplaceLossRecord(ctx, s, release.RootSigner(priv), created, r)
93 if err != nil {
94 t.Fatal(err)
95 }
96 in := Admission{ObservedOpID: "return", ObservedPhase: "recover", ObservedSession: primary.Session, ObservedGeneration: primary.Generation, Box: "recovery", Mode: "cold", NewGeneration: "g-recovery", Region: "ie", PredecessorRegion: "ie", RemoteMaxTXID: primary.RemoteMaxTXID, AdditionalWritesLost: []WritesLost{additional}}
97 validated := []ValidatedWriteSource{
98 {Reason: "target", Box: primary.Box, Generation: primary.Generation, Session: primary.Session, Region: "ie", Baseline: "target-snapshot", RemoteMaxTXID: primary.RemoteMaxTXID},
99 {Reason: "source-cut", Box: additional.Box, Generation: additional.Generation, Session: additional.Session, Region: "ie", Baseline: "source-seal", RemoteMaxTXID: additional.RemoteMaxTXID},
100 }
101 admitted, err := AdmitWriterWithLoss(ctx, s, pub, obs, in, r.ID, validated, testLossRecoveryOwner{})
102 if err != nil {
103 t.Fatal(err)
104 }
105 a := admitted.Claim.Holder.Admissions[0]
106 if a.LossID != r.ID || !reflect.DeepEqual(a.WritesLost, &primary) || !reflect.DeepEqual(a.AdditionalWritesLost, []WritesLost{additional}) {
107 t.Fatalf("admission lost identity: %+v", a)
108 }
109 if _, err := ArchiveAdmissionLosses(ctx, s, pub, admitted, r.ID); !errors.Is(err, ErrLossEvidencePending) {
110 t.Fatalf("unarchived legacy evidence = %v", err)
111 }
112 r.ArchivedAdmissionLosses = []ArchivedAdmissionLoss{{LossID: r.ID, AdmissionClaimGen: a.Session, WritesLost: &primary, AdditionalWritesLost: []WritesLost{additional}}}
113 if _, err := ReplaceLossRecord(ctx, s, release.RootSigner(priv), committed, r); err != nil {
114 t.Fatal(err)
115 }
116 archived, err := ArchiveAdmissionLosses(ctx, s, pub, admitted, r.ID)
117 if err != nil {
118 t.Fatal(err)
119 }
120 if archived.Claim.Holder.Admissions[0].LossArchivedClaimGen != a.Session || RequireLossEvidenceArchived(archived.Claim) != nil {
121 t.Fatalf("legacy evidence not archived: %+v", archived.Claim.Holder.Admissions)
122 }
123 })
124 }
125}
126