package host import ( "context" "encoding/json" "errors" "github.com/carlosframework/platform/internal/hostfiles" "strings" "testing" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/lease/leasetest" ) func TestPlan14UnlaunchedBootstrapRecovery(t *testing.T) { for _, mode := range []string{"empty", "lease-only", "platform-only", "tenant-files", "unreferenced-file", "published", "deleted-publication", "missing-receipt", "wrong-stamp", "wrong-session", "ordinary-stop", "covered", "foreign-box", "view-error"} { t.Run(mode, func(t *testing.T) { a := hostclaim.AdmissionRecord{Session: 1, Box: "box", Mode: "cold", Region: "ie", Generation: "first"} h := &hostclaim.Holder{Incarnation: "inc", Stamp: 1, Session: 1, Generation: "first", WriterBox: "box", Location: "location", Admissions: []hostclaim.AdmissionRecord{a}} o := hostclaim.Observed{Host: "shop.example", Claim: &hostclaim.Claim{Holder: h}} target := hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: 1, Box: "box", Enrollment: "enrollment", FenceID: "fence"} if mode == "wrong-stamp" { target.Stamp++ } if mode == "wrong-session" { target.Session++ } receipt := StopReceipt{Kind: StopReceiptStopped, Host: target.Host, Incarnation: target.Incarnation, InstallationStamp: target.Stamp, WriterSession: target.Session, FenceID: target.FenceID, CgroupIdentities: []string{"unlaunched:" + digestStop(target)}} if mode == "ordinary-stop" { receipt.CgroupIdentities = []string{"empty-cgroup"} } if mode == "covered" { h.Admissions[0].Covered = true } if mode == "foreign-box" { h.Admissions[0].Box = "other" } raw, _ := json.Marshal(receipt) view := hostguard.FenceView{Installation: hostguard.RootInstallation{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Box: "box", Enrollment: "enrollment"}, Receipts: []hostguard.FenceReceipt{{Target: target, Bytes: raw}}} if mode == "missing-receipt" { view.Receipts = nil } g := hostguard.Guard{FenceView: func(context.Context, string, hostguard.Captured) (hostguard.FenceView, error) { if mode == "view-error" { return hostguard.FenceView{}, errors.New("unavailable") } return view, nil }} data := leasetest.NewVersionedBucket() data.AddBucket("data") if mode == "lease-only" { data.Put("data", "location/lock.json", []byte("lease")) } if mode == "published" || mode == "deleted-publication" { data.Put("data", "location/generations/first/manifest.json", []byte("published")) } if mode == "deleted-publication" { data.BareDelete("data", "location/generations/first/manifest.json") } if mode == "platform-only" || mode == "tenant-files" || mode == "unreferenced-file" { prefix := movesFilesPrefix(h.Location, a.Generation) object := prefix + "objects/0001-" + strings.Repeat("a", 64) data.Put("data", object, []byte("env")) m := hostfiles.Manifest{V: hostfiles.ManifestVersion, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{hostfiles.EnvItem: {Object: object, SHA256: strings.Repeat("a", 64), Size: 3}}} if mode == "tenant-files" { m.Entries["tenant.txt"] = m.Platform[hostfiles.EnvItem] } if mode == "unreferenced-file" { data.Put("data", prefix+"objects/unreferenced", []byte("foreign")) } raw, _ := json.Marshal(m) data.Put("data", hostfiles.ManifestKey(prefix), raw) } s := &claimServingOwners{Data: data, Bucket: "data", emptyRecovery: rootEmptyRecovery(g)} yes, err := s.emptyBootstrap(t.Context(), o) want := mode == "empty" || mode == "lease-only" || mode == "platform-only" if (yes && err == nil) != want { t.Fatalf("recovery=%v err=%v", yes, err) } if want { h.Admissions = append(h.Admissions, hostclaim.AdmissionRecord{Session: 2, Box: "box", Mode: "cold", Region: "ie", Generation: "second", Predecessor: "first"}) if yes, err := s.emptyBootstrap(t.Context(), o); yes && err == nil { t.Fatal("unproved second admission accepted") } h.Stamp = 2 view.Installation.Stamp = 2 view.Retired = []hostguard.Captured{{Incarnation: h.Incarnation, Stamp: 1}} if yes, err := s.emptyRecovery(t.Context(), o, h.Admissions[:1]); !yes || err != nil { t.Fatal("proved predecessor refused after handover", err) } view.Retired = nil if yes, err := s.emptyRecovery(t.Context(), o, h.Admissions[:1]); yes && err == nil { t.Fatal("unretained installation accepted") } } }) } }