carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "github.com/carlosframework/platform/internal/hostclaim"
7 "github.com/carlosframework/platform/internal/hostguard"
8 "github.com/carlosframework/platform/internal/registry"
9 "os/exec"
10 "testing"
11)
12
13func TestPlan11Fix1RelocationAdmissionHooks(t *testing.T) {
14 for _, writer := range []string{"source", "", "target"} {
15 t.Run(writer, func(t *testing.T) {
16 calls := []string{}
17 w := &ClaimWakeAssembly{Stop: func(context.Context, hostclaim.Observed) error { calls = append(calls, "local-stop"); return nil }}
18 local := func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
19 calls = append(calls, "local-after")
20 return nil
21 }
22 reloc := ClaimRelocationOwners{BeforeAdmission: func(context.Context, hostclaim.Observed) error {
23 calls = append(calls, "relocation-before")
24 return nil
25 }, AfterAdmission: func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
26 calls = append(calls, "relocation-after")
27 return nil
28 }}
29 wireClaimAdmission(w, "target", local, claimCrossBoxHandover{}, &reloc, nil)
30 o := plan11Relocating("probing")
31 o.Claim.Op.Exec = "target"
32 o.Claim.Holder.WriterBox = writer
33 if err := w.stopPredecessor(t.Context(), o); err != nil {
34 t.Fatal(err)
35 }
36 if err := w.AfterAdmission(t.Context(), o, o); err != nil {
37 t.Fatal(err)
38 }
39 want := []string{"relocation-before", "relocation-after"}
40 if writer == "target" {
41 want = []string{"local-stop", "local-after"}
42 }
43 if len(calls) != 2 || calls[0] != want[0] || calls[1] != want[1] {
44 t.Fatalf("calls=%v want=%v", calls, want)
45 }
46 })
47 }
48}
49
50func TestPlan11Fix1CollectRelocationStops(t *testing.T) {
51 for _, phase := range []string{"releasing", "probing", "serving"} {
52 t.Run(phase, func(t *testing.T) {
53 f := newRecoveryFixture(t)
54 c := f.controller("boot-1", "controller-1", 4101, "start-1")
55 c.admit(t, f)
56 o, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(cl *hostclaim.Claim) error {
57 cl.Op = hostclaim.Op{Kind: hostclaim.KindRelocating, ID: "reloc", Phase: phase, Exec: "box-1", Participants: []string{"box-0", "box-1"}, Intent: map[string]string{"region": "ie", "source_box": "box-1", "target_box": "box-1"}}
58 if phase == "releasing" {
59 return hostclaim.QueueRelocationRelease("reloc", "box-1", "ie", "retirement")(cl)
60 }
61 return nil
62 })
63 if err != nil {
64 t.Fatal(err)
65 }
66 local, err := c.assembly.localStopIdentity(t.Context())
67 if err != nil {
68 t.Fatal(err)
69 }
70 seen := false
71 sentinel := errors.New("collected")
72 c.assembly.collectStop = func(_ context.Context, req StopRequest, _ StopDrainer) (StopResult, error) {
73 seen = true
74 want := hostguard.WriterStopObligation(o.Claim.Holder.Session)
75 if phase == "releasing" {
76 want = "relocation-release"
77 }
78 if req.ObligationID != want {
79 t.Errorf("obligation=%q want=%q", req.ObligationID, want)
80 }
81 return StopResult{}, sentinel
82 }
83 c.wake.Continue = hostclaim.SessionContinues
84 _, err = c.assembly.collectCapturedStop(t.Context(), o, local, claimLaunchOwner{BootID: local.BootID, ControllerID: local.ControllerID}, phase != "releasing")
85 if !seen || !errors.Is(err, sentinel) {
86 t.Fatalf("relocation stop never collected: %v", err)
87 }
88 })
89 }
90}
91
92func TestPlan11Fix1RelocationComposedAdmission(t *testing.T) {
93 f := newRecoveryFixture(t)
94 if _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error {
95 c.Op = hostclaim.Op{ID: "reloc", Kind: hostclaim.KindRelocating, Phase: "probing", Exec: "box-1", Participants: []string{"source", "box-1"}, Intent: map[string]string{"source_box": "source", "target_box": "box-1", "region": "ie"}}
96 return nil
97 }); err != nil {
98 t.Fatal(err)
99 }
100 c := f.controller("boot-1", "controller-1", 4101, "start-1")
101 c.wake.Inputs = plan9D2Inputs{recoveryInputs{f}}
102 c.wake.Continue = hostclaim.SessionContinues
103 c.wake.Operations = &plan9D2Attempts{}
104 c.wake.ProbeReady = func(context.Context, *exec.Cmd, registry.Route, ClaimLaunchInputs, string) error { return nil }
105 before, after := 0, 0
106 refusal := errors.New("release proof refused")
107 owners := ClaimRelocationOwners{
108 BeforeAdmission: func(context.Context, hostclaim.Observed) error { before++; return refusal },
109 AfterAdmission: func(_ context.Context, prior, admitted hostclaim.Observed) error {
110 after++
111 if admitted.Claim.Holder.WriterBox != "box-1" || admitted.Claim.Holder.Session <= prior.Claim.Holder.Session {
112 t.Fatal("handover did not receive admitted writer")
113 }
114 return nil
115 },
116 }
117 wireClaimAdmission(c.wake, "box-1", func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
118 t.Fatal("remote predecessor reached local handover")
119 return nil
120 }, claimCrossBoxHandover{}, &owners, nil)
121 prior := f.read()
122 if err := c.wake.stopPredecessor(t.Context(), prior); !errors.Is(err, refusal) {
123 t.Fatalf("release refusal lost: %v", err)
124 }
125 refusal = nil
126 if err := c.wake.stopPredecessor(t.Context(), prior); err != nil {
127 t.Fatal(err)
128 }
129 live, cancel := context.WithCancel(t.Context())
130 defer cancel()
131 done := make(chan struct{})
132 go func() { <-live.Done(); close(done) }()
133 c.wake.pending[recoveryHost] = &claimWakeAttempt{observed: prior, held: recoveryHeld{}, ctx: live, cancel: cancel, done: done}
134 policy := recoveryPolicy
135 policy.Probe = true
136 admitted, err := c.runtime.Admit(t.Context(), registry.Route{Host: recoveryHost}, prior, policy)
137 if err != nil {
138 t.Fatal(err)
139 }
140 if before != 2 || after != 1 || len(f.requests) != 0 {
141 t.Fatalf("before=%d after=%d local stops=%d", before, after, len(f.requests))
142 }
143 if admitted.Claim.Op.Kind != hostclaim.KindRelocating {
144 t.Fatal("lost relocation")
145 }
146}
147
148func TestPlan11Fix1RelocationRequiresHandoverOwners(t *testing.T) {
149 for _, missing := range []string{"before", "after"} {
150 var calls []string
151 owners := plan11RelocationOwners(&calls)
152 if missing == "before" {
153 owners.BeforeAdmission = nil
154 } else {
155 owners.AfterAdmission = nil
156 }
157 if _, err := claimRelocationC1(plan11Env("1", "1"), true, owners); err == nil {
158 t.Fatalf("accepted missing %s hook", missing)
159 }
160 }
161}
162