carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3import (
4 "errors"
5 "strings"
6 "testing"
7 "time"
8)
9
10func c7RelocationFixture() (*Record, MoveGuard, string, string) {
11 g := MoveGuard{ID: "mv_abcdefghijklmnopqrstuvwxyz", Incarnation: strings.Repeat("a", 64)}
12 en := strings.Repeat("b", 32)
13 fence := `{"enrollment":"` + en + `","key":"control/box-fences/` + en + `.json","sha256":"` + strings.Repeat("c", 64) + `"}`
14 rec := &Record{Region: "ie", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, From: "ie", To: "uk", State: MoveCutting, CutPhase: CutArchived}}
15 return rec, g, en, fence
16}
17
18func c7Decide(g MoveGuard, en, region, fence string, now time.Time) func(*Record) error {
19 return MoveRelocatingDecision(g, "source", en, region, fence, 7, "claim-etag", strings.Repeat("d", 64), strings.Repeat("e", 64), now)
20}
21
22// The decision and the flip exclude each other under one record CAS, in
23// both orders. MoveComplete is the flip that exists on main today; Moves
24// Plan 2's MoveFlip must add the same negative case.
25func TestC7RecordDecisionAndFlipExcludeEachOther(t *testing.T) {
26 now := time.Now().UTC()
27 rec, g, en, fence := c7RelocationFixture()
28 if err := c7Decide(g, en, "ie", fence, now)(rec); err != nil {
29 t.Fatal(err)
30 }
31 if rec.Move.State != MoveRelocating || rec.Move.Relocation.PreviousState != MoveCutting || rec.Move.Relocation.PreviousCutPhase != CutArchived || rec.Move.To != "uk" || rec.Region != "ie" {
32 t.Fatalf("lost source or move history: %+v", rec.Move)
33 }
34 for name, tr := range map[string]func(*Record) error{
35 "complete": MoveComplete(),
36 "guarded-cut": MoveGuardedCut(g),
37 "guarded-abort": MoveGuardedAbort(g),
38 "abort": MoveAbort(),
39 "abort-complete": MoveGuardedAbortComplete(g, now),
40 "promote": MoveGuardedPromoteReady(g, now, MoveInputs{}),
41 "mark-cutting": MoveMarkCutting(),
42 "start": MoveStartTransition(MoveStart{ID: g.ID, Incarnation: g.Incarnation, From: "ie", To: "uk", Cut: "auto", Now: now}),
43 } {
44 probe := *rec
45 m := *rec.Move
46 probe.Move = &m
47 if err := tr(&probe); !errors.Is(err, ErrMoveConflict) || probe.Move.State != MoveRelocating || probe.Region != "ie" {
48 t.Fatalf("%s crossed a durable relocation decision: %v", name, err)
49 }
50 }
51 if err := c7Decide(g, en, "ie", fence, now.Add(time.Hour))(rec); err != nil || !rec.Move.Relocation.DecidedAt.Equal(now) {
52 t.Fatal("replay refreshed decision", err)
53 }
54 other := MoveRelocatingDecision(g, "source", en, "ie", fence, 7, "claim-etag", strings.Repeat("f", 64), strings.Repeat("e", 64), now)
55 if err := other(rec); !errors.Is(err, ErrMoveConflict) {
56 t.Fatal("a different moving operation replayed the decision", err)
57 }
58
59 rec, g, en, fence = c7RelocationFixture()
60 if err := MoveComplete()(rec); err != nil {
61 t.Fatal(err)
62 }
63 if err := c7Decide(g, en, "ie", fence, now)(rec); !errors.Is(err, ErrMoveConflict) {
64 t.Fatal("conversion crossed flip", err)
65 }
66}
67
68func TestC7RecordDecisionRefusals(t *testing.T) {
69 now := time.Now().UTC()
70 for _, tc := range []string{"wrong-region", "record-region", "flipped", "rollback", "aborting", "landing", "wrong-move", "fence-enrollment", "fence-key", "zero-time"} {
71 t.Run(tc, func(t *testing.T) {
72 rec, g, en, fence := c7RelocationFixture()
73 region, now := "ie", now
74 switch tc {
75 case "wrong-region":
76 region = "uk"
77 case "record-region":
78 rec.Region = "uk"
79 case "flipped":
80 at := now
81 rec.Move.FlippedAt = &at
82 case "rollback":
83 rec.Move.Rollback = &MoveRollback{ID: "rb"}
84 case "aborting":
85 rec.Move.State = MoveAborting
86 case "landing":
87 rec.Move.State = MoveLanding
88 case "wrong-move":
89 g.ID = "mv_bcdefghijklmnopqrstuvwxyza"
90 case "fence-enrollment":
91 fence = strings.Replace(fence, `"enrollment":"`+en, `"enrollment":"`+strings.Repeat("9", 32), 1)
92 case "fence-key":
93 fence = strings.Replace(fence, "control/box-fences/", "control/other/", 1)
94 case "zero-time":
95 now = time.Time{}
96 }
97 before := *rec.Move
98 if err := c7Decide(g, en, region, fence, now)(rec); !errors.Is(err, ErrMoveConflict) {
99 t.Fatalf("accepted: %v", err)
100 }
101 if rec.Move.State != before.State || rec.Move.Relocation != nil {
102 t.Fatal("a refused decision changed the record")
103 }
104 })
105 }
106}
107
108func TestC7LostTargetParkStampIsOriginalAndConditional(t *testing.T) {
109 now := time.Now().UTC()
110 g := MoveGuard{ID: "mv_abcdefghijklmnopqrstuvwxyz", Incarnation: strings.Repeat("a", 64)}
111 rec := &Record{Region: "ie", Move: &MoveState{ID: g.ID, Incarnation: g.Incarnation, State: MoveRollingBack, Rollback: &MoveRollback{ID: "rb-1", TargetPhase: RbTargetReleased}}}
112 if err := MoveLostTargetParked(g, "other", now)(rec); err == nil {
113 t.Fatal("other rollback stamped")
114 }
115 rec.Move.Rollback.Dirty = true
116 if err := MoveLostTargetParked(g, "rb-1", now)(rec); err == nil {
117 t.Fatal("uncommitted dirty loss stamped")
118 }
119 rec.Move.Rollback.Dirty = false
120 if err := MoveLostTargetParked(g, "rb-1", now)(rec); err != nil {
121 t.Fatal(err)
122 }
123 if err := MoveLostTargetParked(g, "rb-1", now.Add(time.Hour))(rec); err != nil || !rec.Move.Rollback.TargetParkedAt.Equal(now) || rec.Move.Rollback.TargetPhase != RbTargetCleaned {
124 t.Fatal("replay changed stamp", err)
125 }
126}
127