Refactor Quota Summary API (#10505)

* Refactor Quota Summary API

* Fixes imports

* Fix QuotaServiceImplTest

* Update plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java

Co-authored-by: Fabricio Duarte <fabricio.duarte.jr@gmail.com>

* Fix QuotaSummaryCmd

* Remove unnecessary imports

* Remove unused createQuotaSummaryResponse declarations

* Remove unnecessary imports

* Update plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java

Co-authored-by: dahn <daan.hoogland@gmail.com>

* Fix QuotaSummaryCmd

* Fix QuotaResponseBuilderImplTest

* Refactor test

* Fix QuotaSummaryCmd

* Fix projectid behavior

* Simplify QuotaSummary and deprecate listall

* Fix createQuotaSummaryResponse

* Remove unused import

* Apply suggestions + some adjustments

* Remove duplicated check

* Fix checkstyle

* Adjust entity owner

* Remove unused method + fix tests

* Add missing @ACL to some parameters

* Adjust how the parameters behave

* Allow domain admins and users to use keyword

* Address reviews

---------

Co-authored-by: Julien Hervot de Mattos Vaz <julien.vaz@scclouds.com.br>
Co-authored-by: Fabricio Duarte <fabricio.duarte.jr@gmail.com>
Co-authored-by: dahn <daan.hoogland@gmail.com>
This commit is contained in:
julien-vaz 2026-03-31 20:29:30 -03:00 committed by GitHub
parent 5d61ba3538
commit 4f93ba888c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 747 additions and 231 deletions

View File

@ -138,6 +138,8 @@ public interface AccountService {
Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly);
Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId);
/**
* returns the user account object for a given user id
* @param userId user id

View File

@ -20,6 +20,7 @@ public class ApiConstants {
public static final String ACCOUNT = "account";
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNT_NAME = "accountname";
public static final String ACCOUNT_STATE_TO_SHOW = "accountstatetoshow";
public static final String ACCOUNT_TYPE = "accounttype";
public static final String ACCOUNT_ID = "accountid";
public static final String ACCOUNT_IDS = "accountids";

View File

@ -262,7 +262,7 @@ public class DomainDaoImpl extends GenericDaoBase<DomainVO, Long> implements Dom
SearchCriteria<DomainVO> sc = DomainPairSearch.create();
sc.setParameters("id", parentId, childId);
List<DomainVO> domainPair = listBy(sc);
List<DomainVO> domainPair = listIncludingRemovedBy(sc);
if ((domainPair != null) && (domainPair.size() == 2)) {
DomainVO d1 = domainPair.get(0);

View File

@ -0,0 +1,48 @@
-- 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.
-- cloud_usage.quota_summary_view source
-- Create view for quota summary
DROP VIEW IF EXISTS `cloud_usage`.`quota_summary_view`;
CREATE VIEW `cloud_usage`.`quota_summary_view` AS
SELECT
cloud_usage.quota_account.account_id AS account_id,
cloud_usage.quota_account.quota_balance AS quota_balance,
cloud_usage.quota_account.quota_balance_date AS quota_balance_date,
cloud_usage.quota_account.quota_enforce AS quota_enforce,
cloud_usage.quota_account.quota_min_balance AS quota_min_balance,
cloud_usage.quota_account.quota_alert_date AS quota_alert_date,
cloud_usage.quota_account.quota_alert_type AS quota_alert_type,
cloud_usage.quota_account.last_statement_date AS last_statement_date,
cloud.account.uuid AS account_uuid,
cloud.account.account_name AS account_name,
cloud.account.state AS account_state,
cloud.account.removed AS account_removed,
cloud.domain.id AS domain_id,
cloud.domain.uuid AS domain_uuid,
cloud.domain.name AS domain_name,
cloud.domain.path AS domain_path,
cloud.domain.removed AS domain_removed,
cloud.projects.uuid AS project_uuid,
cloud.projects.name AS project_name,
cloud.projects.removed AS project_removed
FROM
cloud_usage.quota_account
INNER JOIN cloud.account ON (cloud.account.id = cloud_usage.quota_account.account_id)
INNER JOIN cloud.domain ON (cloud.domain.id = cloud.account.domain_id)
LEFT JOIN cloud.projects ON (cloud.account.type = 5 AND cloud.account.id = cloud.projects.project_account_id);

View File

@ -0,0 +1,35 @@
// 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;
import org.apache.commons.lang3.StringUtils;
public enum QuotaAccountStateFilter {
ALL, ACTIVE, REMOVED;
public static QuotaAccountStateFilter getValue(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
for (QuotaAccountStateFilter state : values()) {
if (state.name().equalsIgnoreCase(value)) {
return state;
}
}
return null;
}
}

View File

@ -0,0 +1,32 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.quota.dao;
import java.util.List;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.GenericDao;
public interface QuotaSummaryDao extends GenericDao<QuotaSummaryVO, Long> {
Pair<List<QuotaSummaryVO>, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize);
}

View File

@ -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 java.util.List;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
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;
public class QuotaSummaryDaoImpl extends GenericDaoBase<QuotaSummaryVO, Long> implements QuotaSummaryDao {
@Override
public Pair<List<QuotaSummaryVO>, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize) {
SearchCriteria<QuotaSummaryVO> searchCriteria = createListQuotaSummariesSearchCriteria(accountId, accountName, domainId, domainPath, accountStateFilter);
Filter filter = new Filter(QuotaSummaryVO.class, "accountName", true, startIndex, pageSize);
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<Pair<List<QuotaSummaryVO>, Integer>>) status -> searchAndCount(searchCriteria, filter));
}
protected SearchCriteria<QuotaSummaryVO> createListQuotaSummariesSearchCriteria(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter) {
SearchCriteria<QuotaSummaryVO> searchCriteria = createListQuotaSummariesSearchBuilder(accountStateFilter).create();
searchCriteria.setParametersIfNotNull("accountId", accountId);
searchCriteria.setParametersIfNotNull("domainId", domainId);
if (accountName != null) {
searchCriteria.setParameters("accountName", "%" + accountName + "%");
}
if (domainPath != null) {
searchCriteria.setParameters("domainPath", domainPath + "%");
}
return searchCriteria;
}
protected SearchBuilder<QuotaSummaryVO> createListQuotaSummariesSearchBuilder(QuotaAccountStateFilter accountStateFilter) {
SearchBuilder<QuotaSummaryVO> searchBuilder = createSearchBuilder();
searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ);
searchBuilder.and("accountName", searchBuilder.entity().getAccountName(), SearchCriteria.Op.LIKE);
searchBuilder.and("domainId", searchBuilder.entity().getDomainId(), SearchCriteria.Op.EQ);
searchBuilder.and("domainPath", searchBuilder.entity().getDomainPath(), SearchCriteria.Op.LIKE);
if (QuotaAccountStateFilter.REMOVED.equals(accountStateFilter)) {
searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NNULL);
} else if (QuotaAccountStateFilter.ACTIVE.equals(accountStateFilter)) {
searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NULL);
}
return searchBuilder;
}
}

View File

@ -0,0 +1,154 @@
// 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 java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.cloud.user.Account;
@Entity
@Table(name = "quota_summary_view")
public class QuotaSummaryVO {
@Id
@Column(name = "account_id")
private Long accountId = null;
@Column(name = "quota_enforce")
private Integer quotaEnforce = 0;
@Column(name = "quota_balance")
private BigDecimal quotaBalance;
@Column(name = "quota_balance_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date quotaBalanceDate = null;
@Column(name = "quota_min_balance")
private BigDecimal quotaMinBalance;
@Column(name = "quota_alert_type")
private Integer quotaAlertType = null;
@Column(name = "quota_alert_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date quotaAlertDate = null;
@Column(name = "last_statement_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date lastStatementDate = null;
@Column(name = "account_uuid")
private String accountUuid;
@Column(name = "account_name")
private String accountName;
@Column(name = "account_state")
@Enumerated(EnumType.STRING)
private Account.State accountState;
@Column(name = "account_removed")
private Date accountRemoved;
@Column(name = "domain_id")
private Long domainId;
@Column(name = "domain_uuid")
private String domainUuid;
@Column(name = "domain_name")
private String domainName;
@Column(name = "domain_path")
private String domainPath;
@Column(name = "domain_removed")
private Date domainRemoved;
@Column(name = "project_uuid")
private String projectUuid;
@Column(name = "project_name")
private String projectName;
@Column(name = "project_removed")
private Date projectRemoved;
public Long getAccountId() {
return accountId;
}
public BigDecimal getQuotaBalance() {
return quotaBalance;
}
public String getAccountUuid() {
return accountUuid;
}
public String getAccountName() {
return accountName;
}
public Date getAccountRemoved() {
return accountRemoved;
}
public Account.State getAccountState() {
return accountState;
}
public Long getDomainId() {
return domainId;
}
public String getDomainUuid() {
return domainUuid;
}
public String getDomainPath() {
return domainPath;
}
public Date getDomainRemoved() {
return domainRemoved;
}
public String getProjectUuid() {
return projectUuid;
}
public String getProjectName() {
return projectName;
}
public Date getProjectRemoved() {
return projectRemoved;
}
}

View File

@ -19,7 +19,8 @@
<bean id="presetVariableHelper" class="org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableHelper" />
<bean id="QuotaTariffDao" class="org.apache.cloudstack.quota.dao.QuotaTariffDaoImpl" />
<bean id="QuotaAccountDao" class="org.apache.cloudstack.quota.dao.QuotaAccountDaoImpl" />
<bean id="QuotaSummaryDao" class="org.apache.cloudstack.quota.dao.QuotaSummaryDaoImpl" />
<bean id="QuotaAccountDao" class="org.apache.cloudstack.quota.dao.QuotaAccountDaoImpl" />
<bean id="QuotaBalanceDao" class="org.apache.cloudstack.quota.dao.QuotaBalanceDaoImpl" />
<bean id="QuotaCreditsDao" class="org.apache.cloudstack.quota.dao.QuotaCreditsDaoImpl" />
<bean id="QuotaEmailTemplatesDao"

View File

@ -16,61 +16,80 @@
//under the License.
package org.apache.cloudstack.api.command;
import com.cloud.user.Account;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaSummaryResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.QuotaService;
import org.apache.commons.lang3.ObjectUtils;
import java.util.List;
import javax.inject.Inject;
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists Quota balance summary of Accounts and Projects.", since = "4.7.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET")
public class QuotaSummaryCmd extends BaseListCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Optional, Account Id for which statement needs to be generated")
private String accountName;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "Optional, If domain Id is given and the caller is domain admin then the statement is generated for domain.")
private Long domainId;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, required = false, description = "Optional, to list all Accounts irrespective of the quota activity")
private Boolean listAll;
@Inject
QuotaResponseBuilder quotaResponseBuilder;
@Inject
QuotaResponseBuilder _responseBuilder;
QuotaService quotaService;
public QuotaSummaryCmd() {
super();
}
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the Account for which balance will be listed. Can not be specified with projectid.", since = "4.23.0")
private Long accountId;
@ACL
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Name of the Account for which balance will be listed.")
private String accountName;
@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "ID of the Domain for which balance will be listed. May be used individually or with accountname.")
private Long domainId;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "False (default) lists the Quota balance summary for calling Account. True lists balance summary for " +
"Accounts which the caller has access. If domain ID is informed, this parameter is considered as true.")
private Boolean listAll;
@Parameter(name = ApiConstants.ACCOUNT_STATE_TO_SHOW, type = CommandType.STRING, description = "Possible values are [ALL, ACTIVE, REMOVED]. ALL will list summaries for " +
"active and removed accounts; ACTIVE will list summaries only for active accounts; REMOVED will list summaries only for removed accounts. The default value is ACTIVE.",
since = "4.23.0")
private String accountStateToShow;
@ACL
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "ID of the Project for which balance will be listed. Can not be specified with accountId.", since = "4.23.0")
private Long projectId;
@Override
public void execute() {
Account caller = CallContext.current().getCallingAccount();
Pair<List<QuotaSummaryResponse>, Integer> responses;
if (caller.getType() == Account.Type.ADMIN) {
if (getAccountName() != null && getDomainId() != null)
responses = _responseBuilder.createQuotaSummaryResponse(getAccountName(), getDomainId());
else
responses = _responseBuilder.createQuotaSummaryResponse(isListAll(), getKeyword(), getStartIndex(), getPageSizeVal());
} else {
responses = _responseBuilder.createQuotaSummaryResponse(caller.getAccountName(), caller.getDomainId());
}
final ListResponse<QuotaSummaryResponse> response = new ListResponse<QuotaSummaryResponse>();
Pair<List<QuotaSummaryResponse>, Integer> responses = quotaResponseBuilder.createQuotaSummaryResponse(this);
ListResponse<QuotaSummaryResponse> response = new ListResponse<>();
response.setResponses(responses.first(), responses.second());
response.setResponseName(getCommandName());
setResponseObject(response);
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public String getAccountName() {
return accountName;
}
@ -88,16 +107,31 @@ public class QuotaSummaryCmd extends BaseListCmd {
}
public Boolean isListAll() {
return listAll == null ? false: listAll;
// If a domain ID was specified, then allow listing all summaries of domain
return ObjectUtils.defaultIfNull(listAll, Boolean.FALSE) || domainId != null;
}
public void setListAll(Boolean listAll) {
this.listAll = listAll;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
public Long getProjectId() {
return projectId;
}
public QuotaAccountStateFilter getAccountStateToShow() {
QuotaAccountStateFilter state = QuotaAccountStateFilter.getValue(accountStateToShow);
if (state != null) {
return state;
}
return QuotaAccountStateFilter.ACTIVE;
}
@Override
public long getEntityOwnerId() {
if (ObjectUtils.allNull(accountId, accountName, projectId)) {
return -1;
}
return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId);
}
}

View File

@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
@ -52,11 +53,7 @@ public interface QuotaResponseBuilder {
QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO> quotaUsage, Date startDate, Date endDate);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll, String keyword, Long startIndex, Long pageSize);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(String accountName, Long domainId);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd);
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate);

View File

@ -42,7 +42,9 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.domain.Domain;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.projects.dao.ProjectDao;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
@ -56,6 +58,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
@ -74,11 +77,13 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariable
import org.apache.cloudstack.quota.activationrule.presetvariables.Value;
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.QuotaSummaryDao;
import org.apache.cloudstack.quota.dao.QuotaTariffDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
@ -86,10 +91,13 @@ 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.QuotaSummaryVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
@ -108,7 +116,6 @@ import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
@Component
public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@ -121,7 +128,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private QuotaCreditsDao quotaCreditsDao;
@Inject
private QuotaUsageDao _quotaUsageDao;
private QuotaUsageDao quotaUsageDao;
@Inject
private QuotaEmailTemplatesDao _quotaEmailTemplateDao;
@ -132,24 +139,30 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private AccountDao _accountDao;
@Inject
private ProjectDao projectDao;
@Inject
private QuotaAccountDao quotaAccountDao;
@Inject
private DomainDao _domainDao;
private DomainDao domainDao;
@Inject
private AccountManager _accountMgr;
@Inject
private QuotaStatement _statement;
private QuotaStatement quotaStatement;
@Inject
private QuotaManager _quotaManager;
@Inject
private QuotaEmailConfigurationDao quotaEmailConfigurationDao;
@Inject
private QuotaSummaryDao quotaSummaryDao;
@Inject
private JsInterpreterHelper jsInterpreterHelper;
@Inject
private ApiDiscoveryService apiDiscoveryService;
private final Class<?>[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class};
private Set<Account.Type> accountTypesThatCanListAllQuotaSummaries = Sets.newHashSet(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN);
protected void checkActivationRulesAllowed(String activationRule) {
if (!_quotaService.isJsInterpretationEnabled() && StringUtils.isNotEmpty(activationRule)) {
throw new PermissionDeniedException("Quota Tariff Activation Rule cannot be set, as Javascript interpretation is disabled in the configuration.");
@ -180,75 +193,113 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(final String accountName, final Long domainId) {
List<QuotaSummaryResponse> result = new ArrayList<QuotaSummaryResponse>();
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
if (accountName != null && domainId != null) {
Account account = _accountDao.findActiveAccount(accountName, domainId);
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
if (!accountTypesThatCanListAllQuotaSummaries.contains(caller.getType()) || !cmd.isListAll()) {
return getQuotaSummaryResponse(cmd.getEntityOwnerId(), null, null, cmd);
}
return new Pair<>(result, result.size());
return getQuotaSummaryResponseWithListAll(cmd, caller);
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll) {
return createQuotaSummaryResponse(listAll, null, null, null);
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll, final String keyword, final Long startIndex, final Long pageSize) {
List<QuotaSummaryResponse> result = new ArrayList<QuotaSummaryResponse>();
Integer count = 0;
if (listAll) {
Filter filter = new Filter(AccountVO.class, "accountName", true, startIndex, pageSize);
Pair<List<AccountVO>, Integer> data = _accountDao.findAccountsLike(keyword, filter);
count = data.second();
for (final AccountVO account : data.first()) {
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
}
} else {
Pair<List<QuotaAccountVO>, Integer> data = quotaAccountDao.listAllQuotaAccount(startIndex, pageSize);
count = data.second();
for (final QuotaAccountVO quotaAccount : data.first()) {
AccountVO account = _accountDao.findById(quotaAccount.getId());
if (account == null) {
continue;
}
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
protected Pair<List<QuotaSummaryResponse>, Integer> getQuotaSummaryResponseWithListAll(QuotaSummaryCmd cmd, Account caller) {
Long domainId = cmd.getDomainId();
if (domainId != null) {
DomainVO domain = domainDao.findByIdIncludingRemoved(domainId);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Domain [%s] does not exist.", domainId));
}
}
return new Pair<>(result, count);
String domainPath = getDomainPathByDomainIdForDomainAdmin(caller);
Long accountId = cmd.getEntityOwnerId();
if (accountId == -1) {
accountId = cmd.isListAll() ? null : caller.getAccountId();
}
return getQuotaSummaryResponse(accountId, domainId, domainPath, cmd);
}
protected QuotaSummaryResponse getQuotaSummaryResponse(final Account account) {
Calendar[] period = _statement.getCurrentStatementTime();
if (account != null) {
QuotaSummaryResponse qr = new QuotaSummaryResponse();
DomainVO domain = _domainDao.findById(account.getDomainId());
BigDecimal curBalance = _quotaBalanceDao.lastQuotaBalance(account.getAccountId(), account.getDomainId(), period[1].getTime());
BigDecimal quotaUsage = _quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, period[0].getTime(), period[1].getTime());
qr.setAccountId(account.getUuid());
qr.setAccountName(account.getAccountName());
qr.setDomainId(domain.getUuid());
qr.setDomainName(domain.getName());
qr.setBalance(curBalance);
qr.setQuotaUsage(quotaUsage);
qr.setState(account.getState());
qr.setStartDate(period[0].getTime());
qr.setEndDate(period[1].getTime());
qr.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
qr.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId()));
qr.setObjectName("summary");
return qr;
} else {
return new QuotaSummaryResponse();
/**
* Retrieves the domain path of the caller's domain (if the caller is Domain Admin) for filtering in the quota summary query.
* @return null if the caller is an Admin or the domain path of the caller's domain if the caller is a Domain Admin.
* @throws InvalidParameterValueException if it cannot find the domain.
*/
protected String getDomainPathByDomainIdForDomainAdmin(Account caller) {
if (caller.getType() != Account.Type.DOMAIN_ADMIN) {
return null;
}
Long domainId = caller.getDomainId();
Domain domain = domainDao.findById(domainId);
_accountMgr.checkAccess(caller, domain);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Domain ID [%s] is invalid.", domainId));
}
return domain.getPath();
}
/**
* Returns a <code>List</code> of <code>QuotaSummaryResponse</code> based on the provided parameters.
* @param accountId ID of the Account to return the summaries for. If <code>-1</code>, either because no specific
* Account was provided, or list all is disabled, then the summary is generated for the calling Account.
* @param domainId ID of the Domain to return the summaries for.
* @param domainPath path of the Domain to return the summaries for.
*/
protected Pair<List<QuotaSummaryResponse>, Integer> getQuotaSummaryResponse(Long accountId, Long domainId, String domainPath, QuotaSummaryCmd cmd) {
if (accountId != null && accountId == -1) {
accountId = CallContext.current().getCallingAccountId();
}
Pair<List<QuotaSummaryVO>, Integer> pairSummaries = quotaSummaryDao.listQuotaSummariesForAccountAndOrDomain(accountId, cmd.getKeyword(), domainId, domainPath,
cmd.getAccountStateToShow(), cmd.getStartIndex(), cmd.getPageSizeVal());
List<QuotaSummaryVO> summaries = pairSummaries.first();
if (CollectionUtils.isEmpty(summaries)) {
logger.info("There are no summaries to list for parameters [{}].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "accountName", "domainId", "listAll", "page", "pageSize"));
return new Pair<>(new ArrayList<>(), 0);
}
List<QuotaSummaryResponse> responses = summaries.stream().map(this::getQuotaSummaryResponse).collect(Collectors.toList());
return new Pair<>(responses, pairSummaries.second());
}
protected QuotaSummaryResponse getQuotaSummaryResponse(QuotaSummaryVO summary) {
QuotaSummaryResponse response = new QuotaSummaryResponse();
Account account = _accountDao.findByUuidIncludingRemoved(summary.getAccountUuid());
Calendar[] period = quotaStatement.getCurrentStatementTime();
Date startDate = period[0].getTime();
Date endDate = period[1].getTime();
BigDecimal quotaUsage = quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, startDate, endDate);
response.setQuotaUsage(quotaUsage);
response.setStartDate(startDate);
response.setEndDate(endDate);
response.setAccountId(summary.getAccountUuid());
response.setAccountName(summary.getAccountName());
response.setDomainId(summary.getDomainUuid());
response.setDomainPath(summary.getDomainPath());
response.setBalance(summary.getQuotaBalance());
response.setState(summary.getAccountState());
response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
response.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId()));
response.setDomainRemoved(summary.getDomainRemoved() != null);
response.setAccountRemoved(summary.getAccountRemoved() != null);
response.setObjectName("summary");
if (summary.getProjectUuid() != null) {
response.setProjectId(summary.getProjectUuid());
response.setProjectName(summary.getProjectName());
response.setProjectRemoved(summary.getProjectRemoved() != null);
}
return response;
}
public boolean isUserAllowedToSeeActivationRules(User user) {

View File

@ -17,7 +17,6 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import com.google.gson.annotations.SerializedName;
@ -30,40 +29,48 @@ import com.cloud.user.Account.State;
public class QuotaSummaryResponse extends BaseResponse {
@SerializedName("accountid")
@Param(description = "Account ID")
@Param(description = "Account's ID")
private String accountId;
@SerializedName("account")
@Param(description = "Account name")
@Param(description = "Account's name")
private String accountName;
@SerializedName("domainid")
@Param(description = "Domain ID")
@Param(description = "Domain's ID")
private String domainId;
@SerializedName("domain")
@Param(description = "Domain name")
private String domainName;
@Param(description = "Domain's path")
private String domainPath;
@SerializedName("balance")
@Param(description = "Account balance")
@Param(description = "Account's balance")
private BigDecimal balance;
@SerializedName("state")
@Param(description = "Account state")
@Param(description = "Account's state")
private State state;
@SerializedName("domainremoved")
@Param(description = "If the domain is removed or not", since = "4.23.0")
private boolean domainRemoved;
@SerializedName("accountremoved")
@Param(description = "If the account is removed or not", since = "4.23.0")
private boolean accountRemoved;
@SerializedName("quota")
@Param(description = "Quota usage of this period")
@Param(description = "Quota consumed between the startdate and enddate")
private BigDecimal quotaUsage;
@SerializedName("startdate")
@Param(description = "Start date")
private Date startDate = null;
@Param(description = "Start date of the quota consumption")
private Date startDate;
@SerializedName("enddate")
@Param(description = "End date")
private Date endDate = null;
@Param(description = "End date of the quota consumption")
private Date endDate;
@SerializedName("currency")
@Param(description = "Currency")
@ -73,9 +80,17 @@ public class QuotaSummaryResponse extends BaseResponse {
@Param(description = "If the account has the quota config enabled")
private boolean quotaEnabled;
public QuotaSummaryResponse() {
super();
}
@SerializedName("projectname")
@Param(description = "Name of the project", since = "4.23.0")
private String projectName;
@SerializedName("projectid")
@Param(description = "Project's id", since = "4.23.0")
private String projectId;
@SerializedName("projectremoved")
@Param(description = "Whether the project is removed or not", since = "4.23.0")
private Boolean projectRemoved;
public String getAccountId() {
return accountId;
@ -101,28 +116,16 @@ public class QuotaSummaryResponse extends BaseResponse {
this.domainId = domainId;
}
public String getDomainName() {
return domainName;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public BigDecimal getQuotaUsage() {
return quotaUsage;
}
public State getState() {
return state;
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public void setState(State state) {
this.state = state;
}
public void setQuotaUsage(BigDecimal startQuota) {
this.quotaUsage = startQuota.setScale(2, RoundingMode.HALF_EVEN);
public void setQuotaUsage(BigDecimal quotaUsage) {
this.quotaUsage = quotaUsage;
}
public BigDecimal getBalance() {
@ -130,38 +133,42 @@ public class QuotaSummaryResponse extends BaseResponse {
}
public void setBalance(BigDecimal balance) {
this.balance = balance.setScale(2, RoundingMode.HALF_EVEN);
}
public Date getStartDate() {
return startDate == null ? null : new Date(startDate.getTime());
this.balance = balance;
}
public void setStartDate(Date startDate) {
this.startDate = startDate == null ? null : new Date(startDate.getTime());
}
public Date getEndDate() {
return endDate == null ? null : new Date(endDate.getTime());
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate == null ? null : new Date(endDate.getTime());
}
public String getCurrency() {
return currency;
this.endDate = endDate;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public boolean getQuotaEnabled() {
return quotaEnabled;
}
public void setQuotaEnabled(boolean quotaEnabled) {
this.quotaEnabled = quotaEnabled;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public void setProjectRemoved(Boolean projectRemoved) {
this.projectRemoved = projectRemoved;
}
public void setDomainRemoved(boolean domainRemoved) {
this.domainRemoved = domainRemoved;
}
public void setAccountRemoved(boolean accountRemoved) {
this.accountRemoved = accountRemoved;
}
}

View File

@ -26,6 +26,8 @@ import java.util.TimeZone;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.projects.ProjectManager;
import com.cloud.user.AccountService;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsCmd;
@ -75,6 +77,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
@Inject
private AccountDao _accountDao;
@Inject
private AccountService accountService;
@Inject
private QuotaAccountDao _quotaAcc;
@Inject
private QuotaUsageDao _quotaUsageDao;
@ -86,6 +90,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
private QuotaBalanceDao _quotaBalanceDao;
@Inject
private QuotaResponseBuilder _respBldr;
@Inject
private ProjectManager projectMgr;
private TimeZone _usageTimezone;

View File

@ -16,13 +16,11 @@
// under the License.
package org.apache.cloudstack.api.response;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -31,6 +29,7 @@ import java.util.Set;
import java.util.HashSet;
import java.util.function.Consumer;
import com.cloud.domain.Domain;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.PermissionDeniedException;
@ -43,10 +42,10 @@ import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.discovery.ApiDiscoveryService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.jsinterpreter.JsInterpreterHelper;
import org.apache.cloudstack.quota.QuotaService;
import org.apache.cloudstack.quota.QuotaStatement;
@ -79,7 +78,10 @@ import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
@ -89,8 +91,7 @@ import com.cloud.user.dao.UserDao;
import com.cloud.user.User;
import junit.framework.TestCase;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class QuotaResponseBuilderImplTest extends TestCase {
@ -153,7 +154,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Account accountMock;
@Mock
DomainVO domainVOMock;
DomainVO domainVoMock;
@Mock
QuotaConfigureEmailCmd quotaConfigureEmailCmdMock;
@ -161,6 +162,9 @@ public class QuotaResponseBuilderImplTest extends TestCase {
@Mock
QuotaAccountVO quotaAccountVOMock;
@Mock
CallContext callContextMock;
@Mock
QuotaEmailTemplatesVO quotaEmailTemplatesVoMock;
@ -184,17 +188,8 @@ public class QuotaResponseBuilderImplTest extends TestCase {
CallContext.register(callerUserMock, callerAccountMock);
}
private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException {
Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);
f.set(QuotaConfig.QuotaAccountEnabled, value);
}
private Calendar[] createPeriodForQuotaSummary() {
final Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR, 0);
return new Calendar[] {calendar, calendar};
}
@Mock
Pair<List<QuotaSummaryResponse>, Integer> quotaSummaryResponseMock1, quotaSummaryResponseMock2;
@Mock
QuotaValidateActivationRuleCmd quotaValidateActivationRuleCmdMock = Mockito.mock(QuotaValidateActivationRuleCmd.class);
@ -466,36 +461,6 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Mockito.verify(quotaTariffVoMock).setRemoved(Mockito.any(Date.class));
}
@Test
public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsDisabledShouldReturnFalse() throws NoSuchFieldException, IllegalAccessException {
Calendar[] period = createPeriodForQuotaSummary();
overrideDefaultQuotaEnabledConfigValue("false");
Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class));
QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock);
assertFalse(quotaSummaryResponse.getQuotaEnabled());
}
@Test
public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsEnabledShouldReturnTrue() throws NoSuchFieldException, IllegalAccessException {
Calendar[] period = createPeriodForQuotaSummary();
overrideDefaultQuotaEnabledConfigValue("true");
Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class));
QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock);
assertTrue(quotaSummaryResponse.getQuotaEnabled());
}
@Test
public void filterSupportedTypesTestReturnWhenQuotaTypeDoesNotMatch() throws NoSuchFieldException {
List<Pair<String, String>> variables = new ArrayList<>();
@ -576,6 +541,63 @@ public class QuotaResponseBuilderImplTest extends TestCase {
quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock);
}
@Test
public void createQuotaSummaryResponseTestNotListAllAndAllAccountTypesReturnsSingleRecord() {
QuotaSummaryCmd cmd = new QuotaSummaryCmd();
try(MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(quotaSummaryResponseMock1).when(quotaResponseBuilderSpy).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
for (Account.Type type : Account.Type.values()) {
Mockito.doReturn(type).when(accountMock).getType();
Pair<List<QuotaSummaryResponse>, Integer> result = quotaResponseBuilderSpy.createQuotaSummaryResponse(cmd);
Assert.assertEquals(quotaSummaryResponseMock1, result);
}
Mockito.verify(quotaResponseBuilderSpy, Mockito.times(Account.Type.values().length)).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any());
};
}
@Test
public void getDomainPathByDomainIdForDomainAdminTestAccountNotDomainAdminReturnsNull() {
for (Account.Type type : Account.Type.values()) {
if (Account.Type.DOMAIN_ADMIN.equals(type)) {
continue;
}
Mockito.doReturn(type).when(accountMock).getType();
Assert.assertNull(quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock));
}
}
@Test(expected = InvalidParameterValueException.class)
public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNullThrowsInvalidParameterValueException() {
Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType();
Mockito.doReturn(null).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.lenient().doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock);
}
@Test
public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNotNullReturnsPath() {
String expected = "/test/";
Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType();
Mockito.doReturn(domainVoMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
Mockito.doReturn(expected).when(domainVoMock).getPath();
String result = quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock);
Assert.assertEquals(expected, result);
}
@Test
public void getQuotaEmailConfigurationVoTestTemplateNameIsNull() {
Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName();

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.quota;
import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.db.TransactionLegacy;
import junit.framework.TestCase;
@ -33,8 +34,10 @@ import org.joda.time.DateTime;
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;
import org.mockito.junit.MockitoJUnitRunner;
import javax.naming.ConfigurationException;
@ -48,7 +51,7 @@ import java.util.List;
public class QuotaServiceImplTest extends TestCase {
@Mock
AccountDao accountDao;
AccountDao accountDaoMock;
@Mock
QuotaAccountDao quotaAcc;
@Mock
@ -61,8 +64,13 @@ public class QuotaServiceImplTest extends TestCase {
QuotaBalanceDao quotaBalanceDao;
@Mock
QuotaResponseBuilder respBldr;
@Mock
private AccountVO accountVoMock;
@Spy
@InjectMocks
QuotaServiceImpl quotaServiceImplSpy;
QuotaServiceImpl quotaService = new QuotaServiceImpl();
@Before
public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException {
@ -71,34 +79,34 @@ public class QuotaServiceImplTest extends TestCase {
Field accountDaoField = QuotaServiceImpl.class.getDeclaredField("_accountDao");
accountDaoField.setAccessible(true);
accountDaoField.set(quotaService, accountDao);
accountDaoField.set(quotaServiceImplSpy, accountDaoMock);
Field quotaAccountDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaAcc");
quotaAccountDaoField.setAccessible(true);
quotaAccountDaoField.set(quotaService, quotaAcc);
quotaAccountDaoField.set(quotaServiceImplSpy, quotaAcc);
Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao");
quotaUsageDaoField.setAccessible(true);
quotaUsageDaoField.set(quotaService, quotaUsageDao);
quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageDao);
Field domainDaoField = QuotaServiceImpl.class.getDeclaredField("_domainDao");
domainDaoField.setAccessible(true);
domainDaoField.set(quotaService, domainDao);
domainDaoField.set(quotaServiceImplSpy, domainDao);
Field configDaoField = QuotaServiceImpl.class.getDeclaredField("_configDao");
configDaoField.setAccessible(true);
configDaoField.set(quotaService, configDao);
configDaoField.set(quotaServiceImplSpy, configDao);
Field balanceDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaBalanceDao");
balanceDaoField.setAccessible(true);
balanceDaoField.set(quotaService, quotaBalanceDao);
balanceDaoField.set(quotaServiceImplSpy, quotaBalanceDao);
Field QuotaResponseBuilderField = QuotaServiceImpl.class.getDeclaredField("_respBldr");
QuotaResponseBuilderField.setAccessible(true);
QuotaResponseBuilderField.set(quotaService, respBldr);
QuotaResponseBuilderField.set(quotaServiceImplSpy, respBldr);
Mockito.when(configDao.getValue(Mockito.eq(Config.UsageAggregationTimezone.toString()))).thenReturn("IST");
quotaService.configure("randomName", null);
quotaServiceImplSpy.configure("randomName", null);
}
@Test
@ -120,9 +128,9 @@ public class QuotaServiceImplTest extends TestCase {
Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records);
// with enddate
assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb));
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb));
// without enddate
assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb));
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb));
}
@Test
@ -133,7 +141,7 @@ public class QuotaServiceImplTest extends TestCase {
final Date startDate = new DateTime().minusDays(2).toDate();
final Date endDate = new Date();
quotaService.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
Mockito.verify(quotaUsageDao, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(Date.class), Mockito.any(Date.class));
}
@ -142,13 +150,13 @@ public class QuotaServiceImplTest extends TestCase {
// existing account
QuotaAccountVO quotaAccountVO = new QuotaAccountVO();
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO);
quotaService.setLockAccount(2L, true);
quotaServiceImplSpy.setLockAccount(2L, true);
Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class));
// new account
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null);
quotaService.setLockAccount(2L, true);
quotaServiceImplSpy.setLockAccount(2L, true);
Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
}
@ -160,13 +168,14 @@ public class QuotaServiceImplTest extends TestCase {
// existing account setting
QuotaAccountVO quotaAccountVO = new QuotaAccountVO();
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO);
quotaService.setMinBalance(accountId, balance);
quotaServiceImplSpy.setMinBalance(accountId, balance);
Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class));
// no account with limit set
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null);
quotaService.setMinBalance(accountId, balance);
quotaServiceImplSpy.setMinBalance(accountId, balance);
Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
}
}

