From 2c9471efca04ae59cbef8720280ea0ae69c4c993 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 5 Nov 2025 17:23:23 +0530 Subject: [PATCH 1/4] kvm: use preallocation option for fat disk resize Fixes #10589 Signed-off-by: Abhishek Kumar --- .../kvm/storage/LibvirtStorageAdaptor.java | 2 +- .../apache/cloudstack/utils/qemu/QemuImg.java | 64 ++++++++++--------- .../cloudstack/utils/qemu/QemuImgTest.java | 10 +-- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index e8924ecf5ebc..860c78f71663 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -1081,7 +1081,7 @@ private KVMPhysicalDisk createPhysicalDiskByQemuImg(String name, KVMStoragePool destFile.setSize(size); Map options = new HashMap(); if (List.of(StoragePoolType.NetworkFilesystem, StoragePoolType.Filesystem).contains(pool.getType())) { - options.put("preallocation", QemuImg.PreallocationType.getPreallocationType(provisioningType).toString()); + options.put(QemuImg.PREALLOCATION, QemuImg.PreallocationType.getPreallocationType(provisioningType).toString()); } try (KeyFile keyFile = new KeyFile(passphrase)) { diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index 0a8ea27cd490..6ee1e2d01e9c 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -25,6 +25,7 @@ import java.util.regex.Pattern; import org.apache.cloudstack.storage.formatinspector.Qcow2Inspector; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.libvirt.LibvirtException; @@ -51,6 +52,7 @@ public class QemuImg { public static final String ENCRYPT_FORMAT = "encrypt.format"; public static final String ENCRYPT_KEY_SECRET = "encrypt.key-secret"; public static final String TARGET_ZERO_FLAG = "--target-is-zero"; + public static final String PREALLOCATION = "preallocation"; public static final long QEMU_2_10 = 2010000; public static final long QEMU_5_10 = 5010000; @@ -420,7 +422,6 @@ public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, final Map options, final List qemuObjects, final QemuImageOptions srcImageOpts, final String snapshotName, final boolean forceSourceFormat, boolean keepBitmaps) throws QemuImgException { - Script script = new Script(_qemuImgPath, timeout); if (StringUtils.isNotBlank(snapshotName)) { String qemuPath = Script.runSimpleBashScript(getQemuImgPathScript); @@ -484,7 +485,11 @@ public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, } if (srcFile.getSize() < destFile.getSize()) { - this.resize(destFile, destFile.getSize()); + Map resizeOpts = new HashMap<>(); + if (options.containsKey(PREALLOCATION)) { + resizeOpts.put(PREALLOCATION, options.get(PREALLOCATION)); + } + this.resize(destFile, destFile.getSize(), resizeOpts); } } @@ -692,16 +697,17 @@ public void deleteSnapshot(final QemuImageOptions srcImageOpts, final String sna } private void addScriptOptionsFromMap(Map options, Script s) { - if (options != null && !options.isEmpty()) { - s.add("-o"); - final StringBuffer optionsBuffer = new StringBuffer(); - for (final Map.Entry option : options.entrySet()) { - optionsBuffer.append(option.getKey()).append('=').append(option.getValue()).append(','); - } - String optionsStr = optionsBuffer.toString(); - optionsStr = optionsStr.replaceAll(",$", ""); - s.add(optionsStr); + if (MapUtils.isEmpty(options)) { + return; } + s.add("-o"); + final StringBuffer optionsBuffer = new StringBuffer(); + for (final Map.Entry option : options.entrySet()) { + optionsBuffer.append(option.getKey()).append('=').append(option.getValue()).append(','); + } + String optionsStr = optionsBuffer.toString(); + optionsStr = optionsStr.replaceAll(",$", ""); + s.add(optionsStr); } /** @@ -747,19 +753,17 @@ public void rebase(final QemuImgFile file, final QemuImgFile backingFile, final /** * Resizes an image. - * + *

* This method is a facade for 'qemu-img resize'. - * + *

