This is an automated email from the ASF dual-hosted git repository.

winterhazel pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new ba67a627f7a quota: skip usage records registering the period a volume 
has been attached (#13936)
ba67a627f7a is described below

commit ba67a627f7a25ca63a8128d28183c5f47f7eeb69
Author: Fabricio Duarte <[email protected]>
AuthorDate: Fri Aug 21 10:59:54 2026 -0300

    quota: skip usage records registering the period a volume has been attached 
(#13936)
---
 .../apache/cloudstack/quota/QuotaManagerImpl.java  |  6 +++
 .../cloudstack/quota/QuotaManagerImplTest.java     | 62 ++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
index 5afef8bc95b..01bb9bc7e7a 100644
--- 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java
@@ -54,6 +54,7 @@ import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
 import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
 import org.apache.cloudstack.quota.vo.QuotaTariffVO;
 import org.apache.cloudstack.quota.vo.QuotaUsageVO;
+import org.apache.cloudstack.usage.UsageTypes;
 import org.apache.cloudstack.usage.UsageUnitTypes;
 import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
 import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
@@ -366,6 +367,11 @@ public class QuotaManagerImpl extends ManagerBase 
implements QuotaManager {
                     usageRecord.toString(usageAggregationTimeZone), 
accountVO.reflectionToString());
             return false;
         }
+        if (usageRecord.getUsageType() == UsageTypes.VOLUME && 
usageRecord.getVmInstanceId() != null) {
+            logger.debug("Considering usage record [{}] as calculated and 
skipping it because it represents the period " +
+                    "a volume has remained attached to an instance, which 
Quota does not handle.", usageRecord.toString(usageAggregationTimeZone));
+            return false;
+        }
         return true;
     }
 
diff --git 
a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
 
b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
index 1e08e7d7fc0..5f9a9d3c022 100644
--- 
a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
+++ 
b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaManagerImplTest.java
@@ -26,12 +26,15 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
 import org.apache.cloudstack.quota.activationrule.presetvariables.Domain;
 import 
org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable;
 import 
org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableHelper;
 import 
org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariables;
 import org.apache.cloudstack.quota.activationrule.presetvariables.Tariff;
 import org.apache.cloudstack.quota.activationrule.presetvariables.Value;
+import org.apache.cloudstack.quota.constant.QuotaConfig;
 import org.apache.cloudstack.quota.constant.QuotaTypes;
 import org.apache.cloudstack.quota.dao.QuotaTariffDao;
 import org.apache.cloudstack.quota.dao.QuotaTariffUsageDao;
@@ -39,10 +42,13 @@ import org.apache.cloudstack.quota.dao.QuotaUsageDao;
 import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
 import org.apache.cloudstack.quota.vo.QuotaTariffVO;
 import org.apache.cloudstack.quota.vo.QuotaUsageVO;
+import org.apache.cloudstack.usage.UsageTypes;
 import org.apache.cloudstack.usage.UsageUnitTypes;
 import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
 import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
@@ -95,8 +101,21 @@ public class QuotaManagerImplTest {
     @Mock
     QuotaTariffUsageDao quotaTariffUsageDaoMock;
 
+    @Mock
+    ConfigDepotImpl configDepotImplMock;
+
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
+    @Before
+    public void setup() {
+        ConfigKey.init(configDepotImplMock);
+    }
+
+    @After
+    public void tearDown() {
+        ConfigKey.init(null);
+    }
+
     @Test
     public void isLockableTestValidateAccountTypes() {
         List<Account.Type> lockablesAccountTypes = 
Arrays.asList(Account.Type.NORMAL, Account.Type.DOMAIN_ADMIN);
@@ -113,6 +132,49 @@ public class QuotaManagerImplTest {
         });
     }
 
+    @Test
+    public void 
shouldCalculateUsageRecordTestQuotaIsDisabledForAccountReturnFalse() {
+        Mockito.doReturn(1L).when(accountVoMock).getAccountId();
+        
Mockito.doReturn("false").when(configDepotImplMock).getConfigStringValue(Mockito.eq(QuotaConfig.QuotaAccountEnabled.key()),
 Mockito.eq(ConfigKey.Scope.Account),
+                Mockito.eq(1L));
+
+        boolean result = 
quotaManagerImplSpy.shouldCalculateUsageRecord(accountVoMock, usageVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void 
shouldCalculateUsageRecordTestQuotaIsEnabledForAccountAndUsageRecordIsVolumeWithVmInstanceIdReturnFalse()
 {
+        Mockito.doReturn(1L).when(accountVoMock).getAccountId();
+        Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
+        Mockito.doReturn(1L).when(usageVoMock).getVmInstanceId();
+
+        boolean result = 
quotaManagerImplSpy.shouldCalculateUsageRecord(accountVoMock, usageVoMock);
+
+        Assert.assertFalse(result);
+    }
+
+    @Test
+    public void 
shouldCalculateUsageRecordTestQuotaIsEnabledForAccountAndUsageRecordIsVolumeWithoutVmInstanceIdReturnTrue()
 {
+        Mockito.doReturn(1L).when(accountVoMock).getAccountId();
+        Mockito.doReturn(UsageTypes.VOLUME).when(usageVoMock).getUsageType();
+        Mockito.doReturn(null).when(usageVoMock).getVmInstanceId();
+
+        boolean result = 
quotaManagerImplSpy.shouldCalculateUsageRecord(accountVoMock, usageVoMock);
+
+        Assert.assertTrue(result);
+    }
+
+    @Test
+    public void 
shouldCalculateUsageRecordTestQuotaIsEnabledForAccountAndUsageRecordIsNotVolumeReturnTrue()
 {
+        Mockito.doReturn(1L).when(accountVoMock).getAccountId();
+        
Mockito.doReturn(UsageTypes.RUNNING_VM).when(usageVoMock).getUsageType();
+
+        boolean result = 
quotaManagerImplSpy.shouldCalculateUsageRecord(accountVoMock, usageVoMock);
+
+        Assert.assertTrue(result);
+    }
+
     @Test
     public void 
getPendingUsageRecordsForQuotaAggregationTestNullListReturnNull() {
         
Mockito.doReturn(pairMock).when(usageDaoMock).listUsageRecordsPendingForQuotaAggregation(Mockito.anyLong(),
 Mockito.anyLong());

Reply via email to