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 "encoding/json"
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostclaim"
15 execlease "github.com/carlosframework/platform/internal/hostexec"
16 "github.com/carlosframework/platform/internal/registry"
17)
18
19type staleClaimReader func(context.Context, string) (hostclaim.Observed, error)
20
21func (f staleClaimReader) Read(ctx context.Context, host string) (hostclaim.Observed, error) {
22 return f(ctx, host)
23}
24
25func TestPlan9PreviousAgentRetiredParticipation(t *testing.T) {
26 t.Parallel()
27 for _, mode := range []string{"free", "free-lock-held", "free-without-history", "replacement", "replacement-artifact", "missing-retirement", "artifact", "launch", "fresh-retirement-missing", "fresh-absent", "root", "adopt"} {
28 t.Run(mode, func(t *testing.T) {
29 f := newRecoveryFixture(t)
30 ctx := t.Context()
31 old := f.read()
32 inc := old.Claim.Holder.Incarnation
33 if mode == "launch" {
34 f.controller("boot-old", "controller-old", 901, "101").admit(t, f)
35 }
36 o, err := hostclaim.Update(ctx, f.claims, f.read(), hostclaim.BeginDelete("delete", "console", "record-etag", f.now, nil, nil, ""))
37 if err != nil {
38 t.Fatal(err)
39 }
40 o, err = hostclaim.Update(ctx, f.claims, o, hostclaim.ToParking("delete", nil, nil, f.newID))
41 if err != nil {
42 t.Fatal(err)
43 }
44 for len(o.Claim.Obligations) > 0 {
45 chore := o.Claim.Obligations[0]
46 o, err = hostclaim.Update(ctx, f.claims, o, hostclaim.CompleteObligation(chore.Op, chore.ID))
47 if err != nil {
48 t.Fatal(err)
49 }
50 }
51 o, err = hostclaim.Update(ctx, f.claims, o, hostclaim.FreeAfterPark("delete"))
52 if err != nil || !o.Claim.Free() {
53 t.Fatalf("FreeAfterPark: %+v %v", o, err)
54 }
55 if mode == "free-without-history" {
56 o, err = hostclaim.Update(ctx, f.claims, o, func(c *hostclaim.Claim) error { c.RetiredIncarnations = nil; return nil })
57 if err != nil {
58 t.Fatal(err)
59 }
60 }
61 if mode == "replacement" || mode == "replacement-artifact" || mode == "missing-retirement" || mode == "fresh-retirement-missing" {
62 created := f.now.Add(time.Hour)
63 in := hostclaim.NewHolder{Family: "instance", Key: old.Claim.Holder.Key, Account: "bab", App: "jam", Host: recoveryHost, Region: "ie", RecordRegion: "ie", CreatedAt: created}
64 body := []byte(fmt.Sprintf(`{"schema":1,"host":%q,"account":"bab","app":"jam","region":"ie","created_at":%q}`, recoveryHost, created.Format(time.RFC3339)))
65 o, err = hostclaim.Update(ctx, f.claims, o, hostclaim.BeginCreate(in, body, "create-next", "console", created))
66 if err != nil {
67 t.Fatal(err)
68 }
69 o, err = hostclaim.Update(ctx, f.claims, o, hostclaim.CompleteCreate("create-next", "box-2"))
70 if err != nil {
71 t.Fatal(err)
72 }
73 if mode == "missing-retirement" {
74 o, err = hostclaim.Update(ctx, f.claims, o, func(c *hostclaim.Claim) error { c.RetiredIncarnations = nil; return nil })
75 if err != nil {
76 t.Fatal(err)
77 }
78 }
79 }
80 inventory, err := openLegacyInventory(t, filepath.Join(t.TempDir(), "inventory.db"))
81 if err != nil {
82 t.Fatal(err)
83 }
84 t.Cleanup(func() { inventory.Close() })
85 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
86 if err != nil {
87 t.Fatal(err)
88 }
89 t.Cleanup(func() { reg.Close() })
90 census := &hostbox.EmptyInstallationCensus{Inventory: inventory, Registry: reg, Launches: f.journal, Box: "box-1", Enrollment: "enrollment-1", DirectoryRevision: "7", Roots: map[string]string{"data": f.guard.DataDir, "opt": t.TempDir(), "units": t.TempDir()}, SharedPaths: map[string][]string{"data": {".hostexec", "hostguard"}}}
91 p := &productionClaimLocal{legacyParticipation: true, Resources: ClaimRuntimeResources{Mode: "agent", DataDir: f.guard.DataDir}, lifetime: ctx, assembly: &ClaimAssembly{Wake: &ClaimWakeAssembly{}, Launches: f.journal, Reconcile: &hostbox.ReconcileAdapter{Census: census}}}
92 state := execlease.State{V: 1, Binding: execlease.Binding{Host: recoveryHost, Box: "box-1", Enrollment: "enrollment-1", Incarnation: inc, Op: "old-op", BootID: "old-boot", ControllerID: "old-controller"}, State: "lost", SafeUntil: 1, Expires: time.Now()}
93 dir := filepath.Join(f.guard.DataDir, ".hostexec")
94 if err = os.Mkdir(dir, 0700); err != nil {
95 t.Fatal(err)
96 }
97 body, _ := json.Marshal(state)
98 path := filepath.Join(dir, recoveryHost+".json")
99 if err = os.WriteFile(path, body, 0600); err != nil {
100 t.Fatal(err)
101 }
102 if mode == "artifact" || mode == "replacement-artifact" {
103 // Even complete previous-installation metadata cannot bypass the full scan.
104 if err = inventory.WriteStopInventory(ctx, hostbox.StopInventory{Complete: true, Host: recoveryHost, Incarnation: inc, InstallationStamp: old.Claim.Holder.Stamp, Box: "box-1", Enrollment: "enrollment-1", DirectoryRevision: "7"}); err != nil {
105 t.Fatal(err)
106 }
107 if err = os.WriteFile(filepath.Join(census.Roots["opt"], "leftover"), []byte("data"), 0600); err != nil {
108 t.Fatal(err)
109 }
110 }
111 cfg := execlease.Config{Binding: state.Binding, Guard: f.guard, LockDir: t.TempDir()}
112 if err = census.Initialize(ctx, o, hostbox.RecordSelection{Host: recoveryHost, Incarnation: inc}); !errors.Is(err, hostbox.ErrChanged) {
113 t.Fatalf("cleanup scope initialized replacement inventory: %v", err)
114 }
115 if mode == "free-lock-held" {
116 original := cfg.Guard.Claims
117 competing := cfg
118 cfg.Guard.Claims = staleClaimReader(func(ctx context.Context, host string) (hostclaim.Observed, error) {
119 deadline, cancel := context.WithTimeout(ctx, 25*time.Millisecond)
120 defer cancel()
121 if err := execlease.ClearStale(deadline, competing); !errors.Is(err, context.DeadlineExceeded) {
122 t.Fatalf("fresh proof did not retain lifetime lock: %v", err)
123 }
124 return original.Read(ctx, host)
125 })
126 }
127 if mode == "fresh-retirement-missing" || mode == "fresh-absent" {
128 cfg.Guard.Claims = staleClaimReader(func(ctx context.Context, host string) (hostclaim.Observed, error) {
129 live, err := execlease.InspectLocal(f.guard.DataDir, host)
130 if err != nil || live.State != "lost" {
131 t.Fatalf("fresh proof without durable denial: %+v %v", live, err)
132 }
133 if mode == "fresh-absent" {
134 return hostclaim.Observed{Host: host, Absent: true}, nil
135 }
136 fresh := o
137 c := *o.Claim
138 c.RetiredIncarnations = nil
139 fresh.Claim = &c
140 return fresh, nil
141 })
142 }
143 actor := hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1"}
144 if mode == "root" || mode == "adopt" {
145 p.Resources.Mode = mode
146 err = p.prepareWith(ctx, actor, o)
147 } else {
148 err = p.clearUntracked(ctx, actor, o, state, cfg)
149 }
150 wantOK := mode == "free" || mode == "free-lock-held" || mode == "free-without-history" || mode == "replacement"
151 if wantOK {
152 if err != nil {
153 t.Fatalf("positive stale cleanup refused: %v", err)
154 }
155 if _, err = os.Stat(path); !errors.Is(err, os.ErrNotExist) {
156 t.Fatalf("stale state retained: %v", err)
157 }
158 } else {
159 if err == nil {
160 t.Fatal("unproven stale cleanup accepted")
161 }
162 if _, err = os.Stat(path); err != nil {
163 t.Fatalf("refusal removed local denial: %v", err)
164 }
165 }
166 })
167 }
168}
169