Skip to content

Commit 5bbaec6

Browse files
authored
Merge pull request #188 from walteh/feat/vm-memory-balloons
feat: VZMemoryBalloonDevice support
2 parents fe4cf05 + 3497539 commit 5bbaec6

File tree

4 files changed

+328
-0
lines changed

4 files changed

+328
-0
lines changed

memory_balloon.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package vz
77
*/
88
import "C"
99
import (
10+
"unsafe"
11+
1012
"github.com/Code-Hex/vz/v3/internal/objc"
1113
)
1214

@@ -51,3 +53,97 @@ func NewVirtioTraditionalMemoryBalloonDeviceConfiguration() (*VirtioTraditionalM
5153
})
5254
return config, nil
5355
}
56+
57+
// MemoryBalloonDevice is the base interface for memory balloon devices.
58+
//
59+
// This represents MemoryBalloonDevice in the Virtualization framework.
60+
// It is an abstract class that should not be directly used.
61+
//
62+
// see: https://developer.apple.com/documentation/virtualization/vzmemoryballoondevice?language=objc
63+
type MemoryBalloonDevice interface {
64+
objc.NSObject
65+
66+
memoryBalloonDevice()
67+
}
68+
69+
type baseMemoryBalloonDevice struct{}
70+
71+
func (*baseMemoryBalloonDevice) memoryBalloonDevice() {}
72+
73+
// MemoryBalloonDevices returns the list of memory balloon devices configured on this virtual machine.
74+
//
75+
// Returns an empty array if no memory balloon device is configured.
76+
//
77+
// This is only supported on macOS 11 and newer.
78+
func (v *VirtualMachine) MemoryBalloonDevices() []MemoryBalloonDevice {
79+
nsArray := objc.NewNSArray(
80+
C.VZVirtualMachine_memoryBalloonDevices(objc.Ptr(v)),
81+
)
82+
ptrs := nsArray.ToPointerSlice()
83+
devices := make([]MemoryBalloonDevice, len(ptrs))
84+
for i, ptr := range ptrs {
85+
// TODO: When Apple adds more memory balloon device types in future macOS versions,
86+
// implement type checking here to create the appropriate device wrapper.
87+
// Currently, VirtioTraditionalMemoryBalloonDevice is the only type supported.
88+
devices[i] = newVirtioTraditionalMemoryBalloonDevice(ptr, v)
89+
}
90+
return devices
91+
}
92+
93+
// VirtioTraditionalMemoryBalloonDevice represents a Virtio traditional memory balloon device.
94+
//
95+
// The balloon device allows for dynamic memory management by inflating or deflating
96+
// the balloon to control memory available to the guest OS.
97+
//
98+
// see: https://developer.apple.com/documentation/virtualization/vzvirtiotraditionalmemoryballoondevice?language=objc
99+
type VirtioTraditionalMemoryBalloonDevice struct {
100+
*pointer
101+
vm *VirtualMachine
102+
103+
*baseMemoryBalloonDevice
104+
}
105+
106+
var _ MemoryBalloonDevice = (*VirtioTraditionalMemoryBalloonDevice)(nil)
107+
108+
// AsVirtioTraditionalMemoryBalloonDevice attempts to convert a MemoryBalloonDevice to a VirtioTraditionalMemoryBalloonDevice.
109+
//
110+
// Returns the VirtioTraditionalMemoryBalloonDevice if the device is of that type, or nil otherwise.
111+
func AsVirtioTraditionalMemoryBalloonDevice(device MemoryBalloonDevice) *VirtioTraditionalMemoryBalloonDevice {
112+
if traditionalDevice, ok := device.(*VirtioTraditionalMemoryBalloonDevice); ok {
113+
return traditionalDevice
114+
}
115+
return nil
116+
}
117+
118+
func newVirtioTraditionalMemoryBalloonDevice(pointer unsafe.Pointer, vm *VirtualMachine) *VirtioTraditionalMemoryBalloonDevice {
119+
device := &VirtioTraditionalMemoryBalloonDevice{
120+
pointer: objc.NewPointer(pointer),
121+
vm: vm,
122+
}
123+
objc.SetFinalizer(device, func(self *VirtioTraditionalMemoryBalloonDevice) {
124+
objc.Release(self)
125+
})
126+
return device
127+
}
128+
129+
// SetTargetVirtualMachineMemorySize sets the target memory size in bytes for the virtual machine.
130+
//
131+
// This method inflates or deflates the memory balloon to adjust the amount of memory
132+
// available to the guest OS. The target memory size must be less than the total memory
133+
// configured for the virtual machine.
134+
//
135+
// This is only supported on macOS 11 and newer.
136+
func (v *VirtioTraditionalMemoryBalloonDevice) SetTargetVirtualMachineMemorySize(targetMemorySize uint64) {
137+
C.VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(
138+
objc.Ptr(v),
139+
v.vm.dispatchQueue,
140+
C.ulonglong(targetMemorySize),
141+
)
142+
}
143+
144+
// GetTargetVirtualMachineMemorySize returns the current target memory size in bytes for the virtual machine.
145+
//
146+
// This is only supported on macOS 11 and newer.
147+
func (v *VirtioTraditionalMemoryBalloonDevice) GetTargetVirtualMachineMemorySize() uint64 {
148+
return uint64(C.VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(objc.Ptr(v), v.vm.dispatchQueue))
149+
}

