Skip to content
11 changes: 10 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{ pkgs ? import <nixpkgs> {} }:
{
pkgs ? import <nixpkgs> {},

virtiofsd_pinned ? import (fetchTarball {
name = "nixpkgs-pinned-for-virtiofsd";
url = "https://github.com/NixOS/nixpkgs/archive/13e8d35b7d6028b7198f8186bc0347c6abaa2701.tar.gz";
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this pin - we can inherit the toplevel pin, typically from IncldueOS' pinned.nix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can easily remove it. VirtioFSD should work with the latest nixpkgs. We need to check for breaking changes
when updating nixpkgs in the future.

sha256 = "0nqbvgmm7pbpyd8ihg2bi62pxihj8r673bc9ll4qhi6xwlfqac5q";
}) {},
}:
pkgs.python3.pkgs.buildPythonPackage rec {
pname = "vmrunner";
version = "0.16.0";
Expand All @@ -22,6 +30,7 @@ pkgs.python3.pkgs.buildPythonPackage rec {

passthru = {
inherit create_bridge;
virtiofsd = virtiofsd_pinned.virtiofsd;
};

meta = {
Expand Down
37 changes: 37 additions & 0 deletions vmrunner/vm.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,43 @@
}
},

"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"
}
}
},

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

"virtiopmem": {
"description" : "VirtioPMEM device",
"type" : "object",
"properties" : {
"image" : {
"description" : "Path to persistent image file",
"type" : "string"
},
"size" : {
"description" : "Persistent memory size in megabytes",
"type" : "number"
}
}
},
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
70 changes: 68 additions & 2 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 time
from enum import Enum
import platform
import psutil
Expand Down Expand Up @@ -186,6 +187,7 @@ def __init__(self, config):
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._virtiofsd_proc = None

# 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 @@ -516,6 +518,39 @@ 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=char0"]
qemu_args += ["-chardev", f"file,id=char0,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

time.sleep(0.1)
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't want to sleep - wait for a specific event if there's a race. You poll just below - there's probably a way to find out if the process is ready

Copy link
Contributor Author

@torgeiru torgeiru Oct 4, 2025

Choose a reason for hiding this comment

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

I reckon we can generate the socket and then wait for it to show up (the socket path).

Copy link
Contributor Author

@torgeiru torgeiru Oct 17, 2025

Choose a reason for hiding this comment

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

Found this TODO in the CI script for virtio-fs (virtio-fs-ci):

# TODO This is racy.  Wait for the listening message on virtiofsd stderr instead.
while [ ! -e vhost-fs.sock ]; do
	sleep 0.1
done

May have changed because that belongs to an old VirtioFSD, but helpful for knowing when process is ready.

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

info("Successfully started VirtioFSD!")

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=virtiofs0"]
qemu_args += ["-object", f"memory-backend-memfd,id=mem0,size={mem}M,share=on"]

return qemu_args

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

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 +695,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={64}G"]
Copy link
Contributor

Choose a reason for hiding this comment

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

1TB should be the least we support 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Easy.


vga_arg = ["-nographic" ]
if "vga" in self._config:
Expand All @@ -674,6 +709,36 @@ 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:
if "path" not in self._config["virtiocon"]:
raise Exception("Missing redirection path for guest output")
virtiocon_args = self.init_virtiocon(self._config["virtiocon"]["path"])

virtiofs_args = []
if "virtiofs" in self._config:
socket = "/tmp/virtiofsd.sock"
Copy link
Contributor

Choose a reason for hiding this comment

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

This will be problematic when we run multiple vms simultaneously; I think you can just have python create a tempfile here

Copy link
Contributor Author

@torgeiru torgeiru Oct 4, 2025

Choose a reason for hiding this comment

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

Do you think it would be okay to use something like mktemp and unlink the socket just for getting a conflict-free name?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps you could use Python's tempfile to create a temporary directory and put the socket there? Then you avoid the race and could get automatic cleanup when the script exits

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That seems like a clean way of achieving the wanted result.

if "socket" in self._config["virtiofs"]:
socket = self._config["virtiofs"]["socket"]

if "shared" not in self._config["virtiofs"]:
raise Exception("Shared directory not specified for VirtioFS!")
shared = self._config["virtiofs"]["shared"]

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

virtiopmem_args = []
if "virtiopmem" in self._config:
if "image" not in self._config["virtiopmem"]:
raise Exception("Missing path to persistent image file")
image = self._config["virtiopmem"]["image"]

if "size" not in self._config["virtiopmem"]:
raise Exception("Missing persistent memory size")
size = self._config["virtiopmem"]["size"]

virtiopmem_args = self.init_pmem(image, size)

# custom qemu binary/location
qemu_binary = "qemu-system-x86_64"
if "qemu" in self._config:
Expand Down Expand Up @@ -703,7 +768,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