From b36cef06397cbbc638fdd6dacc576a41d3f7480c Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:52:19 +0900 Subject: [PATCH 1/3] =?UTF-8?q?guest-get-fsinfo/NIC/QGA=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EA=B0=81=EA=B0=81=20?= =?UTF-8?q?=EB=8F=85=EB=A6=BD=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=B4,=20=EC=9D=BC=EB=B6=80?= =?UTF-8?q?=20=EC=8B=A4=ED=8C=A8=ED=95=B4=EB=8F=84=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EC=88=98=EC=A7=91=EB=90=9C=20VM=20stats=EB=8A=94=20=EA=B3=84?= =?UTF-8?q?=EC=86=8D=20=EB=B0=98=ED=99=98=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resource/LibvirtComputingResource.java | 128 ++++++++++-------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index bd371ceacecf..1a65f81a6633 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -5093,82 +5093,91 @@ public VmStatsEntry getVmStat(final Connect conn, final String vmName) throws Li String mergeCommand = String.format("virsh qemu-agent-command %s '{\"execute\":\"guest-info\"}'", vmName); String result = Script.runSimpleBashScript(mergeCommand); - if (result != null) { - qemuAgentVersion = new JsonParser().parse(result).getAsJsonObject().get("return").getAsJsonObject().get("version").getAsString(); - metrics.setQemuAgentVersion(qemuAgentVersion); - - result = dm.qemuAgentCommand(QemuCommand.buildQemuCommand(QemuCommand.AGENT_NETWORK_GET_INTERFACES, null), 2, 0); - if (result != null && !(result.startsWith("error"))) { - LOGGER.debug(dm.getName() +" >> " + result); - JsonArray arrData = (JsonArray) new JsonParser().parse(result).getAsJsonObject().get("return"); - for (JsonElement je : arrData) { - JsonElement nicName = je.getAsJsonObject().get("name") == null ? null : je.getAsJsonObject().get("name"); - JsonElement nicAddrs = je.getAsJsonObject().get("ip-addresses") == null ? null : je.getAsJsonObject().get("ip-addresses"); - if(nicName == null || "lo".equals(nicName.getAsString())) { - continue; - } else { - if (nicAddrs == null){ + if (StringUtils.isNotBlank(result) && !(result.startsWith("error"))) { + try { + qemuAgentVersion = new JsonParser().parse(result).getAsJsonObject().get("return").getAsJsonObject().get("version").getAsString(); + metrics.setQemuAgentVersion(qemuAgentVersion); + } catch (Exception e) { + LOGGER.warn("Failed to parse qemu guest-info result for VM [{}], keeping default qemu agent version.", vmName, e); + } + + try { + result = dm.qemuAgentCommand(QemuCommand.buildQemuCommand(QemuCommand.AGENT_NETWORK_GET_INTERFACES, null), 2, 0); + if (StringUtils.isNotBlank(result) && !(result.startsWith("error"))) { + LOGGER.debug(dm.getName() + " >> " + result); + JsonArray arrData = (JsonArray) new JsonParser().parse(result).getAsJsonObject().get("return"); + for (JsonElement je : arrData) { + JsonElement nicName = je.getAsJsonObject().get("name") == null ? null : je.getAsJsonObject().get("name"); + JsonElement nicAddrs = je.getAsJsonObject().get("ip-addresses") == null ? null : je.getAsJsonObject().get("ip-addresses"); + if (nicName == null || "lo".equals(nicName.getAsString())) { continue; } else { - JsonElement nicMac = je.getAsJsonObject().get("hardware-address") == null ? null : je.getAsJsonObject().get("hardware-address"); - if (nicMac == null) { + if (nicAddrs == null) { continue; } else { - JsonArray arrData2 = (JsonArray) je.getAsJsonObject().get("ip-addresses"); - for (JsonElement je2 : arrData2) { - JsonElement nicAddrIp = je2.getAsJsonObject().get("ip-address") == null ? null : je2.getAsJsonObject().get("ip-address"); - JsonElement nicAddrIpType = je2.getAsJsonObject().get("ip-address-type") == null ? null : je2.getAsJsonObject().get("ip-address-type"); - if(nicAddrIp == null || nicAddrIpType== null || !"ipv4".equals(nicAddrIpType.getAsString())) { - continue; - } else { - nicAddrMap.put(nicMac.getAsString(), nicAddrIp.getAsString()); + JsonElement nicMac = je.getAsJsonObject().get("hardware-address") == null ? null : je.getAsJsonObject().get("hardware-address"); + if (nicMac == null) { + continue; + } else { + JsonArray arrData2 = (JsonArray) je.getAsJsonObject().get("ip-addresses"); + for (JsonElement je2 : arrData2) { + JsonElement nicAddrIp = je2.getAsJsonObject().get("ip-address") == null ? null : je2.getAsJsonObject().get("ip-address"); + JsonElement nicAddrIpType = je2.getAsJsonObject().get("ip-address-type") == null ? null : je2.getAsJsonObject().get("ip-address-type"); + if (nicAddrIp == null || nicAddrIpType == null || !"ipv4".equals(nicAddrIpType.getAsString())) { + continue; + } else { + nicAddrMap.put(nicMac.getAsString(), nicAddrIp.getAsString()); + } } } } } } + metrics.setNicAddrMap(nicAddrMap); } - metrics.setNicAddrMap(nicAddrMap); + } catch (Exception e) { + LOGGER.warn("Failed to fetch guest network interfaces for VM [{}], continuing without NIC address stats.", vmName, e); } - result = dm.qemuAgentCommand(QemuCommand.buildQemuCommand(QemuCommand.AGENT_GET_FSINFO, null), 2, 0); - if (result != null && !(result.startsWith("error"))) { - // logger.debug(dm.getName() + " >> " + result); - - JsonArray arrData = (JsonArray) new JsonParser().parse(result).getAsJsonObject().get("return"); - - for (JsonElement je : arrData) { - JsonObject jsonObj = je.getAsJsonObject(); - JsonElement diskInfo = jsonObj.get("disk"); - - if (diskInfo != null && diskInfo.isJsonArray()) { - for (JsonElement diskElement : diskInfo.getAsJsonArray()) { - // Capacity used by disk file system - JsonObject diskObj = diskElement.getAsJsonObject(); - - JsonElement serialElement = diskObj.get("serial"); - JsonElement usedFsBytesElement = jsonObj.get("used-bytes"); - if (serialElement == null || serialElement.isJsonNull() || usedFsBytesElement == null || usedFsBytesElement.isJsonNull()) { - continue; - } + try { + result = dm.qemuAgentCommand(QemuCommand.buildQemuCommand(QemuCommand.AGENT_GET_FSINFO, null), 2, 0); + if (StringUtils.isNotBlank(result) && !(result.startsWith("error"))) { + JsonArray arrData = (JsonArray) new JsonParser().parse(result).getAsJsonObject().get("return"); + + for (JsonElement je : arrData) { + JsonObject jsonObj = je.getAsJsonObject(); + JsonElement diskInfo = jsonObj.get("disk"); + + if (diskInfo != null && diskInfo.isJsonArray()) { + for (JsonElement diskElement : diskInfo.getAsJsonArray()) { + // Capacity used by disk file system + JsonObject diskObj = diskElement.getAsJsonObject(); + + JsonElement serialElement = diskObj.get("serial"); + JsonElement usedFsBytesElement = jsonObj.get("used-bytes"); + if (serialElement == null || serialElement.isJsonNull() || usedFsBytesElement == null || usedFsBytesElement.isJsonNull()) { + continue; + } - String serial = diskObj.get("serial").getAsString(); - long usedFsBytes = usedFsBytesElement.getAsLong(); - if (serial.length() >= 20) { - //serial to half path uuid - String serial_val = serial.substring(serial.length() - 20); - String serial_uuid = serial_val.substring(0, 8) + "-" - + serial_val.substring(8, 12) + "-" - + serial_val.substring(12, 16) + "-" - + serial_val.substring(16, 20); - serial = serial_uuid; - System.out.println(serial); + String serial = diskObj.get("serial").getAsString(); + long usedFsBytes = usedFsBytesElement.getAsLong(); + if (serial.length() >= 20) { + // serial to half path uuid + String serialVal = serial.substring(serial.length() - 20); + String serialUuid = serialVal.substring(0, 8) + "-" + + serialVal.substring(8, 12) + "-" + + serialVal.substring(12, 16) + "-" + + serialVal.substring(16, 20); + serial = serialUuid; + } + fsUsageMap.put(serial, fsUsageMap.getOrDefault(serial, 0L) + usedFsBytes); } - fsUsageMap.put(serial, fsUsageMap.getOrDefault(serial, 0L) + usedFsBytes); } } + metrics.setFsUsageMap(fsUsageMap); } - metrics.setFsUsageMap(fsUsageMap); + } catch (Exception e) { + LOGGER.warn("Failed to fetch guest filesystem info for VM [{}], continuing without filesystem usage stats.", vmName, e); } } @@ -6451,4 +6460,3 @@ public void createRBDSecretKeyFileIfNoExist(String uuid, String localPath, Strin } catch (IOException e) {} } } - From d4268ca625579271e2dc34726d971dad1b85974a Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:53:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?root=20=EB=B3=BC=EB=A5=A8=EC=9D=B4=20"alloc?= =?UTF-8?q?ated=20=EC=83=81=ED=83=9C"=20&=20"=EA=B0=80=EC=83=81=EB=A8=B8?= =?UTF-8?q?=EC=8B=A0=20=EB=AF=B8=EC=97=B0=EA=B2=B0"=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=9D=B4=EB=A9=B4=20=EC=82=AD=EC=A0=9C=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=B4=20=ED=99=9C=EC=84=B1=ED=99=94=20=EB=90=A0=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8F=84=EB=A1=9D=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/config/section/storage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 3ffeabafc67e..e51e0e07f5f0 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -304,15 +304,17 @@ export default { message: 'message.action.delete.volume', dataView: true, show: (record, store) => { + const isDetached = !record.virtualmachineid + const isDetachedAllocatedRoot = record.state === 'Allocated' && record.type === 'ROOT' && isDetached return ['Expunging', 'Expunged', 'UploadError'].includes(record.state) || - ['Allocated', 'Uploaded'].includes(record.state) && record.type !== 'ROOT' && !record.virtualmachineid || - (record.state === 'Ready' && record.type !== 'ROOT' && !record.virtualmachineid) || + ['Allocated', 'Uploaded'].includes(record.state) && record.type !== 'ROOT' && isDetached || + (record.state === 'Ready' && record.type !== 'ROOT' && isDetached) || + isDetachedAllocatedRoot || ((['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) || store.features.allowuserexpungerecovervolume) && record.state === 'Destroy') }, groupAction: true, popup: true, - groupMap: (selection) => { return selection.map(x => { return { id: x } }) } - }, + groupMap: (selection) => { return selection.map(x => { return { id: x } }) } }, { api: 'destroyVolume', icon: 'delete-outlined', From 1d4668abd37b1726f2cb6467dc47297da9f15792 Mon Sep 17 00:00:00 2001 From: JS Choi <77760789+jschoiRR@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:11:10 +0900 Subject: [PATCH 3/3] =?UTF-8?q?ui=20=EB=B9=8C=EB=93=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/config/section/storage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index e51e0e07f5f0..8bb49007e20c 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -314,7 +314,8 @@ export default { }, groupAction: true, popup: true, - groupMap: (selection) => { return selection.map(x => { return { id: x } }) } }, + groupMap: (selection) => { return selection.map(x => { return { id: x } }) } + }, { api: 'destroyVolume', icon: 'delete-outlined',