forked from wrenn/wrenn
Add disk_size_mb, auto-expand base images, admin templates endpoint
Disk sizing: - Add disk_size_mb column to sandboxes table (default 20480 = 20GB) - Add disk_size_mb to CreateSandboxRequest proto, passed through the full chain: service → RPC → host agent → sandbox manager → devicemapper - devicemapper.CreateSnapshot takes separate cowSizeBytes param so the sparse CoW file can be sized independently from the origin - EnsureImageSizes() runs at host agent startup: expands any base image smaller than 20GB via truncate + resize2fs (sparse, no extra physical disk). Sandboxes then get the full 20GB via fast dm-snapshot path - FlattenRootfs shrinks output images with resize2fs -M so stored templates are compact; EnsureImageSizes re-expands on next startup Admin templates visibility: - Add GET /v1/admin/templates endpoint listing all templates across teams - Frontend admin templates page uses listAdminTemplates() instead of team-scoped listSnapshots() - Platform templates (team_id = all-zeros UUID) now visible to all teams: GetTemplateByTeam, ListTemplatesByTeam, ListTemplatesByTeamAndType queries include platform team_id in WHERE clause
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
@ -51,8 +52,8 @@ type sandboxState struct {
|
||||
slot *network.Slot
|
||||
client *envdclient.Client
|
||||
uffdSocketPath string // non-empty for sandboxes restored from snapshot
|
||||
dmDevice *devicemapper.SnapshotDevice
|
||||
baseImagePath string // path to the base template rootfs (for loop registry release)
|
||||
dmDevice *devicemapper.SnapshotDevice
|
||||
baseImagePath string // path to the base template rootfs (for loop registry release)
|
||||
|
||||
// parent holds the snapshot header and diff file paths from which this
|
||||
// sandbox was restored. Non-nil means re-pause should use "Diff" snapshot
|
||||
@ -94,7 +95,7 @@ func New(cfg Config) *Manager {
|
||||
|
||||
// Create boots a new sandbox: clone rootfs, set up network, start VM, wait for envd.
|
||||
// If sandboxID is empty, a new ID is generated.
|
||||
func (m *Manager) Create(ctx context.Context, sandboxID, template string, vcpus, memoryMB, timeoutSec int) (*models.Sandbox, error) {
|
||||
func (m *Manager) Create(ctx context.Context, sandboxID, template string, vcpus, memoryMB, timeoutSec, diskSizeMB int) (*models.Sandbox, error) {
|
||||
if sandboxID == "" {
|
||||
sandboxID = id.FormatSandboxID(id.NewSandboxID())
|
||||
}
|
||||
@ -105,6 +106,9 @@ func (m *Manager) Create(ctx context.Context, sandboxID, template string, vcpus,
|
||||
if memoryMB <= 0 {
|
||||
memoryMB = 512
|
||||
}
|
||||
if diskSizeMB <= 0 {
|
||||
diskSizeMB = 20480 // 20 GB default
|
||||
}
|
||||
|
||||
if template == "" {
|
||||
template = "minimal"
|
||||
@ -115,7 +119,7 @@ func (m *Manager) Create(ctx context.Context, sandboxID, template string, vcpus,
|
||||
|
||||
// Check if template refers to a snapshot (has snapfile + memfile + header + rootfs).
|
||||
if snapshot.IsSnapshot(m.cfg.ImagesDir, template) {
|
||||
return m.createFromSnapshot(ctx, sandboxID, template, vcpus, memoryMB, timeoutSec)
|
||||
return m.createFromSnapshot(ctx, sandboxID, template, vcpus, memoryMB, timeoutSec, diskSizeMB)
|
||||
}
|
||||
|
||||
// Resolve base rootfs image: /var/lib/wrenn/images/{template}/rootfs.ext4
|
||||
@ -139,7 +143,8 @@ func (m *Manager) Create(ctx context.Context, sandboxID, template string, vcpus,
|
||||
// Create dm-snapshot with per-sandbox CoW file.
|
||||
dmName := "wrenn-" + sandboxID
|
||||
cowPath := filepath.Join(m.cfg.SandboxesDir, fmt.Sprintf("%s.cow", sandboxID))
|
||||
dmDev, err := devicemapper.CreateSnapshot(dmName, originLoop, cowPath, originSize)
|
||||
cowSize := int64(diskSizeMB) * 1024 * 1024
|
||||
dmDev, err := devicemapper.CreateSnapshot(dmName, originLoop, cowPath, originSize, cowSize)
|
||||
if err != nil {
|
||||
m.loops.Release(baseRootfs)
|
||||
return nil, fmt.Errorf("create dm-snapshot: %w", err)
|
||||
@ -853,6 +858,17 @@ func (m *Manager) FlattenRootfs(ctx context.Context, sandboxID, name string) (in
|
||||
// Clean up dm device and loop device now that flatten is complete.
|
||||
m.cleanupDM(sb)
|
||||
|
||||
// Shrink the flattened image to its minimum size so stored templates are
|
||||
// compact. EnsureImageSizes will re-expand them on the next agent startup.
|
||||
if out, err := exec.Command("e2fsck", "-fy", outputPath).CombinedOutput(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() > 1 {
|
||||
slog.Warn("e2fsck before shrink failed (non-fatal)", "output", string(out), "error", err)
|
||||
}
|
||||
}
|
||||
if out, err := exec.Command("resize2fs", "-M", outputPath).CombinedOutput(); err != nil {
|
||||
slog.Warn("resize2fs -M failed (non-fatal)", "output", string(out), "error", err)
|
||||
}
|
||||
|
||||
sizeBytes, err := snapshot.DirSize(m.cfg.ImagesDir, name)
|
||||
if err != nil {
|
||||
slog.Warn("failed to calculate template size", "error", err)
|
||||
@ -891,7 +907,7 @@ func (m *Manager) DeleteSnapshot(name string) error {
|
||||
// in ImagesDir/{snapshotName}/. Uses UFFD for lazy memory loading.
|
||||
// The template's rootfs.ext4 is a flattened standalone image — we create a
|
||||
// dm-snapshot on top of it just like a normal Create.
|
||||
func (m *Manager) createFromSnapshot(ctx context.Context, sandboxID, snapshotName string, vcpus, _, timeoutSec int) (*models.Sandbox, error) {
|
||||
func (m *Manager) createFromSnapshot(ctx context.Context, sandboxID, snapshotName string, vcpus, _, timeoutSec, diskSizeMB int) (*models.Sandbox, error) {
|
||||
imagesDir := m.cfg.ImagesDir
|
||||
|
||||
// Read the header.
|
||||
@ -936,7 +952,8 @@ func (m *Manager) createFromSnapshot(ctx context.Context, sandboxID, snapshotNam
|
||||
|
||||
dmName := "wrenn-" + sandboxID
|
||||
cowPath := filepath.Join(m.cfg.SandboxesDir, fmt.Sprintf("%s.cow", sandboxID))
|
||||
dmDev, err := devicemapper.CreateSnapshot(dmName, originLoop, cowPath, originSize)
|
||||
cowSize := int64(diskSizeMB) * 1024 * 1024
|
||||
dmDev, err := devicemapper.CreateSnapshot(dmName, originLoop, cowPath, originSize, cowSize)
|
||||
if err != nil {
|
||||
source.Close()
|
||||
m.loops.Release(baseRootfs)
|
||||
|
||||
Reference in New Issue
Block a user