Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

suggest for #167 #168

Merged
merged 3 commits into from
Nov 2, 2024
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
9 changes: 9 additions & 0 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type VirtualMachineConfiguration struct {
cpuCount uint
memorySize uint64
*pointer

storageDeviceConfiguration []StorageDeviceConfiguration
}

// NewVirtualMachineConfiguration creates a new configuration.
Expand Down Expand Up @@ -172,6 +174,13 @@ func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfigurati
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setStorageDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
v.storageDeviceConfiguration = cs
}

// StorageDevices return the list of storage device configuration configured in this virtual machine configuration.
// Return an empty array if no storage device configuration is set.
func (v *VirtualMachineConfiguration) StorageDevices() []StorageDeviceConfiguration {
return v.storageDeviceConfiguration
}
Comment on lines +180 to 184
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took this approach because converting from raw pointers to a slice of interfaces and then to each concrete type is challenging. I plan to apply similar fixes to other similar methods in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this was definitely the part I was missing as I didn't change it during my tests, thanks for the fix 🙏


// SetDirectorySharingDevicesVirtualMachineConfiguration sets list of directory sharing devices. Empty by default.
Expand Down
78 changes: 55 additions & 23 deletions example/macOS/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ func runVM(ctx context.Context) error {
}
}()

// it start listening to the NBD server, if any
nbdAttachment := retrieveNetworkBlockDeviceStorageDeviceAttachment(config.StorageDevices())
if nbdAttachment != nil {
go func() {
for {
select {
case err := <-nbdAttachment.DidEncounterError():
log.Printf("NBD client has been encountered error: %v\n", err)
case <-nbdAttachment.Connected():
log.Println("NBD client connected with the server")
}
}
}()
}

