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
96 changes: 96 additions & 0 deletions memory_balloon.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package vz
*/
import "C"
import (
"unsafe"

"github.com/Code-Hex/vz/v3/internal/objc"
)

Expand Down Expand Up @@ -51,3 +53,97 @@ func NewVirtioTraditionalMemoryBalloonDeviceConfiguration() (*VirtioTraditionalM
})
return config, nil
}

// MemoryBalloonDevice is the base interface for memory balloon devices.
//
// This represents MemoryBalloonDevice in the Virtualization framework.
// It is an abstract class that should not be directly used.
//
// see: https://developer.apple.com/documentation/virtualization/vzmemoryballoondevice?language=objc
type MemoryBalloonDevice interface {
objc.NSObject

memoryBalloonDevice()
}

type baseMemoryBalloonDevice struct{}

func (*baseMemoryBalloonDevice) memoryBalloonDevice() {}

// MemoryBalloonDevices returns the list of memory balloon devices configured on this virtual machine.
//
// Returns an empty array if no memory balloon device is configured.
//
// This is only supported on macOS 11 and newer.
func (v *VirtualMachine) MemoryBalloonDevices() []MemoryBalloonDevice {
nsArray := objc.NewNSArray(
C.VZVirtualMachine_memoryBalloonDevices(objc.Ptr(v)),
)
ptrs := nsArray.ToPointerSlice()
devices := make([]MemoryBalloonDevice, len(ptrs))
for i, ptr := range ptrs {
// TODO: When Apple adds more memory balloon device types in future macOS versions,
// implement type checking here to create the appropriate device wrapper.
// Currently, VirtioTraditionalMemoryBalloonDevice is the only type supported.
devices[i] = newVirtioTraditionalMemoryBalloonDevice(ptr, v)
}
return devices
}

// VirtioTraditionalMemoryBalloonDevice represents a Virtio traditional memory balloon device.
//
// The balloon device allows for dynamic memory management by inflating or deflating
// the balloon to control memory available to the guest OS.
//
// see: https://developer.apple.com/documentation/virtualization/vzvirtiotraditionalmemoryballoondevice?language=objc
type VirtioTraditionalMemoryBalloonDevice struct {
*pointer
vm *VirtualMachine

*baseMemoryBalloonDevice
}

var _ MemoryBalloonDevice = (*VirtioTraditionalMemoryBalloonDevice)(nil)

// AsVirtioTraditionalMemoryBalloonDevice attempts to convert a MemoryBalloonDevice to a VirtioTraditionalMemoryBalloonDevice.
//
// Returns the VirtioTraditionalMemoryBalloonDevice if the device is of that type, or nil otherwise.
func AsVirtioTraditionalMemoryBalloonDevice(device MemoryBalloonDevice) *VirtioTraditionalMemoryBalloonDevice {
if traditionalDevice, ok := device.(*VirtioTraditionalMemoryBalloonDevice); ok {
return traditionalDevice
}
return nil
}

func newVirtioTraditionalMemoryBalloonDevice(pointer unsafe.Pointer, vm *VirtualMachine) *VirtioTraditionalMemoryBalloonDevice {
device := &VirtioTraditionalMemoryBalloonDevice{
pointer: objc.NewPointer(pointer),
vm: vm,
}
objc.SetFinalizer(device, func(self *VirtioTraditionalMemoryBalloonDevice) {
objc.Release(self)
})
return device
}

// SetTargetVirtualMachineMemorySize sets the target memory size in bytes for the virtual machine.
//
// This method inflates or deflates the memory balloon to adjust the amount of memory
// available to the guest OS. The target memory size must be less than the total memory
// configured for the virtual machine.
//
// This is only supported on macOS 11 and newer.
func (v *VirtioTraditionalMemoryBalloonDevice) SetTargetVirtualMachineMemorySize(targetMemorySize uint64) {
C.VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(
objc.Ptr(v),
v.vm.dispatchQueue,
C.ulonglong(targetMemorySize),
)
}

