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 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "github.com/carlosframework/platform/internal/registry"
9 "github.com/carlosframework/platform/internal/release"
10 "os"
11 "path/filepath"
12 "strings"
13 "testing"
14 "time"
15)
16
17func TestClaimReviewFlagOffAdoptPreservesDrift(t *testing.T) {
18 etc := t.TempDir()
19 dir := filepath.Join(etc, "systemd/system")
20 if err := os.MkdirAll(dir, 0755); err != nil {
21 t.Fatal(err)
22 }
23 for _, name := range systemUnitFiles {
24 b, err := unitsFS.ReadFile("units/" + name)
25 if err != nil {
26 t.Fatal(err)
27 }
28 if name == claimControlUnit {
29 b = append(b, []byte("\n# operator customization\n")...)
30 }
31 if err := os.WriteFile(filepath.Join(dir, name), b, 0644); err != nil {
32 t.Fatal(err)
33 }
34 }
35 before, _ := os.ReadFile(filepath.Join(dir, claimControlUnit))
36 calls := 0
37 ctl := func(...string) (string, error) { calls++; return "", nil }
38 probe := func(args ...string) (string, error) {
39 // This box's host.env does not arm db reads, so its db-read socket
40 // is dark (dbReadsSocketWanted), as ensureSystemUnits keeps it.
41 if args[1] == dbReadSocketUnit {
42 if args[0] == "is-active" {
43 return "inactive", systemctlExit(3)
44 }
45 return "disabled", systemctlExit(1)
46 }
47 if args[0] == "is-active" {
48 return "active", nil
49 }
50 return "enabled", nil
51 }
52 ensureSystemUnits(etc, filepath.Join(t.TempDir(), "lock"), ctl, probe, t.Logf)
53 after, err := os.ReadFile(filepath.Join(dir, claimControlUnit))
54 if err != nil || calls != 0 || !bytes.Equal(before, after) {
55 t.Fatalf("flag-off drift: calls=%d changed=%v err=%v", calls, !bytes.Equal(before, after), err)
56 }
57}
58
59func TestClaimReviewMalformedSettingsInstallMissingControl(t *testing.T) {
60 etc := t.TempDir()
61 if err := os.MkdirAll(filepath.Join(etc, "carlos"), 0755); err != nil {
62 t.Fatal(err)
63 }
64 if err := os.WriteFile(filepath.Join(etc, "carlos/host.env"), []byte("CARLOS_HOST_CLAIM='unterminated\n"), 0600); err != nil {
65 t.Fatal(err)
66 }
67 ensureSystemUnits(etc, filepath.Join(t.TempDir(), "lock"), func(...string) (string, error) { return "", nil }, func(...string) (string, error) { return "inactive", nil }, t.Logf)
68 got, err := os.ReadFile(filepath.Join(etc, "systemd/system", claimControlUnit))
69 want, _ := unitsFS.ReadFile("units/" + claimControlUnit)
70 if err != nil || !bytes.Equal(got, want) {
71 t.Fatalf("missing control not restored to main bytes: %v", err)
72 }
73}
74
75func TestClaimReviewBootstrapToleratesHostEnvironment(t *testing.T) {
76 for _, body := range []string{"not-a-setting\n", "CARLOS_X=foo\\\n", "CARLOS_X=\"unterminated\n", "CARLOS_HOST_CLAIM='unterminated\n"} {
77 t.Run(body, func(t *testing.T) {
78 root := t.TempDir()
79 if err := os.MkdirAll(filepath.Join(root, "etc/carlos"), 0755); err != nil {
80 t.Fatal(err)
81 }
82 if err := os.WriteFile(filepath.Join(root, "etc/carlos/host.env"), []byte(body), 0600); err != nil {
83 t.Fatal(err)
84 }
85 if err := bootstrap(root, nil); err != nil {
86 t.Fatalf("flag-off bootstrap failed: %v", err)
87 }
88 got, err := os.ReadFile(filepath.Join(root, "etc/systemd/system", claimControlUnit))
89 want, _ := unitsFS.ReadFile("units/" + claimControlUnit)
90 if err != nil || !bytes.Equal(got, want) {
91 t.Fatalf("not main bytes: %v", err)
92 }
93 })
94 }
95}
96
97func TestClaimReviewSystemStageUsesLiveControlSettings(t *testing.T) {
98 for _, enabled := range []bool{false, true} {
99 t.Run(map[bool]string{false: "off", true: "on"}[enabled], func(t *testing.T) {
100 f := newApplyFixture(t, "stable", "v1.0.0")
101 if err := os.MkdirAll(filepath.Join(f.a.etcDir, "carlos"), 0755); err != nil {
102 t.Fatal(err)
103 }
104 flag := ""
105 if enabled {
106 flag = "1"
107 }
108 if err := os.WriteFile(filepath.Join(f.a.etcDir, "carlos/host.env"), []byte("CARLOS_HOST_CLAIM="+flag+"\n"), 0600); err != nil {
109 t.Fatal(err)
110 }
111 legacy, _ := unitsFS.ReadFile("units/" + claimControlUnit)
112 want := claimUnitContent(claimControlUnit, legacy, claimUnitSettings{enabled: enabled, data: "/data", opt: "/opt/carlos"})
113 livePath := filepath.Join(f.a.etcDir, "systemd/system", claimControlUnit)
114 if err := os.WriteFile(livePath, want, 0644); err != nil {
115 t.Fatal(err)
116 }
117 run := f.a.ops.run
118 f.a.ops.run = func(ctx context.Context, limit time.Duration, name string, args ...string) (string, error) {
119 if len(args) > 2 && args[0] == "bootstrap" {
120 if err := bootstrapWith(args[2], nil, true); err != nil {
121 return "", err
122 }
123 b, err := json.Marshal(currentBootstrapPlan())
124 return string(b), err
125 }
126 return run(ctx, limit, name, args...)
127 }
128 stage := filepath.Join(t.TempDir(), "stage")
129 if _, _, err := f.a.stagePlan(t.Context(), "candidate", stage); err != nil {
130 t.Fatal(err)
131 }
132 stagedPath := filepath.Join(stage, "etc/systemd/system", claimControlUnit)
133 staged, err := os.ReadFile(stagedPath)
134 if err != nil || !bytes.Equal(staged, legacy) {
135 t.Fatalf("stage read live settings: %v", err)
136 }
137 marker := []byte("\n# candidate release setting\n")
138 want = append(want, marker...)
139 if err := os.WriteFile(stagedPath, append(staged, marker...), 0644); err != nil {
140 t.Fatal(err)
141 }
142 if err := os.WriteFile(livePath, want, 0644); err != nil {
143 t.Fatal(err)
144 }
145 if err := f.a.installUnits(t.Context(), stage, nil); err != nil {
146 t.Fatal(err)
147 }
148 got, err := os.ReadFile(livePath)
149 if err != nil || !bytes.Equal(got, want) {
150 t.Errorf("live control rendering changed: %v", err)
151 }
152 if strings.Contains(strings.Join(f.calls, "\n"), "try-restart "+claimControlUnit) {
153 t.Errorf("unchanged control restarted: %v", f.calls)
154 }
155 })
156 }
157}
158
159func TestClaimReviewControlEntryKeepsServiceLifetime(t *testing.T) {
160 fixtureWithoutRootMigrationCensus(t)
161 service, cancel := context.WithCancel(t.Context())
162 defer cancel()
163 var startup context.Context
164 store := release.NewDirStore(t.TempDir())
165 ctx, err := claimEntryContext(service, func(string) string { return "1" }, func(call context.Context) (release.Store, error) {
166 startup = call
167 if _, bounded := call.Deadline(); !bounded {
168 t.Error("store startup has no deadline")
169 }
170 return store, nil
171 })
172 if err != nil {
173 t.Fatal(err)
174 }
175 if _, bounded := ctx.Deadline(); bounded {
176 t.Fatal("service inherited startup deadline")
177 }
178 if startup.Err() != context.Canceled {
179 t.Error("startup context not released")
180 }
181 if ctx.Err() != nil || claimStore(ctx) != store {
182 t.Fatal("startup cancellation lost service or store")
183 }
184 sentinel := errors.New("provider stop")
185 owner := &reviewClaimOwner{open: func(call context.Context) error {
186 if _, bounded := call.Deadline(); bounded {
187 t.Error("owners inherited startup deadline")
188 }
189 if call.Err() != nil {
190 t.Error("owner context canceled before shutdown")
191 }
192 cancel()
193 if call.Err() != context.Canceled {
194 t.Error("owners do not follow service shutdown")
195 }
196 return sentinel
197 }}
198 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
199 if err != nil {
200 t.Fatal(err)
201 }
202 defer reg.Close()
203 _, err = OpenProductionClaimAssembly(ctx, store, ClaimRuntimeResources{Mode: "instance-control", Registry: reg, DataDir: t.TempDir(), OptDir: t.TempDir(), UnitsDir: t.TempDir()}, owner)
204 if !errors.Is(err, sentinel) {
205 t.Fatal(err)
206 }
207}
208
209type reviewClaimOwner struct{ open func(context.Context) error }
210
211func (p *reviewClaimOwner) OpenClaimOwners(ctx context.Context, _ release.Store, _ ClaimRuntimeResources) (ClaimOwnerBindings, error) {
212 return ClaimOwnerBindings{}, p.open(ctx)
213}
214