feat(operator): implement idle auto-stop (spec §8)

Adds empty-server auto-stop to the reconciler. When Idle.AutoStopEnabled
is true and the RCON player tally is zero for EmptySecondsBeforeStop seconds,
the operator flips desiredState to Stopped, which triggers the normal
graceful-shutdown path.

- Add EmptySince status field to track empty duration
- Clear EmptySince on stop and when players return
- Reuse existing RCON probe's player count (zero extra network cost)
- 4 new test cases covering timestamp, timeout, player-join reset, and
  disabled-by-default
This commit is contained in:
Lemon-miaow committed 2026-07-05 01:52:45 +08:00
1 parent bd49313e7e
commit 91bfa27e8c
4 files changed
+154

No files matched your search

@@ -253,6 +253,11 @@ type MinecraftServerStatus struct {
// clears this on stop so the next start re-anchors. Persisted in status
// because the two endpoints fall in different reconcile passes.
StartRequestedAt *metav1.Time `json:"startRequestedAt,omitempty"`
// EmptySince is when the operator first observed 0 online players during a
// Running phase (spec §8 idle auto-stop). It is reset when a player joins
// or the server stops, so the empty-duration counter starts fresh each time
// the server becomes unoccupied.
EmptySince *metav1.Time `json:"emptySince,omitempty"`
// ObservedGeneration is the spec generation this status reflects.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions are the standard metav1 conditions (Ready, RconReached, ...).
@@ -118,6 +118,9 @@ func (in *MinecraftServerStatus) DeepCopyInto(out *MinecraftServerStatus) {
if in.StartRequestedAt != nil {
out.StartRequestedAt = in.StartRequestedAt.DeepCopy()
}
if in.EmptySince != nil {
out.EmptySince = in.EmptySince.DeepCopy()
}
if in.Conditions != nil {
l := make([]metav1.Condition, len(in.Conditions))
for i := range in.Conditions {