incusd/instance/qemu: Resize metadata image on disk size change

The newly introduced QCOW2 metadata file wasn't getting resized during a
VM offline resize event. This got caught by our daily VM tests.

Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
This commit is contained in:
Stéphane Graber 2026-07-23 22:42:51 -04:00
parent bf08b404f9
commit 5645b80efb
No known key found for this signature in database
GPG Key ID: C638974D64792D67

View File

@ -4684,15 +4684,30 @@ func (d *qemu) ensureMetadataImage(rawPath string, devName string) (string, stri
// If we already have metadata image, then just use it
if util.PathExists(qcow2Path) {
isQcow2, err := d.isQCOW2(qcow2Path)
imgInfo, err := storageDrivers.Qcow2Info(qcow2Path)
if err != nil {
return "", "", err
}
if !isQcow2 {
if imgInfo.Format != storageDrivers.BlockVolumeTypeQcow2 {
return "", "", fmt.Errorf("Existing metadata image %q is not qcow2", qcow2Path)
}
// Get size of disk block device.
blockDiskSize, err := storageDrivers.BlockDiskSizeBytes(rawPath)
if err != nil {
return "", "", fmt.Errorf("Error getting block device size %q: %w", rawPath, err)
}
// Keep the metadata image in sync with the disk size as the volume
// may have been resized while the instance was stopped.
if int64(imgInfo.VirtualSize) != blockDiskSize {
err = d.resizeMetadataImage(qcow2Path, rawPath, blockDiskSize, int64(imgInfo.VirtualSize))
if err != nil {
return "", "", err
}
}
return qcow2Path, rawPath, nil
}
@ -4734,6 +4749,40 @@ func (d *qemu) ensureMetadataImage(rawPath string, devName string) (string, stri
return qcow2Path, rawPath, nil
}
// resizeMetadataImage resizes the qcow2 metadata image to match the raw disk size.
// The data-file has to be overridden as the one recorded in the image no longer exists.
func (d *qemu) resizeMetadataImage(qcow2Path string, rawPath string, newSize int64, oldSize int64) error {
fInfo, err := os.Stat(rawPath)
if err != nil {
return err
}
rawDriver := "file"
if linux.IsBlockdev(fInfo.Mode()) {
rawDriver = "host_device"
}
escape := func(s string) string {
return strings.ReplaceAll(s, ",", ",,")
}
args := []string{"resize"}
if newSize < oldSize {
args = append(args, "--shrink")
} else {
args = append(args, "--preallocation=metadata")
}
args = append(args, "--image-opts", fmt.Sprintf("driver=qcow2,file.filename=%s,data-file.driver=%s,data-file.filename=%s", escape(qcow2Path), rawDriver, escape(rawPath)), fmt.Sprintf("%d", newSize))
_, err = subprocess.RunCommand("qemu-img", args...)
if err != nil {
return fmt.Errorf("Failed resizing qcow2 metadata image %q: %w", qcow2Path, err)
}
return nil
}
// addRootDriveConfig adds the qemu config required for adding the root drive.
func (d *qemu) addRootDriveConfig(qemuDev map[string]any, mountInfo *storagePools.MountInfo, bootIndexes map[string]int, rootDriveConf deviceConfig.MountEntryItem) (monitorHook, error) {
if rootDriveConf.TargetPath != "/" {