// GetTargetVirtualMachineMemorySize returns the current target memory size in bytes for the virtual machine.
//
// This is only supported on macOS 11 and newer.
func (v *VirtioTraditionalMemoryBalloonDevice) GetTargetVirtualMachineMemorySize() uint64 {
return uint64(C.VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(objc.Ptr(v), v.vm.dispatchQueue))
}
174 changes: 174 additions & 0 deletions memory_balloon_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package vz_test

import (
"testing"
"time"

"github.com/Code-Hex/vz/v3"
)

func TestVirtioTraditionalMemoryBalloonDeviceConfiguration(t *testing.T) {
// Create memory balloon device configuration
config, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
if err != nil {
t.Fatalf("failed to create memory balloon device configuration: %v", err)
}
if config == nil {
t.Fatal("memory balloon configuration should not be nil")
}
}

func TestMemoryBalloonDevices(t *testing.T) {
// Create a simple VM configuration
bootLoader, err := vz.NewLinuxBootLoader(
"./testdata/Image",
vz.WithCommandLine("console=hvc0"),
)
if err != nil {
t.Fatalf("failed to create boot loader: %v", err)
}

config, err := vz.NewVirtualMachineConfiguration(
bootLoader,
1,
256*1024*1024,
)
if err != nil {
t.Fatalf("failed to create virtual machine configuration: %v", err)
}

// Create and add a memory balloon device
memoryBalloonConfig, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
if err != nil {
t.Fatalf("failed to create memory balloon device configuration: %v", err)
}

config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
memoryBalloonConfig,
})

// Create the VM
vm, err := vz.NewVirtualMachine(config)
if err != nil {
t.Fatalf("failed to create virtual machine: %v", err)
}

// Get memory balloon devices
balloonDevices := vm.MemoryBalloonDevices()
if len(balloonDevices) != 1 {
t.Fatalf("expected 1 memory balloon device, got %d", len(balloonDevices))
}

// Verify we can access the balloon device
balloonDevice := balloonDevices[0]
if balloonDevice == nil {
t.Fatal("memory balloon device should not be nil")
}

// Verify we can cast to VirtioTraditionalMemoryBalloonDevice
traditionalDevice := vz.AsVirtioTraditionalMemoryBalloonDevice(balloonDevice)
if traditionalDevice == nil {
t.Fatal("failed to cast to VirtioTraditionalMemoryBalloonDevice")
}
}

func TestMemoryBalloonTargetSizeAdjustment(t *testing.T) {
// Create a VM with a memory balloon device
bootLoader, err := vz.NewLinuxBootLoader(
"./testdata/Image",
vz.WithCommandLine("console=hvc0"),
vz.WithInitrd("./testdata/initramfs.cpio.gz"),
)
if err != nil {
t.Fatalf("failed to create boot loader: %v", err)
}

startingMemory := uint64(512 * 1024 * 1024)
targetMemory := uint64(300 * 1024 * 1024)

t.Logf("Starting memory: %d bytes", startingMemory)
t.Logf("Target memory: %d bytes", targetMemory)

config, err := vz.NewVirtualMachineConfiguration(
bootLoader,
1,
startingMemory,
)
if err != nil {
t.Fatalf("failed to create virtual machine configuration: %v", err)
}

// Create memory balloon device
memoryBalloonConfig, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
if err != nil {
t.Fatalf("failed to create memory balloon device configuration: %v", err)
}

// Add memory balloon device to VM configuration
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{
memoryBalloonConfig,
})

// Validate the configuration
valid, err := config.Validate()
if err != nil {
t.Fatalf("configuration validation failed: %v", err)
}
if !valid {
t.Fatal("configuration is not valid")
}

// Create the VM
vm, err := vz.NewVirtualMachine(config)
if err != nil {
t.Fatalf("failed to create virtual machine: %v", err)
}

// Check memory balloon devices
balloonDevices := vm.MemoryBalloonDevices()
if len(balloonDevices) != 1 {
t.Fatalf("expected 1 memory balloon device, got %d", len(balloonDevices))
}

// Cast to VirtioTraditionalMemoryBalloonDevice
balloonDevice := vz.AsVirtioTraditionalMemoryBalloonDevice(balloonDevices[0])
if balloonDevice == nil {
t.Fatal("failed to cast to VirtioTraditionalMemoryBalloonDevice")
}

