diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 19c2ebe455a5..b85e2e4b65fd 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -203,4 +203,6 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId, Pair checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException; Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo); + + Long getVolumeTotalPhysicalSize(Storage.ImageFormat format, String path, String chainInfo); } diff --git a/api/src/main/java/com/cloud/storage/VolumeStats.java b/api/src/main/java/com/cloud/storage/VolumeStats.java index 81fa7eabdd76..e5305203b55b 100644 --- a/api/src/main/java/com/cloud/storage/VolumeStats.java +++ b/api/src/main/java/com/cloud/storage/VolumeStats.java @@ -26,4 +26,6 @@ public interface VolumeStats { * @return bytes allocated */ long getPhysicalSize(); + + Long getTotalPhysicalSize(); } diff --git a/core/src/main/java/com/cloud/agent/api/VolumeStatsEntry.java b/core/src/main/java/com/cloud/agent/api/VolumeStatsEntry.java index fb4ecc750d04..62e7695dfea2 100644 --- a/core/src/main/java/com/cloud/agent/api/VolumeStatsEntry.java +++ b/core/src/main/java/com/cloud/agent/api/VolumeStatsEntry.java @@ -25,6 +25,7 @@ public class VolumeStatsEntry implements VolumeStats { String volumeUuid; long physicalsize = 0; long virtualSize = 0; + Long totalPhysicalSize = null; public VolumeStatsEntry(String volumeUuid, long physicalsize, long virtualSize) { this.volumeUuid = volumeUuid; @@ -56,6 +57,23 @@ public void setVirtualSize(long virtualSize) { this.virtualSize = virtualSize; } + public long getPhysicalsize() { + return physicalsize; + } + + public void setPhysicalsize(long physicalsize) { + this.physicalsize = physicalsize; + } + + @Override + public Long getTotalPhysicalSize() { + return totalPhysicalSize; + } + + public void setTotalPhysicalSize(Long totalPhysicalSize) { + this.totalPhysicalSize = totalPhysicalSize; + } + @Override public String toString() { return "VolumeStatsEntry [volumeUuid=" + volumeUuid + ", size=" + physicalsize + ", virtualSize=" + virtualSize + "]"; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumeStatsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumeStatsCommandWrapper.java index 677af99ed157..9008a8358d0f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumeStatsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumeStatsCommandWrapper.java @@ -21,10 +21,18 @@ import java.util.HashMap; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; import org.libvirt.Connect; import org.libvirt.LibvirtException; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetVolumeStatsAnswer; +import com.cloud.agent.api.GetVolumeStatsCommand; +import com.cloud.agent.api.VolumeStatsEntry; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; @@ -33,9 +41,10 @@ import com.cloud.resource.ResourceWrapper; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.agent.api.GetVolumeStatsAnswer; -import com.cloud.agent.api.GetVolumeStatsCommand; -import com.cloud.agent.api.VolumeStatsEntry; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; @ResourceWrapper(handles = GetVolumeStatsCommand.class) public final class LibvirtGetVolumeStatsCommandWrapper extends CommandWrapper { @@ -72,6 +81,61 @@ private VolumeStatsEntry getVolumeStat(final LibvirtComputingResource libvirtCom return null; } - return new VolumeStatsEntry(volumeUuid, sourceKVMVolume.getSize(), sourceKVMVolume.getVirtualSize()); + VolumeStatsEntry entry = new VolumeStatsEntry(volumeUuid, sourceKVMVolume.getSize(), + sourceKVMVolume.getVirtualSize()); + entry.setTotalPhysicalSize(retrieveTotalPhysicalSize(volumeUuid, sourceKVMVolume.getPath(), logger)); + + return entry; + } + + + private static Long retrieveTotalPhysicalSize(String volumeUuid, String volumePath, Logger logger) { + logger.trace("Retrieving total physical size for volume: {} with path: {}", volumeUuid, volumePath); + Long totalPhysicalSize = null; + try { + QemuImg qemu = new QemuImg(10000); + QemuImgFile file = new QemuImgFile(volumePath); + String info = qemu.infoBackingJson(file); + if (StringUtils.isBlank(info)) { + logger.debug("Failed to get qemu info for volume: {} as the output is empty"); + return null; + } + totalPhysicalSize = parseTotalActualSize(info, logger); + if (totalPhysicalSize != null) { + logger.trace("Total physical size for volume {} is: {}", volumeUuid, totalPhysicalSize); + } + } catch (LibvirtException | QemuImgException e) { + logger.debug("Failed to get qemu info for volume: {} due to: {}", volumeUuid, e.getMessage()); + } + return totalPhysicalSize; + } + + private static Long parseTotalActualSize(String json, Logger logger) { + JsonElement root = JsonParser.parseString(json); + + Long total = null; + + if (root.isJsonArray()) { + JsonArray arr = root.getAsJsonArray(); + for (JsonElement elem : arr) { + JsonObject obj = elem.getAsJsonObject(); + if (!obj.has("actual-size")) { + continue; + } + if (total == null) { + total = 0L; + } + total += obj.get("actual-size").getAsLong(); + } + } else if (root.isJsonObject()) { + JsonObject obj = root.getAsJsonObject(); + if (obj.has("actual-size")) { + total = obj.get("actual-size").getAsLong(); + } + } else { + logger.debug("Unexpected JSON format when parsing qemu info: {}", json); + } + + return total; } } 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 1fec561dc890..e2a9f536a731 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 @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.utils.qemu; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; @@ -28,16 +30,14 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.libvirt.LibvirtException; import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import com.cloud.storage.Storage; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.Script; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import static java.util.regex.Pattern.CASE_INSENSITIVE; public class QemuImg { private Logger logger = LogManager.getLogger(this.getClass()); @@ -661,6 +661,24 @@ public Map info(final QemuImgFile file, boolean secure) throws Q return info; } + public String infoBackingJson(final QemuImgFile file) throws QemuImgException { + final Script s = new Script(_qemuImgPath); + s.add("info"); + s.add("--backing-chain"); + s.add("--output=json"); + if (this.version >= QEMU_2_10) { + s.add("-U"); + } + s.add(file.getFileName()); + + final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + final String result = s.execute(parser); + if (result != null) { + throw new QemuImgException(result); + } + return parser.getLines().trim(); + } + /* create snapshots in image */ public void snapshot(final QemuImageOptions srcImageOpts, final String snapshotName, final List qemuObjects) throws QemuImgException { final Script s = new Script(_qemuImgPath, timeout); diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ca3d31d4fad3..d1d2ecb52fb2 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -5348,8 +5348,7 @@ private VmWorkJobVO createPlaceHolderWork(long instanceId) { return workJob; } - @Override - public Long getVolumePhysicalSize(ImageFormat format, String path, String chainInfo) { + protected VolumeStats getVolumeStats(ImageFormat format, String path, String chainInfo) { VolumeStats vs = null; if (format == ImageFormat.VHD || format == ImageFormat.QCOW2 || format == ImageFormat.RAW) { if (path != null) { @@ -5360,9 +5359,27 @@ public Long getVolumePhysicalSize(ImageFormat format, String path, String chainI vs = statsCollector.getVolumeStats(chainInfo); } } + return vs; + } + + @Override + public Long getVolumePhysicalSize(ImageFormat format, String path, String chainInfo) { + VolumeStats vs = getVolumeStats(format, path, chainInfo); return (vs == null) ? null : vs.getPhysicalSize(); } + @Override + public Long getVolumeTotalPhysicalSize(ImageFormat format, String path, String chainInfo) { + VolumeStats vs = getVolumeStats(format, path, chainInfo); + if (vs == null) { + return null; + } + if (vs.getTotalPhysicalSize() == null) { + return vs.getPhysicalSize(); + } + return vs.getTotalPhysicalSize(); + } + @Override public String getConfigComponentName() { return VolumeApiService.class.getSimpleName();