d2071430ea
Tier 2 rsync-mirrors each HDD app's recovery unit + appdata to a DIFFERENT physical disk (the only off-drive protection bind-mounted userdata can get; PBS can't reach it). Auto-enabled, auto-target: prefer another registered drive (different physical disk via system.SamePhysicalDevice), else the internal SSD for SMALL units only — with a size-aware headroom guard that REFUSES rather than fill the ~8G guest rootfs, recording an honest "needs 2nd HDD" status. Status persisted via the surviving CrossDriveBackup; "2. mentés" UI card now populated. Daily tier2-backup job + POST /api/backup/tier2. - backup/tier2.go (engine+selection+headroom), tier2_test.go (headroom arithmetic) - system.SamePhysicalDevice (linux Stat_t.Dev + stub) - handlers.go Tier2 UI population + tier2DestLabel; backups.html honest no-target reason - fixed stale TestBackupCopiesOnPath (old felhom-data layout -> in-guest layout) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 lines
1.3 KiB
Go
31 lines
1.3 KiB
Go
package backup
|
|
|
|
import "testing"
|
|
|
|
// TestTier2FitsHeadroom covers the size-aware rootfs-headroom guard that protects the ~8 GB guest
|
|
// rootfs from being filled by a Tier 2 SSD copy (reserve = max(2 GB, 20% of total)).
|
|
func TestTier2FitsHeadroom(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
availGB, totalGB, unitGB float64
|
|
want bool
|
|
}{
|
|
// 8 GB rootfs, ~2.4 GB free: a tiny unit fits (reserve = 2 GB), a 1 GB unit does NOT.
|
|
{"8G rootfs, tiny unit fits", 2.4, 8.0, 0.02, true},
|
|
{"8G rootfs, 1G unit refused", 2.4, 8.0, 1.0, false},
|
|
{"8G rootfs, 0.3G unit fits", 2.4, 8.0, 0.3, true},
|
|
// Reserve is the larger of 2 GB and 20%: on 8 GB, 20% = 1.6 GB < 2 GB, so 2 GB applies.
|
|
{"8G rootfs exactly at 2G reserve", 2.0, 8.0, 0.0, true},
|
|
{"8G rootfs just under reserve", 2.0, 8.0, 0.01, false},
|
|
// Large drive: 20% reserve dominates (204.8 GB on a 1 TB drive).
|
|
{"1TB drive, 50G unit fits", 500.0, 1024.0, 50.0, true},
|
|
{"1TB drive, 320G unit refused (under 20% reserve)", 500.0, 1024.0, 320.0, false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := tier2FitsHeadroom(c.availGB, c.totalGB, c.unitGB); got != c.want {
|
|
t.Errorf("%s: tier2FitsHeadroom(avail=%.2f,total=%.2f,unit=%.2f)=%v want %v",
|
|
c.name, c.availGB, c.totalGB, c.unitGB, got, c.want)
|
|
}
|
|
}
|
|
}
|