diff --git a/src/runtime/pkg/govmm/qemu/qemu.go b/src/runtime/pkg/govmm/qemu/qemu.go index c0b7d1022e..4a258ea59c 100644 --- a/src/runtime/pkg/govmm/qemu/qemu.go +++ b/src/runtime/pkg/govmm/qemu/qemu.go @@ -1378,6 +1378,9 @@ type BlockDevice struct { // ReadOnly sets the block device in readonly mode ReadOnly bool + // DiscardUnmap enables discard/unmap support for this block device. + DiscardUnmap bool + // Transport is the virtio transport for this device. Transport VirtioTransport } @@ -1425,6 +1428,9 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { if blkdev.ShareRW { deviceParams = append(deviceParams, "share-rw=on") } + if blkdev.DiscardUnmap { + deviceParams = append(deviceParams, "discard=on") + } deviceParams = append(deviceParams, fmt.Sprintf("serial=%s", blkdev.ID)) @@ -1437,6 +1443,9 @@ func (blkdev BlockDevice) QemuParams(config *Config) []string { if blkdev.ReadOnly { blkParams = append(blkParams, "readonly=on") } + if blkdev.DiscardUnmap { + blkParams = append(blkParams, "discard=unmap") + } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) diff --git a/src/runtime/pkg/govmm/qemu/qemu_test.go b/src/runtime/pkg/govmm/qemu/qemu_test.go index 1cce08bae7..54fd59ccfd 100644 --- a/src/runtime/pkg/govmm/qemu/qemu_test.go +++ b/src/runtime/pkg/govmm/qemu/qemu_test.go @@ -342,6 +342,29 @@ func TestAppendDeviceBlock(t *testing.T) { testAppend(blkdev, deviceBlockString, t) } +func TestAppendDeviceBlockDiscardUnmap(t *testing.T) { + blkdev := BlockDevice{ + Driver: VirtioBlock, + ID: "hd0", + File: "/var/lib/vm.img", + AIO: Threads, + Format: QCOW2, + Interface: NoInterface, + WCE: false, + DisableModern: true, + ROMFile: romfile, + ShareRW: true, + ReadOnly: true, + DiscardUnmap: true, + } + params := strings.Join(blkdev.QemuParams(&Config{}), " ") + for _, opt := range []string{"discard=on", "discard=unmap"} { + if !strings.Contains(params, opt) { + t.Fatalf("missing %s in block device params: %s", opt, params) + } + } +} + func TestAppendDeviceVFIO(t *testing.T) { vfioDevice := VFIODevice{ BDF: "02:10.0", diff --git a/src/runtime/pkg/govmm/qemu/qmp.go b/src/runtime/pkg/govmm/qemu/qmp.go index efe565542b..676111d8f8 100644 --- a/src/runtime/pkg/govmm/qemu/qmp.go +++ b/src/runtime/pkg/govmm/qemu/qmp.go @@ -800,6 +800,10 @@ func (q *QMP) blockdevAddBaseArgs(driver string, blockDevice *BlockDevice) map[s } blockdevArgs["node-name"] = blockDevice.ID + if blockDevice.DiscardUnmap { + blockdevArgs["discard"] = "unmap" + blockdevArgs["file"].(map[string]interface{})["discard"] = "unmap" + } return blockdevArgs } @@ -863,6 +867,16 @@ func (q *QMP) ExecuteBlockdevAddWithDriverCache(ctx context.Context, driver stri // the logical and physical block sizes for the device; if either is 0, the // hypervisor default is used for that size. func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern bool, logicalBlockSize, physicalBlockSize uint32) error { + return q.executeDeviceAdd(ctx, blockdevID, devID, driver, bus, romfile, shared, disableModern, false, logicalBlockSize, physicalBlockSize) +} + +// ExecuteDeviceAddWithDiscard is like ExecuteDeviceAdd, with explicit virtio-blk +// discard support. +func (q *QMP) ExecuteDeviceAddWithDiscard(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern, discardUnmap bool, logicalBlockSize, physicalBlockSize uint32) error { + return q.executeDeviceAdd(ctx, blockdevID, devID, driver, bus, romfile, shared, disableModern, discardUnmap, logicalBlockSize, physicalBlockSize) +} + +func (q *QMP) executeDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern, discardUnmap bool, logicalBlockSize, physicalBlockSize uint32) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -880,6 +894,9 @@ func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, b if shared { args["share-rw"] = true } + if discardUnmap && strings.HasPrefix(driver, "virtio-blk") { + args["discard"] = true + } if transport.isVirtioPCI(nil) { args["romfile"] = romfile @@ -1121,6 +1138,16 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { // 1.0 in nested environments. logicalBlockSize and physicalBlockSize specify the logical and // physical sector sizes reported to the guest; set to 0 to use the hypervisor default. func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern bool, iothreadID string, logicalBlockSize, physicalBlockSize uint32) error { + return q.executePCIDeviceAdd(ctx, blockdevID, devID, driver, addr, bus, romfile, queues, shared, disableModern, false, iothreadID, logicalBlockSize, physicalBlockSize) +} + +// ExecutePCIDeviceAddWithDiscard is like ExecutePCIDeviceAdd, with explicit +// virtio-blk discard support. +func (q *QMP) ExecutePCIDeviceAddWithDiscard(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern, discardUnmap bool, iothreadID string, logicalBlockSize, physicalBlockSize uint32) error { + return q.executePCIDeviceAdd(ctx, blockdevID, devID, driver, addr, bus, romfile, queues, shared, disableModern, discardUnmap, iothreadID, logicalBlockSize, physicalBlockSize) +} + +func (q *QMP) executePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern, discardUnmap bool, iothreadID string, logicalBlockSize, physicalBlockSize uint32) error { args := map[string]interface{}{ "id": devID, "driver": driver, @@ -1133,6 +1160,9 @@ func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver if shared { args["share-rw"] = true } + if discardUnmap && strings.HasPrefix(driver, "virtio-blk") { + args["discard"] = true + } if queues > 0 { args["num-queues"] = queues } diff --git a/src/runtime/pkg/govmm/qemu/qmp_test.go b/src/runtime/pkg/govmm/qemu/qmp_test.go index f53c64d60c..d86f89fffd 100644 --- a/src/runtime/pkg/govmm/qemu/qmp_test.go +++ b/src/runtime/pkg/govmm/qemu/qmp_test.go @@ -508,6 +508,25 @@ func TestQMPBlockdevAddWithCache(t *testing.T) { <-disconnectedCh } +func TestQMPBlockdevAddDiscardUnmapArgs(t *testing.T) { + q := &QMP{} + dev := BlockDevice{ + ID: fmt.Sprintf("drive_%s", volumeUUID), + File: "/tmp/disk.img", + AIO: Native, + DiscardUnmap: true, + } + + args := q.blockdevAddBaseArgs("file", &dev) + if got := args["discard"]; got != "unmap" { + t.Fatalf("unexpected discard option: got %v, expecting unmap", got) + } + fileArgs := args["file"].(map[string]interface{}) + if got := fileArgs["discard"]; got != "unmap" { + t.Fatalf("unexpected file discard option: got %v, expecting unmap", got) + } +} + // Checks that the netdev_add command is correctly sent. // // We start a QMPLoop, send the netdev_add command and stop the loop. diff --git a/src/runtime/virtcontainers/qemu.go b/src/runtime/virtcontainers/qemu.go index 81e6a55d90..cd4ef69224 100644 --- a/src/runtime/virtcontainers/qemu.go +++ b/src/runtime/virtcontainers/qemu.go @@ -2097,10 +2097,11 @@ func (q *qemu) hotplugAddBlockDevice(ctx context.Context, drive *config.BlockDri } qblkDevice := govmmQemu.BlockDevice{ - ID: drive.ID, - File: drive.File, - ReadOnly: drive.ReadOnly, - AIO: govmmQemu.BlockDeviceAIO(q.config.BlockDeviceAIO), + ID: drive.ID, + File: drive.File, + ReadOnly: drive.ReadOnly, + DiscardUnmap: drive.DiscardUnmap, + AIO: govmmQemu.BlockDeviceAIO(q.config.BlockDeviceAIO), } if drive.Swap { @@ -2157,7 +2158,7 @@ func (q *qemu) hotplugAddBlockDevice(ctx context.Context, drive *config.BlockDri iothreadID = fmt.Sprintf("%s_%d", indepIOThreadsPrefix, 0) } - if err = q.qmpMonitorCh.qmp.ExecutePCIDeviceAdd(q.qmpMonitorCh.ctx, drive.ID, devID, driver, addr, bridge.ID, romFile, queues, true, defaultDisableModern, iothreadID, q.config.BlockDeviceLogicalSectorSize, q.config.BlockDevicePhysicalSectorSize); err != nil { + if err = q.qmpMonitorCh.qmp.ExecutePCIDeviceAddWithDiscard(q.qmpMonitorCh.ctx, drive.ID, devID, driver, addr, bridge.ID, romFile, queues, true, defaultDisableModern, drive.DiscardUnmap, iothreadID, q.config.BlockDeviceLogicalSectorSize, q.config.BlockDevicePhysicalSectorSize); err != nil { return err } case q.config.BlockDeviceDriver == config.VirtioBlockCCW: @@ -2176,7 +2177,7 @@ func (q *qemu) hotplugAddBlockDevice(ctx context.Context, drive *config.BlockDri if err != nil { return err } - if err = q.qmpMonitorCh.qmp.ExecuteDeviceAdd(q.qmpMonitorCh.ctx, drive.ID, devID, driver, devNoHotplug, "", true, false, q.config.BlockDeviceLogicalSectorSize, q.config.BlockDevicePhysicalSectorSize); err != nil { + if err = q.qmpMonitorCh.qmp.ExecuteDeviceAddWithDiscard(q.qmpMonitorCh.ctx, drive.ID, devID, driver, devNoHotplug, "", true, false, drive.DiscardUnmap, q.config.BlockDeviceLogicalSectorSize, q.config.BlockDevicePhysicalSectorSize); err != nil { return err } case q.config.BlockDeviceDriver == config.VirtioSCSI: diff --git a/src/runtime/virtcontainers/qemu_arch_base.go b/src/runtime/virtcontainers/qemu_arch_base.go index 1ad19af1a6..6db292942b 100644 --- a/src/runtime/virtcontainers/qemu_arch_base.go +++ b/src/runtime/virtcontainers/qemu_arch_base.go @@ -692,6 +692,7 @@ func genericBlockDevice(drive config.BlockDrive, nestedRun bool) (govmmQemu.Bloc DisableModern: nestedRun, ShareRW: drive.ShareRW, ReadOnly: drive.ReadOnly, + DiscardUnmap: drive.DiscardUnmap, }, nil } diff --git a/src/runtime/virtcontainers/qemu_arch_base_test.go b/src/runtime/virtcontainers/qemu_arch_base_test.go index c838441198..2015ca76fb 100644 --- a/src/runtime/virtcontainers/qemu_arch_base_test.go +++ b/src/runtime/virtcontainers/qemu_arch_base_test.go @@ -526,6 +526,21 @@ func TestQemuArchBaseAppendBlockDevice(t *testing.T) { testQemuArchBaseAppend(t, drive, expectedOut) } +func TestGenericBlockDeviceDiscardUnmap(t *testing.T) { + assert := assert.New(t) + + drive := config.BlockDrive{ + File: "/root", + Format: "raw", + ID: "blockDevTest", + DiscardUnmap: true, + } + + blockDevice, err := genericBlockDevice(drive, false) + assert.NoError(err) + assert.True(blockDevice.DiscardUnmap) +} + func TestQemuArchBaseAppendVhostUserDevice(t *testing.T) { socketPath := "nonexistentpath.sock" macAddress := "00:11:22:33:44:55:66"