Files
Felis/cmd/felis/reaper_test.go
T
Lemon-miaow fd33fd05e1 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.
2026-09-22 20:48:07 +08:00

78 lines
2.8 KiB
Go

package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
// TestResolveWorldDir pins the two world layouts the reaper must find, and the
// fail-closed miss. The stock local-path arm is derived from the live PVC's
// volumeName — a name-based guess (glob) could tar a stale deleted PV's bytes and
// then delete the current world, which is why it is read from the API instead.
func TestResolveWorldDir(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
// Arrange a world under the documented <root>/<pvc> layout.
named := filepath.Join(root, "world-named-0")
if err := os.MkdirAll(named, 0o750); err != nil {
t.Fatal(err)
}
// Arrange a second world the way k3s local-path stores it.
pvDir := filepath.Join(root, "pvc-11111111-2222-3333-4444-555555555555_minecraft_world-live-0")
if err := os.MkdirAll(pvDir, 0o750); err != nil {
t.Fatal(err)
}
claim := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: "world-live-0", Namespace: "minecraft"},
Spec: corev1.PersistentVolumeClaimSpec{
VolumeName: "pvc-11111111-2222-3333-4444-555555555555",
},
}
cl := fake.NewClientBuilder().WithScheme(haltScheme(t)).WithObjects(claim).Build()
resolve := resolveWorldDir(ctx, cl, "minecraft", root)
t.Run("documented name layout wins", func(t *testing.T) {
got, err := resolve("world-named-0")
if err != nil || got != named {
t.Fatalf("resolve = (%q, %v), want (%q, nil)", got, err, named)
}
})
t.Run("stock local-path layout resolves exactly", func(t *testing.T) {
got, err := resolve("world-live-0")
if err != nil || got != pvDir {
t.Fatalf("resolve = (%q, %v), want (%q, nil)", got, err, pvDir)
}
})
t.Run("neither layout present falls back to the documented path", func(t *testing.T) {
// The claim exists but its directory does not: return the documented path so
// the archive walk fails there, and the reaper preserves the world.
missing := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: "world-gone-0", Namespace: "minecraft"},
Spec: corev1.PersistentVolumeClaimSpec{VolumeName: "pvc-99999999-0000-0000-0000-000000000000"},
}
cl := fake.NewClientBuilder().WithScheme(haltScheme(t)).WithObjects(missing).Build()
got, err := resolveWorldDir(ctx, cl, "minecraft", root)("world-gone-0")
if err != nil || got != filepath.Join(root, "world-gone-0") {
t.Fatalf("resolve = (%q, %v), want (%q, nil)", got, err, filepath.Join(root, "world-gone-0"))
}
})
t.Run("unknown pvc is an error, not a guess", func(t *testing.T) {
_, err := resolve("world-unknown-0")
if err == nil || !strings.Contains(err.Error(), "resolve world PVC world-unknown-0") {
t.Fatalf("err = %v, want a resolve-world-PVC error", err)
}
})
}