From f76c6f3ea9710b70770c26193eaccd1caa6207e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Jandre?= <48719461+JoaoJandre@users.noreply.github.com> Date: Mon, 18 Mar 2024 08:26:41 -0300 Subject: [PATCH] Quota email configuration (#8307) * Quota email configuration feature --- .../META-INF/db/schema-41900to42000.sql | 10 ++ .../cloudstack/quota/QuotaAlertManager.java | 3 + .../quota/QuotaAlertManagerImpl.java | 130 +++++++++----- .../cloudstack/quota/QuotaStatementImpl.java | 41 +++-- .../quota/constant/QuotaConfig.java | 3 + .../quota/dao/QuotaEmailConfigurationDao.java | 36 ++++ .../dao/QuotaEmailConfigurationDaoImpl.java | 105 +++++++++++ .../quota/dao/QuotaEmailTemplatesDao.java | 2 + .../quota/dao/QuotaEmailTemplatesDaoImpl.java | 5 + .../quota/vo/QuotaEmailConfigurationVO.java | 68 ++++++++ .../quota/spring-framework-quota-context.xml | 1 + .../quota/QuotaAlertManagerImplTest.java | 163 ++++++++++++++---- .../cloudstack/quota/QuotaStatementTest.java | 67 ++++++- .../api/command/QuotaConfigureEmailCmd.java | 79 +++++++++ .../QuotaListEmailConfigurationCmd.java | 54 ++++++ .../response/QuotaConfigureEmailResponse.java | 78 +++++++++ .../api/response/QuotaResponseBuilder.java | 8 + .../response/QuotaResponseBuilderImpl.java | 106 +++++++++++- .../cloudstack/quota/QuotaServiceImpl.java | 6 +- .../QuotaResponseBuilderImplTest.java | 112 ++++++++++++ 20 files changed, 987 insertions(+), 90 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-41900to42000.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to42000.sql index 3ebab6b15f2..1bb1905443a 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 @@ -69,3 +69,13 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.network_offerings','for_nsx', 'int(1 CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.network_offerings','nsx_mode', 'varchar(32) COMMENT "mode in which the network would route traffic"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','for_nsx', 'int(1) unsigned DEFAULT "0" COMMENT "is nsx enabled for the resource"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','nsx_mode', 'varchar(32) COMMENT "mode in which the network would route traffic"'); + + +-- 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_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/QuotaAlertManager.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManager.java index 44204e8d116..f4ee2362c7e 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 ff41a8141d7..b26b3171f5b 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 @@ -34,8 +34,10 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; 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; @@ -80,7 +82,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,55 +144,100 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana 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(); - final BigDecimal zeroBalance = new BigDecimal(0); + + logger.info("Checking and sending quota alert emails."); for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { - if (logger.isDebugEnabled()) { - 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 - } - logger.debug("checkAndSendQuotaAlertEmails: Check id={} bal={}, alertDate={}, lockable={}", account.getId(), - accountBalance, DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), alertDate), - lockable); - if (accountBalance.compareTo(zeroBalance) < 0) { - if (_lockAccountEnforcement && (lockable == 1)) { - if (_quotaManager.isLockable(account)) { - logger.info("Locking account " + account.getAccountName() + " due to quota < 0."); - lockAccount(account.getId()); - } - } - if (alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1)) { - 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)) { - 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 (logger.isDebugEnabled()) { - logger.debug("checkAndSendQuotaAlertEmails: Attempting to send quota alert email to users of account: " + emailToBeSent.getAccount().getAccountName()); - } + logger.debug("Attempting to send a quota alert email to users of account [{}].", 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) { + logger.debug("Checking {} for email alerts.", quotaAccount); + BigDecimal accountBalance = quotaAccount.getQuotaBalance(); + + if (accountBalance == null) { + 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("Account of {} is removed, thus it will not receive quota alert emails.", quotaAccount); + 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(); + BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); + + logger.debug("Checking {} with accountBalance [{}], alertDate [{}] and lockable [{}] to see if a quota alert email should be sent.", account, + accountBalance, DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), alertDate), lockable); + + 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("Locking {}, as quota balance is lower than 0.", account); + lockAccount(account.getId()); + } + + 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) { + 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("{} will not receive any quota alert emails in this round.", account); + } + @Override public void sendQuotaAlert(DeferredQuotaEmail emailToBeSent) { final AccountVO account = emailToBeSent.getAccount(); @@ -285,7 +335,7 @@ public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertMana 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 a4ee0e2584e..5ee327fb9a5 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,6 +31,8 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; 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.springframework.stereotype.Component; @@ -53,6 +55,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 { @@ -111,29 +119,34 @@ public class QuotaStatementImpl extends ManagerBase implements QuotaStatement { if (quotaAccount.getQuotaBalance() == null) { continue; // no quota usage for this account ever, ignore } + 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; + } //check if it is statement time Calendar interval[] = statementTime(Calendar.getInstance(), _period); 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 59aa54424cf..df7ffa5c3cd 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/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 00000000000..4bb3395cc11 --- /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 00000000000..9466340ad05 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java @@ -0,0 +1,105 @@ +// 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.TransactionLegacy; +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, (TransactionCallback) status -> 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, (TransactionCallback) status -> update(quotaEmailConfigurationVO, sc)); + + return quotaEmailConfigurationVO; + } + + @Override + public void persistQuotaEmailConfiguration(QuotaEmailConfigurationVO quotaEmailConfigurationVO) { + Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> persist(quotaEmailConfigurationVO)); + } + + @Override + public List listByAccount(long accountId) { + SearchCriteria sc = searchBuilderFindByIds.create(); + sc.setParameters("account_id", accountId); + + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback>) status -> 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, (TransactionCallback) status -> 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 573a7539744..346bb9a4a6a 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 @@ import java.util.List; 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 b44ace0a1ff..c27f2df299b 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 @@ -66,4 +66,9 @@ public class QuotaEmailTemplatesDaoImpl extends GenericDaoBase) status -> 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 00000000000..e50c7ce6250 --- /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 5f1c274f049..e634321208f 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 a0dd4c22814..54d4f1d5b69 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,9 +30,12 @@ import javax.naming.ConfigurationException; 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.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; @@ -71,6 +74,9 @@ public class QuotaAlertManagerImplTest extends TestCase { @Mock private ConfigurationDao configDao; + @Mock + private QuotaEmailConfigurationDaoImpl quotaEmailConfigurationDaoMock; + @Mock private QuotaAccountVO quotaAccountVOMock; @@ -92,43 +98,142 @@ public class QuotaAlertManagerImplTest extends TestCase { @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(); - // Don't test sendQuotaAlert yet - Mockito.doNothing().when(quotaAlertManager).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); - Mockito.lenient().doReturn(true).when(quotaAlertManager).lockAccount(Mockito.anyLong()); + TransactionLegacy.open("QuotaAlertManagerImplTest"); + } - // call real method on send monthly statement - Mockito.doCallRealMethod().when(quotaAlertManager).checkAndSendQuotaAlertEmails(); + @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)); - // Case1: valid balance, no email should be sent - quotaAlertManager.checkAndSendQuotaAlertEmails(); - Mockito.verify(quotaAlertManager, Mockito.times(0)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + boolean result = quotaAlertManager.isQuotaEmailTypeEnabledForAccount(accountMock, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY); - // 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)); + 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(); + 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(); + + 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()); + } + + public void checkQuotaAlertEmailForAccountTestBalanceLowerThanZeroAndAlertDateNull() { + Mockito.doReturn(new BigDecimal(-1)).when(quotaAccountVOMock).getQuotaBalance(); + + quotaAlertManager.checkQuotaAlertEmailForAccount(deferredQuotaEmailListMock, quotaAccountVOMock); + Mockito.verify(deferredQuotaEmailListMock).add(Mockito.any()); } @Test @@ -196,12 +301,12 @@ public class QuotaAlertManagerImplTest extends TestCase { @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 baf749cd0c9..507834fef41 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.Calendar; 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.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.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,10 @@ public class QuotaStatementTest extends TestCase { @Test - public void testSendStatement() throws UnsupportedEncodingException, MessagingException { + public void sendStatementTestUnconfiguredEmail() { + 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(); accountVO.setId(2L); @@ -252,4 +271,46 @@ public class QuotaStatementTest extends TestCase { } } + @Test + public void sendStatementTestEnabledEmail() { + Mockito.doReturn(true).when(alertManager).isQuotaEmailTypeEnabledForAccount(Mockito.any(AccountVO.class), Mockito.any(QuotaConfig.QuotaEmailTemplateTypes.class)); + + 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() { + 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(); + + 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 00000000000..01d9ffc1529 --- /dev/null +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java @@ -0,0 +1,79 @@ +//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 = "quotaConfigureEmail", responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.20.0.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaConfigureEmailCmd extends BaseCmd { + + @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 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 00000000000..8915158461f --- /dev/null +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java @@ -0,0 +1,54 @@ +//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 = "quotaListEmailConfiguration", responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.20.0.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaListEmailConfigurationCmd extends BaseCmd { + + @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 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 00000000000..4f84a2c2828 --- /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 36033043bcf..57aa04e00fa 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.QuotaTariffCreateCmd; 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 d4996887a40..94897b410f4 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 @@ -38,6 +38,7 @@ import com.cloud.utils.DateUtil; 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; @@ -54,12 +55,14 @@ 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.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; @@ -104,7 +107,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @Inject private AccountDao _accountDao; @Inject - private QuotaAccountDao _quotaAccountDao; + private QuotaAccountDao quotaAccountDao; @Inject private DomainDao _domainDao; @Inject @@ -113,6 +116,8 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { private QuotaStatement _statement; @Inject private QuotaManager _quotaManager; + @Inject + private QuotaEmailConfigurationDao quotaEmailConfigurationDao; @Override public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff) { @@ -165,7 +170,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { 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()); @@ -676,4 +681,99 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { 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()); + } + + QuotaEmailConfigurationVO configurationVO = getQuotaEmailConfigurationVo(cmd); + return new Pair<>(configurationVO, minBalance); + } + + protected QuotaEmailConfigurationVO getQuotaEmailConfigurationVo(QuotaConfigureEmailCmd cmd) { + if (cmd.getTemplateName() == null) { + return 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 configurationVO; + } + + configurationVO.setEnabled(cmd.getEnable()); + return quotaEmailConfigurationDao.updateQuotaEmailConfiguration(configurationVO); + } + + 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 88a69c47e05..da3f50b165a 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.inject.Inject; 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; @@ -116,6 +118,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi cmdList.add(QuotaEmailTemplateUpdateCmd.class); cmdList.add(QuotaTariffCreateCmd.class); cmdList.add(QuotaTariffDeleteCmd.class); + cmdList.add(QuotaConfigureEmailCmd.class); + cmdList.add(QuotaListEmailConfigurationCmd.class); return cmdList; } @@ -128,7 +132,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi 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 b960a1be569..899ce649fce 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 java.util.function.Consumer; 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,13 +38,17 @@ import org.apache.cloudstack.quota.QuotaService; 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.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; @@ -103,6 +108,12 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaUsageDao quotaUsageDaoMock; + @Mock + QuotaAccountDao quotaAccountDaoMock; + + @Mock + QuotaEmailConfigurationDao quotaEmailConfigurationDaoMock; + @InjectMocks QuotaResponseBuilderImpl quotaResponseBuilderSpy = Mockito.spy(QuotaResponseBuilderImpl.class); @@ -114,6 +125,15 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock DomainVO domainVOMock; + @Mock + QuotaConfigureEmailCmd quotaConfigureEmailCmdMock; + + @Mock + QuotaAccountVO quotaAccountVOMock; + + @Mock + QuotaEmailTemplatesVO quotaEmailTemplatesVoMock; + private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException { Field f = ConfigKey.class.getDeclaredField("_defaultValue"); f.setAccessible(true); @@ -403,4 +423,96 @@ public class QuotaResponseBuilderImplTest extends TestCase { 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); + } + + @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()); + } }