clear qcow on rebuild, update fs mounting

This commit is contained in:
Joshua Bell 2026-03-09 23:24:19 -05:00
parent cbe9b7241a
commit 08236f04a0
4 changed files with 101 additions and 15 deletions

View file

@ -94,12 +94,12 @@ func (c *Client) AddChardev(id, socketPath string) mo.Result[struct{}] {
return mo.Ok(struct{}{})
}
// AddVhostUserFsDevice adds a vhost-user-fs-pci device connected to a chardev.
// This is the second step of hot-mounting a filesystem.
func (c *Client) AddVhostUserFsDevice(chardevID, tag string) mo.Result[struct{}] {
// AddVhostUserFsDevice adds a vhost-user-fs-pci device connected to a chardev,
// targeting a hotplug-capable PCIe root port bus.
func (c *Client) AddVhostUserFsDevice(chardevID, tag, bus string) mo.Result[struct{}] {
// Device ID must be unique, derive from chardev ID
deviceID := "dev_" + chardevID
cmd := fmt.Sprintf(`{"execute":"device_add","arguments":{"driver":"vhost-user-fs-pci","chardev":"%s","tag":"%s","id":"%s","queue-size":1024}}`, chardevID, tag, deviceID)
cmd := fmt.Sprintf(`{"execute":"device_add","arguments":{"driver":"vhost-user-fs-pci","chardev":"%s","tag":"%s","id":"%s","queue-size":1024,"bus":"%s"}}`, chardevID, tag, deviceID, bus)
raw, err := c.monitor.Run([]byte(cmd))
if err != nil {
return mo.Err[struct{}](fmt.Errorf("failed to add vhost-user-fs device for %s: %w", tag, err))
@ -116,9 +116,67 @@ func (c *Client) AddVhostUserFsDevice(chardevID, tag string) mo.Result[struct{}]
return mo.Ok(struct{}{})
}
// FindFreeHotplugBus queries PCI devices and returns the first hotplug root port
// that has no child devices attached. Returns an error if all slots are occupied.
func (c *Client) FindFreeHotplugBus(busPrefix string, slotCount int) mo.Result[string] {
// Query all PCI devices to find which buses are occupied
cmd := []byte(`{"execute":"query-pci"}`)
raw, err := c.monitor.Run(cmd)
if err != nil {
return mo.Err[string](fmt.Errorf("failed to query PCI devices: %w", err))
}
// Parse to find which hotplug buses have devices
var resp struct {
Return []struct {
Devices []struct {
Bus int `json:"bus"`
QdevID string `json:"qdev_id"`
PCIBridge *struct {
Bus struct {
Number int `json:"number"`
} `json:"bus"`
Devices []json.RawMessage `json:"devices"`
} `json:"pci_bridge"`
} `json:"devices"`
} `json:"return"`
}
if err := json.Unmarshal(raw, &resp); err != nil {
return mo.Err[string](fmt.Errorf("failed to parse PCI response: %w", err))
}
// Build set of occupied hotplug buses
occupied := make(map[string]bool)
for _, bus := range resp.Return {
for _, dev := range bus.Devices {
if dev.PCIBridge != nil && len(dev.PCIBridge.Devices) > 0 {
occupied[dev.QdevID] = true
}
}
}
// Find first free hotplug slot
for i := 0; i < slotCount; i++ {
busID := fmt.Sprintf("%s%d", busPrefix, i)
if !occupied[busID] {
return mo.Ok(busID)
}
}
return mo.Err[string](fmt.Errorf("all %d hotplug slots are occupied", slotCount))
}
// HotMountFilesystem performs a complete hot-mount of a virtiofsd filesystem.
// Requires the virtiofsd daemon to already be running and listening on socketPath.
func (c *Client) HotMountFilesystem(tag, socketPath string) mo.Result[struct{}] {
// Finds a free PCIe hotplug root port and attaches the device there.
func (c *Client) HotMountFilesystem(tag, socketPath, busPrefix string, slotCount int) mo.Result[struct{}] {
// Find a free hotplug bus
busResult := c.FindFreeHotplugBus(busPrefix, slotCount)
if busResult.IsError() {
return mo.Err[struct{}](busResult.Error())
}
bus := busResult.MustGet()
// Use tag as chardev ID for simplicity
chardevID := tag
@ -127,8 +185,8 @@ func (c *Client) HotMountFilesystem(tag, socketPath string) mo.Result[struct{}]
return result
}
// Step 2: Add device
if result := c.AddVhostUserFsDevice(chardevID, tag); result.IsError() {
// Step 2: Add device on the hotplug bus
if result := c.AddVhostUserFsDevice(chardevID, tag, bus); result.IsError() {
return result
}