package hostresolve import ( "encoding/json" "fmt" "reflect" "slices" "testing" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostfloor" "github.com/carlosframework/platform/internal/hostquarantine" "github.com/carlosframework/platform/internal/status" ) func TestReadmitClearsEveryCoveredHostRevision(t *testing.T) { for _, fault := range []string{"none", "uncovered-revision", "missing-loss-reference", "future-revision"} { t.Run(fault, func(t *testing.T) { ctx := t.Context() s, store, now, first := resolveFixture(t) hosts := []string{first, "zzz.bab.oncarlos.com"} other, err := hostclaim.Read(ctx, store, hosts[1]) if err != nil { t.Fatal(err) } n := 0 _, err = hostclaim.Update(ctx, store, other, hostclaim.Adopt(hostclaim.NewHolder{Family: "instance", Key: "apps/bab/jam/instances/" + hosts[1] + ".json", Account: "bab", App: "jam", Host: hosts[1], Region: "ie", RecordRegion: "ie"}, "live", nil, []string{"dead"}, "discovery", func() string { n++; return fmt.Sprintf("other-%d", n) })) if err != nil { t.Fatal(err) } var losses []hostclaim.LossRecord for _, host := range hosts { in := submitted(t, s, now, host) if _, err := s.Declare(ctx, "operator", host, in); err != nil { t.Fatal(err) } loss, err := hostclaim.ReadLossRecord(ctx, store, s.Directory.PublicKey, in.Confirmation) if err != nil { t.Fatal(err) } losses = append(losses, loss.Record) } prepareReadmission(t, s, *now, losses[1]) key, err := hostquarantine.RetirementKey("dead", losses[1].Enrollment, 2) if err != nil { t.Fatal(err) } raw, etag, err := store.Get(ctx, key) if err != nil { t.Fatal(err) } var receipt hostquarantine.RetirementCompletion if err := json.Unmarshal(raw, &receipt); err != nil { t.Fatal(err) } receipt.Entries = append(receipt.Entries, hostquarantine.RetirementEntry{Host: first, Incarnation: losses[0].HolderIncarnation, Stamp: losses[0].HolderStamp, RetirementID: "earlier-retirement", JournalSHA256: receipt.Entries[0].JournalSHA256}) raw, err = json.Marshal(receipt) if err != nil { t.Fatal(err) } if _, err = store.Put(ctx, key, raw, etag); err != nil { t.Fatal(err) } digest := hostfloor.Digest(raw) raw, etag, err = store.Get(ctx, "status/boxes/dead.json") if err != nil { t.Fatal(err) } var report status.Box if err := json.Unmarshal(raw, &report); err != nil { t.Fatal(err) } report.Metrics.RetirementReceiptSHA256 = digest raw, err = json.Marshal(report) if err != nil { t.Fatal(err) } if _, err = store.Put(ctx, "status/boxes/dead.json", raw, etag); err != nil { t.Fatal(err) } if fault != "none" { other, err = hostclaim.Read(ctx, store, hosts[1]) if err != nil { t.Fatal(err) } _, err = hostclaim.Update(ctx, store, other, func(c *hostclaim.Claim) error { switch fault { case "uncovered-revision": c.LostRevisions["dead"] = 1 case "missing-loss-reference": c.LossRefs = nil case "future-revision": c.LostRevisions["dead"] = 3 } return nil }) if err != nil { t.Fatal(err) } } before := make([]hostclaim.Observed, len(hosts)) for i, host := range hosts { before[i], err = hostclaim.Read(ctx, store, host) if err != nil { t.Fatal(err) } } roster, err := s.Directory.Read(ctx) if err != nil { t.Fatal(err) } box := roster.Roster.Boxes["dead"] if box.QuarantineRevision != 2 { t.Fatal("expected two retained revisions", box) } err = s.Readmit(ctx, "dead", box.Enrollment, box.QuarantineRevision, box.LossIDs) if (err == nil) != (fault == "none") { t.Fatalf("Readmit = %v, fault = %s", err, fault) } for i, host := range hosts { after, err := hostclaim.Read(ctx, store, host) if err != nil { t.Fatal(err) } if fault != "none" { if after.ETag != before[i].ETag { t.Fatal("invalid inventory changed a claim", host) } continue } if slices.Contains(after.Claim.Lost, "dead") || after.Claim.LostRevisions["dead"] != 0 { t.Errorf("readmission stranded %s at revision %d", host, after.Claim.LostRevisions["dead"]) } if !reflect.DeepEqual(after.Claim.LossRefs, before[i].Claim.LossRefs) { t.Fatal("retained loss references changed", host) } } after, err := s.Directory.Read(ctx) if err != nil { t.Fatal(err) } if fault != "none" && after.ETag != roster.ETag { t.Fatal("invalid inventory cleared global quarantine") } if fault == "none" && after.Roster.Boxes["dead"].State != "active" { t.Fatal("box was not readmitted") } }) } }