diff --git a/cmd/vfkit/main.go b/cmd/vfkit/main.go index ca526da4..9e7b515c 100644 --- a/cmd/vfkit/main.go +++ b/cmd/vfkit/main.go @@ -33,7 +33,6 @@ import ( "github.com/crc-org/vfkit/pkg/rest" restvf "github.com/crc-org/vfkit/pkg/rest/vf" "github.com/crc-org/vfkit/pkg/vf" - "github.com/docker/go-units" log "github.com/sirupsen/logrus" ) @@ -71,7 +70,7 @@ func newVMConfiguration(opts *cmdline.Options) (*config.VirtualMachine, error) { vmConfig := config.NewVirtualMachine( opts.Vcpus, - uint64(opts.MemoryMiB*units.MiB), + uint64(opts.MemoryMiB), bootloader, ) log.Info("virtual machine parameters:") @@ -131,7 +130,7 @@ func runVFKit(vmConfig *config.VirtualMachine, opts *cmdline.Options) error { // Do not enable the rests server if user sets scheme to None if opts.RestfulURI != cmdline.DefaultRestfulURI { - restVM := restvf.NewVzVirtualMachine(vm, vzVMConfig) + restVM := restvf.NewVzVirtualMachine(vm, vzVMConfig, vmConfig) srv, err := rest.NewServer(restVM, restVM, opts.RestfulURI) if err != nil { return err diff --git a/go.mod b/go.mod index af71816b..f99d92f5 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.18 require ( github.com/Code-Hex/vz/v3 v3.1.0 github.com/cavaliergopher/grab/v3 v3.0.1 + github.com/containers/common v0.57.1-0.20240210120841-91e0fac33e22 github.com/crc-org/crc/v2 v2.32.0 - github.com/docker/go-units v0.5.0 github.com/gin-gonic/gin v1.9.1 github.com/prashantgupta24/mac-sleep-notifier v1.0.1 github.com/sirupsen/logrus v1.9.3 @@ -40,7 +40,7 @@ require ( github.com/h2non/filetype v1.1.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect diff --git a/go.sum b/go.sum index 578b36b9..6b7bb48e 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/containers/common v0.57.1-0.20240210120841-91e0fac33e22 h1:HH3JAenyz6ysup3ADvVPuX0TqLcg6IleuoMi15B5MXM= +github.com/containers/common v0.57.1-0.20240210120841-91e0fac33e22/go.mod h1:Al9edwL72aYyVOfIE+DB635ltve3S98TrQgg/Tv5PLE= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crc-org/crc/v2 v2.32.0 h1:I/62j5KrID8ua1vgAUPOVTtzhcsCsHWdqqiIRHySLfQ= github.com/crc-org/crc/v2 v2.32.0/go.mod h1:Q2XJM3KkR/Gu+tBjeN77pk5P8DWYKdbxCSf+9l9MYcs= @@ -31,8 +33,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -63,8 +63,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= diff --git a/pkg/config/bootloader.go b/pkg/config/bootloader.go index 52cbeb5e..67510881 100644 --- a/pkg/config/bootloader.go +++ b/pkg/config/bootloader.go @@ -18,16 +18,16 @@ type Bootloader interface { // LinuxBootloader determines which kernel/initrd/kernel args to use when starting // the virtual machine. type LinuxBootloader struct { - VmlinuzPath string - KernelCmdLine string - InitrdPath string + VmlinuzPath string `json:"vmlinuzPath"` + KernelCmdLine string `json:"kernelCmdLine"` + InitrdPath string `json:"initrdPath"` } // EFIBootloader allows to set a few options related to EFI variable storage type EFIBootloader struct { - EFIVariableStorePath string + EFIVariableStorePath string `json:"efiVariableStorePath"` // TODO: virtualization framework allow both create and overwrite - CreateVariableStore bool + CreateVariableStore bool `json:"createVariableStore"` } // NewLinuxBootloader creates a new bootloader to start a VM with the file at diff --git a/pkg/config/config.go b/pkg/config/config.go index 2f84efbe..d836d342 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,22 +6,24 @@ import ( "os/exec" "strconv" "strings" + + "github.com/containers/common/pkg/strongunits" ) // VirtualMachine is the top-level type. It describes the virtual machine // configuration (bootloader, devices, ...). type VirtualMachine struct { - Vcpus uint `json:"vcpus"` - MemoryBytes uint64 `json:"memoryBytes"` - Bootloader Bootloader `json:"bootloader"` - Devices []VirtioDevice `json:"devices,omitempty"` - Timesync *TimeSync `json:"timesync,omitempty"` + Vcpus uint `json:"vcpus"` + Memory strongunits.B `json:"memoryBytes"` + Bootloader Bootloader `json:"bootloader"` + Devices []VirtioDevice `json:"devices,omitempty"` + Timesync *TimeSync `json:"timesync,omitempty"` } // TimeSync enables synchronization of the host time to the linux guest after the host was suspended. // This requires qemu-guest-agent to be running in the guest, and to be listening on a vsock socket type TimeSync struct { - VsockPort uint + VsockPort uint `json:"vsockPort"` } // The VMComponent interface represents a VM element (device, bootloader, ...) @@ -32,16 +34,24 @@ type VMComponent interface { } // NewVirtualMachine creates a new VirtualMachine instance. The virtual machine -// will use vcpus virtual CPUs and it will be allocated memoryBytes bytes of -// RAM. bootloader specifies which kernel/initrd/kernel args it will be using. -func NewVirtualMachine(vcpus uint, memoryBytes uint64, bootloader Bootloader) *VirtualMachine { +// will use vcpus virtual CPUs and it will be allocated memoryMiB mibibytes +// (1024*1024 bytes) of RAM. bootloader specifies how the virtual machine will +// be booted (UEFI or with the specified kernel/initrd/commandline) +func NewVirtualMachine(vcpus uint, memoryMiB uint64, bootloader Bootloader) *VirtualMachine { return &VirtualMachine{ - Vcpus: vcpus, - MemoryBytes: memoryBytes, - Bootloader: bootloader, + Vcpus: vcpus, + Memory: strongunits.MiB(memoryMiB).ToBytes(), + Bootloader: bootloader, } } +// round value up to the nearest mibibyte multiple +func roundToMiB(value strongunits.StorageUnits) strongunits.MiB { + mib := uint64(strongunits.MiB(1).ToBytes()) + valueB := strongunits.B(uint64(value.ToBytes()) + mib - 1) + return strongunits.ToMib(valueB) +} + // ToCmdLine generates a list of arguments for use with the [os/exec] package. // These arguments will start a virtual machine with the devices/bootloader/... // described by vm If the virtual machine configuration described by vm is @@ -53,8 +63,8 @@ func (vm *VirtualMachine) ToCmdLine() ([]string, error) { if vm.Vcpus != 0 { args = append(args, "--cpus", strconv.FormatUint(uint64(vm.Vcpus), 10)) } - if vm.MemoryBytes != 0 { - args = append(args, "--memory", strconv.FormatUint(vm.MemoryBytes, 10)) + if uint64(vm.Memory.ToBytes()) != 0 { + args = append(args, "--memory", strconv.FormatUint(uint64(roundToMiB(vm.Memory)), 10)) } if vm.Bootloader == nil { diff --git a/pkg/config/json.go b/pkg/config/json.go index 312c5f3f..ba011cff 100644 --- a/pkg/config/json.go +++ b/pkg/config/json.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "fmt" + "net" ) // The technique for json (de)serialization was explained here: @@ -88,6 +89,27 @@ func unmarshalDevices(rawMsg json.RawMessage) ([]VirtioDevice, error) { return devices, nil } +// VirtioNet needs a custom unmarshaller as net.HardwareAddress is not +// serialized/unserialized in its expected format, instead of +// '00:11:22:33:44:55', it's serialized as base64-encoded raw bytes such as +// 'ABEiM0RV'. This custom (un)marshalling code will use the desired format. +func unmarshalVirtioNet(rawMsg json.RawMessage) (*VirtioNet, error) { + var dev virtioNetForMarshalling + + err := json.Unmarshal(rawMsg, &dev) + if err != nil { + return nil, err + } + if dev.MacAddress != "" { + macAddr, err := net.ParseMAC(dev.MacAddress) + if err != nil { + return nil, err + } + dev.VirtioNet.MacAddress = macAddr + } + return &dev.VirtioNet, nil +} + func unmarshalDevice(rawMsg json.RawMessage) (VirtioDevice, error) { var ( kind jsonKind @@ -99,9 +121,7 @@ func unmarshalDevice(rawMsg json.RawMessage) (VirtioDevice, error) { } switch kind.Kind { case vfNet: - var newDevice VirtioNet - err = json.Unmarshal(rawMsg, &newDevice) - dev = &newDevice + dev, err = unmarshalVirtioNet(rawMsg) case vfVsock: var newDevice VirtioVsock err = json.Unmarshal(rawMsg, &newDevice) @@ -173,7 +193,7 @@ func (vm *VirtualMachine) UnmarshalJSON(b []byte) error { case "vcpus": err = json.Unmarshal(*rawMsg, &vm.Vcpus) case "memoryBytes": - err = json.Unmarshal(*rawMsg, &vm.MemoryBytes) + err = json.Unmarshal(*rawMsg, &vm.Memory) case "bootloader": var bootloader Bootloader bootloader, err = unmarshalBootloader(*rawMsg) @@ -219,14 +239,22 @@ func (bootloader *LinuxBootloader) MarshalJSON() ([]byte, error) { }) } +type virtioNetForMarshalling struct { + VirtioNet + MacAddress string `json:"macAddress,omitempty"` +} + func (dev *VirtioNet) MarshalJSON() ([]byte, error) { type devWithKind struct { jsonKind - VirtioNet + virtioNetForMarshalling } return json.Marshal(devWithKind{ - jsonKind: kind(vfNet), - VirtioNet: *dev, + jsonKind: kind(vfNet), + virtioNetForMarshalling: virtioNetForMarshalling{ + VirtioNet: *dev, + MacAddress: dev.MacAddress.String(), + }, }) } diff --git a/pkg/config/json_test.go b/pkg/config/json_test.go index 9345ebb1..afdefeab 100644 --- a/pkg/config/json_test.go +++ b/pkg/config/json_test.go @@ -15,11 +15,11 @@ type jsonTest struct { var jsonTests = map[string]jsonTest{ "TestLinuxVM": { newVM: newLinuxVM, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"}}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}}`, }, "TestUEFIVM": { newVM: newUEFIVM, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"efiBootloader","EFIVariableStorePath":"/variable-store","CreateVariableStore":false}}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"efiBootloader","efiVariableStorePath":"/variable-store","createVariableStore":false}}`, }, "TestTimeSync": { newVM: func(t *testing.T) *VirtualMachine { @@ -29,7 +29,7 @@ var jsonTests = map[string]jsonTest{ vm.Timesync = timesync.(*TimeSync) return vm }, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"timesync":{"VsockPort":1234}}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"timesync":{"vsockPort":1234}}`, }, "TestVirtioRNG": { newVM: func(t *testing.T) *VirtualMachine { @@ -40,7 +40,7 @@ var jsonTests = map[string]jsonTest{ require.NoError(t, err) return vm }, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"kind":"virtiorng"}]}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"kind":"virtiorng"}]}`, }, "TestMultipleVirtioBlk": { newVM: func(t *testing.T) *VirtualMachine { @@ -56,7 +56,7 @@ var jsonTests = map[string]jsonTest{ require.NoError(t, err) return vm }, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"kind":"virtioblk","DevName":"virtio-blk","ImagePath":"/virtioblk1","ReadOnly":false,"DeviceIdentifier":""},{"kind":"virtioblk","DevName":"virtio-blk","ImagePath":"/virtioblk2","ReadOnly":false,"DeviceIdentifier":"virtio-blk2"}]}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"kind":"virtioblk","devName":"virtio-blk","imagePath":"/virtioblk1"},{"kind":"virtioblk","devName":"virtio-blk","imagePath":"/virtioblk2","deviceIdentifier":"virtio-blk2"}]}`, }, "TestAllVirtioDevices": { newVM: func(t *testing.T) *VirtualMachine { @@ -110,7 +110,7 @@ var jsonTests = map[string]jsonTest{ return vm }, - expectedJSON: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"kind":"virtioserial","LogFile":"/virtioserial","UsesStdio":false},{"kind":"virtioinput","inputType":"keyboard"},{"kind":"virtiogpu","usesGUI":false,"width":800,"height":600},{"kind":"virtionet","Nat":true,"MacAddress":"ABEiM0RV","Socket":null,"UnixSocketPath":""},{"kind":"virtiorng"},{"kind":"virtioblk","DevName":"virtio-blk","ImagePath":"/virtioblk","ReadOnly":false,"DeviceIdentifier":""},{"kind":"virtiosock","Port":1234,"SocketURL":"/virtiovsock","Listen":false},{"kind":"virtiofs","MountTag":"tag","SharedDir":"/virtiofs"},{"kind":"usbmassstorage","DevName":"usb-mass-storage","ImagePath":"/usbmassstorage","ReadOnly":false},{"kind":"rosetta","MountTag":"vz-rosetta","InstallRosetta":false}]}`, + expectedJSON: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"kind":"virtioserial","logFile":"/virtioserial"},{"kind":"virtioinput","inputType":"keyboard"},{"kind":"virtiogpu","usesGUI":false,"width":800,"height":600},{"kind":"virtionet","nat":true,"macAddress":"00:11:22:33:44:55"},{"kind":"virtiorng"},{"kind":"virtioblk","devName":"virtio-blk","imagePath":"/virtioblk"},{"kind":"virtiosock","port":1234,"socketURL":"/virtiovsock"},{"kind":"virtiofs","mountTag":"tag","sharedDir":"/virtiofs"},{"kind":"usbmassstorage","devName":"usb-mass-storage","imagePath":"/usbmassstorage"},{"kind":"rosetta","mountTag":"vz-rosetta","installRosetta":false}]}`, }, } @@ -120,22 +120,22 @@ type invalidJSONTest struct { var invalidJSONTests = map[string]invalidJSONTest{ "TestEmptyBootloaderKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"empty",VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"}}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"empty",vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}}`, }, "TestInvalidBootloaderKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"invalid",VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"}}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"invalid",vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}}`, }, "TestMissingBootloaderKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"}}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"}}`, }, "TestEmptyDeviceKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"kind":"","DevName":"virtio-blk","ImagePath":"/virtioblk1","ReadOnly":false,"DeviceIdentifier":""}]}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"kind":"","devName":"virtio-blk","imagePath":"/virtioblk1"}]}`, }, "TestInvalidDeviceKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"kind":"invalid","DevName":"virtio-blk","ImagePath":"/virtioblk1","ReadOnly":false,"DeviceIdentifier":""}]}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"kind":"invalid","devName":"virtio-blk","imagePath":"/virtioblk1"}]}`, }, "TestMissingDeviceKind": { - json: `{"vcpus":3,"memoryBytes":4000000000,"bootloader":{"kind":"linuxBootloader","VmlinuzPath":"/vmlinuz","KernelCmdLine":"/initrd","InitrdPath":"console=hvc0"},"devices":[{"DevName":"virtio-blk","ImagePath":"/virtioblk1","ReadOnly":false,"DeviceIdentifier":""}]}`, + json: `{"vcpus":3,"memoryBytes":4194304000,"bootloader":{"kind":"linuxBootloader","vmlinuzPath":"/vmlinuz","initrdPath":"/initrd","kernelCmdLine":"console=hvc0"},"devices":[{"devName":"virtio-blk","imagePath":"/virtioblk1"}]}`, }, } @@ -153,7 +153,6 @@ func TestJSON(t *testing.T) { testInvalidJSON(t, &test) }) } - }) } @@ -177,15 +176,15 @@ func testInvalidJSON(t *testing.T, test *invalidJSONTest) { } func newLinuxVM(*testing.T) *VirtualMachine { - bootloader := NewLinuxBootloader("/vmlinuz", "/initrd", "console=hvc0") - vm := NewVirtualMachine(3, 4_000_000_000, bootloader) + bootloader := NewLinuxBootloader("/vmlinuz", "console=hvc0", "/initrd") + vm := NewVirtualMachine(3, 4_000, bootloader) return vm } func newUEFIVM(_ *testing.T) *VirtualMachine { bootloader := NewEFIBootloader("/variable-store", false) - vm := NewVirtualMachine(3, 4_000_000_000, bootloader) + vm := NewVirtualMachine(3, 4_000, bootloader) return vm } diff --git a/pkg/config/virtio.go b/pkg/config/virtio.go index c8631e84..5523cf9a 100644 --- a/pkg/config/virtio.go +++ b/pkg/config/virtio.go @@ -47,34 +47,34 @@ type VirtioGPU struct { type VirtioVsock struct { // Port is the virtio-vsock port used for this device, see `man vsock` for more // details. - Port uint + Port uint `json:"port"` // SocketURL is the path to a unix socket on the host to use for the virtio-vsock communication with the guest. - SocketURL string + SocketURL string `json:"socketURL"` // If true, vsock connections will have to be done from guest to host. If false, vsock connections will only be possible // from host to guest - Listen bool + Listen bool `json:"listen,omitempty"` } // VirtioBlk configures a disk device. type VirtioBlk struct { StorageConfig - DeviceIdentifier string + DeviceIdentifier string `json:"deviceIdentifier,omitempty"` } type DirectorySharingConfig struct { - MountTag string + MountTag string `json:"mountTag"` } // VirtioFs configures directory sharing between the guest and the host. type VirtioFs struct { DirectorySharingConfig - SharedDir string + SharedDir string `json:"sharedDir"` } // RosettaShare configures rosetta support in the guest to run Intel binaries on Apple CPUs type RosettaShare struct { DirectorySharingConfig - InstallRosetta bool + InstallRosetta bool `json:"installRosetta"` } // NVMExpressController configures a NVMe controller in the guest @@ -91,19 +91,19 @@ type VirtioRng struct { // VirtioNet configures the virtual machine networking. type VirtioNet struct { - Nat bool - MacAddress net.HardwareAddr + Nat bool `json:"nat"` + MacAddress net.HardwareAddr `json:"-"` // custom marshaller in json.go // file parameter is holding a connected datagram socket. // see https://github.com/Code-Hex/vz/blob/7f648b6fb9205d6f11792263d79876e3042c33ec/network.go#L113-L155 - Socket *os.File + Socket *os.File `json:"socket,omitempty"` - UnixSocketPath string + UnixSocketPath string `json:"unixSocketPath,omitempty"` } // VirtioSerial configures the virtual machine serial ports. type VirtioSerial struct { - LogFile string - UsesStdio bool + LogFile string `json:"logFile,omitempty"` + UsesStdio bool `json:"usesStdio,omitempty"` } // TODO: Add VirtioBalloon @@ -676,9 +676,9 @@ func USBMassStorageNew(imagePath string) (VMComponent, error) { // StorageConfig configures a disk device. type StorageConfig struct { - DevName string - ImagePath string - ReadOnly bool + DevName string `json:"devName"` + ImagePath string `json:"imagePath"` + ReadOnly bool `json:"readOnly,omitempty"` } func (config *StorageConfig) ToCmdLine() ([]string, error) { diff --git a/pkg/rest/define/config.go b/pkg/rest/define/config.go index 62f85cf1..76bdaf54 100644 --- a/pkg/rest/define/config.go +++ b/pkg/rest/define/config.go @@ -1,13 +1,5 @@ package define -// InspectResponse is used when responding to a request for -// information about the virtual machine -type InspectResponse struct { - CPUs uint `json:"cpus"` - Memory uint64 `json:"memory"` - // Devices []config.VirtioDevice `json:"devices"` -} - // VMState can be used to describe the current state of a VM // as well as used to request a state change type VMState struct { diff --git a/pkg/rest/rest.go b/pkg/rest/rest.go index 9b1bd576..3db945c0 100644 --- a/pkg/rest/rest.go +++ b/pkg/rest/rest.go @@ -5,12 +5,21 @@ import ( "fmt" "net/url" "strings" + "syscall" "github.com/crc-org/vfkit/pkg/cmdline" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) +// see `man unix`: +// UNIX-domain addresses are variable-length filesystem pathnames of at most 104 characters. +func maxSocketPathLen() int { + var sockaddr syscall.RawSockaddrUnix + // sockaddr.Path must end with '\0', it's not relevant for go strings + return len(sockaddr.Path) - 1 +} + type Endpoint struct { Host string Path string @@ -72,11 +81,16 @@ func (v *VFKitService) Start() { // NewServer creates a new restful service func NewServer(inspector VirtualMachineInspector, stateHandler VirtualMachineStateHandler, endpoint string) (*VFKitService, error) { + gin.SetMode(gin.ReleaseMode) r := gin.Default() ep, err := NewEndpoint(endpoint) if err != nil { return nil, err } + err = r.SetTrustedProxies(nil) + if err != nil { + return nil, err + } s := VFKitService{ router: r, Endpoint: ep, @@ -123,6 +137,9 @@ func parseRestfulURI(inputURI string) (*url.URL, error) { if scheme == Unix && len(restURI.Host) > 0 { return nil, errors.New("invalid unix uri: host is forbidden") } + if scheme == Unix && len(restURI.Path) > maxSocketPathLen() { + return nil, fmt.Errorf("invalid unix uri: socket path length exceeds macOS limits") + } return restURI, err } diff --git a/pkg/rest/vf/vm_config.go b/pkg/rest/vf/vm_config.go index 5f5cbbd5..a4aff1dd 100644 --- a/pkg/rest/vf/vm_config.go +++ b/pkg/rest/vf/vm_config.go @@ -4,30 +4,26 @@ import ( "net/http" "github.com/Code-Hex/vz/v3" + "github.com/crc-org/vfkit/pkg/config" "github.com/crc-org/vfkit/pkg/rest/define" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) type VzVirtualMachine struct { - VzVM *vz.VirtualMachine - config *vz.VirtualMachineConfiguration + VzVM *vz.VirtualMachine + config *vz.VirtualMachineConfiguration + vmConfig *config.VirtualMachine } -func NewVzVirtualMachine(vm *vz.VirtualMachine, config *vz.VirtualMachineConfiguration) *VzVirtualMachine { - return &VzVirtualMachine{config: config, VzVM: vm} +func NewVzVirtualMachine(vm *vz.VirtualMachine, config *vz.VirtualMachineConfiguration, vmConfig *config.VirtualMachine) *VzVirtualMachine { + return &VzVirtualMachine{config: config, VzVM: vm, vmConfig: vmConfig} } // Inspect returns information about the virtual machine like hw resources // and devices func (vm *VzVirtualMachine) Inspect(c *gin.Context) { - ii := define.InspectResponse{ - // TODO complete me - CPUs: 1, - Memory: 2048, - //Devices: vm.Devices, - } - c.JSON(http.StatusOK, ii) + c.JSON(http.StatusOK, vm.vmConfig) } // GetVMState retrieves the current vm state diff --git a/pkg/vf/vm.go b/pkg/vf/vm.go index a34b38f1..cde5cf98 100644 --- a/pkg/vf/vm.go +++ b/pkg/vf/vm.go @@ -26,7 +26,7 @@ func newVzVirtualMachineConfiguration(vm *config.VirtualMachine) (*vzVirtualMach return nil, err } - vzVMConfig, err := vz.NewVirtualMachineConfiguration(vzBootloader, vm.Vcpus, vm.MemoryBytes) + vzVMConfig, err := vz.NewVirtualMachineConfiguration(vzBootloader, vm.Vcpus, uint64(vm.Memory.ToBytes())) if err != nil { return nil, err } diff --git a/test/osprovider.go b/test/osprovider.go index 208142ab..6af09603 100644 --- a/test/osprovider.go +++ b/test/osprovider.go @@ -143,12 +143,12 @@ func (puipui *PuiPuiProvider) Fetch(destDir string) error { return nil } -const puipuiMemoryBytes = 1 * 1024 // MiB +const puipuiMemoryMiB = 1 * 1024 const puipuiCPUs = 2 func (puipui *PuiPuiProvider) ToVirtualMachine() (*config.VirtualMachine, error) { bootloader := config.NewLinuxBootloader(puipui.vmlinuz, puipui.kernelArgs, puipui.initramfs) - vm := config.NewVirtualMachine(puipuiCPUs, puipuiMemoryBytes, bootloader) + vm := config.NewVirtualMachine(puipuiCPUs, puipuiMemoryMiB, bootloader) return vm, nil } diff --git a/test/vm_helpers.go b/test/vm_helpers.go index a414fc62..da0e3ae8 100644 --- a/test/vm_helpers.go +++ b/test/vm_helpers.go @@ -12,6 +12,7 @@ import ( "time" "github.com/crc-org/vfkit/pkg/config" + "github.com/crc-org/vfkit/pkg/rest" vfkithelpers "github.com/crc-org/crc/v2/pkg/drivers/vfkit" log "github.com/sirupsen/logrus" @@ -68,6 +69,7 @@ type vfkitRunner struct { *exec.Cmd errCh chan error gracefullyShutdown bool + restSocketPath string } func startVfkit(t *testing.T, vm *config.VirtualMachine) *vfkitRunner { @@ -81,11 +83,19 @@ func startVfkit(t *testing.T, vm *config.VirtualMachine) *vfkitRunner { binaryPath, err := exec.LookPath(vfkitRelativePath) require.NoError(t, err) + restSocketPath := filepath.Join(t.TempDir(), "rest.sock") + restEndpoint, err := rest.NewEndpoint(fmt.Sprintf("unix://%s", restSocketPath)) + + require.NoError(t, err) + restArgs, err := restEndpoint.ToCmdLine() + require.NoError(t, err) + log.Infof("starting %s", binaryPath) vfkitCmd, err := vm.Cmd(binaryPath) require.NoError(t, err) vfkitCmd.Stdout = logFile vfkitCmd.Stderr = logFile + vfkitCmd.Args = append(vfkitCmd.Args, restArgs...) err = vfkitCmd.Start() require.NoError(t, err) @@ -103,6 +113,7 @@ func startVfkit(t *testing.T, vm *config.VirtualMachine) *vfkitRunner { vfkitCmd, errCh, false, + restSocketPath, } } @@ -126,11 +137,12 @@ type testVM struct { provider OsProvider config *config.VirtualMachine - sshNetwork string - macAddress string // for SSH over TCP - port int - vsockPath string // for SSH over vsock - sshClient *ssh.Client + sshNetwork string + macAddress string // for SSH over TCP + port int + vsockPath string // for SSH over vsock + sshClient *ssh.Client + restSocketPath string vfkitCmd *vfkitRunner } @@ -199,6 +211,7 @@ func (vm *testVM) AddDevice(t *testing.T, dev config.VirtioDevice) { func (vm *testVM) Start(t *testing.T) { vm.vfkitCmd = startVfkit(t, vm.config) + vm.restSocketPath = vm.vfkitCmd.restSocketPath } func (vm *testVM) Stop(t *testing.T) { diff --git a/test/vm_test.go b/test/vm_test.go index b57c25dd..336ecea0 100644 --- a/test/vm_test.go +++ b/test/vm_test.go @@ -1,9 +1,11 @@ package test import ( + "encoding/json" "fmt" "io" "net" + "net/http" "os" "path/filepath" "regexp" @@ -235,7 +237,7 @@ var pciidMacOS13Tests = map[string]pciidTest{ return config.VirtioGPUNew() }, }, - "virtio-input/pointing-device": { + "virtio-input/trackpad": { vendorID: 0x106b, // Apple deviceID: 0x1a06, createDev: func(_ *testing.T) (config.VirtioDevice, error) { @@ -270,6 +272,24 @@ var pciidVersionedTests = map[int]map[string]pciidTest{ 14: pciidMacOS14Tests, } +func checkRestDevices(t *testing.T, vm *testVM) { + tr := &http.Transport{ + Dial: func(_, _ string) (conn net.Conn, err error) { + return net.Dial("unix", vm.restSocketPath) + }, + } + client := &http.Client{Transport: tr} + resp, err := client.Get("http://vfkit/vm/inspect") + require.NoError(t, err) + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + var unmarshalledVM config.VirtualMachine + err = json.Unmarshal(body, &unmarshalledVM) + require.NoError(t, err) + require.Equal(t, vm.config, &unmarshalledVM) +} + func testPCIId(t *testing.T, test pciidTest, provider OsProvider) { vm := NewTestVM(t, provider) defer vm.Close(t) @@ -283,6 +303,8 @@ func testPCIId(t *testing.T, test pciidTest, provider OsProvider) { vm.Start(t) vm.WaitForSSH(t) checkPCIDevice(t, vm, test.vendorID, test.deviceID) + checkRestDevices(t, vm) + vm.Stop(t) }