package hostclaim import ( "errors" "reflect" "strconv" "testing" "time" ) func TestPlan12TargetFenceRelease(t *testing.T) { for _, kind := range []string{KindLanding, KindRestoring} { for _, mode := range []string{"ok", "wrong-op", "wrong-target", "wrong-enrollment", "wrong-region", "unverified", "zero-proof", "stale", "committed", "partial-return", "parks", "no-grant"} { t.Run(kind+"/"+mode, func(t *testing.T) { s, o, ref, a := c7Fixture(t) var err error o, err = Update(t.Context(), s, o, func(c *Claim) error { c.Obligations = nil if mode == "parks" { for _, owner := range []string{"box:adi-knitting", "box:other", "region:ie"} { id := "existing-" + strconv.Itoa(len(c.Obligations)) c.Obligations = append(c.Obligations, Obligation{ID: id, Op: c.Op.ID, Kind: "park-generation", Generation: id, Region: "ie", Owner: owner, Retirement: id}) } } c.Op.Kind, c.Op.Phase, c.Op.Exec = kind, PhaseProbing, "adi-knitting" c.Op.Participants = []string{"bri-knitting", "adi-knitting"} c.Op.Intent["target"], c.Op.Intent["source"], c.Op.Intent["to"], c.Op.Intent["target_enrollment"] = "adi-knitting", "bri-knitting", "ie", ref.Enrollment setJournal(c, JournalSourceStamp, "1") setJournal(c, JournalSrcGen, "src") if kind == KindRestoring { c.Op.Phase = PhaseReleasing c.Op.Intent["rollback"] = lostRB setJournal(c, JournalTargetStamp, strconv.FormatUint(c.Holder.Stamp, 10)) } if mode == "wrong-target" { c.Op.Intent["target"] = "other" } if mode == "wrong-enrollment" { c.Op.Intent["target_enrollment"] = "bad" } if mode == "wrong-region" { c.Op.Intent["to"] = "uk" } if mode == "committed" { c.Op.Phase = PhaseCommitted } return nil }) if err != nil { t.Fatal(err) } if mode == "unverified" { a.err = ErrFence } f, err := VerifyMoveTargetFence(t.Context(), s, o, ref, a) n := 0 scope := func(loc string) ParkScope { return ParkScope{Location: loc, Incarnation: o.Claim.Holder.Incarnation, EvidenceAt: f.ObservedAt(), RequestLimit: 2 * time.Minute} } r := FencedTargetRelease{OpID: o.Claim.Op.ID, RollbackID: lostRB, Target: "adi-knitting", TargetRegion: "ie", SourceRegion: "uk", Location: scope(o.Claim.Holder.Location), Lane: scope("moves/" + o.Host + "/" + o.Claim.Op.ID), Fence: f, NewID: func() string { n++; return "fence-" + strconv.Itoa(n) }} if mode == "wrong-op" { r.OpID = "other" } if mode == "zero-proof" { r.Fence = ReturnFence{} } if mode == "partial-return" { lane := scope("moves/" + o.Host + "/" + lostRB) r.ReturnLane = &lane } if mode == "stale" { if _, e := Update(t.Context(), s, o, func(*Claim) error { return nil }); e != nil { t.Fatal(e) } } s.puts = 0 var got Observed if err == nil { transition := releaseFencedTarget(r) if mode == "no-grant" { transition = ReleaseFencedTarget(r) } got, err = Update(t.Context(), s, o, transition) } if mode != "ok" && mode != "partial-return" && mode != "parks" { if err == nil { t.Fatal("accepted", mode) } if mode == "stale" && !errors.Is(err, ErrConflict) { t.Fatal(err) } return } if err != nil { t.Fatal(err) } if s.puts != 1 || got.Claim.Op.Phase != PhaseReleased || got.Claim.Op.Exec != "none" || stringPresent(got.Claim.Op.Participants, r.Target) || len(got.Claim.Obligations) < 2 { t.Fatal(got.Claim) } if mode == "parks" { gotLoss := got.Claim.EvidenceRefs[len(got.Claim.EvidenceRefs)-1] want := []ObligationRef{{ID: "existing-0", Op: o.Claim.Op.ID, Retirement: "existing-0"}} if !reflect.DeepEqual(gotLoss.Reassigned, want) { t.Fatalf("reassigned %+v", gotLoss.Reassigned) } for _, ob := range got.Claim.Obligations { if ob.ID == "existing-1" && ob.Owner != "box:other" { t.Fatal("stole another box's park") } } } clone, _ := cloneClaim(got.Claim) if err := releaseFencedTarget(r)(clone); err != nil || !reflect.DeepEqual(clone, got.Claim) { t.Fatal("replay", err) } if err := ReturnToSource(r.OpID, "bri-knitting", false, ReturnFence{})(clone); err != nil { t.Fatal("source needs fence authority", err) } }) } } } func TestPlan12FenceRefusesCleanCoverage(t *testing.T) { c := lostTargetClaim(t, KindRestoring, PhaseProbing) c.LossRefs = nil delete(c.Op.Journal, "loss."+lostID) c.Op.Exec = "ie-box" c.Op.Journal["fence.uk-box"] = "verified-in-release" h := c.Holder in := Admission{ObservedOpID: c.Op.ID, ObservedPhase: c.Op.Phase, ObservedSession: h.Session, ObservedGeneration: h.Generation, Box: "ie-box", Mode: "cold", NewGeneration: "return", Region: "ie", PredecessorRegion: "uk", Coverage: "clean-return"} if err := AdmitWriter(in)(c); !errors.Is(err, ErrFence) { t.Fatal(err) } }