// cleanup is this function is useful when finished graphic application.
cleanup := func() {
for i := 1; vm.CanRequestStop(); i++ {
Expand All @@ -102,9 +117,7 @@ func runVM(ctx context.Context) error {
log.Println("finished cleanup")
}

runtime.LockOSThread()
vm.StartGraphicApplication(960, 600)
runtime.UnlockOSThread()

cleanup()

Expand Down Expand Up @@ -144,29 +157,30 @@ func computeMemorySize() uint64 {
}

func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
var attachment vz.StorageDeviceAttachment
var err error

if nbdURL == "" {
// create disk image with 64 GiB
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
if !os.IsExist(err) {
return nil, fmt.Errorf("failed to create disk image: %w", err)
}
// create disk image with 64 GiB
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
if !os.IsExist(err) {
return nil, fmt.Errorf("failed to create disk image: %w", err)
}
}

attachment, err = vz.NewDiskImageStorageDeviceAttachment(
diskPath,
false,
)
} else {
attachment, err = vz.NewNetworkBlockDeviceStorageDeviceAttachment(
nbdURL,
10*time.Second,
false,
vz.DiskSynchronizationModeFull,
)
attachment, err := vz.NewDiskImageStorageDeviceAttachment(
diskPath,
false,
)
if err != nil {
return nil, err
}
return vz.NewVirtioBlockDeviceConfiguration(attachment)
}

func createNetworkBlockDeviceConfiguration(nbdURL string) (*vz.VirtioBlockDeviceConfiguration, error) {
attachment, err := vz.NewNetworkBlockDeviceStorageDeviceAttachment(
nbdURL,
10*time.Second,
false,
vz.DiskSynchronizationModeFull,
)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -277,7 +291,15 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM
if err != nil {
return nil, fmt.Errorf("failed to create block device configuration: %w", err)
}
config.SetStorageDevicesVirtualMachineConfiguration([]vz.StorageDeviceConfiguration{blockDeviceConfig})
sdconfigs := []vz.StorageDeviceConfiguration{blockDeviceConfig}
if nbdURL != "" {
ndbConfig, err := createNetworkBlockDeviceConfiguration(nbdURL)
if err != nil {
return nil, fmt.Errorf("failed to create network block device configuration: %w", err)
}
sdconfigs = append(sdconfigs, ndbConfig)
}
config.SetStorageDevicesVirtualMachineConfiguration(sdconfigs)

networkDeviceConfig, err := createNetworkDeviceConfiguration()
if err != nil {
Expand Down Expand Up @@ -331,3 +353,13 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM

return config, nil
}

func retrieveNetworkBlockDeviceStorageDeviceAttachment(storages []vz.StorageDeviceConfiguration) *vz.NetworkBlockDeviceStorageDeviceAttachment {
for _, storage := range storages {
attachment := storage.Attachment()
if nbdAttachment, ok := attachment.(*vz.NetworkBlockDeviceStorageDeviceAttachment); ok {
return nbdAttachment
}
}
return nil
}
96 changes: 85 additions & 11 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ package vz
import "C"
import (
"os"
"runtime/cgo"
"time"
"unsafe"

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

Expand Down Expand Up @@ -151,11 +154,17 @@ type StorageDeviceConfiguration interface {
objc.NSObject

storageDeviceConfiguration()
Attachment() StorageDeviceAttachment
}

type baseStorageDeviceConfiguration struct{}
type baseStorageDeviceConfiguration struct {
attachment StorageDeviceAttachment
}

func (*baseStorageDeviceConfiguration) storageDeviceConfiguration() {}
func (b *baseStorageDeviceConfiguration) Attachment() StorageDeviceAttachment {
return b.attachment
}

var _ StorageDeviceConfiguration = (*VirtioBlockDeviceConfiguration)(nil)

Expand Down Expand Up @@ -192,6 +201,9 @@ func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) (*Vir
objc.Ptr(attachment),
),
),
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
objc.Release(self)
Expand Down Expand Up @@ -250,10 +262,6 @@ type USBMassStorageDeviceConfiguration struct {
*pointer

*baseStorageDeviceConfiguration

// marking as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
attachment StorageDeviceAttachment
}

// NewUSBMassStorageDeviceConfiguration initialize a USBMassStorageDeviceConfiguration
Expand All @@ -269,7 +277,9 @@ func NewUSBMassStorageDeviceConfiguration(attachment StorageDeviceAttachment) (*
pointer: objc.NewPointer(
C.newVZUSBMassStorageDeviceConfiguration(objc.Ptr(attachment)),
),
attachment: attachment,
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(usbMass, func(self *USBMassStorageDeviceConfiguration) {
objc.Release(self)
Expand All @@ -284,10 +294,6 @@ type NVMExpressControllerDeviceConfiguration struct {
*pointer

*baseStorageDeviceConfiguration

// marking as currently reachable.
// This ensures that the object is not freed, and its finalizer is not run
attachment StorageDeviceAttachment
}

// NewNVMExpressControllerDeviceConfiguration creates a new NVMExpressControllerDeviceConfiguration with
Expand All @@ -306,7 +312,9 @@ func NewNVMExpressControllerDeviceConfiguration(attachment StorageDeviceAttachme
pointer: objc.NewPointer(
C.newVZNVMExpressControllerDeviceConfiguration(objc.Ptr(attachment)),
),
attachment: attachment,
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
attachment: attachment,
},
}
objc.SetFinalizer(nvmExpress, func(self *NVMExpressControllerDeviceConfiguration) {
objc.Release(self)
Expand Down Expand Up @@ -411,6 +419,9 @@ type NetworkBlockDeviceStorageDeviceAttachment struct {
*pointer

*baseStorageDeviceAttachment

didEncounterError *infinity.Channel[error]
connected *infinity.Channel[struct{}]
}

var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil)
Expand All @@ -419,6 +430,9 @@ var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil
// Uniform Resource Indicator (URI) represented as a URL, timeout value, and read-only and synchronization modes
// that you provide.
//
// It also set up a channel that will be used by the VZNetworkBlockDeviceStorageDeviceAttachmentDelegate to
// return changes to the NetworkBlockDeviceAttachment
//
// - url is the NBD server URI. The format specified by https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
// - timeout is the duration for the connection between the client and server. When the timeout expires, an attempt to reconnect with the server takes place.
// - forcedReadOnly if true forces the disk attachment to be read-only, regardless of whether or not the NBD server supports write requests.
Expand All @@ -431,6 +445,17 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
return nil, err
}

didEncounterError := infinity.NewChannel[error]()
connected := infinity.NewChannel[struct{}]()

handle := cgo.NewHandle(func(err error) {
if err != nil {
didEncounterError.In() <- err
return
}
connected.In() <- struct{}{}
})

nserrPtr := newNSErrorAsNil()

urlChar := charWithGoString(url)
Expand All @@ -443,8 +468,11 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
C.bool(forcedReadOnly),
C.int(syncMode),
&nserrPtr,
C.uintptr_t(handle),
),
),
didEncounterError: didEncounterError,
connected: connected,
}
if err := newNSError(nserrPtr); err != nil {
return nil, err
Expand All @@ -454,3 +482,49 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
})
return attachment, nil
}

