diff --git a/internal/server/instance/drivers/driver_qemu.go b/internal/server/instance/drivers/driver_qemu.go index 74f4e60d6e..d0e6a158d2 100644 --- a/internal/server/instance/drivers/driver_qemu.go +++ b/internal/server/instance/drivers/driver_qemu.go @@ -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 != "/" {