two/internal/qemu/start_linux.go
GnomeZworc 26d5805747
All checks were successful
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m36s
f-33: vm: start vms with multiple nic
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-08-25 23:12:55 +02:00

137 lines
3.8 KiB
Go

//go:build linux
package qemu
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
)
const (
firstNICSlot = 0x03
lastNICSlot = 0x1d
maxNICs = lastNICSlot - firstNICSlot + 1
)
func Start(cfg Config) error {
memory := cfg.Memory
if memory == 0 {
memory = 512
}
cpus := cfg.CPUs
if cpus == 0 {
cpus = 1
}
for _, dir := range []string{cfg.SerialDir, cfg.MonitorDir, cfg.QMPDir} {
if dir != "" {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("mkdir %s: %w", dir, err)
}
}
}
serialSock := filepath.Join(cfg.SerialDir, cfg.Name+".sock")
monitorSock := filepath.Join(cfg.MonitorDir, cfg.Name+".sock")
qmpSock := filepath.Join(cfg.QMPDir, cfg.Name+".sock")
args := []string{
"-enable-kvm",
"-cpu", "host",
"-m", fmt.Sprintf("%d", memory),
"-smp", fmt.Sprintf("%d", cpus),
"-serial", fmt.Sprintf("unix:%s,server,nowait", serialSock),
"-monitor", fmt.Sprintf("unix:%s,server,nowait", monitorSock),
"-qmp", fmt.Sprintf("unix:%s,server,nowait", qmpSock),
"-display", "none",
}
if cfg.UEFICodePath != "" && cfg.UEFIVarsPath != "" {
args = append(args,
"-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", cfg.UEFICodePath),
"-drive", fmt.Sprintf("if=pflash,format=raw,file=%s", cfg.UEFIVarsPath),
)
}
hasScsi := false
virtioBlk := 0
for _, d := range cfg.Disks {
if strings.HasPrefix(d.Dev, "sd") {
hasScsi = true
} else {
virtioBlk++
}
}
if virtioBlk > 1 {
return fmt.Errorf("qemu: %d disques virtio-blk (vd*) déclarés, un seul est supporté — utiliser des disques sd* pour les disques additionnels", virtioBlk)
}
if hasScsi {
args = append(args, "-device", "virtio-scsi-pci,id=scsi0,bus=pci.0,addr=0x1e")
}
sorted := make([]DiskConfig, len(cfg.Disks))
copy(sorted, cfg.Disks)
// vd* avant sd* : les disques virtio-blk bootent en premier.
// À lettre égale de type, ordre alphabétique.
sort.Slice(sorted, func(i, j int) bool {
iVirtio := strings.HasPrefix(sorted[i].Dev, "vd")
jVirtio := strings.HasPrefix(sorted[j].Dev, "vd")
if iVirtio != jVirtio {
return iVirtio
}
return sorted[i].Dev < sorted[j].Dev
})
for idx, d := range sorted {
bootindex := idx + 1
if strings.HasPrefix(d.Dev, "sd") {
scsiID := int(d.Dev[2] - 'a')
args = append(args,
"-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev),
"-device", fmt.Sprintf("scsi-hd,drive=%s,bus=scsi0.0,scsi-id=%d,bootindex=%d", d.Dev, scsiID, bootindex),
)
} else {
args = append(args,
"-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev),
"-device", fmt.Sprintf("virtio-blk-pci,drive=%s,bus=pci.0,addr=0x1f,bootindex=%d", d.Dev, bootindex),
)
}
}
// Slots 0x03 à 0x1d réservés au réseau par la carte PCI (#36). Le slot est
// dérivé de l'index et non laissé à QEMU : c'est lui qui fixe le nom de
// l'interface dans le guest, et un slot flottant la renomme d'un démarrage
// à l'autre.
if len(cfg.NICs) == 0 {
return fmt.Errorf("vm %s has no network interface", cfg.Name)
}
if len(cfg.NICs) > maxNICs {
return fmt.Errorf("vm %s has %d interfaces, the pci map holds %d", cfg.Name, len(cfg.NICs), maxNICs)
}
for i, n := range cfg.NICs {
args = append(args,
"-netdev", fmt.Sprintf("tap,id=net%d,ifname=tap%d,script=no,downscript=no", i, n.TapID),
"-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s,bus=pci.0,addr=0x%02x", i, n.Mac, firstNICSlot+i),
)
}
args = append(args, "-daemonize")
scopeArgs := append([]string{
"--scope",
"--unit=" + ScopeName(cfg.Name),
"--collect",
"qemu-system-x86_64",
}, args...)
cmd := exec.Command("systemd-run", scopeArgs...)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("systemd-run qemu-system-x86_64: %w: %s", err, strings.TrimSpace(string(out)))
}
return nil
}