package host import ( "context" "errors" "os" "os/exec" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" ) func TestPlan14FreshInstallProcessCrash(t *testing.T) { t.Parallel() if boundary := os.Getenv("PLAN14_CREATION_CRASH"); boundary != "" { dir := os.Getenv("PLAN14_CREATION_DIR") root, disk := plan14InventoryProcessRoot(t, dir) hs := &rollbackDisk{dir: filepath.Join(dir, "history")} h := &release.RootHistory{Store: hs} root.history = func(_ context.Context, fn func(*release.RootHistory) error) error { return fn(h) } count := 0 disk.afterWrite = func(string, []byte) { count++ if boundary == "intent" && count == 1 || boundary == "committed" && count == 2 { os.Exit(73) } } hs.afterWrite = func(string, []byte) { if boundary == "creation" { os.Exit(73) } } obs, err := hostclaim.Read(t.Context(), root.Stop.Coordinator.Evidence, recoveryHost) if err != nil { t.Fatal(err) } holder := obs.Claim.Holder _, etag, err := root.Stop.Coordinator.Evidence.Get(t.Context(), holder.Key) if err != nil { t.Fatal(err) } _, client := hostbox.RemoteJournals(root.commitRootInventory) _, err = client.WriteExpected(t.Context(), hostbox.StopInventory{Complete: true, Host: obs.Host, Incarnation: holder.Incarnation, InstallationStamp: holder.Stamp, Box: "box-1", Enrollment: "en", DirectoryRevision: "1"}, hostbox.InventoryExpectation{RecordKey: holder.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: obs.Host, Incarnation: holder.Incarnation, Stamp: holder.Stamp, Session: holder.Session, Box: "box-1", Enrollment: "en", ClaimETag: obs.ETag, Op: obs.Claim.Op.ID, Phase: obs.Claim.Op.Phase}}) t.Fatal("boundary not reached", boundary, err) } for _, boundary := range []string{"intent", "creation", "committed"} { t.Run(boundary, func(t *testing.T) { dir := t.TempDir() for _, name := range []string{"root", "data", "opt", "units", "locks", "child", "history"} { if err := os.Mkdir(filepath.Join(dir, name), 0700); err != nil { t.Fatal(err) } } f := newRecoveryFixture(t) obs := f.read() holder := obs.Claim.Holder plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: obs.Host, Account: holder.Account, App: holder.App, Channel: "stable", CreatedAt: f.now}) store := release.NewDirStore(filepath.Join(dir, "deployment")) keys, err := f.claims.List(t.Context(), "") if err != nil { t.Fatal(err) } for _, key := range keys { raw, _, err := f.claims.Get(t.Context(), key) if err != nil { t.Fatal(err) } if _, err = store.Put(t.Context(), key, raw, ""); err != nil { t.Fatal(err) } } cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14FreshInstallProcessCrash$") cmd.Env = append(os.Environ(), "PLAN14_CREATION_CRASH="+boundary, "PLAN14_CREATION_DIR="+dir, "TMPDIR="+filepath.Join(dir, "child")) output, err := cmd.CombinedOutput() var exit *exec.ExitError if !errors.As(err, &exit) || exit.ExitCode() != 73 { t.Fatalf("child: %v %s", err, output) } root, _ := plan14InventoryProcessRoot(t, dir) h := &release.RootHistory{Store: &rollbackDisk{dir: filepath.Join(dir, "history")}} root.history = func(_ context.Context, fn func(*release.RootHistory) error) error { return fn(h) } if err := root.recoverRootInventory(t.Context()); err != nil { t.Fatal(err) } view, err := root.Stop.Coordinator.Guard.Root.View(t.Context(), obs.Host, hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp}) if err != nil { t.Fatal(err) } b := release.HistoryBinding{Host: obs.Host, Account: holder.Account, App: holder.App} fresh, err := h.FreshInstallation(b, rootHistoryInstallation(view.Installation)) if err != nil || !fresh || view.Installation.InventoryRevision != 1 { t.Fatalf("recovered: %+v fresh=%v: %v", view, fresh, err) } }) } }