From c0bceedc09ea95928db8593932b03a349c2b720d Mon Sep 17 00:00:00 2001 From: "tian.huang" Date: Fri, 27 Mar 2026 14:32:41 +0800 Subject: [PATCH] [compute]: fix VM clone quota check fail 1.Clone a tenant VM using admin account, quota check didn't base on current role. 2.quota error is overwritten during exception handling. http://jira.zstack.io/browse/ZSTAC-83646 Resolves: ZSTAC-83646 Change-Id: I776c6b7a786d79746f616b716f736f646e686868 --- .../compute/allocator/QuotaAllocatorFlow.java | 18 ++++- .../zstack/compute/vm/VmQuotaOperator.java | 71 +++++++++++++++---- .../header/allocator/AllocateHostMsg.java | 5 ++ .../header/allocator/HostAllocatorSpec.java | 8 +++ .../java/org/zstack/identity/QuotaUtil.java | 15 +++- 5 files changed, 97 insertions(+), 20 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/allocator/QuotaAllocatorFlow.java b/compute/src/main/java/org/zstack/compute/allocator/QuotaAllocatorFlow.java index 4d9d469c796..9781f90148a 100644 --- a/compute/src/main/java/org/zstack/compute/allocator/QuotaAllocatorFlow.java +++ b/compute/src/main/java/org/zstack/compute/allocator/QuotaAllocatorFlow.java @@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Configurable; import org.zstack.compute.vm.VmQuotaOperator; import org.zstack.header.allocator.AbstractHostAllocatorFlow; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.identity.AccountConstant; import org.zstack.identity.Account; import org.zstack.identity.QuotaUtil; @@ -51,6 +53,12 @@ public void allocate() { } throwExceptionIfIAmTheFirstFlow(); + // skip checkquota if the operator is admin + String sessionAccountUuid = spec.getSessionAccountUuid(); + if (sessionAccountUuid != null && AccountConstant.isAdminPermission(sessionAccountUuid)) { + next(candidates); + return; + } final String vmInstanceUuid = spec.getVmInstance().getUuid(); final String accountUuid = Account.getAccountUuidOfResource(vmInstanceUuid); @@ -60,21 +68,27 @@ public void allocate() { } if (!spec.isFullAllocate()) { - new VmQuotaOperator().checkVmCupAndMemoryCapacity(accountUuid, + ErrorCode error = new VmQuotaOperator().checkVmCupAndMemoryCapacityWithResult(accountUuid, accountUuid, spec.getCpuCapacity(), spec.getMemoryCapacity(), new QuotaUtil().makeQuotaPairs(accountUuid)); + if (error != null) { + throw new OperationFailureException(error); + } next(candidates); return; } - new VmQuotaOperator().checkVmInstanceQuota( + ErrorCode error = new VmQuotaOperator().checkVmInstanceQuotaWithResult( accountUuid, accountUuid, vmInstanceUuid, new QuotaUtil().makeQuotaPairs(accountUuid)); + if (error != null) { + throw new OperationFailureException(error); + } next(candidates); } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmQuotaOperator.java b/compute/src/main/java/org/zstack/compute/vm/VmQuotaOperator.java index bb63bbc9991..b8aa803644a 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmQuotaOperator.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmQuotaOperator.java @@ -9,6 +9,7 @@ import org.zstack.core.db.SimpleQuery; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.apimediator.ApiMessageInterceptionException; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.identity.APIChangeResourceOwnerMsg; import org.zstack.header.identity.AccountType; import org.zstack.header.identity.Quota; @@ -151,12 +152,27 @@ private void checkTotalVMQuota(String currentAccountUuid, String vmInstanceUuid, long totalVmNumQuota, long totalVmNum) { + ErrorCode error = checkTotalVMQuotaWithResult(currentAccountUuid, + resourceTargetOwnerAccountUuid, + vmInstanceUuid, + totalVmNumQuota, + totalVmNum); + if (error != null) { + throw new ApiMessageInterceptionException(error); + } + } + + private ErrorCode checkTotalVMQuotaWithResult(String currentAccountUuid, + String resourceTargetOwnerAccountUuid, + String vmInstanceUuid, + long totalVmNumQuota, + long totalVmNum) { if (Q.New(VmInstanceVO.class) .eq(VmInstanceVO_.uuid, vmInstanceUuid) .notNull(VmInstanceVO_.lastHostUuid) .isExists()) { // Dirty hack - VM with last host UUID means existing VM. - return; + return null; } QuotaUtil.QuotaCompareInfo quotaCompareInfo; @@ -167,18 +183,17 @@ private void checkTotalVMQuota(String currentAccountUuid, quotaCompareInfo.quotaValue = totalVmNumQuota; quotaCompareInfo.currentUsed = totalVmNum; quotaCompareInfo.request = 1; - new QuotaUtil().CheckQuota(quotaCompareInfo); + return new QuotaUtil().checkQuotaAndReturn(quotaCompareInfo); } @Transactional(readOnly = true) - public void checkVmInstanceQuota(String currentAccountUuid, - String resourceTargetOwnerAccountUuid, - String vmInstanceUuid, - Map pairs) { + public ErrorCode checkVmInstanceQuotaWithResult(String currentAccountUuid, + String resourceTargetOwnerAccountUuid, + String vmInstanceUuid, + Map pairs) { long vmNumQuota = pairs.get(VmQuotaConstant.VM_RUNNING_NUM).getValue(); VmQuotaUtil.VmQuota vmQuotaUsed = new VmQuotaUtil().getUsedVmCpuMemory(resourceTargetOwnerAccountUuid, null); - // { QuotaUtil.QuotaCompareInfo quotaCompareInfo; quotaCompareInfo = new QuotaUtil.QuotaCompareInfo(); @@ -188,22 +203,38 @@ public void checkVmInstanceQuota(String currentAccountUuid, quotaCompareInfo.quotaValue = vmNumQuota; quotaCompareInfo.currentUsed = vmQuotaUsed.runningVmNum; quotaCompareInfo.request = 1; - new QuotaUtil().CheckQuota(quotaCompareInfo); + ErrorCode error = new QuotaUtil().checkQuotaAndReturn(quotaCompareInfo); + if (error != null) { + return error; + } } - // - checkTotalVMQuota(currentAccountUuid, + + ErrorCode error = checkTotalVMQuotaWithResult(currentAccountUuid, resourceTargetOwnerAccountUuid, vmInstanceUuid, pairs.get(VmQuotaConstant.VM_TOTAL_NUM).getValue(), vmQuotaUsed.totalVmNum); - // + if (error != null) { + return error; + } + VmInstanceVO vm = dbf.getEntityManager().find(VmInstanceVO.class, vmInstanceUuid); - checkVmCupAndMemoryCapacity(currentAccountUuid, resourceTargetOwnerAccountUuid, vm.getCpuNum(), vm.getMemorySize(), pairs); + return checkVmCupAndMemoryCapacityWithResult(currentAccountUuid, resourceTargetOwnerAccountUuid, vm.getCpuNum(), vm.getMemorySize(), pairs); + } + + public void checkVmInstanceQuota(String currentAccountUuid, + String resourceTargetOwnerAccountUuid, + String vmInstanceUuid, + Map pairs) { + ErrorCode error = checkVmInstanceQuotaWithResult(currentAccountUuid, resourceTargetOwnerAccountUuid, vmInstanceUuid, pairs); + if (error != null) { + throw new ApiMessageInterceptionException(error); + } } @Transactional(readOnly = true) - public void checkVmCupAndMemoryCapacity(String currentAccountUuid, String resourceTargetOwnerAccountUuid, long cpu, long memory, Map pairs) { + public ErrorCode checkVmCupAndMemoryCapacityWithResult(String currentAccountUuid, String resourceTargetOwnerAccountUuid, long cpu, long memory, Map pairs) { VmQuotaUtil.VmQuota vmQuotaUsed = new VmQuotaUtil().getUsedVmCpuMemory(resourceTargetOwnerAccountUuid); long cpuNumQuota = pairs.get(VmQuotaConstant.VM_RUNNING_CPU_NUM).getValue(); long memoryQuota = pairs.get(VmQuotaConstant.VM_RUNNING_MEMORY_SIZE).getValue(); @@ -217,7 +248,10 @@ public void checkVmCupAndMemoryCapacity(String currentAccountUuid, String resour quotaCompareInfo.quotaValue = cpuNumQuota; quotaCompareInfo.currentUsed = vmQuotaUsed.runningVmCpuNum; quotaCompareInfo.request = cpu; - new QuotaUtil().CheckQuota(quotaCompareInfo); + ErrorCode error = new QuotaUtil().checkQuotaAndReturn(quotaCompareInfo); + if (error != null) { + return error; + } } { QuotaUtil.QuotaCompareInfo quotaCompareInfo; @@ -228,7 +262,14 @@ public void checkVmCupAndMemoryCapacity(String currentAccountUuid, String resour quotaCompareInfo.quotaValue = memoryQuota; quotaCompareInfo.currentUsed = vmQuotaUsed.runningVmMemorySize; quotaCompareInfo.request = memory; - new QuotaUtil().CheckQuota(quotaCompareInfo); + return new QuotaUtil().checkQuotaAndReturn(quotaCompareInfo); + } + } + + public void checkVmCupAndMemoryCapacity(String currentAccountUuid, String resourceTargetOwnerAccountUuid, long cpu, long memory, Map pairs) { + ErrorCode error = checkVmCupAndMemoryCapacityWithResult(currentAccountUuid, resourceTargetOwnerAccountUuid, cpu, memory, pairs); + if (error != null) { + throw new ApiMessageInterceptionException(error); } } diff --git a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java index 23869aa1327..91c2a9d1bbe 100755 --- a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java +++ b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java @@ -31,6 +31,7 @@ public class AllocateHostMsg extends NeedReplyMessage { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; + private String sessionAccountUuid; public List> getOptionalPrimaryStorageUuids() { return optionalPrimaryStorageUuids; @@ -211,4 +212,8 @@ public String getArchitecture() { public void setArchitecture(String architecture) { this.architecture = architecture; } + + public String getSessionAccountUuid() { return sessionAccountUuid; } + + public void setSessionAccountUuid(String sessionAccountUuid) { this.sessionAccountUuid = sessionAccountUuid; } } diff --git a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java index c8d8ddc3af8..3ae910c1f4e 100755 --- a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java +++ b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java @@ -37,6 +37,7 @@ public class HostAllocatorSpec { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; + private String sessionAccountUuid; public AllocationScene getAllocationScene() { return allocationScene; @@ -161,7 +162,13 @@ public List getL3NetworkUuids() { } return l3NetworkUuids; } + public String getSessionAccountUuid() { + return sessionAccountUuid; + } + public void setSessionAccountUuid(String sessionAccountUuid) { + this.sessionAccountUuid = sessionAccountUuid; + } public void setL3NetworkUuids(List l3NetworkUuids) { this.l3NetworkUuids = l3NetworkUuids; } @@ -250,6 +257,7 @@ public static HostAllocatorSpec fromAllocationMsg(AllocateHostMsg msg) { msg.getOptionalPrimaryStorageUuids().forEach(spec::addOptionalPrimaryStorageUuids); spec.setAllocationScene(msg.getAllocationScene()); spec.setArchitecture(msg.getArchitecture()); + spec.setSessionAccountUuid(msg.getSessionAccountUuid()); if (msg.getSystemTags() != null && !msg.getSystemTags().isEmpty()){ spec.setSystemTags(new ArrayList(msg.getSystemTags())); } diff --git a/identity/src/main/java/org/zstack/identity/QuotaUtil.java b/identity/src/main/java/org/zstack/identity/QuotaUtil.java index d7b29015624..34fce1b5f80 100644 --- a/identity/src/main/java/org/zstack/identity/QuotaUtil.java +++ b/identity/src/main/java/org/zstack/identity/QuotaUtil.java @@ -72,7 +72,7 @@ public String getResourceOwnerAccountUuid(String resourceUuid) { } @Transactional(readOnly = true) - public void CheckQuota(QuotaCompareInfo quotaCompareInfo) { + public ErrorCode checkQuotaAndReturn(QuotaCompareInfo quotaCompareInfo) { logger.trace(String.format("dump quota QuotaCompareInfo: \n %s", JSONObjectUtil.toJsonString(quotaCompareInfo))); String accountName = Q.New(AccountVO.class) @@ -80,14 +80,23 @@ public void CheckQuota(QuotaCompareInfo quotaCompareInfo) { .eq(AccountVO_.uuid, quotaCompareInfo.resourceTargetOwnerAccountUuid) .findValue(); if (quotaCompareInfo.currentUsed + quotaCompareInfo.request > quotaCompareInfo.quotaValue) { - throw new ApiMessageInterceptionException(err(ORG_ZSTACK_IDENTITY_10002, IdentityErrors.QUOTA_EXCEEDING, + return err(ORG_ZSTACK_IDENTITY_10002, IdentityErrors.QUOTA_EXCEEDING, "quota exceeding." + "The resource owner(or target resource owner) account[uuid: %s name: %s] exceeds a quota[name: %s, value: %s], " + "Current used:%s, Request:%s. Please contact the administrator.", quotaCompareInfo.resourceTargetOwnerAccountUuid, StringUtils.trimToEmpty(accountName), quotaCompareInfo.quotaName, quotaCompareInfo.quotaValue, quotaCompareInfo.currentUsed, quotaCompareInfo.request - )); + ); + } + + return null; + } + + public void CheckQuota(QuotaCompareInfo quotaCompareInfo) { + ErrorCode error = checkQuotaAndReturn(quotaCompareInfo); + if (error != null) { + throw new ApiMessageInterceptionException(error); } }