libvirtd

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

Libvirtd

Runs a libvirtd container (KVM/QEMU virtualization manager) for integration testing and returns a connected go-libvirt client. The client exposes the full libvirt API — virtual machine (domain) lifecycle, storage pools and volumes, virtual networks, snapshots, and more.

libvirtd is configured to listen on TCP (port 16509), which is exposed to the host so the test connects over TCP.

The client interface provides Client(), Addr(), HasKVM(), and Close(ctx).

Privileges and requirements

Running libvirtd + QEMU inside a container needs several things. The wrapper sets them up automatically; this section documents what and why.

TCP (default)

The container mounts a libvirtd.conf with:

listen_tcp = 1
listen_tls = 0
auth_tcp = "none"
listen_addr = "0.0.0.0"
  • listen_tls = 0 is required — otherwise libvirtd tries to set up TLS and aborts when no CA certificate is present.
  • auth_tcp = "none" disables authentication (fine for an ephemeral test container).

The port 16509 is exposed to the host and the test connects over TCP.

Capabilities

By default the container runs in a least-privilege configuration: --cap-drop=ALL plus only the capabilities QEMU/libvirtd need:

NET_ADMIN        NET_RAW        NET_BIND_SERVICE
DAC_OVERRIDE     DAC_READ_SEARCH
SYS_NICE         SYS_RESOURCE   SYS_PTRACE
MKNOD            CHOWN          SETUID         SETGID
FOWNER           FSETID         KILL           SETPCAP
IPC_LOCK         AUDIT_WRITE

Docker's default seccomp profile is relaxed (seccomp=unconfined) because QEMU needs syscalls it blocks. This config supports: connect, enumeration, and storage pool/volume CRUD.

Privileged mode (WithPrivileged())

Booting VMs and creating libvirt virtual networks require more than the least-privilege set:

  • cgroup — launching QEMU creates cgroups under /sys/fs/cgroup; this needs a privileged container and a /sys/fs/cgroup mount (the wrapper adds it in privileged mode).
  • /proc/sys — creating a network bridge writes /proc/sys/net/ipv6/conf/<bridge>/disable_ipv6, which is read-only outside a privileged container.

Pass libvirtd.WithPrivileged() to New/NewWithImage for VM boot and network tests:

app, err := libvirtd.New(ctx, libvirtd.WithPrivileged())
Device passthrough

/dev/kvm and /dev/net/tun are passed through only if they exist on the host (a missing device never fails container creation):

  • /dev/kvm — hardware acceleration. When absent, QEMU falls back to software (TCG) emulation — slower but functional. HasKVM() reports whether KVM is in use.
  • /dev/net/tun — lets QEMU create tap devices for guest NICs. The tap interfaces live in the container's own network namespace (we do not use network_mode: host), so they do not affect the host network stack.
QEMU config

The wrapper also mounts a qemu.conf with:

user = "root"
group = "root"
remember_owner = 0

remember_owner = 0 is required: without it libvirt tries to set the trusted.libvirt.security.dac xattr to remember/restore file ownership, which needs CAP_SYS_ADMIN that a least-privilege container does not have (starting a VM otherwise fails with "Unable to set XATTR ...").

Tested versions

The default image is used (built from teran/libvirtd-container):

ghcr.io/teran/libvirtd-container/libvirtd:v0.1.0

How to use

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/teran/go-docker-testsuite/applications/libvirtd"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
    defer cancel()

    app, err := libvirtd.New(ctx)
    if err != nil {
        panic(err)
    }
    defer app.Close(ctx)

    // app.Client() exposes the full go-libvirt API.
    ver, err := app.Client().ConnectGetLibVersion()
    if err != nil {
        panic(err)
    }
    fmt.Printf("libvirt version: %d (KVM: %v)\n", ver, app.HasKVM())
}

Running the tests

The example requires a running Docker daemon:

go test -run Example ./applications/libvirtd/

The integration tests (VM boot, network) additionally require a Docker daemon with a proper cgroup v2 hierarchy and run libvirtd in privileged mode.

Documentation

Overview

Package libvirtd runs libvirtd (the KVM/QEMU virtualization manager) in a Docker container and returns a connected go-libvirt client. This exposes the full libvirt API — virtual machine (domain) lifecycle, storage pools and volumes, virtual networks, snapshots and more — so tests can drive real virtualization without mocks.

The container is configured to listen on a TCP socket (port 16509) so the test connects over TCP. By default it runs in a least-privilege configuration: every capability is dropped and only the minimal set QEMU/libvirtd need is added back, with Docker's default seccomp profile relaxed (seccomp=unconfined) so QEMU can start. /dev/kvm and /dev/net/tun are passed through when they exist on the host (otherwise QEMU falls back to software/TCG emulation). Call WithPrivileged for full-privilege mode.

Example

This example demonstrates starting a libvirtd container and retrieving the libvirt version via the connected go-libvirt client.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/teran/go-docker-testsuite/applications/libvirtd"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
	defer cancel()

	app, err := libvirtd.New(ctx)
	if err != nil {
		fmt.Printf("error: %v (is Docker running, with /dev/kvm?)\n", err)
		return
	}
	defer func() { _ = app.Close(ctx) }()

	ver, err := app.Client().ConnectGetLibVersion()
	if err != nil {
		fmt.Printf("error getting libvirt version: %v\n", err)
		return
	}
	fmt.Printf("libvirt version: %d\n", ver)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Libvirt

type Libvirt interface {
	// Client returns a connected go-libvirt client exposing the full
	// libvirt API (domains, storage pools/volumes, networks, snapshots, ...).
	Client() *libvirt.Libvirt
	// Addr returns the host:port of the libvirtd TCP endpoint.
	Addr() string
	// HasKVM reports whether /dev/kvm was present and passed into the
	// container (i.e. whether KVM acceleration is in use vs software/TCG).
	HasKVM() bool
	Close(ctx context.Context) error
}

Libvirt is the interface returned by the libvirtd application.

func New

func New(ctx context.Context, opts ...Option) (Libvirt, error)

New starts a libvirtd container using the default image.

func NewWithImage

func NewWithImage(ctx context.Context, image string, opts ...Option) (Libvirt, error)

NewWithImage starts a libvirtd container using the given image.

libvirtd is configured to listen on TCP (port 16509) via an injected libvirtd.conf, and the port is exposed to the host. /dev/kvm and /dev/net/tun are passed through only if they exist on the host; when /dev/kvm is absent, QEMU falls back to slower software (TCG) emulation rather than failing.

func NewWithImageT

func NewWithImageT(t *testing.T, ctx context.Context, image string, opts ...Option) (Libvirt, error)

NewWithImageT is NewWithImage bound to a *testing.T: the container's lifecycle is tied to the test and cleaned up automatically via t.Cleanup.

func NewWithT

func NewWithT(t *testing.T, ctx context.Context, opts ...Option) (Libvirt, error)

NewWithT is New bound to a *testing.T: the container's lifecycle is tied to the test and cleaned up automatically via t.Cleanup.

type Option

type Option func(*options)

Option configures a libvirtd container.

func WithPrivileged

func WithPrivileged() Option

WithPrivileged runs the container in full Docker privileged mode. This is an opt-in for workloads that need capabilities beyond the least-privilege default (e.g. SYS_ADMIN for mount/loop-backed storage pools or LVM). It is mutually exclusive with the default capability whitelist.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL