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

fix(velocity): 等待队列跟随服的启动进度,自动重试期间一直等并每分钟报进度,放弃或被停止时说明原因

parent df8b0978
Loading
Loading
Loading
Loading
+10 −0
Changes for docs/openapi.yaml: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -488,6 +488,16 @@ components:
            Present and true when the CR carries the label felis.lolicon.best/forwarding=legacy.
            The proxy then forwards this server's players BungeeCord-style in the handshake
            address instead of modern forwarding (Felis-Legacy Velocity fork only).
        autoRestarts:
          type: integer
          format: int32
          description: Present when non-zero; how often the operator recreated the pod of this start after it timed out (at most 3).
        startGaveUp:
          type: boolean
          description: >-
            Present and true for a Failed server no automatic retry will bring up: its
            start timed out with the retries spent, or its spec is invalid. A Failed
            server without it is still in its restart backoff and may come up on its own.

    FleetServer:
      description: One row of the fleet-wide admin read (internal/api/handlers_user.go fleetServerView).
+6 −0
Changes for internal/api/cluster.go: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -37,6 +37,12 @@ type ServerInfo struct {
	// LegacyForwarding mirrors the CR's forwarding=legacy label: the proxy
	// forwards this server's players in the handshake address (#15).
	LegacyForwarding bool `json:"legacyForwarding,omitempty"`
	// AutoRestarts is how often the operator has recreated the pod of this start
	// after it timed out; StartGaveUp is true once no automatic retry is coming
	// (v1alpha1.StartGaveUp). A Failed server without StartGaveUp is still in its
	// restart backoff and may yet come up on its own.
	AutoRestarts int32 `json:"autoRestarts,omitempty"`
	StartGaveUp  bool  `json:"startGaveUp,omitempty"`
}

// CreateServerInput is the validated, structured create-server form (spec §15).
+2 −0
Changes for internal/api/k8scluster.go: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -447,6 +447,8 @@ func serverInfo(ms *v1alpha1.MinecraftServer) *ServerInfo {
		PlayerCountUnknown: ms.Status.Phase == v1alpha1.PhaseRunning &&
			meta.IsStatusConditionFalse(ms.Status.Conditions, v1alpha1.ConditionPlayersCounted),
		LegacyForwarding: ms.Labels[v1alpha1.LabelForwarding] == v1alpha1.ForwardingLegacy,
		AutoRestarts:     ms.Status.AutoRestarts,
		StartGaveUp:      v1alpha1.StartGaveUp(&ms.Status),
	}
}

+30 −0
Changes for internal/api/k8scluster_test.go: 30 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -290,3 +290,33 @@ func TestServerListCarriesLegacyForwarding(t *testing.T) {
		}
	}
}

// A Failed server reaches the proxy and the panel with whether the operator will
// still retry it: the proxy keeps a waiting player through the restart backoff and
// lets them go once the retries are spent.
func TestServerInfoCarriesStartGaveUp(t *testing.T) {
	failed := func(name string, restarts int32) *v1alpha1.MinecraftServer {
		ms := testServer(name, name)
		now := metav1.Now()
		ms.Status = v1alpha1.MinecraftServerStatus{
			Phase:            v1alpha1.PhaseFailed,
			AutoRestarts:     restarts,
			StartRequestedAt: &now,
			Conditions: []metav1.Condition{{Type: v1alpha1.ConditionReady, Status: metav1.ConditionFalse,
				Reason: v1alpha1.ReasonStartupTimeout}},
		}
		return ms
	}
	retrying := serverInfo(failed("retrying", 1))
	if retrying.StartGaveUp || retrying.AutoRestarts != 1 {
		t.Fatalf("retrying: startGaveUp=%v autoRestarts=%d, want false 1", retrying.StartGaveUp, retrying.AutoRestarts)
	}
	spent := serverInfo(failed("spent", v1alpha1.MaxAutoRestarts))
	b, err := json.Marshal(spent)
	if err != nil {
		t.Fatal(err)
	}
	if !spent.StartGaveUp || !strings.Contains(string(b), `"startGaveUp":true`) {
		t.Fatalf("spent: startGaveUp=%v JSON %s, want true", spent.StartGaveUp, b)
	}
}
+27 −1
Changes for internal/apis/felis/v1alpha1/minecraftserver_types.go: 27 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package v1alpha1

import (
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

@@ -91,6 +92,31 @@ const (
	ConditionPlayersCounted = "PlayersCounted"
)

// Ready-condition reasons of a start that timed out: the pod never passed its TCP
// readiness, or RCON never answered. Both are retried by recreating the pod, at
// most MaxAutoRestarts times with a doubling backoff.
const (
	ReasonStartupTimeout   = "StartupTimeout"
	ReasonReadinessTimeout = "ReadinessTimeout"
)

// MaxAutoRestarts bounds how often the operator retries a timed-out start.
const MaxAutoRestarts = 3

// StartGaveUp reports a Failed server that no automatic retry will bring up: its
// start timed out with the retries spent, or it failed for a reason the operator
// never retries (an invalid spec). Only a person moves it on. A Failed server still
// inside its restart backoff has not given up: the operator recreates its pod when
// the backoff runs out, and whoever waits on it should keep waiting.
func StartGaveUp(s *MinecraftServerStatus) bool {
	if s.Phase != PhaseFailed {
		return false
	}
	c := meta.FindStatusCondition(s.Conditions, ConditionReady)
	timedOut := c != nil && (c.Reason == ReasonStartupTimeout || c.Reason == ReasonReadinessTimeout)
	return !timedOut || s.AutoRestarts >= MaxAutoRestarts || s.StartRequestedAt == nil
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

@@ -310,7 +336,7 @@ type MinecraftServerStatus struct {
	// the server becomes unoccupied.
	EmptySince *metav1.Time `json:"emptySince,omitempty"`
	// AutoRestarts counts how often the operator recreated the pod of a start
	// that timed out (at most 3, with a doubling backoff); reaching Ready or
	// that timed out (at most MaxAutoRestarts, with a doubling backoff); reaching Ready or
	// stopping resets it.
	AutoRestarts int32 `json:"autoRestarts,omitempty"`
	// LastAutoRestartAt is when the operator last recreated the pod.
Loading