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 "testing"
7
8 "github.com/carlosframework/platform/internal/hostclaim"
9 "github.com/carlosframework/platform/internal/hostwrite"
10)
11
12func relocating(phase, exec string) *hostclaim.Claim {
13 return &hostclaim.Claim{Holder: &hostclaim.Holder{}, Op: hostclaim.Op{Kind: hostclaim.KindRelocating, ID: "rel-1", Phase: phase, Exec: exec,
14 Participants: []string{"src", "dst"}, Intent: map[string]string{"source_box": "src", "target_box": "dst", "region": "ie"}}}
15}
16
17func TestRelocationRowCases(t *testing.T) {
18 withChore := func(c *hostclaim.Claim, box string) *hostclaim.Claim {
19 c.Obligations = append(c.Obligations, hostclaim.Obligation{ID: "o", Op: "rel-0", Kind: "park-local", Box: box, Owner: "box:" + box, Retirement: "r"})
20 return c
21 }
22 serving := &hostclaim.Claim{Holder: &hostclaim.Holder{}, Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: "src", Participants: []string{"src"}}}
23 lostSource := relocating(hostclaim.PhaseStarting, "src")
24 lostSource.Lost = []string{"src"}
25 for _, tc := range []struct {
26 name string
27 c *hostclaim.Claim
28 box string
29 exists, fenceClosed bool
30 want RowDecision
31 }{
32 {"the serving executor", serving, "src", false, false, RowDecision{Case: RowReady}},
33 {"a serving host elsewhere, no row", serving, "dst", false, false, RowDecision{Case: RowNone}},
34 {"a serving host elsewhere, a row", serving, "dst", true, false, RowDecision{Case: RowUnexplained}},
35 {"the source in starting", relocating(hostclaim.PhaseStarting, "src"), "src", true, false, RowDecision{Case: RowReady}},
36 {"the source releasing, fence open", relocating(hostclaim.PhaseReleasing, "src"), "src", true, false, RowDecision{Case: RowReady}},
37 {"the source releasing, fence closed", relocating(hostclaim.PhaseReleasing, "src"), "src", true, true, RowDecision{Case: RowHeld, HoldOp: "rel-1"}},
38 {"the source in pinning", relocating(hostclaim.PhasePinning, "none"), "src", true, false, RowDecision{Case: RowHeld, HoldOp: "rel-1"}},
39 {"the target in starting", relocating(hostclaim.PhaseStarting, "src"), "dst", false, false, RowDecision{Case: RowHeld, HoldOp: "rel-1"}},
40 {"the target executing in probing", relocating(hostclaim.PhaseRelocatingProbing, "dst"), "dst", true, false, RowDecision{Case: RowHeld, HoldOp: "rel-1"}},
41 {"a lost source", lostSource, "src", true, false, RowDecision{Case: RowUnexplained}},
42 {"a former executor owing park-local", withChore(serving, "old"), "old", true, false, RowDecision{Case: RowRetiring, HoldOp: "rel-0"}},
43 {"a former executor with no row", withChore(serving, "old"), "old", false, false, RowDecision{Case: RowNone}},
44 {"no claim, a row", nil, "src", true, false, RowDecision{Case: RowUnexplained}},
45 } {
46 t.Run(tc.name, func(t *testing.T) {
47 if got := RelocationRow(tc.c, tc.box, tc.exists, tc.fenceClosed); got != tc.want {
48 t.Fatalf("got %+v, want %+v", got, tc.want)
49 }
50 })
51 }
52}
53
54func TestWakePhaseRelocating(t *testing.T) {
55 for _, tc := range []struct {
56 phase, exec, box string
57 probe, want bool
58 }{
59 // Claim Plan 11 R1 as accepted: no ordinary relocating wake, the
60 // source's in starting and releasing included.
61 {hostclaim.PhaseStarting, "src", "src", false, false},
62 {hostclaim.PhaseReleasing, "src", "src", false, false},
63 {hostclaim.PhaseStarting, "src", "src", true, false},
64 {hostclaim.PhaseInstalling, "dst", "dst", false, false},
65 {hostclaim.PhaseInstalled, "dst", "dst", true, false},
66 {hostclaim.PhaseRelocatingProbing, "dst", "dst", true, true},
67 {hostclaim.PhaseRelocatingProbing, "dst", "dst", false, false},
68 {hostclaim.PhaseProbeFailed, "dst", "dst", true, false},
69 {hostclaim.PhasePinning, "none", "src", false, false},
70 } {
71 if got := WakePhase(relocating(tc.phase, tc.exec), tc.box, tc.probe) == nil; got != tc.want {
72 t.Fatalf("%+v: %v", tc, got)
73 }
74 }
75 stuck := relocating(hostclaim.PhaseRelocatingProbing, "dst")
76 stuck.Op.Journal = map[string]string{"stuck": "operator"}
77 if WakePhase(stuck, "dst", true) == nil {
78 t.Fatal("a stuck operation probed")
79 }
80}
81
82// Claim Plan 11 (R1, R8) owns Policy for a relocating claim: its own gate
83// admits only op.exec's probe in probing (or that probe's session serving),
84// and Policy.Relocation can only refuse after it. The region HA rights this
85// branch first gave a relocating claim (a participant's reconcile, the
86// source's ordinary wake in starting, the serving executor clearing an
87// obsolete hold) are refused, whatever Relocation says.
88func TestPolicyUnderRelocatingAdmitsOnlyTheProbe(t *testing.T) {
89 ctx := context.Background()
90 none := func(context.Context, string) (string, error) { return "", nil }
91 held := func(context.Context, string) (string, error) { return "rel-1", nil }
92 allow := func(context.Context, *hostclaim.Claim, Selection, hostwrite.Identity) error { return nil }
93 for name, tc := range map[string]struct {
94 c *hostclaim.Claim
95 held func(context.Context, string) (string, error)
96 s Selection
97 }{
98 "the source spawns in starting": {relocating(hostclaim.PhaseStarting, "src"), none, Selection{Host: "x.example", Box: "src", Op: "rel-1", Action: "spawn"}},
99 "the source reconciles": {relocating(hostclaim.PhaseStarting, "src"), held, Selection{Host: "x.example", Box: "src", Op: "rel-1", Action: "reconcile"}},
100 "a held target reconciles": {relocating(hostclaim.PhaseStarting, "src"), held, Selection{Host: "x.example", Box: "dst", Op: "rel-1", Action: "reconcile"}},
101 "the executor installs": {relocating(hostclaim.PhaseInstalled, "dst"), none, Selection{Host: "x.example", Box: "dst", Op: "rel-1", Action: "install"}},
102 "the probing target spawns": {relocating(hostclaim.PhaseRelocatingProbing, "dst"), none, Selection{Host: "x.example", Box: "dst", Op: "rel-1", Action: "spawn"}},
103 "the source probes": {relocating(hostclaim.PhaseRelocatingProbing, "dst"), none, Selection{Host: "x.example", Box: "src", Op: "rel-1", Action: "probe"}},
104 "a serving executor under a hold": {&hostclaim.Claim{Holder: &hostclaim.Holder{}, Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: "dst", Participants: []string{"dst"}}}, held, Selection{Host: "x.example", Box: "dst", Action: "reconcile"}},
105 } {
106 if err := (Policy{Held: tc.held, Moves: allow, Relocation: allow}).Check(ctx, tc.c, tc.s, hostwrite.Identity{}); !errors.Is(err, ErrChanged) {
107 t.Fatalf("%s: %v", name, err)
108 }
109 }
110 probing := relocating(hostclaim.PhaseRelocatingProbing, "dst")
111 probe := Selection{Host: "x.example", Box: "dst", Op: "rel-1", Action: "probe", Phase: hostclaim.PhaseRelocatingProbing}
112 if err := (Policy{Held: none, Relocation: allow}).Check(ctx, probing, probe, hostwrite.Identity{}); err != nil {
113 t.Fatalf("the target's probe: %v", err)
114 }
115 if err := (Policy{Held: none}).Check(ctx, probing, probe, hostwrite.Identity{}); !errors.Is(err, ErrChanged) {
116 t.Fatalf("a probe with no relocation owner: %v", err)
117 }
118}
119