Skip to content

Commit 52982d0

Browse files
committed
Move vmm calls behind hypervisor interface
1 parent 4b0c8f3 commit 52982d0

File tree

17 files changed

+805
-397
lines changed

17 files changed

+805
-397
lines changed

cmd/api/api/api_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func newTestService(t *testing.T) *ApiService {
5656
}
5757
}
5858

59-
// cleanupOrphanedProcesses kills Cloud Hypervisor processes from metadata files
59+
// cleanupOrphanedProcesses kills hypervisor processes from metadata files
6060
func cleanupOrphanedProcesses(t *testing.T, dataDir string) {
6161
p := paths.New(dataDir)
6262
guestsDir := p.GuestsDir()
@@ -77,21 +77,21 @@ func cleanupOrphanedProcesses(t *testing.T, dataDir string) {
7777
continue
7878
}
7979

80-
// Parse just the CHPID field
80+
// Parse just the HypervisorPID field
8181
var meta struct {
82-
CHPID *int `json:"CHPID"`
82+
HypervisorPID *int `json:"HypervisorPID"`
8383
}
8484
if err := json.Unmarshal(data, &meta); err != nil {
8585
continue
8686
}
8787

8888
// If metadata has a PID, try to kill it
89-
if meta.CHPID != nil {
90-
pid := *meta.CHPID
89+
if meta.HypervisorPID != nil {
90+
pid := *meta.HypervisorPID
9191

9292
// Check if process exists
9393
if err := syscall.Kill(pid, 0); err == nil {
94-
t.Logf("Cleaning up orphaned Cloud Hypervisor process: PID %d", pid)
94+
t.Logf("Cleaning up orphaned hypervisor process: PID %d", pid)
9595
syscall.Kill(pid, syscall.SIGKILL)
9696
}
9797
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Package cloudhypervisor implements the hypervisor.Hypervisor interface
2+
// for Cloud Hypervisor VMM.
3+
package cloudhypervisor
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
"github.com/onkernel/hypeman/lib/hypervisor"
10+
"github.com/onkernel/hypeman/lib/vmm"
11+
)
12+
13+
// CloudHypervisor implements hypervisor.Hypervisor for Cloud Hypervisor VMM.
14+
type CloudHypervisor struct {
15+
client *vmm.VMM
16+
socketPath string
17+
}
18+
19+
// New creates a new Cloud Hypervisor client for an existing VMM socket.
20+
func New(socketPath string) (*CloudHypervisor, error) {
21+
client, err := vmm.NewVMM(socketPath)
22+
if err != nil {
23+
return nil, fmt.Errorf("create vmm client: %w", err)
24+
}
25+
return &CloudHypervisor{
26+
client: client,
27+
socketPath: socketPath,
28+
}, nil
29+
}
30+
31+
// Capabilities returns the features supported by Cloud Hypervisor.
32+
func (c *CloudHypervisor) Capabilities() hypervisor.Capabilities {
33+
return hypervisor.Capabilities{
34+
SupportsSnapshot: true,
35+
SupportsHotplugMemory: true,
36+
SupportsPause: true,
37+
SupportsVsock: true,
38+
SupportsGPUPassthrough: true,
39+
}
40+
}
41+
42+
// CreateVM configures the VM in Cloud Hypervisor.
43+
func (c *CloudHypervisor) CreateVM(ctx context.Context, config hypervisor.VMConfig) error {
44+
vmConfig := ToVMConfig(config)
45+
resp, err := c.client.CreateVMWithResponse(ctx, vmConfig)
46+
if err != nil {
47+
return fmt.Errorf("create vm: %w", err)
48+
}
49+
if resp.StatusCode() != 204 {
50+
return fmt.Errorf("create vm failed with status %d: %s", resp.StatusCode(), string(resp.Body))
51+
}
52+
return nil
53+
}
54+
55+
// BootVM starts the configured VM.
56+
func (c *CloudHypervisor) BootVM(ctx context.Context) error {
57+
resp, err := c.client.BootVMWithResponse(ctx)
58+
if err != nil {
59+
return fmt.Errorf("boot vm: %w", err)
60+
}
61+
if resp.StatusCode() != 204 {
62+
return fmt.Errorf("boot vm failed with status %d: %s", resp.StatusCode(), string(resp.Body))
63+
}
64+
return nil
65+
}
66+
67+
// DeleteVM removes the VM configuration from Cloud Hypervisor.
68+
func (c *CloudHypervisor) DeleteVM(ctx context.Context) error {
69+
resp, err := c.client.DeleteVMWithResponse(ctx)
70+
if err != nil {
71+
return fmt.Errorf("delete vm: %w", err)
72+
}
73+
if resp.StatusCode() != 204 {
74+
return fmt.Errorf("delete vm failed with status %d: %s", resp.StatusCode(), string(resp.Body))
75+
}
76+
return nil
77+
}
78+
79+
// Shutdown stops the VMM process gracefully.
80+
func (c *CloudHypervisor) Shutdown(ctx context.Context) error {
81+
resp, err := c.client.ShutdownVMMWithResponse(ctx)
82+
if err != nil {
83+
return fmt.Errorf("shutdown vmm: %w", err)
84+
}
85+
// ShutdownVMM may return various codes, 204 is success
86+
if resp.StatusCode() != 204 {
87+
return fmt.Errorf("shutdown vmm failed with status %d", resp.StatusCode())
88+
}
89+
return nil
90+
}
91+
92+
// GetVMInfo returns current VM state.
93+
func (c *CloudHypervisor) GetVMInfo(ctx context.Context) (*hypervisor.VMInfo, error) {
94+
resp, err := c.client.GetVmInfoWithResponse(ctx)
95+
if err != nil {
96+
return nil, fmt.Errorf("get vm info: %w", err)
97+
}
98+
if resp.StatusCode() != 200 || resp.JSON200 == nil {
99+
return nil, fmt.Errorf("get vm info failed with status %d", resp.StatusCode())
100+
}
101+
102+
// Map Cloud Hypervisor state to hypervisor.VMState
103+
var state hypervisor.VMState
104+
switch resp.JSON200.State {
105+
case vmm.Created:
106+
state = hypervisor.StateCreated
107+
case vmm.Running:
108+
state = hypervisor.StateRunning
109+
case vmm.Paused:
110+
state = hypervisor.StatePaused
111+
case vmm.Shutdown:
112+
state = hypervisor.StateShutdown
113+
default:
114+
return nil, fmt.Errorf("unknown vm state: %s", resp.JSON200.State)
115+
}
116+
117+
return &hypervisor.VMInfo{State: state}, nil
118+
}
119+
120+
// Pause suspends VM execution.
121+
func (c *CloudHypervisor) Pause(ctx context.Context) error {
122+
resp, err := c.client.PauseVMWithResponse(ctx)
123+
if err != nil {
124+
return fmt.Errorf("pause vm: %w", err)
125+
}
126+
if resp.StatusCode() != 204 {
127+
return fmt.Errorf("pause vm failed with status %d", resp.StatusCode())
128+
}
129+
return nil
130+
}
131+
132+
// Resume continues VM execution.
133+
func (c *CloudHypervisor) Resume(ctx context.Context) error {
134+
resp, err := c.client.ResumeVMWithResponse(ctx)
135+
if err != nil {
136+
return fmt.Errorf("resume vm: %w", err)
137+
}
138+
if resp.StatusCode() != 204 {
139+
return fmt.Errorf("resume vm failed with status %d", resp.StatusCode())
140+
}
141+
return nil
142+
}
143+
144+
// Snapshot creates a VM snapshot.
145+
func (c *CloudHypervisor) Snapshot(ctx context.Context, destPath string) error {
146+
snapshotURL := "file://" + destPath
147+
snapshotConfig := vmm.VmSnapshotConfig{DestinationUrl: &snapshotURL}
148+
resp, err := c.client.PutVmSnapshotWithResponse(ctx, snapshotConfig)
149+
if err != nil {
150+
return fmt.Errorf("snapshot: %w", err)
151+
}
152+
if resp.StatusCode() != 204 {
153+
return fmt.Errorf("snapshot failed with status %d", resp.StatusCode())
154+
}
155+
return nil
156+
}
157+
158+
// Restore loads a VM from snapshot.
159+
func (c *CloudHypervisor) Restore(ctx context.Context, sourcePath string) error {
160+
sourceURL := "file://" + sourcePath
161+
restoreConfig := vmm.RestoreConfig{
162+
SourceUrl: sourceURL,
163+
Prefault: ptr(false),
164+
}
165+
resp, err := c.client.PutVmRestoreWithResponse(ctx, restoreConfig)
166+
if err != nil {
167+
return fmt.Errorf("restore: %w", err)
168+
}
169+
if resp.StatusCode() != 204 {
170+
return fmt.Errorf("restore failed with status %d", resp.StatusCode())
171+
}
172+
return nil
173+
}
174+
175+
// ResizeMemory changes the VM's memory allocation.
176+
func (c *CloudHypervisor) ResizeMemory(ctx context.Context, bytes int64) error {
177+
resizeConfig := vmm.VmResize{DesiredRam: &bytes}
178+
resp, err := c.client.PutVmResizeWithResponse(ctx, resizeConfig)
179+
if err != nil {
180+
return fmt.Errorf("resize memory: %w", err)
181+
}
182+
if resp.StatusCode() != 204 {
183+
return fmt.Errorf("resize memory failed with status %d", resp.StatusCode())
184+
}
185+
return nil
186+
}
187+
188+
func ptr[T any](v T) *T {
189+
return &v
190+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package cloudhypervisor
2+
3+
import (
4+
"github.com/onkernel/hypeman/lib/hypervisor"
5+
"github.com/onkernel/hypeman/lib/vmm"
6+
)
7+
8+
// ToVMConfig converts hypervisor.VMConfig to Cloud Hypervisor's vmm.VmConfig.
9+
func ToVMConfig(cfg hypervisor.VMConfig) vmm.VmConfig {
10+
// Payload configuration (kernel + initramfs)
11+
payload := vmm.PayloadConfig{
12+
Kernel: ptr(cfg.KernelPath),
13+
Cmdline: ptr(cfg.KernelArgs),
14+
Initramfs: ptr(cfg.InitrdPath),
15+
}
16+
17+
// CPU configuration
18+
cpus := vmm.CpusConfig{
19+
BootVcpus: cfg.VCPUs,
20+
MaxVcpus: cfg.VCPUs,
21+
}
22+
23+
// Add topology if provided
24+
if cfg.Topology != nil {
25+
cpus.Topology = &vmm.CpuTopology{
26+
ThreadsPerCore: ptr(cfg.Topology.ThreadsPerCore),
27+
CoresPerDie: ptr(cfg.Topology.CoresPerDie),
28+
DiesPerPackage: ptr(cfg.Topology.DiesPerPackage),
29+
Packages: ptr(cfg.Topology.Packages),
30+
}
31+
}
32+
33+
// Memory configuration
34+
memory := vmm.MemoryConfig{
35+
Size: cfg.MemoryBytes,
36+
}
37+
if cfg.HotplugBytes > 0 {
38+
memory.HotplugSize = &cfg.HotplugBytes
39+
memory.HotplugMethod = ptr("VirtioMem")
40+
}
41+
42+
// Disk configuration
43+
var disks []vmm.DiskConfig
44+
for _, d := range cfg.Disks {
45+
disk := vmm.DiskConfig{
46+
Path: ptr(d.Path),
47+
}
48+
if d.Readonly {
49+
disk.Readonly = ptr(true)
50+
}
51+
disks = append(disks, disk)
52+
}
53+
54+
// Serial console configuration
55+
serial := vmm.ConsoleConfig{
56+
Mode: vmm.ConsoleConfigMode("File"),
57+
File: ptr(cfg.SerialLogPath),
58+
}
59+
60+
// Console off (we use serial)
61+
console := vmm.ConsoleConfig{
62+
Mode: vmm.ConsoleConfigMode("Off"),
63+
}
64+
65+
// Network configuration
66+
var nets *[]vmm.NetConfig
67+
if len(cfg.Networks) > 0 {
68+
netConfigs := make([]vmm.NetConfig, 0, len(cfg.Networks))
69+
for _, n := range cfg.Networks {
70+
netConfigs = append(netConfigs, vmm.NetConfig{
71+
Tap: ptr(n.TAPDevice),
72+
Ip: ptr(n.IP),
73+
Mac: ptr(n.MAC),
74+
Mask: ptr(n.Netmask),
75+
})
76+
}
77+
nets = &netConfigs
78+
}
79+
80+
// Vsock configuration
81+
var vsock *vmm.VsockConfig
82+
if cfg.VsockCID > 0 {
83+
vsock = &vmm.VsockConfig{
84+
Cid: cfg.VsockCID,
85+
Socket: cfg.VsockSocket,
86+
}
87+
}
88+
89+
// Device passthrough configuration
90+
var devices *[]vmm.DeviceConfig
91+
if len(cfg.PCIDevices) > 0 {
92+
deviceConfigs := make([]vmm.DeviceConfig, 0, len(cfg.PCIDevices))
93+
for _, path := range cfg.PCIDevices {
94+
deviceConfigs = append(deviceConfigs, vmm.DeviceConfig{
95+
Path: path,
96+
})
97+
}
98+
devices = &deviceConfigs
99+
}
100+
101+
return vmm.VmConfig{
102+
Payload: payload,
103+
Cpus: &cpus,
104+
Memory: &memory,
105+
Disks: &disks,
106+
Serial: &serial,
107+
Console: &console,
108+
Net: nets,
109+
Vsock: vsock,
110+
Devices: devices,
111+
}
112+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cloudhypervisor
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/onkernel/hypeman/lib/hypervisor"
8+
"github.com/onkernel/hypeman/lib/paths"
9+
"github.com/onkernel/hypeman/lib/vmm"
10+
)
11+
12+
// ProcessManager implements hypervisor.ProcessManager for Cloud Hypervisor.
13+
type ProcessManager struct{}
14+
15+
// NewProcessManager creates a new Cloud Hypervisor process manager.
16+
func NewProcessManager() *ProcessManager {
17+
return &ProcessManager{}
18+
}
19+
20+
// Verify ProcessManager implements the interface
21+
var _ hypervisor.ProcessManager = (*ProcessManager)(nil)
22+
23+
// StartProcess launches a Cloud Hypervisor VMM process.
24+
func (p *ProcessManager) StartProcess(ctx context.Context, paths *paths.Paths, version string, socketPath string) (int, error) {
25+
chVersion := vmm.CHVersion(version)
26+
if !vmm.IsVersionSupported(chVersion) {
27+
return 0, fmt.Errorf("unsupported cloud-hypervisor version: %s", version)
28+
}
29+
return vmm.StartProcess(ctx, paths, chVersion, socketPath)
30+
}
31+
32+
// StartProcessWithArgs launches a Cloud Hypervisor VMM process with extra arguments.
33+
func (p *ProcessManager) StartProcessWithArgs(ctx context.Context, paths *paths.Paths, version string, socketPath string, extraArgs []string) (int, error) {
34+
chVersion := vmm.CHVersion(version)
35+
if !vmm.IsVersionSupported(chVersion) {
36+
return 0, fmt.Errorf("unsupported cloud-hypervisor version: %s", version)
37+
}
38+
return vmm.StartProcessWithArgs(ctx, paths, chVersion, socketPath, extraArgs)
39+
}
40+
41+
// GetBinaryPath returns the path to the Cloud Hypervisor binary.
42+
func (p *ProcessManager) GetBinaryPath(paths *paths.Paths, version string) (string, error) {
43+
chVersion := vmm.CHVersion(version)
44+
if !vmm.IsVersionSupported(chVersion) {
45+
return "", fmt.Errorf("unsupported cloud-hypervisor version: %s", version)
46+
}
47+
return vmm.GetBinaryPath(paths, chVersion)
48+
}

0 commit comments

Comments
 (0)