Skip to content

Commit 2fd83e1

Browse files
authored
Introduce Quota resource statement API (#13236)
1 parent b0601e5 commit 2fd83e1

18 files changed

Lines changed: 805 additions & 198 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,8 @@ public class ApiConstants {
602602
public static final String SUITABLE_FOR_VM = "suitableforvirtualmachine";
603603
public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot";
604604
public static final String TARGET_IQN = "targetiqn";
605+
public static final String TARIFF_ID = "tariffid";
606+
public static final String TARIFF_NAME = "tariffname";
605607
public static final String TASKS_FILTER = "tasksfilter";
606608
public static final String TEMPLATE_FILTER = "templatefilter";
607609
public static final String TEMPLATE_ID = "templateid";
@@ -663,6 +665,7 @@ public class ApiConstants {
663665
public static final String VIRTUAL_MACHINE_STATE = "vmstate";
664666
public static final String VIRTUAL_MACHINES = "virtualmachines";
665667
public static final String USAGE_ID = "usageid";
668+
public static final String USAGE_NAME = "usagename";
666669
public static final String USAGE_TYPE = "usagetype";
667670
public static final String INCLUDE_TAGS = "includetags";
668671

@@ -879,6 +882,7 @@ public class ApiConstants {
879882
public static final String IS_SOURCE_NAT = "issourcenat";
880883
public static final String IS_STATIC_NAT = "isstaticnat";
881884
public static final String ITERATIONS = "iterations";
885+
public static final String ITEMS = "items";
882886
public static final String SORT_BY = "sortby";
883887
public static final String CHANGE_CIDR = "changecidr";
884888
public static final String PURPOSE = "purpose";

engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,9 @@ CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
202202
CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
203203
INDEX `i_image_transfer__backup_id`(`backup_id`)
204204
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
205+
206+
--- Quota resource statement
207+
INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order)
208+
SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order
209+
FROM cloud.role_permissions rp
210+
WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement');

framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Comparator;
2424
import java.util.Date;
2525
import java.util.HashMap;
26+
import java.util.LinkedHashMap;
2627
import java.util.LinkedHashSet;
2728
import java.util.List;
2829
import java.util.Map;
@@ -32,6 +33,9 @@
3233
import javax.inject.Inject;
3334
import javax.naming.ConfigurationException;
3435

36+
import com.cloud.utils.db.Transaction;
37+
import com.cloud.utils.db.TransactionCallback;
38+
import com.cloud.utils.db.TransactionLegacy;
3539
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3640
import org.apache.cloudstack.quota.activationrule.presetvariables.Configuration;
3741
import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable;
@@ -43,9 +47,11 @@
4347
import org.apache.cloudstack.quota.dao.QuotaAccountDao;
4448
import org.apache.cloudstack.quota.dao.QuotaBalanceDao;
4549
import org.apache.cloudstack.quota.dao.QuotaTariffDao;
50+
import org.apache.cloudstack.quota.dao.QuotaTariffUsageDao;
4651
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
4752
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
4853
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
54+
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
4955
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
5056
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
5157
import org.apache.cloudstack.usage.UsageUnitTypes;
@@ -86,7 +92,8 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
8692
private QuotaBalanceDao _quotaBalanceDao;
8793
@Inject
8894
private ConfigurationDao _configDao;
89-
95+
@Inject
96+
private QuotaTariffUsageDao quotaTariffUsageDao;
9097
@Inject
9198
protected PresetVariableHelper presetVariableHelper;
9299

@@ -311,33 +318,46 @@ protected List<QuotaUsageVO> createQuotaUsagesAccordingToQuotaTariffs(AccountVO
311318
String accountToString = account.reflectionToString();
312319
logger.info("Calculating quota usage of [{}] usage records for account [{}].", usageRecords.size(), accountToString);
313320

314-
List<Pair<UsageVO, QuotaUsageVO>> pairsUsageAndQuotaUsage = new ArrayList<>();
321+
Map<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> mapUsageAndQuotaUsage = new LinkedHashMap<>();
315322

316323
try (JsInterpreter jsInterpreter = new JsInterpreter(QuotaConfig.QuotaActivationRuleTimeout.value())) {
317324
for (UsageVO usageRecord : usageRecords) {
318325
int usageType = usageRecord.getUsageType();
319326

320327
if (!shouldCalculateUsageRecord(account, usageRecord)) {
321-
pairsUsageAndQuotaUsage.add(new Pair<>(usageRecord, null));
328+
mapUsageAndQuotaUsage.put(usageRecord, null);
322329
continue;
323330
}
324331

325332
Pair<List<QuotaTariffVO>, Boolean> pairQuotaTariffsPerUsageTypeAndHasActivationRule = mapQuotaTariffsPerUsageType.get(usageType);
326333
List<QuotaTariffVO> quotaTariffs = pairQuotaTariffsPerUsageTypeAndHasActivationRule.first();
327334
boolean hasAnyQuotaTariffWithActivationRule = pairQuotaTariffsPerUsageTypeAndHasActivationRule.second();
328335

329-
BigDecimal aggregatedQuotaTariffsValue = aggregateQuotaTariffsValues(usageRecord, quotaTariffs, hasAnyQuotaTariffWithActivationRule, jsInterpreter, accountToString);
336+
Map<QuotaTariffVO, BigDecimal> aggregatedQuotaTariffsAndValues = aggregateQuotaTariffsValues(usageRecord,
337+
quotaTariffs, hasAnyQuotaTariffWithActivationRule, jsInterpreter, accountToString);
338+
BigDecimal aggregatedQuotaTariffsValue = aggregatedQuotaTariffsAndValues.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
339+
logger.debug("The aggregation of the quota tariffs of account [{}] resulted in [{}] for the usage record [{}].",
340+
account, aggregatedQuotaTariffsValue, usageRecord);
330341

331342
QuotaUsageVO quotaUsage = createQuotaUsageAccordingToUsageUnit(usageRecord, aggregatedQuotaTariffsValue, accountToString);
343+
if (quotaUsage == null) {
344+
mapUsageAndQuotaUsage.put(usageRecord, null);
345+
continue;
346+
}
332347

333-
pairsUsageAndQuotaUsage.add(new Pair<>(usageRecord, quotaUsage));
348+
List<QuotaTariffUsageVO> quotaTariffUsages = new ArrayList<>();
349+
for (Map.Entry<QuotaTariffVO, BigDecimal> entry : aggregatedQuotaTariffsAndValues.entrySet()) {
350+
QuotaTariffUsageVO quotaTariffUsage = createQuotaTariffUsage(usageRecord, entry.getKey(), entry.getValue());
351+
quotaTariffUsages.add(quotaTariffUsage);
352+
}
353+
mapUsageAndQuotaUsage.put(usageRecord, new Pair<>(quotaUsage, quotaTariffUsages));
334354
}
335355
} catch (Exception e) {
336356
logger.error(String.format("Failed to calculate the quota usage for account [%s] due to [%s].", accountToString, e.getMessage()), e);
337357
return new ArrayList<>();
338358
}
339359

340-
return persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(pairsUsageAndQuotaUsage);
360+
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaUsageVO>>) status -> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(mapUsageAndQuotaUsage));
341361
}
342362

343363
protected boolean shouldCalculateUsageRecord(AccountVO accountVO, UsageVO usageRecord) {
@@ -349,31 +369,41 @@ protected boolean shouldCalculateUsageRecord(AccountVO accountVO, UsageVO usageR
349369
return true;
350370
}
351371

352-
protected List<QuotaUsageVO> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(List<Pair<UsageVO, QuotaUsageVO>> pairsUsageAndQuotaUsage) {
372+
protected List<QuotaUsageVO> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(Map<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> mapUsageAndQuotaTariffUsage) {
353373
List<QuotaUsageVO> quotaUsages = new ArrayList<>();
354374

355-
for (Pair<UsageVO, QuotaUsageVO> pairUsageAndQuotaUsage : pairsUsageAndQuotaUsage) {
356-
UsageVO usageVo = pairUsageAndQuotaUsage.first();
375+
for (Map.Entry<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> usageAndTariffUsage : mapUsageAndQuotaTariffUsage.entrySet()) {
376+
UsageVO usageVo = usageAndTariffUsage.getKey();
357377
usageVo.setQuotaCalculated(1);
358378
_usageDao.persistUsage(usageVo);
359379

360-
QuotaUsageVO quotaUsageVo = pairUsageAndQuotaUsage.second();
361-
if (quotaUsageVo != null) {
362-
_quotaUsageDao.persistQuotaUsage(quotaUsageVo);
363-
quotaUsages.add(quotaUsageVo);
380+
Pair<QuotaUsageVO, List<QuotaTariffUsageVO>> pairUsageAndTariffUsages = usageAndTariffUsage.getValue();
381+
if (pairUsageAndTariffUsages != null) {
382+
QuotaUsageVO quotaUsage = pairUsageAndTariffUsages.first();
383+
_quotaUsageDao.persistQuotaUsage(quotaUsage);
384+
quotaUsages.add(quotaUsage);
385+
386+
persistQuotaTariffUsages(pairUsageAndTariffUsages.second(), quotaUsage.getId());
364387
}
365388
}
366389

367390
return quotaUsages;
368391
}
369392

370-
protected BigDecimal aggregateQuotaTariffsValues(UsageVO usageRecord, List<QuotaTariffVO> quotaTariffs, boolean hasAnyQuotaTariffWithActivationRule,
371-
JsInterpreter jsInterpreter, String accountToString) {
393+
protected void persistQuotaTariffUsages(List<QuotaTariffUsageVO> quotaTariffUsages, Long quotaUsageId) {
394+
for (QuotaTariffUsageVO quotaTariffUsage : quotaTariffUsages) {
395+
quotaTariffUsage.setQuotaUsageId(quotaUsageId);
396+
quotaTariffUsageDao.persistQuotaTariffUsage(quotaTariffUsage);
397+
}
398+
}
399+
400+
protected Map<QuotaTariffVO, BigDecimal> aggregateQuotaTariffsValues(UsageVO usageRecord, List<QuotaTariffVO> quotaTariffs, boolean hasAnyQuotaTariffWithActivationRule,
401+
JsInterpreter jsInterpreter, String accountToString) {
372402
String usageRecordToString = usageRecord.toString(usageAggregationTimeZone);
373403
logger.debug("Validating usage record [{}] for account [{}] against [{}] quota tariffs.", usageRecordToString, accountToString, quotaTariffs.size());
374404

375405
PresetVariables presetVariables = getPresetVariables(hasAnyQuotaTariffWithActivationRule, usageRecord);
376-
BigDecimal aggregatedQuotaTariffsValue = BigDecimal.ZERO;
406+
Map<QuotaTariffVO, BigDecimal> aggregatedQuotaTariffsAndValues = new HashMap<>();
377407

378408
quotaTariffs.sort(Comparator.comparing(QuotaTariffVO::getPosition));
379409

@@ -382,10 +412,9 @@ protected BigDecimal aggregateQuotaTariffsValues(UsageVO usageRecord, List<Quota
382412

383413
for (QuotaTariffVO quotaTariff : quotaTariffs) {
384414
if (isQuotaTariffInPeriodToBeApplied(usageRecord, quotaTariff, accountToString)) {
385-
386415
BigDecimal tariffValue = getQuotaTariffValueToBeApplied(quotaTariff, jsInterpreter, presetVariables, lastTariffs);
387416

388-
aggregatedQuotaTariffsValue = aggregatedQuotaTariffsValue.add(tariffValue);
417+
aggregatedQuotaTariffsAndValues.put(quotaTariff, tariffValue);
389418

390419
Tariff tariffPresetVariable = new Tariff();
391420
tariffPresetVariable.setId(quotaTariff.getUuid());
@@ -394,10 +423,10 @@ protected BigDecimal aggregateQuotaTariffsValues(UsageVO usageRecord, List<Quota
394423
}
395424
}
396425

397-
logger.debug(String.format("The aggregation of the quota tariffs resulted in the value [%s] for the usage record [%s]. We will use this value to calculate the final"
398-
+ " usage value.", aggregatedQuotaTariffsValue, usageRecordToString));
426+
logger.debug("The aggregation of the quota tariffs resulted in [{}] quota tariffs for the usage record [{}]. The values of the quota tariffs will be used"
427+
+ " to calculate the final usage value.", aggregatedQuotaTariffsAndValues.size(), usageRecordToString);
399428

400-
return aggregatedQuotaTariffsValue;
429+
return aggregatedQuotaTariffsAndValues;
401430
}
402431

403432
protected PresetVariables getPresetVariables(boolean hasAnyQuotaTariffWithActivationRule, UsageVO usageRecord) {
@@ -408,6 +437,26 @@ protected PresetVariables getPresetVariables(boolean hasAnyQuotaTariffWithActiva
408437
return null;
409438
}
410439

440+
protected QuotaTariffUsageVO createQuotaTariffUsage(UsageVO usageRecord, QuotaTariffVO quotaTariff, BigDecimal quotaTariffValue) {
441+
BigDecimal quotaUsageValue = BigDecimal.ZERO;
442+
443+
if (!quotaTariffValue.equals(BigDecimal.ZERO)) {
444+
String quotaUnit = QuotaTypes.getQuotaType(usageRecord.getUsageType()).getQuotaUnit();
445+
logger.trace("Calculating the value of the quota tariff [{}] according to its value [{}] and its quota unit [{}].", quotaTariff, quotaTariffValue, quotaUnit);
446+
quotaUsageValue = getUsageValueAccordingToUsageUnitType(usageRecord, quotaTariffValue, quotaUnit);
447+
logger.debug("The calculation of the value of the quota tariff [{}] according to its value [{}] and its usage unit [{}] resulted in the value [{}].",
448+
quotaTariff, quotaTariffValue, quotaUnit, quotaUsageValue);
449+
} else {
450+
logger.debug("Quota tariff [{}] has no value to be calculated; therefore, it will be marked as value zero.", quotaTariff);
451+
}
452+
453+
QuotaTariffUsageVO quotaTariffUsage = new QuotaTariffUsageVO();
454+
quotaTariffUsage.setTariffId(quotaTariff.getId());
455+
quotaTariffUsage.setQuotaUsed(quotaUsageValue);
456+
457+
return quotaTariffUsage;
458+
}
459+
411460
/**
412461
* Returns the quota tariff value according to the result of the activation rule.<br/>
413462
* <ul>

0 commit comments

Comments
 (0)