fix(api): refuse backup/restore before a missing world volume

A server whose world PVC does not exist yet (never started) or no longer exists
(the world was already reaped) accepted the backup/restore POST, answered 202,
and the Job sat Pending on the missing claim until its deadline with nothing
recorded anywhere — a silent no-op from the operator's seat. The live drill on
the reaped `resolvecheck` world reproduced exactly that.

Both handlers now read the world PVC (Cluster.WorldVolumeExists, over the same
naming.WorldPVCName the Jobs mount) and answer a specific 409 no_world_volume
with "start it once to create it, then retry". The felis-api Role gains the
matching get-only PVC grant — the first live run surfaced the missing RBAC as a
403 behind a 500, so the fix ships with it.

Live (auditfix38): resolvecheck -> 409 no_world_volume on both faces; test-one
(which has a world) still backs up through the new gate end to end.
This commit is contained in:
Lemon-miaow committed 2026-09-23 07:49:00 +08:00
1 parent 55d515d41f
commit 508a1c02da
8 files changed
+127 -3

No files matched your search

+9 -1
View File
@@ -1451,6 +1451,7 @@ type fakeCluster struct {
desired map[string]v1alpha1.DesiredState
created map[string]CreateServerInput // name -> the validated input it was created from
patched map[string]ServerSpecPatch // name -> the validated spec patch it received
noWorld map[string]bool // server names modeled WITHOUT a world volume (never started / reaped)
createErr error
pingErr error
}
@@ -1458,7 +1459,7 @@ type fakeCluster struct {
func newFakeCluster() *fakeCluster {
return &fakeCluster{byName: map[string]*ServerInfo{}, bySub: map[string]*ServerInfo{},
desired: map[string]v1alpha1.DesiredState{}, created: map[string]CreateServerInput{},
patched: map[string]ServerSpecPatch{}}
patched: map[string]ServerSpecPatch{}, noWorld: map[string]bool{}}
}
func (c *fakeCluster) GetServer(_ context.Context, n string) (*ServerInfo, error) {
if s, ok := c.byName[n]; ok {
@@ -1474,6 +1475,13 @@ func (c *fakeCluster) GetBySubdomain(_ context.Context, s string) (*ServerInfo,
}
func (c *fakeCluster) ListServers(_ context.Context) ([]ServerInfo, error) { return c.list, nil }
func (c *fakeCluster) Ping(_ context.Context) error { return c.pingErr }
// WorldVolumeExists models the world PVC: present unless the test named the
// server in noWorld (never started / already reaped).
func (c *fakeCluster) WorldVolumeExists(_ context.Context, n string) (bool, error) {
return !c.noWorld[n], nil
}
func (c *fakeCluster) SetDesiredState(_ context.Context, n string, s v1alpha1.DesiredState) error {
c.desired[n] = s
return nil