fix(operator): StatefulSet 显式保留世界卷,同名旧世界卷仍在时建服回 409 world_volume_exists,文档写明孤儿卷查找与单节点约束

This commit is contained in:
Lemon-miaow committed 2026-09-25 19:38:13 +08:00
1 parent b7d4275ca9
commit 925cfcf8f8
11 files changed
+112 -22

No files matched your search

+10
View File
@@ -320,6 +320,16 @@ func buildStatefulSet(server *v1alpha1.MinecraftServer, replicas int32, felisIma
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{pvc},
// The world outlives its StatefulSet: deleting the CR (and with it,
// by owner reference, the StatefulSet) keeps the claim, so a CR that
// comes back under the same name mounts the same world. The reaper is
// the one path that deletes a world, after its final backup. Stated
// here although it is the API default, so the semantics never ride on
// a default.
PersistentVolumeClaimRetentionPolicy: &appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy{
WhenDeleted: appsv1.RetainPersistentVolumeClaimRetentionPolicyType,
WhenScaled: appsv1.RetainPersistentVolumeClaimRetentionPolicyType,
},
},
}
return sts, nil
+14
View File
@@ -54,3 +54,17 @@ func TestGameContainerProbes(t *testing.T) {
t.Fatalf("login-gate startup = %+v", p)
}
}
// Deleting a server's StatefulSet, or scaling it to zero, keeps the world claim.
func TestWorldClaimOutlivesTheStatefulSet(t *testing.T) {
srv := &v1alpha1.MinecraftServer{ObjectMeta: metav1.ObjectMeta{Name: "survival", Namespace: "minecraft"}}
srv.Spec.Image = "itzg/minecraft-server:java21"
sts, err := buildStatefulSet(srv, 1, "felis:test")
if err != nil {
t.Fatal(err)
}
p := sts.Spec.PersistentVolumeClaimRetentionPolicy
if p == nil || p.WhenDeleted != "Retain" || p.WhenScaled != "Retain" {
t.Fatalf("retention policy = %+v, want Retain on delete and on scale", p)
}
}