| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | "github.com/carlosframework/platform/internal/hostfiles" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "github.com/carlosframework/platform/internal/hostclaim" |
| 12 | "github.com/carlosframework/platform/internal/hostguard" |
| 13 | "github.com/carlosframework/platform/internal/lease/leasetest" |
| 14 | ) |
| 15 | |
| 16 | func TestPlan14UnlaunchedBootstrapRecovery(t *testing.T) { |
| 17 | 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"} { |
| 18 | t.Run(mode, func(t *testing.T) { |
| 19 | a := hostclaim.AdmissionRecord{Session: 1, Box: "box", Mode: "cold", Region: "ie", Generation: "first"} |
| 20 | h := &hostclaim.Holder{Incarnation: "inc", Stamp: 1, Session: 1, Generation: "first", WriterBox: "box", Location: "location", Admissions: []hostclaim.AdmissionRecord{a}} |
| 21 | o := hostclaim.Observed{Host: "shop.example", Claim: &hostclaim.Claim{Holder: h}} |
| 22 | target := hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: 1, Box: "box", Enrollment: "enrollment", FenceID: "fence"} |
| 23 | if mode == "wrong-stamp" { |
| 24 | target.Stamp++ |
| 25 | } |
| 26 | if mode == "wrong-session" { |
| 27 | target.Session++ |
| 28 | } |
| 29 | receipt := StopReceipt{Kind: StopReceiptStopped, Host: target.Host, Incarnation: target.Incarnation, InstallationStamp: target.Stamp, WriterSession: target.Session, FenceID: target.FenceID, CgroupIdentities: []string{"unlaunched:" + digestStop(target)}} |
| 30 | if mode == "ordinary-stop" { |
| 31 | receipt.CgroupIdentities = []string{"empty-cgroup"} |
| 32 | } |
| 33 | if mode == "covered" { |
| 34 | h.Admissions[0].Covered = true |
| 35 | } |
| 36 | if mode == "foreign-box" { |
| 37 | h.Admissions[0].Box = "other" |
| 38 | } |
| 39 | raw, _ := json.Marshal(receipt) |
| 40 | 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}}} |
| 41 | if mode == "missing-receipt" { |
| 42 | view.Receipts = nil |
| 43 | } |
| 44 | g := hostguard.Guard{FenceView: func(context.Context, string, hostguard.Captured) (hostguard.FenceView, error) { |
| 45 | if mode == "view-error" { |
| 46 | return hostguard.FenceView{}, errors.New("unavailable") |
| 47 | } |
| 48 | return view, nil |
| 49 | }} |
| 50 | data := leasetest.NewVersionedBucket() |
| 51 | data.AddBucket("data") |
| 52 | if mode == "lease-only" { |
| 53 | data.Put("data", "location/lock.json", []byte("lease")) |
| 54 | } |
| 55 | if mode == "published" || mode == "deleted-publication" { |
| 56 | data.Put("data", "location/generations/first/manifest.json", []byte("published")) |
| 57 | } |
| 58 | if mode == "deleted-publication" { |
| 59 | data.BareDelete("data", "location/generations/first/manifest.json") |
| 60 | } |
| 61 | if mode == "platform-only" || mode == "tenant-files" || mode == "unreferenced-file" { |
| 62 | prefix := movesFilesPrefix(h.Location, a.Generation) |
| 63 | object := prefix + "objects/0001-" + strings.Repeat("a", 64) |
| 64 | data.Put("data", object, []byte("env")) |
| 65 | 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}}} |
| 66 | if mode == "tenant-files" { |
| 67 | m.Entries["tenant.txt"] = m.Platform[hostfiles.EnvItem] |
| 68 | } |
| 69 | if mode == "unreferenced-file" { |
| 70 | data.Put("data", prefix+"objects/unreferenced", []byte("foreign")) |
| 71 | } |
| 72 | raw, _ := json.Marshal(m) |
| 73 | data.Put("data", hostfiles.ManifestKey(prefix), raw) |
| 74 | } |
| 75 | s := &claimServingOwners{Data: data, Bucket: "data", emptyRecovery: rootEmptyRecovery(g)} |
| 76 | yes, err := s.emptyBootstrap(t.Context(), o) |
| 77 | want := mode == "empty" || mode == "lease-only" || mode == "platform-only" |
| 78 | if (yes && err == nil) != want { |
| 79 | t.Fatalf("recovery=%v err=%v", yes, err) |
| 80 | } |
| 81 | if want { |
| 82 | h.Admissions = append(h.Admissions, hostclaim.AdmissionRecord{Session: 2, Box: "box", Mode: "cold", Region: "ie", Generation: "second", Predecessor: "first"}) |
| 83 | if yes, err := s.emptyBootstrap(t.Context(), o); yes && err == nil { |
| 84 | t.Fatal("unproved second admission accepted") |
| 85 | } |
| 86 | h.Stamp = 2 |
| 87 | view.Installation.Stamp = 2 |
| 88 | view.Retired = []hostguard.Captured{{Incarnation: h.Incarnation, Stamp: 1}} |
| 89 | if yes, err := s.emptyRecovery(t.Context(), o, h.Admissions[:1]); !yes || err != nil { |
| 90 | t.Fatal("proved predecessor refused after handover", err) |
| 91 | } |
| 92 | view.Retired = nil |
| 93 | if yes, err := s.emptyRecovery(t.Context(), o, h.Admissions[:1]); yes && err == nil { |
| 94 | t.Fatal("unretained installation accepted") |
| 95 | } |
| 96 | |
| 97 | } |
| 98 | }) |
| 99 | } |
| 100 | } |
| 101 | |