Files
mesh-controller/internal/builder/floor.go
T
jschoubben 995f8237ce Free a build node's disk after every build and refuse work under a floor of free space (issue 1834)
A full / made every check on shanks end 'store never became ready' with the reason erased by --rm.
Builds now remove their local image tag, narrowed source and context clone; a build or check
refuses under 20 GiB free on / and the workspace, saying so; the throwaway store is no longer
started with --rm and a store that does not come up reports its exit code and last 20 log lines.
2026-10-22 13:14:00 +02:00

47 lines
1.4 KiB
Go

package builder
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
// FreeSpaceFloor is the free space a build node must have, on / and on its workspace's filesystem, before it takes
// a build or a check (novox/hq issue 1834): under it, a throwaway store cannot initialise its data directory and
// dies at once, and the check ended "never became ready" instead of saying the disk was full.
const FreeSpaceFloor = 20 << 30
// FreeBytes reads the bytes an unprivileged writer can still use on the filesystem holding path. A variable so a
// test answers for a disk it does not have.
var FreeBytes = func(path string) (uint64, error) {
var s syscall.Statfs_t
if err := syscall.Statfs(path, &s); err != nil {
return 0, err
}
return s.Bavail * uint64(s.Bsize), nil
}
// RoomToWork refuses, saying how much is free, when any of the paths' filesystems is under FreeSpaceFloor. A path
// that does not exist yet (a workspace not made) is read at its nearest folder that does.
func RoomToWork(paths ...string) error {
for _, p := range paths {
at := p
for {
if _, err := os.Stat(at); err == nil || filepath.Dir(at) == at {
break
}
at = filepath.Dir(at)
}
free, err := FreeBytes(at)
if err != nil {
return fmt.Errorf("the free space on %s cannot be read: %w", p, err)
}
if free < FreeSpaceFloor {
return fmt.Errorf("the build node has %d GiB free on %s, under the floor of %d GiB",
free>>30, p, FreeSpaceFloor>>30)
}
}
return nil
}