From 25b8808338ec6084177cfbab050306d0fea11266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:59:45 -0300 Subject: [PATCH 01/11] Quota email configuration feature --- .../META-INF/db/schema-41810to41900.sql | 9 ++ .../quota/QuotaAlertManagerImpl.java | 101 ++++++++----- .../cloudstack/quota/QuotaStatementImpl.java | 16 ++ .../quota/dao/QuotaEmailConfigurationDao.java | 36 +++++ .../dao/QuotaEmailConfigurationDaoImpl.java | 132 ++++++++++++++++ .../quota/dao/QuotaEmailTemplatesDao.java | 2 + .../quota/dao/QuotaEmailTemplatesDaoImpl.java | 10 ++ .../quota/vo/QuotaEmailConfigurationVO.java | 68 +++++++++ .../quota/spring-framework-quota-context.xml | 1 + .../quota/QuotaAlertManagerImplTest.java | 143 ++++++++++++++---- .../cloudstack/quota/QuotaStatementTest.java | 75 ++++++++- .../api/command/QuotaConfigureEmailCmd.java | 86 +++++++++++ .../QuotaListEmailConfigurationCmd.java | 61 ++++++++ .../response/QuotaConfigureEmailResponse.java | 78 ++++++++++ .../api/response/QuotaResponseBuilder.java | 8 + .../response/QuotaResponseBuilderImpl.java | 100 +++++++++++- .../cloudstack/quota/QuotaServiceImpl.java | 4 + .../QuotaResponseBuilderImplTest.java | 53 +++++++ 18 files changed, 907 insertions(+), 76 deletions(-) create mode 100644 framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDao.java create mode 100644 framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java create mode 100644 framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java create mode 100644 plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java create mode 100644 plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java create mode 100644 plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaConfigureEmailResponse.java diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql index f7920667210a..aed4ccaff253 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql @@ -183,3 +183,12 @@ ALTER TABLE `cloud`.`kubernetes_cluster` MODIFY COLUMN `kubernetes_version_id` b -- Set removed state for all removed accounts UPDATE `cloud`.`account` SET state='removed' WHERE `removed` IS NOT NULL; + +-- Create table to persist quota email template configurations +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`( + `account_id` int(11) NOT NULL, + `email_template_id` bigint(20) NOT NULL, + `enabled` int(1) UNSIGNED NOT NULL, + PRIMARY KEY (`account_id`, `email_template_id`), + CONSTRAINT `FK_quota_email_configuration_account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`quota_account`(`account_id`), + CONSTRAINT `FK_quota_email_configuration_email_te1mplate_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`)); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 6be739919ead..2a8049015b2d 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -31,8 +31,10 @@ import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.constant.QuotaConfig.QuotaEmailTemplateTypes; import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.text.StrSubstitutor; @@ -77,7 +79,10 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana @Inject private QuotaManager _quotaManager; - private boolean _lockAccountEnforcement = false; + @Inject + private QuotaEmailConfigurationDao quotaEmailConfigurationDao; + + protected boolean _lockAccountEnforcement = false; private String senderAddress; protected SMTPMailSender mailSender; @@ -139,52 +144,68 @@ public boolean stop() { @Override public void checkAndSendQuotaAlertEmails() { List deferredQuotaEmailList = new ArrayList(); - final BigDecimal zeroBalance = new BigDecimal(0); + + s_logger.info("Checking and sending quota alert emails."); for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("checkAndSendQuotaAlertEmails accId=" + quotaAccount.getId()); - } - BigDecimal accountBalance = quotaAccount.getQuotaBalance(); - Date balanceDate = quotaAccount.getQuotaBalanceDate(); - Date alertDate = quotaAccount.getQuotaAlertDate(); - int lockable = quotaAccount.getQuotaEnforce(); - BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); - if (accountBalance != null) { - AccountVO account = _accountDao.findById(quotaAccount.getId()); - if (account == null) { - continue; // the account is removed - } - if (s_logger.isDebugEnabled()) { - s_logger.debug("checkAndSendQuotaAlertEmails: Check id=" + account.getId() + " bal=" + accountBalance + ", alertDate=" + alertDate + ", lockable=" + lockable); - } - if (accountBalance.compareTo(zeroBalance) < 0) { - if (_lockAccountEnforcement && (lockable == 1)) { - if (_quotaManager.isLockable(account)) { - s_logger.info("Locking account " + account.getAccountName() + " due to quota < 0."); - lockAccount(account.getId()); - } - } - if (alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1)) { - s_logger.info("Sending alert " + account.getAccountName() + " due to quota < 0."); - deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY)); - } - } else if (accountBalance.compareTo(thresholdBalance) < 0) { - if (alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1)) { - s_logger.info("Sending alert " + account.getAccountName() + " due to quota below threshold."); - deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW)); - } - } - } + checkQuotaAlertEmailForAccount(deferredQuotaEmailList, quotaAccount); } for (DeferredQuotaEmail emailToBeSent : deferredQuotaEmailList) { - if (s_logger.isDebugEnabled()) { - s_logger.debug("checkAndSendQuotaAlertEmails: Attempting to send quota alert email to users of account: " + emailToBeSent.getAccount().getAccountName()); - } + s_logger.debug(String.format("Attempting to send a quota alert email to users of account [%s].", emailToBeSent.getAccount().getAccountName())); sendQuotaAlert(emailToBeSent); } } + /** + * Checks a given quota account to see if they should receive any emails. First by checking if it has any balance at all, if its account can be found, then checks + * if they should receive either QUOTA_EMPTY or QUOTA_LOW emails, taking into account if these email templates are disabled or not for that account. + * */ + protected void checkQuotaAlertEmailForAccount(List deferredQuotaEmailList, QuotaAccountVO quotaAccount) { + s_logger.debug(String.format("Checking %s for email alerts.", quotaAccount)); + BigDecimal accountBalance = quotaAccount.getQuotaBalance(); + + if (accountBalance == null) { + s_logger.debug(String.format("%s has a null balance, therefore it will not receive quota alert emails.", quotaAccount)); + return; + } + + AccountVO account = _accountDao.findById(quotaAccount.getId()); + if (account == null) { + s_logger.debug(String.format("Account of %s is removed, thus it will not receive quota alert emails.", quotaAccount)); + return; + } + + Date balanceDate = quotaAccount.getQuotaBalanceDate(); + Date alertDate = quotaAccount.getQuotaAlertDate(); + int lockable = quotaAccount.getQuotaEnforce(); + BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); + + s_logger.debug(String.format("Checking %s with accountBalance [%s], alertDate [%s] and lockable [%s] to see if a quota alert email should be sent.", account, + accountBalance, alertDate, lockable)); + + QuotaEmailConfigurationVO quotaEmpty = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateType(account.getAccountId(), QuotaEmailTemplateTypes.QUOTA_EMPTY); + QuotaEmailConfigurationVO quotaLow = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateType(account.getAccountId(), QuotaEmailTemplateTypes.QUOTA_LOW); + + boolean shouldSendEmail = alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1); + + if (accountBalance.compareTo(BigDecimal.ZERO) < 0) { + if (_lockAccountEnforcement && lockable == 1 && _quotaManager.isLockable(account)) { + s_logger.info(String.format("Locking %s, as quota balance is lower than 0.", account)); + lockAccount(account.getId()); + } + if (quotaEmpty != null && quotaEmpty.isEnabled() && shouldSendEmail) { + s_logger.debug(String.format("Adding %s to the deferred emails list, as quota balance is lower than 0.", account)); + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaEmailTemplateTypes.QUOTA_EMPTY)); + return; + } + } else if (accountBalance.compareTo(thresholdBalance) < 0 && quotaLow != null && quotaLow.isEnabled() && shouldSendEmail) { + s_logger.debug(String.format("Adding %s to the deferred emails list, as quota balance [%s] is below the threshold [%s].", account, accountBalance, thresholdBalance)); + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaEmailTemplateTypes.QUOTA_LOW)); + return; + } + s_logger.debug(String.format("%s will not receive any quota alert emails in this round.", account)); + } + @Override public void sendQuotaAlert(DeferredQuotaEmail emailToBeSent) { final AccountVO account = emailToBeSent.getAccount(); @@ -266,7 +287,7 @@ public Map generateOptionMap(AccountVO accountVO, String userNam return optionMap; } - public static long getDifferenceDays(Date d1, Date d2) { + public long getDifferenceDays(Date d1, Date d2) { long diff = d2.getTime() - d1.getTime(); return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java index 9523c874558d..707097bcc737 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java @@ -31,8 +31,12 @@ import org.apache.cloudstack.quota.QuotaAlertManagerImpl.DeferredQuotaEmail; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; +import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -55,6 +59,12 @@ public class QuotaStatementImpl extends ManagerBase implements QuotaStatement { @Inject private ConfigurationDao _configDao; + @Inject + private QuotaEmailConfigurationDao quotaEmailConfigurationDao; + + @Inject + private QuotaEmailTemplatesDao quotaEmailTemplatesDao; + final public static int s_LAST_STATEMENT_SENT_DAYS = 6; //ideally should be less than 7 days public enum QuotaStatementPeriods { @@ -109,10 +119,16 @@ public boolean stop() { public void sendStatement() { List deferredQuotaEmailList = new ArrayList(); + QuotaEmailTemplatesVO templateVO = quotaEmailTemplatesDao.listAllQuotaEmailTemplates(QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT.toString()).get(0); for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { if (quotaAccount.getQuotaBalance() == null) { continue; // no quota usage for this account ever, ignore } + QuotaEmailConfigurationVO quotaEmailConfigurationVO = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateId(quotaAccount.getAccountId(), templateVO.getId()); + if (quotaEmailConfigurationVO != null && !quotaEmailConfigurationVO.isEnabled()) { + s_logger.debug(String.format("%s has [%s] email disabled. Therefore the email will not be sent.", quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT)); + continue; + } //check if it is statement time Calendar interval[] = statementTime(Calendar.getInstance(), _period); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDao.java new file mode 100644 index 000000000000..4bb3395cc114 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDao.java @@ -0,0 +1,36 @@ +// 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 com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; + +import java.util.List; + +public interface QuotaEmailConfigurationDao extends GenericDao { + + QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateId(long accountId, long emailTemplateId); + + QuotaEmailConfigurationVO updateQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO); + + void persistQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO); + + List listByAccount(long accountId); + + QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateType(long accountId, QuotaConfig.QuotaEmailTemplateTypes quotaEmailTemplateType); +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java new file mode 100644 index 000000000000..a54177f637b6 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java @@ -0,0 +1,132 @@ +// 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 com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.List; + +@Component +public class QuotaEmailConfigurationDaoImpl extends GenericDaoBase implements QuotaEmailConfigurationDao { + + @Inject + private QuotaEmailTemplatesDao quotaEmailTemplatesDao; + + private SearchBuilder searchBuilderFindByIds; + + private SearchBuilder searchBuilderFindByTemplateName; + + private SearchBuilder searchBuilderFindByTemplateTypeAndAccountId; + + @PostConstruct + public void init() { + searchBuilderFindByIds = createSearchBuilder(); + searchBuilderFindByIds.and("account_id", searchBuilderFindByIds.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilderFindByIds.and("email_template_id", searchBuilderFindByIds.entity().getEmailTemplateId(), SearchCriteria.Op.EQ); + searchBuilderFindByIds.done(); + + searchBuilderFindByTemplateName = quotaEmailTemplatesDao.createSearchBuilder(); + searchBuilderFindByTemplateName.and("template_name", searchBuilderFindByTemplateName.entity().getTemplateName(), SearchCriteria.Op.EQ); + + searchBuilderFindByTemplateTypeAndAccountId = createSearchBuilder(); + searchBuilderFindByTemplateTypeAndAccountId.and("account_id", searchBuilderFindByTemplateTypeAndAccountId.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilderFindByTemplateTypeAndAccountId.join("email_template_id", searchBuilderFindByTemplateName, searchBuilderFindByTemplateName.entity().getId(), + searchBuilderFindByTemplateTypeAndAccountId.entity().getEmailTemplateId(), JoinBuilder.JoinType.INNER); + + searchBuilderFindByTemplateName.done(); + searchBuilderFindByTemplateTypeAndAccountId.done(); + } + + @Override + public QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateId(long accountId, long emailTemplateId) { + SearchCriteria sc = searchBuilderFindByIds.create(); + sc.setParameters("account_id", accountId); + sc.setParameters("email_template_id", emailTemplateId); + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaEmailConfigurationVO doInTransaction(TransactionStatus status) { + return findOneBy(sc); + } + }); + } + + @Override + public QuotaEmailConfigurationVO updateQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO) { + SearchCriteria sc = searchBuilderFindByIds.create(); + sc.setParameters("account_id", quotaEmailConfigurationVO.getAccountId()); + sc.setParameters("email_template_id", quotaEmailConfigurationVO.getEmailTemplateId()); + Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + update(quotaEmailConfigurationVO, sc); + } + }); + + return quotaEmailConfigurationVO; + } + + @Override + public void persistQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO) { + Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + persist(quotaEmailConfigurationVO); + } + }); + } + + @Override + public List listByAccount(long accountId) { + SearchCriteria sc = searchBuilderFindByIds.create(); + sc.setParameters("account_id", accountId); + + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus status) { + return listBy(sc); + } + }); + } + + @Override + public QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateType(long accountId, QuotaConfig.QuotaEmailTemplateTypes quotaEmailTemplateType) { + SearchCriteria sc = searchBuilderFindByTemplateTypeAndAccountId.create(); + sc.setParameters("account_id", accountId); + sc.setJoinParameters("email_template_id", "template_name", quotaEmailTemplateType.toString()); + + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaEmailConfigurationVO doInTransaction(TransactionStatus status) { + return findOneBy(sc); + } + }); + } +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java index 573a75397442..346bb9a4a6a2 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java @@ -24,4 +24,6 @@ public interface QuotaEmailTemplatesDao extends GenericDao { List listAllQuotaEmailTemplates(String templateName); boolean updateQuotaEmailTemplate(QuotaEmailTemplatesVO template); + + QuotaEmailTemplatesVO findById(long id); } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java index e774a52648ec..065e782d7c96 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java @@ -68,4 +68,14 @@ public Boolean doInTransaction(final TransactionStatus status) { } }); } + + @Override + public QuotaEmailTemplatesVO findById(long id) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaEmailTemplatesVO doInTransaction(final TransactionStatus status) { + return QuotaEmailTemplatesDaoImpl.super.findById(id); + } + }); + } } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java new file mode 100644 index 000000000000..e50c7ce62504 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java @@ -0,0 +1,68 @@ +// 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.vo; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "quota_email_configuration") +public class QuotaEmailConfigurationVO { + + @Column(name = "account_id") + private long accountId; + + @Column(name = "email_template_id") + private long emailTemplateId; + + @Column(name = "enabled") + private boolean enabled; + + public QuotaEmailConfigurationVO() { + } + + public QuotaEmailConfigurationVO(long accountId, long emailTemplateId, boolean enable) { + this.accountId = accountId; + this.emailTemplateId = emailTemplateId; + this.enabled = enable; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getEmailTemplateId() { + return emailTemplateId; + } + + public void setEmailTemplateId(long emailTemplateId) { + this.emailTemplateId = emailTemplateId; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml b/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml index 5f1c274f0492..e634321208f4 100644 --- a/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml +++ b/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml @@ -30,5 +30,6 @@ + diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java index ae2f2e9926e6..ce122f3fd593 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDaoImpl; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; @@ -71,6 +72,9 @@ public class QuotaAlertManagerImplTest extends TestCase { @Mock private ConfigurationDao configDao; + @Mock + private QuotaEmailConfigurationDaoImpl quotaEmailConfigurationDaoMock; + @Mock private QuotaAccountVO quotaAccountVOMock; @@ -83,52 +87,127 @@ public class QuotaAlertManagerImplTest extends TestCase { @Mock private Date balanceDateMock; - @Mock - private AccountVO accountMock; - @Spy @InjectMocks private QuotaAlertManagerImpl quotaAlertManager = new QuotaAlertManagerImpl(); @Before public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { - TransactionLegacy.open("QuotaAlertManagerImplTest"); - } - - @Test - public void testCheckAndSendQuotaAlertEmails() { AccountVO accountVO = new AccountVO(); accountVO.setId(2L); accountVO.setDomainId(1L); accountVO.setType(Account.Type.NORMAL); Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(accountVO); - QuotaAccountVO acc = new QuotaAccountVO(2L); - acc.setQuotaBalance(new BigDecimal(404)); - acc.setQuotaMinBalance(new BigDecimal(100)); - acc.setQuotaBalanceDate(new Date()); - acc.setQuotaAlertDate(null); - acc.setQuotaEnforce(0); - List accounts = new ArrayList<>(); - accounts.add(acc); - Mockito.when(quotaAcc.listAllQuotaAccount()).thenReturn(accounts); + Mockito.doReturn(new BigDecimal(404)).when(quotaAccountVOMock).getQuotaBalance(); + Mockito.doReturn(new BigDecimal(100)).when(quotaAccountVOMock).getQuotaMinBalance(); + Mockito.doReturn(balanceDateMock).when(quotaAccountVOMock).getQuotaBalanceDate(); + Mockito.doReturn(null).when(quotaAccountVOMock).getQuotaAlertDate(); + Mockito.doReturn(0).when(quotaAccountVOMock).getQuotaEnforce(); + + TransactionLegacy.open("QuotaAlertManagerImplTest"); + } - // Don't test sendQuotaAlert yet - Mockito.doNothing().when(quotaAlertManager).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); - Mockito.lenient().doReturn(true).when(quotaAlertManager).lockAccount(Mockito.anyLong()); + @Test + public void checkQuotaAlertEmailForAccountTestNullAccountBalance() { + Mockito.doReturn(null).when(quotaAccountVOMock).getQuotaBalance(); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(accountDao, Mockito.never()).findById(Mockito.any()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestNullAccount() { + Mockito.doReturn(new BigDecimal(1)).when(quotaAccountVOMock).getQuotaBalance(); + Mockito.doReturn(null).when(accountDao).findById(Mockito.any()); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAccountVOMock, Mockito.never()).getQuotaBalanceDate(); + } + + @Test + public void checkQuotaAlertEmailForAccountTestEnoughBalance() { + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAlertManager, Mockito.never()).lockAccount(Mockito.anyLong()); + Mockito.verify(deferredQuotaEmailListMock, Mockito.never()).add(Mockito.any()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndLockAccountEnforcementFalse() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); - // call real method on send monthly statement - Mockito.doCallRealMethod().when(quotaAlertManager).checkAndSendQuotaAlertEmails(); + quotaAlertManager._lockAccountEnforcement = false; + Mockito.doReturn(1).when(quotaAccountVOMock).getQuotaEnforce(); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAlertManager, Mockito.never()).lockAccount(Mockito.anyLong()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndLockableFalse() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + + quotaAlertManager._lockAccountEnforcement = true; + Mockito.doReturn(1).when(quotaAccountVOMock).getQuotaEnforce(); + Mockito.doReturn(false).when(quotaManagerMock).isLockable(Mockito.any()); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAlertManager, Mockito.never()).lockAccount(Mockito.anyLong()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndIsLockableFalse() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + + quotaAlertManager._lockAccountEnforcement = true; + Mockito.doReturn(1).when(quotaAccountVOMock).getQuotaEnforce(); + Mockito.doReturn(false).when(quotaManagerMock).isLockable(Mockito.any()); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAlertManager, Mockito.never()).lockAccount(Mockito.anyLong()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndLockAccount() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + + quotaAlertManager._lockAccountEnforcement = true; + Mockito.doReturn(1).when(quotaAccountVOMock).getQuotaEnforce(); + Mockito.doReturn(true).when(quotaManagerMock).isLockable(Mockito.any()); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(quotaAlertManager).lockAccount(Mockito.anyLong()); + } + + @Test + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndAlertDateNotNullAndBalanceDateNotAfter() { + Mockito.doReturn(new Date()).when(quotaAccountVOMock).getQuotaAlertDate(); + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + Mockito.doReturn(false).when(balanceDateMock).after(Mockito.any()); + + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(deferredQuotaEmailListMock, Mockito.never()).add(Mockito.any()); + } + + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndAlertDateNotNullAndGetDifferenceDaysSmallerThanOne() { + Mockito.doReturn(new Date()).when(quotaAccountVOMock).getQuotaAlertDate(); + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + Mockito.doReturn(true).when(balanceDateMock).after(Mockito.any()); + Mockito.doReturn(0L).when(quotaAlertManager).getDifferenceDays(Mockito.any(), Mockito.any()); + + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(deferredQuotaEmailListMock, Mockito.never()).add(Mockito.any()); + } + + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndAlertDateNotNullAndBalanceAfterAndDifferenceBiggerThanOne() { + Mockito.doReturn(new Date()).when(quotaAccountVOMock).getQuotaAlertDate(); + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + Mockito.doReturn(true).when(balanceDateMock).after(Mockito.any()); + Mockito.doReturn(2).when(quotaAlertManager).getDifferenceDays(Mockito.any(), Mockito.any()); + + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(deferredQuotaEmailListMock).add(Mockito.any()); + } - // Case1: valid balance, no email should be sent - quotaAlertManager.checkAndSendQuotaAlertEmails(); - Mockito.verify(quotaAlertManager, Mockito.times(0)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndAlertDateNull() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); - // Case2: low balance, email should be sent - accounts.get(0).setQuotaBalance(new BigDecimal(99)); - //Mockito.when(quotaAcc.listAll()).thenReturn(accounts); - quotaAlertManager.checkAndSendQuotaAlertEmails(); - Mockito.verify(quotaAlertManager, Mockito.times(1)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(deferredQuotaEmailListMock).add(Mockito.any()); } @Test @@ -196,12 +275,12 @@ public void addHeaderAndFooterTestIfHeaderAndFootersAreNotAddedIfEmpty() { @Test public void testGetDifferenceDays() { Date now = new Date(); - assertTrue(QuotaAlertManagerImpl.getDifferenceDays(now, now) == 0L); + assertTrue(quotaAlertManager.getDifferenceDays(now, now) == 0L); Calendar c = Calendar.getInstance(); c.setTimeZone(TimeZone.getTimeZone("UTC")); Calendar c2 = (Calendar)c.clone(); c2.add(Calendar.DATE, 1); - assertEquals(1L, QuotaAlertManagerImpl.getDifferenceDays(c.getTime(), c2.getTime())); + assertEquals(1L, quotaAlertManager.getDifferenceDays(c.getTime(), c2.getTime())); } @Test diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java index 1b28f66b2bb9..045ce501eafb 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.quota; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.ArrayList; @@ -24,17 +23,21 @@ import java.util.Date; import java.util.List; -import javax.mail.MessagingException; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.quota.QuotaStatementImpl.QuotaStatementPeriods; import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDaoImpl; +import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.junit.Before; 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; @@ -60,7 +63,20 @@ public class QuotaStatementTest extends TestCase { @Mock QuotaAlertManager alertManager; + @Mock + QuotaEmailConfigurationDaoImpl quotaEmailConfigurationDaoMock; + + @Mock + QuotaEmailTemplatesDao quotaEmailTemplatesDaoMock; + + @Mock + QuotaEmailTemplatesVO quotaEmailTemplatesVOMock; + + @Mock + List listMock; + @Spy + @InjectMocks QuotaStatementImpl quotaStatement = new QuotaStatementImpl(); private void injectMockToField(Object mock, String fieldName) throws NoSuchFieldException, IllegalAccessException { @@ -227,7 +243,11 @@ public void testStatementPeriodYEARLY() { @Test - public void testSendStatement() throws UnsupportedEncodingException, MessagingException { + public void sendStatementTestUnconfiguredEmail() { + Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); + Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); + Mockito.doReturn(null).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + Calendar date = Calendar.getInstance(); AccountVO accountVO = new AccountVO(); accountVO.setId(2L); @@ -252,4 +272,53 @@ public void testSendStatement() throws UnsupportedEncodingException, MessagingEx } } + @Test + public void sendStatementTestEnabledEmail() { + Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); + Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); + + QuotaEmailConfigurationVO quotaEmailConfigurationVOMock = new QuotaEmailConfigurationVO(); + quotaEmailConfigurationVOMock.setEnabled(true); + + Mockito.doReturn(quotaEmailConfigurationVOMock).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + + Calendar date = Calendar.getInstance(); + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + Mockito.lenient().when(accountDao.findById(Mockito.anyLong())).thenReturn(accountVO); + + QuotaAccountVO acc = new QuotaAccountVO(2L); + acc.setQuotaBalance(new BigDecimal(404)); + acc.setLastStatementDate(null); + List accounts = new ArrayList<>(); + accounts.add(acc); + Mockito.lenient().when(quotaAcc.listAllQuotaAccount()).thenReturn(accounts); + + Mockito.lenient().when(quotaUsage.findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyInt(), Mockito.any(Date.class), Mockito.any(Date.class))) + .thenReturn(new BigDecimal(100)); + + // call real method on send monthly statement + quotaStatement.sendStatement(); + Calendar period[] = quotaStatement.statementTime(date, QuotaStatementPeriods.MONTHLY); + if (period != null){ + Mockito.verify(alertManager, Mockito.times(1)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + } + } + + @Test + public void sendStatementTestDisabledEmail() { + Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); + Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); + + QuotaEmailConfigurationVO quotaEmailConfigurationVOMock = new QuotaEmailConfigurationVO(); + quotaEmailConfigurationVOMock.setEnabled(false); + + Mockito.lenient().doReturn(quotaEmailConfigurationVOMock).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + + quotaStatement.sendStatement(); + + Mockito.verify(quotaStatement, Mockito.never()).statementTime(Mockito.any(), Mockito.any()); + } + } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java new file mode 100644 index 000000000000..8a6f22c944e0 --- /dev/null +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java @@ -0,0 +1,86 @@ +//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.api.command; + +import com.cloud.utils.Pair; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +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.QuotaConfigureEmailResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; + +import javax.inject.Inject; + +@APICommand(name = QuotaConfigureEmailCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.16.0.11", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaConfigureEmailCmd extends QuotaBaseCmd { + + public static final String API_NAME = "quotaConfigureEmail"; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, required = true, + description = "Account ID for which to configure quota template email or min balance") + private long accountId; + + @Parameter(name = ApiConstants.TEMPLATE_NAME, type = CommandType.STRING, description = "Quota email template name which should be configured") + private String templateName; + + @Parameter(name = ApiConstants.ENABLE, type = CommandType.BOOLEAN, description = "If the quota email template should be enabled") + private Boolean enable; + + @Parameter(name = "minbalance", type = CommandType.DOUBLE, description = "New quota account min balance") + private Double minBalance; + + @Inject + private QuotaResponseBuilder responseBuilder; + + @Override + public void execute() { + Pair result = responseBuilder.configureQuotaEmail(this); + QuotaConfigureEmailResponse quotaConfigureEmailResponse = responseBuilder.createQuotaConfigureEmailResponse(result.first(), result.second(), accountId); + quotaConfigureEmailResponse.setResponseName(getCommandName()); + this.setResponseObject(quotaConfigureEmailResponse); + } + + @Override + public String getCommandName() { + return API_NAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return accountId; + } + + public long getAccountId() { + return accountId; + } + + public String getTemplateName() { + return templateName; + } + + public Boolean getEnable() { + return enable; + } + + public Double getMinBalance() { + return minBalance; + } +} diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java new file mode 100644 index 000000000000..332412bc186d --- /dev/null +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java @@ -0,0 +1,61 @@ +//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.api.command; + +import com.cloud.user.Account; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +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.ListResponse; +import org.apache.cloudstack.api.response.QuotaConfigureEmailResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; + +import javax.inject.Inject; + +@APICommand(name = QuotaListEmailConfigurationCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.16.0.11", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaListEmailConfigurationCmd extends BaseCmd { + + public static final String API_NAME = "quotaListEmailConfiguration"; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = BaseCmd.CommandType.UUID, entityType = AccountResponse.class, required = true, + description = "Account ID for which to list quota template email configurations") + private long accountId; + + @Inject + private QuotaResponseBuilder responseBuilder; + + @Override + public void execute() { + ListResponse response = new ListResponse<>(); + response.setResponses(responseBuilder.listEmailConfiguration(accountId)); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return API_NAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaConfigureEmailResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaConfigureEmailResponse.java new file mode 100644 index 000000000000..4f84a2c28283 --- /dev/null +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaConfigureEmailResponse.java @@ -0,0 +1,78 @@ +//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.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.BaseResponse; + + +public class QuotaConfigureEmailResponse extends BaseResponse { + + @SerializedName("account") + @Param(description = "The configured account's id.") + private String accountId; + + @SerializedName("templatename") + @Param(description = "The template's name.") + private String templateName; + + @SerializedName("enabled") + @Param(description = "Whether the template is enabled.") + private Boolean enabled; + + @SerializedName("minbalance") + @Param(description = "The configured account's min balance.") + private Double minBalance; + + public QuotaConfigureEmailResponse() { + super("quotaconfigureemail"); + setResponseName(""); + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getTemplateName() { + return templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Double getMinBalance() { + return minBalance; + } + + public void setMinBalance(Double minBalance) { + this.minBalance = minBalance; + } +} 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 36033043bcfd..57aa04e00faa 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 @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.response; import org.apache.cloudstack.api.command.QuotaBalanceCmd; +import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.api.command.QuotaStatementCmd; @@ -24,6 +25,7 @@ import org.apache.cloudstack.api.command.QuotaTariffListCmd; import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.quota.vo.QuotaUsageVO; @@ -69,4 +71,10 @@ public interface QuotaResponseBuilder { QuotaTariffVO createQuotaTariff(QuotaTariffCreateCmd cmd); boolean deleteQuotaTariff(String quotaTariffUuid); + + Pair configureQuotaEmail(QuotaConfigureEmailCmd cmd); + + QuotaConfigureEmailResponse createQuotaConfigureEmailResponse(QuotaEmailConfigurationVO quotaEmailConfigurationVO, Double minBalance, long accountId); + + List listEmailConfiguration(long accountId); } 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 32b49a72ae41..ae116ac55591 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 @@ -37,6 +37,7 @@ import org.apache.cloudstack.api.ApiErrorCode; 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.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.api.command.QuotaStatementCmd; @@ -51,12 +52,14 @@ import org.apache.cloudstack.quota.dao.QuotaAccountDao; import org.apache.cloudstack.quota.dao.QuotaBalanceDao; import org.apache.cloudstack.quota.dao.QuotaCreditsDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaTariffDao; -import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaCreditsVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.quota.vo.QuotaUsageVO; @@ -108,6 +111,12 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @Inject private QuotaManager _quotaManager; + @Inject + private QuotaAccountDao quotaAccountDao; + + @Inject + private QuotaEmailConfigurationDao quotaEmailConfigurationDao; + @Override public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff) { final QuotaTariffResponse response = new QuotaTariffResponse(); @@ -653,4 +662,93 @@ public boolean deleteQuotaTariff(String quotaTariffUuid) { quotaTariff.setRemoved(_quotaService.computeAdjustedTime(new Date())); return _quotaTariffDao.updateQuotaTariff(quotaTariff); } + + @Override + public Pair configureQuotaEmail(QuotaConfigureEmailCmd cmd) { + validateQuotaConfigureEmailCmdParameters(cmd); + + Double minBalance = cmd.getMinBalance(); + + if (minBalance != null) { + _quotaService.setMinBalance(cmd.getAccountId(), cmd.getMinBalance()); + } + + if (cmd.getTemplateName() != null) { + List templateVO = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(cmd.getTemplateName()); + if (templateVO.isEmpty()) { + throw new InvalidParameterValueException(String.format("Could not find template with name [%s].", cmd.getTemplateName())); + } + long templateId = templateVO.get(0).getId(); + QuotaEmailConfigurationVO configurationVO = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateId(cmd.getAccountId(), templateId); + + if (configurationVO == null) { + configurationVO = new QuotaEmailConfigurationVO(cmd.getAccountId(), templateId, cmd.getEnable()); + quotaEmailConfigurationDao.persistQuotaEmailConfiguration(configurationVO); + return new Pair<>(configurationVO, minBalance); + } + + configurationVO.setEnabled(cmd.getEnable()); + return new Pair<>(quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO), minBalance); + } + return new Pair<>(null, minBalance); + } + + protected void validateQuotaConfigureEmailCmdParameters(QuotaConfigureEmailCmd cmd) { + if (quotaAccountDao.findByIdQuotaAccount(cmd.getAccountId()) == null) { + throw new InvalidParameterValueException("You must have the quota enabled for this account to configure quota emails."); + } + + if (cmd.getTemplateName() == null && cmd.getMinBalance() == null) { + throw new InvalidParameterValueException("You should inform at least the 'minbalance' or both the 'templatename' and 'enable' parameters."); + } + + if ((cmd.getTemplateName() != null && cmd.getEnable() == null) || (cmd.getTemplateName() == null && cmd.getEnable() != null)) { + throw new InvalidParameterValueException("Parameter 'enable' must be informed along with 'templatename'."); + } + } + + public QuotaConfigureEmailResponse createQuotaConfigureEmailResponse(QuotaEmailConfigurationVO quotaEmailConfigurationVO, Double minBalance, long accountId) { + QuotaConfigureEmailResponse quotaConfigureEmailResponse = new QuotaConfigureEmailResponse(); + + Account account = _accountDao.findByIdIncludingRemoved(accountId); + if (quotaEmailConfigurationVO != null) { + QuotaEmailTemplatesVO templateVO = _quotaEmailTemplateDao.findById(quotaEmailConfigurationVO.getEmailTemplateId()); + + quotaConfigureEmailResponse.setAccountId(account.getUuid()); + quotaConfigureEmailResponse.setTemplateName(templateVO.getTemplateName()); + quotaConfigureEmailResponse.setEnabled(quotaEmailConfigurationVO.isEnabled()); + } + + quotaConfigureEmailResponse.setMinBalance(minBalance); + + return quotaConfigureEmailResponse; + } + + @Override + public List listEmailConfiguration(long accountId) { + List emailConfigurationVOList = quotaEmailConfigurationDao.listByAccount(accountId); + Account account = _accountDao.findById(accountId); + QuotaAccountVO quotaAccountVO = quotaAccountDao.findByIdQuotaAccount(accountId); + + List quotaConfigureEmailResponseList = new ArrayList<>(); + for (QuotaEmailConfigurationVO quotaEmailConfigurationVO : emailConfigurationVOList) { + quotaConfigureEmailResponseList.add(createQuotaConfigureEmailResponse(quotaEmailConfigurationVO, account, quotaAccountVO)); + } + + return quotaConfigureEmailResponseList; + } + + protected QuotaConfigureEmailResponse createQuotaConfigureEmailResponse(QuotaEmailConfigurationVO quotaEmailConfigurationVO, Account account, QuotaAccountVO quotaAccountVO) { + QuotaConfigureEmailResponse quotaConfigureEmailResponse = new QuotaConfigureEmailResponse(); + + QuotaEmailTemplatesVO templateVO = _quotaEmailTemplateDao.findById(quotaEmailConfigurationVO.getEmailTemplateId()); + + quotaConfigureEmailResponse.setAccountId(account.getUuid()); + quotaConfigureEmailResponse.setTemplateName(templateVO.getTemplateName()); + quotaConfigureEmailResponse.setEnabled(quotaEmailConfigurationVO.isEnabled()); + + quotaConfigureEmailResponse.setMinBalance(quotaAccountVO.getQuotaMinBalance().doubleValue()); + + return quotaConfigureEmailResponse; + } } 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 1a4007ca5dee..c4ba10826471 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,10 +28,12 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.QuotaBalanceCmd; +import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaCreditsCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.api.command.QuotaEnabledCmd; +import org.apache.cloudstack.api.command.QuotaListEmailConfigurationCmd; import org.apache.cloudstack.api.command.QuotaStatementCmd; import org.apache.cloudstack.api.command.QuotaSummaryCmd; import org.apache.cloudstack.api.command.QuotaTariffCreateCmd; @@ -130,6 +132,8 @@ public List> getCommands() { cmdList.add(QuotaEmailTemplateUpdateCmd.class); cmdList.add(QuotaTariffCreateCmd.class); cmdList.add(QuotaTariffDeleteCmd.class); + cmdList.add(QuotaConfigureEmailCmd.class); + cmdList.add(QuotaListEmailConfigurationCmd.class); return cmdList; } 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 b960a1be5692..47fba3c2c106 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 @@ -30,6 +30,7 @@ import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -37,11 +38,13 @@ import org.apache.cloudstack.quota.QuotaStatement; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.dao.QuotaAccountDao; import org.apache.cloudstack.quota.dao.QuotaBalanceDao; import org.apache.cloudstack.quota.dao.QuotaCreditsDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaTariffDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaCreditsVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; @@ -114,6 +117,15 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock DomainVO domainVOMock; + @Mock + QuotaConfigureEmailCmd quotaConfigureEmailCmdMock; + + @Mock + QuotaAccountDao quotaAccountDaoMock; + + @Mock + QuotaAccountVO quotaAccountVOMock; + private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException { Field f = ConfigKey.class.getDeclaredField("_defaultValue"); f.setAccessible(true); @@ -403,4 +415,45 @@ public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsEnabledShouldRetur assertTrue(quotaSummaryResponse.getQuotaEnabled()); } + + + @Test (expected = InvalidParameterValueException.class) + public void validateQuotaConfigureEmailCmdParametersTestNullQuotaAccount() { + Mockito.doReturn(null).when(quotaAccountDaoMock).findByIdQuotaAccount(Mockito.any()); + quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); + } + + @Test (expected = InvalidParameterValueException.class) + public void validateQuotaConfigureEmailCmdParametersTestNullTemplateNameAndMinBalance() { + Mockito.doReturn(quotaAccountVOMock).when(quotaAccountDaoMock).findByIdQuotaAccount(Mockito.any()); + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName(); + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getMinBalance(); + quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); + } + + @Test (expected = InvalidParameterValueException.class) + public void validateQuotaConfigureEmailCmdParametersTestEnableNullAndTemplateNameNotNull() { + Mockito.doReturn(quotaAccountVOMock).when(quotaAccountDaoMock).findByIdQuotaAccount(Mockito.any()); + Mockito.doReturn(QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW.toString()).when(quotaConfigureEmailCmdMock).getTemplateName(); + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getEnable(); + quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); + } + + + @Test + public void validateQuotaConfigureEmailCmdParametersTestNullTemplateName() { + Mockito.doReturn(quotaAccountVOMock).when(quotaAccountDaoMock).findByIdQuotaAccount(Mockito.any()); + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName(); + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getEnable(); + Mockito.doReturn(100D).when(quotaConfigureEmailCmdMock).getMinBalance(); + quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); + } + + @Test + public void validateQuotaConfigureEmailCmdParametersTestWithTemplateNameAndEnable() { + Mockito.doReturn(quotaAccountVOMock).when(quotaAccountDaoMock).findByIdQuotaAccount(Mockito.any()); + Mockito.doReturn(QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW.toString()).when(quotaConfigureEmailCmdMock).getTemplateName(); + Mockito.doReturn(true).when(quotaConfigureEmailCmdMock).getEnable(); + quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); + } } From 7d5211af0a160630cd8cae1a74bacd85970d97bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:55:08 -0300 Subject: [PATCH 02/11] fix double injecting --- .../cloudstack/api/response/QuotaResponseBuilderImpl.java | 8 ++------ .../api/response/QuotaResponseBuilderImplTest.java | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) 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 ae116ac55591..f900824aec00 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 @@ -101,7 +101,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @Inject private AccountDao _accountDao; @Inject - private QuotaAccountDao _quotaAccountDao; + private QuotaAccountDao quotaAccountDao; @Inject private DomainDao _domainDao; @Inject @@ -110,10 +110,6 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { private QuotaStatement _statement; @Inject private QuotaManager _quotaManager; - - @Inject - private QuotaAccountDao quotaAccountDao; - @Inject private QuotaEmailConfigurationDao quotaEmailConfigurationDao; @@ -168,7 +164,7 @@ public Pair, Integer> createQuotaSummaryResponse(Bool result.add(qr); } } else { - Pair, Integer> data = _quotaAccountDao.listAllQuotaAccount(startIndex, pageSize); + Pair, Integer> data = quotaAccountDao.listAllQuotaAccount(startIndex, pageSize); count = data.second(); for (final QuotaAccountVO quotaAccount : data.first()) { AccountVO account = _accountDao.findById(quotaAccount.getId()); 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 47fba3c2c106..c172dda8f212 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 @@ -106,6 +106,9 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaUsageDao quotaUsageDaoMock; + @Mock + QuotaAccountDao quotaAccountDaoMock; + @InjectMocks QuotaResponseBuilderImpl quotaResponseBuilderSpy = Mockito.spy(QuotaResponseBuilderImpl.class); @@ -120,9 +123,6 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaConfigureEmailCmd quotaConfigureEmailCmdMock; - @Mock - QuotaAccountDao quotaAccountDaoMock; - @Mock QuotaAccountVO quotaAccountVOMock; From b9efd14a9625123b2dcc387052750e269c0a94c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:14:54 -0300 Subject: [PATCH 03/11] Fix command versions --- .../apache/cloudstack/api/command/QuotaConfigureEmailCmd.java | 2 +- .../cloudstack/api/command/QuotaListEmailConfigurationCmd.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java index c7f53de29518..9c10b5038207 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java @@ -28,7 +28,7 @@ import javax.inject.Inject; -@APICommand(name = QuotaConfigureEmailCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.16.0.11", +@APICommand(name = QuotaConfigureEmailCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.19.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaConfigureEmailCmd extends BaseCmd { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java index 332412bc186d..a930c95e0b95 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java @@ -28,7 +28,7 @@ import javax.inject.Inject; -@APICommand(name = QuotaListEmailConfigurationCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.16.0.11", +@APICommand(name = QuotaListEmailConfigurationCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.19.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaListEmailConfigurationCmd extends BaseCmd { From d9126effdcc36c65e03ba78374bcde43b5e12f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:56:02 -0300 Subject: [PATCH 04/11] move sql to 4.20 --- .../main/resources/META-INF/db/schema-41900to42000.sql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql index 1c368a2fbee2..0593595e5485 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql @@ -18,3 +18,12 @@ --; -- Schema upgrade from 4.19.0.0 to 4.20.0.0 --; + +-- Create table to persist quota email template configurations +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`( + `account_id` int(11) NOT NULL, + `email_template_id` bigint(20) NOT NULL, + `enabled` int(1) UNSIGNED NOT NULL, + PRIMARY KEY (`account_id`, `email_template_id`), + CONSTRAINT `FK_quota_email_configuration_account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`quota_account`(`account_id`), + CONSTRAINT `FK_quota_email_configuration_email_te1mplate_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`)); From 6d442130fc12d61d95106deebccfbe0e4d703e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:23:12 -0300 Subject: [PATCH 05/11] Address reviews --- .../quota/QuotaAlertManagerImpl.java | 4 +++ .../dao/QuotaEmailConfigurationDaoImpl.java | 35 +++---------------- .../api/command/QuotaConfigureEmailCmd.java | 9 +---- .../QuotaListEmailConfigurationCmd.java | 9 +---- .../response/QuotaResponseBuilderImpl.java | 11 ++++-- 5 files changed, 19 insertions(+), 49 deletions(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 3516ba1413a6..e51f6131c402 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -177,6 +177,10 @@ protected void checkQuotaAlertEmailForAccount(List deferredQ return; } + checkBalanceAndAddToEmailList(deferredQuotaEmailList, quotaAccount, account, accountBalance); + } + + private void checkBalanceAndAddToEmailList(List deferredQuotaEmailList, QuotaAccountVO quotaAccount, AccountVO account, BigDecimal accountBalance) { Date balanceDate = quotaAccount.getQuotaBalanceDate(); Date alertDate = quotaAccount.getQuotaAlertDate(); int lockable = quotaAccount.getQuotaEnforce(); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java index a54177f637b6..d4d076087686 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java @@ -70,12 +70,7 @@ public QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateId(long accountI SearchCriteria sc = searchBuilderFindByIds.create(); sc.setParameters("account_id", accountId); sc.setParameters("email_template_id", emailTemplateId); - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaEmailConfigurationVO doInTransaction(TransactionStatus status) { - return findOneBy(sc); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> findOneBy(sc)); } @Override @@ -83,24 +78,14 @@ public QuotaEmailConfigurationVO updateQuotaEmailConfiguration(QuotaEmailConfigu SearchCriteria sc = searchBuilderFindByIds.create(); sc.setParameters("account_id", quotaEmailConfigurationVO.getAccountId()); sc.setParameters("email_template_id", quotaEmailConfigurationVO.getEmailTemplateId()); - Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - update(quotaEmailConfigurationVO, sc); - } - }); + Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> update(quotaEmailConfigurationVO, sc)); return quotaEmailConfigurationVO; } @Override public void persistQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO) { - Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - persist(quotaEmailConfigurationVO); - } - }); + Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> persist(quotaEmailConfigurationVO)); } @Override @@ -108,12 +93,7 @@ public List listByAccount(long accountId) { SearchCriteria sc = searchBuilderFindByIds.create(); sc.setParameters("account_id", accountId); - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { - @Override - public List doInTransaction(TransactionStatus status) { - return listBy(sc); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback>) status -> listBy(sc)); } @Override @@ -122,11 +102,6 @@ public QuotaEmailConfigurationVO findByAccountIdAndEmailTemplateType(long accoun sc.setParameters("account_id", accountId); sc.setJoinParameters("email_template_id", "template_name", quotaEmailTemplateType.toString()); - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaEmailConfigurationVO doInTransaction(TransactionStatus status) { - return findOneBy(sc); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> findOneBy(sc)); } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java index 9c10b5038207..01d9ffc15295 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java @@ -28,12 +28,10 @@ import javax.inject.Inject; -@APICommand(name = QuotaConfigureEmailCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.19.0.0", +@APICommand(name = "quotaConfigureEmail", responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.20.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaConfigureEmailCmd extends BaseCmd { - public static final String API_NAME = "quotaConfigureEmail"; - @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, required = true, description = "Account ID for which to configure quota template email or min balance") private long accountId; @@ -58,11 +56,6 @@ public void execute() { this.setResponseObject(quotaConfigureEmailResponse); } - @Override - public String getCommandName() { - return API_NAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; - } - @Override public long getEntityOwnerId() { return accountId; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java index a930c95e0b95..8915158461f8 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java @@ -28,12 +28,10 @@ import javax.inject.Inject; -@APICommand(name = QuotaListEmailConfigurationCmd.API_NAME, responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.19.0.0", +@APICommand(name = "quotaListEmailConfiguration", responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.20.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaListEmailConfigurationCmd extends BaseCmd { - public static final String API_NAME = "quotaListEmailConfiguration"; - @Parameter(name = ApiConstants.ACCOUNT_ID, type = BaseCmd.CommandType.UUID, entityType = AccountResponse.class, required = true, description = "Account ID for which to list quota template email configurations") private long accountId; @@ -49,11 +47,6 @@ public void execute() { setResponseObject(response); } - @Override - public String getCommandName() { - return API_NAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; - } - @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; 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 dd2c29b59a7e..8a6dfd048f04 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 @@ -670,6 +670,11 @@ public Pair configureQuotaEmail(QuotaConfigur _quotaService.setMinBalance(cmd.getAccountId(), cmd.getMinBalance()); } + QuotaEmailConfigurationVO configurationVO = getQuotaEmailConfigurationVo(cmd); + return new Pair<>(configurationVO, minBalance); + } + + private QuotaEmailConfigurationVO getQuotaEmailConfigurationVo(QuotaConfigureEmailCmd cmd) { if (cmd.getTemplateName() != null) { List templateVO = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(cmd.getTemplateName()); if (templateVO.isEmpty()) { @@ -681,13 +686,13 @@ public Pair configureQuotaEmail(QuotaConfigur if (configurationVO == null) { configurationVO = new QuotaEmailConfigurationVO(cmd.getAccountId(), templateId, cmd.getEnable()); quotaEmailConfigurationDao.persistQuotaEmailConfiguration(configurationVO); - return new Pair<>(configurationVO, minBalance); + return configurationVO; } configurationVO.setEnabled(cmd.getEnable()); - return new Pair<>(quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO), minBalance); + return quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO); } - return new Pair<>(null, minBalance); + return null; } protected void validateQuotaConfigureEmailCmdParameters(QuotaConfigureEmailCmd cmd) { From de99c9e968ea3781d3ada27daeb9917f416c7522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:34:20 -0300 Subject: [PATCH 06/11] remove unused imports --- .../cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java index d4d076087686..9466340ad053 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java @@ -22,9 +22,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; -import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionLegacy; -import com.cloud.utils.db.TransactionStatus; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; From 4129499b20ff58cd3a83a6560454881f9e72407f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:26:07 -0300 Subject: [PATCH 07/11] Address reviews and add config --- .../cloudstack/quota/QuotaAlertManager.java | 3 + .../quota/QuotaAlertManagerImpl.java | 59 ++++++++++++++----- .../cloudstack/quota/QuotaStatementImpl.java | 39 ++++++------ .../quota/constant/QuotaConfig.java | 3 + .../quota/QuotaAlertManagerImplTest.java | 26 ++++++++ .../cloudstack/quota/QuotaStatementTest.java | 28 ++++----- .../response/QuotaResponseBuilderImpl.java | 33 ++++++----- .../cloudstack/quota/QuotaServiceImpl.java | 2 +- .../QuotaResponseBuilderImplTest.java | 59 +++++++++++++++++++ 9 files changed, 180 insertions(+), 72 deletions(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManager.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManager.java index 44204e8d1167..f4ee2362c7e8 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManager.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManager.java @@ -16,11 +16,14 @@ //under the License. package org.apache.cloudstack.quota; +import com.cloud.user.AccountVO; import com.cloud.utils.component.Manager; import org.apache.cloudstack.quota.QuotaAlertManagerImpl.DeferredQuotaEmail; +import org.apache.cloudstack.quota.constant.QuotaConfig; public interface QuotaAlertManager extends Manager { + boolean isQuotaEmailTypeEnabledForAccount(AccountVO account, QuotaConfig.QuotaEmailTemplateTypes quotaEmailTemplateType); void checkAndSendQuotaAlertEmails(); void sendQuotaAlert(DeferredQuotaEmail emailToBeSent); } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index e51f6131c402..9d3f1c89e2d3 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -143,6 +143,29 @@ public boolean stop() { return true; } + /** + * Returns whether a Quota email type is enabled or not for the provided account. + */ + @Override + public boolean isQuotaEmailTypeEnabledForAccount(AccountVO account, QuotaEmailTemplateTypes quotaEmailTemplateType) { + boolean quotaEmailsEnabled = QuotaConfig.QuotaEnableEmails.valueIn(account.getAccountId()); + if (!quotaEmailsEnabled) { + logger.debug("Configuration [{}] is disabled for account [{}]. Therefore, the account will not receive Quota email of type [{}].", QuotaConfig.QuotaEnableEmails.key(), account, quotaEmailTemplateType); + return false; + } + + QuotaEmailConfigurationVO quotaEmail = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateType(account.getAccountId(), quotaEmailTemplateType); + + boolean emailEnabled = quotaEmail == null || quotaEmail.isEnabled(); + if (emailEnabled) { + logger.debug("Quota email [{}] is enabled for account [{}].", quotaEmailTemplateType, account); + } else { + logger.debug("Quota email [{}] has been manually disabled for account [{}] through the API quotaConfigureEmail.", quotaEmailTemplateType, account); + } + return emailEnabled; + } + + @Override public void checkAndSendQuotaAlertEmails() { List deferredQuotaEmailList = new ArrayList(); @@ -153,7 +176,7 @@ public void checkAndSendQuotaAlertEmails() { } for (DeferredQuotaEmail emailToBeSent : deferredQuotaEmailList) { - logger.debug(String.format("Attempting to send a quota alert email to users of account [%s].", emailToBeSent.getAccount().getAccountName())); + logger.debug("Attempting to send a quota alert email to users of account [{}].", emailToBeSent.getAccount().getAccountName()); sendQuotaAlert(emailToBeSent); } } @@ -163,17 +186,17 @@ public void checkAndSendQuotaAlertEmails() { * if they should receive either QUOTA_EMPTY or QUOTA_LOW emails, taking into account if these email templates are disabled or not for that account. * */ protected void checkQuotaAlertEmailForAccount(List deferredQuotaEmailList, QuotaAccountVO quotaAccount) { - logger.debug(String.format("Checking %s for email alerts.", quotaAccount)); + logger.debug("Checking {} for email alerts.", quotaAccount); BigDecimal accountBalance = quotaAccount.getQuotaBalance(); if (accountBalance == null) { - logger.debug(String.format("%s has a null balance, therefore it will not receive quota alert emails.", quotaAccount)); + logger.debug("{} has a null balance, therefore it will not receive quota alert emails.", quotaAccount); return; } AccountVO account = _accountDao.findById(quotaAccount.getId()); if (account == null) { - logger.debug(String.format("Account of %s is removed, thus it will not receive quota alert emails.", quotaAccount)); + logger.debug("Account of {} is removed, thus it will not receive quota alert emails.", quotaAccount); return; } @@ -186,30 +209,34 @@ private void checkBalanceAndAddToEmailList(List deferredQuot int lockable = quotaAccount.getQuotaEnforce(); BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); - logger.debug(String.format("Checking %s with accountBalance [%s], alertDate [%s] and lockable [%s] to see if a quota alert email should be sent.", account, - accountBalance, alertDate, lockable)); + logger.debug("Checking {} with accountBalance [{}], alertDate [{}] and lockable [{}] to see if a quota alert email should be sent.", account, + accountBalance, alertDate, lockable); + - QuotaEmailConfigurationVO quotaEmpty = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateType(account.getAccountId(), QuotaEmailTemplateTypes.QUOTA_EMPTY); - QuotaEmailConfigurationVO quotaLow = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateType(account.getAccountId(), QuotaEmailTemplateTypes.QUOTA_LOW); boolean shouldSendEmail = alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1); if (accountBalance.compareTo(BigDecimal.ZERO) < 0) { if (_lockAccountEnforcement && lockable == 1 && _quotaManager.isLockable(account)) { - logger.info(String.format("Locking %s, as quota balance is lower than 0.", account)); + logger.info("Locking {}, as quota balance is lower than 0.", account); lockAccount(account.getId()); } - if (quotaEmpty != null && quotaEmpty.isEnabled() && shouldSendEmail) { - logger.debug(String.format("Adding %s to the deferred emails list, as quota balance is lower than 0.", account)); + + boolean quotaEmptyEmailEnabled = isQuotaEmailTypeEnabledForAccount(account, QuotaEmailTemplateTypes.QUOTA_EMPTY); + if (quotaEmptyEmailEnabled && shouldSendEmail) { + logger.debug("Adding {} to the deferred emails list, as quota balance is lower than 0.", account); deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaEmailTemplateTypes.QUOTA_EMPTY)); return; } - } else if (accountBalance.compareTo(thresholdBalance) < 0 && quotaLow != null && quotaLow.isEnabled() && shouldSendEmail) { - logger.debug(String.format("Adding %s to the deferred emails list, as quota balance [%s] is below the threshold [%s].", account, accountBalance, thresholdBalance)); - deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaEmailTemplateTypes.QUOTA_LOW)); - return; + } else if (accountBalance.compareTo(thresholdBalance) < 0) { + boolean quotaLowEmailEnabled = isQuotaEmailTypeEnabledForAccount(account, QuotaEmailTemplateTypes.QUOTA_LOW); + if (quotaLowEmailEnabled && shouldSendEmail) { + logger.debug("Adding {} to the deferred emails list, as quota balance [{}] is below the threshold [{}].", account, accountBalance, thresholdBalance); + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaEmailTemplateTypes.QUOTA_LOW)); + return; + } } - logger.debug(String.format("%s will not receive any quota alert emails in this round.", account)); + logger.debug("{} will not receive any quota alert emails in this round.", account); } @Override diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java index a2e8379afe9d..5ee327fb9a5c 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java @@ -35,8 +35,6 @@ import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; -import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; -import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.springframework.stereotype.Component; import com.cloud.user.AccountVO; @@ -117,14 +115,19 @@ public boolean stop() { public void sendStatement() { List deferredQuotaEmailList = new ArrayList(); - QuotaEmailTemplatesVO templateVO = quotaEmailTemplatesDao.listAllQuotaEmailTemplates(QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT.toString()).get(0); for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { if (quotaAccount.getQuotaBalance() == null) { continue; // no quota usage for this account ever, ignore } - QuotaEmailConfigurationVO quotaEmailConfigurationVO = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateId(quotaAccount.getAccountId(), templateVO.getId()); - if (quotaEmailConfigurationVO != null && !quotaEmailConfigurationVO.isEnabled()) { - logger.debug(String.format("%s has [%s] email disabled. Therefore the email will not be sent.", quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT)); + AccountVO account = _accountDao.findById(quotaAccount.getId()); + if (account == null) { + logger.debug("Could not find an account corresponding to [{}]. Therefore, the statement email will not be sent.", quotaAccount); + continue; + } + + boolean quotaStatementEmailEnabled = _quotaAlert.isQuotaEmailTypeEnabledForAccount(account, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT); + if (!quotaStatementEmailEnabled) { + logger.debug("{} has [{}] email disabled. Therefore the email will not be sent.", quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT); continue; } @@ -133,23 +136,17 @@ public void sendStatement() { Date lastStatementDate = quotaAccount.getLastStatementDate(); if (interval != null) { - AccountVO account = _accountDao.findById(quotaAccount.getId()); - if (account != null) { - if (lastStatementDate == null || getDifferenceDays(lastStatementDate, new Date()) >= s_LAST_STATEMENT_SENT_DAYS + 1) { - BigDecimal quotaUsage = _quotaUsage.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, interval[0].getTime(), interval[1].getTime()); - logger.info("For account=" + quotaAccount.getId() + ", quota used = " + quotaUsage); - // send statement - deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, quotaUsage, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT)); - } else { - if (logger.isDebugEnabled()) { - logger.debug("For " + quotaAccount.getId() + " the statement has been sent recently"); - - } - } + if (lastStatementDate == null || getDifferenceDays(lastStatementDate, new Date()) >= s_LAST_STATEMENT_SENT_DAYS + 1) { + BigDecimal quotaUsage = _quotaUsage.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, interval[0].getTime(), interval[1].getTime()); + logger.info("Quota statement for account [{}] has an usage of [{}].", quotaAccount, quotaUsage); + + // send statement + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, quotaUsage, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT)); + } else { + logger.debug("Quota statement has already been sent recently to account [{}].", quotaAccount); } } else if (lastStatementDate != null) { - logger.info("For " + quotaAccount.getId() + " it is already more than " + getDifferenceDays(lastStatementDate, new Date()) - + " days, will send statement in next cycle"); + logger.info("For account {} it is already more than {} days, will send statement in next cycle.", quotaAccount.getId(), getDifferenceDays(lastStatementDate, new Date())); } } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java index 59aa54424cf0..df7ffa5c3cdf 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/constant/QuotaConfig.java @@ -72,6 +72,9 @@ public interface QuotaConfig { ConfigKey QuotaEmailFooter = new ConfigKey<>("Advanced", String.class, "quota.email.footer", "", "Text to be added as a footer for quota emails. Line breaks are not automatically inserted between this section and the body.", true, ConfigKey.Scope.Domain); + ConfigKey QuotaEnableEmails = new ConfigKey<>("Advanced", Boolean.class, "quota.enable.emails", "true", + "Indicates whether Quota emails should be sent or not to accounts. When enabled, the behavior for each account can be overridden through the API quotaConfigureEmail.", true, ConfigKey.Scope.Account); + enum QuotaEmailTemplateTypes { QUOTA_LOW, QUOTA_EMPTY, QUOTA_UNLOCK_ACCOUNT, QUOTA_STATEMENT } diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java index 287242d20567..54d4f1d5b690 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java @@ -33,7 +33,9 @@ import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDaoImpl; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,6 +89,9 @@ public class QuotaAlertManagerImplTest extends TestCase { @Mock private Date balanceDateMock; + @Mock + private AccountVO accountMock; + @Spy @InjectMocks private QuotaAlertManagerImpl quotaAlertManager = new QuotaAlertManagerImpl(); @@ -108,6 +113,27 @@ public void setup() throws IllegalAccessException, NoSuchFieldException, Configu TransactionLegacy.open("QuotaAlertManagerImplTest"); } + @Test + public void isQuotaEmailTypeEnabledForAccountTestConfigurationIsEnabledAndEmailIsConfiguredReturnConfiguredValue() { + boolean expectedValue = !QuotaConfig.QuotaEnableEmails.value(); + QuotaEmailConfigurationVO quotaEmailConfigurationVoMock = Mockito.mock(QuotaEmailConfigurationVO.class); + Mockito.when(quotaEmailConfigurationVoMock.isEnabled()).thenReturn(expectedValue); + Mockito.doReturn(quotaEmailConfigurationVoMock).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateType(Mockito.anyLong(), Mockito.any(QuotaConfig.QuotaEmailTemplateTypes.class)); + + boolean result = quotaAlertManager.isQuotaEmailTypeEnabledForAccount(accountMock, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY); + + Assert.assertEquals(expectedValue, result); + } + + @Test + public void isQuotaEmailTypeEnabledForAccountTestConfigurationIsEnabledAndEmailIsNotConfiguredReturnDefaultValue() { + boolean defaultValue = QuotaConfig.QuotaEnableEmails.value(); + + boolean result = quotaAlertManager.isQuotaEmailTypeEnabledForAccount(accountMock, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY); + + Assert.assertEquals(defaultValue, result); + } + @Test public void checkQuotaAlertEmailForAccountTestNullAccountBalance() { Mockito.doReturn(null).when(quotaAccountVOMock).getQuotaBalance(); diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java index a9a0c9bb9644..507834fef41c 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaStatementTest.java @@ -27,12 +27,12 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.quota.QuotaStatementImpl.QuotaStatementPeriods; +import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.dao.QuotaAccountDao; import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDaoImpl; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; -import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.junit.Before; import org.junit.Test; @@ -244,9 +244,8 @@ public void testStatementPeriodYEARLY() { @Test public void sendStatementTestUnconfiguredEmail() { - Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); - Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); - Mockito.doReturn(null).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + boolean defaultConfigurationValue = QuotaConfig.QuotaEnableEmails.value(); + Mockito.doReturn(defaultConfigurationValue).when(alertManager).isQuotaEmailTypeEnabledForAccount(Mockito.any(AccountVO.class), Mockito.any(QuotaConfig.QuotaEmailTemplateTypes.class)); Calendar date = Calendar.getInstance(); AccountVO accountVO = new AccountVO(); @@ -274,13 +273,7 @@ public void sendStatementTestUnconfiguredEmail() { @Test public void sendStatementTestEnabledEmail() { - Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); - Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); - - QuotaEmailConfigurationVO quotaEmailConfigurationVOMock = new QuotaEmailConfigurationVO(); - quotaEmailConfigurationVOMock.setEnabled(true); - - Mockito.doReturn(quotaEmailConfigurationVOMock).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(true).when(alertManager).isQuotaEmailTypeEnabledForAccount(Mockito.any(AccountVO.class), Mockito.any(QuotaConfig.QuotaEmailTemplateTypes.class)); Calendar date = Calendar.getInstance(); AccountVO accountVO = new AccountVO(); @@ -308,13 +301,12 @@ public void sendStatementTestEnabledEmail() { @Test public void sendStatementTestDisabledEmail() { - Mockito.doReturn(listMock).when(quotaEmailTemplatesDaoMock).listAllQuotaEmailTemplates(Mockito.anyString()); - Mockito.doReturn(quotaEmailTemplatesVOMock).when(listMock).get(Mockito.anyInt()); - - QuotaEmailConfigurationVO quotaEmailConfigurationVOMock = new QuotaEmailConfigurationVO(); - quotaEmailConfigurationVOMock.setEnabled(false); - - Mockito.lenient().doReturn(quotaEmailConfigurationVOMock).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + QuotaAccountVO quotaAccountVoMock = Mockito.mock(QuotaAccountVO.class); + Mockito.when(quotaAccountVoMock.getQuotaBalance()).thenReturn(BigDecimal.ONE); + Mockito.when(quotaAcc.listAllQuotaAccount()).thenReturn(List.of(quotaAccountVoMock)); + AccountVO accountVoMock = Mockito.mock(AccountVO.class); + Mockito.doReturn(accountVoMock).when(accountDao).findById(Mockito.anyLong()); + Mockito.doReturn(false).when(alertManager).isQuotaEmailTypeEnabledForAccount(Mockito.any(AccountVO.class), Mockito.any(QuotaConfig.QuotaEmailTemplateTypes.class)); quotaStatement.sendStatement(); 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 8a6dfd048f04..a0a217e8e88f 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 @@ -674,25 +674,26 @@ public Pair configureQuotaEmail(QuotaConfigur return new Pair<>(configurationVO, minBalance); } - private QuotaEmailConfigurationVO getQuotaEmailConfigurationVo(QuotaConfigureEmailCmd cmd) { - if (cmd.getTemplateName() != null) { - List templateVO = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(cmd.getTemplateName()); - if (templateVO.isEmpty()) { - throw new InvalidParameterValueException(String.format("Could not find template with name [%s].", cmd.getTemplateName())); - } - long templateId = templateVO.get(0).getId(); - QuotaEmailConfigurationVO configurationVO = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateId(cmd.getAccountId(), templateId); + protected QuotaEmailConfigurationVO getQuotaEmailConfigurationVo(QuotaConfigureEmailCmd cmd) { + if (cmd.getTemplateName() == null) { + return null; + } - if (configurationVO == null) { - configurationVO = new QuotaEmailConfigurationVO(cmd.getAccountId(), templateId, cmd.getEnable()); - quotaEmailConfigurationDao.persistQuotaEmailConfiguration(configurationVO); - return configurationVO; - } + List templateVO = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(cmd.getTemplateName()); + if (templateVO.isEmpty()) { + throw new InvalidParameterValueException(String.format("Could not find template with name [%s].", cmd.getTemplateName())); + } + long templateId = templateVO.get(0).getId(); + QuotaEmailConfigurationVO configurationVO = quotaEmailConfigurationDao.findByAccountIdAndEmailTemplateId(cmd.getAccountId(), templateId); - configurationVO.setEnabled(cmd.getEnable()); - return quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO); + if (configurationVO == null) { + configurationVO = new QuotaEmailConfigurationVO(cmd.getAccountId(), templateId, cmd.getEnable()); + quotaEmailConfigurationDao.persistQuotaEmailConfiguration(configurationVO); + return configurationVO; } - return null; + + configurationVO.setEnabled(cmd.getEnable()); + return quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO); } protected void validateQuotaConfigureEmailCmdParameters(QuotaConfigureEmailCmd cmd) { 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 450a878520fe..91da86f37bd0 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 @@ -144,7 +144,7 @@ public String getConfigComponentName() { public ConfigKey[] getConfigKeys() { return new ConfigKey[] {QuotaPluginEnabled, QuotaEnableEnforcement, QuotaCurrencySymbol, QuotaCurrencyLocale, QuotaStatementPeriod, QuotaSmtpHost, QuotaSmtpPort, QuotaSmtpTimeout, QuotaSmtpUser, QuotaSmtpPassword, QuotaSmtpAuthType, QuotaSmtpSender, QuotaSmtpEnabledSecurityProtocols, QuotaSmtpUseStartTLS, QuotaActivationRuleTimeout, QuotaAccountEnabled, - QuotaEmailHeader, QuotaEmailFooter}; + QuotaEmailHeader, QuotaEmailFooter, QuotaEnableEmails}; } @Override 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 c172dda8f212..899ce649fce0 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 @@ -41,12 +41,14 @@ import org.apache.cloudstack.quota.dao.QuotaAccountDao; import org.apache.cloudstack.quota.dao.QuotaBalanceDao; import org.apache.cloudstack.quota.dao.QuotaCreditsDao; +import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; import org.apache.cloudstack.quota.dao.QuotaTariffDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaCreditsVO; +import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.commons.lang3.time.DateUtils; @@ -109,6 +111,9 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaAccountDao quotaAccountDaoMock; + @Mock + QuotaEmailConfigurationDao quotaEmailConfigurationDaoMock; + @InjectMocks QuotaResponseBuilderImpl quotaResponseBuilderSpy = Mockito.spy(QuotaResponseBuilderImpl.class); @@ -126,6 +131,9 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaAccountVO quotaAccountVOMock; + @Mock + QuotaEmailTemplatesVO quotaEmailTemplatesVoMock; + private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException { Field f = ConfigKey.class.getDeclaredField("_defaultValue"); f.setAccessible(true); @@ -456,4 +464,55 @@ public void validateQuotaConfigureEmailCmdParametersTestWithTemplateNameAndEnabl Mockito.doReturn(true).when(quotaConfigureEmailCmdMock).getEnable(); quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); } + + @Test + public void getQuotaEmailConfigurationVoTestTemplateNameIsNull() { + Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName(); + + QuotaEmailConfigurationVO result = quotaResponseBuilderSpy.getQuotaEmailConfigurationVo(quotaConfigureEmailCmdMock); + + Assert.assertNull(result); + } + + @Test (expected = InvalidParameterValueException.class) + public void getQuotaEmailConfigurationVoTestNoTemplateFound() { + Mockito.doReturn("name").when(quotaConfigureEmailCmdMock).getTemplateName(); + Mockito.doReturn(new ArrayList()).when(quotaEmailTemplateDaoMock).listAllQuotaEmailTemplates(Mockito.any()); + + quotaResponseBuilderSpy.getQuotaEmailConfigurationVo(quotaConfigureEmailCmdMock); + } + + @Test + public void getQuotaEmailConfigurationVoTestNewConfiguration() { + Mockito.doReturn("name").when(quotaConfigureEmailCmdMock).getTemplateName(); + List templatesVOArrayList = List.of(quotaEmailTemplatesVoMock); + Mockito.doReturn(templatesVOArrayList).when(quotaEmailTemplateDaoMock).listAllQuotaEmailTemplates(Mockito.any()); + Mockito.doReturn(null).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + + QuotaEmailConfigurationVO result = quotaResponseBuilderSpy.getQuotaEmailConfigurationVo(quotaConfigureEmailCmdMock); + + Mockito.verify(quotaEmailConfigurationDaoMock).persistQuotaEmailConfiguration(Mockito.any()); + assertEquals(0, result.getAccountId()); + assertEquals(0, result.getEmailTemplateId()); + assertFalse(result.isEnabled()); + } + + @Test + public void getQuotaEmailConfigurationVoTestExistingConfiguration() { + Mockito.doReturn("name").when(quotaConfigureEmailCmdMock).getTemplateName(); + List templatesVOArrayList = List.of(quotaEmailTemplatesVoMock); + Mockito.doReturn(templatesVOArrayList).when(quotaEmailTemplateDaoMock).listAllQuotaEmailTemplates(Mockito.any()); + + QuotaEmailConfigurationVO quotaEmailConfigurationVO = new QuotaEmailConfigurationVO(1, 2, true); + Mockito.doReturn(quotaEmailConfigurationVO).when(quotaEmailConfigurationDaoMock).findByAccountIdAndEmailTemplateId(Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(quotaEmailConfigurationVO).when(quotaEmailConfigurationDaoMock).updateQuotaEmailConfiguration(Mockito.any()); + + QuotaEmailConfigurationVO result = quotaResponseBuilderSpy.getQuotaEmailConfigurationVo(quotaConfigureEmailCmdMock); + + Mockito.verify(quotaEmailConfigurationDaoMock).updateQuotaEmailConfiguration(Mockito.any()); + + assertEquals(1, result.getAccountId()); + assertEquals(2, result.getEmailTemplateId()); + assertFalse(result.isEnabled()); + } } From efad281708ce16e42d061665a41de1f1f6d8fdd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Fri, 1 Mar 2024 08:55:35 -0300 Subject: [PATCH 08/11] Adress review --- .../java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 9d3f1c89e2d3..5027783a4964 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -212,8 +212,6 @@ private void checkBalanceAndAddToEmailList(List deferredQuot logger.debug("Checking {} with accountBalance [{}], alertDate [{}] and lockable [{}] to see if a quota alert email should be sent.", account, accountBalance, alertDate, lockable); - - boolean shouldSendEmail = alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1); if (accountBalance.compareTo(BigDecimal.ZERO) < 0) { From aeeab867aee4ba43546a1525ad1879c65bd11d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Fri, 1 Mar 2024 08:59:06 -0300 Subject: [PATCH 09/11] Use lambda --- .../cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java index a50c325cbdf8..c27f2df299bf 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java @@ -69,11 +69,6 @@ public Boolean doInTransaction(final TransactionStatus status) { @Override public QuotaEmailTemplatesVO findById(long id) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaEmailTemplatesVO doInTransaction(final TransactionStatus status) { - return QuotaEmailTemplatesDaoImpl.super.findById(id); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> QuotaEmailTemplatesDaoImpl.super.findById(id)); } } From 8cc13bcdc93d708de6dcd9c12c836d21f8862c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:49:18 -0300 Subject: [PATCH 10/11] Address reviews --- .../src/main/resources/META-INF/db/schema-41900to42000.sql | 2 +- .../java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql index d49d13d8697f..942083cbfbda 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql @@ -42,4 +42,4 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`( `enabled` int(1) UNSIGNED NOT NULL, PRIMARY KEY (`account_id`, `email_template_id`), CONSTRAINT `FK_quota_email_configuration_account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`quota_account`(`account_id`), - CONSTRAINT `FK_quota_email_configuration_email_te1mplate_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`)); + CONSTRAINT `FK_quota_email_configuration_email_template_id` FOREIGN KEY (`email_template_id`) REFERENCES `cloud_usage`.`quota_email_templates`(`id`)); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 5027783a4964..e9aa8daed10f 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -234,7 +234,7 @@ private void checkBalanceAndAddToEmailList(List deferredQuot return; } } - logger.debug("{} will not receive any quota alert emails in this round.", account); + logger.debug("{} will not receive any quota alert emails in this round.", account); } @Override From fa027f642fe197f44acdff904e9f90b214e3f254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:55:11 -0300 Subject: [PATCH 11/11] fix log --- .../java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index 517e5b3d307d..b26b3171f5b5 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -211,7 +211,7 @@ private void checkBalanceAndAddToEmailList(List deferredQuot BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); logger.debug("Checking {} with accountBalance [{}], alertDate [{}] and lockable [{}] to see if a quota alert email should be sent.", account, - accountBalance, alertDate, lockable); + accountBalance, DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), alertDate), lockable); boolean shouldSendEmail = alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1);