Skip to content

Commit

Permalink
Add support for a graphical user interface, virtio-input and virtio-g…
Browse files Browse the repository at this point in the history
…pu devices

I added command line support for the user to add virtio-gpu and
virtio-input devices which also ties into having a good experience with
the newly added gui compatability.

I added command line support for the user to start a graphical application
window when their virtual machine starts through the `--gui` flag
(assuming the user added a virtio-gpu device as well).

I also added some mutual exclusion during creation of the
virtual machine configuration. Without calling `runtime.LockOSThread()`,
starting the virtual machine subsequently starting a graphic application
window would result in a SIGILL error. Alongside calling
`runtime.LockOSThread()`, I parallelized the running of the graphic
application window and checking the status of the virtual machine.
Otherwise, the two wouldn't be able to run simultaneously.

These new features can be testing using a command similar to this:

```bash
$ ./out/vfkit --cpus 2 --memory 2048 \
--bootloader efi,variable-store=/Users/jakecorrenti/efi-variable-store,create \
--device virtio-serial,stdio \
--device virtio-fs,sharedDir=/Users/jakecorrenti,mountTag=dir0 \
--device virtio-blk,path=/Users/jakecorrenti/Downloads/Fedora-Server-dvd-x86_64-38-1.6.iso \
--device virtio-net,nat,mac=72:20:43:d4:38:62 --device virtio-input,keyboard --device virtio-input,pointing --device virtio-gpu,display --gui
```

Signed-off-by: Jake Correnti <[email protected]>
  • Loading branch information
jakecorrenti committed Jun 6, 2023
1 parent 3d57f09 commit a061ac4
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 10 deletions.
53 changes: 43 additions & 10 deletions cmd/vfkit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"os"
"os/signal"
"runtime"
"syscall"
"time"

Expand Down Expand Up @@ -59,6 +60,9 @@ func newBootloaderConfiguration(opts *cmdline.Options) (config.Bootloader, error
}

