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 "github.com/carlosframework/platform/internal/activator"
7 "github.com/carlosframework/platform/internal/appconfig"
8 "github.com/carlosframework/platform/internal/provision"
9 "github.com/carlosframework/platform/internal/registry"
10 "github.com/carlosframework/platform/internal/release"
11 "os"
12 "path/filepath"
13 "testing"
14)
15
16type fix2IndexStore struct {
17 release.Store
18 lists int
19 accountFailure bool
20}
21
22func (s *fix2IndexStore) List(ctx context.Context, prefix string) ([]string, error) {
23 s.lists++
24 if prefix == "accounts/" && s.accountFailure {
25 return []string{"accounts/bap/account.json"}, nil
26 }
27 return s.Store.List(ctx, prefix)
28}
29func (s *fix2IndexStore) Get(ctx context.Context, key string) ([]byte, string, error) {
30 if key == "accounts/bap/account.json" && s.accountFailure {
31 s.accountFailure = false
32 return nil, "", os.ErrDeadlineExceeded
33 }
34 return s.Store.Get(ctx, key)
35}
36func TestPlan14Fix2RecordIndexCachesDiscoveryAndRefreshesBodies(t *testing.T) {
37 e, rt, rec, source := plan14ProofFixture(t)
38 store := &fix2IndexStore{Store: e.store}
39 source.store = store
40 source.cacheIndex = true
41 if _, err := source.resolve(t.Context(), rt); err != nil {
42 t.Fatal("record discovery failed", err)
43 }
44 lists := store.lists
45 rec.RecordSeq++
46 plan14PutRecord(t, e.store, rec)
47 first, err := source.resolve(t.Context(), rt)
48 if err != nil {
49 t.Fatal(err)
50 }
51 rec.RecordSeq++
52 plan14PutRecord(t, e.store, rec)
53 second, err := source.resolve(t.Context(), rt)
54 if err != nil {
55 t.Fatal(err)
56 }
57 if first.RecordETag == second.RecordETag || store.lists != lists {
58 t.Fatal("cached bodies or repeated listing", first.RecordETag, second.RecordETag, lists, store.lists)
59 }
60 store.accountFailure = true
61 if _, _, err := provision.FindRecordByHost(t.Context(), store, rt.Host); !errors.Is(err, provision.ErrRecordIndexRetry) || errors.Is(err, os.ErrPermission) {
62 t.Fatal("partial listing not retryable", err)
63 }
64}
65
66func TestPlan14Fix2OrdinaryInstanceUnchanged(t *testing.T) {
67 e, rt, rec, source := plan14ProofFixture(t)
68 rec.Unit = ""
69 rec.Addr = ""
70 rec.Health = nil
71 plan14PutRecord(t, e.store, rec)
72 rt.Unit = "forged.service"
73 if err := e.root.reg.Upsert(rt); err != nil {
74 t.Fatal(err)
75 }
76 unit := activator.UnitName(rt.Host)
77 file := filepath.Join(t.TempDir(), "carlos-app@.service")
78 if err := os.WriteFile(file, []byte("root unit"), 0644); err != nil {
79 t.Fatal(err)
80 }
81 u := &plan14OrdinaryUnits{}
82 u.props = unitProps{Name: unit, ExecPath: release.LiveLink(source.optDir, rt.Host), FragmentPath: file, ActiveState: "active"}
83 checks := rootRestartChecks{dispatch: source.needsRestart, targetFile: os.Open}
84 restart := rootRestartRouteWith(e.root.reg, u, source.optDir, nil, nil, checks)
85 if err := restart(rt.Host); err != nil {
86 t.Fatal(err)
87 }
88 if len(u.effects) != 1 || u.effects[0] != unit {
89 t.Fatal("ordinary unit not restarted", u.effects)
90 }
91 u.props.ActiveState = "inactive"
92 if err := restart(rt.Host); err != nil {
93 t.Fatal(err)
94 }
95 if len(u.effects) != 1 {
96 t.Fatal("operator-stopped ordinary unit restarted")
97 }
98}
99
100func TestPlan14Fix2OrdinaryAdoptionConfigAndRestartRequests(t *testing.T) {
101 t.Parallel()
102 e, rt, u, c, pub := plan14AdoptionFixture(t)
103 if err := e.store.Delete(t.Context(), provision.RecordKey(rt.Account, rt.App, rt.Host)); err != nil {
104 t.Fatal(err)
105 }
106 rt.Unit, rt.Addr, rt.Health = "", "", ""
107 rt.Backing, rt.Socket = registry.BackingUnit, provision.DefaultSocket(rt.Host)
108 if err := e.root.reg.Delete(rt.Host); err != nil {
109 t.Fatal(err)
110 }
111 if err := e.root.reg.Upsert(rt); err != nil {
112 t.Fatal(err)
113 }
114 u.props.Name = activator.UnitName(rt.Host)
115 source := &proofBindingSource{store: e.store, sys: u, optDir: u.opt, openFile: os.Open, unitDirs: []string{filepath.Dir(u.props.FragmentPath)}, namedPath: filepath.Join(t.TempDir(), "absent.json")}
116 c.dispatch = source.needsRestart
117 restart := rootRestartRouteWith(e.root.reg, u, u.opt, nil, nil, c)
118 if err := release.Pass(t.Context(), e.root.reg, e.store, pub, u.opt, restart, release.WithRestartFailurePropagation()); err != nil {
119 t.Fatal("ordinary adoption", err)
120 }
121 if len(u.effects) != 1 || u.exe != release.VersionPath(u.opt, rt.Account, "v2", rt.App) {
122 t.Fatal("ordinary adoption rolled back or missed restart", u.effects, u.exe)
123 }
124 if err := appconfig.SetEnv(t.Context(), e.store, rt.Account, rt.App, map[string]string{"LOG_LEVEL": "debug"}); err != nil {
125 t.Fatal(err)
126 }
127 envDir := t.TempDir()
128 for i := 0; i < 2; i++ {
129 if err := appconfig.Sync(t.Context(), e.root.reg, e.store, nil, envDir, restart, nil); err != nil {
130 t.Fatal("config sync", err)
131 }
132 }
133 if len(u.effects) != 2 {
134 t.Fatal("config restart missing or repeated", u.effects)
135 }
136 routes, err := e.root.reg.List()
137 if err != nil {
138 t.Fatal(err)
139 }
140 marks := filepath.Join(t.TempDir(), "restart-marks.json")
141 restartPass(t.Context(), routes, e.store, marks, restart)
142 askRestart(t, t.Context(), e.store, rt.Account, rt.App)
143 restartPass(t.Context(), routes, e.store, marks, restart)
144 restartPass(t.Context(), routes, e.store, marks, restart)
145 if len(u.effects) != 3 {
146 t.Fatal("restart request missing or repeated", u.effects)
147 }
148}
149