carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package hostexec
4
5import (
6 "context"
7 "errors"
8 "sync/atomic"
9 "testing"
10 "time"
11)
12
13func TestPlan14ExecutionClientHasOneRenewalOwner(t *testing.T) {
14 b, _, remote, clock, q, peer := brokerFixture(t)
15 var calls, cleanup atomic.Int32
16 c, err := OpenRootClient(t.Context(), RootClientConfig{
17 Binding: Binding{Host: q.Host, Incarnation: q.Incarnation, Op: q.Op, Box: "box", Enrollment: "en", BootID: peer.KernelBootID}, Clock: clock,
18 Observe: func(context.Context) (ExecutionRequest, error) { return q, nil },
19 Call: func(ctx context.Context, q ExecutionRequest) (ExecutionResponse, error) {
20 calls.Add(1)
21 return b.Execute(ctx, peer, q)
22 },
23 CloseAdmission: func() {}, Cleanup: func(context.Context) error { cleanup.Add(1); return nil },
24 })
25 if err != nil {
26 t.Fatal(err)
27 }
28 t.Cleanup(func() { _ = c.Close(context.Background()) })
29 s, err := c.Ready(t.Context())
30 if err != nil || s.ControllerID == "" || s.LeaseETag == "" {
31 t.Fatalf("ready %+v %v", s, err)
32 }
33 if err = c.Refresh(t.Context()); err != nil {
34 t.Fatal(err)
35 }
36 if remote.calls != 1 {
37 t.Fatalf("view renewed: %d writes", remote.calls)
38 }
39 clock.advance(20 * time.Second)
40 if err = c.Refresh(t.Context()); err != nil {
41 t.Fatal(err)
42 }
43 if remote.calls != 2 || calls.Load() < 3 {
44 t.Fatalf("owners: writes=%d calls=%d", remote.calls, calls.Load())
45 }
46 clock.advance(TTL)
47 if _, err = c.Ready(t.Context()); err == nil {
48 t.Fatal("client admitted expired root response")
49 }
50 if err = c.Close(t.Context()); err != nil {
51 t.Fatal(err)
52 }
53 if cleanup.Load() != 1 {
54 t.Fatalf("cleanup=%d", cleanup.Load())
55 }
56}
57
58func TestPlan14ExecutionClientLostBeginResponseSettles(t *testing.T) {
59 b, _, remote, clock, q, peer := brokerFixture(t)
60 var begin ExecutionRequest
61 var lost bool
62 c, err := OpenRootClient(t.Context(), RootClientConfig{
63 Binding: Binding{Host: q.Host, Incarnation: q.Incarnation, Op: q.Op, Box: "box", Enrollment: "en", BootID: peer.KernelBootID}, Clock: clock,
64 Observe: func(context.Context) (ExecutionRequest, error) { return q, nil },
65 Call: func(ctx context.Context, request ExecutionRequest) (ExecutionResponse, error) {
66 if request.Operation == "executionBegin" {
67 if begin.ID != "" && begin != request {
68 t.Error("begin replay changed identity")
69 }
70 begin = request
71 if _, err := b.Execute(ctx, peer, request); err != nil {
72 return ExecutionResponse{}, err
73 }
74 if !lost {
75 lost = true
76 return ExecutionResponse{}, context.DeadlineExceeded
77 }
78 }
79 if request.Host != begin.Host || request.Incarnation != begin.Incarnation || request.Stamp != begin.Stamp || request.Session != begin.Session || request.Op != begin.Op {
80 t.Error("cleanup lost the captured begin tuple")
81 }
82 return b.Execute(ctx, peer, request)
83 }, CloseAdmission: func() {}, Cleanup: func(context.Context) error { return nil },
84 })
85 if !errors.Is(err, context.DeadlineExceeded) {
86 t.Fatal(err)
87 }
88 if err = c.Close(t.Context()); err != nil {
89 t.Fatal("lost begin cannot settle", err)
90 }
91 record, err := b.read(q.Host)
92 if err != nil || record.Status != "settled" || remote.calls != 1 {
93 t.Fatalf("record=%+v err=%v calls=%d", record, err, remote.calls)
94 }
95}
96
97func TestPlan14ExecutionClientLostTransitionResponseSettles(t *testing.T) {
98 b, _, remote, clock, q, peer := brokerFixture(t)
99 initial := q
100 original := b.cfg.Authority
101 b.cfg.Authority = func(ctx context.Context, got ExecutionRequest, peer ControllerProcess) (ExecutionAuthority, error) {
102 got.Session = initial.Session
103 return original(ctx, got, peer)
104 }
105 b.cfg.Transition = func(context.Context, ExecutionRecord, ExecutionRequest, ExecutionAuthority) error { return nil }
106 var uncertain ExecutionRequest
107 lost := false
108 c, err := OpenRootClient(t.Context(), RootClientConfig{
109 Binding: Binding{Host: q.Host, Incarnation: q.Incarnation, Op: q.Op, Box: "box", Enrollment: "en", BootID: peer.KernelBootID}, Clock: clock,
110 Observe: func(context.Context) (ExecutionRequest, error) { return q, nil },
111 Call: func(ctx context.Context, request ExecutionRequest) (ExecutionResponse, error) {
112 out, err := b.Execute(ctx, peer, request)
113 if err == nil && request.Operation == "executionRenew" {
114 if uncertain.ID != "" && uncertain != request {
115 t.Error("transition replay changed request")
116 }
117 uncertain = request
118 if !lost {
119 lost = true
120 return ExecutionResponse{}, context.DeadlineExceeded
121 }
122 }
123 return out, err
124 }, CloseAdmission: func() {}, Cleanup: func(context.Context) error { return nil },
125 })
126 if err != nil {
127 t.Fatal(err)
128 }
129 q.Session++
130 if err = c.Refresh(t.Context()); !errors.Is(err, context.DeadlineExceeded) {
131 t.Fatal(err)
132 }
133 if err = c.Close(t.Context()); err != nil {
134 t.Fatal("lost transition cannot settle", err)
135 }
136 r, err := b.read(q.Host)
137 if err != nil || r.Status != "settled" || r.Request.Session != q.Session || remote.calls != 2 {
138 t.Fatalf("transition recovery: %+v %v writes=%d", r, err, remote.calls)
139 }
140}
141