package console import ( "context" "errors" "net/http" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/provision" ) // mc19 is M-C19 for the fixture's host. const mc19 = "acme.go.tito.io is being moved. Delete it after the move finishes, or abort the move first." func recordExists(t *testing.T, fx *consoleFixture) bool { t.Helper() _, err := provision.GetRecord(context.Background(), fx.rel, "bab", "titogo", "acme.go.tito.io") if errors.Is(err, os.ErrNotExist) { return false } if err != nil { t.Fatal(err) } return true } // Task 2b (preflight L6): with the switch on, a console without the host // claim refuses to delete an instance whose record carries a live move, // with M-C19, and deletes nothing. Once the move has closed the delete is // main's. func TestMoveDeleteRefusesDuringAMove(t *testing.T) { fx, token, moveURL := moveDoorWithClaim(t, "code-inst-move-del") instURL := strings.TrimSuffix(moveURL, "/move") readMoveAnswer(t, http.MethodPost, moveURL, token, `{"region":"uk","cut":"manual"}`, http.StatusAccepted) for _, state := range []string{provision.MovePreparing, provision.MoveCommitted, provision.MoveStuck} { setMove(t, fx, func(rec *provision.Record) { rec.Move.State = state }) if code, _, out := rawReq(t, http.MethodDelete, instURL, token, ""); code != http.StatusConflict || errorText(out) != mc19 { t.Fatalf("delete during a move in %s = %d %s, want M-C19", state, code, out) } if !recordExists(t, fx) { t.Fatalf("a refused delete in %s removed the record", state) } } mv := recordMove(t, fx) setMove(t, fx, func(rec *provision.Record) { rec.Move.State = provision.MoveAborting }) if _, err := provision.SetRecordMove(context.Background(), fx.rel, "bab", "titogo", "acme.go.tito.io", provision.MoveGuardedAbortComplete(provision.MoveGuard{ID: mv.ID, Incarnation: mv.Incarnation}, time.Now())); err != nil { t.Fatal(err) } if code, _, out := rawReq(t, http.MethodDelete, instURL, token, ""); code != http.StatusOK { t.Fatalf("delete after the move closed = %d %s", code, out) } if recordExists(t, fx) { t.Fatal("the delete after the move closed left the record") } } // With the switch off the delete door is main's: a record with a move is // deleted as before. func TestMoveDeleteSwitchOffIsMains(t *testing.T) { fx, token, moveURL := moveDoorWithClaim(t, "code-inst-move-del-off") instURL := strings.TrimSuffix(moveURL, "/move") setMove(t, fx, func(rec *provision.Record) { rec.Move = &provision.MoveState{To: "uk", State: provision.MovePreparing, RequestedAt: time.Now().UTC()} }) fx.srv.cfg.MovesEnabled = false if code, _, out := rawReq(t, http.MethodDelete, instURL, token, ""); code != http.StatusOK { t.Fatalf("switch-off delete during a legacy move = %d %s", code, out) } if recordExists(t, fx) { t.Fatal("the switch-off delete left the record") } } // On a host-claim console a delete during a move meets the claim's pending // refusal; the door says M-C19 for it, not the generic busy sentence, when // the claim's operation is the move's. func TestMoveDeleteUnderHostClaimSaysItIsMoving(t *testing.T) { fx, token, moveURL, _ := j17StartedMove(t, "code-inst-move-del-claim") instURL := strings.TrimSuffix(moveURL, "/move") code, _, out := rawReq(t, http.MethodDelete, instURL, token, "") if code != http.StatusConflict || errorText(out) != mc19 { t.Fatalf("delete under the host claim during a move = %d %s, want M-C19", code, out) } if !recordExists(t, fx) { t.Fatal("a refused delete removed the record") } } // A host-claim console says M-C19 only for a move's claim operation: a // serving claim, and a relocation with no move journaled on the record (a // region HA relocation of its own), go on to the claim's retire. func TestMoveDeleteUnderHostClaimOnlyForAMove(t *testing.T) { fx, token, moveURL := moveDoorWithClaim(t, "code-inst-move-del-claim2") armClaimMoves(t, fx, true, true, nil) instURL := strings.TrimSuffix(moveURL, "/move") if code, _, out := rawReq(t, http.MethodDelete, instURL, token, ""); errorText(out) == mc19 { t.Fatalf("delete of a serving host under the host claim = %d %s", code, out) } rewriteClaim(t, fx, "acme.go.tito.io", func(c *hostclaim.Claim) { c.Op = hostclaim.Op{Kind: hostclaim.KindRelocating, ID: "op-ha", Phase: "pinning", Exec: "none", Intent: map[string]string{"region": "ie", "source_box": "ie-box"}} }) if code, _, out := rawReq(t, http.MethodDelete, instURL, token, ""); errorText(out) == mc19 { t.Fatalf("delete during a relocation that is not a move = %d %s", code, out) } }