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 "encoding/json"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/hostbox"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/release"
14)
15
16func plan14RebindProcessRoot(t *testing.T, dir string) (ClaimRootHTTP, *rollbackDisk, recoveryRebindRequest) {
17 root, disk := plan14InventoryProcessRoot(t, dir)
18 root.InventoryActor = func(context.Context) (hostbox.Actor, error) {
19 return hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 7, Binding: "7"}, nil
20 }
21 raw, err := os.ReadFile(filepath.Join(dir, "request.json"))
22 if err != nil {
23 t.Fatal(err)
24 }
25 var q recoveryRebindRequest
26 if err = json.Unmarshal(raw, &q); err != nil {
27 t.Fatal(err)
28 }
29 return root, disk, q
30}
31func TestPlan14RecoveryRebindProcessCrash(t *testing.T) {
32 t.Parallel()
33 if phase := os.Getenv("PLAN14_REBIND_CRASH"); phase != "" {
34 root, disk, q := plan14RebindProcessRoot(t, os.Getenv("PLAN14_REBIND_DIR"))
35 writes := 0
36 disk.beforeWrite = func(string, []byte) {
37 if phase == "before intent" && writes == 0 || phase == "database" && writes == 1 {
38 os.Exit(73)
39 }
40 }
41 disk.afterWrite = func(string, []byte) {
42 writes++
43 if phase == "intent" && writes == 1 || phase == "commit" && writes == 2 {
44 os.Exit(73)
45 }
46 }
47 err := root.rebindRecoveryInventory(t.Context(), q)
48 t.Fatal("crash not reached", phase, err)
49 }
50 for _, phase := range []string{"before intent", "intent", "database", "commit"} {
51 t.Run(phase, func(t *testing.T) {
52 f, original, disk, q := plan14RecoveryRebind(t)
53 dir := t.TempDir()
54 for _, name := range []string{"root", "data", "opt", "units", "locks", "child"} {
55 if err := os.Mkdir(filepath.Join(dir, name), 0700); err != nil {
56 t.Fatal(err)
57 }
58 }
59 store := release.NewDirStore(filepath.Join(dir, "deployment"))
60 keys, err := f.claims.List(t.Context(), "")
61 if err != nil {
62 t.Fatal(err)
63 }
64 for _, key := range keys {
65 body, _, e := f.claims.Get(t.Context(), key)
66 if e != nil {
67 t.Fatal(e)
68 }
69 if _, e = store.Put(t.Context(), key, body, ""); e != nil {
70 t.Fatal(e)
71 }
72 }
73 raw, err := disk.Read(q.Next.Host + ".json")
74 if err != nil {
75 t.Fatal(err)
76 }
77 if err = os.WriteFile(filepath.Join(dir, "root", q.Next.Host+".json"), raw, 0600); err != nil {
78 t.Fatal(err)
79 }
80 raw, _ = json.Marshal(q)
81 if err = os.WriteFile(filepath.Join(dir, "request.json"), raw, 0600); err != nil {
82 t.Fatal(err)
83 }
84 root, _, _ := plan14RebindProcessRoot(t, dir)
85 source, _, err := original.Reconcile.Inventory.Snapshot(t.Context(), q.Next.Host, q.Prior)
86 if err != nil {
87 t.Fatal(err)
88 }
89 if err = root.Reconcile.Inventory.WriteStopInventory(t.Context(), source); err != nil {
90 t.Fatal(err)
91 }
92 cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^TestPlan14RecoveryRebindProcessCrash$")
93 cmd.Dir = filepath.Join(dir, "child")
94 cmd.Env = append(os.Environ(), "PLAN14_REBIND_CRASH="+phase, "PLAN14_REBIND_DIR="+dir)
95 output, err := cmd.CombinedOutput()
96 exit, ok := err.(*exec.ExitError)
97 if !ok || exit.ExitCode() != 73 {
98 t.Fatalf("crash %v: %s", err, output)
99 }
100 resumed, _, _ := plan14RebindProcessRoot(t, dir)
101 if err = resumed.recoverRootInventory(t.Context()); err != nil {
102 t.Fatal("startup recovery", err)
103 }
104 if err = resumed.rebindRecoveryInventory(t.Context(), q); err != nil {
105 t.Fatal("replay", err)
106 }
107 view, err := resumed.Stop.Coordinator.Guard.Root.View(t.Context(), q.Next.Host, hostguard.Captured{Incarnation: q.Next.Incarnation, Stamp: q.Next.Stamp})
108 if err != nil || view.Installation.Stamp != q.Next.Stamp || len(view.Retired) != 1 || view.Retired[0] != q.Prior {
109 t.Fatal(view, err)
110 }
111 _, rev, err := resumed.Reconcile.Inventory.Snapshot(t.Context(), q.Next.Host, view.Captured)
112 if err != nil || rev != 1 {
113 t.Fatal(rev, err)
114 }
115 })
116 }
117}
118