// Connected receive the signal via channel when the NBD client successfully connects or reconnects with the server.
//
// The NBD connection with the server takes place when the VM is first started, and reconnection attempts take place when the connection
// times out or when the NBD client has encountered a recoverable error, such as an I/O error from the server.
//
// Note that the Virtualization framework may call this method multiple times during a VM’s life cycle. Reconnections are transparent to the guest.
func (n *NetworkBlockDeviceStorageDeviceAttachment) Connected() <-chan struct{} {
return n.connected.Out()
}

// The DidEncounterError is triggered via the channel when the NBD client encounters an error that cannot be resolved on the client side.
// In this state, the client will continue attempting to reconnect, but recovery depends entirely on the server's availability.
// If the server resumes operation, the connection will recover automatically; however, until the server is restored, the client will continue to experience errors.
func (n *NetworkBlockDeviceStorageDeviceAttachment) DidEncounterError() <-chan error {
return n.didEncounterError.Out()
}

// attachmentDidEncounterErrorHandler function is called when the NBD client encounters a nonrecoverable error.
// After the attachment object calls this method, the NBD client is in a nonfunctional state.
//
//export attachmentDidEncounterErrorHandler
func attachmentDidEncounterErrorHandler(cgoHandleUintptr C.uintptr_t, errorPtr unsafe.Pointer) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

err := newNSError(errorPtr)

handler(err)
}

// attachmentWasConnectedHandler function is called when a connection to the server is first established as the VM starts,
// and during any reconnection attempts triggered by connection timeouts or recoverable errors encountered by the NBD client,
// such as server-side I/O errors.
//
// Note that the Virtualization framework may invoke this method multiple times throughout the VM’s lifecycle,
// ensuring reconnection processes remain seamless and transparent to the guest.
// For more details, see: https://developer.apple.com/documentation/virtualization/vznetworkblockdevicestoragedeviceattachmentdelegate/4168511-attachmentwasconnected?language=objc
//
//export attachmentWasConnectedHandler
func attachmentWasConnectedHandler(cgoHandleUintptr C.uintptr_t) {
cgoHandle := cgo.Handle(cgoHandleUintptr)
handler := cgoHandle.Value().(func(error))

handler(nil)
}
1 change: 1 addition & 0 deletions virtualization_11.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ void setSocketDevicesVZVirtualMachineConfiguration(void *config,
void *socketDevicesVZVirtualMachineConfiguration(void *config);
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
void *storageDevices);
void *storageDevicesVZVirtualMachineConfiguration(void *config);

/* Configurations */
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor);
Expand Down
12 changes: 12 additions & 0 deletions virtualization_11.m
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,18 @@ void setStorageDevicesVZVirtualMachineConfiguration(void *config,
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Return the list of storage devices configurations for this VZVirtualMachineConfiguration. Return an empty array if no storage device configuration is set.
*/
void *storageDevicesVZVirtualMachineConfiguration(void *config)
{
if (@available(macOS 11, *)) {
return [(VZVirtualMachineConfiguration *)config storageDevices]; // NSArray<VZStorageDeviceConfiguration *>
}

RAISE_UNSUPPORTED_MACOS_EXCEPTION();
}

/*!
@abstract Intialize the VZFileHandleSerialPortAttachment from file descriptors.
@param readFileDescriptor File descriptor for reading from the file.
Expand Down
12 changes: 11 additions & 1 deletion virtualization_14.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@
#import "virtualization_helper.h"
#import <Virtualization/Virtualization.h>

/* exported from cgo */
void attachmentDidEncounterErrorHandler(uintptr_t cgoHandle, void *err);
void attachmentWasConnectedHandler(uintptr_t cgoHandle);

/* macOS 14 API */
void *newVZNVMExpressControllerDeviceConfiguration(void *attachment);
void *newVZDiskBlockDeviceStorageDeviceAttachment(int fileDescriptor, bool readOnly, int syncMode, void **error);
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error);
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error, uintptr_t cgoHandle);

@interface VZNetworkBlockDeviceStorageDeviceAttachmentDelegateImpl : NSObject <VZNetworkBlockDeviceStorageDeviceAttachmentDelegate>
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
- (void)attachment:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment didEncounterError:(NSError *)error;
- (void)attachmentWasConnected:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment;
@end
Loading