memory_balloon_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package vz_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/Code-Hex/vz/v3"
8+
)
9+
10+
func TestVirtioTraditionalMemoryBalloonDeviceConfiguration(t *testing.T) {
11+
// Create memory balloon device configuration
12+
config, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
13+
if err != nil {
14+
t.Fatalf("failed to create memory balloon device configuration: %v", err)
15+
}
16+
if config == nil {
17+
t.Fatal("memory balloon configuration should not be nil")
18+
}
19+
}
20+
21+
func TestMemoryBalloonDevices(t *testing.T) {
22+
// Create a simple VM configuration
23+
bootLoader, err := vz.NewLinuxBootLoader(
24+
"./testdata/Image",
25+
vz.WithCommandLine("console=hvc0"),
26+
)
27+
if err != nil {
28+
t.Fatalf("failed to create boot loader: %v", err)
29+
}
30+
31+
config, err := vz.NewVirtualMachineConfiguration(
32+
bootLoader,
33+
1,
34+
256*1024*1024,
35+
)
36+
if err != nil {
37+
t.Fatalf("failed to create virtual machine configuration: %v", err)
38+
}
39+
40+
// Create and add a memory balloon device
41+
memoryBalloonConfig, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
42+
if err != nil {
43+
t.Fatalf("failed to create memory balloon device configuration: %v", err)
44+
}
45+
46+
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
47+
memoryBalloonConfig,
48+
})
49+
50+
// Create the VM
51+
vm, err := vz.NewVirtualMachine(config)
52+
if err != nil {
53+
t.Fatalf("failed to create virtual machine: %v", err)
54+
}
55+
56+
// Get memory balloon devices
57+
balloonDevices := vm.MemoryBalloonDevices()
58+
if len(balloonDevices) != 1 {
59+
t.Fatalf("expected 1 memory balloon device, got %d", len(balloonDevices))
60+
}
61+
62+
// Verify we can access the balloon device
63+
balloonDevice := balloonDevices[0]
64+
if balloonDevice == nil {
65+
t.Fatal("memory balloon device should not be nil")
66+
}
67+
68+
// Verify we can cast to VirtioTraditionalMemoryBalloonDevice
69+
traditionalDevice := vz.AsVirtioTraditionalMemoryBalloonDevice(balloonDevice)
70+
if traditionalDevice == nil {
71+
t.Fatal("failed to cast to VirtioTraditionalMemoryBalloonDevice")
72+
}
73+
}
74+
75+
func TestMemoryBalloonTargetSizeAdjustment(t *testing.T) {
76+
// Create a VM with a memory balloon device
77+
bootLoader, err := vz.NewLinuxBootLoader(
78+
"./testdata/Image",
79+
vz.WithCommandLine("console=hvc0"),
80+
vz.WithInitrd("./testdata/initramfs.cpio.gz"),
81+
)
82+
if err != nil {
83+
t.Fatalf("failed to create boot loader: %v", err)
84+
}
85+
86+
startingMemory := uint64(512 * 1024 * 1024)
87+
targetMemory := uint64(300 * 1024 * 1024)
88+
89+
t.Logf("Starting memory: %d bytes", startingMemory)
90+
t.Logf("Target memory: %d bytes", targetMemory)
91+
92+
config, err := vz.NewVirtualMachineConfiguration(
93+
bootLoader,
94+
1,
95+
startingMemory,
96+
)
97+
if err != nil {
98+
t.Fatalf("failed to create virtual machine configuration: %v", err)
99+
}
100+
101+
// Create memory balloon device
102+
memoryBalloonConfig, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
103+
if err != nil {
104+
t.Fatalf("failed to create memory balloon device configuration: %v", err)
105+
}
106+
107+
// Add memory balloon device to VM configuration
108+
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
109+
memoryBalloonConfig,
110+
})
111+
112+
// Validate the configuration
113+
valid, err := config.Validate()
114+
if err != nil {
115+
t.Fatalf("configuration validation failed: %v", err)
116+
}
117+
if !valid {
118+
t.Fatal("configuration is not valid")
119+
}
120+
121+
// Create the VM
122+
vm, err := vz.NewVirtualMachine(config)
123+
if err != nil {
124+
t.Fatalf("failed to create virtual machine: %v", err)
125+
}
126+
127+
// Check memory balloon devices
128+
balloonDevices := vm.MemoryBalloonDevices()
129+
if len(balloonDevices) != 1 {
130+
t.Fatalf("expected 1 memory balloon device, got %d", len(balloonDevices))
131+
}
132+
133+
// Cast to VirtioTraditionalMemoryBalloonDevice
134+
balloonDevice := vz.AsVirtioTraditionalMemoryBalloonDevice(balloonDevices[0])
135+
if balloonDevice == nil {
136+
t.Fatal("failed to cast to VirtioTraditionalMemoryBalloonDevice")
137+
}
138+
139+
// Start the VM
140+
t.Log("Starting virtual machine...")
141+
err = vm.Start()
142+
if err != nil {
143+
t.Fatalf("failed to start virtual machine: %v", err)
144+
}
145+
146+
defer func() {
147+
if vm.CanStop() {
148+
_ = vm.Stop() // Cleanup VM
149+
}
150+
}()
151+
152+
// Wait until the VM is running
153+
err = waitUntilState(10*time.Second, vm, vz.VirtualMachineStateRunning)
154+
if err != nil {
155+
t.Fatalf("failed to wait for VM to start: %v", err)
156+
}
157+
158+
// Get the current target memory size
159+
currentMemoryBefore := balloonDevice.GetTargetVirtualMachineMemorySize()
160+
161+
if currentMemoryBefore != startingMemory {
162+
t.Fatalf("expected starting memory size to be %d, got %d", startingMemory, currentMemoryBefore)
163+
}
164+
165+
// Set a new target memory size
166+
balloonDevice.SetTargetVirtualMachineMemorySize(targetMemory)
167+
168+
// Verify the new memory size was set
169+
currentMemoryAfter := balloonDevice.GetTargetVirtualMachineMemorySize()
170+
171+
if currentMemoryAfter != targetMemory {
172+
t.Fatalf("expected memory size after adjustment to be %d, got %d", targetMemory, currentMemoryAfter)
173+
}
174+
}