// Start the VM
t.Log("Starting virtual machine...")
err = vm.Start()
if err != nil {
t.Fatalf("failed to start virtual machine: %v", err)
}

defer func() {
if vm.CanStop() {
_ = vm.Stop() // Cleanup VM
}
}()

// Wait until the VM is running
err = waitUntilState(10*time.Second, vm, vz.VirtualMachineStateRunning)
if err != nil {
t.Fatalf("failed to wait for VM to start: %v", err)
}

// Get the current target memory size
currentMemoryBefore := balloonDevice.GetTargetVirtualMachineMemorySize()

if currentMemoryBefore != startingMemory {
t.Fatalf("expected starting memory size to be %d, got %d", startingMemory, currentMemoryBefore)
}

// Set a new target memory size
balloonDevice.SetTargetVirtualMachineMemorySize(targetMemory)

// Verify the new memory size was set
currentMemoryAfter := balloonDevice.GetTargetVirtualMachineMemorySize()

if currentMemoryAfter != targetMemory {
t.Fatalf("expected memory size after adjustment to be %d, got %d", targetMemory, currentMemoryAfter)
}
}
5 changes: 5 additions & 0 deletions virtualization_11.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ void *VZVirtualMachine_socketDevices(void *machine);
void VZVirtioSocketDevice_setSocketListenerForPort(void *socketDevice, void *vmQueue, void *listener, uint32_t port);
void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *vmQueue, uint32_t port);
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, uintptr_t cgoHandle);
void *VZVirtualMachine_memoryBalloonDevices(void *machine);

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

VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection);

/* VZVirtioTraditionalMemoryBalloonDevice */
void VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(void *balloonDevice, void *queue, unsigned long long targetMemorySize);
unsigned long long VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(void *balloonDevice, void *queue);
53 changes: 53 additions & 0 deletions virtualization_11.m
Original file line number Diff line number Diff line change
Expand Up @@ -1046,3 +1046,56 @@ bool vmCanRequestStop(void *machine, void *queue)
}

// --- TODO end

/*!
@abstract Return the list of memory balloon devices configured on this virtual machine.
@discussion Returns an empty array if no memory balloon device is configured.
@see VZVirtioTraditionalMemoryBalloonDeviceConfiguration
@see VZVirtualMachineConfiguration
*/
void *VZVirtualMachine_memoryBalloonDevices(void *machine)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachine *)machine memoryBalloonDevices]; // NSArray<VZMemoryBalloonDevice *>
}

RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Set the target memory size for the virtual machine.
@discussion Adjusts the memory balloon to make the specified amount of memory available to the guest OS.
@param memoryBalloonDevice The memory balloon device to set the target memory size for.
@param vmQueue The dispatch queue on which the virtual machine operates.
@param targetMemorySize The target memory size in bytes to set for the virtual machine.
*/
void VZVirtioTraditionalMemoryBalloonDevice_setTargetVirtualMachineMemorySize(void *memoryBalloonDevice, void *vmQueue, unsigned long long targetMemorySize)
{
if (@available(macOS 11, *)) {
dispatch_sync((dispatch_queue_t)vmQueue, ^{
[(VZVirtioTraditionalMemoryBalloonDevice *)memoryBalloonDevice setTargetVirtualMachineMemorySize:targetMemorySize];
});
return;
}

RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Get the current target memory size for the virtual machine.
@param memoryBalloonDevice The memory balloon device to get the target memory size from.
@param vmQueue The dispatch queue on which the virtual machine operates.
@return The current target memory size in bytes for the virtual machine.
*/
unsigned long long VZVirtioTraditionalMemoryBalloonDevice_getTargetVirtualMachineMemorySize(void *memoryBalloonDevice, void *vmQueue)
{
if (@available(macOS 11, *)) {
__block unsigned long long ret;
dispatch_sync((dispatch_queue_t)vmQueue, ^{
ret = [(VZVirtioTraditionalMemoryBalloonDevice *)memoryBalloonDevice targetVirtualMachineMemorySize];
});
return ret;
}

RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}