From f580a8d7a2344ce732239f1c7b974a5b2c7f2f0a Mon Sep 17 00:00:00 2001 From: Bryan Lima <42067040+BryanMLima@users.noreply.github.com> Date: Thu, 27 Oct 2022 08:48:54 -0300 Subject: [PATCH] Quota enable config to user/domain scope (#6690) * Add quota plugin to accout/domain scope * Add check in quota usage calculation to skip accounts with quota disabled * Set quota config enabled default to true * Fix if condition * Update condition to use primitive boolean expression Co-authored-by: dahn * Remove unused var * Add quota state as a column in the Quota Summary view * Remove trailling spaces * Address review Co-authored-by: dahn --- .../cloudstack/quota/QuotaManagerImpl.java | 19 ++++- .../quota/constant/QuotaConfig.java | 3 + .../quota/dao/QuotaAccountDaoImpl.java | 15 +++- .../quota/dao/QuotaAccountDaoImplTest.java | 80 +++++++++++++++++++ .../api/response/QuotaSummaryResponse.java | 12 +++ .../cloudstack/quota/QuotaServiceImpl.java | 2 +- ui/public/locales/en.json | 2 + ui/public/locales/pt_BR.json | 2 + ui/src/components/view/ListView.vue | 3 + ui/src/config/section/plugin/quota.js | 10 ++- 10 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImplTest.java diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java index e74bf681cf2..e9c93807783 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java @@ -320,9 +320,7 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager { for (UsageVO usageRecord : usageRecords) { int usageType = usageRecord.getUsageType(); - if (usageTypesToAvoidCalculation.contains(usageType)) { - s_logger.debug(String.format("Considering usage record [%s] as calculated and skipping it because the calculation of the types [%s] has not been implemented yet.", - usageRecord.toString(), usageTypesToAvoidCalculation)); + if (Boolean.FALSE.equals(shouldCalculateUsageRecord(account,usageRecord))) { pairsUsageAndQuotaUsage.add(new Pair<>(usageRecord, null)); continue; } @@ -345,6 +343,21 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager { return persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(pairsUsageAndQuotaUsage); } + protected boolean shouldCalculateUsageRecord(AccountVO accountVO, UsageVO usageRecord) { + if (usageTypesToAvoidCalculation.contains(usageRecord.getUsageType())) { + s_logger.debug(String.format("Considering usage record [%s] as calculated and skipping it because the calculation of the types [%s] has not been implemented yet.", + usageRecord, usageTypesToAvoidCalculation)); + return false; + } + + if (Boolean.FALSE.equals(QuotaConfig.QuotaAccountEnabled.valueIn(accountVO.getAccountId()))) { + s_logger.debug(String.format("Considering usage record [%s] as calculated and skipping it because account [%s] has the quota plugin disabled.", + usageRecord, accountVO.reflectionToString())); + return false; + } + return true; + } + protected List persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(List> pairsUsageAndQuotaUsage) { List quotaUsages = new ArrayList<>(); 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 35106654dfe..b157c6ab740 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 @@ -60,6 +60,9 @@ public interface QuotaConfig { public static final ConfigKey QuotaActivationRuleTimeout = new ConfigKey<>("Advanced", Long.class, "quota.activationrule.timeout", "2000", "The maximum runtime," + " in milliseconds, to execute the quota tariff's activation rule; if it is reached, a timeout will happen.", true); + ConfigKey QuotaAccountEnabled = new ConfigKey<>("Advanced", Boolean.class, "quota.account.enabled", "true", "Indicates whether Quota plugin is enabled or not for " + + "the account.", 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/QuotaAccountDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java index 23df0d43cbc..084abcfc2b5 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java @@ -16,8 +16,10 @@ //under the License. package org.apache.cloudstack.quota.dao; +import java.util.ArrayList; import java.util.List; +import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.vo.QuotaAccountVO; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -36,7 +38,15 @@ public class QuotaAccountDaoImpl extends GenericDaoBase im @Override public List listAllQuotaAccount() { - return listAllQuotaAccount(null, null).first(); + List accountsWithQuotaEnabled = new ArrayList<>(); + for (QuotaAccountVO account : listAllQuotaAccount(null, null).first()) { + if (Boolean.TRUE.equals(getQuotaAccountEnabled(account.getAccountId()))) { + accountsWithQuotaEnabled.add(account); + continue; + } + s_logger.trace(String.format("Account [%s] has the quota plugin disabled. Thus, it will not receive quota emails.", account)); + } + return accountsWithQuotaEnabled; } @Override @@ -80,4 +90,7 @@ public class QuotaAccountDaoImpl extends GenericDaoBase im }); } + public Boolean getQuotaAccountEnabled(Long accountId) { + return QuotaConfig.QuotaAccountEnabled.valueIn(accountId); + } } diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImplTest.java new file mode 100644 index 00000000000..19040168c10 --- /dev/null +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaAccountDaoImplTest.java @@ -0,0 +1,80 @@ +// 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.Pair; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaAccountDaoImplTest { + @Spy + QuotaAccountDaoImpl quotaAccountDaoImplSpy; + + @Test + public void listAllQuotaAccountTestShouldReturnNullWithAccountWithQuotaDisabled() { + QuotaAccountVO accountWithQuotaDisabled = new QuotaAccountVO(1L); + + List allQuotaAccounts = List.of(accountWithQuotaDisabled); + Pair,Integer> pair = new Pair<>(allQuotaAccounts, 1); + + Mockito.doReturn(pair).when(quotaAccountDaoImplSpy).listAllQuotaAccount(null, null); + Mockito.doReturn(false).when(quotaAccountDaoImplSpy).getQuotaAccountEnabled(accountWithQuotaDisabled.getAccountId()); + + int expected = quotaAccountDaoImplSpy.listAllQuotaAccount().size(); + Assert.assertEquals(0, expected); + } + + @Test + public void listAllQuotaAccountTestShouldReturnSizeOneWithAccountWithQuotaEnabled() { + QuotaAccountVO accountWithQuotaEnabled = new QuotaAccountVO(2L); + + List allQuotaAccounts = List.of(accountWithQuotaEnabled); + Pair,Integer> pair = new Pair<>(allQuotaAccounts, 1); + + Mockito.doReturn(pair).when(quotaAccountDaoImplSpy).listAllQuotaAccount(null, null); + Mockito.doReturn(true).when(quotaAccountDaoImplSpy).getQuotaAccountEnabled(accountWithQuotaEnabled.getAccountId()); + + int expected = quotaAccountDaoImplSpy.listAllQuotaAccount().size(); + Assert.assertEquals(1, expected); + } + + @Test + public void listAllQuotaAccountTestShouldReturnOnlyAccountsWithQuotaEnabled() { + QuotaAccountVO accountWithQuotaEnabled = new QuotaAccountVO(1L); + QuotaAccountVO accountWithQuotaDisabled = new QuotaAccountVO(2L); + + List allQuotaAccounts = List.of(accountWithQuotaEnabled, accountWithQuotaDisabled); + Pair,Integer> pair = new Pair<>(allQuotaAccounts, 1); + + Mockito.doReturn(pair).when(quotaAccountDaoImplSpy).listAllQuotaAccount(null, null); + Mockito.doReturn(true).when(quotaAccountDaoImplSpy).getQuotaAccountEnabled(accountWithQuotaEnabled.getAccountId()); + Mockito.doReturn(false).when(quotaAccountDaoImplSpy).getQuotaAccountEnabled(accountWithQuotaDisabled.getAccountId()); + + int expected = quotaAccountDaoImplSpy.listAllQuotaAccount().size(); + Assert.assertEquals(1, expected); + } + +} diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java index 863a0c3cd59..5b3526fba50 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java @@ -69,6 +69,10 @@ public class QuotaSummaryResponse extends BaseResponse { @Param(description = "currency") private String currency; + @SerializedName("quotaenabled") + @Param(description = "if the account has the quota config enabled") + private boolean quotaEnabled; + public QuotaSummaryResponse() { super(); } @@ -152,4 +156,12 @@ public class QuotaSummaryResponse extends BaseResponse { public void setCurrency(String currency) { this.currency = currency; } + + public boolean getQuotaEnabled() { + return quotaEnabled; + } + + public void setQuotaEnabled(boolean quotaEnabled) { + this.quotaEnabled = quotaEnabled; + } } 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 f0cf8833b85..afcfe3ead99 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 @@ -141,7 +141,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {QuotaPluginEnabled, QuotaEnableEnforcement, QuotaCurrencySymbol, QuotaStatementPeriod, QuotaSmtpHost, QuotaSmtpPort, QuotaSmtpTimeout, - QuotaSmtpUser, QuotaSmtpPassword, QuotaSmtpAuthType, QuotaSmtpSender, QuotaSmtpEnabledSecurityProtocols, QuotaSmtpUseStartTLS, QuotaActivationRuleTimeout}; + QuotaSmtpUser, QuotaSmtpPassword, QuotaSmtpAuthType, QuotaSmtpSender, QuotaSmtpEnabledSecurityProtocols, QuotaSmtpUseStartTLS, QuotaActivationRuleTimeout, QuotaAccountEnabled}; } @Override diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index babd453baa1..1f3d9f05f0f 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -30,6 +30,7 @@ "label.account.name": "Account name", "label.account.specific": "Account-specific", "label.accounts": "Accounts", +"label.accountstate": "Account state", "label.accounttype": "Account type", "label.acl.export": "Export ACLs", "label.acl.id": "ACL ID", @@ -1361,6 +1362,7 @@ "label.quota.type.unit": "Usage unit", "label.quota.usage": "Quota consumption", "label.quota.value": "Quota value", +"label.quotastate": "Quota state", "label.quota_enforce": "Enforce Quota", "label.rados.monitor": "RADOS monitor", "label.rados.monitor.description": "The RADOS monitor(s). If there are multiple monitors, they are separated by comma. For example, \"192.168.0.1,192.168.0.2,192.168.0.3\", \"mon1, mon2, mon3\". IPv6 addresses must include square brackets, for example, \"[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]\".", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index d6b098d56ec..ce665ffc0f4 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -29,6 +29,7 @@ "label.account.name": "Nome da conta", "label.account.specific": "Espec\u00edfico da conta", "label.accounts": "Contas", +"label.accountstate": "Estado da conta", "label.accounttype": "Tipo de conta", "label.acl.export": "Exportar ACLs", "label.acl.id": "ACL ID", @@ -1267,6 +1268,7 @@ "label.quota.statement.quota": "Utiliza\u00e7\u00e3o", "label.quota.statement.tariff": "Tarifa", "label.quota.summary": "Relat\u00f3rios", +"label.quotastate": "Estado da cota", "label.summary": "Sum\u00e1rio", "label.quota.tariff": "Tarifa", "label.quota.tariff.effectivedate": "Data efetiva", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 690e4f0a858..dc71216a54c 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -195,6 +195,9 @@ +