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 <[email protected]>
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