Skip to content
46 changes: 46 additions & 0 deletions vmrunner/vm.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,52 @@
}
},

"virtiocon" : {
"description" : "VirtioCON device. Only used for testing Virtio queues for now",
"type" : "object",
"properties" : {
"path" : {
"description" : "The path to redirect the guest output",
"type" : "string"
}
},

"required" : ["path"]
},

"virtiofs" : {
"description" : "VirtioFS device",
"type" : "object",
"properties" : {
"shared" : {
"description" : "Directory to be shared with guest",
"type" : "string"
}
},

"required" : ["shared"]
},

"virtiopmem": {
"description" : "VirtioPMEM devices",
"type" : "array",
"items" : {
"description" : "Device configuration",
"type" : "object",
"properties" : {
"image" : {
"description" : "Path to persistent image file",
"type" : "string"
},
"size" : {
"description" : "Persistent memory size in megabytes",
"type" : "number"
}
}
},

"required" : ["image", "size"]
},
Copy link
Contributor

Choose a reason for hiding this comment

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

So new device types for virtio console, fs and pmem - nice!


"mem" : {
"description" : "Amount of memory in megabytes",
Expand Down
68 changes: 64 additions & 4 deletions vmrunner/vmrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import re
import traceback
import signal
import tempfile
from enum import Enum
import platform
import psutil
Expand Down Expand Up @@ -184,8 +185,9 @@ def __init__(self, config):
self._config = config
self._allow_sudo = False # must be explicitly turned on at boot.
self._enable_kvm = False # must be explicitly turned on at boot.
self._sudo = False # Set to true if sudo is available
self._proc = None # A running subprocess
self._sudo = False # Set to true if sudo is available
self._proc = None # A running subprocess
self._tmp_dirs = [] # A list of tmp dirs created using tempfile module. Used for socket creation for automatic cleanup and garbage collection

# pylint: disable-next=unused-argument
def boot_in_hypervisor(self, multiboot=False, debug=False, kernel_args="", image_name="", allow_sudo = False, enable_kvm = False):
Expand Down Expand Up @@ -418,6 +420,7 @@ class qemu(hypervisor):
def __init__(self, config):
super().__init__(config)
self._proc = None
self._virtiofsd_proc = None
self._stopped = False
self._sudo = False
self._image_name = self._config if "image" in self._config else self.name() + " vm"
Expand Down Expand Up @@ -516,6 +519,41 @@ def net_arg(self, backend, device, if_name = "net0", mac = None, bridge = None,
return ["-device", device,
"-netdev", netdev]

def init_virtiocon(self, path):
""" creates a console device and redirects to the path given """
qemu_args = ["-device", "virtio-serial-pci,disable-legacy=on,id=virtio-serial0"]
qemu_args += ["-device", "virtserialport,chardev=virtiocon0"]
qemu_args += ["-chardev", f"file,id=virtiocon0,path={path}"]

return qemu_args

def init_virtiofs(self, socket, shared, mem):
""" initializes virtiofs by launching virtiofsd and creating a virtiofs device """
virtiofsd_args = ["virtiofsd", "--socket", socket, "--shared-dir", shared, "--sandbox", "none"]
self._virtiofsd_proc = subprocess.Popen(virtiofsd_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # pylint: disable=consider-using-with

if self._virtiofsd_proc.poll():
raise Exception("VirtioFSD failed to start")

info("Successfully started VirtioFSD!")

while not os.path.exists(socket):
Copy link
Contributor

@alfreb alfreb Oct 18, 2025

Choose a reason for hiding this comment

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

This will hang with no helpful information if the shared folder doesn't exist:

* <VMRunner>:  Booting ./fs_virtiopmem.elf.bin directly without bootloader (multiboot / -kernel args) 
* <VMRunner>:  Successfully started VirtioFSD! 

You can reproduce this by adding this to vm.json

  "virtiofs" : {
      "shared" : "./this_is_shared"
      
  }

And then booting with boot, when ./this_is_shared does not exist. After creating the directory this_is_shared, the unikernel starts. So we should check if the provided shared folder exists and is a folder, and if not error out. But since it works in the happy path, and only takes effect if you actually add a virtiofs entry to vm.json, I'm happy to merge this - just add the check in a separate PR.

Copy link
Contributor Author

@torgeiru torgeiru Oct 18, 2025

Choose a reason for hiding this comment

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

This is because poll() returns immediately and error might not have been raised before poll() have returned (this is a race). I quickly tried to add a time.sleep(0.1) and it didn't hang because poll then returns the error code and the failure exception is raised. We need another signal. Any suggestions on how to solve this? I know that tests use a "SUCCESS" to indicate a successful test. I think a similar pattern could be used here (error is printed early on error, a watch).

...

qemu_args = ["-machine", "memory-backend=mem0"]
qemu_args += ["-chardev", f"socket,id=virtiofsd0,path={socket}"]
qemu_args += ["-device", "vhost-user-fs-pci,chardev=virtiofsd0,tag=vfs"]
qemu_args += ["-object", f"memory-backend-memfd,id=mem0,size={mem}M,share=on"]

return qemu_args

def init_pmem(self, path, size, pmem_id):
""" creates a pmem device with image path as memory mapped backend """
qemu_args = ["-object", f"memory-backend-file,id=pmemdev{pmem_id},mem-path={path},size={size}M,share=on"]
qemu_args += ["-device", f"virtio-pmem-pci,memdev=pmemdev{pmem_id}"]

return qemu_args

def kvm_present(self):
""" returns true if KVM is present and available """
if not self._enable_kvm:
Expand Down Expand Up @@ -660,7 +698,7 @@ def boot_in_hypervisor(self, multiboot=True, debug = False, kernel_args = "", im

mem_arg = []
if "mem" in self._config:
mem_arg = ["-m", str(self._config["mem"])]
mem_arg = ["-m", f"size={self._config["mem"]},maxmem=1000G"]

vga_arg = ["-nographic" ]
if "vga" in self._config:
Expand All @@ -674,6 +712,27 @@ def boot_in_hypervisor(self, multiboot=True, debug = False, kernel_args = "", im
if "vfio" in self._config:
pci_arg = ["-device", "vfio-pci,host=" + self._config["vfio"]]

virtiocon_args = []
if "virtiocon" in self._config:
virtiocon_args = self.init_virtiocon(self._config["virtiocon"]["path"])

virtiofs_args = []
if "virtiofs" in self._config:
tmp_virtiofs_dir = tempfile.TemporaryDirectory(prefix="virtiofs-") # pylint: disable=consider-using-with
self._tmp_dirs.append(tmp_virtiofs_dir)
socket_path = os.path.join(tmp_virtiofs_dir.name, "virtiofsd.sock")

shared = self._config["virtiofs"]["shared"]

virtiofs_args = self.init_virtiofs(socket_path, shared, self._config["mem"])

virtiopmem_args = []
if "virtiopmem" in self._config:
for pmem_id, virtiopmem in enumerate(self._config["virtiopmem"]):
image = virtiopmem["image"]
size = virtiopmem["size"]
virtiopmem_args += self.init_pmem(image, size, pmem_id)

# custom qemu binary/location
qemu_binary = "qemu-system-x86_64"
if "qemu" in self._config:
Expand Down Expand Up @@ -703,7 +762,8 @@ def boot_in_hypervisor(self, multiboot=True, debug = False, kernel_args = "", im

command += kernel_args
command += disk_args + debug_args + net_args + mem_arg + mod_args
command += vga_arg + trace_arg + pci_arg
command += vga_arg + trace_arg + pci_arg + virtiocon_args + virtiofs_args
command += virtiopmem_args

#command_str = " ".join(command)
#command_str.encode('ascii','ignore')
Expand Down