qemu-tool 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qemu_tool-1.4.0/PKG-INFO +47 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-2vm/README.md +116 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-2vm/docker-compose.yml +180 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-rocjitsu-scale-out/README.md +276 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-rocjitsu-scale-out/docker-compose.yml +762 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-rocjitsu-scale-out/prometheus.yml +56 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-rocjitsu-vm/README.md +103 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-rocjitsu-vm/docker-compose.yml +93 -0
- qemu_tool-1.4.0/compose/vfio-user-ernic-vm/docker-compose.yml +69 -0
- qemu_tool-1.4.0/compose/vfio-user-rocjitsu-vm/docker-compose.yml +81 -0
- qemu_tool-1.4.0/env.example +133 -0
- qemu_tool-1.4.0/env.scale-out +255 -0
- qemu_tool-1.4.0/packages.d/packages-default +32 -0
- qemu_tool-1.4.0/packages.d/packages-minimal +6 -0
- qemu_tool-1.4.0/pyproject.toml +97 -0
- qemu_tool-1.4.0/qemu-tool/__init__.py +6 -0
- qemu_tool-1.4.0/qemu-tool/__main__.py +4 -0
- qemu_tool-1.4.0/qemu-tool/caps.py +102 -0
- qemu_tool-1.4.0/qemu-tool/cli.py +541 -0
- qemu_tool-1.4.0/qemu-tool/compose.py +102 -0
- qemu_tool-1.4.0/qemu-tool/config.py +107 -0
- qemu_tool-1.4.0/qemu-tool/envfile.py +143 -0
- qemu_tool-1.4.0/qemu-tool/ernic_stats.py +241 -0
- qemu_tool-1.4.0/qemu-tool/gen_compose.py +610 -0
- qemu_tool-1.4.0/qemu-tool/gen_vm.py +866 -0
- qemu_tool-1.4.0/qemu-tool/identity.py +44 -0
- qemu_tool-1.4.0/qemu-tool/libvirt_xml.py +717 -0
- qemu_tool-1.4.0/qemu-tool/list_vms.py +357 -0
- qemu_tool-1.4.0/qemu-tool/man/qemu-tool.1 +683 -0
- qemu_tool-1.4.0/qemu-tool/py.typed +0 -0
- qemu_tool-1.4.0/qemu-tool/resources.py +48 -0
- qemu_tool-1.4.0/qemu-tool/run_vm.py +564 -0
- qemu_tool-1.4.0/qemu_tool.egg-info/PKG-INFO +47 -0
- qemu_tool-1.4.0/qemu_tool.egg-info/SOURCES.txt +38 -0
- qemu_tool-1.4.0/qemu_tool.egg-info/dependency_links.txt +1 -0
- qemu_tool-1.4.0/qemu_tool.egg-info/entry_points.txt +2 -0
- qemu_tool-1.4.0/qemu_tool.egg-info/top_level.txt +1 -0
- qemu_tool-1.4.0/setup.cfg +4 -0
qemu_tool-1.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qemu-tool
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: CLI tool for running and generating QEMU VMs
|
|
5
|
+
Author: Stephen Bates
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sbates130272/qemu-minimal
|
|
8
|
+
Project-URL: Repository, https://github.com/sbates130272/qemu-minimal
|
|
9
|
+
Project-URL: Changelog, https://github.com/sbates130272/qemu-minimal/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/sbates130272/qemu-minimal/issues
|
|
11
|
+
Keywords: qemu,kvm,vm,vfio,cloud-init,libvirt,rocm
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: System :: Emulators
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# qemu-tool
|
|
29
|
+
|
|
30
|
+
A single CLI for creating and running QEMU virtual machines: cloud-init image
|
|
31
|
+
generation for x86_64, ARM64 and RISC-V, a flexible runner with NVMe, VFIO,
|
|
32
|
+
CXL, VirtFS and QMP support, and libvirt domain XML round-tripping.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pipx install qemu-tool
|
|
36
|
+
qemu-tool gen-vm --vm-name vm1 --arch x86_64
|
|
37
|
+
qemu-tool run-vm --vm-name vm1 --ssh-port 2222
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The wheel bundles its compose stacks and package manifests, so a rootless
|
|
41
|
+
install is fully functional. Settings come from
|
|
42
|
+
`$XDG_CONFIG_HOME/qemu-tool/env` and images default to
|
|
43
|
+
`$XDG_DATA_HOME/qemu-tool/images`. Installing the Debian package instead
|
|
44
|
+
puts both at their system locations.
|
|
45
|
+
|
|
46
|
+
Full documentation, the Docker Compose stacks and the Ansible playbooks live
|
|
47
|
+
in the [qemu-minimal repository](https://github.com/sbates130272/qemu-minimal).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# vfio-user ernic 2-VM mesh — Docker Compose stack
|
|
2
|
+
|
|
3
|
+
Spins up two VMs connected via a rocm-ernic TCP manager/worker mesh.
|
|
4
|
+
rocjitsu GPU emulators are optional per VM via Compose profiles
|
|
5
|
+
(`rocjitsu-vm1`, `rocjitsu-vm2`). The ernic-hub relays L2 Ethernet
|
|
6
|
+
frames between VMs so they share a private network; RDMA operations go
|
|
7
|
+
peer-to-peer over the same TCP connections.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
ernic-hub (tcp:manager:listen:6320) ──── VM 1 (qemu-1, SSH :2222)
|
|
13
|
+
│ └─ ernic-1.sock [+ rocjitsu-1.sock with profile rocjitsu-vm1]
|
|
14
|
+
│ TCP mesh
|
|
15
|
+
ernic-worker (tcp:worker:ernic-hub) ──── VM 2 (qemu-2, SSH :2223)
|
|
16
|
+
└─ ernic-2.sock [+ rocjitsu-2.sock with profile rocjitsu-vm2]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Docker with Compose v2
|
|
22
|
+
- `/dev/kvm` on the host (recommended)
|
|
23
|
+
- Two qcow2 VM images — each VM must have its own overlay (they can share a
|
|
24
|
+
backing file; see below)
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
# 1. Configure
|
|
30
|
+
cp qemu/env.example qemu/.env
|
|
31
|
+
$EDITOR qemu/.env # set VM1_NAME, VM2_NAME, VM_IMAGES_DIR
|
|
32
|
+
|
|
33
|
+
# 2. Create a second VM overlay if you only have one image
|
|
34
|
+
# (both VMs need distinct qcow2 files)
|
|
35
|
+
qemu-tool gen-vm --vm-name qemu-minimal-2 --backing-file \
|
|
36
|
+
/var/lib/qemu-tool/images/qemu-minimal-backing.qcow2
|
|
37
|
+
|
|
38
|
+
# 3a. Start ernic-only (no GPUs)
|
|
39
|
+
qemu-tool compose --stack vfio-user-ernic-2vm --vm-name qemu-minimal up
|
|
40
|
+
|
|
41
|
+
# 3b. Both VMs with GPUs
|
|
42
|
+
qemu-tool compose --stack vfio-user-ernic-2vm --vm-name qemu-minimal \
|
|
43
|
+
--profile rocjitsu-vm1 --profile rocjitsu-vm2 up
|
|
44
|
+
|
|
45
|
+
# 3c. VM 1 with GPU, VM 2 without
|
|
46
|
+
qemu-tool compose --stack vfio-user-ernic-2vm --vm-name qemu-minimal \
|
|
47
|
+
--profile rocjitsu-vm1 up
|
|
48
|
+
|
|
49
|
+
# 4. Connect to each VM
|
|
50
|
+
ssh -p 2222 ubuntu@localhost # VM 1
|
|
51
|
+
ssh -p 2223 ubuntu@localhost # VM 2
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configuring the NIC in each guest
|
|
55
|
+
|
|
56
|
+
Run `vm-ernic.yml --tags configure` once per guest, and **pass `vm_index`** —
|
|
57
|
+
it is what decides the address the guest takes on `rocm-ernic0`. The default is
|
|
58
|
+
`1`, so running the play twice unchanged puts both guests on `192.168.200.10`
|
|
59
|
+
and the mesh silently has an address collision rather than a working pair.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
ansible-playbook -i "vm1," ansible/playbooks/vm-ernic.yml --tags configure \
|
|
63
|
+
-e ansible_host=localhost -e ansible_port=2222 -e ansible_user=ubuntu \
|
|
64
|
+
-e vm_index=1 # -> 192.168.200.10
|
|
65
|
+
|
|
66
|
+
ansible-playbook -i "vm2," ansible/playbooks/vm-ernic.yml --tags configure \
|
|
67
|
+
-e ansible_host=localhost -e ansible_port=2223 -e ansible_user=ubuntu \
|
|
68
|
+
-e vm_index=2 # -> 192.168.200.20
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The address is derived as `192.168.200.<10 * vm_index>`, which is the same
|
|
72
|
+
arithmetic upstream's `vm-register.yml` applies to its instance manifest, so
|
|
73
|
+
these are the addresses the rocm-ernic system test expects. `-e vm_ip=<addr>`
|
|
74
|
+
overrides the result outright if you need a different scheme.
|
|
75
|
+
|
|
76
|
+
## Environment variables
|
|
77
|
+
|
|
78
|
+
| Variable | Default | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `VM1_NAME` | `qemu-minimal` | VM 1 image basename (no extension) |
|
|
81
|
+
| `VM2_NAME` | `qemu-minimal-2` | VM 2 image basename (no extension) |
|
|
82
|
+
| `VM_IMAGES_DIR` | `/var/lib/qemu-tool/images` | Directory containing both VM images |
|
|
83
|
+
| `VM1_SSH_PORT` | `2222` | Host port forwarded to VM 1 SSH |
|
|
84
|
+
| `VM2_SSH_PORT` | `2223` | Host port forwarded to VM 2 SSH |
|
|
85
|
+
| `VM_VCPUS` | `4` | vCPU count **per VM** (so 8 total); 4 is the floor for `ionic_rdma` |
|
|
86
|
+
| `VM_VMEM` | `8192` | RAM in MiB **per VM** (so 16 GiB total) |
|
|
87
|
+
| `ERNIC_TCP_PORT` | `6320` | TCP port for ernic manager/worker mesh |
|
|
88
|
+
| `ROCJITSU_CONFIG` | `gfx1250_mi455x.json` | rocjitsu GPU config (used when rocjitsu profiles active) |
|
|
89
|
+
| `VM_SHM_SIZE` | `8g` | Container shared memory (must be >= VM_VMEM MiB) |
|
|
90
|
+
|
|
91
|
+
## Compose profiles
|
|
92
|
+
|
|
93
|
+
| Profile | Effect |
|
|
94
|
+
|---|---|
|
|
95
|
+
| *(none)* | ernic mesh only — no GPU services |
|
|
96
|
+
| `rocjitsu-vm1` | add rocjitsu GPU for VM 1 |
|
|
97
|
+
| `rocjitsu-vm2` | add rocjitsu GPU for VM 2 |
|
|
98
|
+
| `rocjitsu-vm1 rocjitsu-vm2` | both VMs get a GPU |
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## VM image requirements
|
|
102
|
+
|
|
103
|
+
Each VM needs its **own qcow2 overlay** — two VMs cannot safely share a
|
|
104
|
+
single overlay file. They can however share the same backing file:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
# Create VM2's overlay from VM1's existing backing file
|
|
108
|
+
qemu-img create -f qcow2 -b /path/to/vm1-backing.qcow2 \
|
|
109
|
+
-F qcow2 /path/to/qemu-minimal-2.qcow2
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Known issues
|
|
113
|
+
|
|
114
|
+
See [../vfio-user-ernic-rocjitsu-vm/README.md](../vfio-user-ernic-rocjitsu-vm/README.md) for the amdgpu
|
|
115
|
+
driver panic workaround — apply it to both VM images before starting the
|
|
116
|
+
stack.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
# 2-VM ernic mesh stack with optional rocjitsu GPUs
|
|
3
|
+
#
|
|
4
|
+
# Always starts: ernic-hub (VM 1 NIC) + ernic-worker (VM 2 NIC) + qemu-1 + qemu-2.
|
|
5
|
+
# rocjitsu GPUs are opt-in per VM via Compose profiles:
|
|
6
|
+
# --profile rocjitsu-vm1 add GPU to VM 1
|
|
7
|
+
# --profile rocjitsu-vm2 add GPU to VM 2
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# cp qemu/env.example qemu/.env && $EDITOR qemu/.env
|
|
11
|
+
# # ernic-only:
|
|
12
|
+
# qemu-tool compose --stack vfio-user-ernic-2vm --vm-name vm1-name up
|
|
13
|
+
# # both VMs with GPUs:
|
|
14
|
+
# qemu-tool compose --stack vfio-user-ernic-2vm --vm-name vm1-name \
|
|
15
|
+
# --profile rocjitsu-vm1 --profile rocjitsu-vm2 up
|
|
16
|
+
# ssh -p ${VM1_SSH_PORT:-2222} ubuntu@localhost # VM 1
|
|
17
|
+
# ssh -p ${VM2_SSH_PORT:-2223} ubuntu@localhost # VM 2
|
|
18
|
+
|
|
19
|
+
x-gpu-common: &gpu-common
|
|
20
|
+
restart: on-failure
|
|
21
|
+
|
|
22
|
+
x-qemu-common: &qemu-common
|
|
23
|
+
image: "${QEMU_IMAGE:-docker.io/sbates130272/batesste-ci-images-ubuntu-qemu-libvfio-user:20260929.g2bdbd16-qemu11.1.2-vfu.8039244}"
|
|
24
|
+
volumes:
|
|
25
|
+
- vfu-sockets:/run/vfu:ro
|
|
26
|
+
- "${VM_IMAGES_DIR:-/var/lib/qemu-tool/images}:${VM_IMAGES_DIR:-/var/lib/qemu-tool/images}"
|
|
27
|
+
- "${QEMU_TOOL_SRC:-../../..}:/qemu-tool-src:ro"
|
|
28
|
+
devices:
|
|
29
|
+
- /dev/kvm
|
|
30
|
+
shm_size: "${VM_SHM_SIZE:-8g}"
|
|
31
|
+
restart: "no"
|
|
32
|
+
|
|
33
|
+
services:
|
|
34
|
+
# ---- rocm-ernic NIC for VM 1: TCP manager (hub) ----
|
|
35
|
+
ernic-hub:
|
|
36
|
+
<<: *gpu-common
|
|
37
|
+
image: "${ERNIC_IMAGE:-docker.io/sbates130272/batesste-ci-images-ubuntu-rocm-ernic:20260929.g2bdbd16-ernic.1484557-vfu.8039244}"
|
|
38
|
+
environment:
|
|
39
|
+
- ERNIC_DEBUG_MESH
|
|
40
|
+
command:
|
|
41
|
+
- rocm-ernic
|
|
42
|
+
- -s
|
|
43
|
+
- /run/vfu/ernic-1.sock
|
|
44
|
+
- -b
|
|
45
|
+
- "tcp:manager:listen:${ERNIC_TCP_PORT:-6320}"
|
|
46
|
+
- -m
|
|
47
|
+
- "${ERNIC1_MAC:-72:6f:63:6d:00:01}"
|
|
48
|
+
volumes:
|
|
49
|
+
- vfu-sockets:/run/vfu
|
|
50
|
+
healthcheck:
|
|
51
|
+
test: ["CMD", "sh", "-c", "test -S /run/vfu/ernic-1.sock"]
|
|
52
|
+
interval: 2s
|
|
53
|
+
timeout: 5s
|
|
54
|
+
retries: 30
|
|
55
|
+
start_period: 5s
|
|
56
|
+
|
|
57
|
+
# ---- rocm-ernic NIC for VM 2: TCP worker (spoke) ----
|
|
58
|
+
ernic-worker:
|
|
59
|
+
<<: *gpu-common
|
|
60
|
+
image: "${ERNIC_IMAGE:-docker.io/sbates130272/batesste-ci-images-ubuntu-rocm-ernic:20260929.g2bdbd16-ernic.1484557-vfu.8039244}"
|
|
61
|
+
environment:
|
|
62
|
+
- ERNIC_DEBUG_MESH
|
|
63
|
+
command:
|
|
64
|
+
- rocm-ernic
|
|
65
|
+
- -s
|
|
66
|
+
- /run/vfu/ernic-2.sock
|
|
67
|
+
- -b
|
|
68
|
+
- "tcp:worker:ernic-hub:${ERNIC_TCP_PORT:-6320}"
|
|
69
|
+
- -m
|
|
70
|
+
- "${ERNIC2_MAC:-72:6f:63:6d:00:02}"
|
|
71
|
+
volumes:
|
|
72
|
+
- vfu-sockets:/run/vfu
|
|
73
|
+
depends_on:
|
|
74
|
+
ernic-hub:
|
|
75
|
+
condition: service_healthy
|
|
76
|
+
healthcheck:
|
|
77
|
+
test: ["CMD", "sh", "-c", "test -S /run/vfu/ernic-2.sock"]
|
|
78
|
+
interval: 2s
|
|
79
|
+
timeout: 5s
|
|
80
|
+
retries: 30
|
|
81
|
+
start_period: 5s
|
|
82
|
+
|
|
83
|
+
# ---- rocjitsu GPU for VM 1 (opt-in: --profile rocjitsu-vm1) ----
|
|
84
|
+
rocjitsu-1:
|
|
85
|
+
<<: *gpu-common
|
|
86
|
+
profiles: [rocjitsu-vm1]
|
|
87
|
+
image: "${ROCJITSU_IMAGE:-docker.io/sbates130272/batesste-ci-images-ubuntu-rocm-rocjitsu:20260929.g2bdbd16-rocjitsu.f2d13fb}"
|
|
88
|
+
command:
|
|
89
|
+
- rocjitsu
|
|
90
|
+
- --config
|
|
91
|
+
- "/usr/local/share/rocjitsu/configs/${ROCJITSU_CONFIG:-gfx1250_mi455x.json}"
|
|
92
|
+
- --vfio-socket
|
|
93
|
+
- /run/vfu/rocjitsu-1.sock
|
|
94
|
+
volumes:
|
|
95
|
+
- vfu-sockets:/run/vfu
|
|
96
|
+
healthcheck:
|
|
97
|
+
test: ["CMD", "sh", "-c", "test -S /run/vfu/rocjitsu-1.sock"]
|
|
98
|
+
interval: 2s
|
|
99
|
+
timeout: 5s
|
|
100
|
+
retries: 30
|
|
101
|
+
start_period: 5s
|
|
102
|
+
|
|
103
|
+
# ---- rocjitsu GPU for VM 2 (opt-in: --profile rocjitsu-vm2) ----
|
|
104
|
+
rocjitsu-2:
|
|
105
|
+
<<: *gpu-common
|
|
106
|
+
profiles: [rocjitsu-vm2]
|
|
107
|
+
image: "${ROCJITSU_IMAGE:-docker.io/sbates130272/batesste-ci-images-ubuntu-rocm-rocjitsu:20260929.g2bdbd16-rocjitsu.f2d13fb}"
|
|
108
|
+
command:
|
|
109
|
+
- rocjitsu
|
|
110
|
+
- --config
|
|
111
|
+
- "/usr/local/share/rocjitsu/configs/${ROCJITSU_CONFIG:-gfx1250_mi455x.json}"
|
|
112
|
+
- --vfio-socket
|
|
113
|
+
- /run/vfu/rocjitsu-2.sock
|
|
114
|
+
volumes:
|
|
115
|
+
- vfu-sockets:/run/vfu
|
|
116
|
+
healthcheck:
|
|
117
|
+
test: ["CMD", "sh", "-c", "test -S /run/vfu/rocjitsu-2.sock"]
|
|
118
|
+
interval: 2s
|
|
119
|
+
timeout: 5s
|
|
120
|
+
retries: 30
|
|
121
|
+
start_period: 5s
|
|
122
|
+
|
|
123
|
+
# ---- VM 1: always connected to ernic-hub; GPU added when rocjitsu-vm1 profile active ----
|
|
124
|
+
qemu-1:
|
|
125
|
+
<<: *qemu-common
|
|
126
|
+
depends_on:
|
|
127
|
+
ernic-hub:
|
|
128
|
+
condition: service_healthy
|
|
129
|
+
rocjitsu-1:
|
|
130
|
+
condition: service_healthy
|
|
131
|
+
required: false
|
|
132
|
+
ports:
|
|
133
|
+
- "${VM1_SSH_PORT:-2222}:2222"
|
|
134
|
+
entrypoint:
|
|
135
|
+
- /bin/sh
|
|
136
|
+
- -c
|
|
137
|
+
- |
|
|
138
|
+
cp -r /qemu-tool-src/qemu /tmp/qemu-tool-build
|
|
139
|
+
PIPX_BIN_DIR=/usr/local/bin pipx install --force /tmp/qemu-tool-build
|
|
140
|
+
socks=$(ls /run/vfu/ernic-1.sock /run/vfu/rocjitsu-1.sock 2>/dev/null | tr '\n' ',' | sed 's/,$//')
|
|
141
|
+
exec qemu-tool run-vm \
|
|
142
|
+
--images "${VM_IMAGES_DIR:-/var/lib/qemu-tool/images}" \
|
|
143
|
+
--vm-name "${VM1_NAME:-qemu-minimal}" \
|
|
144
|
+
--vcpus "${VM_VCPUS:-4}" \
|
|
145
|
+
--vmem "${VM_VMEM:-8192}" \
|
|
146
|
+
--ssh-port 2222 \
|
|
147
|
+
--vfio-userdev "$$socks"
|
|
148
|
+
|
|
149
|
+
# ---- VM 2: always connected to ernic-worker; GPU added when rocjitsu-vm2 profile active ----
|
|
150
|
+
qemu-2:
|
|
151
|
+
<<: *qemu-common
|
|
152
|
+
depends_on:
|
|
153
|
+
ernic-worker:
|
|
154
|
+
condition: service_healthy
|
|
155
|
+
rocjitsu-2:
|
|
156
|
+
condition: service_healthy
|
|
157
|
+
required: false
|
|
158
|
+
ports:
|
|
159
|
+
- "${VM2_SSH_PORT:-2223}:2222"
|
|
160
|
+
entrypoint:
|
|
161
|
+
- /bin/sh
|
|
162
|
+
- -c
|
|
163
|
+
- |
|
|
164
|
+
cp -r /qemu-tool-src/qemu /tmp/qemu-tool-build
|
|
165
|
+
PIPX_BIN_DIR=/usr/local/bin pipx install --force /tmp/qemu-tool-build
|
|
166
|
+
socks=$(ls /run/vfu/ernic-2.sock /run/vfu/rocjitsu-2.sock 2>/dev/null | tr '\n' ',' | sed 's/,$//')
|
|
167
|
+
exec qemu-tool run-vm \
|
|
168
|
+
--images "${VM_IMAGES_DIR:-/var/lib/qemu-tool/images}" \
|
|
169
|
+
--vm-name "${VM2_NAME:-qemu-minimal-2}" \
|
|
170
|
+
--vcpus "${VM_VCPUS:-4}" \
|
|
171
|
+
--vmem "${VM_VMEM:-8192}" \
|
|
172
|
+
--ssh-port 2222 \
|
|
173
|
+
--vfio-userdev "$$socks"
|
|
174
|
+
|
|
175
|
+
volumes:
|
|
176
|
+
vfu-sockets:
|
|
177
|
+
driver: local
|
|
178
|
+
driver_opts:
|
|
179
|
+
type: tmpfs
|
|
180
|
+
device: tmpfs
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# vfio-user-ernic-rocjitsu-scale-out
|
|
2
|
+
|
|
3
|
+
An N-VM rocm-ernic mesh, one emulated RDMA NIC and one rocjitsu GPU per VM.
|
|
4
|
+
Same topology as `vfio-user-ernic-2vm`, sized by a variable instead of by hand.
|
|
5
|
+
|
|
6
|
+
**`docker-compose.yml` and `prometheus.yml` in this directory are generated.**
|
|
7
|
+
Edit [`qemu/env.scale-out`](../../env.scale-out) and regenerate; do not edit
|
|
8
|
+
them directly. Both are committed so the stack stays reviewable in a diff and
|
|
9
|
+
usable with plain `docker compose`, but a hand edit is lost on the next
|
|
10
|
+
regeneration and `gen-compose --check` will fail in CI.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
qemu-tool gen-compose --env-file qemu/env.scale-out
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Why generated
|
|
17
|
+
|
|
18
|
+
The 2-VM stack hand-writes every socket path, MAC, SSH port and qcow2 name.
|
|
19
|
+
That does not survive 8 VMs, let alone the mesh's 64-node ceiling
|
|
20
|
+
(`rdma_backend_tcp.c`), and hand-editing 24 services is exactly how two VMs end
|
|
21
|
+
up sharing a MAC — which presents as an intermittent RDMA fault, not as a
|
|
22
|
+
config error.
|
|
23
|
+
|
|
24
|
+
Per-VM values in the settings file are comma-lists, not numbered variables:
|
|
25
|
+
`VM1_NAME`/`VM2_NAME` does not scale. A bare value broadcasts to every VM, a
|
|
26
|
+
list of length `VM_COUNT` applies positionally, and any other length is an
|
|
27
|
+
error. The exception is a key naming a field that is *already* a list
|
|
28
|
+
(`VM_EXTRA_HOSTFWD` and friends), where the commas mean several values for one
|
|
29
|
+
VM and the whole value goes to every VM. `env.scale-out` documents this at
|
|
30
|
+
length because getting it backwards fails silently.
|
|
31
|
+
|
|
32
|
+
## Bringing it up
|
|
33
|
+
|
|
34
|
+
The VMs need qcow2 overlays. Fetch the published backing image once and
|
|
35
|
+
overlay the rest on it — `gen-compose` prints these commands with the names
|
|
36
|
+
filled in:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
qemu-tool gen-vm --env-file qemu/env.scale-out \
|
|
40
|
+
--vm-name stebates-fleet-1 --backing-image "$VM_BACKING_IMAGE"
|
|
41
|
+
for n in $(seq 2 8); do
|
|
42
|
+
qemu-tool gen-vm --env-file qemu/env.scale-out \
|
|
43
|
+
--vm-name stebates-fleet-$n \
|
|
44
|
+
--backing-file images/stebates-fleet-1-backing.qcow2
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
qemu-tool compose --env-file qemu/env.scale-out \
|
|
48
|
+
--stack vfio-user-ernic-rocjitsu-scale-out --profile metrics up -d
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
One backing file serves the whole fleet, which is why `VM_BACKING_SHARED=true`
|
|
52
|
+
is not optional here: all N VMs open it at once, and that needs
|
|
53
|
+
`backing.file.locking=off`.
|
|
54
|
+
|
|
55
|
+
## Topology
|
|
56
|
+
|
|
57
|
+
| Service | Role |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `ernic-1` | mesh manager, `tcp:manager:listen:$ERNIC_TCP_PORT` |
|
|
60
|
+
| `ernic-2..N` | workers, dialling the manager's **address**, not its name |
|
|
61
|
+
| `rocjitsu-1..N` | one emulated GPU per VM |
|
|
62
|
+
| `qemu-1..N` | the guests, each attached to its own two sockets |
|
|
63
|
+
| `ernic-stats-exporter` | `--profile metrics`, serves `:9840/metrics` |
|
|
64
|
+
| `prometheus` | `--profile metrics`, scrapes both sources |
|
|
65
|
+
|
|
66
|
+
Derived per VM `n`: name `<VM_NAME_PREFIX>-<n>`, SSH port
|
|
67
|
+
`VM_SSH_PORT_BASE + n - 1`, MAC `<ERNIC_MAC_PREFIX>:<n>>8>:<n&0xff>`, guest IP
|
|
68
|
+
`<ERNIC_GUEST_SUBNET>.<10+n>`. Guests start at `.11`: the manager claims `.1`
|
|
69
|
+
as its DHCP `server_ip` and intercepts ARP for it, so a VM placed there is
|
|
70
|
+
unreachable in a way that looks like a driver fault.
|
|
71
|
+
|
|
72
|
+
## Addressing
|
|
73
|
+
|
|
74
|
+
Every service has a fixed address on the `fleet` network —
|
|
75
|
+
`$FLEET_NET_PREFIX.<family>.<n>`, family `0` infra, `1` ernic, `2` rocjitsu,
|
|
76
|
+
`3` qemu — and **nothing in the stack resolves a service name at run time**.
|
|
77
|
+
Workers dial the manager's address, Prometheus scrapes addresses.
|
|
78
|
+
|
|
79
|
+
That is a scaling fix, not tidiness; see the ramp below.
|
|
80
|
+
|
|
81
|
+
Sockets (`/run/vfu/{ernic,rocjitsu}-<n>.sock`) are derived only, with no
|
|
82
|
+
override — they are internal wiring between services generated together, so an
|
|
83
|
+
override could only desynchronise them. Each guest is passed its own two
|
|
84
|
+
explicitly rather than globbing the directory, which the single-VM stacks do
|
|
85
|
+
and which would attach every device to every VM here.
|
|
86
|
+
|
|
87
|
+
## `ernic-1` does not restart
|
|
88
|
+
|
|
89
|
+
Deliberate, and the one thing worth knowing before reading the logs. If the
|
|
90
|
+
mesh manager restarts, every guest driver enters a DSR initialization timeout
|
|
91
|
+
on the next `modprobe ionic_rdma`, and the vfio-user sessions QEMU holds are
|
|
92
|
+
stale — reloading guest modules does not recover them. The only fix is
|
|
93
|
+
`docker compose down && up` for the whole stack. `restart: on-failure` would
|
|
94
|
+
turn that into a quiet wedge instead of a visible crash. See AGENTS.md issue 7.
|
|
95
|
+
|
|
96
|
+
## Metrics
|
|
97
|
+
|
|
98
|
+
Opt-in with `--profile metrics`. Two sources:
|
|
99
|
+
|
|
100
|
+
- **Guest node-exporter**, scraped at the qemu container's fleet address on
|
|
101
|
+
`:9100`. The guests are behind
|
|
102
|
+
QEMU SLIRP inside their containers and have no address on this network, so
|
|
103
|
+
the *container* is the target and `VM_EXTRA_HOSTFWD=tcp::9100-:9100` forwards
|
|
104
|
+
through to the guest. Each container has its own netns, so every VM uses the
|
|
105
|
+
same port with no arithmetic and nothing published to the host.
|
|
106
|
+
- **ernic stats**, from the dumps each `rocm-ernic -S` writes to the shared
|
|
107
|
+
`ernic-stats` tmpfs about once a second: per-QP bytes, doorbells, CQEs, WQEs
|
|
108
|
+
by opcode, MMIO and interrupt counts. `qemu-tool ernic-stats` parses them;
|
|
109
|
+
its metric and label names match rocm-ernic's own exporter, so that project's
|
|
110
|
+
`grafana/ernic-dashboard.json` works here unmodified.
|
|
111
|
+
|
|
112
|
+
The in-guest GPU exporters (`amd-metrics-exporter` on :5000,
|
|
113
|
+
`hsa-snoop-prometheus` on :9488) are deliberately *not* scraped: they poll the
|
|
114
|
+
emulated device, and rocjitsu serves single-threaded, so every scrape comes out
|
|
115
|
+
of the same thread as the workload being measured.
|
|
116
|
+
|
|
117
|
+
## Staleness
|
|
118
|
+
|
|
119
|
+
`docker-compose.yml` carries an `# env-sha256:` header recording the settings
|
|
120
|
+
file it was generated from. `qemu-tool compose` recomputes it and warns on
|
|
121
|
+
stderr when they disagree, naming the regeneration command. It never blocks —
|
|
122
|
+
`down` against a stale tree is how you recover from a bad edit.
|
|
123
|
+
|
|
124
|
+
## Ceilings
|
|
125
|
+
|
|
126
|
+
### Static limits
|
|
127
|
+
|
|
128
|
+
| Limit | Value |
|
|
129
|
+
|---|---|
|
|
130
|
+
| **mesh registration, measured** | **~40 nodes — the real ceiling** |
|
|
131
|
+
| rocm-ernic mesh topology payload | 64 nodes (`rdma_backend_tcp.c:2570`) |
|
|
132
|
+
| `VM_SHM_SIZE` × `VM_COUNT` ≤ `/dev/shm` | ~30 VMs at 8g on a 252G host |
|
|
133
|
+
| `VM_VCPUS` × `VM_COUNT` ≤ `nproc` | 32 VMs at 4 vCPU on 128 cores |
|
|
134
|
+
|
|
135
|
+
The vCPU row is the one that actually binds; see *Measured* below. It tolerates
|
|
136
|
+
about 25% of overcommit and then the fleet starts losing guests.
|
|
137
|
+
|
|
138
|
+
`VM_VCPUS` must not drop below 4 on an ernic stack: `ionic_lif_size()` derives
|
|
139
|
+
its EQ count from `num_online_cpus()` and `ionic_create_rdma_admin()` rejects
|
|
140
|
+
fewer than `IONIC_EQ_COUNT_MIN`, so `ionic_rdma` never probes. The only symptom
|
|
141
|
+
is "Failed to register ibdev" in the guest log.
|
|
142
|
+
|
|
143
|
+
### Measured
|
|
144
|
+
|
|
145
|
+
Ramped on a 128-core / 503G / 252G-`/dev/shm` host, 4 vCPU and 8G per VM, timed
|
|
146
|
+
from `compose up` to every guest's sshd banner. Nothing was overcommitted at
|
|
147
|
+
the top of this range: 48 VMs used 107G of 503G.
|
|
148
|
+
|
|
149
|
+
| VMs | containers | vCPU allocated | guests at sshd | lost |
|
|
150
|
+
|---|---|---|---|---|
|
|
151
|
+
| 4 | 12 | 16 | 26s | 0 |
|
|
152
|
+
| 8 | 24 | 32 | 28s | 0 |
|
|
153
|
+
| 16 | 48 | 64 | 31s | 0 |
|
|
154
|
+
| 32 | 96 | 128 | 46s | 0 |
|
|
155
|
+
| **40** | **120** | **160** | **117s** | **0** |
|
|
156
|
+
| 40 (repeat) | 122 | 160 | 37 of 40 | 3 |
|
|
157
|
+
| 44 | 132 | 176 | 41 of 44 | 3 |
|
|
158
|
+
| 48 | 144 | 192 | 41 of 48 | 7 |
|
|
159
|
+
| 64 | 196 | 256 | 40 of 64 (12 min) | 24 |
|
|
160
|
+
|
|
161
|
+
Sub-linear to 32 — 8× the VMs for 1.8× the time, with vCPU allocation exactly
|
|
162
|
+
matching the host's 128 cores. **32 VMs is the largest size that booted clean
|
|
163
|
+
every time. 40 is marginal**: the same stack on the same host booted 40/40
|
|
164
|
+
once and lost 3 on a repeat, so treat the 40 row as a coin flip rather than a
|
|
165
|
+
supported size. The boot burst is what decides it — load average peaked at
|
|
166
|
+
**417** on 128 cores during the losing run, and fell back to 54 once the
|
|
167
|
+
survivors were up.
|
|
168
|
+
|
|
169
|
+
Memory is not what runs out: 48 VMs used 107G of 503G, and the fleet at 44 had
|
|
170
|
+
396G free. What runs out is CPU. Every VM needs 4 vCPU (`ionic_rdma` will not
|
|
171
|
+
probe below `IONIC_EQ_COUNT_MIN`), so 32 VMs already saturate a 128-core host
|
|
172
|
+
and 40 is 25% over. Guests idle after boot, which is why 40 works at all — but
|
|
173
|
+
the boot burst is not idle, and load peaked at 174 during the 40-VM start.
|
|
174
|
+
|
|
175
|
+
### What breaks first, and why it is not memory
|
|
176
|
+
|
|
177
|
+
Two of the three failure modes found at 48 were name resolution, and both are
|
|
178
|
+
fixed above; the third is upstream's and is not.
|
|
179
|
+
|
|
180
|
+
1. *(fixed — static addressing)* Docker runs one embedded resolver per network
|
|
181
|
+
and a 144-container start burst saturates it. Workers died with `Failed to
|
|
182
|
+
resolve host 'ernic-1': Temporary failure in name resolution`. Worse, the
|
|
183
|
+
mesh advertises each node by `gethostname()` and peers resolve that name to
|
|
184
|
+
dial it, so every one of the fleet's N×(N−1) peer links went through the
|
|
185
|
+
same resolver. Each container is now named after its own address, which
|
|
186
|
+
makes those a numeric `getaddrinfo`. Measured effect at 48: **4497 → 0**
|
|
187
|
+
resolution failures.
|
|
188
|
+
2. *(fixed — retry)* The entrypoint's `pipx install` reaches pypi.org, and 48
|
|
189
|
+
of those racing killed 12 guests before QEMU started. Retried five times.
|
|
190
|
+
3. *(upstream, unfixed)* The manager's heartbeat timeout is 20 s and is not
|
|
191
|
+
tunable — `rocm-ernic --help` exposes no knob. During a 48-VM start the
|
|
192
|
+
manager slips past it and declares live nodes dead (`Node N failed health
|
|
193
|
+
check (last heartbeat: 20 seconds ago)`). The evicted worker exits, its
|
|
194
|
+
vfio-user socket disappears, and the guest that had attached to it dies
|
|
195
|
+
with `vfio-user: timed out waiting for reply`. At both 44 and 48 the lost
|
|
196
|
+
guests matched the restarted ernics one for one — 3 and 7 respectively.
|
|
197
|
+
|
|
198
|
+
This is the CPU limit wearing a protocol's clothes. The heartbeat is a wall
|
|
199
|
+
clock, so oversubscribing the host does not merely slow the fleet down, it
|
|
200
|
+
makes the manager mistake slow nodes for dead ones. Which is why 40 VMs
|
|
201
|
+
boot cleanly in 117 s and 44 lose three: nothing is short of memory at
|
|
202
|
+
either size.
|
|
203
|
+
|
|
204
|
+
Failure 3 compounds because **the mesh allocates a new node id on every
|
|
205
|
+
reconnect and never reclaims the old one**. The 64-node cap is therefore a cap
|
|
206
|
+
on *connection events*, not on VMs. The same 48-VM fleet reached id 61 before
|
|
207
|
+
the fixes above and 50 after them — so churn, not `VM_COUNT`, is what consumes
|
|
208
|
+
the budget, and a fleet that flaps its way past 64 stops working for reasons
|
|
209
|
+
that have nothing to do with its size.
|
|
210
|
+
|
|
211
|
+
### What actually kills the fleet
|
|
212
|
+
|
|
213
|
+
The node id is not the only thing a reconnect consumes. **The manager does not
|
|
214
|
+
close the socket of a node it evicts**, so each eviction leaks a file
|
|
215
|
+
descriptor, and Docker's default soft `nofile` is 1024. On the 40-VM fleet the
|
|
216
|
+
manager evicted eighteen live workers before its own guest had even attached;
|
|
217
|
+
at roughly one leaked fd per eviction the limit arrives well inside a normal
|
|
218
|
+
run. The first thing to fail is the manager reopening its own stats file, then
|
|
219
|
+
every `accept()`:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
ERROR: rdma: Failed to open stats file /run/ernic-stats/ernic-1.stats: Too many open files
|
|
223
|
+
ERROR: rdma: TCP: Failed to accept connection: Too many open files
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
There is no backoff on that path — it spins, and produced 3.3 GB of log in 12
|
|
227
|
+
hours. The manager then goes unhealthy, and being `restart: "no"` it stays
|
|
228
|
+
down, so its guest dies on an assertion in QEMU's own error handling
|
|
229
|
+
(`error_setv: Assertion *errp == NULL failed`).
|
|
230
|
+
|
|
231
|
+
Every ernic service therefore gets `ulimits.nofile` from `ERNIC_NOFILE`
|
|
232
|
+
(default 65536). **This does not fix the leak** — that is upstream's — it moves
|
|
233
|
+
the wall far enough out that a fleet-length run finishes first. Raise it
|
|
234
|
+
further, or reduce churn, for a fleet that flaps.
|
|
235
|
+
|
|
236
|
+
Re-measured at 40 VMs with the limit raised: manager steady at 165 fds, **0**
|
|
237
|
+
EMFILE, max node id 31 of the 64 budget (it reached 50–61 before). So the
|
|
238
|
+
long-run death is gone. The *boot* ceiling is unchanged — that run still lost
|
|
239
|
+
3 guests to 24 heartbeat evictions, and the three lost guests were exactly the
|
|
240
|
+
three ernics that restarted. Raising the fd limit buys longevity, not scale.
|
|
241
|
+
|
|
242
|
+
### The mesh saturates at ~40 nodes, and 64 is not reachable
|
|
243
|
+
|
|
244
|
+
Asking for 64 VMs on this host settles at **40 of 64**. The other 24 workers
|
|
245
|
+
never register; they sit in a permanent restart loop on
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
ERROR: rdma: TCP: Registration timeout
|
|
249
|
+
Failed to realize PVRDMA device with backend 'tcp:worker:172.31.1.1:6320'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This is **not** the boot burst. Load peaked at 843 on 128 cores — 64 VMs ask
|
|
253
|
+
for 256 vCPU — and the fleet took 12 minutes to settle. But the stragglers
|
|
254
|
+
still fail identically with the host back at load 16, and stopping a healthy
|
|
255
|
+
worker to free a slot lets others in. It is a capacity wall, not a delay.
|
|
256
|
+
|
|
257
|
+
The cause is that this is a **full mesh**. A worker holds about N+2 sockets
|
|
258
|
+
where N is the *registered* size, not `VM_COUNT` — 42 sockets measured on both
|
|
259
|
+
the 40-VM and the 64-VM fleet. Registering node N+1 therefore costs N new
|
|
260
|
+
connections and touches every existing node, through a single-threaded manager,
|
|
261
|
+
against a fixed wall-clock registration timeout. The cost per registration
|
|
262
|
+
climbs with the mesh until registration stops completing.
|
|
263
|
+
|
|
264
|
+
The 64-node topology cap never came into it: **max node id reached 30**. Plan
|
|
265
|
+
around ~40, expect it to move with host speed, and note two consequences:
|
|
266
|
+
|
|
267
|
+
- **A stuck worker retries forever; its guest gets one chance.** `ernic` is
|
|
268
|
+
`restart: on-failure`, `qemu` is `restart: "no"`. By the time a worker on its
|
|
269
|
+
fifteenth attempt registers, the VM it exists to serve has been dead for ten
|
|
270
|
+
minutes. The 24 stuck workers matched the 24 dead guests exactly.
|
|
271
|
+
- **Failed registrations leak manager fds too, and faster than evictions do.**
|
|
272
|
+
Measured over a six-hour run parked above the wall: 8006 worker restarts and
|
|
273
|
+
50 fds/min at the manager, reaching 18164 open descriptors. That is Docker's
|
|
274
|
+
1024 default in 20 minutes and `ERNIC_NOFILE=65536` in about 22 hours — a
|
|
275
|
+
working day, not an indefinite reprieve. Size the fleet below the wall; no
|
|
276
|
+
fd limit makes sitting above it survivable.
|