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 "path/filepath"
7 "slices"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/activator"
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/registry"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19// unreadableClaim is a control bucket whose reads of one claim key fail.
20type unreadableClaim struct {
21 release.Store
22 key string
23}
24
25func (s unreadableClaim) Get(ctx context.Context, key string) ([]byte, string, error) {
26 if key == s.key {
27 return nil, "", errors.New("the claim could not be read")
28 }
29 return s.Store.Get(ctx, key)
30}
31
32// Absence is never evidence: a claim the pass cannot read is said, once,
33// and read again next pass; it is never passed over as nothing to do. The
34// other hosts' steps run as before.
35func TestRelocatePassSaysAClaimItCannotRead(t *testing.T) {
36 ctx := t.Context()
37 dir := release.NewDirStore(t.TempDir())
38 passClaim(t, dir, "a.example", relocatingOp("rel-a"))
39 passClaim(t, dir, "b.example", relocatingOp("rel-b"))
40 key, _ := hostclaim.Key("b.example")
41 store := unreadableClaim{Store: dir, key: key}
42 buf := captureRacyLog(t)
43 var order []string
44 r := &RelocationRuntime{Worker: stepRecorder{name: "worker", order: &order}, Participant: stepRecorder{name: "participant", order: &order}, Box: "dst"}
45 r.once(ctx, store)
46 r.once(ctx, store)
47 if want := []string{"participant:a.example", "worker:a.example", "participant:a.example", "worker:a.example"}; !slices.Equal(order, want) {
48 t.Fatalf("stepped %v, want %v", order, want)
49 }
50 if n := strings.Count(buf.String(), "relocate b.example: read: the claim could not be read"); n != 1 {
51 t.Fatalf("an unreadable claim was said %d times, want once:\n%s", n, buf.String())
52 }
53}
54
55// Absence is never evidence: with region HA on, a full pass that could not
56// be built at startup (the roster, the enrollment or the release key not
57// readable then) is not "no relocation on this box". The completion-only
58// runtime stands in, finishing completions, and tries the build again each
59// pass until it builds; then the same Run is the full pass. A build that
60// answers the owners are not bound ends the tries.
61func TestRelocationPassBuildsTheFullPassOnceItCan(t *testing.T) {
62 ctx := t.Context()
63 store := release.NewDirStore(t.TempDir())
64 passClaim(t, store, "a.example", relocatingOp("rel-a"))
65 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
66 if err != nil {
67 t.Fatal(err)
68 }
69 defer reg.Close()
70 local := &productionClaimLocal{legacyParticipation: true, assembly: &ClaimAssembly{Claims: store, Guard: hostguard.Guard{DataDir: t.TempDir()}, stopLocal: func(context.Context) (claimStopLocal, error) {
71 return claimStopLocal{Actor: hostbox.Actor{Box: "dst"}}, nil
72 }}}
73 buf := captureRacyLog(t)
74 var order []string
75 builds := 0
76 unreadable := errors.New("the roster could not be read")
77 full := func(context.Context) (*RelocationRuntime, error) {
78 builds++
79 if builds <= 2 {
80 return nil, unreadable
81 }
82 return &RelocationRuntime{Worker: stepRecorder{name: "worker", order: &order}, Participant: stepRecorder{name: "participant", order: &order}, Box: "dst",
83 Finish: func(context.Context, string, string) error { return nil }}, nil
84 }
85 r, err := relocationPassFor(local, reg, &activator.Service{}, full)
86 if err != nil || r == nil || r.Worker != nil || r.Finish == nil || r.Hosts == nil {
87 t.Fatalf("not the completion-only stand-in: %+v %v", r, err)
88 }
89 // Review M9: nothing is built on the startup path.
90 if builds != 0 {
91 t.Fatalf("built %d times before the first pass", builds)
92 }
93 r.once(ctx, store) // the first build fails
94 r.once(ctx, store) // the second build fails too
95 if len(order) != 0 || builds != 2 {
96 t.Fatalf("stepped %v after %d builds", order, builds)
97 }
98 r.once(ctx, store) // the third builds
99 if want := []string{"participant:a.example", "worker:a.example"}; !slices.Equal(order, want) || r.Box != "dst" || r.Hosts != nil {
100 t.Fatalf("not upgraded: stepped %v, %+v", order, r)
101 }
102 r.once(ctx, store)
103 if builds != 3 {
104 t.Fatalf("built %d times, want no more once built", builds)
105 }
106 if n := strings.Count(buf.String(), unreadable.Error()); n != 1 {
107 t.Fatalf("the failed build was said %d times, want once:\n%s", n, buf.String())
108 }
109 // Owners not bound: the build answers the completion-only runtime, and
110 // the tries end.
111 builds = 0
112 r, err = relocationPassFor(local, reg, &activator.Service{}, func(context.Context) (*RelocationRuntime, error) {
113 builds++
114 if builds == 1 {
115 return nil, unreadable
116 }
117 return newCompletionRuntime(local, reg, &activator.Service{})
118 })
119 if err != nil {
120 t.Fatal(err)
121 }
122 r.once(ctx, store)
123 r.once(ctx, store)
124 if builds != 2 || r.Worker != nil {
125 t.Fatalf("built %d times (want 2), worker %v", builds, r.Worker)
126 }
127}
128
129// Claim mode is on, so a completion recorded here must be finished here:
130// the agent never leaves the completion pass unstarted without saying so.
131func TestStartRelocationPassSaysWhyItDidNotStart(t *testing.T) {
132 buf := captureRacyLog(t)
133 claimCtx := context.WithValue(t.Context(), claimEntryKey{}, claimEntry{Enabled: true, FlagRead: true})
134 startRelocationPass(t.Context(), claimCtx, &ClaimWiring{}, nil, &activator.Service{})
135 if !strings.Contains(buf.String(), "relocation completion pass not started") {
136 t.Fatalf("not said:\n%s", buf.String())
137 }
138 off := context.WithValue(t.Context(), claimEntryKey{}, claimEntry{FlagRead: true})
139 before := buf.String()
140 startRelocationPass(t.Context(), off, &ClaimWiring{}, nil, &activator.Service{})
141 if buf.String() != before {
142 t.Fatalf("claim mode off: %q", strings.TrimPrefix(buf.String(), before))
143 }
144}
145