* A negative size value will get prefixed with '-' and a positive with '+'. Sizes are in bytes and will be passed on that way. * - * @param file - * The file to be resized. - * @param size - * The new size. - * @param delta - * Flag to inform if the new size is a delta. + * @param file The file to be resized. + * @param size The new size. + * @param delta Flag to inform if the new size is a delta. + * @param options Script options for resizing. Takes a Map with key value */ - public void resize(final QemuImgFile file, final long size, final boolean delta) throws QemuImgException { + public void resize(final QemuImgFile file, final long size, final boolean delta, Map options) throws QemuImgException { String newSize = null; if (size == 0) { @@ -781,6 +785,7 @@ public void resize(final QemuImgFile file, final long size, final boolean delta) final Script s = new Script(_qemuImgPath); s.add("resize"); + addScriptOptionsFromMap(options, s); s.add(file.getFileName()); s.add(newSize); s.execute(); @@ -789,7 +794,7 @@ public void resize(final QemuImgFile file, final long size, final boolean delta) /** * Resizes an image. * - * This method is a facade for {@link QemuImg#resize(QemuImgFile, long, boolean)}. + * This method is a facade for {@link QemuImg#resize(QemuImgFile, long, boolean, Map)}. * * A negative size value will get prefixed with - and a positive with +. Sizes are in bytes and will be passed on that way. * @@ -818,18 +823,17 @@ public void resize(final QemuImageOptions imageOptions, final List q /** * Resizes an image. - * - * This method is a facade for {@link QemuImg#resize(QemuImgFile, long, boolean)}. - * + *

+ * This method is a facade for {@link QemuImg#resize(QemuImgFile, long, boolean, Map)}. + *

* A negative size value will get prefixed with - and a positive with +. Sizes are in bytes and will be passed on that way. * - * @param file - * The file to be resized. - * @param size - * The new size. + * @param file The file to be resized. + * @param size The new size. + * @param options Script options for resizing. Takes a Map with key value */ - public void resize(final QemuImgFile file, final long size) throws QemuImgException { - this.resize(file, size, false); + public void resize(final QemuImgFile file, final long size, Map options) throws QemuImgException { + this.resize(file, size, false, options); } /** diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index b0981dde26e7..4b530ad232c1 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -162,7 +162,7 @@ public void testCreateAndResize() throws QemuImgException, LibvirtException { try { QemuImg qemu = new QemuImg(0); qemu.create(file); - qemu.resize(file, endSize); + qemu.resize(file, endSize, null); Map info = qemu.info(file); if (info == null) { @@ -191,7 +191,7 @@ public void testCreateAndResizeDeltaPositive() throws QemuImgException, LibvirtE try { QemuImg qemu = new QemuImg(0); qemu.create(file); - qemu.resize(file, increment, true); + qemu.resize(file, increment, true, null); Map info = qemu.info(file); if (info == null) { @@ -219,7 +219,7 @@ public void testCreateAndResizeDeltaNegative() throws QemuImgException, LibvirtE try { QemuImg qemu = new QemuImg(0); qemu.create(file); - qemu.resize(file, increment, true); + qemu.resize(file, increment, true, null); Map info = qemu.info(file); if (info == null) { @@ -249,7 +249,7 @@ public void testCreateAndResizeFail() throws QemuImgException, LibvirtException QemuImg qemu = new QemuImg(0); try { qemu.create(file); - qemu.resize(file, endSize); + qemu.resize(file, endSize, null); } finally { File f = new File(filename); f.delete(); @@ -265,7 +265,7 @@ public void testCreateAndResizeZero() throws QemuImgException, LibvirtException QemuImg qemu = new QemuImg(0); qemu.create(file); - qemu.resize(file, 0); + qemu.resize(file, 0, null); File f = new File(filename); f.delete(); From aaadbb661749756983772b7d96cf52e87f78d00c Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 6 Nov 2025 10:11:35 +0530 Subject: [PATCH 2/4] fix tests Signed-off-by: Abhishek Kumar --- .../apache/cloudstack/utils/qemu/QemuImg.java | 19 ++- .../cloudstack/utils/qemu/QemuImgTest.java | 131 ++++++++++++++++-- 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index 6ee1e2d01e9c..bf0002e7a71b 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -395,6 +395,17 @@ public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, convert(srcFile, destFile, options, qemuObjects, srcImageOpts, snapshotName, forceSourceFormat, false); } + protected Map getResizeOptionsFromConvertOptions(final Map options) { + if (MapUtils.isEmpty(options)) { + return null; + } + Map resizeOpts = new HashMap<>(); + if (options.containsKey(PREALLOCATION)) { + resizeOpts.put(PREALLOCATION, options.get(PREALLOCATION)); + } + return resizeOpts; + } + /** * Converts an image from source to destination. * @@ -485,11 +496,7 @@ public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, } if (srcFile.getSize() < destFile.getSize()) { - Map resizeOpts = new HashMap<>(); - if (options.containsKey(PREALLOCATION)) { - resizeOpts.put(PREALLOCATION, options.get(PREALLOCATION)); - } - this.resize(destFile, destFile.getSize(), resizeOpts); + this.resize(destFile, destFile.getSize(), getResizeOptionsFromConvertOptions(options)); } } @@ -696,7 +703,7 @@ public void deleteSnapshot(final QemuImageOptions srcImageOpts, final String sna } } - private void addScriptOptionsFromMap(Map options, Script s) { + protected void addScriptOptionsFromMap(Map options, Script s) { if (MapUtils.isEmpty(options)) { return; } diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index 4b530ad232c1..ee738afe1cbc 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -22,24 +22,28 @@ import static org.junit.Assert.fail; import java.io.File; -import com.cloud.utils.script.Script; - import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; +import org.apache.commons.collections.MapUtils; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; - -import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; +import org.junit.runner.RunWith; import org.libvirt.LibvirtException; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.script.Script; -@Ignore +@RunWith(MockitoJUnitRunner.class) public class QemuImgTest { @Test @@ -130,8 +134,7 @@ public void testCreateWithSecretObject() throws QemuImgException, LibvirtExcepti public void testCreateSparseVolume() throws QemuImgException, LibvirtException { String filename = "/tmp/" + UUID.randomUUID() + ".qcow2"; - /* 10TB virtual_size */ - long size = 10995116277760l; + long size = 10 * 1024 * 1024L; QemuImgFile file = new QemuImgFile(filename, size, PhysicalDiskFormat.QCOW2); String preallocation = "metadata"; Map options = new HashMap(); @@ -141,8 +144,8 @@ public void testCreateSparseVolume() throws QemuImgException, LibvirtException { QemuImg qemu = new QemuImg(0); qemu.create(file, options); - String allocatedSize = Script.runSimpleBashScript(String.format("ls -alhs %s | awk '{print $1}'", file)); - String declaredSize = Script.runSimpleBashScript(String.format("ls -alhs %s | awk '{print $6}'", file)); + String allocatedSize = Script.runSimpleBashScript(String.format("ls -alhs %s | awk '{print $1}'", filename)); + String declaredSize = Script.runSimpleBashScript(String.format("ls -alhs %s | awk '{print $6}'", filename)); assertFalse(allocatedSize.equals(declaredSize)); @@ -208,6 +211,9 @@ public void testCreateAndResizeDeltaPositive() throws QemuImgException, LibvirtE f.delete(); } + // This test is failing and needs changes in QemuImg.resize to support shrinking images with delta sizes. + // Earlier whole test suite was ignored, now only this test is ignored to allow other tests to run. + @Ignore @Test public void testCreateAndResizeDeltaNegative() throws QemuImgException, LibvirtException { String filename = "/tmp/" + UUID.randomUUID() + ".qcow2"; @@ -377,7 +383,7 @@ public void testCheckAndRepair() throws LibvirtException { try { QemuImg qemu = new QemuImg(0); - qemu.checkAndRepair(file, null, null, null); + qemu.checkAndRepair(file, new QemuImageOptions(Collections.emptyMap()), Collections.emptyList(), null); } catch (QemuImgException e) { fail(e.getMessage()); } @@ -385,4 +391,109 @@ public void testCheckAndRepair() throws LibvirtException { File f = new File(filename); f.delete(); } + + @Test + public void addScriptOptionsFromMapAddsValidOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + options.put("key1", "value1"); + options.put("key2", "value2"); + + QemuImg qemu = new QemuImg(0); + qemu.addScriptOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.times(1)).add("-o"); + Mockito.verify(script, Mockito.times(1)).add("key1=value1,key2=value2"); + } + + @Test + public void addScriptOptionsFromMapHandlesEmptyOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + + QemuImg qemu = new QemuImg(0); + qemu.addScriptOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.never()).add(Mockito.anyString()); + } + + @Test + public void addScriptOptionsFromMapHandlesNullOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + + QemuImg qemu = new QemuImg(0); + qemu.addScriptOptionsFromMap(null, script); + + Mockito.verify(script, Mockito.never()).add(Mockito.anyString()); + } + + @Test + public void addScriptOptionsFromMapHandlesTrailingComma() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + options.put("key1", "value1"); + + QemuImg qemu = new QemuImg(0); + qemu.addScriptOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.times(1)).add("-o"); + Mockito.verify(script, Mockito.times(1)).add("key1=value1"); + } + + @Test + public void getResizeOptionsFromConvertOptionsReturnsNullForEmptyOptions() throws LibvirtException, QemuImgException { + QemuImg qemuImg = new QemuImg(0); + Map options = new HashMap<>(); + + Map result = qemuImg.getResizeOptionsFromConvertOptions(options); + + Assert.assertNull(result); + } + + @Test + public void getResizeOptionsFromConvertOptionsReturnsNullForNullOptions() throws LibvirtException, QemuImgException { + QemuImg qemuImg = new QemuImg(0); + + Map result = qemuImg.getResizeOptionsFromConvertOptions(null); + + Assert.assertNull(result); + } + + @Test + public void getResizeOptionsFromConvertOptionsReturnsPreallocationOption() throws LibvirtException, QemuImgException { + QemuImg qemuImg = new QemuImg(0); + Map options = new HashMap<>(); + options.put(QemuImg.PREALLOCATION, "metadata"); + + Map result = qemuImg.getResizeOptionsFromConvertOptions(options); + + Assert.assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("metadata", result.get(QemuImg.PREALLOCATION)); + } + + @Test + public void getResizeOptionsFromConvertOptionsIgnoresUnrelatedOptions() throws LibvirtException, QemuImgException { + QemuImg qemuImg = new QemuImg(0); + Map options = new HashMap<>(); + options.put("unrelatedKey", "unrelatedValue"); + + Map result = qemuImg.getResizeOptionsFromConvertOptions(options); + + Assert.assertTrue(MapUtils.isEmpty(result)); + } + + @Test + public void getResizeOptionsFromConvertOptionsHandlesMixedOptions() throws LibvirtException, QemuImgException { + QemuImg qemuImg = new QemuImg(0); + Map options = new HashMap<>(); + options.put(QemuImg.PREALLOCATION, "full"); + options.put("unrelatedKey", "unrelatedValue"); + + Map result = qemuImg.getResizeOptionsFromConvertOptions(options); + + Assert.assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("full", result.get(QemuImg.PREALLOCATION)); + } } From 91a27df5393934778c529de373159cfd9c45ce9c Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 12 Nov 2025 18:20:08 +0530 Subject: [PATCH 3/4] skip tests conditionally Signed-off-by: Abhishek Kumar --- plugins/hypervisors/kvm/pom.xml | 9 --------- .../apache/cloudstack/utils/qemu/QemuImgTest.java | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index 8ed960c6bc0c..bc7b6903aae7 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -107,15 +107,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - **/QemuImg*.java - - - diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index ee738afe1cbc..f39628b4af49 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -34,9 +34,12 @@ import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import org.apache.commons.collections.MapUtils; import org.junit.Assert; +import org.junit.Assume; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.libvirt.Connect; import org.libvirt.LibvirtException; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @@ -46,6 +49,18 @@ @RunWith(MockitoJUnitRunner.class) public class QemuImgTest { + @BeforeClass + public static void setUp() { + Assume.assumeTrue("qemu-img not found", Script.runSimpleBashScript("command -v qemu-img") != null); + boolean libVirtAvailable = false; + try { + Connect conn = new Connect("qemu:///system", false); + conn.getVersion(); + libVirtAvailable = true; + } catch (LibvirtException ignored) {} + Assume.assumeTrue("libvirt not available", libVirtAvailable); + } + @Test public void testCreateAndInfo() throws QemuImgException, LibvirtException { String filename = "/tmp/" + UUID.randomUUID() + ".qcow2"; From 5934307a973671d6670984589861bfd52866063f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 12 Dec 2025 16:24:54 +0530 Subject: [PATCH 4/4] fix Signed-off-by: Abhishek Kumar --- .../apache/cloudstack/utils/qemu/QemuImg.java | 13 ++++- .../cloudstack/utils/qemu/QemuImgTest.java | 51 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index bf0002e7a71b..1fec561dc890 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -717,6 +717,17 @@ protected void addScriptOptionsFromMap(Map options, Script s) { s.add(optionsStr); } + protected void addScriptResizeOptionsFromMap(Map options, Script s) { + if (MapUtils.isEmpty(options)) { + return; + } + if (options.containsKey(PREALLOCATION)) { + s.add(String.format("--%s=%s", PREALLOCATION, options.get(PREALLOCATION))); + options.remove(PREALLOCATION); + } + addScriptOptionsFromMap(options, s); + } + /** * Rebases the backing file of the image. * @@ -792,7 +803,7 @@ public void resize(final QemuImgFile file, final long size, final boolean delta, final Script s = new Script(_qemuImgPath); s.add("resize"); - addScriptOptionsFromMap(options, s); + addScriptResizeOptionsFromMap(options, s); s.add(file.getFileName()); s.add(newSize); s.execute(); diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index f39628b4af49..5a0274257764 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -511,4 +511,55 @@ public void getResizeOptionsFromConvertOptionsHandlesMixedOptions() throws Libvi assertEquals(1, result.size()); assertEquals("full", result.get(QemuImg.PREALLOCATION)); } + + @Test + public void addScriptResizeOptionsFromMapAddsPreallocationOption() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + options.put(QemuImg.PREALLOCATION, "metadata"); + + QemuImg qemuImg = new QemuImg(0); + qemuImg.addScriptResizeOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.times(1)).add("--preallocation=metadata"); + Mockito.verify(script, Mockito.never()).add("-o"); + assertTrue(options.isEmpty()); + } + + @Test + public void addScriptResizeOptionsFromMapHandlesEmptyOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + + QemuImg qemuImg = new QemuImg(0); + qemuImg.addScriptResizeOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.never()).add(Mockito.anyString()); + } + + @Test + public void addScriptResizeOptionsFromMapHandlesNullOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + + QemuImg qemuImg = new QemuImg(0); + qemuImg.addScriptResizeOptionsFromMap(null, script); + + Mockito.verify(script, Mockito.never()).add(Mockito.anyString()); + } + + @Test + public void addScriptResizeOptionsFromMapHandlesMixedOptions() throws LibvirtException, QemuImgException { + Script script = Mockito.mock(Script.class); + Map options = new HashMap<>(); + options.put(QemuImg.PREALLOCATION, "full"); + options.put("key", "value"); + + QemuImg qemuImg = new QemuImg(0); + qemuImg.addScriptResizeOptionsFromMap(options, script); + + Mockito.verify(script, Mockito.times(1)).add("--preallocation=full"); + Mockito.verify(script, Mockito.times(1)).add("-o"); + Mockito.verify(script, Mockito.times(1)).add("key=value"); + assertFalse(options.containsKey(QemuImg.PREALLOCATION)); + } }