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 "os"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/activator"
9 "github.com/carlosframework/platform/internal/provision"
10 "github.com/carlosframework/platform/internal/registry"
11 "github.com/carlosframework/platform/internal/release"
12)
13
14func e7RecoveryOptions(f *r2Live) []release.PassOption {
15 n := namedUnitRecovery{source: f.source, pub: f.pub, readLive: os.Readlink}
16 return []release.PassOption{release.WithRecovery(func(ctx context.Context, rt registry.Route) (bool, error) {
17 return n.recover(ctx, rt.Host, f.c, func(string) string { return "5s" }, f.e.root.reg)
18 }, f.c.allowLegacy)}
19}
20
21func TestR2UnguardedERecoveryUpdate(t *testing.T) {
22 if !plan14RootFixture(t) {
23 return
24 }
25 for _, shape := range []string{"no record", "unavailable history", "empty unit", "socket unit", "list failure", "ordinary", "derived", "exec"} {
26 t.Run(shape, func(t *testing.T) {
27 f := r2Fixture(t, "none")
28 var failed *r2ListFailure
29 switch shape {
30 case "empty unit", "socket unit":
31 unit := ""
32 if shape == "socket unit" {
33 unit = "custom.service"
34 }
35 plan14PutRecord(t, f.e.store, provision.Record{Schema: 1, Host: f.rt.Host, Account: "bab", App: f.rt.App, Channel: f.rt.Channel, Unit: unit})
36 case "list failure":
37 failed = &r2ListFailure{Store: f.e.store}
38 f.source.store = failed
39 case "ordinary", "derived", "exec":
40 f.rt.Unit = ""
41 f.rt.Backing = registry.BackingUnit
42 if shape == "derived" {
43 f.rt.Unit = activator.UnitName(f.rt.Host)
44 }
45 if shape == "exec" {
46 f.rt.Backing = "exec"
47 }
48 if err := f.e.root.reg.Delete(f.rt.Host); err != nil {
49 t.Fatal(err)
50 }
51 if err := f.e.root.reg.Upsert(f.rt); err != nil {
52 t.Fatal(err)
53 }
54 failed = &r2ListFailure{Store: f.e.store}
55 f.source.store = failed
56 }
57 opts := e7RecoveryOptions(f)
58 if shape == "unavailable history" {
59 opts = append(opts, release.WithRootHistory(nil))
60 }
61 if err := f.pass(t, opts...); err != nil {
62 t.Fatal(err)
63 }
64 f.wantLive(t, "v2")
65 f.wantRunning(t, "v2")
66 if failed != nil && shape != "list failure" && failed.lists != 0 {
67 t.Fatalf("ordinary recovery made %d store calls", failed.lists)
68 }
69 })
70 }
71}
72
73func TestR2UnguardedERecoveryIncidentRecipe(t *testing.T) {
74 if !plan14RootFixture(t) {
75 return
76 }
77 for _, before := range []bool{false, true} {
78 t.Run(map[bool]string{false: "reset after", true: "reset before"}[before], func(t *testing.T) {
79 f := r2Fixture(t, "none")
80 if err := release.StageArtifact(f.u.opt, "", "v2", f.rt.App, []byte("new build")); err != nil {
81 t.Fatal(err)
82 }
83 if err := release.PointLive(f.u.opt, f.rt.Host, "", "v2", f.rt.App); err != nil {
84 t.Fatal(err)
85 }
86 if err := f.e.root.reg.SetVersions(f.rt.Host, "v2", "v1"); err != nil {
87 t.Fatal(err)
88 }
89 f.u.exe = release.VersionPath(f.u.opt, "", "v2", f.rt.App)
90 f.u.props.ActiveState, f.u.props.SubState, f.u.props.MainPID = "failed", "failed", 0
91 if before {
92 if err := f.u.ResetFailed(f.rt.Unit); err != nil {
93 t.Fatal(err)
94 }
95 }
96 f.promote(t, "v1")
97 if err := f.pass(t, e7RecoveryOptions(f)...); err != nil {
98 t.Fatal(err)
99 }
100 f.wantLive(t, "v1")
101 if f.u.props.MainPID != 0 {
102 t.Fatal("recovery woke stopped console")
103 }
104 if !before {
105 if err := f.u.ResetFailed(f.rt.Unit); err != nil {
106 t.Fatal(err)
107 }
108 }
109 if err := f.u.Restart(f.rt.Unit); err != nil {
110 t.Fatal(err)
111 }
112 f.wantRunning(t, "v1")
113 })
114 }
115}
116
117func TestR2ERecoveryKeepsCensusBoundary(t *testing.T) {
118 if !plan14RootFixture(t) {
119 return
120 }
121 for _, unreadable := range []bool{false, true} {
122 t.Run(map[bool]string{false: "required", true: "unreadable"}[unreadable], func(t *testing.T) {
123 f := r2Fixture(t, "none")
124 f.c.legacy = func() (bool, error) {
125 if unreadable {
126 return false, os.ErrPermission
127 }
128 return false, nil
129 }
130 opts := append(e7RecoveryOptions(f), release.WithRootHistory(nil))
131 if err := f.pass(t, opts...); err == nil {
132 t.Fatal("strict census path selected legacy fallback")
133 }
134 f.wantLive(t, "v1")
135 if len(f.u.effects) != 0 || len(f.u.tries) != 0 {
136 t.Fatal("strict census path restarted", f.u.effects, f.u.tries)
137 }
138 })
139 }
140}
141
142func TestR2ERecoveryFlagWithoutCensus(t *testing.T) {
143 if !plan14RootFixture(t) {
144 return
145 }
146 f := r2Fixture(t, "none")
147 t.Setenv("CARLOS_HOST_CLAIM", "1")
148 if legacy, err := legacyRootRestart(); err != nil || !legacy {
149 t.Fatalf("flag selected bound effects without a census: %v %v", legacy, err)
150 }
151 n := namedUnitRecovery{source: f.source, pub: f.pub, readLive: os.Readlink}
152 handled, err := n.recover(t.Context(), f.rt.Host, f.c, func(string) string { return "5s" }, f.e.root.reg)
153 if err != nil || handled {
154 t.Fatalf("E claimed no-census route: %v %v", handled, err)
155 }
156}
157