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 "errors"
8 "os"
9 "path/filepath"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/hostclaim"
13 "github.com/carlosframework/platform/internal/hostfiles"
14 "github.com/carlosframework/platform/internal/instancekey"
15 "github.com/carlosframework/platform/internal/registry"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19// O5 (instance-key plan rev 3): the release flush publishes the adoption
20// record in the same commit as the env item only when it binds exactly the
21// releasing writer; a stale record, or none, publishes none. A record that
22// does not parse refuses the flush (fix round 1, M9): it may be this
23// writer's, and absence is never evidence.
24func TestReleaseFlushPublishesOnlyTheReleasingWritersAdoption(t *testing.T) {
25 t.Parallel()
26 ctx := context.Background()
27 const host = "shop.example"
28 writer := instancekey.Binding{Host: host, Incarnation: "inc-1", Session: 6, Generation: "g6"}
29 for _, tc := range []struct {
30 name string
31 record []byte // nil is no file
32 want bool
33 refuses bool
34 }{
35 {"the releasing writer's record", instancekey.AdoptionRecord(writer, instancekey.SourcePlatform, instancekey.Digest("k1")), true, false},
36 {"a stale record from session 5", instancekey.AdoptionRecord(instancekey.Binding{Host: host, Incarnation: "inc-1", Session: 5, Generation: "g5"}, instancekey.SourcePlatform, instancekey.Digest("k1")), false, false},
37 {"a malformed record", []byte(`{"v":1}`), false, true},
38 {"no record", nil, false, false},
39 } {
40 t.Run(tc.name, func(t *testing.T) {
41 data := release.NewDirStore(t.TempDir())
42 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
43 if err != nil {
44 t.Fatal(err)
45 }
46 t.Cleanup(func() { reg.Close() })
47 if err := reg.Upsert(registry.Route{Host: host, Kind: "service", Addr: "10.0.0.1:3000", Status: "ready", Hibernate: true}); err != nil {
48 t.Fatal(err)
49 }
50 dir, envDir := t.TempDir(), t.TempDir()
51 if err := os.WriteFile(filepath.Join(envDir, host+".env"), []byte("CARLOS_INSTANCE_KEY=k1\n"), 0o600); err != nil {
52 t.Fatal(err)
53 }
54 if tc.record != nil {
55 if err := os.WriteFile(filepath.Join(envDir, host+instancekey.AdoptionSuffix), tc.record, 0o600); err != nil {
56 t.Fatal(err)
57 }
58 }
59 f := ReleaseFlushFiles{Data: data, Registry: reg, FilesDir: func(string) string { return dir }, EnvPath: func(h string) string { return filepath.Join(envDir, h+".env") }}
60 obs := hostclaim.Observed{Host: host, Claim: &hostclaim.Claim{Host: host, Holder: &hostclaim.Holder{Location: "instances/" + host + "/inc", Incarnation: "inc-1", Session: 6, Generation: "g6"}}}
61 seq, err := f.Flush(ctx, obs)
62 if tc.refuses {
63 if !errors.Is(err, ErrClaimStopUnavailable) {
64 t.Fatalf("a malformed record was flushed: %v", err)
65 }
66 if _, ok, err := hostfiles.Load(ctx, data, "instances/"+host+"/inc/files/g-g6/"); ok || err != nil {
67 t.Fatalf("a refused flush committed a manifest: %v %v", ok, err)
68 }
69 return
70 }
71 if err != nil {
72 t.Fatal(err)
73 }
74 prefix := "instances/" + host + "/inc/files/g-g6/"
75 m, ok, err := hostfiles.Load(ctx, data, prefix)
76 if err != nil || !ok || m.Seq != seq {
77 t.Fatalf("manifest: %v %v", ok, err)
78 }
79 got, err := hostfiles.ReadPlatform(ctx, data, prefix, m, instancekey.AdoptionItem, nil)
80 if tc.want && (err != nil || string(got) != string(tc.record)) {
81 t.Fatalf("the writer's record was not published: %q %v", got, err)
82 }
83 if !tc.want && err == nil {
84 t.Fatalf("a record not the releasing writer's was published: %q", got)
85 }
86 })
87 }
88}
89
90// Fix round 1, M6: a host with no paths (no route, or a route that could
91// not be read) is refused before anything is staged, never flushed with an
92// empty env path read as "no env file".
93func TestReleaseFlushRefusesAHostWithNoPaths(t *testing.T) {
94 ctx := context.Background()
95 const host = "shop.example"
96 data := release.NewDirStore(t.TempDir())
97 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
98 if err != nil {
99 t.Fatal(err)
100 }
101 t.Cleanup(func() { reg.Close() })
102 paths := relocationHostPaths(t.TempDir(), reg)
103 if p := paths(host); p.FilesDir != "" || p.EnvPath != "" {
104 t.Fatalf("paths without a route: %+v", p)
105 }
106 f := ReleaseFlushFiles{Data: data, Registry: reg, FilesDir: func(h string) string { return paths(h).FilesDir }, EnvPath: func(h string) string { return paths(h).EnvPath }}
107 obs := hostclaim.Observed{Host: host, Claim: &hostclaim.Claim{Host: host, Holder: &hostclaim.Holder{Location: "instances/" + host + "/inc", Incarnation: "inc-1", Session: 6, Generation: "g6"}}}
108 if _, err := f.Flush(ctx, obs); !errors.Is(err, ErrClaimStopUnavailable) {
109 t.Fatalf("flushed with no paths: %v", err)
110 }
111 if _, ok, err := hostfiles.Load(ctx, data, "instances/"+host+"/inc/files/g-g6/"); ok || err != nil {
112 t.Fatalf("a refused flush committed a manifest: %v %v", ok, err)
113 }
114}
115