Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions cmd/core/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core

import (
"context"
"fmt"

"github.com/spf13/cobra"

Expand All @@ -15,20 +14,5 @@ type BaseHandler struct {
}

func (h BaseHandler) Init(cmd *cobra.Command) (context.Context, *config.Config, error) {
conf, err := h.Conf()
if err != nil {
return nil, nil, err
}
return cliutil.CommandContext(cmd), conf, nil
}

func (h BaseHandler) Conf() (*config.Config, error) {
if h.ConfProvider == nil {
return nil, fmt.Errorf("config provider is nil")
}
conf := h.ConfProvider()
if conf == nil {
return nil, fmt.Errorf("config not initialized")
}
return conf, nil
return cliutil.CommandContext(cmd), h.ConfProvider(), nil
}
20 changes: 10 additions & 10 deletions cmd/vm/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ func (h Handler) Debug(cmd *cobra.Command, args []string) error {

func printFCDebug(configs []*types.StorageConfig, boot *types.BootConfig, vmCfg *types.VMConfig, fcBin string) {
cowPath := fmt.Sprintf("cow-%s.raw", vmCfg.Name)
memMiB := int(vmCfg.Memory >> 20) //nolint:mnd
cowSizeGB := int(vmCfg.Storage >> 30) //nolint:mnd
memMiB := int(vmCfg.Memory >> 20) //nolint:mnd

nLayers := 0
for _, s := range configs {
Expand All @@ -92,10 +91,7 @@ func printFCDebug(configs []*types.StorageConfig, boot *types.BootConfig, vmCfg
cmdline := hypervisor.BuildBaseCmdline("console=ttyS0 reboot=k loglevel=3 pci=off i8042.noaux 8250.nr_uarts=1",
strings.Join(layerDevs, ","), cowDev, nil, vmCfg.Name, nil)

fmt.Println("# Prepare COW disk")
fmt.Printf("truncate -s %dG %s\n", cowSizeGB, cowPath)
fmt.Printf("mkfs.ext4 -F -m 0 -q -E lazy_itable_init=1,lazy_journal_init=1,discard %s\n", cowPath)
fmt.Println()
printPrepareCOWDisk(vmCfg.Storage>>30, cowPath) //nolint:mnd

fmt.Printf("# Launch Firecracker: %s (image: %s)\n", vmCfg.Name, vmCfg.Image)
fmt.Printf("%s --api-sock /tmp/fc-%s.sock --id %s\n", fcBin, vmCfg.Name, vmCfg.Name)
Expand Down Expand Up @@ -179,10 +175,7 @@ func printCHDebug(s chDebugSpec) {
cmdline := hypervisor.BuildBaseCmdline("console=hvc0 loglevel=3",
cocoonLayers, hypervisor.CowSerial, nil, s.VMCfg.Name, nil)

fmt.Println("# Prepare COW disk")
fmt.Printf("truncate -s %dG %s\n", s.VMCfg.Storage>>30, s.CowPath) //nolint:mnd
fmt.Printf("mkfs.ext4 -F -m 0 -q -E lazy_itable_init=1,lazy_journal_init=1,discard %s\n", s.CowPath)
fmt.Println()
printPrepareCOWDisk(s.VMCfg.Storage>>30, s.CowPath) //nolint:mnd
fmt.Printf("# Launch VM: %s (image: %s, boot: direct kernel)\n", s.VMCfg.Name, s.VMCfg.Image)
fmt.Printf("%s \\\n", s.CHBin)
fmt.Printf(" --kernel %s \\\n", s.Boot.KernelPath)
Expand Down Expand Up @@ -214,6 +207,13 @@ func printCHDebug(s chDebugSpec) {
printCommonCHArgs(s)
}

func printPrepareCOWDisk(sizeGB int64, path string) {
fmt.Println("# Prepare COW disk")
fmt.Printf("truncate -s %dG %s\n", sizeGB, path)
fmt.Printf("mkfs.ext4 -F -m 0 -q -E lazy_itable_init=1,lazy_journal_init=1,discard %s\n", path)
fmt.Println()
}

func printCommonCHArgs(s chDebugSpec) {
cpuExtra := ""
if s.VMCfg.Windows {
Expand Down
31 changes: 29 additions & 2 deletions cmd/vm/reseed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import (
"context"
"crypto/rand"
"fmt"
"os"
"os/exec"
"syscall"
"time"

"github.com/projecteru2/core/log"
"github.com/spf13/cobra"

"github.com/cocoonstack/cocoon-agent/client"
"github.com/cocoonstack/cocoon/config"
"github.com/cocoonstack/cocoon/hypervisor"
"github.com/cocoonstack/cocoon/types"
)
Expand Down Expand Up @@ -37,11 +41,14 @@ func (h Handler) Reseed(cmd *cobra.Command, args []string) error {
}

// reseedAfterResume fires the best-effort reseed, re-inspecting only when the in-process record lacks VsockSocket — a zero value would silently no-op.
func (h Handler) reseedAfterResume(ctx context.Context, hyper hypervisor.Hypervisor, vm *types.VM, regenMachineID bool) {
// It hands the reseed to a detached child by default: the vsock dial waits out the guest's post-resume wakeup (tens of ms, growing with snapshot age), which would otherwise sit on every clone/restore critical path.
func (h Handler) reseedAfterResume(ctx context.Context, conf *config.Config, hyper hypervisor.Hypervisor, vm *types.VM, regenMachineID bool) {
if vm.VsockSocket == "" {
vm = refreshVM(ctx, hyper, vm)
}
signalReseed(ctx, vm, regenMachineID)
if vm.VsockSocket == "" || vm.Config.Windows || !detachReseed(ctx, conf, vm, regenMachineID) {
signalReseed(ctx, vm, regenMachineID)
}
}

// reseedVM pushes fresh entropy and a CRNG reseed order over vsock; only a failed dial retries (the agent re-listens shortly after resume) — a live agent's reply is final.
Expand Down Expand Up @@ -84,6 +91,26 @@ func reseedVM(ctx context.Context, vm *types.VM, regenMachineID bool) error {
return fmt.Errorf("reseed: dial agent: %w", dialErr)
}

// detachReseed re-execs `cocoon vm reseed` as a session-detached child and reports whether the hand-off started; resolved dirs travel as flags so file- or flag-configured parents behave like env-configured ones.
func detachReseed(ctx context.Context, conf *config.Config, vm *types.VM, regenMachineID bool) bool {
exe, err := os.Executable()
if err != nil {
return false
}
args := []string{"--root-dir", conf.RootDir, "--run-dir", conf.RunDir, "--log-dir", conf.LogDir, "vm", "reseed"}
if regenMachineID {
args = append(args, "--machine-id")
}
c := exec.Command(exe, append(args, vm.ID)...) //nolint:gosec // self re-exec: path from os.Executable, args are internal flags/IDs
c.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
if err := c.Start(); err != nil {
log.WithFunc("cmd.vm.reseed").Warnf(ctx, "detached reseed spawn failed, reseeding inline: %v", err)
return false
}
go c.Wait() //nolint:errcheck
return true
}

// signalReseed is the non-fatal clone/restore wrapper: reports whether a reseed was attempted (false = Windows or no vsock) and never fails the calling command.
func signalReseed(ctx context.Context, vm *types.VM, regenMachineID bool) bool {
logger := log.WithFunc("cmd.vm.reseed")
Expand Down
24 changes: 9 additions & 15 deletions cmd/vm/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (h Handler) Clone(cmd *cobra.Command, args []string) error {
rollbackReserve()
return fmt.Errorf("clone VM: %w", cloneErr)
}
h.reseedAfterResume(ctx, hyper, vm, true)
h.reseedAfterResume(ctx, conf, hyper, vm, true)

if done, jsonErr := cliutil.MaybeOutputJSON(cmd, vm); done {
return jsonErr
Expand Down Expand Up @@ -182,7 +182,7 @@ func (h Handler) Restore(cmd *cobra.Command, args []string) error {
}

// Pin the inspected IDs: re-resolving mutable names here would let a delete+reuse bypass the ownership check above.
done, directErr := h.restoreDirect(ctx, cmd, snapInfo.ID, vm.ID, vmCfg, snapBackend, hyper, logger)
done, directErr := h.restoreDirect(ctx, cmd, conf, snapInfo.ID, vm.ID, vmCfg, snapBackend, hyper, logger)
if done {
return directErr
}
Expand All @@ -200,7 +200,7 @@ func (h Handler) Restore(cmd *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("restore: %w", err)
}
h.reseedAfterResume(ctx, hyper, result, false)
h.reseedAfterResume(ctx, conf, hyper, result, false)

if done, jsonErr := cliutil.MaybeOutputJSON(cmd, result); done {
return jsonErr
Expand Down Expand Up @@ -245,7 +245,7 @@ func (h Handler) restoreFromDir(ctx context.Context, cmd *cobra.Command, conf *c
return err
}
defer releasePins()
return h.runDirectRestore(ctx, cmd, hyper, dcr, vm.ID, vmCfg, dir, cfg.ID,
return h.runDirectRestore(ctx, cmd, conf, hyper, dcr, vm.ID, vmCfg, dir, cfg.ID,
fmt.Sprintf("dir %s", dir), logger)
}

Expand All @@ -265,9 +265,6 @@ func (h Handler) cloneFromDir(ctx context.Context, cmd *cobra.Command, conf *con
if err != nil {
return fmt.Errorf("load envelope: %w", err)
}
if conf == nil {
return fmt.Errorf("nil config")
}
// Local copy keeps backend flip from leaking to the caller's shared *config.Config.
localConf := *conf
if cfg.Hypervisor != "" {
Expand Down Expand Up @@ -303,7 +300,7 @@ func (h Handler) cloneFromSrcDir(ctx context.Context, cmd *cobra.Command, conf *
rollbackReserve()
return fmt.Errorf("clone VM: %w", cloneErr)
}
h.reseedAfterResume(ctx, hyper, vm, true)
h.reseedAfterResume(ctx, conf, hyper, vm, true)

if wantJSON {
return cliutil.OutputJSON(vm)
Expand Down Expand Up @@ -369,7 +366,7 @@ func (h Handler) prepareClone(ctx context.Context, cmd *cobra.Command, conf *con
return vmCfg, vmID, rollbackReserve, unlock, netProvider, netSetup, nil
}

func (h Handler) restoreDirect(ctx context.Context, cmd *cobra.Command, snapRef, vmRef string, vmCfg *types.VMConfig, snapBackend snapshot.Snapshot, hyper hypervisor.Hypervisor, logger *log.Fields) (bool, error) {
func (h Handler) restoreDirect(ctx context.Context, cmd *cobra.Command, conf *config.Config, snapRef, vmRef string, vmCfg *types.VMConfig, snapBackend snapshot.Snapshot, hyper hypervisor.Hypervisor, logger *log.Fields) (bool, error) {
da, ok := snapBackend.(snapshot.Direct)
if !ok {
return false, nil
Expand All @@ -383,11 +380,11 @@ func (h Handler) restoreDirect(ctx context.Context, cmd *cobra.Command, snapRef,
return true, fmt.Errorf("open snapshot: %w", err)
}
defer release()
return true, h.runDirectRestore(ctx, cmd, hyper, dcr, vmRef, vmCfg, dataDir, snapCfg.ID,
return true, h.runDirectRestore(ctx, cmd, conf, hyper, dcr, vmRef, vmCfg, dataDir, snapCfg.ID,
fmt.Sprintf("snapshot %s", snapRef), logger)
}

func (h Handler) runDirectRestore(ctx context.Context, cmd *cobra.Command, hyper hypervisor.Hypervisor, dcr hypervisor.Direct, vmRef string, vmCfg *types.VMConfig, srcDir, sourceSnapshotID, sourceLabel string, logger *log.Fields) error {
func (h Handler) runDirectRestore(ctx context.Context, cmd *cobra.Command, conf *config.Config, hyper hypervisor.Hypervisor, dcr hypervisor.Direct, vmRef string, vmCfg *types.VMConfig, srcDir, sourceSnapshotID, sourceLabel string, logger *log.Fields) error {
wantJSON := cliutil.WantJSON(cmd)
if !wantJSON {
logger.Infof(ctx, "restoring VM %s from %s (direct) ...", vmRef, sourceLabel)
Expand All @@ -396,7 +393,7 @@ func (h Handler) runDirectRestore(ctx context.Context, cmd *cobra.Command, hyper
if err != nil {
return fmt.Errorf("restore: %w", err)
}
h.reseedAfterResume(ctx, hyper, result, false)
h.reseedAfterResume(ctx, conf, hyper, result, false)
if wantJSON {
return cliutil.OutputJSON(result)
}
Expand Down Expand Up @@ -596,9 +593,6 @@ func initNetwork(ctx context.Context, conf *config.Config, vmID string, nics int
}

func rollbackNetwork(ctx context.Context, netProvider network.Network, vmID string) {
if netProvider == nil {
return
}
// Survive Ctrl-C, bounded so a hung plugin can't wedge the CLI; an aborted rollback keeps its records for GC retry.
ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), rollbackTimeout)
defer cancel()
Expand Down
2 changes: 1 addition & 1 deletion daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (d *Daemon) settle(ctx context.Context) bool {

// gcTick returns the periodic-GC channel and its stop; with GC off (the default) the nil channel keeps the select arm inert.
func (d *Daemon) gcTick() (<-chan time.Time, func()) {
if d.conf.GCInterval <= 0 || d.conf.GC == nil {
if d.conf.GCInterval <= 0 {
return nil, func() {}
}
t := time.NewTicker(d.conf.GCInterval)
Expand Down
2 changes: 1 addition & 1 deletion hypervisor/baseconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (c *BaseConfig) LoadAndValidateMeta(dir string) (*SnapshotMeta, error) {
}

// PreflightRestore runs the shared restore preflight against this backend's managed roots.
func (c *BaseConfig) PreflightRestore(srcDir string, rec *VMRecord, integrity func(srcDir string, sidecar []*types.StorageConfig) error) error {
func (c *BaseConfig) PreflightRestore(srcDir string, rec *VMRecord, integrity func(srcDir string, sidecar []*types.StorageConfig) error) (*SnapshotMeta, error) {
return PreflightRestore(srcDir, c.RootDir, c.Config.RunDir, rec, integrity)
}

Expand Down
14 changes: 9 additions & 5 deletions hypervisor/cloudhypervisor/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ func (ch *CloudHypervisor) Clone(ctx context.Context, vmID string, vmCfg *types.
}

func (ch *CloudHypervisor) cloneAfterExtract(ctx context.Context, vmID string, vmCfg *types.VMConfig, net types.NetSetup, runDir, logDir string, now time.Time, sourceSnapshotID string) (*types.VM, error) {
networkConfigs := net.NetworkConfigs
logger := log.WithFunc("cloudhypervisor.cloneAfterExtract")

chConfigPath := filepath.Join(runDir, configJSONName)
chCfg, err := parseCHConfig(chConfigPath)
chCfg, err := parseCHConfig(filepath.Join(runDir, configJSONName))
if err != nil {
return nil, fmt.Errorf("parse CH config: %w", err)
}
return ch.cloneAfterExtractParsed(ctx, vmID, vmCfg, net, runDir, logDir, now, sourceSnapshotID, chCfg)
}

// cloneAfterExtractParsed is cloneAfterExtract minus the config.json parse; DirectClone passes the copy step's parse of the verbatim-copied source.
func (ch *CloudHypervisor) cloneAfterExtractParsed(ctx context.Context, vmID string, vmCfg *types.VMConfig, net types.NetSetup, runDir, logDir string, now time.Time, sourceSnapshotID string, chCfg *chVMConfig) (*types.VM, error) {
networkConfigs := net.NetworkConfigs
logger := log.WithFunc("cloudhypervisor.cloneAfterExtract")

chConfigPath := filepath.Join(runDir, configJSONName)
vmCfg.RestoreMode = resolveRestoreMode(ctx, vmCfg.RestoreMode, chCfg.Memory)

meta, err := ch.conf.LoadAndValidateMeta(runDir)
Expand Down
27 changes: 19 additions & 8 deletions hypervisor/cloudhypervisor/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,58 @@ import (
"fmt"
"path/filepath"
"strings"
"time"

"github.com/cocoonstack/cocoon/hypervisor"
"github.com/cocoonstack/cocoon/types"
)

// DirectClone clones from a local snapshot dir. Per-type: hardlink memory-range-*, reflink/copy COW, plain copy metadata; cidata is regenerated.
func (ch *CloudHypervisor) DirectClone(ctx context.Context, vmID string, vmCfg *types.VMConfig, net types.NetSetup, snapshotConfig *types.SnapshotConfig, srcDir string) (*types.VM, error) {
// The copy step's parse feeds cloneAfterExtractParsed: config.json is copied verbatim, so re-parsing the runDir copy would decode identical bytes.
var srcCfg *chVMConfig
return ch.DirectCloneBase(ctx, vmID, vmCfg, net, snapshotConfig, srcDir, func(dstDir, srcDir string) error {
return cloneSnapshotFiles(ctx, dstDir, srcDir)
}, ch.cloneAfterExtract)
var err error
srcCfg, err = cloneSnapshotFiles(ctx, dstDir, srcDir)
return err
}, func(ctx context.Context, vmID string, vmCfg *types.VMConfig, net types.NetSetup, runDir, logDir string, now time.Time, sourceSnapshotID string) (*types.VM, error) {
return ch.cloneAfterExtractParsed(ctx, vmID, vmCfg, net, runDir, logDir, now, sourceSnapshotID, srcCfg)
})
}

// DirectRestore restores a VM in place from a local snapshot dir.
func (ch *CloudHypervisor) DirectRestore(ctx context.Context, vmRef string, vmCfg *types.VMConfig, srcDir, sourceSnapshotID string) (*types.VM, error) {
var meta *hypervisor.SnapshotMeta
return ch.DirectRestoreSequence(ctx, vmRef, hypervisor.DirectRestoreSpec{
VMCfg: vmCfg,
SrcDir: srcDir,
SourceSnapshotID: sourceSnapshotID,
Preflight: func(srcDir string, rec *hypervisor.VMRecord) error {
return ch.preflightRestore(ctx, srcDir, rec, vmCfg)
var err error
meta, err = ch.preflightRestore(ctx, srcDir, rec, vmCfg)
return err
},
Kill: ch.killForRestore,
Populate: func(rec *hypervisor.VMRecord, srcDir string) error {
return hypervisor.PopulateFromSrc(rec.RunDir, srcDir, cleanSnapshotFiles, func(dstDir, srcDir string) error {
return cloneSnapshotFiles(ctx, dstDir, srcDir)
_, err := cloneSnapshotFiles(ctx, dstDir, srcDir)
return err
})
},
AfterExtract: func(ctx context.Context, vmID string, vmCfg *types.VMConfig, rec *hypervisor.VMRecord) (*types.VM, error) {
directBoot := hypervisor.IsDirectBoot(rec.BootConfig)
return ch.restoreAfterExtract(ctx, vmID, vmCfg, rec, directBoot)
return ch.restoreAfterExtract(ctx, vmID, vmCfg, rec, directBoot, meta)
},
})
}

func cloneSnapshotFiles(ctx context.Context, dstDir, srcDir string) error {
func cloneSnapshotFiles(ctx context.Context, dstDir, srcDir string) (*chVMConfig, error) {
chCfg, err := parseCHConfig(filepath.Join(srcDir, configJSONName))
if err != nil {
return fmt.Errorf("parse source config: %w", err)
return nil, fmt.Errorf("parse source config: %w", err)
}
cowFiles := identifyCOWFiles(chCfg)
return hypervisor.CloneSnapshotFiles(ctx, dstDir, srcDir, func(name string) hypervisor.SnapshotFileKind {
return chCfg, hypervisor.CloneSnapshotFiles(ctx, dstDir, srcDir, func(name string) hypervisor.SnapshotFileKind {
switch {
case strings.HasPrefix(name, memoryRangeFile):
return hypervisor.SnapshotFileMemory
Expand Down
Loading