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 host
4
5import (
6 "context"
7 "encoding/json"
8 "errors"
9 "os"
10 "path/filepath"
11 "testing"
12
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostfiles"
15 "github.com/carlosframework/platform/internal/registry"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19// The release flush opens the files directory and reads the env file
20// through instancefs (openat2), so this test is Linux's.
21func TestReleaseFlushFilesCarriesTheLastWakeStateAndEnv(t *testing.T) {
22 ctx := context.Background()
23 data := release.NewDirStore(t.TempDir())
24 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
25 if err != nil {
26 t.Fatal(err)
27 }
28 t.Cleanup(func() { reg.Close() })
29 const host = "shop.example"
30 if err := reg.Upsert(registry.Route{Host: host, Kind: "service", Addr: "10.0.0.1:3000", Status: "ready", Hibernate: true}); err != nil {
31 t.Fatal(err)
32 }
33 dir, envDir := t.TempDir(), t.TempDir()
34 if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hello"), 0o644); err != nil {
35 t.Fatal(err)
36 }
37 if err := os.WriteFile(filepath.Join(envDir, host+".env"), []byte("KEY=v\n"), 0o600); err != nil {
38 t.Fatal(err)
39 }
40 f := ReleaseFlushFiles{Data: data, Registry: reg, FilesDir: func(string) string { return dir }, EnvPath: func(h string) string { return filepath.Join(envDir, h+".env") }}
41 obs := hostclaim.Observed{Host: host, Claim: &hostclaim.Claim{Host: host, Holder: &hostclaim.Holder{Location: "instances/" + host + "/inc", Generation: "g6"}}}
42 first, err := f.Flush(ctx, obs)
43 if err != nil || first == 0 {
44 t.Fatalf("first flush: %d %v", first, err)
45 }
46 // A timer registered immediately before the release, and a new file.
47 if err := reg.ReplaceTimers(host, []registry.Timer{{Host: host, Name: "late", Kind: "schedule", Spec: "0 3 * * *", Path: "/n", NextAt: 5000, CreatedAt: 10, DueAt: 5000, Registration: "reg-late"}}); err != nil {
48 t.Fatal(err)
49 }
50 if err := reg.SetNextDue(host, 7777); err != nil {
51 t.Fatal(err)
52 }
53 if err := os.WriteFile(filepath.Join(dir, "b.txt"), []byte("later"), 0o644); err != nil {
54 t.Fatal(err)
55 }
56 seq, err := f.Flush(ctx, obs)
57 if err != nil || seq <= first {
58 t.Fatalf("second flush: %d %v", seq, err)
59 }
60 prefix := "instances/" + host + "/inc/files/g-g6/"
61 m, ok, err := hostfiles.Load(ctx, data, prefix)
62 if err != nil || !ok || m.Seq != seq {
63 t.Fatalf("manifest: %+v %v %v", m, ok, err)
64 }
65 if _, ok := m.Entries["b.txt"]; !ok {
66 t.Fatal("the file written before the release is not in the manifest")
67 }
68 ws, ok, err := hostfiles.LoadWake(ctx, data, prefix, m, nil)
69 if err != nil || !ok || len(ws.Timers) != 1 || ws.Timers[0].Name != "late" || ws.NextDue != 7777 {
70 t.Fatalf("wake state: %+v %v %v", ws, ok, err)
71 }
72 env, err := hostfiles.ReadPlatform(ctx, data, prefix, m, hostfiles.EnvItem, nil)
73 if err != nil || string(env) != "KEY=v\n" {
74 t.Fatalf("env: %q %v", env, err)
75 }
76 // Nothing changed: the same sequence, still the final state.
77 again, err := f.Flush(ctx, obs)
78 if err != nil || again != seq {
79 t.Fatalf("repeat: %d %v", again, err)
80 }
81 // The env file is removed before a later release: the flush stages its
82 // deletion, so the final state carries no env, not the old KEY=v.
83 if err := os.Remove(filepath.Join(envDir, host+".env")); err != nil {
84 t.Fatal(err)
85 }
86 gone, err := f.Flush(ctx, obs)
87 if err != nil || gone <= seq {
88 t.Fatalf("flush after the env file was removed: %d %v", gone, err)
89 }
90 m, ok, err = hostfiles.Load(ctx, data, prefix)
91 if err != nil || !ok || m.Seq != gone {
92 t.Fatalf("manifest after removal: %+v %v %v", m, ok, err)
93 }
94 if env, err := hostfiles.ReadPlatform(ctx, data, prefix, m, hostfiles.EnvItem, nil); !errors.Is(err, os.ErrNotExist) {
95 t.Fatalf("a removed env file was still published: %q %v", env, err)
96 }
97 if again, err := f.Flush(ctx, obs); err != nil || again != gone {
98 t.Fatalf("repeat after removal: %d %v", again, err)
99 }
100 var raw map[string]any
101 body, _, _ := data.Get(ctx, hostfiles.ManifestKey(prefix))
102 _ = json.Unmarshal(body, &raw)
103 if raw["v"] != float64(hostfiles.ManifestVersion) {
104 t.Fatalf("manifest version: %v", raw["v"])
105 }
106}
107