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 295ad147a993..f59eda5c06c9 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 ded35338aeaf..226a47bb7dfa 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.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.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 @@ protected BigDecimal aggregateQuotaTariffsValues(UsageVO usageRecord, List 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 @@ protected PresetVariables getPresetVariables(boolean hasAnyQuotaTariffWithActiva *
  • If the activation rule result in something else, returns {@link BigDecimal#ZERO}.
  • * */ - protected BigDecimal getQuotaTariffValueToBeApplied(QuotaTariffVO quotaTariff, JsInterpreter jsInterpreter, PresetVariables presetVariables) { + protected BigDecimal getQuotaTariffValueToBeApplied(QuotaTariffVO quotaTariff, JsInterpreter jsInterpreter, PresetVariables presetVariables, List lastAppliedTariffsList) { String activationRule = quotaTariff.getActivationRule(); BigDecimal quotaTariffValue = quotaTariff.getCurrencyValue(); String quotaTariffToString = quotaTariff.toString(usageAggregationTimeZone); @@ -413,6 +428,7 @@ protected BigDecimal getQuotaTariffValueToBeApplied(QuotaTariffVO quotaTariff, J } 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 000000000000..3703820a1a40 --- /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 40a751c62002..bd6aeb134180 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 QuotaTariffVO(QuotaTariffVO that) { 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 boolean setUsageTypeData(int usageType) { 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 e53051f2925b..5dfc12f7ef89 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.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 void getQuotaTariffValueToBeAppliedTestActivationRuleIsNullReturnTariffVa 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 void getQuotaTariffValueToBeAppliedTestActivationRuleIsEmptyReturnTariffV 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 void getQuotaTariffValueToBeAppliedTestActivationRuleIsEmptyReturnTariffV @Test public void getQuotaTariffValueToBeAppliedTestScriptResultIsNumberReturnIt() { BigDecimal expected = new BigDecimal(50.1); + List 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 void getQuotaTariffValueToBeAppliedTestScriptResultIsNumberReturnIt() { @Test public void getQuotaTariffValueToBeAppliedTestScriptResultIsTrueReturnTariffValue() { BigDecimal expected = new BigDecimal(236.84); + List 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 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 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 void getPresetVariablesTestHasTariffsWithActivationRuleReturnPresetVariab @Test public void aggregateQuotaTariffsValuesTestTariffsWereNotInPeriodToBeAppliedReturnZero() { - List tariffs = new ArrayList<>(); - tariffs.add(new QuotaTariffVO()); - tariffs.add(new QuotaTariffVO()); - tariffs.add(new QuotaTariffVO()); + List 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 void aggregateQuotaTariffsValuesTestTariffsIsEmptyReturnZero() { @Test public void aggregateQuotaTariffsValuesTestTariffsAreInPeriodToBeAppliedReturnAggregation() { - List tariffs = new ArrayList<>(); - tariffs.add(new QuotaTariffVO()); - tariffs.add(new QuotaTariffVO()); - tariffs.add(new QuotaTariffVO()); + List 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 void persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsagesTestReturn Assert.assertEquals(quotaUsageVoMock1, result.get(0)); Assert.assertEquals(quotaUsageVoMock2, result.get(1)); } + + private static List createTariffList() { + List 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 createLastAppliedTariffsPresetVariableList(int numberOfTariffs) { + List 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 b9406754b31f..137f42536dfb 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 void setEndDate(Date endDate) { 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 4fc1f08da881..6370cc57e4ef 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 long getEntityOwnerId() { 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 7b5667ac13db..88e90cc9ba98 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.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 QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff) { response.setDescription(tariff.getDescription()); response.setId(tariff.getUuid()); response.setRemoved(tariff.getRemoved()); + response.setPosition(tariff.getPosition()); return response; } @@ -414,6 +416,7 @@ public QuotaTariffVO updateQuotaTariffPlan(QuotaTariffUpdateCmd cmd) { String description = cmd.getDescription(); String activationRule = cmd.getActivationRule(); Date now = new Date(); + Integer position = cmd.getPosition(); warnQuotaTariffUpdateDeprecatedFields(cmd); @@ -428,7 +431,7 @@ public QuotaTariffVO updateQuotaTariffPlan(QuotaTariffUpdateCmd cmd) { 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 @@ protected void warnQuotaTariffUpdateDeprecatedFields(QuotaTariffUpdateCmd cmd) { } 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 @@ protected QuotaTariffVO persistNewQuotaTariff(QuotaTariffVO currentQuotaTariff, validateValueOnCreatingNewQuotaTariff(newQuotaTariff, value); validateStringsOnCreatingNewQuotaTariff(newQuotaTariff::setDescription, description); validateStringsOnCreatingNewQuotaTariff(newQuotaTariff::setActivationRule, activationRule); + validatePositionOnCreatingNewQuotaTariff(newQuotaTariff, position); _quotaTariffDao.addQuotaTariff(newQuotaTariff); return newQuotaTariff; @@ -481,6 +485,13 @@ protected QuotaTariffVO getNewQuotaTariffObject(QuotaTariffVO currentQuotaTariff return newQuotaTariff; } + protected void validatePositionOnCreatingNewQuotaTariff(QuotaTariffVO newQuotaTariff, Integer position) { + if (position != null) { + newQuotaTariff.setPosition(position); + } + } + + protected void validateStringsOnCreatingNewQuotaTariff(Consumer method, String value){ if (value != null) { method.accept(value.isBlank() ? null : value); @@ -663,6 +674,7 @@ public QuotaTariffVO createQuotaTariff(QuotaTariffCreateCmd cmd) { 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 QuotaTariffVO createQuotaTariff(QuotaTariffCreateCmd cmd) { "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 cec3634c76d8..6d844d78427a 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 void setRemoved(Date removed) { 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 da02b6d37093..71e38a5ab8cc 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 void persistNewQuotaTariffTestpersistNewQuotaTariff() { 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 void getQuotaEmailConfigurationVoTestExistingConfiguration() { 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 000000000000..3236c6c3c18f --- /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