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 "os"
7 "os/exec"
8 "path/filepath"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/hostclaim"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/release"
17)
18
19func plan14InventoryProcessRoot(t *testing.T, dir string) (ClaimRootHTTP, *rollbackDisk) {
20 t.Helper()
21 store := release.NewDirStore(filepath.Join(dir, "deployment"))
22 journal, err := hostbox.OpenLaunchJournal(filepath.Join(dir, "launches.db"))
23 if err != nil {
24 t.Fatal(err)
25 }
26 t.Cleanup(func() { journal.Close() })
27 journals, err := hostbox.NewJournalService(journal)
28 if err != nil {
29 t.Fatal(err)
30 }
31 disk := &rollbackDisk{dir: filepath.Join(dir, "root")}
32 guard := hostguard.Guard{DataDir: filepath.Join(dir, "data"), LockDir: filepath.Join(dir, "locks"), Claims: hostguard.StoreReader{Store: store}, Root: hostguard.NewRootAuthority(disk)}
33 actor := hostbox.Actor{Box: "box-1", Enrollment: "en", Revision: 1, Binding: "1", Region: "ie"}
34 roots := map[string]string{"data": guard.DataDir, "opt": filepath.Join(dir, "opt"), "units": filepath.Join(dir, "units")}
35 reg := restartTestRegistry(t)
36 census := &hostbox.EmptyInstallationCensus{Inventory: journals.Inventory, Registry: reg, Launches: journal, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1", Roots: roots}
37 return ClaimRootHTTP{Journals: journals, Launches: journal, Roots: roots, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Inventory: journals.Inventory, Census: census, Box: actor.Box, Enrollment: actor.Enrollment}, Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: guard, Evidence: store}}}, disk
38}
39func TestPlan14InventoryProcessCrash(t *testing.T) {
40 t.Parallel()
41 if phase := os.Getenv("PLAN14_INVENTORY_CRASH"); phase != "" {
42 root, disk := plan14InventoryProcessRoot(t, os.Getenv("PLAN14_INVENTORY_DIR"))
43 writes := 0
44 disk.beforeWrite = func(string, []byte) {
45 if phase == "database" && writes == 1 {
46 os.Exit(73)
47 }
48 }
49 disk.afterWrite = func(string, []byte) {
50 writes++
51 if phase == "intent" && writes == 1 || phase == "snapshot" && writes == 2 {
52 os.Exit(73)
53 }
54 }
55 obs, err := hostclaim.Read(t.Context(), root.Stop.Coordinator.Evidence, recoveryHost)
56 if err != nil {
57 t.Fatal(err)
58 }
59 h := obs.Claim.Holder
60 _, etag, err := root.Stop.Coordinator.Evidence.Get(t.Context(), h.Key)
61 if err != nil {
62 t.Fatal(err)
63 }
64 _, client := hostbox.RemoteJournals(root.commitRootInventory)
65 _, err = client.WriteExpected(t.Context(), hostbox.StopInventory{Complete: true, Host: obs.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: "box-1", Enrollment: "en", DirectoryRevision: "1"}, hostbox.InventoryExpectation{RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: obs.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: "box-1", Enrollment: "en", ClaimETag: obs.ETag, Op: obs.Claim.Op.ID, Phase: obs.Claim.Op.Phase}})
66 t.Fatal("crash boundary not reached", phase, err)
67 }
68 for _, phase := range []string{"intent", "database", "snapshot"} {
69 t.Run(phase, func(t *testing.T) {
70 dir := t.TempDir()
71 for _, name := range []string{"root", "data", "opt", "units", "locks", "child"} {
72 if err := os.Mkdir(filepath.Join(dir, name), 0700); err != nil {
73 t.Fatal(err)
74 }
75 }
76 f := newRecoveryFixture(t)
77 obs := f.read()
78 h := obs.Claim.Holder
79 plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: obs.Host, Account: h.Account, App: h.App, Channel: "stable", CreatedAt: f.now})
80 store := release.NewDirStore(filepath.Join(dir, "deployment"))
81 keys, err := f.claims.List(t.Context(), "")
82 if err != nil {
83 t.Fatal(err)
84 }
85 for _, key := range keys {
86 body, _, err := f.claims.Get(t.Context(), key)
87 if err != nil {
88 t.Fatal(err)
89 }
90 if _, err = store.Put(t.Context(), key, body, ""); err != nil {
91 t.Fatal(err)
92 }
93 }
94 cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14InventoryProcessCrash$")
95 cmd.Env = append(os.Environ(), "PLAN14_INVENTORY_CRASH="+phase, "PLAN14_INVENTORY_DIR="+dir, "TMPDIR="+filepath.Join(dir, "child"))
96 output, err := cmd.CombinedOutput()
97 var exit *exec.ExitError
98 if !errors.As(err, &exit) || exit.ExitCode() != 73 {
99 t.Fatalf("child: %v %s", err, output)
100 }
101 root, _ := plan14InventoryProcessRoot(t, dir)
102 root.Journals.Now = func() time.Time { return time.Now().Add(25 * time.Hour) }
103 if err = root.recoverRootInventory(t.Context()); err != nil {
104 t.Fatal(err)
105 }
106 captured := hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp}
107 view, err := root.Stop.Coordinator.Guard.Root.View(t.Context(), obs.Host, captured)
108 if err != nil || view.Installation.InventoryRevision != 1 {
109 t.Fatal(view, err)
110 }
111 _, revision, err := root.Journals.Inventory.Snapshot(t.Context(), obs.Host, captured)
112 if err != nil || revision != 1 {
113 t.Fatal(revision, err)
114 }
115 if err = root.recoverRootInventory(t.Context()); err != nil {
116 t.Fatal("replay", err)
117 }
118 })
119 }
120}
121