func newVMConfiguration(opts *cmdline.Options) (*config.VirtualMachine, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

bootloader, err := newBootloaderConfiguration(opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -113,10 +117,22 @@ func waitForVMState(vm *vz.VirtualMachine, state vz.VirtualMachineState) error {
}

func runVFKit(vmConfig *config.VirtualMachine, opts *cmdline.Options) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
vzVMConfig, err := vf.ToVzVirtualMachineConfig(vmConfig)
if err != nil {
return err
}

if opts.UseGUI {
for _, gpuDev := range vmConfig.VirtioGPUDevices() {
if gpuDev.Device == config.VirtioGPUDisplayDevice {
gpuDev.UsesGUI = true
break
}
}
}

vm, err := vz.NewVirtualMachine(vzVMConfig)
if err != nil {
return err
Expand Down Expand Up @@ -168,18 +184,35 @@ func runVirtualMachine(vmConfig *config.VirtualMachine, vm *vz.VirtualMachine) e
}

log.Infof("waiting for VM to stop")
for {
err := waitForVMState(vm, vz.VirtualMachineStateStopped)
if err == nil {
log.Infof("VM is stopped")
break

errCh := make(chan error, 1)
go func() {
for {
err := waitForVMState(vm, vz.VirtualMachineStateStopped)
if err == nil {
log.Infof("VM is stopped")
errCh <- nil
return
}
if !errors.Is(err, errVMStateTimeout) {
errCh <- fmt.Errorf("virtualization error: %v", err)
return
}
// errVMStateTimeout -> keep looping
}
if !errors.Is(err, errVMStateTimeout) {
log.Infof("virtualization error: %v", err)
return err
}()

for _, gpuDev := range vmConfig.VirtioGPUDevices() {
if gpuDev.UsesGUI {
runtime.LockOSThread()
err := vm.StartGraphicApplication(900, 600)
runtime.UnlockOSThread()
if err != nil {
return err
}
break
}
// errVMStateTimeout -> keep looping
}

return nil
return <-errCh
}
3 changes: 3 additions & 0 deletions pkg/cmdline/cmdline.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type Options struct {
RestfulURI string

LogLevel string

UseGUI bool
}

const DefaultRestfulURI = "none://"
Expand All @@ -31,6 +33,7 @@ func AddFlags(cmd *cobra.Command, opts *Options) {
cmd.Flags().StringVarP(&opts.InitrdPath, "initrd", "i", "", "path to the virtual machine initrd")

cmd.Flags().VarP(&opts.Bootloader, "bootloader", "b", "bootloader configuration")
cmd.Flags().BoolVar(&opts.UseGUI, "gui", false, "display the contents of the virtual machine onto a graphical user interface")

cmd.MarkFlagsMutuallyExclusive("kernel", "bootloader")
cmd.MarkFlagsMutuallyExclusive("initrd", "bootloader")
Expand Down
22 changes: 22 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,28 @@ func (vm *VirtualMachine) AddDevicesFromCmdLine(cmdlineOpts []string) error {
return nil
}

func (vm *VirtualMachine) VirtioInputDevices() []*VirtioInput {
inputDevs := []*VirtioInput{}
for _, dev := range vm.Devices {
if inputDev, isVirtioInput := dev.(*VirtioInput); isVirtioInput {
inputDevs = append(inputDevs, inputDev)
}
}

return inputDevs
}

func (vm *VirtualMachine) VirtioGPUDevices() []*VirtioGPU {
gpuDevs := []*VirtioGPU{}
for _, dev := range vm.Devices {
if gpuDev, isVirtioGPU := dev.(*VirtioGPU); isVirtioGPU {
gpuDevs = append(gpuDevs, gpuDev)
}
}

return gpuDevs
}

func (vm *VirtualMachine) VirtioVsockDevices() []*VirtioVsock {
vsockDevs := []*VirtioVsock{}
for _, dev := range vm.Devices {
Expand Down
128 changes: 128 additions & 0 deletions pkg/config/virtio.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ import (
// The VirtioDevice interface is an interface which is implemented by all virtio devices.
type VirtioDevice VMComponent

const (
// Possible values for VirtioInput.InputType
VirtioInputPointingDevice = "pointing"
VirtioInputKeyboardDevice = "keyboard"

// Possible values for VirtioGPU.Device
VirtioGPUDisplayDevice = "display"
)

// VirtioInput configures an input device, such as a keyboard or pointing device
// (mouse) that the virtual machine can use
type VirtioInput struct {
InputType string // currently supports "pointing" and "keyboard" input types
}

// VirtioGPU configures a GPU device, such as the host computer's display
type VirtioGPU struct {
Device string // currently supports "display" as the device
UsesGUI bool
}

// VirtioVsock configures of a virtio-vsock device allowing 2-way communication
// between the host and the virtual machine type
type VirtioVsock struct {
Expand Down Expand Up @@ -114,6 +135,10 @@ func deviceFromCmdLine(deviceOpts string) (VirtioDevice, error) {
dev = &VirtioVsock{}
case "usb-mass-storage":
dev = usbMassStorageNewEmpty()
case "virtio-input":
dev = &VirtioInput{}
case "virtio-gpu":
dev = &VirtioGPU{}
default:
return nil, fmt.Errorf("unknown device type: %s", opts[0])
}
Expand Down Expand Up @@ -181,6 +206,109 @@ func (dev *VirtioSerial) FromOptions(options []option) error {
return dev.validate()
}

// VirtioInputNew creates a new input device for the virtual machine.
// The inputType parameter is the type of virtio-input device that will be added
// to the machine.
func VirtioInputNew(inputType string) (VirtioDevice, error) {
if inputType != VirtioInputPointingDevice && inputType != VirtioInputKeyboardDevice {
return nil, fmt.Errorf("Unknown option for virtio-input devices: %s", inputType)
}

return &VirtioInput{
InputType: inputType,
}, nil
}

func (dev *VirtioInput) validate() error {
if dev.InputType != VirtioInputPointingDevice && dev.InputType != VirtioInputKeyboardDevice {
return fmt.Errorf("Unknown option for virtio-input devices: %s", dev.InputType)
} else if dev.InputType == "" {
return fmt.Errorf("Device option for virtio-input device not provided")
}

return nil
}

func (dev *VirtioInput) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}

return []string{"--device", fmt.Sprintf("virtio-input,%s", dev.InputType)}, nil
}

func (dev *VirtioInput) FromOptions(options []option) error {
for _, option := range options {
if option.key == VirtioInputPointingDevice || option.key == VirtioInputKeyboardDevice {
if option.value != "" {
return fmt.Errorf(fmt.Sprintf("Unexpected value for virtio-input %s option: %s", option.key, option.value))
}
dev.InputType = option.key
} else {
return fmt.Errorf("Unknown option for virtio-input devices: %s", option.key)
}
}
return dev.validate()
}

// VirtioGPUNew creates a new gpu device for the virtual machine.
// The device parameter is the virtio-gpu device that will be added to the machine
func VirtioGPUNew(device string) (VirtioDevice, error) {
if device != VirtioGPUDisplayDevice {
return nil, fmt.Errorf("Unknown option for virtio-gpu devices: %s", device)
}

return &VirtioGPU{
Device: device,
UsesGUI: false,
}, nil
}

// VirtioGPUNewWithGUI creates a new gpu device for the virtual machine with the
// graphical user interface enabled. The device parameter is the virtio-gpu
// device that will be added to the machine
func VirtioGPUNewWithGUI(device string) (VirtioDevice, error) {
dev, err := VirtioGPUNew(device)
if err != nil {
return nil, err
}
dev.(*VirtioGPU).UsesGUI = true
return dev, nil
}

func (dev *VirtioGPU) validate() error {
if dev.Device != VirtioGPUDisplayDevice {
return fmt.Errorf("Unknown option for virtio-gpu devices: %s", dev.Device)
} else if dev.Device == "" {
return fmt.Errorf("Device option for virtio-gpu device not provided")
}

return nil
}

func (dev *VirtioGPU) ToCmdLine() ([]string, error) {
if err := dev.validate(); err != nil {
return nil, err
}

return []string{"--device", fmt.Sprintf("virtio-gpu,%s", dev.Device)}, nil
}

func (dev *VirtioGPU) FromOptions(options []option) error {
for _, option := range options {
if option.key == VirtioGPUDisplayDevice {
if option.value != "" {
return fmt.Errorf(fmt.Sprintf("Unexpected value for virtio-gpu %s option: %s", option.key, option.value))
}
dev.Device = option.key
} else {
return fmt.Errorf("Unknown option for virtio-gpu devices: %s", option.key)
}
}

return dev.validate()
}

// VirtioNetNew creates a new network device for the virtual machine. It will
// use macAddress as its MAC address.
func VirtioNetNew(macAddress string) (*VirtioNet, error) {
Expand Down
21 changes: 21 additions & 0 deletions pkg/config/virtio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,27 @@ var virtioDevTests = map[string]virtioDevTest{
},
expectedCmdLine: []string{"--device", "usb-mass-storage,path=/foo/bar"},
},
"NewVirtioInputWithPointingDevice": {
newDev: func() (VirtioDevice, error) { return VirtioInputNew("pointing") },
expectedDev: &VirtioInput{
InputType: "pointing",
},
expectedCmdLine: []string{"--device", "virtio-input,pointing"},
},
"NewVirtioInputWithKeyboardDevice": {
newDev: func() (VirtioDevice, error) { return VirtioInputNew("keyboard") },
expectedDev: &VirtioInput{
InputType: "keyboard",
},
expectedCmdLine: []string{"--device", "virtio-input,keyboard"},
},
"NewVirtioGPUDevice": {
newDev: func() (VirtioDevice, error) { return VirtioGPUNew("display") },
expectedDev: &VirtioGPU{
Device: "display",
},
expectedCmdLine: []string{"--device", "virtio-gpu,display"},
},
}

func testVirtioDev(t *testing.T, test *virtioDevTest) {
Expand Down
Loading

0 comments on commit a061ac4

Please sign in to comment.