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: 12 additions & 6 deletions lib/hypervisor/cloudhypervisor/cloudhypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ var _ hypervisor.Hypervisor = (*CloudHypervisor)(nil)

// Capabilities returns the features supported by Cloud Hypervisor.
func (c *CloudHypervisor) Capabilities() hypervisor.Capabilities {
return capabilities()
}

func capabilities() hypervisor.Capabilities {
return hypervisor.Capabilities{
SupportsSnapshot: true,
SupportsHotplugMemory: true,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: true,
SupportsDiskIOLimit: true,
SupportsSnapshot: true,
SupportsHotplugMemory: true,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: true,
SupportsDiskIOLimit: true,
SupportsGracefulVMMShutdown: true,
SupportsSnapshotBaseReuse: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/hypervisor/cloudhypervisor/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

func init() {
hypervisor.RegisterSocketName(hypervisor.TypeCloudHypervisor, "ch.sock")
hypervisor.RegisterCapabilities(hypervisor.TypeCloudHypervisor, capabilities())
hypervisor.RegisterClientFactory(hypervisor.TypeCloudHypervisor, func(socketPath string) (hypervisor.Hypervisor, error) {
return New(socketPath)
})
Expand Down
7 changes: 6 additions & 1 deletion lib/hypervisor/firecracker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,15 @@ func toRateLimiter(limit int64, burst int64) *rateLimiter {
}

func toSnapshotCreateParams(snapshotDir string) snapshotCreateParams {
snapshotType := "Full"
if _, err := os.Stat(snapshotMemoryPath(snapshotDir)); err == nil {
snapshotType = "Diff"
}

return snapshotCreateParams{
MemFilePath: snapshotMemoryPath(snapshotDir),
SnapshotPath: snapshotStatePath(snapshotDir),
SnapshotType: "Full",
SnapshotType: snapshotType,
}
}

Expand Down
24 changes: 20 additions & 4 deletions lib/hypervisor/firecracker/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package firecracker

import (
"os"
"path/filepath"
"testing"

"github.com/kernel/hypeman/lib/hypervisor"
Expand Down Expand Up @@ -59,10 +61,24 @@ func TestToNetworkInterfaces(t *testing.T) {
}

func TestSnapshotParamPaths(t *testing.T) {
create := toSnapshotCreateParams("/tmp/snapshot-latest")
assert.Equal(t, "/tmp/snapshot-latest/state", create.SnapshotPath)
assert.Equal(t, "/tmp/snapshot-latest/memory", create.MemFilePath)
assert.Equal(t, "Full", create.SnapshotType)
t.Run("uses full snapshots when no retained base exists", func(t *testing.T) {
snapshotDir := filepath.Join(t.TempDir(), "snapshot-latest")
create := toSnapshotCreateParams(snapshotDir)
assert.Equal(t, filepath.Join(snapshotDir, "state"), create.SnapshotPath)
assert.Equal(t, filepath.Join(snapshotDir, "memory"), create.MemFilePath)
assert.Equal(t, "Full", create.SnapshotType)
})

t.Run("uses diff snapshots when retained base memory exists", func(t *testing.T) {
snapshotDir := filepath.Join(t.TempDir(), "snapshot-latest")
require.NoError(t, os.MkdirAll(snapshotDir, 0755))
require.NoError(t, os.WriteFile(filepath.Join(snapshotDir, "memory"), []byte("base"), 0644))

create := toSnapshotCreateParams(snapshotDir)
assert.Equal(t, filepath.Join(snapshotDir, "state"), create.SnapshotPath)
assert.Equal(t, filepath.Join(snapshotDir, "memory"), create.MemFilePath)
assert.Equal(t, "Diff", create.SnapshotType)
})

load := toSnapshotLoadParams("/tmp/snapshot-latest", []networkOverride{
{IfaceID: "eth0", HostDevName: "hype-abc123"},
Expand Down
18 changes: 12 additions & 6 deletions lib/hypervisor/firecracker/firecracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,19 @@ func New(socketPath string) (*Firecracker, error) {
var _ hypervisor.Hypervisor = (*Firecracker)(nil)

func (f *Firecracker) Capabilities() hypervisor.Capabilities {
return capabilities()
}

func capabilities() hypervisor.Capabilities {
return hypervisor.Capabilities{
SupportsSnapshot: true,
SupportsHotplugMemory: false,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: false,
SupportsDiskIOLimit: true,
SupportsSnapshot: true,
SupportsHotplugMemory: false,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: false,
SupportsDiskIOLimit: true,
SupportsGracefulVMMShutdown: false,
SupportsSnapshotBaseReuse: true,
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/hypervisor/firecracker/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (

func init() {
hypervisor.RegisterSocketName(hypervisor.TypeFirecracker, "fc.sock")
hypervisor.RegisterCapabilities(hypervisor.TypeFirecracker, capabilities())
hypervisor.RegisterClientFactory(hypervisor.TypeFirecracker, func(socketPath string) (hypervisor.Hypervisor, error) {
return New(socketPath)
})
Expand Down
23 changes: 23 additions & 0 deletions lib/hypervisor/hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ var socketNames = make(map[Type]string)
// Registered by hypervisor packages when they use socket-based vsock routing.
var vsockSocketNames = make(map[Type]string)

// capabilitiesByType maps hypervisor types to their static capabilities.
// Registered by each hypervisor package's init() function.
var capabilitiesByType = make(map[Type]Capabilities)

// RegisterSocketName registers the socket filename for a hypervisor type.
// Called by each hypervisor implementation's init() function.
func RegisterSocketName(t Type, name string) {
Expand Down Expand Up @@ -74,6 +78,17 @@ func VsockSocketNameForType(t Type) string {
return "vsock.sock"
}

// RegisterCapabilities registers static capabilities for a hypervisor type.
func RegisterCapabilities(t Type, caps Capabilities) {
capabilitiesByType[t] = caps
}

// CapabilitiesForType returns static capabilities for a hypervisor type.
func CapabilitiesForType(t Type) (Capabilities, bool) {
caps, ok := capabilitiesByType[t]
return caps, ok
}

// VMStarter handles the full VM startup sequence.
// Each hypervisor implements its own startup flow:
// - Cloud Hypervisor: starts process, configures via HTTP API, boots via HTTP API
Expand Down Expand Up @@ -197,6 +212,14 @@ type Capabilities struct {

// SupportsDiskIOLimit indicates if disk I/O rate limiting is available
SupportsDiskIOLimit bool

// SupportsGracefulVMMShutdown indicates the hypervisor exposes an API to
// ask the VMM process itself to exit cleanly.
SupportsGracefulVMMShutdown bool

// SupportsSnapshotBaseReuse indicates snapshots can safely reuse a retained
// on-disk base across restore/standby cycles.
SupportsSnapshotBaseReuse bool
}

// VsockDialer provides vsock connectivity to a guest VM.
Expand Down
1 change: 1 addition & 0 deletions lib/hypervisor/qemu/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (

func init() {
hypervisor.RegisterSocketName(hypervisor.TypeQEMU, "qemu.sock")
hypervisor.RegisterCapabilities(hypervisor.TypeQEMU, capabilities())
hypervisor.RegisterClientFactory(hypervisor.TypeQEMU, func(socketPath string) (hypervisor.Hypervisor, error) {
return New(socketPath)
})
Expand Down
18 changes: 12 additions & 6 deletions lib/hypervisor/qemu/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ var _ hypervisor.Hypervisor = (*QEMU)(nil)

// Capabilities returns the features supported by QEMU.
func (q *QEMU) Capabilities() hypervisor.Capabilities {
return capabilities()
}

func capabilities() hypervisor.Capabilities {
return hypervisor.Capabilities{
SupportsSnapshot: true, // Uses QMP migrate file:// for snapshot
SupportsHotplugMemory: false, // Not implemented - balloon not configured
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: true,
SupportsDiskIOLimit: true,
SupportsSnapshot: true, // Uses QMP migrate file:// for snapshot
SupportsHotplugMemory: false, // Not implemented - balloon not configured
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: true,
SupportsDiskIOLimit: true,
SupportsGracefulVMMShutdown: true,
SupportsSnapshotBaseReuse: false,
}
}

Expand Down
18 changes: 12 additions & 6 deletions lib/hypervisor/vz/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ type snapshotRequest struct {
}

func (c *Client) Capabilities() hypervisor.Capabilities {
return capabilities()
}

func capabilities() hypervisor.Capabilities {
return hypervisor.Capabilities{
SupportsSnapshot: runtime.GOARCH == "arm64",
SupportsHotplugMemory: false,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: false,
SupportsDiskIOLimit: false,
SupportsSnapshot: runtime.GOARCH == "arm64",
SupportsHotplugMemory: false,
SupportsPause: true,
SupportsVsock: true,
SupportsGPUPassthrough: false,
SupportsDiskIOLimit: false,
SupportsGracefulVMMShutdown: true,
SupportsSnapshotBaseReuse: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/hypervisor/vz/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

func init() {
hypervisor.RegisterSocketName(hypervisor.TypeVZ, "vz.sock")
hypervisor.RegisterCapabilities(hypervisor.TypeVZ, capabilities())
hypervisor.RegisterVsockSocketName(hypervisor.TypeVZ, "vz.vsock")
hypervisor.RegisterVsockDialerFactory(hypervisor.TypeVZ, NewVsockDialer)
hypervisor.RegisterClientFactory(hypervisor.TypeVZ, func(socketPath string) (hypervisor.Hypervisor, error) {
Expand Down
Loading
Loading