CLOUDSTACK-9153: When negative credits are added to an account the
balance credits can become negative for that account. This will fix will
lock the account if quota is enforced.
diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
index a4260cb..6e7ab81 100644
--- a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
+++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java
@@ -47,8 +47,6 @@
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate);
- QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Date despositedOn);
-
List<QuotaUsageVO> getQuotaUsage(QuotaStatementCmd cmd);
List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd);
diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
index 56d108a..45e0e69 100644
--- a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
+++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java
@@ -20,6 +20,7 @@
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.dao.AccountDao;
@@ -47,7 +48,6 @@
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
-import org.apache.cloudstack.region.RegionManager;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@@ -95,7 +95,7 @@
@Inject
private DomainDao _domainDao;
@Inject
- private RegionManager _regionMgr;
+ private AccountManager _accountMgr;
@Inject
private QuotaStatement _statement;
@@ -383,31 +383,41 @@
@Override
public QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy) {
- Date depositDate = new Date();
- Date adjustedStartDate = _quotaService.computeAdjustedTime(depositDate);
- QuotaBalanceVO qb = _quotaBalanceDao.findLaterBalanceEntry(accountId, domainId, adjustedStartDate);
+ Date despositedOn = _quotaService.computeAdjustedTime(new Date());
+ QuotaBalanceVO qb = _quotaBalanceDao.findLaterBalanceEntry(accountId, domainId, despositedOn);
+
if (qb != null) {
- throw new InvalidParameterValueException("Incorrect deposit date: " + adjustedStartDate + " there are balance entries after this date");
+ throw new InvalidParameterValueException("Incorrect deposit date: " + despositedOn + " there are balance entries after this date");
}
- return addQuotaCredits(accountId, domainId, amount, updatedBy, adjustedStartDate);
- }
-
- @Override
- public QuotaCreditsResponse addQuotaCredits(final Long accountId, final Long domainId, final Double amount, final Long updatedBy, final Date despositedOn) {
-
QuotaCreditsVO credits = new QuotaCreditsVO(accountId, domainId, new BigDecimal(amount), updatedBy);
- s_logger.debug("AddQuotaCredits: Depositing " + amount + " on adjusted date " + despositedOn);
credits.setUpdatedOn(despositedOn);
QuotaCreditsVO result = _quotaCreditsDao.saveCredits(credits);
final AccountVO account = _accountDao.findById(accountId);
final boolean lockAccountEnforcement = "true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value());
- final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(despositedOn));
- if (lockAccountEnforcement && (currentAccountBalance.compareTo(new BigDecimal(0)) >= 0)) {
- if (account.getState() == Account.State.locked) {
- _regionMgr.enableAccount(account.getAccountName(), domainId, accountId);
+ final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new Date(despositedOn.getTime())));
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("AddQuotaCredits: Depositing " + amount + " on adjusted date " + despositedOn + ", current balance " + currentAccountBalance);
+ }
+ // update quota account with the balance
+ _quotaService.saveQuotaAccount(account, currentAccountBalance, despositedOn);
+ if (lockAccountEnforcement) {
+ if (currentAccountBalance.compareTo(new BigDecimal(0)) >= 0) {
+ if (account.getType() == Account.ACCOUNT_TYPE_NORMAL) {
+ if (account.getState() == Account.State.locked) {
+ s_logger.info("UnLocking account " + account.getAccountName() + " , due to positive balance " + currentAccountBalance);
+ _accountMgr.enableAccount(account.getAccountName(), domainId, accountId);
+ }
+ } else {
+ s_logger.warn("Only normal accounts will get locked " + account.getAccountName() + " even if they have run out of quota " + currentAccountBalance);
+ }
+ } else { // currentAccountBalance < 0 then lock the account
+ if (account.getState() == Account.State.enabled) {
+ s_logger.info("Locking account " + account.getAccountName() + " , due to negative balance " + currentAccountBalance);
+ _accountMgr.lockAccount(account.getAccountName(), domainId, accountId);
+ }
}
}
@@ -500,8 +510,11 @@
Calendar c = Calendar.getInstance();
c.setTime(dt);
c.add(Calendar.DATE, 1);
- dt = c.getTime();
- return dt;
+ c.set(Calendar.HOUR, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c.getTime();
}
@Override
@@ -509,8 +522,11 @@
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DATE, 1);
- Date dt = c.getTime();
- return dt;
+ c.set(Calendar.HOUR, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c.getTime();
}
}
diff --git a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java
index 6c645de..fe63471 100644
--- a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java
+++ b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java
@@ -16,11 +16,13 @@
//under the License.
package org.apache.cloudstack.quota;
+import com.cloud.user.AccountVO;
import com.cloud.utils.component.PluggableService;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
+import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@@ -38,4 +40,6 @@
Boolean isQuotaServiceEnabled();
+ boolean saveQuotaAccount(AccountVO account, BigDecimal aggrUsage, Date endDate);
+
}
diff --git a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java
index 355982b..0fd7101 100644
--- a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java
+++ b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java
@@ -287,6 +287,30 @@
}
@Override
+ public boolean saveQuotaAccount(final AccountVO account, final BigDecimal aggrUsage, final Date endDate) {
+ // update quota_accounts
+ QuotaAccountVO quota_account = _quotaAcc.findByIdQuotaAccount(account.getAccountId());
+
+ if (quota_account == null) {
+ quota_account = new QuotaAccountVO(account.getAccountId());
+ quota_account.setQuotaBalance(aggrUsage);
+ quota_account.setQuotaBalanceDate(endDate);
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug(quota_account);
+ }
+ _quotaAcc.persistQuotaAccount(quota_account);
+ return true;
+ } else {
+ quota_account.setQuotaBalance(aggrUsage);
+ quota_account.setQuotaBalanceDate(endDate);
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug(quota_account);
+ }
+ return _quotaAcc.updateQuotaAccount(account.getAccountId(), quota_account);
+ }
+ }
+
+ @Override
public void setMinBalance(Long accountId, Double balance) {
QuotaAccountVO acc = _quotaAcc.findByIdQuotaAccount(accountId);
if (acc == null) {
diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
index 65aadd8..73ead4a 100644
--- a/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
+++ b/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java
@@ -18,6 +18,7 @@
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
@@ -35,7 +36,6 @@
import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
-import org.apache.cloudstack.region.RegionManager;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +50,8 @@
import java.util.Date;
import java.util.List;
+import javax.inject.Inject;
+
@RunWith(MockitoJUnitRunner.class)
public class QuotaResponseBuilderImplTest extends TestCase {
@@ -68,8 +70,8 @@
QuotaService quotaService;
@Mock
AccountDao accountDao;
- @Mock
- RegionManager regionMgr;
+ @Inject
+ AccountManager accountMgr;
QuotaResponseBuilderImpl quotaResponseBuilder = new QuotaResponseBuilderImpl();
@@ -106,9 +108,9 @@
accountDaoField.setAccessible(true);
accountDaoField.set(quotaResponseBuilder, accountDao);
- Field regionMgrField = QuotaResponseBuilderImpl.class.getDeclaredField("_regionMgr");
+ Field regionMgrField = QuotaResponseBuilderImpl.class.getDeclaredField("_accountMgr");
regionMgrField.setAccessible(true);
- regionMgrField.set(quotaResponseBuilder, regionMgr);
+ regionMgrField.set(quotaResponseBuilder, accountMgr);
}
private QuotaTariffVO makeTariffTestData() {
@@ -133,22 +135,22 @@
@Test
public void testAddQuotaCredits() {
final long accountId = 2L;
- final long domainId = 2L;
+ final long domainId = 1L;
final double amount = 11.0;
final long updatedBy = 2L;
- final Date now = new Date();
QuotaCreditsVO credit = new QuotaCreditsVO();
credit.setCredit(new BigDecimal(amount));
Mockito.when(quotaCreditsDao.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit);
Mockito.when(quotaBalanceDao.lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111));
+ Mockito.when(quotaService.computeAdjustedTime(Mockito.any(Date.class))).thenReturn(new Date());
AccountVO account = new AccountVO();
account.setState(Account.State.locked);
Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(account);
- QuotaCreditsResponse resp = quotaResponseBuilder.addQuotaCredits(accountId, domainId, amount, updatedBy, now);
+ QuotaCreditsResponse resp = quotaResponseBuilder.addQuotaCredits(accountId, domainId, amount, updatedBy);
assertTrue(resp.getCredits().compareTo(credit.getCredit()) == 0);
}