Unverified Commit 1e42e9a8 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

fix(velocity): 休眠服 MOTD 按唤醒策略说明谁能唤醒,闲置回收期间加入给出回收提示

parent 249d675b
Loading
Loading
Loading
Loading
+3 −1
Changes for docs/openapi.yaml: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -1276,7 +1276,9 @@ paths:
            start failed and its automatic retries are spent (ServerInfo.startGaveUp);
            the server stays down until a person starts it from the panel.
            server_retiring: the owner gave the server up or an admin is deleting it;
            it stays down until the reaper archives it.
            it stays down until the reaper archives it. world_reclaiming: the idle
            reaper is archiving the world; afterwards the server is released with an
            empty world.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
+3 −1
Changes for docs/troubleshooting.md: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -221,6 +221,7 @@ per-server cooldown → global running cap**. Map the API result:
|---|---|---|---|
| `403` | `forbidden` | `autostartPolicy=allowlist` and UUID not allowlisted, or `ownerOnly` and caller is not owner | Add the UUID / claim the server / set `autostartPolicy=public` |
| `409` | `maintenance_in_progress` | A restore, backup or file write holds the server's world volume (§3b) | Wait for the Job to finish |
| `409` | `world_reclaiming` | The idle reaper is archiving the world (§3b item 3); afterwards the server is released with an empty world | Nothing to wait for; the old world stays in the archive |
| `429` | (cooldown) | Wake retried within the 30s per-server `WakeCooldown` | Wait out the cooldown |
| `503` | `at_capacity` | Global `MaxRunningServers` cap reached | Stop another server or raise the cap |

@@ -231,7 +232,8 @@ teleports when `ready=true`.

The Velocity-side consumption of these codes (`403` → "You're not allowed to
start «server»"; `409 maintenance_in_progress` → "«server» is under
maintenance", not queued; `429` → re-queue; other → "Couldn't start … Try again
maintenance", not queued; `409 world_reclaiming` → "«server» sat idle too
long and its world is being archived", not queued; `429` → re-queue; other → "Couldn't start … Try again
shortly.") lives in the Java plugin and is **[CODE-ONLY]** — the codes it reacts
to are produced by the Go-tested `authorizeWakeByUUID` / cooldown limiter, so
grade the two halves separately.
+16 −1
Changes for internal/api/handlers_internal.go: 16 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ import (
	"net/http"

	"felis.lolicon.best/internal/apis/felis/v1alpha1"
	"felis.lolicon.best/internal/maintenance"
	"felis.lolicon.best/internal/naming"
)

@@ -192,8 +193,16 @@ func (a *API) handleInternalWake(w http.ResponseWriter, r *http.Request) {
	}

	// A 409 maintenance_in_progress tells velocity nothing is coming up until the
	// restore/backup/file write finishes, so it does not enqueue the player.
	// restore/backup/file write finishes, so it does not enqueue the player. The
	// idle reaper gets its own code: when it lets go, the world is archived and the
	// server released, so "try again shortly" would send the player back to a server
	// that is no longer the one they knew.
	if err := a.Cluster.SetDesiredState(r.Context(), name, v1alpha1.DesiredRunning); err != nil {
		var busy *MaintenanceBusyError
		if errors.As(err, &busy) && busy.Kind == maintenance.KindReap {
			writeError(w, r, errWorldReclaiming)
			return
		}
		a.writeLookupError(w, r, err)
		return
	}
@@ -210,6 +219,12 @@ func (a *API) handleInternalWake(w http.ResponseWriter, r *http.Request) {
	})
}

// errWorldReclaiming refuses a join-driven wake while the idle reaper archives the
// server's world. Once it is done the server is released with an empty world and
// the old one stays in the archive.
var errWorldReclaiming = newError(http.StatusConflict, "world_reclaiming",
	"this server sat idle too long and its world is being archived; afterwards it is released with an empty world")

// internalClaimRequest is the velocity `Claim & Start` body: the verified
// online-mode UUID of the player claiming an ownerless server (spec §9.3, §12).
type internalClaimRequest struct {
+14 −0
Changes for internal/api/handlers_maintenance_test.go: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -58,6 +58,20 @@ func TestWakeRefusedDuringMaintenance(t *testing.T) {
			t.Fatalf("wake after maintenance: code = %d body %s", w.Code, w.Body.String())
		}
	})

	// The idle reaper is no wait-and-retry: when it lets go the world is archived
	// and the server released, so velocity must be able to tell the player that.
	t.Run("internal wake during an idle reap -> 409 world_reclaiming", func(t *testing.T) {
		api, cl := newInternalWakeAPI("public")
		cl.wakeErr["survival"] = &MaintenanceBusyError{Kind: maintenance.KindReap}
		w := internalWake(api, `{"mc_uuid":"`+wakeUUID+`"}`)
		if w.Code != http.StatusConflict || decodeErr(t, w) != "world_reclaiming" {
			t.Fatalf("code = %d body %s, want 409 world_reclaiming", w.Code, w.Body.String())
		}
		if _, set := cl.desired["survival"]; set {
			t.Fatal("a refused wake must not flip desiredState")
		}
	})
}

// maintenanceOp is one world-volume operation as the external face serves it.
+1 −1
Changes for panel/src/lib/openapi.gen.ts: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -3280,7 +3280,7 @@ export interface operations {
            401: components["responses"]["Unauthorized"];
            403: components["responses"]["Forbidden"];
            404: components["responses"]["NotFound"];
            /** @description Nothing was started. maintenance_in_progress: a restore, backup or file write holds the server's world volume. start_failed: the last start failed and its automatic retries are spent (ServerInfo.startGaveUp); the server stays down until a person starts it from the panel. server_retiring: the owner gave the server up or an admin is deleting it; it stays down until the reaper archives it. */
            /** @description Nothing was started. maintenance_in_progress: a restore, backup or file write holds the server's world volume. start_failed: the last start failed and its automatic retries are spent (ServerInfo.startGaveUp); the server stays down until a person starts it from the panel. server_retiring: the owner gave the server up or an admin is deleting it; it stays down until the reaper archives it. world_reclaiming: the idle reaper is archiving the world; afterwards the server is released with an empty world. */
            409: {
                headers: {
                    [name: string]: unknown;
Loading