2323import java .util .Comparator ;
2424import java .util .Date ;
2525import java .util .HashMap ;
26+ import java .util .LinkedHashMap ;
2627import java .util .LinkedHashSet ;
2728import java .util .List ;
2829import java .util .Map ;
3233import javax .inject .Inject ;
3334import 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 ;
3539import org .apache .cloudstack .framework .config .dao .ConfigurationDao ;
3640import org .apache .cloudstack .quota .activationrule .presetvariables .Configuration ;
3741import org .apache .cloudstack .quota .activationrule .presetvariables .GenericPresetVariable ;
4347import org .apache .cloudstack .quota .dao .QuotaAccountDao ;
4448import org .apache .cloudstack .quota .dao .QuotaBalanceDao ;
4549import org .apache .cloudstack .quota .dao .QuotaTariffDao ;
50+ import org .apache .cloudstack .quota .dao .QuotaTariffUsageDao ;
4651import org .apache .cloudstack .quota .dao .QuotaUsageDao ;
4752import org .apache .cloudstack .quota .vo .QuotaAccountVO ;
4853import org .apache .cloudstack .quota .vo .QuotaBalanceVO ;
54+ import org .apache .cloudstack .quota .vo .QuotaTariffUsageVO ;
4955import org .apache .cloudstack .quota .vo .QuotaTariffVO ;
5056import org .apache .cloudstack .quota .vo .QuotaUsageVO ;
5157import 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