carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "errors"
6 "github.com/carlosframework/platform/internal/hostclaim"
7 "testing"
8 "time"
9)
10
11// m-9: a replay keeps its uncertain state. Whatever typed refusal the transport
12// reports, the exact request may already have committed.
13func TestPlan9Fix2ReplayRefusalStaysUncertain(t *testing.T) {
14 for _, code := range []string{"refused", "changed", "missing", "unsupported", "boom"} {
15 t.Run(code, func(t *testing.T) {
16 client, _ := RemoteJournals(func(_ context.Context, q JournalRequest) (JournalResponse, error) {
17 return JournalResponse{V: 1, ID: q.ID, Error: code}, nil
18 })
19 q, err := journalPrepare(JournalRequest{Op: "producer"}, time.Now())
20 if err != nil {
21 t.Fatal(err)
22 }
23 _, err = client.ReplayUncertain(t.Context(), &JournalUncertainError{Request: q})
24 var uncertain *JournalUncertainError
25 if !errors.As(err, &uncertain) || uncertain.Request.ID != q.ID {
26 t.Fatalf("%s refusal on replay read as definite: %v", code, err)
27 }
28 if errors.Is(err, ErrChanged) || errors.Is(err, ErrTypedInventory) {
29 t.Fatalf("%s refusal on replay leaked a typed refusal: %v", code, err)
30 }
31 })
32 }
33}
34
35// m-9: success is only a positive reply for this exact request.
36func TestPlan9Fix2ReplaySuccessNeedsExactReceipt(t *testing.T) {
37 q, err := journalPrepare(JournalRequest{Op: "producer"}, time.Now())
38 if err != nil {
39 t.Fatal(err)
40 }
41 client, _ := RemoteJournals(func(context.Context, JournalRequest) (JournalResponse, error) {
42 return JournalResponse{V: 1, ID: "ffffffffffffffffffffffffffffffff"}, nil
43 })
44 var uncertain *JournalUncertainError
45 if _, err = client.ReplayUncertain(t.Context(), &JournalUncertainError{Request: q}); !errors.As(err, &uncertain) {
46 t.Fatalf("a reply for another request confirmed the replay: %v", err)
47 }
48 client, _ = RemoteJournals(func(_ context.Context, r JournalRequest) (JournalResponse, error) {
49 return JournalResponse{V: 1, ID: r.ID}, nil
50 })
51 if _, err = client.ReplayUncertain(t.Context(), &JournalUncertainError{Request: q}); err != nil {
52 t.Fatalf("exact confirmation refused: %v", err)
53 }
54}
55
56// m-11: a forward jump of the host clock that is later corrected must not
57// stall new mutations until real time catches up, and must not let a pruned
58// request execute again.
59func TestPlan9Fix2ForwardClockJumpThenCorrectionKeepsLiveness(t *testing.T) {
60 s, _, in := journalFixture(t)
61 origin := time.Now()
62 now := origin
63 s.Now = func() time.Time { return now }
64 issue := func(q JournalRequest) JournalRequest {
65 t.Helper()
66 q, err := journalPrepare(q, now)
67 if err != nil {
68 t.Fatal(err)
69 }
70 return q
71 }
72 if _, err := s.Execute(t.Context(), "peer", issue(JournalRequest{Op: "create", Launch: in})); err != nil {
73 t.Fatal(err)
74 }
75 old := issue(JournalRequest{Op: "producer", Target: in.Target, Producer: "old"})
76 if _, err := s.Execute(t.Context(), "peer", old); err != nil {
77 t.Fatal(err)
78 }
79 now = origin.Add(5 * 24 * time.Hour)
80 if _, err := s.Execute(t.Context(), "peer", issue(JournalRequest{Op: "producer", Target: in.Target, Producer: "jumped"})); err != nil {
81 t.Fatal(err)
82 }
83 var n int
84 if err := s.Launches.db.QueryRow("SELECT count(*) FROM journal_requests WHERE id=?", old.ID).Scan(&n); err != nil || n != 0 {
85 t.Fatalf("jump did not prune the old receipt: %d %v", n, err)
86 }
87 now = origin.Add(2 * time.Minute)
88 if _, err := s.Execute(t.Context(), "peer", issue(JournalRequest{Op: "producer", Target: in.Target, Producer: "corrected"})); err != nil {
89 t.Fatalf("mutation stalled after the clock was corrected: %v", err)
90 }
91 if _, err := s.Execute(t.Context(), "peer", old); !errors.Is(err, ErrJournalUncertain) {
92 t.Fatalf("a pruned request executed again: %v", err)
93 }
94}
95
96// m-2: an admitted writer that never checked probed may still observe the
97// committed phase when op, kind, session, stamp and generation are unchanged.
98// The transition itself still cannot skip a step.
99func TestPlan9Fix2SelectedWorkObservesCommittedAfterMissingProbed(t *testing.T) {
100 work, _, update := plan9D1Probe(t, hostclaim.SessionObserves)
101 if err := plan9D1Write(work); err != nil {
102 t.Fatal(err)
103 }
104 update(hostclaim.SetMovePhase("m1", hostclaim.KindLanding, hostclaim.PhaseLandingProbing, hostclaim.PhaseLandingProbed))
105 update(hostclaim.KeepWriterSession("m1", hostclaim.SetMovePhase("m1", hostclaim.KindLanding, hostclaim.PhaseLandingProbed, hostclaim.PhaseLandingCommitted)))
106 if err := plan9D1Write(work); err != nil {
107 t.Fatalf("the admitted writer lost its session across an unobserved probed: %v", err)
108 }
109}
110
111func TestPlan9Fix2SelectedWorkStillRefusesIdentityChangeAndBackwardSteps(t *testing.T) {
112 for name, change := range map[string]func(*hostclaim.Claim) error{
113 "session": func(c *hostclaim.Claim) error {
114 c.Op.Phase = hostclaim.PhaseLandingCommitted
115 c.Holder.Session++
116 return nil
117 },
118 "op": func(c *hostclaim.Claim) error {
119 c.Op.Phase = hostclaim.PhaseLandingCommitted
120 c.Op.ID = "m2"
121 return nil
122 },
123 "gen": func(c *hostclaim.Claim) error {
124 c.Op.Phase = hostclaim.PhaseLandingCommitted
125 c.Holder.Generation = "g2"
126 return nil
127 },
128 "rollback": func(c *hostclaim.Claim) error {
129 c.Op.Kind, c.Op.Phase = hostclaim.KindRestoring, "releasing"
130 return nil
131 },
132 } {
133 t.Run(name, func(t *testing.T) {
134 work, _, update := plan9D1Probe(t, hostclaim.SessionObserves)
135 update(change)
136 if err := plan9D1Write(work); err == nil {
137 t.Fatal("observation catch-up kept a changed session")
138 }
139 })
140 }
141 work, _, update := plan9D1Probe(t, hostclaim.SessionObserves)
142 update(hostclaim.SetMovePhase("m1", hostclaim.KindLanding, hostclaim.PhaseLandingProbing, hostclaim.PhaseLandingProbed))
143 if err := plan9D1Write(work); err != nil {
144 t.Fatal(err)
145 }
146 update(func(c *hostclaim.Claim) error { c.Op.Phase = hostclaim.PhaseLandingProbing; return nil })
147 if err := plan9D1Write(work); err == nil {
148 t.Fatal("observation went backward")
149 }
150}
151
152// m-2: the keep guard is transition authority and still refuses a skip.
153func TestPlan9Fix2KeepWriterSessionStillRefusesASkippedTransition(t *testing.T) {
154 _, store, update := plan9D1Probe(t, hostclaim.SessionObserves)
155 current, err := hostclaim.Read(t.Context(), store, "shop.example")
156 if err != nil {
157 t.Fatal(err)
158 }
159 _, err = hostclaim.Update(t.Context(), store, current, hostclaim.KeepWriterSession("m1", hostclaim.SetMovePhase("m1", hostclaim.KindLanding, hostclaim.PhaseLandingProbing, hostclaim.PhaseLandingCommitted)))
160 if err == nil {
161 t.Fatal("a session-keeping step skipped probed")
162 }
163 _ = update
164}
165