This is an automated email from the ASF dual-hosted git repository.
winterhazel 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 3f6866d70a6 Refactor Quota balance (#12961)
3f6866d70a6 is described below
commit 3f6866d70a6e4eb8a12dd257d33bde5aae79aacf
Author: Fabricio Duarte <[email protected]>
AuthorDate: Tue May 26 10:36:04 2026 -0300
Refactor Quota balance (#12961)
---
.../org/apache/cloudstack/api/ApiConstants.java | 3 +
.../apache/cloudstack/quota/QuotaManagerImpl.java | 9 +-
.../cloudstack/quota/dao/QuotaBalanceDao.java | 10 +-
.../cloudstack/quota/dao/QuotaBalanceDaoImpl.java | 187 ++++++++-------------
.../quota/dao/QuotaBalanceDaoImplTest.java | 91 ++++++++++
.../cloudstack/api/command/QuotaBalanceCmd.java | 62 +++----
.../api/response/QuotaBalanceResponse.java | 132 ++++-----------
.../api/response/QuotaResponseBuilder.java | 7 +-
.../api/response/QuotaResponseBuilderImpl.java | 128 ++------------
.../org/apache/cloudstack/quota/QuotaService.java | 2 +-
.../apache/cloudstack/quota/QuotaServiceImpl.java | 100 +++++------
.../api/command/QuotaBalanceCmdTest.java | 42 ++---
.../api/response/QuotaResponseBuilderImplTest.java | 70 +++++---
.../cloudstack/quota/QuotaServiceImplTest.java | 95 ++++++++---
.../java/com/cloud/user/AccountManagerImpl.java | 8 +-
.../plugins/quota/test_quota_balance.py | 49 ++++--
16 files changed, 442 insertions(+), 553 deletions(-)
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 4d4ead277e5..694830ea2f3 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -69,6 +69,8 @@ public class ApiConstants {
public static final String BACKUP_VM_OFFERING_REMOVED =
"vmbackupofferingremoved";
public static final String IS_BACKUP_VM_EXPUNGED = "isbackupvmexpunged";
public static final String BACKUP_TOTAL = "backuptotal";
+ public static final String BALANCE = "balance";
+ public static final String BALANCES = "balances";
public static final String BASE64_IMAGE = "base64image";
public static final String BGP_PEERS = "bgppeers";
public static final String BGP_PEER_IDS = "bgppeerids";
@@ -171,6 +173,7 @@ public class ApiConstants {
public static final String DATACENTER_NAME = "datacentername";
public static final String DATADISKS_DETAILS = "datadisksdetails";
public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist";
+ public static final String DATE = "date";
public static final String DEFAULT_VALUE = "defaultvalue";
public static final String DELETE_PROTECTION = "deleteprotection";
public static final String DESCRIPTION = "description";
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 816144aa2f1..58f51eae9fc 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
@@ -150,8 +150,9 @@ public class QuotaManagerImpl extends ManagerBase
implements QuotaManager {
return;
}
- Date startDate = accountQuotaUsages.get(0).getStartDate();
- Date endDate = accountQuotaUsages.get(0).getEndDate();
+ QuotaUsageVO firstQuotaUsage = accountQuotaUsages.get(0);
+ Date startDate = firstQuotaUsage.getStartDate();
+ Date endDate = firstQuotaUsage.getEndDate();
Date lastQuotaUsageEndDate =
accountQuotaUsages.get(accountQuotaUsages.size() - 1).getEndDate();
LinkedHashSet<Pair<Date, Date>> periods = accountQuotaUsages.stream()
@@ -215,7 +216,7 @@ public class QuotaManagerImpl extends ManagerBase
implements QuotaManager {
logger.debug(String.format("Persisting the first quota balance
[%s] for account [%s].", firstBalance, accountToString));
_quotaBalanceDao.saveQuotaBalance(firstBalance);
} else {
- QuotaBalanceVO lastRealBalance =
_quotaBalanceDao.findLastBalanceEntry(accountId, domainId, startDate);
+ QuotaBalanceVO lastRealBalance =
_quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, startDate);
if (lastRealBalance == null) {
logger.warn("Account [{}] has quota usage entries, however it
does not have a quota balance.", accountToString);
@@ -244,7 +245,7 @@ public class QuotaManagerImpl extends ManagerBase
implements QuotaManager {
}
protected BigDecimal aggregateCreditBetweenDates(Long accountId, Long
domainId, Date startDate, Date endDate, String accountToString) {
- List<QuotaBalanceVO> creditsReceived =
_quotaBalanceDao.findCreditBalance(accountId, domainId, startDate, endDate);
+ List<QuotaBalanceVO> creditsReceived =
_quotaBalanceDao.findCreditBalances(accountId, domainId, startDate, endDate);
logger.debug("Account [{}] has [{}] credit entries before [{}].",
accountToString, creditsReceived.size(),
DateUtil.displayDateInTimezone(usageAggregationTimeZone,
endDate));
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java
index c694eaeefbe..2964746bdf0 100644
---
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java
@@ -28,16 +28,14 @@ public interface QuotaBalanceDao extends
GenericDao<QuotaBalanceVO, Long> {
QuotaBalanceVO saveQuotaBalance(QuotaBalanceVO qb);
- List<QuotaBalanceVO> findCreditBalance(Long accountId, Long domainId, Date
startDate, Date endDate);
+ List<QuotaBalanceVO> findCreditBalances(Long accountId, Long domainId,
Date startDate, Date endDate);
- QuotaBalanceVO findLastBalanceEntry(Long accountId, Long domainId, Date
beforeThis);
+ QuotaBalanceVO getLastQuotaBalanceEntry(Long accountId, Long domainId,
Date beforeThis);
QuotaBalanceVO findLaterBalanceEntry(Long accountId, Long domainId, Date
afterThis);
- List<QuotaBalanceVO> findQuotaBalance(Long accountId, Long domainId, Date
startDate, Date endDate);
+ List<QuotaBalanceVO> listQuotaBalances(Long accountId, Long domainId, Date
startDate, Date endDate);
- List<QuotaBalanceVO> lastQuotaBalanceVO(Long accountId, Long domainId,
Date startDate);
-
- BigDecimal lastQuotaBalance(Long accountId, Long domainId, Date startDate);
+ BigDecimal getLastQuotaBalance(Long accountId, Long domainId);
}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java
index 01272d1a618..21553ebd27b 100644
---
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java
@@ -18,11 +18,14 @@ package org.apache.cloudstack.quota.dao;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.Filter;
@@ -32,160 +35,104 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
-import com.cloud.utils.db.TransactionStatus;
@Component
public class QuotaBalanceDaoImpl extends GenericDaoBase<QuotaBalanceVO, Long>
implements QuotaBalanceDao {
+ private static final Logger logger =
LogManager.getLogger(QuotaBalanceDaoImpl.class);
@Override
- public QuotaBalanceVO findLastBalanceEntry(final Long accountId, final
Long domainId, final Date beforeThis) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<QuotaBalanceVO>() {
- @Override
- public QuotaBalanceVO doInTransaction(final TransactionStatus
status) {
- List<QuotaBalanceVO> quotaBalanceEntries = new ArrayList<>();
- Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
false, 0L, 1L);
- QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ,
accountId);
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ,
domainId);
- qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
+ public QuotaBalanceVO getLastQuotaBalanceEntry(final Long accountId, final
Long domainId, final Date beforeThis) {
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<QuotaBalanceVO>) status -> {
+ Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
false, 0L, 1L);
+ QueryBuilder<QuotaBalanceVO> qb =
getQuotaBalanceQueryBuilder(accountId, domainId);
+ qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
+
+ if (beforeThis != null) {
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LT,
beforeThis);
- quotaBalanceEntries = search(qb.create(), filter);
- return !quotaBalanceEntries.isEmpty() ?
quotaBalanceEntries.get(0) : null;
}
+
+ List<QuotaBalanceVO> quotaBalanceEntries = search(qb.create(),
filter);
+ return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0)
: null;
});
}
@Override
public QuotaBalanceVO findLaterBalanceEntry(final Long accountId, final
Long domainId, final Date afterThis) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<QuotaBalanceVO>() {
- @Override
- public QuotaBalanceVO doInTransaction(final TransactionStatus
status) {
- List<QuotaBalanceVO> quotaBalanceEntries = new ArrayList<>();
- Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
true, 0L, 1L);
- QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ,
accountId);
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ,
domainId);
- qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
- qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT,
afterThis);
- quotaBalanceEntries = search(qb.create(), filter);
- return quotaBalanceEntries.size() > 0 ?
quotaBalanceEntries.get(0) : null;
- }
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<QuotaBalanceVO>) status -> {
+ Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
true, 0L, 1L);
+ QueryBuilder<QuotaBalanceVO> qb =
getQuotaBalanceQueryBuilder(accountId, domainId);
+ qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
+ qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT,
afterThis);
+
+ List<QuotaBalanceVO> quotaBalanceEntries = search(qb.create(),
filter);
+ return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0)
: null;
});
}
@Override
public QuotaBalanceVO saveQuotaBalance(final QuotaBalanceVO qb) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<QuotaBalanceVO>() {
- @Override
- public QuotaBalanceVO doInTransaction(final TransactionStatus
status) {
- return persist(qb);
- }
- });
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<QuotaBalanceVO>) status -> persist(qb));
}
@Override
- public List<QuotaBalanceVO> findCreditBalance(final Long accountId, final
Long domainId, final Date lastbalancedate, final Date beforeThis) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<List<QuotaBalanceVO>>() {
- @Override
- public List<QuotaBalanceVO> doInTransaction(final
TransactionStatus status) {
- if ((lastbalancedate != null) && (beforeThis != null) &&
lastbalancedate.before(beforeThis)) {
- Filter filter = new Filter(QuotaBalanceVO.class,
"updatedOn", true, 0L, Long.MAX_VALUE);
- QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ,
accountId);
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ,
domainId);
- qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT,
0);
- qb.and(qb.entity().getUpdatedOn(),
SearchCriteria.Op.BETWEEN, lastbalancedate, beforeThis);
- return search(qb.create(), filter);
- } else {
- return new ArrayList<QuotaBalanceVO>();
- }
+ public List<QuotaBalanceVO> findCreditBalances(final Long accountId, final
Long domainId, final Date startDate, final Date endDate) {
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<List<QuotaBalanceVO>>) status -> {
+ if (ObjectUtils.anyNull(startDate, endDate) ||
startDate.after(endDate)) {
+ return new ArrayList<>();
}
- });
- }
- @Override
- public List<QuotaBalanceVO> findQuotaBalance(final Long accountId, final
Long domainId, final Date startDate, final Date endDate) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<List<QuotaBalanceVO>>() {
- @Override
- public List<QuotaBalanceVO> doInTransaction(final
TransactionStatus status) {
- List<QuotaBalanceVO> quotaUsageRecords = null;
- QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
- if (accountId != null) {
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ,
accountId);
- }
- if (domainId != null) {
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ,
domainId);
- }
- if ((startDate != null) && (endDate != null) &&
startDate.before(endDate)) {
- qb.and(qb.entity().getUpdatedOn(),
SearchCriteria.Op.BETWEEN, startDate, endDate);
- } else {
- return Collections.<QuotaBalanceVO> emptyList();
- }
- quotaUsageRecords = listBy(qb.create());
- if (quotaUsageRecords.size() == 0) {
- quotaUsageRecords.addAll(lastQuotaBalanceVO(accountId,
domainId, startDate));
- }
- return quotaUsageRecords;
+ Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
true, 0L, Long.MAX_VALUE);
+ QueryBuilder<QuotaBalanceVO> qb =
getQuotaBalanceQueryBuilder(accountId, domainId);
+ qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0);
+ qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN,
startDate, endDate);
- }
+ return search(qb.create(), filter);
});
-
}
@Override
- public List<QuotaBalanceVO> lastQuotaBalanceVO(final Long accountId, final
Long domainId, final Date pivotDate) {
- return Transaction.execute(TransactionLegacy.USAGE_DB, new
TransactionCallback<List<QuotaBalanceVO>>() {
- @Override
- public List<QuotaBalanceVO> doInTransaction(final
TransactionStatus status) {
- List<QuotaBalanceVO> quotaUsageRecords = null;
- List<QuotaBalanceVO> trimmedRecords = new
ArrayList<QuotaBalanceVO>();
- Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
false, 0L, 100L);
- // ASSUMPTION there will be less than 100 continuous credit
- // transactions
- QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
- if (accountId != null) {
- qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ,
accountId);
- }
- if (domainId != null) {
- qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ,
domainId);
- }
- if ((pivotDate != null)) {
- qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ,
pivotDate);
- }
- quotaUsageRecords = search(qb.create(), filter);
-
- // get records before startDate to find start balance
- for (QuotaBalanceVO entry : quotaUsageRecords) {
- if (logger.isDebugEnabled()) {
- logger.debug("FindQuotaBalance Entry=" + entry);
- }
- if (entry.getCreditsId() > 0) {
- trimmedRecords.add(entry);
- } else {
- trimmedRecords.add(entry);
- break; // add only consecutive credit entries and last
balance entry
- }
- }
- return trimmedRecords;
+ public List<QuotaBalanceVO> listQuotaBalances(final Long accountId, final
Long domainId, final Date startDate, final Date endDate) {
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<List<QuotaBalanceVO>>) status -> {
+ QueryBuilder<QuotaBalanceVO> qb =
getQuotaBalanceQueryBuilder(accountId, domainId);
+ qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
+ if (startDate != null) {
+ qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GTEQ,
startDate);
+ }
+ if (endDate != null) {
+ qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ,
endDate);
}
+
+ Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn",
true);
+ return listBy(qb.create(), filter);
});
}
@Override
- public BigDecimal lastQuotaBalance(final Long accountId, final Long
domainId, Date startDate) {
- List<QuotaBalanceVO> quotaBalance = lastQuotaBalanceVO(accountId,
domainId, startDate);
- BigDecimal finalBalance = new BigDecimal(0);
- if (quotaBalance.isEmpty()) {
- logger.info("There are no balance entries on or before the
requested date.");
- return finalBalance;
+ public BigDecimal getLastQuotaBalance(final Long accountId, final Long
domainId) {
+ QuotaBalanceVO quotaBalance = getLastQuotaBalanceEntry(accountId,
domainId, null);
+ BigDecimal finalBalance = BigDecimal.ZERO;
+ Date startDate = DateUtils.addDays(new Date(), -1);
+ if (quotaBalance == null) {
+ logger.info("There are no balance entries for account [{}] and
domain [{}]. Considering only new added credits.", accountId, domainId);
+ } else {
+ finalBalance = quotaBalance.getCreditBalance();
+ startDate = quotaBalance.getUpdatedOn();
}
- for (QuotaBalanceVO entry : quotaBalance) {
- if (logger.isDebugEnabled()) {
- logger.debug("lastQuotaBalance Entry=" + entry);
- }
- finalBalance = finalBalance.add(entry.getCreditBalance());
+
+ List<QuotaBalanceVO> credits = findCreditBalances(accountId, domainId,
startDate, new Date());
+
+ for (QuotaBalanceVO credit : credits) {
+ finalBalance = finalBalance.add(credit.getCreditBalance());
}
+
return finalBalance;
}
+ private QueryBuilder<QuotaBalanceVO> getQuotaBalanceQueryBuilder(Long
accountId, Long domainId) {
+ QueryBuilder<QuotaBalanceVO> qb =
QueryBuilder.create(QuotaBalanceVO.class);
+ qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
+ qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
+ return qb;
+ }
+
}
diff --git
a/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.java
b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.java
new file mode 100644
index 00000000000..afdd88f8d1d
--- /dev/null
+++
b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.java
@@ -0,0 +1,91 @@
+
+// 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.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class QuotaBalanceDaoImplTest {
+ QuotaBalanceDaoImpl quotaBalanceDaoImplSpy =
Mockito.spy(QuotaBalanceDaoImpl.class);
+
+ @Mock
+ QuotaBalanceVO quotaBalanceVoMock;
+
+ @Test
+ public void
getLastQuotaBalanceTestLastEntryIsNullAndNoCreditsReturnsZero() {
+
Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+ Mockito.doReturn(new
ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(), Mockito.any());
+
+ BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(1L, 2L);
+
+ Assert.assertEquals(BigDecimal.ZERO, result);
+ }
+
+ @Test
+ public void getLastQuotaBalanceTestReturnsLastEntryAndNoCredits() {
+ BigDecimal expected = BigDecimal.valueOf(-1542.46);
+
Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+ Mockito.doReturn(expected).when(quotaBalanceVoMock).getCreditBalance();
+ Mockito.doReturn(new
ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(), Mockito.any());
+
+ BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
+
+ Assert.assertEquals(expected, result);
+ }
+
+ @Test
+ public void getLastQuotaBalanceTestReturnsLastEntryPlusCredits() {
+ BigDecimal balance = BigDecimal.valueOf(-1542.46);
+ BigDecimal credit1 = new BigDecimal("150.14");
+ BigDecimal credit2 = new BigDecimal("78.96");
+ BigDecimal expected = balance.add(credit1).add(credit2);
+
+
Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+ Mockito.doReturn(balance, credit1,
credit2).when(quotaBalanceVoMock).getCreditBalance();
+ Mockito.doReturn(Arrays.asList(quotaBalanceVoMock,
quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(), Mockito.any());
+
+ BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
+
+ Assert.assertEquals(expected, result);
+ }
+
+ @Test
+ public void getLastQuotaBalanceTestReturnsLastEntryIsNullPlusCredits() {
+ BigDecimal credit1 = new BigDecimal("150.14");
+ BigDecimal credit2 = new BigDecimal("78.96");
+ BigDecimal expected = credit1.add(credit2);
+
+
Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+ Mockito.doReturn(credit1,
credit2).when(quotaBalanceVoMock).getCreditBalance();
+ Mockito.doReturn(Arrays.asList(quotaBalanceVoMock,
quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(), Mockito.any());
+
+ BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
+
+ Assert.assertEquals(expected, result);
+ }
+}
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java
index 0cec0df6618..e5c89c5de1c 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java
@@ -17,12 +17,9 @@
package org.apache.cloudstack.api.command;
import java.util.Date;
-import java.util.List;
import javax.inject.Inject;
-import com.cloud.user.Account;
-
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@@ -30,36 +27,41 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.QuotaBalanceResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
-import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
-import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
+import org.apache.commons.lang3.ObjectUtils;
-@APICommand(name = "quotaBalance", responseObject =
QuotaStatementItemResponse.class, description = "Create a quota balance
statement", since = "4.7.0", requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
+@APICommand(name = "quotaBalance", responseObject =
QuotaBalanceResponse.class, description = "Create Quota balance statements for
the provided Accounts or Projects.", since = "4.7.0", requestHasSensitiveInfo =
false, responseHasSensitiveInfo = false,
httpMethod = "GET")
public class QuotaBalanceCmd extends BaseCmd {
-
-
- @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
required = true, description = "Account Id for which statement needs to be
generated")
+ @ACL
+ @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
description = "Name of the Account for which balance statements will be
generated. " +
+ "Deprecated, please use 'accountid' instead.")
private String accountName;
@ACL
- @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID,
required = true, entityType = DomainResponse.class, description = "If domain Id
is given and the caller is domain admin then the statement is generated for
domain.")
+ @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID,
entityType = DomainResponse.class, description = "ID of the domain which the
Account identified by 'account' belongs to." +
+ "Deprecated, please use 'accountid' instead.")
private Long domainId;
- @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE,
description = "End of the period of the Quota balance." +
- ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
+ @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE,
description = "Date of the last Quota balance to be returned. Must be informed
together with the " +
+ "startdate parameter, and can not be before startdate. " +
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
private Date endDate;
- @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE,
description = "Start of the period of the Quota balance. " +
+ @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE,
description = "Date of the first Quota balance to be returned. Must be before
today. " +
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@ACL
- @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID,
entityType = AccountResponse.class, description = "List usage records for the
specified Account")
+ @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID,
entityType = AccountResponse.class, description = "ID of the Account for which
balance statements will be generated.")
private Long accountId;
+ @ACL
+ @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID,
entityType = ProjectResponse.class, description = "ID of the Project for which
balance statements will be generated. Can not be specified with accountid.")
+ private Long projectId;
+
@Inject
QuotaResponseBuilder _responseBuilder;
@@ -88,48 +90,32 @@ public class QuotaBalanceCmd extends BaseCmd {
}
public Date getEndDate() {
- if (endDate == null){
- return null;
- }
- else{
- return _responseBuilder.startOfNextDay(new
Date(endDate.getTime()));
- }
+ return endDate;
}
public void setEndDate(Date endDate) {
- this.endDate = endDate == null ? null : new Date(endDate.getTime());
+ this.endDate = endDate;
}
public Date getStartDate() {
- return startDate == null ? null : new Date(startDate.getTime());
+ return startDate;
}
public void setStartDate(Date startDate) {
- this.startDate = startDate == null ? null : new
Date(startDate.getTime());
+ this.startDate = startDate;
}
@Override
public long getEntityOwnerId() {
- if (accountId != null) {
- return accountId;
- }
- Account account = _accountService.getActiveAccountByName(accountName,
domainId);
- if (account != null) {
- return account.getAccountId();
+ if (ObjectUtils.allNull(accountId, accountName, domainId, projectId)) {
+ return -1;
}
- return Account.ACCOUNT_ID_SYSTEM;
+ return _accountService.finalizeAccountId(accountId, accountName,
domainId, projectId);
}
@Override
public void execute() {
- List<QuotaBalanceVO> quotaUsage =
_responseBuilder.getQuotaBalance(this);
-
- QuotaBalanceResponse response;
- if (endDate == null) {
- response =
_responseBuilder.createQuotaLastBalanceResponse(quotaUsage, getStartDate());
- } else {
- response = _responseBuilder.createQuotaBalanceResponse(quotaUsage,
getStartDate(), new Date(endDate.getTime()));
- }
+ QuotaBalanceResponse response =
_responseBuilder.createQuotaBalanceResponse(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
index 625f4829c67..714066b048a 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java
@@ -17,137 +17,65 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
-import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import com.cloud.serializer.Param;
public class QuotaBalanceResponse extends BaseResponse {
- @SerializedName("accountid")
- @Param(description = "Account ID")
- private Long accountId;
-
- @SerializedName("account")
- @Param(description = "Account name")
- private String accountName;
-
- @SerializedName("domain")
- @Param(description = "Domain ID")
- private Long domainId;
-
- @SerializedName("startquota")
- @Param(description = "Quota started with")
- private BigDecimal startQuota;
-
- @SerializedName("endquota")
- @Param(description = "Quota by end of this period")
- private BigDecimal endQuota;
-
- @SerializedName("credits")
- @Param(description = "List of credits made during this period")
- private List<QuotaCreditsResponse> credits = null;
-
- @SerializedName("startdate")
- @Param(description = "Start date")
- private Date startDate = null;
-
- @SerializedName("enddate")
- @Param(description = "End date")
- private Date endDate = null;
-
- @SerializedName("currency")
- @Param(description = "Currency")
+ @SerializedName(ApiConstants.CURRENCY)
+ @Param(description = "Balance's currency.")
private String currency;
- public QuotaBalanceResponse() {
- super();
- credits = new ArrayList<QuotaCreditsResponse>();
- }
-
- public Long getAccountId() {
- return accountId;
- }
-
- public void setAccountId(Long accountId) {
- this.accountId = accountId;
- }
-
- public String getAccountName() {
- return accountName;
- }
+ @SerializedName(ApiConstants.DATE)
+ @Param(description = "Balance's date.")
+ private Date date;
- public void setAccountName(String accountName) {
- this.accountName = accountName;
- }
+ @SerializedName(ApiConstants.BALANCE)
+ @Param(description = "Balance's value.")
+ private BigDecimal balance;
- public Long getDomainId() {
- return domainId;
- }
-
- public void setDomainId(Long domainId) {
- this.domainId = domainId;
- }
+ @SerializedName(ApiConstants.BALANCES)
+ @Param(description = "List of balances in the period.")
+ private List<QuotaBalanceResponse> balances;
- public BigDecimal getStartQuota() {
- return startQuota;
- }
-
- public void setStartQuota(BigDecimal startQuota) {
- this.startQuota = startQuota.setScale(2, RoundingMode.HALF_EVEN);
- }
-
- public BigDecimal getEndQuota() {
- return endQuota;
- }
-
- public void setEndQuota(BigDecimal endQuota) {
- this.endQuota = endQuota.setScale(2, RoundingMode.HALF_EVEN);
- }
-
- public List<QuotaCreditsResponse> getCredits() {
- return credits;
- }
-
- public void setCredits(List<QuotaCreditsResponse> credits) {
- this.credits = credits;
+ public QuotaBalanceResponse() {
+ super("balance");
}
- public void addCredits(QuotaBalanceVO credit) {
- QuotaCreditsResponse cr = new QuotaCreditsResponse();
- cr.setCredit(credit.getCreditBalance());
- cr.setCreditedOn(credit.getUpdatedOn());
- credits.add(0, cr);
+ public QuotaBalanceResponse(Date date, BigDecimal balance) {
+ super("balance");
+ this.date = date;
+ this.balance = balance;
}
- public Date getStartDate() {
- return startDate == null ? null : new Date(startDate.getTime());
+ public void setCurrency(String currency) {
+ this.currency = currency;
}
- public void setStartDate(Date startDate) {
- this.startDate = startDate == null ? null : new
Date(startDate.getTime());
+ public void setBalances(List<QuotaBalanceResponse> balances) {
+ this.balances = balances;
}
- public Date getEndDate() {
- return endDate == null ? null : new Date(endDate.getTime());
+ public String getCurrency() {
+ return currency;
}
- public void setEndDate(Date endDate) {
- this.endDate = endDate == null ? null : new Date(endDate.getTime());
+ public List<QuotaBalanceResponse> getBalances() {
+ return balances;
}
- public String getCurrency() {
- return currency;
+ public Date getDate() {
+ return date;
}
- public void setCurrency(String currency) {
- this.currency = currency;
+ public BigDecimal getBalance() {
+ return balance;
}
}
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
index bde905c487b..f0390bf626d 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
@@ -29,7 +29,6 @@ import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
-import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
@@ -50,14 +49,10 @@ public interface QuotaResponseBuilder {
QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd);
- QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO>
quotaUsage, Date startDate, Date endDate);
+ QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd cmd);
Pair<List<QuotaSummaryResponse>, Integer>
createQuotaSummaryResponse(QuotaSummaryCmd cmd);
- QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO>
quotaBalance, Date startDate);
-
- List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd);
-
QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double
amount, Long updatedBy, Boolean enforce);
List<QuotaEmailTemplateResponse>
listQuotaEmailTemplates(QuotaEmailTemplateListCmd cmd);
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 c919bb5887c..231ba777e60 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
@@ -26,13 +26,10 @@ import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
-import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -329,93 +326,18 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
}
@Override
- public QuotaBalanceResponse
createQuotaBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate,
Date endDate) {
- if (quotaBalance == null || quotaBalance.isEmpty()) {
- throw new InvalidParameterValueException("The request period does
not contain balance entries.");
- }
- Collections.sort(quotaBalance, new Comparator<QuotaBalanceVO>() {
- @Override
- public int compare(QuotaBalanceVO o1, QuotaBalanceVO o2) {
- o1 = o1 == null ? new QuotaBalanceVO() : o1;
- o2 = o2 == null ? new QuotaBalanceVO() : o2;
- return o2.getUpdatedOn().compareTo(o1.getUpdatedOn()); // desc
- }
- });
-
- boolean have_balance_entries = false;
- //check that there is at least one balance entry
- for (Iterator<QuotaBalanceVO> it = quotaBalance.iterator();
it.hasNext();) {
- QuotaBalanceVO entry = it.next();
- if (entry.isBalanceEntry()) {
- have_balance_entries = true;
- break;
- }
- }
- //if last entry is a credit deposit then remove that as that is already
- //accounted for in the starting balance after that entry, note the
sort is desc
- if (have_balance_entries) {
- ListIterator<QuotaBalanceVO> li =
quotaBalance.listIterator(quotaBalance.size());
- // Iterate in reverse.
- while (li.hasPrevious()) {
- QuotaBalanceVO entry = li.previous();
- if (logger.isDebugEnabled()) {
- logger.debug("createQuotaBalanceResponse: Entry=" + entry);
- }
- if (entry.getCreditsId() > 0) {
- li.remove();
- } else {
- break;
- }
- }
- }
+ public QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd
cmd) {
+ List<QuotaBalanceVO> quotaBalances =
_quotaService.listQuotaBalancesForAccount(cmd.getEntityOwnerId(),
cmd.getStartDate(), cmd.getEndDate());
- int quota_activity = quotaBalance.size();
- QuotaBalanceResponse resp = new QuotaBalanceResponse();
- BigDecimal lastCredits = new BigDecimal(0);
- boolean consecutive = true;
- for (Iterator<QuotaBalanceVO> it = quotaBalance.iterator();
it.hasNext();) {
- QuotaBalanceVO entry = it.next();
- if (logger.isDebugEnabled()) {
- logger.debug("createQuotaBalanceResponse: All Credit Entry=" +
entry);
- }
- if (entry.getCreditsId() > 0) {
- if (consecutive) {
- lastCredits = lastCredits.add(entry.getCreditBalance());
- }
- resp.addCredits(entry);
- it.remove();
- } else {
- consecutive = false;
- }
- }
+ List<QuotaBalanceResponse> balances = quotaBalances.stream()
+ .map(balance -> new
QuotaBalanceResponse(balance.getUpdatedOn(), balance.getCreditBalance()))
+ .collect(Collectors.toList());
- if (quota_activity > 0 && quotaBalance.size() > 0) {
- // order is desc last item is the start item
- QuotaBalanceVO startItem = quotaBalance.get(quotaBalance.size() -
1);
- QuotaBalanceVO endItem = quotaBalance.get(0);
- resp.setStartDate(startDate);
- resp.setStartQuota(startItem.getCreditBalance());
- resp.setEndDate(endDate);
- if (logger.isDebugEnabled()) {
- logger.debug("createQuotaBalanceResponse: Start Entry=" +
startItem);
- logger.debug("createQuotaBalanceResponse: End Entry=" +
endItem);
- }
- resp.setEndQuota(endItem.getCreditBalance().add(lastCredits));
- } else if (quota_activity > 0) {
- // order is desc last item is the start item
- resp.setStartDate(startDate);
- resp.setStartQuota(new BigDecimal(0));
- resp.setEndDate(endDate);
- resp.setEndQuota(new BigDecimal(0).add(lastCredits));
- } else {
- resp.setStartDate(startDate);
- resp.setEndDate(endDate);
- resp.setStartQuota(new BigDecimal(0));
- resp.setEndQuota(new BigDecimal(0));
- }
- resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
- resp.setObjectName("balance");
- return resp;
+ QuotaBalanceResponse response = new QuotaBalanceResponse();
+ response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
+ response.setBalances(balances);
+
+ return response;
}
@Override
@@ -784,7 +706,7 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
throw new InvalidParameterValueException("Account does not exist
with account id " + accountId);
}
final boolean lockAccountEnforcement =
"true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value());
- final BigDecimal currentAccountBalance =
_quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new
Date(depositedOn.getTime())));
+ final BigDecimal currentAccountBalance =
_quotaBalanceDao.getLastQuotaBalance(accountId, domainId);
logger.debug("Depositing [{}] credits on adjusted date [{}]; current
balance is [{}].", amount,
DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(),
depositedOn), currentAccountBalance);
// update quota account with the balance
@@ -848,34 +770,6 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
return false;
}
- @Override
- public QuotaBalanceResponse
createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date
startDate) {
- if (quotaBalance == null) {
- throw new InvalidParameterValueException("There are no balance
entries on or before the requested date.");
- }
- if (startDate == null) {
- startDate = new Date();
- }
- QuotaBalanceResponse resp = new QuotaBalanceResponse();
- BigDecimal lastCredits = new BigDecimal(0);
- for (QuotaBalanceVO entry : quotaBalance) {
- logger.debug("createQuotaLastBalanceResponse Date={} balance={}
credit={}",
-
DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(),
entry.getUpdatedOn()),
- entry.getCreditBalance(), entry.getCreditsId());
-
- lastCredits = lastCredits.add(entry.getCreditBalance());
- }
- resp.setStartQuota(lastCredits);
- resp.setStartDate(startDate);
- resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
- resp.setObjectName("balance");
- return resp;
- }
-
- @Override
- public List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd) {
- return _quotaService.findQuotaBalanceVO(cmd.getAccountId(),
cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate());
- }
@Override
public Date startOfNextDay(Date date) {
LocalDate localDate =
date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
index 78acfc11682..478e43d2e20 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
@@ -30,7 +30,7 @@ public interface QuotaService extends PluggableService {
List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName,
Long domainId, Integer usageType, Date startDate, Date endDate);
- List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String
accountName, Long domainId, Date startDate, Date endDate);
+ List<QuotaBalanceVO> listQuotaBalancesForAccount(Long accountId, Date
startDate, Date endDate);
void setLockAccount(Long accountId, Boolean state);
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
index a0ba2fbc751..e1ec2ebb2a6 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java
@@ -28,6 +28,7 @@ import javax.naming.ConfigurationException;
import com.cloud.projects.ProjectManager;
import com.cloud.user.AccountService;
+import com.cloud.utils.DateUtil;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsCmd;
@@ -58,17 +59,16 @@ import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.time.DateUtils;
import org.springframework.stereotype.Component;
import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.component.ManagerBase;
-import com.cloud.utils.db.Filter;
@Component
public class QuotaServiceImpl extends ManagerBase implements QuotaService,
Configurable, QuotaConfig {
@@ -152,63 +152,53 @@ public class QuotaServiceImpl extends ManagerBase
implements QuotaService, Confi
}
@Override
- public List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String
accountName, Long domainId, Date startDate, Date endDate) {
- if ((accountId == null) && (accountName != null) && (domainId !=
null)) {
- Account userAccount = null;
- Account caller = CallContext.current().getCallingAccount();
- if (_domainDao.isChildDomain(caller.getDomainId(), domainId)) {
- Filter filter = new Filter(AccountVO.class, "id",
Boolean.FALSE, null, null);
- List<AccountVO> accounts =
_accountDao.listAccounts(accountName, domainId, filter);
- if (!accounts.isEmpty()) {
- userAccount = accounts.get(0);
- }
- if (userAccount != null) {
- accountId = userAccount.getId();
- } else {
- throw new InvalidParameterValueException("Unable to find
account " + accountName + " in domain " + domainId);
- }
- } else {
- throw new PermissionDeniedException("Invalid Domain Id or
Account");
- }
+ public List<QuotaBalanceVO> listQuotaBalancesForAccount(Long accountId,
Date startDate, Date endDate) {
+ validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate,
endDate);
+
+ if (accountId == -1) {
+ accountId = CallContext.current().getCallingAccountId();
}
+ Account account = _accountDao.findByIdIncludingRemoved(accountId);
+ Long domainId = account.getDomainId();
- startDate = startDate == null ? new Date() : startDate;
+ if (startDate == null && endDate == null) {
+ logger.debug("Retrieving last quota balance for {}.", account);
+ QuotaBalanceVO lastQuotaBalance =
_quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, null);
- if (endDate == null) {
- // adjust start date to end of day as there is no end date
- startDate = _respBldr.startOfNextDay(startDate);
- if (logger.isDebugEnabled()) {
- logger.debug("getQuotaBalance1: Getting quota balance records
for account: " + accountId + ", domainId: " + domainId + ", on or before " +
startDate);
- }
- List<QuotaBalanceVO> qbrecords =
_quotaBalanceDao.lastQuotaBalanceVO(accountId, domainId, startDate);
- if (logger.isDebugEnabled()) {
- logger.debug("Found records size=" + qbrecords.size());
- }
- if (qbrecords.isEmpty()) {
- logger.info("Incorrect Date there are no quota records before
this date " + startDate);
- return qbrecords;
- } else {
- return qbrecords;
- }
- } else {
- if (startDate.before(endDate)) {
- if (logger.isDebugEnabled()) {
- logger.debug("getQuotaBalance2: Getting quota balance
records for account: " + accountId + ", domainId: " + domainId + ", between " +
startDate
- + " and " + endDate);
- }
- List<QuotaBalanceVO> qbrecords =
_quotaBalanceDao.findQuotaBalance(accountId, domainId, startDate, endDate);
- if (logger.isDebugEnabled()) {
- logger.debug("getQuotaBalance3: Found records size=" +
qbrecords.size());
- }
- if (qbrecords.isEmpty()) {
- logger.info("There are no quota records between these
dates start date " + startDate + " and end date:" + endDate);
- return qbrecords;
- } else {
- return qbrecords;
- }
- } else {
- throw new InvalidParameterValueException("Incorrect Date
Range. Start date: " + startDate + " is after end date:" + endDate);
+ if (lastQuotaBalance == null) {
+ logger.debug("Did not found a quota balance entry for {}.",
account);
+ return new ArrayList<>();
}
+
+ return List.of(lastQuotaBalance);
+ }
+
+ if (endDate == null) {
+ endDate = DateUtils.addDays(new Date(), -1);
+ }
+
+ List<QuotaBalanceVO> quotaBalances =
_quotaBalanceDao.listQuotaBalances(accountId, domainId, startDate, endDate);
+
+ if (quotaBalances.isEmpty()) {
+ logger.info("There are no quota balances for {} between [{}] and
[{}].", account,
+ DateUtil.getOutputString(startDate),
DateUtil.getOutputString(endDate));
+ }
+
+ return quotaBalances;
+ }
+
+ protected void
validateStartDateAndEndDateForListQuotaBalancesForAccount(Date startDate, Date
endDate) {
+ if (startDate == null && endDate != null) {
+ throw new InvalidParameterValueException("Parameter \"enddate\"
must be informed together with parameter \"startdate\".");
+ }
+
+ Date now = new Date();
+ if (startDate != null && startDate.after(now)) {
+ throw new InvalidParameterValueException("The last balance can be
at most from yesterday; therefore, the start date must be before today.");
+ }
+
+ if (ObjectUtils.allNotNull(startDate, endDate) &&
startDate.after(endDate)) {
+ throw new InvalidParameterValueException("The start date cannot be
after the end date.");
}
}
diff --git
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java
index adabc694f25..cd0eeb2c3b4 100644
---
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java
+++
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java
@@ -16,18 +16,15 @@
// under the License.
package org.apache.cloudstack.api.command;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
import org.apache.cloudstack.api.response.QuotaBalanceResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
-import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import junit.framework.TestCase;
@@ -36,31 +33,20 @@ import junit.framework.TestCase;
public class QuotaBalanceCmdTest extends TestCase {
@Mock
- QuotaResponseBuilder responseBuilder;
+ QuotaResponseBuilder quotaResponseBuilderMock;
- @Test
- public void testQuotaBalanceCmd() throws NoSuchFieldException,
IllegalAccessException {
- QuotaBalanceCmd cmd = new QuotaBalanceCmd();
- Field rbField =
QuotaBalanceCmd.class.getDeclaredField("_responseBuilder");
- rbField.setAccessible(true);
- rbField.set(cmd, responseBuilder);
+ @InjectMocks
+ @Spy
+ QuotaBalanceCmd quotaBalanceCmdSpy;
- List<QuotaBalanceVO> quotaBalanceVOList = new
ArrayList<QuotaBalanceVO>();
-
Mockito.when(responseBuilder.getQuotaBalance(Mockito.any(cmd.getClass()))).thenReturn(quotaBalanceVOList);
-
Mockito.when(responseBuilder.createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList),
Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse());
-
Mockito.when(responseBuilder.createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList),
Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(new
QuotaBalanceResponse());
-
Mockito.lenient().when(responseBuilder.startOfNextDay(Mockito.any(Date.class))).thenReturn(new
Date());
+ @Test
+ public void executeTestSetResponseObject() {
+ QuotaBalanceResponse expected = new QuotaBalanceResponse();
+
Mockito.doReturn(expected).when(quotaResponseBuilderMock).createQuotaBalanceResponse(Mockito.eq(quotaBalanceCmdSpy));
- // end date not specified
- cmd.setStartDate(new Date());
- cmd.setEndDate(null);
- cmd.execute();
- Mockito.verify(responseBuilder,
Mockito.times(1)).createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList),
Mockito.any(Date.class));
- Mockito.verify(responseBuilder,
Mockito.times(0)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList),
Mockito.any(Date.class), Mockito.any(Date.class));
+ quotaBalanceCmdSpy.execute();
- // end date specified
- cmd.setEndDate(new Date());
- cmd.execute();
- Mockito.verify(responseBuilder,
Mockito.times(1)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList),
Mockito.any(Date.class), Mockito.any(Date.class));
+ Assert.assertEquals(expected, quotaBalanceCmdSpy.getResponseObject());
}
+
}
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 81b4992082d..0c7610461c1 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
@@ -40,6 +40,7 @@ import com.cloud.user.UserVO;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
@@ -249,7 +250,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
credit.setCredit(new BigDecimal(amount));
Mockito.when(quotaCreditsDaoMock.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit);
- Mockito.when(quotaBalanceDaoMock.lastQuotaBalance(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111));
+
Mockito.when(quotaBalanceDaoMock.getLastQuotaBalance(Mockito.anyLong(),
Mockito.anyLong())).thenReturn(new BigDecimal(111));
Mockito.doReturn(userVoMock).when(quotaResponseBuilderSpy).getCreditorForQuotaCredits(credit);
AccountVO account = new AccountVO();
@@ -295,33 +296,6 @@ public class QuotaResponseBuilderImplTest extends TestCase
{
assertTrue(quotaResponseBuilderSpy.updateQuotaEmailTemplate(cmd));
}
- @Test
- public void testCreateQuotaLastBalanceResponse() {
- List<QuotaBalanceVO> quotaBalance = new ArrayList<>();
- // null balance test
- try {
- quotaResponseBuilderSpy.createQuotaLastBalanceResponse(null, new
Date());
- } catch (InvalidParameterValueException e) {
- assertTrue(e.getMessage().equals("There are no balance entries on
or before the requested date."));
- }
-
- // empty balance test
- try {
-
quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, new
Date());
- } catch (InvalidParameterValueException e) {
- assertTrue(e.getMessage().equals("There are no balance entries on
or before the requested date."));
- }
-
- // valid balance test
- QuotaBalanceVO entry = new QuotaBalanceVO();
- entry.setAccountId(2L);
- entry.setCreditBalance(new BigDecimal(100));
- quotaBalance.add(entry);
- quotaBalance.add(entry);
- QuotaBalanceResponse resp =
quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, null);
- assertTrue(resp.getStartQuota().compareTo(new BigDecimal(200)) == 0);
- }
-
@Test
public void testStartOfNextDayWithoutParameters() {
Date nextDate = quotaResponseBuilderSpy.startOfNextDay();
@@ -919,6 +893,46 @@ public class QuotaResponseBuilderImplTest extends TestCase
{
Assert.assertTrue(formattedVariables.containsValue("zonename"));
}
+ private List<QuotaBalanceVO> getQuotaBalancesForTest() {
+ List<QuotaBalanceVO> balances = new ArrayList<>();
+
+ QuotaBalanceVO balance = new QuotaBalanceVO();
+ balance.setUpdatedOn(new Date());
+ balance.setCreditBalance(BigDecimal.valueOf(-10.42));
+ balances.add(balance);
+
+ balance = new QuotaBalanceVO();
+ balance.setUpdatedOn(new Date());
+ balance.setCreditBalance(BigDecimal.valueOf(-18.94));
+ balances.add(balance);
+
+ balance = new QuotaBalanceVO();
+ balance.setUpdatedOn(new Date());
+ balance.setCreditBalance(BigDecimal.valueOf(-29.37));
+ balances.add(balance);
+
+ return balances;
+ }
+
+ @Test
+ public void createQuotaBalancesResponseTestCreateResponse() {
+ List<QuotaBalanceVO> balances = getQuotaBalancesForTest();
+
+ QuotaBalanceResponse expected = new QuotaBalanceResponse();
+ expected.setObjectName("balance");
+ expected.setCurrency("$");
+
+
Mockito.doReturn(balances).when(quotaServiceMock).listQuotaBalancesForAccount(Mockito.any(),
Mockito.any(), Mockito.any());
+ QuotaBalanceResponse result =
quotaResponseBuilderSpy.createQuotaBalanceResponse(new QuotaBalanceCmd());
+
+ Assert.assertEquals(expected.getCurrency(), result.getCurrency());
+
+ for (int i = 0; i < balances.size(); i++) {
+ Assert.assertEquals(balances.get(i).getUpdatedOn(),
result.getBalances().get(i).getDate());
+ Assert.assertEquals(balances.get(i).getCreditBalance(),
result.getBalances().get(i).getBalance());
+ }
+ }
+
@Test
public void
createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeDifferentFromNullDoNothing()
{
List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
diff --git
a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
index a0fe63de851..89c48b1cc66 100644
---
a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
+++
b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
@@ -18,6 +18,7 @@ package org.apache.cloudstack.quota;
import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao;
+import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.db.TransactionLegacy;
@@ -31,7 +32,9 @@ import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
+import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,7 +46,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import javax.naming.ConfigurationException;
import java.lang.reflect.Field;
-import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -51,6 +53,8 @@ import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class QuotaServiceImplTest extends TestCase {
+ @Mock
+ AccountVO accountVoMock;
@Mock
AccountDao accountDaoMock;
@Mock
@@ -67,9 +71,6 @@ public class QuotaServiceImplTest extends TestCase {
QuotaUsageJoinDao quotaUsageJoinDaoMock;
@Mock
QuotaResponseBuilder respBldr;
- @Mock
- private AccountVO accountVoMock;
-
@Spy
@InjectMocks
QuotaServiceImpl quotaServiceImplSpy;
@@ -112,30 +113,6 @@ public class QuotaServiceImplTest extends TestCase {
quotaServiceImplSpy.configure("randomName", null);
}
- @Test
- public void testFindQuotaBalanceVO() {
- final long accountId = 2L;
- final String accountName = "admin123";
- final long domainId = 1L;
- final Date startDate = new DateTime().minusDays(2).toDate();
- final Date endDate = new Date();
-
- List<QuotaBalanceVO> records = new ArrayList<>();
- QuotaBalanceVO qb = new QuotaBalanceVO();
- qb.setCreditBalance(new BigDecimal(100));
- qb.setAccountId(accountId);
- records.add(qb);
-
-
Mockito.when(respBldr.startOfNextDay(Mockito.any(Date.class))).thenReturn(startDate);
- Mockito.when(quotaBalanceDao.findQuotaBalance(Mockito.eq(accountId),
Mockito.eq(domainId), Mockito.any(Date.class),
Mockito.any(Date.class))).thenReturn(records);
- Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId),
Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records);
-
- // with enddate
- assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId,
accountName, domainId, startDate, endDate).get(0).equals(qb));
- // without enddate
- assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId,
accountName, domainId, startDate, null).get(0).equals(qb));
- }
-
@Test
public void testGetQuotaUsage() {
final long accountId = 2L;
@@ -182,4 +159,66 @@ public class QuotaServiceImplTest extends TestCase {
Mockito.verify(quotaAcc,
Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
}
+ @Test(expected = InvalidParameterValueException.class)
+ public void
validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsNullAndEndDateIsNotNullThrowsInvalidParameterException()
{
+
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(null,
new Date());
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void
validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterNowThrowsInvalidParameterValueException()
{
+ Date startDate = DateUtils.addMinutes(new Date(), 1);
+
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate,
null);
+ }
+
+ @Test
+ public void
validateStartDateAndEndDateForListQuotaBalancesForAccountTestEndDateIsAfterNowDoesNothing()
{
+ Date startDate = DateUtils.addMinutes(new Date(), -1);
+ Date endDate = DateUtils.addMinutes(new Date(), 1);
+
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate,
endDate);
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ public void
validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterEndDateThrowsInvalidParameterValueException()
{
+ Date startDate = DateUtils.addMinutes(new Date(), -10);
+ Date endDate = DateUtils.addMinutes(new Date(), -15);
+
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate,
endDate);
+ }
+
+ @Test
+ public void
listQuotaBalancesForAccountTestLastQuotaBalanceIsNullReturnsEmptyList() {
+
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(),
Mockito.any());
+
Mockito.doReturn(null).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+
+ List<QuotaBalanceVO> result =
quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null);
+
+ Assert.assertTrue(result.isEmpty());
+ }
+
+ @Test
+ public void
listQuotaBalancesForAccountTestLastQuotaBalanceIsNotNullReturnsIt() {
+ QuotaBalanceVO expected = new QuotaBalanceVO();
+
+
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(),
Mockito.any());
+
Mockito.doReturn(expected).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any());
+
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+
+ List<QuotaBalanceVO> result =
quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null);
+
+ Assert.assertEquals(expected, result.get(0));
+ }
+
+ @Test
+ public void listQuotaBalancesForAccountTestReturnsQuotaBalances() {
+ List<QuotaBalanceVO> expected = new ArrayList<>();
+
+
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(),
Mockito.any());
+
Mockito.doReturn(expected).when(quotaBalanceDao).listQuotaBalances(Mockito.anyLong(),
Mockito.anyLong(), Mockito.any(), Mockito.any());
+
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
+
+ List<QuotaBalanceVO> result =
quotaServiceImplSpy.listQuotaBalancesForAccount(1L, new Date(), null);
+
+ Assert.assertEquals(expected, result);
+ }
+
}
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index d4d48bdf77b..52ec26661c9 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -3922,7 +3922,7 @@ public class AccountManagerImpl extends ManagerBase
implements AccountManager, M
if (getActiveAccountById(accountId) != null) {
return accountId;
}
- throw new InvalidParameterValueException(String.format("Unable to
find account with ID [%s].", accountId));
+ throw new InvalidParameterValueException(String.format("Unable to
find account with the specified ID."));
}
if (accountName == null && domainId == null) {
@@ -3938,16 +3938,16 @@ public class AccountManagerImpl extends ManagerBase
implements AccountManager, M
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
String.format("Both %s and %s are needed if using either. Consider using %s
instead.",
ApiConstants.ACCOUNT, ApiConstants.DOMAIN_ID,
ApiConstants.ACCOUNT_ID));
}
- throw new InvalidParameterValueException(String.format("Unable to find
account by name [%s] on domain [%s].", accountName, domainId));
+ throw new InvalidParameterValueException(String.format("Unable to find
account with name [%s] on the specified domain.", accountName));
}
protected long getActiveProjectAccountByProjectId(long projectId) {
Project project = _projectMgr.getProject(projectId);
if (project == null) {
- throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
String.format("Unable to find project with ID [%s].", projectId));
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to
find project with the specified ID.");
}
if (project.getState() != Project.State.Active) {
- throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
String.format("Project with ID [%s] is not active.", projectId));
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Project is
not active.");
}
return project.getProjectAccountId();
}
diff --git a/test/integration/plugins/quota/test_quota_balance.py
b/test/integration/plugins/quota/test_quota_balance.py
index f5c1c75d7b2..ca702bbda01 100644
--- a/test/integration/plugins/quota/test_quota_balance.py
+++ b/test/integration/plugins/quota/test_quota_balance.py
@@ -117,9 +117,28 @@ class TestQuotaBalance(cloudstackTestCase):
def delete_tariffs(self):
for tariff in self.tariffs:
cmd = quotaTariffDelete.quotaTariffDeleteCmd()
- cmd.id = tariff.uuid
+ cmd.id = tariff.id
self.apiclient.quotaTariffDelete(cmd)
+ def insert_usage_and_update_quota(self, zone_id, account_id, domain_id,
relative_start_date, relative_end_date):
+ start_date = f"DATE_ADD(UTC_TIMESTAMP(), INTERVAL
{relative_start_date} HOUR)"
+ end_date = f"DATE_ADD(UTC_TIMESTAMP(), INTERVAL {relative_end_date}
HOUR)"
+
+ # Manually insert a usage regarding the usage type 21 (VM_DISK_IO_READ)
+ sql_query = (f"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,"
+
f"usage_id,`type`,`size`,network_id,start_date,end_date,virtual_size,cpu_speed,cpu_cores,memory,quota_calculated,is_hidden,state)"
+ f" VALUES
('{zone_id}','{account_id}','{domain_id}','Test','1
Hrs',21,1,NULL,NULL,NULL,NULL,NULL,'VirtualMachine',NULL,NULL,{start_date},{end_date},NULL,NULL,NULL,NULL,0,0,NULL);")
+ self.debug(sql_query)
+ self.dbclient.execute(sql_query)
+
+ # Update quota to calculate the balance of the account
+ cmd = quotaUpdate.quotaUpdateCmd()
+ self.apiclient.quotaUpdate(cmd)
+ time.sleep(1)
+
+ def format_date(self, date):
+ return date.strftime("%Y-%m-%d %H:%M:%S")
+
@attr(tags=["advanced", "smoke", "quota"], required_hardware="false")
def test_quota_balance(self):
"""
@@ -146,6 +165,7 @@ class TestQuotaBalance(cloudstackTestCase):
cmd.domainid = self.domain.id
cmd.value = 100
self.apiclient.quotaCredits(cmd)
+ time.sleep(1)
# Fetch account ID from account_uuid
account_id_select = f"SELECT id FROM account WHERE uuid =
'{self.account.id}';"
@@ -165,27 +185,24 @@ class TestQuotaBalance(cloudstackTestCase):
qresultset = self.dbclient.execute(zone_id_select)
zone_id = qresultset[0][0]
- start_date = datetime.datetime.now() + datetime.timedelta(seconds=1)
- end_date = datetime.datetime.now() + datetime.timedelta(hours=1)
-
- # Manually insert a usage regarding the usage type 21 (VM_DISK_IO_READ)
- sql_query = (f"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,"
-
f"usage_id,`type`,`size`,network_id,start_date,end_date,virtual_size,cpu_speed,cpu_cores,memory,quota_calculated,is_hidden,state)"
- f" VALUES
('{zone_id}','{account_id}','{domain_id}','Test','1
Hrs',21,1,NULL,NULL,NULL,NULL,NULL,'VirtualMachine',NULL,NULL,'{start_date}','{end_date}',NULL,NULL,NULL,NULL,0,0,NULL);")
- self.debug(sql_query)
- self.dbclient.execute(sql_query)
-
- # Update quota to calculate the balance of the account
- cmd = quotaUpdate.quotaUpdateCmd()
- self.apiclient.quotaUpdate(cmd)
+ # Generate three quota_balance entries
+ self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 0,
1)
+ self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 1,
2)
+ self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 2,
3)
# Retrieve the quota balance of the account
cmd = quotaBalance.quotaBalanceCmd()
cmd.domainid = self.account.domainid
cmd.account = self.account.name
+ cmd.startdate = datetime.datetime.now() + datetime.timedelta(hours=-1)
+ cmd.enddate = datetime.datetime.now() + datetime.timedelta(hours=3)
response = self.apiclient.quotaBalance(cmd)
- self.debug(f"The quota balance for the account {self.account.name} is
{response.balance}.")
- self.assertEqual(response.balance.startquota, 90, f"The `startQuota`
response field is supposed to be 90 but was {response.balance.startquota}.")
+ self.assertTrue(len(response.balance.balances) == 4, f"Expected 4
balance entries for between {self.format_date(cmd.startdate)} " +
+ f"and {self.format_date(cmd.enddate)} but got
{len(response.balance.balances)}.")
+ for i, balance in enumerate(response.balance.balances):
+ expected_balance = 100 - 10 * i
+ self.debug(f"The quota balance for the account {self.account.name}
at {balance.date} was {balance.balance}.")
+ self.assertEqual(balance.balance, expected_balance, f"The
`balance` field at {balance.date} is supposed to be {expected_balance} but was
{balance.balance}.")
return