This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.22 by this push:
new e08e66d66de kvm: use preallocation option for fat disk resize (#11986)
e08e66d66de is described below
commit e08e66d66deb43ddd7fe6e0185fe7a0fbf94255a
Author: Abhishek Kumar <[email protected]>
AuthorDate: Wed Dec 17 13:03:39 2025 +0100
kvm: use preallocation option for fat disk resize (#11986)
Signed-off-by: Abhishek Kumar <[email protected]>
---
plugins/hypervisors/kvm/pom.xml | 9 -
.../kvm/storage/LibvirtStorageAdaptor.java | 2 +-
.../org/apache/cloudstack/utils/qemu/QemuImg.java | 84 ++++++---
.../apache/cloudstack/utils/qemu/QemuImgTest.java | 207 +++++++++++++++++++--
4 files changed, 246 insertions(+), 56 deletions(-)
diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml
index 8ed960c6bc0..bc7b6903aae 100644
--- a/plugins/hypervisors/kvm/pom.xml
+++ b/plugins/hypervisors/kvm/pom.xml
@@ -107,15 +107,6 @@
</execution>
</executions>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>**/QemuImg*.java</exclude>
- </excludes>
- </configuration>
- </plugin>
</plugins>
</build>
<profiles>
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 87544cfaa9d..a03daeb197b 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 @@ public class LibvirtStorageAdaptor implements
StorageAdaptor {
destFile.setSize(size);
Map<String, String> options = new HashMap<String, String>();
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 0a8ea27cd49..1fec561dc89 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.Map;
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;
@@ -393,6 +395,17 @@ public class QemuImg {
convert(srcFile, destFile, options, qemuObjects, srcImageOpts,
snapshotName, forceSourceFormat, false);
}
+ protected Map<String, String> getResizeOptionsFromConvertOptions(final
Map<String, String> options) {
+ if (MapUtils.isEmpty(options)) {
+ return null;
+ }
+ Map<String, String> resizeOpts = new HashMap<>();
+ if (options.containsKey(PREALLOCATION)) {
+ resizeOpts.put(PREALLOCATION, options.get(PREALLOCATION));
+ }
+ return resizeOpts;
+ }
+
/**
* Converts an image from source to destination.
*
@@ -420,7 +433,6 @@ public class QemuImg {
public void convert(final QemuImgFile srcFile, final QemuImgFile destFile,
final Map<String, String> options, final
List<QemuObject> 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 +496,7 @@ public class QemuImg {
}
if (srcFile.getSize() < destFile.getSize()) {
- this.resize(destFile, destFile.getSize());
+ this.resize(destFile, destFile.getSize(),
getResizeOptionsFromConvertOptions(options));
}
}
@@ -691,17 +703,29 @@ public class QemuImg {
}
}
- private void addScriptOptionsFromMap(Map<String, String> options, Script
s) {
- if (options != null && !options.isEmpty()) {
- s.add("-o");
- final StringBuffer optionsBuffer = new StringBuffer();
- for (final Map.Entry<String, String> option : options.entrySet()) {
-
optionsBuffer.append(option.getKey()).append('=').append(option.getValue()).append(',');
- }
- String optionsStr = optionsBuffer.toString();
- optionsStr = optionsStr.replaceAll(",$", "");
- s.add(optionsStr);
+ protected void addScriptOptionsFromMap(Map<String, String> options, Script
s) {
+ if (MapUtils.isEmpty(options)) {
+ return;
}
+ s.add("-o");
+ final StringBuffer optionsBuffer = new StringBuffer();
+ for (final Map.Entry<String, String> option : options.entrySet()) {
+
optionsBuffer.append(option.getKey()).append('=').append(option.getValue()).append(',');
+ }
+ String optionsStr = optionsBuffer.toString();
+ optionsStr = optionsStr.replaceAll(",$", "");
+ s.add(optionsStr);
+ }
+
+ protected void addScriptResizeOptionsFromMap(Map<String, String> 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);
}
/**
@@ -747,19 +771,17 @@ public class QemuImg {
/**
* Resizes an image.
- *
+ * <p>
* This method is a facade for 'qemu-img resize'.
- *
+ * <p>
* 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<String,
String> 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<String, String> options) throws QemuImgException {
String newSize = null;
if (size == 0) {
@@ -781,6 +803,7 @@ public class QemuImg {
final Script s = new Script(_qemuImgPath);
s.add("resize");
+ addScriptResizeOptionsFromMap(options, s);
s.add(file.getFileName());
s.add(newSize);
s.execute();
@@ -789,7 +812,7 @@ public class QemuImg {
/**
* 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 +841,17 @@ public class QemuImg {
/**
* Resizes an image.
- *
- * This method is a facade for {@link QemuImg#resize(QemuImgFile, long,
boolean)}.
- *
+ * <p>
+ * This method is a facade for {@link QemuImg#resize(QemuImgFile, long,
boolean, Map)}.
+ * <p>
* 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<String,
String> 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<String,
String> 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 b0981dde26e..5a027425776 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,26 +22,45 @@ import static org.junit.Assert.assertTrue;
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.Assume;
+import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
-
-import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
+import org.junit.runner.RunWith;
+import org.libvirt.Connect;
import org.libvirt.LibvirtException;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
-@Ignore
+import com.cloud.utils.script.Script;
+
+@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";
@@ -130,8 +149,7 @@ public class QemuImgTest {
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<String, String> options = new HashMap<String, String>();
@@ -141,8 +159,8 @@ public class QemuImgTest {
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));
@@ -162,7 +180,7 @@ public class QemuImgTest {
try {
QemuImg qemu = new QemuImg(0);
qemu.create(file);
- qemu.resize(file, endSize);
+ qemu.resize(file, endSize, null);
Map<String, String> info = qemu.info(file);
if (info == null) {
@@ -191,7 +209,7 @@ public class QemuImgTest {
try {
QemuImg qemu = new QemuImg(0);
qemu.create(file);
- qemu.resize(file, increment, true);
+ qemu.resize(file, increment, true, null);
Map<String, String> info = qemu.info(file);
if (info == null) {
@@ -208,6 +226,9 @@ public class QemuImgTest {
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";
@@ -219,7 +240,7 @@ public class QemuImgTest {
try {
QemuImg qemu = new QemuImg(0);
qemu.create(file);
- qemu.resize(file, increment, true);
+ qemu.resize(file, increment, true, null);
Map<String, String> info = qemu.info(file);
if (info == null) {
@@ -249,7 +270,7 @@ public class QemuImgTest {
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 +286,7 @@ public class QemuImgTest {
QemuImg qemu = new QemuImg(0);
qemu.create(file);
- qemu.resize(file, 0);
+ qemu.resize(file, 0, null);
File f = new File(filename);
f.delete();
@@ -377,7 +398,7 @@ public class QemuImgTest {
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 +406,160 @@ public class QemuImgTest {
File f = new File(filename);
f.delete();
}
+
+ @Test
+ public void addScriptOptionsFromMapAddsValidOptions() throws
LibvirtException, QemuImgException {
+ Script script = Mockito.mock(Script.class);
+ Map<String, String> 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<String, String> 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<String, String> 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<String, String> options = new HashMap<>();
+
+ Map<String, String> result =
qemuImg.getResizeOptionsFromConvertOptions(options);
+
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void getResizeOptionsFromConvertOptionsReturnsNullForNullOptions()
throws LibvirtException, QemuImgException {
+ QemuImg qemuImg = new QemuImg(0);
+
+ Map<String, String> result =
qemuImg.getResizeOptionsFromConvertOptions(null);
+
+ Assert.assertNull(result);
+ }
+
+ @Test
+ public void getResizeOptionsFromConvertOptionsReturnsPreallocationOption()
throws LibvirtException, QemuImgException {
+ QemuImg qemuImg = new QemuImg(0);
+ Map<String, String> options = new HashMap<>();
+ options.put(QemuImg.PREALLOCATION, "metadata");
+
+ Map<String, String> 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<String, String> options = new HashMap<>();
+ options.put("unrelatedKey", "unrelatedValue");
+
+ Map<String, String> result =
qemuImg.getResizeOptionsFromConvertOptions(options);
+
+ Assert.assertTrue(MapUtils.isEmpty(result));
+ }
+
+ @Test
+ public void getResizeOptionsFromConvertOptionsHandlesMixedOptions() throws
LibvirtException, QemuImgException {
+ QemuImg qemuImg = new QemuImg(0);
+ Map<String, String> options = new HashMap<>();
+ options.put(QemuImg.PREALLOCATION, "full");
+ options.put("unrelatedKey", "unrelatedValue");
+
+ Map<String, String> result =
qemuImg.getResizeOptionsFromConvertOptions(options);
+
+ Assert.assertNotNull(result);
+ assertEquals(1, result.size());
+ assertEquals("full", result.get(QemuImg.PREALLOCATION));
+ }
+
+ @Test
+ public void addScriptResizeOptionsFromMapAddsPreallocationOption() throws
LibvirtException, QemuImgException {
+ Script script = Mockito.mock(Script.class);
+ Map<String, String> 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<String, String> 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<String, String> 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));
+ }
}