virtualization_11.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ void *VZVirtualMachine_socketDevices(void *machine);
110110
void VZVirtioSocketDevice_setSocketListenerForPort(void *socketDevice, void *vmQueue, void *listener, uint32_t port);
111111
void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *vmQueue, uint32_t port);
112112
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, uintptr_t cgoHandle);
113+
void *VZVirtualMachine_memoryBalloonDevices(void *machine);
113114

114115
/* VirtualMachine */
115116
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, uintptr_t statusUpdateCgoHandle, uintptr_t disconnectedCgoHandle);
@@ -132,3 +133,7 @@ typedef struct VZVirtioSocketConnectionFlat {
132133
} VZVirtioSocketConnectionFlat;
133134

134135
VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection);
136+
137+
/* VZVirtioTraditionalMemoryBalloonDevice */
138+
void VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(void *balloonDevice, void *queue, unsigned long long targetMemorySize);
139+
unsigned long long VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(void *balloonDevice, void *queue);

virtualization_11.m

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,3 +1046,56 @@ bool vmCanRequestStop(void *machine, void *queue)
10461046
}
10471047

10481048
// --- TODO end
1049+
1050+
/*!
1051+
@abstract Return the list of memory balloon devices configured on this virtual machine.
1052+
@discussion Returns an empty array if no memory balloon device is configured.
1053+
@see VZVirtioTraditionalMemoryBalloonDeviceConfiguration
1054+
@see VZVirtualMachineConfiguration
1055+
*/
1056+
void *VZVirtualMachine_memoryBalloonDevices(void *machine)
1057+
{
1058+
if (@available(macOS 11, *)) {
1059+
return [(VZVirtualMachine *)machine memoryBalloonDevices]; // NSArray<VZMemoryBalloonDevice *>
1060+
}
1061+
1062+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
1063+
}
1064+
1065+
/*!
1066+
@abstract Set the target memory size for the virtual machine.
1067+
@discussion Adjusts the memory balloon to make the specified amount of memory available to the guest OS.
1068+
@param memoryBalloonDevice The memory balloon device to set the target memory size for.
1069+
@param vmQueue The dispatch queue on which the virtual machine operates.
1070+
@param targetMemorySize The target memory size in bytes to set for the virtual machine.
1071+
*/
1072+
void VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(void *memoryBalloonDevice, void *vmQueue, unsigned long long targetMemorySize)
1073+
{
1074+
if (@available(macOS 11, *)) {
1075+
dispatch_sync((dispatch_queue_t)vmQueue, ^{
1076+
[(VZVirtioTraditionalMemoryBalloonDevice *)memoryBalloonDevice setTargetVirtualMachineMemorySize:targetMemorySize];
1077+
});
1078+
return;
1079+
}
1080+
1081+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
1082+
}
1083+
1084+
/*!
1085+
@abstract Get the current target memory size for the virtual machine.
1086+
@param memoryBalloonDevice The memory balloon device to get the target memory size from.
1087+
@param vmQueue The dispatch queue on which the virtual machine operates.
1088+
@return The current target memory size in bytes for the virtual machine.
1089+
*/
1090+
unsigned long long VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(void *memoryBalloonDevice, void *vmQueue)
1091+
{
1092+
if (@available(macOS 11, *)) {
1093+
__block unsigned long long ret;
1094+
dispatch_sync((dispatch_queue_t)vmQueue, ^{
1095+
ret = [(VZVirtioTraditionalMemoryBalloonDevice *)memoryBalloonDevice targetVirtualMachineMemorySize];
1096+
});
1097+
return ret;
1098+
}
1099+
1100+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
1101+
}

0 commit comments

Comments
 (0)