fix(install): backups exist on a default install; retention resolves real world dirs (#6)
Three faces of one gap, all on the supported install path: - Backup/restore answered 503 out of the box: nothing ever rendered the archive PVC, so FELIS_BACKUP_PVC was unset. The bundle now renders the PVC (Minecraft namespace, RWO 10Gi, cluster default class) and 'felis manifests' names it by default (--backup-pvc= is the explicit no-store shape); bootstrap passes it through so the generated felis.toml [archive] local_path and the jobs' mount path come from one variable. - Retention was unreachable: bootstrap never passed the reaper flags. It now forwards FELIS_WORLDS_HOST_PATH/FELIS_ARCHIVE_LOCAL_PATH, so one env enables the daily CronJob; unset keeps today's fail-safe (no reaper, nothing deleted). - Even when enabled it could not find a world on a stock install: resolveWorldDir now also resolves the exact local-path directory <pv-name>_<ns>_<pvc-name> read from the live PVC's volumeName (never a glob, so a stale deleted PV's bytes can't be archived in place of the current world). Reaper Role gains persistentvolumeclaims:get (weaker than the delete it already held). README (zh/en) stops promising automatic/scheduled backups and states retention is opt-in. bootstrap_test covers the env->flag contract.
This commit is contained in:
14 files changed
+394
-82
No files matched your search
@@ -321,6 +321,45 @@ func TestAPIDeployment_BackupPVC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackupPVC_RendersWithTheStore proves the world-archive PVC is part of the
|
||||
// bundle exactly when a backup PVC is named, and that it lands in the Minecraft
|
||||
// namespace where every pod mounting it runs (the backup/restore Jobs and the
|
||||
// reaper CronJob) — the one property that made the reaper's first placement
|
||||
// unschedulable. Without a name the bundle must NOT create one: the api env is
|
||||
// gated on the same value, so the endpoints answer 503 instead of pointing a Job
|
||||
// at a claim nobody provisioned.
|
||||
func TestBackupPVC_RendersWithTheStore(t *testing.T) {
|
||||
p := testParams()
|
||||
p.BackupPVC = "felis-backups"
|
||||
var got *corev1.PersistentVolumeClaim
|
||||
for _, o := range Workloads(p) {
|
||||
if pvc, ok := o.(*corev1.PersistentVolumeClaim); ok && pvc.Name == "felis-backups" {
|
||||
got = pvc
|
||||
}
|
||||
}
|
||||
if got == nil {
|
||||
t.Fatalf("Workloads() must render PVC %q when BackupPVC is set", p.BackupPVC)
|
||||
}
|
||||
if got.Namespace != p.MinecraftNamespace {
|
||||
t.Errorf("backup PVC namespace = %q, want %q (Pod↔PVC mounts are same-namespace only)", got.Namespace, p.MinecraftNamespace)
|
||||
}
|
||||
if len(got.Spec.AccessModes) != 1 || got.Spec.AccessModes[0] != corev1.ReadWriteOnce {
|
||||
t.Errorf("backup PVC access modes = %v, want [ReadWriteOnce]", got.Spec.AccessModes)
|
||||
}
|
||||
if q := got.Spec.Resources.Requests.Storage(); q == nil || q.String() != backupStorageSize {
|
||||
t.Errorf("backup PVC storage = %v, want %s", q, backupStorageSize)
|
||||
}
|
||||
if got.Spec.StorageClassName != nil {
|
||||
t.Errorf("backup PVC pins storageClassName %q; the cluster default is the only safe binding", *got.Spec.StorageClassName)
|
||||
}
|
||||
|
||||
for _, o := range Workloads(testParams()) {
|
||||
if pvc, ok := o.(*corev1.PersistentVolumeClaim); ok && pvc.Name == "felis-backups" {
|
||||
t.Error("no backup PVC may render when none is named")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOperatorDeployment_Wiring pins the operator entrypoint, its namespace split,
|
||||
// and its deliberately smaller surface (NO config Secret — it holds no DB URL).
|
||||
func TestOperatorDeployment_Wiring(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user