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

dahn 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 b9c7275c253 Quota tariff order (#8347)
b9c7275c253 is described below

commit b9c7275c2539a9e7a7de803eb2173ab61e0723f2
Author: João Jandre <48719461+joaojan...@users.noreply.github.com>
AuthorDate: Sun Jul 14 11:05:15 2024 -0300

    Quota tariff order (#8347)
---
 .../resources/META-INF/db/schema-41910to42000.sql  |   4 +
 .../apache/cloudstack/quota/QuotaManagerImpl.java  |  20 ++-
 .../activationrule/presetvariables/Tariff.java     |  33 ++++
 .../apache/cloudstack/quota/vo/QuotaTariffVO.java  |  14 ++
 .../cloudstack/quota/QuotaManagerImplTest.java     |  53 +++++--
 .../api/command/QuotaTariffCreateCmd.java          |  12 ++
 .../api/command/QuotaTariffUpdateCmd.java          |  12 ++
 .../api/response/QuotaResponseBuilderImpl.java     |  18 ++-
 .../api/response/QuotaTariffResponse.java          |  13 ++
 .../api/response/QuotaResponseBuilderImplTest.java |  18 ++-
 .../integration/plugins/test_quota_tariff_order.py | 175 +++++++++++++++++++++
 11 files changed, 351 insertions(+), 21 deletions(-)

diff --git 
a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql 
b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
index 295ad147a99..f59eda5c06c 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
@@ -150,3 +150,7 @@ SET
 WHERE
     name IN ("quota.usage.smtp.useStartTLS", "quota.usage.smtp.useAuth", 
"alert.smtp.useAuth", "project.smtp.useAuth")
     AND value NOT IN ("true", "y", "t", "1", "on", "yes");
+
+
+-- Quota inject tariff result into subsequent ones
+CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.quota_tariff', 
'position', 'bigint(20) NOT NULL DEFAULT 1 COMMENT "Position in the execution 
sequence for tariffs of the same type"');
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 ded35338aea..226a47bb7df 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
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -36,6 +37,7 @@ import 
org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 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.constant.QuotaConfig;
 import org.apache.cloudstack.quota.constant.QuotaTypes;
 import org.apache.cloudstack.quota.dao.QuotaAccountDao;
@@ -371,9 +373,22 @@ public class QuotaManagerImpl extends ManagerBase 
implements QuotaManager {
         PresetVariables presetVariables = 
getPresetVariables(hasAnyQuotaTariffWithActivationRule, usageRecord);
         BigDecimal aggregatedQuotaTariffsValue = BigDecimal.ZERO;
 
+        quotaTariffs.sort(Comparator.comparing(QuotaTariffVO::getPosition));
+
+        List<Tariff> lastTariffs = new ArrayList<>();
+
+
         for (QuotaTariffVO quotaTariff : quotaTariffs) {
             if (isQuotaTariffInPeriodToBeApplied(usageRecord, quotaTariff, 
accountToString)) {
-                aggregatedQuotaTariffsValue = 
aggregatedQuotaTariffsValue.add(getQuotaTariffValueToBeApplied(quotaTariff, 
jsInterpreter, presetVariables));
+
+                BigDecimal tariffValue = 
getQuotaTariffValueToBeApplied(quotaTariff, jsInterpreter, presetVariables, 
lastTariffs);
+
+                aggregatedQuotaTariffsValue = 
aggregatedQuotaTariffsValue.add(tariffValue);
+
+                Tariff tariffPresetVariable = new Tariff();
+                tariffPresetVariable.setId(quotaTariff.getUuid());
+                tariffPresetVariable.setValue(tariffValue);
+                lastTariffs.add(tariffPresetVariable);
             }
         }
 
@@ -401,7 +416,7 @@ public class QuotaManagerImpl extends ManagerBase 
implements QuotaManager {
      *   <li>If the activation rule result in something else, returns {@link 
BigDecimal#ZERO}.</li>
      * </ul>
      */
-    protected BigDecimal getQuotaTariffValueToBeApplied(QuotaTariffVO 
quotaTariff, JsInterpreter jsInterpreter, PresetVariables presetVariables) {
+    protected BigDecimal getQuotaTariffValueToBeApplied(QuotaTariffVO 
quotaTariff, JsInterpreter jsInterpreter, PresetVariables presetVariables, 
List<Tariff> lastAppliedTariffsList) {
         String activationRule = quotaTariff.getActivationRule();
         BigDecimal quotaTariffValue = quotaTariff.getCurrencyValue();
         String quotaTariffToString = 
quotaTariff.toString(usageAggregationTimeZone);
@@ -413,6 +428,7 @@ public class QuotaManagerImpl extends ManagerBase 
implements QuotaManager {
         }
 
         injectPresetVariablesIntoJsInterpreter(jsInterpreter, presetVariables);
+        jsInterpreter.injectVariable("lastTariffs", 
lastAppliedTariffsList.toString());
 
         String scriptResult = 
jsInterpreter.executeScript(activationRule).toString();
 
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Tariff.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Tariff.java
new file mode 100644
index 00000000000..3703820a1a4
--- /dev/null
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Tariff.java
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.quota.activationrule.presetvariables;
+
+import java.math.BigDecimal;
+
+public class Tariff extends GenericPresetVariable {
+    private BigDecimal value;
+
+    public BigDecimal getValue() {
+        return value;
+    }
+
+    public void setValue(BigDecimal value) {
+        this.value = value;
+        fieldNamesToIncludeInToString.add("value");
+    }
+}
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java
index 40a751c6200..bd6aeb13418 100644
--- 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java
@@ -93,6 +93,10 @@ public class QuotaTariffVO implements QuotaTariff {
     @Temporal(value = TemporalType.TIMESTAMP)
     private Date endDate;
 
+    @Column(name = "position")
+    protected Integer position;
+
+
     public QuotaTariffVO() {
     }
 
@@ -120,6 +124,7 @@ public class QuotaTariffVO implements QuotaTariff {
         this.setDescription(that.getDescription());
         this.setActivationRule(that.getActivationRule());
         this.setEndDate(that.getEndDate());
+        this.setPosition(that.getPosition());
     }
 
     public void setId(Long id) {
@@ -263,6 +268,15 @@ public class QuotaTariffVO implements QuotaTariff {
         return true;
     }
 
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+
     @Override
     public String toString() {
         return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, 
"uuid", "name", "usageName");
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 e53051f2925..5dfc12f7ef8 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
@@ -29,6 +29,7 @@ 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.QuotaTypes;
 import org.apache.cloudstack.quota.dao.QuotaTariffDao;
@@ -395,7 +396,7 @@ public class QuotaManagerImplTest {
         Mockito.doReturn(null).when(quotaTariffVoMock).getActivationRule();
         
Mockito.doReturn(BigDecimal.ONE).when(quotaTariffVoMock).getCurrencyValue();
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, null, 
null);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, null, 
null, null);
 
         Assert.assertEquals(BigDecimal.ONE, result);
     }
@@ -405,7 +406,7 @@ public class QuotaManagerImplTest {
         Mockito.doReturn("").when(quotaTariffVoMock).getActivationRule();
         
Mockito.doReturn(BigDecimal.TEN).when(quotaTariffVoMock).getCurrencyValue();
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, null, 
null);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, null, 
null, null);
 
         Assert.assertEquals(BigDecimal.TEN, result);
     }
@@ -413,13 +414,15 @@ public class QuotaManagerImplTest {
     @Test
     public void 
getQuotaTariffValueToBeAppliedTestScriptResultIsNumberReturnIt() {
         BigDecimal expected = new BigDecimal(50.1);
+        List<Tariff> lastTariffs = 
createLastAppliedTariffsPresetVariableList(0);
+
 
         Mockito.doReturn(" ").when(quotaTariffVoMock).getActivationRule();
         
Mockito.doReturn(BigDecimal.TEN).when(quotaTariffVoMock).getCurrencyValue();
         
Mockito.doNothing().when(quotaManagerImplSpy).injectPresetVariablesIntoJsInterpreter(Mockito.any(),
 Mockito.any());
         
Mockito.doReturn(expected).when(jsInterpreterMock).executeScript(Mockito.anyString());
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock, lastTariffs);
 
         Assert.assertEquals(expected, result);
     }
@@ -427,37 +430,42 @@ public class QuotaManagerImplTest {
     @Test
     public void 
getQuotaTariffValueToBeAppliedTestScriptResultIsTrueReturnTariffValue() {
         BigDecimal expected = new BigDecimal(236.84);
+        List<Tariff> lastTariffs = 
createLastAppliedTariffsPresetVariableList(0);
 
         Mockito.doReturn(" ").when(quotaTariffVoMock).getActivationRule();
         Mockito.doReturn(expected).when(quotaTariffVoMock).getCurrencyValue();
         
Mockito.doNothing().when(quotaManagerImplSpy).injectPresetVariablesIntoJsInterpreter(Mockito.any(),
 Mockito.any());
         
Mockito.doReturn(true).when(jsInterpreterMock).executeScript(Mockito.anyString());
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock, lastTariffs);
 
         Assert.assertEquals(expected, result);
     }
 
     @Test
     public void 
getQuotaTariffValueToBeAppliedTestScriptResultIsFalseReturnZero() {
+        List<Tariff> lastTariffs = 
createLastAppliedTariffsPresetVariableList(0);
+
         Mockito.doReturn(" ").when(quotaTariffVoMock).getActivationRule();
         
Mockito.doReturn(BigDecimal.TEN).when(quotaTariffVoMock).getCurrencyValue();
         
Mockito.doNothing().when(quotaManagerImplSpy).injectPresetVariablesIntoJsInterpreter(Mockito.any(),
 Mockito.any());
         
Mockito.doReturn(false).when(jsInterpreterMock).executeScript(Mockito.anyString());
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock, lastTariffs);
 
         Assert.assertEquals(BigDecimal.ZERO, result);
     }
 
     @Test
     public void 
getQuotaTariffValueToBeAppliedTestScriptResultIsNotBooleanNorNumericReturnZero()
 {
+        List<Tariff> lastTariffs = 
createLastAppliedTariffsPresetVariableList(0);
+
         Mockito.doReturn(" ").when(quotaTariffVoMock).getActivationRule();
         
Mockito.doReturn(BigDecimal.TEN).when(quotaTariffVoMock).getCurrencyValue();
         
Mockito.doNothing().when(quotaManagerImplSpy).injectPresetVariablesIntoJsInterpreter(Mockito.any(),
 Mockito.any());
         
Mockito.doReturn("test").when(jsInterpreterMock).executeScript(Mockito.anyString());
 
-        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock);
+        BigDecimal result = 
quotaManagerImplSpy.getQuotaTariffValueToBeApplied(quotaTariffVoMock, 
jsInterpreterMock, presetVariablesMock, lastTariffs);
 
         Assert.assertEquals(BigDecimal.ZERO, result);
     }
@@ -477,10 +485,7 @@ public class QuotaManagerImplTest {
 
     @Test
     public void 
aggregateQuotaTariffsValuesTestTariffsWereNotInPeriodToBeAppliedReturnZero() {
-        List<QuotaTariffVO> tariffs = new ArrayList<>();
-        tariffs.add(new QuotaTariffVO());
-        tariffs.add(new QuotaTariffVO());
-        tariffs.add(new QuotaTariffVO());
+        List<QuotaTariffVO> tariffs = createTariffList();
 
         
Mockito.doReturn(false).when(quotaManagerImplSpy).isQuotaTariffInPeriodToBeApplied(Mockito.any(),
 Mockito.any(), Mockito.anyString());
         BigDecimal result = 
quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, 
jsInterpreterMock, "");
@@ -497,13 +502,10 @@ public class QuotaManagerImplTest {
 
     @Test
     public void 
aggregateQuotaTariffsValuesTestTariffsAreInPeriodToBeAppliedReturnAggregation() 
{
-        List<QuotaTariffVO> tariffs = new ArrayList<>();
-        tariffs.add(new QuotaTariffVO());
-        tariffs.add(new QuotaTariffVO());
-        tariffs.add(new QuotaTariffVO());
+        List<QuotaTariffVO> tariffs = createTariffList();
 
         Mockito.doReturn(true, false, 
true).when(quotaManagerImplSpy).isQuotaTariffInPeriodToBeApplied(Mockito.any(), 
Mockito.any(), Mockito.anyString());
-        
Mockito.doReturn(BigDecimal.TEN).when(quotaManagerImplSpy).getQuotaTariffValueToBeApplied(Mockito.any(),
 Mockito.any(), Mockito.any());
+        
Mockito.doReturn(BigDecimal.TEN).when(quotaManagerImplSpy).getQuotaTariffValueToBeApplied(Mockito.any(),
 Mockito.any(), Mockito.any(), Mockito.any());
         BigDecimal result = 
quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, 
jsInterpreterMock, "");
 
         Assert.assertEquals(BigDecimal.TEN.multiply(new BigDecimal(2)), 
result);
@@ -528,4 +530,25 @@ public class QuotaManagerImplTest {
         Assert.assertEquals(quotaUsageVoMock1, result.get(0));
         Assert.assertEquals(quotaUsageVoMock2, result.get(1));
     }
+
+    private static List<QuotaTariffVO> createTariffList() {
+        List<QuotaTariffVO> tariffs = new ArrayList<>();
+        tariffs.add(new QuotaTariffVO());
+        tariffs.add(new QuotaTariffVO());
+        tariffs.add(new QuotaTariffVO());
+        tariffs.forEach(quotaTariffVO -> quotaTariffVO.setPosition(1));
+        return tariffs;
+    }
+
+    private static List<Tariff> createLastAppliedTariffsPresetVariableList(int 
numberOfTariffs) {
+        List<Tariff> lastTariffs = new ArrayList<>();
+        for (int i = 0; i < numberOfTariffs; i++) {
+            Tariff tariff = new Tariff();
+            tariff.setId(String.valueOf(i));
+            tariff.setValue(BigDecimal.valueOf(i));
+            lastTariffs.add(tariff);
+        }
+        return lastTariffs;
+    }
+
 }
diff --git 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java
 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java
index b9406754b31..137f42536df 100644
--- 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java
+++ 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java
@@ -68,6 +68,9 @@ public class QuotaTariffCreateCmd extends BaseCmd {
             ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
     private Date endDate;
 
+    @Parameter(name = ApiConstants.POSITION, type = CommandType.INTEGER, 
description = "Position in the execution sequence for tariffs of the same 
type", since = "4.20.0.0")
+    private Integer position;
+
     @Override
     public void execute() {
         CallContext.current().setEventDetails(String.format("Tariff: %s, 
description: %s, value: %s", getName(), getDescription(), getValue()));
@@ -139,4 +142,13 @@ public class QuotaTariffCreateCmd extends BaseCmd {
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.QuotaTariff;
     }
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
+
 }
diff --git 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java
 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java
index 4fc1f08da88..6370cc57e4e 100644
--- 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java
+++ 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java
@@ -69,6 +69,9 @@ public class QuotaTariffUpdateCmd extends BaseCmd {
             "value will be applied. Inform empty to remove the activation 
rule.", length = 65535, since = "4.18.0.0")
     private String activationRule;
 
+    @Parameter(name = ApiConstants.POSITION, type = CommandType.INTEGER, 
description = "Position in the execution sequence for tariffs of the same 
type", since = "4.20.0.0")
+    private Integer position;
+
     public Integer getUsageType() {
         return usageType;
     }
@@ -130,4 +133,13 @@ public class QuotaTariffUpdateCmd extends BaseCmd {
     public ApiCommandResourceType getApiResourceType() {
         return ApiCommandResourceType.QuotaTariff;
     }
+
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
 }
diff --git 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
index 7b5667ac13d..88e90cc9ba9 100644
--- 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
+++ 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
@@ -80,6 +80,7 @@ import org.apache.cloudstack.quota.vo.QuotaUsageVO;
 import 
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
 import org.springframework.stereotype.Component;
@@ -151,6 +152,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         response.setDescription(tariff.getDescription());
         response.setId(tariff.getUuid());
         response.setRemoved(tariff.getRemoved());
+        response.setPosition(tariff.getPosition());
         return response;
     }
 
@@ -414,6 +416,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         String description = cmd.getDescription();
         String activationRule = cmd.getActivationRule();
         Date now = new Date();
+        Integer position = cmd.getPosition();
 
         warnQuotaTariffUpdateDeprecatedFields(cmd);
 
@@ -428,7 +431,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         currentQuotaTariff.setRemoved(now);
 
         QuotaTariffVO newQuotaTariff = 
persistNewQuotaTariff(currentQuotaTariff, name, 0, currentQuotaTariffStartDate, 
cmd.getEntityOwnerId(), endDate, value, description,
-                activationRule);
+                activationRule, position);
         _quotaTariffDao.updateQuotaTariff(currentQuotaTariff);
 
         CallContext.current().setEventResourceId(newQuotaTariff.getId());
@@ -449,7 +452,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
     }
 
     protected QuotaTariffVO persistNewQuotaTariff(QuotaTariffVO 
currentQuotaTariff, String name, int usageType, Date startDate, Long 
entityOwnerId, Date endDate, Double value,
-            String description, String activationRule) {
+            String description, String activationRule, Integer position) {
 
         QuotaTariffVO newQuotaTariff = 
getNewQuotaTariffObject(currentQuotaTariff, name, usageType);
 
@@ -461,6 +464,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         validateValueOnCreatingNewQuotaTariff(newQuotaTariff, value);
         
validateStringsOnCreatingNewQuotaTariff(newQuotaTariff::setDescription, 
description);
         
validateStringsOnCreatingNewQuotaTariff(newQuotaTariff::setActivationRule, 
activationRule);
+        validatePositionOnCreatingNewQuotaTariff(newQuotaTariff, position);
 
         _quotaTariffDao.addQuotaTariff(newQuotaTariff);
         return newQuotaTariff;
@@ -481,6 +485,13 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         return newQuotaTariff;
     }
 
+    protected void validatePositionOnCreatingNewQuotaTariff(QuotaTariffVO 
newQuotaTariff, Integer position) {
+        if (position != null) {
+            newQuotaTariff.setPosition(position);
+        }
+    }
+
+
     protected void validateStringsOnCreatingNewQuotaTariff(Consumer<String> 
method, String value){
         if (value != null) {
             method.accept(value.isBlank() ? null : value);
@@ -663,6 +674,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
         Double value = cmd.getValue();
         String description = cmd.getDescription();
         String activationRule = cmd.getActivationRule();
+        Integer position = ObjectUtils.defaultIfNull(cmd.getPosition(), 1);
 
         QuotaTariffVO currentQuotaTariff = _quotaTariffDao.findByName(name);
 
@@ -675,7 +687,7 @@ public class QuotaResponseBuilderImpl implements 
QuotaResponseBuilder {
                     "Please, inform a date in the future or do not pass the 
parameter to use the current date and time.", startDate));
         }
 
-        QuotaTariffVO newQuotaTariff = persistNewQuotaTariff(null, name, 
usageType, startDate, cmd.getEntityOwnerId(), endDate, value, description, 
activationRule);
+        QuotaTariffVO newQuotaTariff = persistNewQuotaTariff(null, name, 
usageType, startDate, cmd.getEntityOwnerId(), endDate, value, description, 
activationRule, position);
 
         CallContext.current().setEventResourceId(newQuotaTariff.getId());
 
diff --git 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaTariffResponse.java
 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaTariffResponse.java
index cec3634c76d..6d844d78427 100644
--- 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaTariffResponse.java
+++ 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaTariffResponse.java
@@ -83,6 +83,11 @@ public class QuotaTariffResponse extends BaseResponse {
     @Param(description = "when the quota tariff was removed")
     private Date removed;
 
+    @SerializedName("position")
+    @Param(description = "position in the execution sequence for tariffs of 
the same type")
+    private Integer position;
+
+
     public QuotaTariffResponse() {
         super();
         this.setObjectName("quotatariff");
@@ -172,4 +177,12 @@ public class QuotaTariffResponse extends BaseResponse {
         this.removed = removed;
     }
 
+    public Integer getPosition() {
+        return position;
+    }
+
+    public void setPosition(Integer position) {
+        this.position = position;
+    }
+
 }
diff --git 
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
 
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
index da02b6d3709..71e38a5ab8c 100644
--- 
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
+++ 
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
@@ -372,8 +372,10 @@ public class QuotaResponseBuilderImplTest extends TestCase 
{
         
Mockito.doNothing().when(quotaResponseBuilderSpy).validateValueOnCreatingNewQuotaTariff(Mockito.any(QuotaTariffVO.class),
 Mockito.anyDouble());
         
Mockito.doNothing().when(quotaResponseBuilderSpy).validateStringsOnCreatingNewQuotaTariff(Mockito.any(Consumer.class),
 Mockito.anyString());
         
Mockito.doReturn(quotaTariffVoMock).when(quotaTariffDaoMock).addQuotaTariff(Mockito.any(QuotaTariffVO.class));
+        
Mockito.doNothing().when(quotaResponseBuilderSpy).validatePositionOnCreatingNewQuotaTariff(Mockito.any(QuotaTariffVO.class),
 Mockito.anyInt());
 
-        quotaResponseBuilderSpy.persistNewQuotaTariff(quotaTariffVoMock, "", 
1, date, 1l, date, 1.0, "", "");
+
+        quotaResponseBuilderSpy.persistNewQuotaTariff(quotaTariffVoMock, "", 
1, date, 1l, date, 1.0, "", "", 2);
 
         
Mockito.verify(quotaTariffDaoMock).addQuotaTariff(Mockito.any(QuotaTariffVO.class));
     }
@@ -553,4 +555,18 @@ public class QuotaResponseBuilderImplTest extends TestCase 
{
         assertEquals(2, result.getEmailTemplateId());
         assertFalse(result.isEnabled());
     }
+
+    @Test
+    public void 
validatePositionOnCreatingNewQuotaTariffTestNullValueDoNothing() {
+        
quotaResponseBuilderSpy.validatePositionOnCreatingNewQuotaTariff(quotaTariffVoMock,
 null);
+        Mockito.verify(quotaTariffVoMock, 
Mockito.never()).setPosition(Mockito.any());
+    }
+
+    @Test
+    public void validatePositionOnCreatingNewQuotaTariffTestAnyValueIsSet() {
+        Integer position = 1;
+        
quotaResponseBuilderSpy.validatePositionOnCreatingNewQuotaTariff(quotaTariffVoMock,
 position);
+        Mockito.verify(quotaTariffVoMock).setPosition(position);
+    }
+
 }
diff --git a/test/integration/plugins/test_quota_tariff_order.py 
b/test/integration/plugins/test_quota_tariff_order.py
new file mode 100644
index 00000000000..3236c6c3c18
--- /dev/null
+++ b/test/integration/plugins/test_quota_tariff_order.py
@@ -0,0 +1,175 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+""" Test cases for checking quota API
+"""
+
+# Import Local Modules
+import tools.marvin.marvin
+from tools.marvin.marvin.cloudstackTestCase import *
+from tools.marvin.marvin.cloudstackAPI import *
+from tools.marvin.marvin.lib.utils import *
+from tools.marvin.marvin.lib.base import *
+from tools.marvin.marvin.lib.common import *
+from nose.plugins.attrib import attr
+
+# Import System modules
+import time
+
+
+class TestQuotaTariffOrder(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        testClient = super(TestQuotaTariffOrder, cls).getClsTestClient()
+        cls.api_client = testClient.getApiClient()
+        cls.services = testClient.getParsedTestDataConfig()
+
+        # Get Zone, Domain and templates
+        cls.domain = get_domain(cls.api_client)
+        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
+
+        cls._cleanup = []
+        # Create Account
+        cls.account = Account.create(
+            cls.api_client,
+            cls.services["account"],
+            domainid=cls.domain.id
+        )
+        cls._cleanup.append(cls.account)
+
+        cls.services["account"] = cls.account.name
+
+        return
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestQuotaTariffOrder, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        self.cleanup = []
+        self.tariffs = []
+        return
+
+    def tearDown(self):
+        self.delete_tariffs()
+        super(TestQuotaTariffOrder, self).tearDown()
+
+    def delete_tariffs(self):
+        for tariff in self.tariffs:
+            cmd = quotaTariffDelete.quotaTariffDeleteCmd()
+            cmd.id = tariff.uuid
+            self.api_client.quotaTariffDelete(cmd)
+
+    @attr(
+        tags=[
+            "advanced",
+            "smoke"],
+        required_hardware="false")
+    def test_01_quota_tariff_order(self):
+        """Test Quota Tariff Order
+        """
+
+        cmd = quotaTariffCreate.quotaTariffCreateCmd()
+        cmd.name = 'tf1'
+        cmd.value = '1'
+        cmd.activationrule = '10'
+        cmd.usagetype = '22'
+        cmd.position = '2'
+        self.tariffs.append(self.api_client.quotaTariffCreate(cmd))
+
+        cmd = quotaTariffCreate.quotaTariffCreateCmd()
+        cmd.name = 'tf2'
+        cmd.value = '1'
+        cmd.activationrule = 'lastTariffs[lastTariffs.length -1].value + 7'
+        cmd.usagetype = '22'
+        cmd.position = '3'
+        self.tariffs.append(self.api_client.quotaTariffCreate(cmd))
+
+        cmd = quotaTariffCreate.quotaTariffCreateCmd()
+        cmd.name = 'tf3'
+        cmd.value = '1'
+        cmd.activationrule = 'lastTariffs[lastTariffs.length -2].value + 
lastTariffs[lastTariffs.length -1].value'
+        cmd.usagetype = '22'
+        cmd.position = '4'
+        self.tariffs.append(self.api_client.quotaTariffCreate(cmd))
+
+        cmd = quotaCredits.quotaCreditsCmd()
+        cmd.account = self.account.name
+        cmd.domainid = self.domain.id
+        cmd.value = 54
+        self.api_client.quotaCredits(cmd)
+
+        # Fetch account ID from account_uuid
+        self.debug("select id from account where uuid = '%s';"
+                   % self.account.id)
+
+        qresultset = self.dbclient.execute(
+            "select id from account where uuid = '%s';"
+            % self.account.id
+        )
+
+        account_id = qresultset[0][0]
+
+        self.debug("SELECT id from `domain` d WHERE uuid = '%s';"
+                   % self.domain.id)
+
+        qresultset = self.dbclient.execute(
+            "SELECT id from `domain` d WHERE uuid = '%s';"
+            % self.domain.id
+        )
+
+        domain_id = qresultset[0][0]
+
+        self.debug("SELECT id from data_center dc where dc.uuid = '%s';"
+                   % self.zone.id)
+
+        qresultset = self.dbclient.execute(
+            "SELECT id from data_center dc where dc.uuid = '%s';"
+            % self.zone.id
+        )
+
+        zone_id = qresultset[0][0]
+
+        start = datetime.datetime.now() + datetime.timedelta(seconds=1)
+        end = datetime.datetime.now() + datetime.timedelta(hours=1)
+
+        query = "INSERT INTO cloud_usage.cloud_usage 
(zone_id,account_id,domain_id,description,usage_display,"
+        
"usage_type,raw_usage,vm_instance_id,vm_name,offering_id,template_id,usage_id,`type`,`size`,"
+        
"network_id,start_date,end_date,virtual_size,cpu_speed,cpu_cores,memory,quota_calculated,"
+        "is_hidden,state) VALUES ('{}','{}','{}','Test','1 
Hrs',22,1,NULL,NULL,NULL,NULL,NULL,"
+        
"'VirtualMachine',NULL,NULL,'{}','{}',NULL,NULL,NULL,NULL,0,0,NULL);".format(zone_id,
 account_id, domain_id, start, end)
+
+        self.debug(query)
+
+        self.dbclient.execute(
+            query)
+
+        cmd = quotaUpdate.quotaUpdateCmd()
+        self.api_client.quotaUpdate(cmd)
+
+        cmd = quotaBalance.quotaBalanceCmd()
+        cmd.domainid = self.account.domainid
+        cmd.account = self.account.name
+        response = self.apiclient.quotaBalance(cmd)
+
+        self.debug(f"Quota Balance: {response.balance}")
+
+        self.assertEqual(response.balance.startquota, 0, f"startQuota is 
supposed to be 0 but was {response.balance.startquota}")
+
+        return

Reply via email to