View File

@ -485,6 +485,12 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
return null;
}
@Override
public Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException {
// TODO Auto-generated method stub

View File

@ -314,20 +314,7 @@ public class ParamProcessWorker implements DispatchWorker {
protected void doAccessChecks(BaseCmd cmd, Map<Object, AccessType> entitiesToAccess) {
Account caller = CallContext.current().getCallingAccount();
List<Long> entityOwners = cmd.getEntityOwnerIds();
Account[] owners = null;
if (entityOwners != null) {
owners = entityOwners.stream().map(id -> _accountMgr.getAccount(id)).toArray(Account[]::new);
} else {
if (cmd.getEntityOwnerId() == Account.ACCOUNT_ID_SYSTEM && cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd)cmd).getApiResourceType() == ApiCommandResourceType.Network) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping access check on the network owner if the owner is ROOT/system.");
}
owners = new Account[]{};
} else {
owners = new Account[]{_accountMgr.getAccount(cmd.getEntityOwnerId())};
}
}
Account[] owners = getEntityOwners(cmd);
if (cmd instanceof BaseAsyncCreateCmd) {
// check that caller can access the owner account.

View File

@ -73,7 +73,9 @@ import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
@ -3886,6 +3888,48 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return null;
}
@Override
public Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId) {
if (projectId != null) {
if (ObjectUtils.anyNotNull(accountId, accountName)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Project and account can not be specified together.");
}
return getActiveProjectAccountByProjectId(projectId);
}
if (accountId != null) {
if (getActiveAccountById(accountId) != null) {
return accountId;
}
throw new InvalidParameterValueException(String.format("Unable to find account with ID [%s].", accountId));
}
if (accountName == null && domainId == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Either %s or %s must be informed.", ApiConstants.ACCOUNT_ID, ApiConstants.PROJECT_ID));
}
try {
Account activeAccount = getActiveAccountByName(accountName, domainId);
if (activeAccount != null) {
return activeAccount.getId();
}
} catch (InvalidParameterValueException exception) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Both %s and %s are needed if using either. Consider using %s instead.",
ApiConstants.ACCOUNT, ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT_ID));
}
throw new InvalidParameterValueException(String.format("Unable to find account by name [%s] on domain [%s].", accountName, domainId));
}
protected long getActiveProjectAccountByProjectId(long projectId) {
Project project = _projectMgr.getProject(projectId);
if (project == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Unable to find project with ID [%s].", projectId));
}
if (project.getState() != Project.State.Active) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Project with ID [%s] is not active.", projectId));
}
return project.getProjectAccountId();
}
@Override
public UserAccount getUserAccountById(Long userId) {
UserAccount userAccount = userAccountDao.findById(userId);