| index 560258018..898057e82 100644 |
| --- a/internal/console/apidbclaim_test.go |
| +++ b/internal/console/apidbclaim_test.go |
| @@ -384,3 +384,82 @@ func TestDoorPlanRangeReaderCloses(t *testing.T) { |
| }) |
| } |
| } |
| + |
| +// TestDBDoorRefusesDuringMoves (moves spec §19.1, Review Focus 4, K14): the |
| +// db door refuses on every move kind, in every phase, including |
| +// landing/committed and the restoring and reverting opened phases; it |
| +// reopens at the close, when the claim is serving again. The refusal is a |
| +// 409 with Retry-After: 1, and the data bucket is never read. |
| +func TestDBDoorRefusesDuringMoves(t *testing.T) { |
| + const move = "mv_abcdefghijklmnopqrstuvwxyz" |
| + reloc := func(phase, exec string) hostclaim.Op { |
| + return hostclaim.Op{Kind: hostclaim.KindRelocating, ID: move, Phase: phase, Exec: exec, |
| + Intent: map[string]string{"region": "uk", "source_box": "ie-box", "target_box": "uk-box"}} |
| + } |
| + cases := []hostclaim.Op{ |
| + {Kind: hostclaim.KindMoving, ID: move, Phase: hostclaim.PhaseMoving, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindMoving, ID: move, Phase: hostclaim.PhaseSealed}, |
| + {Kind: hostclaim.KindMoving, ID: move, Phase: hostclaim.PhaseAborting, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindLanding, ID: move, Phase: "checking", Exec: "uk-box"}, |
| + {Kind: hostclaim.KindLanding, ID: move, Phase: hostclaim.PhaseLandingProbing, Exec: "uk-box"}, |
| + {Kind: hostclaim.KindLanding, ID: move, Phase: hostclaim.PhaseLandingProbed, Exec: "uk-box"}, |
| + {Kind: hostclaim.KindLanding, ID: move, Phase: hostclaim.PhaseLandingCommitted, Exec: "uk-box"}, |
| + {Kind: hostclaim.KindReverting, ID: move, Exec: "ie-box", Journal: map[string]string{hostclaim.JournalRevPhase: hostclaim.RevPhaseEvidence}}, |
| + {Kind: hostclaim.KindReverting, ID: move, Exec: "ie-box", Journal: map[string]string{hostclaim.JournalRevPhase: hostclaim.RevPhaseVerify}}, |
| + {Kind: hostclaim.KindReverting, ID: move, Phase: hostclaim.PhaseProbing, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindReverting, ID: move, Phase: hostclaim.PhaseOpened, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseReleasing, Exec: "uk-box"}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseReleased}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseClaimed, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseProbing, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseProbed, Exec: "ie-box"}, |
| + {Kind: hostclaim.KindRestoring, ID: move, Phase: hostclaim.PhaseOpened, Exec: "ie-box"}, |
| + reloc("starting", "ie-box"), |
| + reloc("releasing", "none"), |
| + reloc("pinning", "none"), |
| + reloc("installing", "uk-box"), |
| + reloc("installed", "uk-box"), |
| + reloc("probing", "uk-box"), |
| + reloc("probe-failed", "uk-box"), |
| + reloc("serving", "uk-box"), |
| + } |
| + kinds := map[string]bool{} |
| + for _, op := range cases { |
| + kinds[op.Kind] = true |
| + name := op.Kind + "/" + op.Phase |
| + if rp := op.Journal[hostclaim.JournalRevPhase]; rp != "" { |
| + name += "rev_phase=" + rp |
| + } |
| + t.Run(name, func(t *testing.T) { |
| + s, g, rec, m := doorPlanFixture(t, false) |
| + m.FailOn = func(op, key string) error { t.Fatalf("a refusal reached the data bucket: %s %s", op, key); return nil } |
| + c := cloneDoorClaim(g.claim.Claim) |
| + c.Op = op |
| + writeDoorClaim(t, s, dbRequest{host: rec.Host}, c) |
| + _, err := s.dbClaim(t.Context(), g, rec) |
| + if !errors.Is(err, errDBClaimClosed) { |
| + t.Fatalf("%v, want the door closed", err) |
| + } |
| + w := httptest.NewRecorder() |
| + s.dbClaimFailure(w, httptest.NewRequest(http.MethodGet, "/", nil), g, err) |
| + if w.Code != http.StatusConflict || w.Header().Get("Retry-After") != "1" { |
| + t.Fatalf("%d Retry-After %q, want 409 and 1", w.Code, w.Header().Get("Retry-After")) |
| + } |
| + }) |
| + } |
| + for _, k := range []string{hostclaim.KindMoving, hostclaim.KindLanding, hostclaim.KindReverting, hostclaim.KindRestoring, hostclaim.KindRelocating} { |
| + if !kinds[k] { |
| + t.Errorf("the table has no %s row", k) |
| + } |
| + } |
| + // The close: serving again, the door reopens. |
| + t.Run("serving", func(t *testing.T) { |
| + s, g, rec, _ := doorPlanFixture(t, false) |
| + c := cloneDoorClaim(g.claim.Claim) |
| + c.Op = hostclaim.Op{Kind: hostclaim.KindServing} |
| + writeDoorClaim(t, s, dbRequest{host: rec.Host}, c) |
| + if _, err := s.dbClaim(t.Context(), g, rec); err != nil { |
| + t.Fatalf("serving: %v", err) |
| + } |
| + }) |
| +} |