This is an automated email from the ASF dual-hosted git repository.

fabricio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 4f93ba888c3 Refactor Quota Summary API (#10505)
4f93ba888c3 is described below

commit 4f93ba888c36d992c09af8d2486ea4ec75102f79
Author: julien-vaz <[email protected]>
AuthorDate: Tue Mar 31 20:29:30 2026 -0300

    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 <[email protected]>
    
    * 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 <[email protected]>
    
    * 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 <[email protected]>
    Co-authored-by: Fabricio Duarte <[email protected]>
    Co-authored-by: dahn <[email protected]>
---
 .../main/java/com/cloud/user/AccountService.java   |   2 +
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../java/com/cloud/domain/dao/DomainDaoImpl.java   |   2 +-
 .../db/views/cloud_usage.quota_summary_view.sql    |  48 ++++++
 .../cloudstack/quota/QuotaAccountStateFilter.java  |  35 +++++
 .../cloudstack/quota/dao/QuotaSummaryDao.java      |  32 ++++
 .../cloudstack/quota/dao/QuotaSummaryDaoImpl.java  |  80 ++++++++++
 .../apache/cloudstack/quota/vo/QuotaSummaryVO.java | 154 ++++++++++++++++++
 .../quota/spring-framework-quota-context.xml       |   3 +-
 .../cloudstack/api/command/QuotaSummaryCmd.java    |  88 +++++++----
 .../api/response/QuotaResponseBuilder.java         |   7 +-
 .../api/response/QuotaResponseBuilderImpl.java     | 175 +++++++++++++--------
 .../api/response/QuotaSummaryResponse.java         | 105 +++++++------
 .../apache/cloudstack/quota/QuotaServiceImpl.java  |   6 +
 .../api/response/QuotaResponseBuilderImplTest.java | 116 ++++++++------
 .../cloudstack/quota/QuotaServiceImplTest.java     |  43 +++--
 .../contrail/management/MockAccountManager.java    |   6 +
 .../com/cloud/api/dispatch/ParamProcessWorker.java |  15 +-
 .../java/com/cloud/user/AccountManagerImpl.java    |  44 ++++++
 19 files changed, 739 insertions(+), 223 deletions(-)

diff --git a/api/src/main/java/com/cloud/user/AccountService.java 
b/api/src/main/java/com/cloud/user/AccountService.java
index 30919e5b782..4145e2b89eb 100644
--- a/api/src/main/java/com/cloud/user/AccountService.java
+++ b/api/src/main/java/com/cloud/user/AccountService.java
@@ -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
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 3d827641358..05c6098bc72 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -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";
diff --git 
a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
index 56d971bbe01..1afa0d22dcc 100644
--- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java
@@ -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);
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql
 
b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql
new file mode 100644
index 00000000000..0646c3b6f91
--- /dev/null
+++ 
b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql
@@ -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);
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java
new file mode 100644
index 00000000000..cc6ca203ef7
--- /dev/null
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java
@@ -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;
+    }
+}
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java
new file mode 100644
index 00000000000..d8ee2607501
--- /dev/null
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java
@@ -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);
+}
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.java
new file mode 100644
index 00000000000..d90d5a75859
--- /dev/null
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.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 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;
+    }
+
+}
diff --git 
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java
 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java
new file mode 100644
index 00000000000..f9796497d57
--- /dev/null
+++ 
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java
@@ -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;
+    }
+}
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 453355c8522..304b23b7220 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
@@ -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"
diff --git 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java
 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java
index 87322b01f4d..870b9b6df6e 100644
--- 
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java
+++ 
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java
@@ -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")
+    @Inject
+    QuotaResponseBuilder quotaResponseBuilder;
+
+    @Inject
+    QuotaService quotaService;
+
+    @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;
 
-    @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.")
+    @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, 
required = false, description = "Optional, to list all Accounts irrespective of 
the quota activity")
+    @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;
 
-    @Inject
-    QuotaResponseBuilder _responseBuilder;
+    @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;
 
-    public QuotaSummaryCmd() {
-        super();
-    }
+    @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;
     }
 
+    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() {
-        return Account.ACCOUNT_ID_SYSTEM;
+        if (ObjectUtils.allNull(accountId, accountName, projectId)) {
+            return -1;
+        }
+        return _accountService.finalizeAccountId(accountId, accountName, 
domainId, projectId);
     }
-
 }
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 67f75ebf82f..177fb00d4b5 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
@@ -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);
 
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 f9456587058..d1992d4c3ad 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
@@ -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);
+    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));
+            }
+        }
+
+        String domainPath = getDomainPathByDomainIdForDomainAdmin(caller);
+
+        Long accountId = cmd.getEntityOwnerId();
+        if (accountId == -1) {
+            accountId = cmd.isListAll() ? null : caller.getAccountId();
+        }
+
+        return getQuotaSummaryResponse(accountId, domainId, domainPath, cmd);
     }
 
-    @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);
-            }
+    /**
+     * 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;
         }
-        return new Pair<>(result, count);
+
+        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();
     }
 
-    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();
+    /**
+     * 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) {
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 f8f27b4813d..d76202bfd88 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
@@ -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);
+        this.balance = balance;
     }
 
-    public Date getStartDate() {
-        return startDate == null ?  null : new Date(startDate.getTime());
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
     }
 
-    public void setStartDate(Date startDate) {
-        this.startDate =  startDate == null ?  null : new 
Date(startDate.getTime());
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
     }
 
-    public Date getEndDate() {
-        return  endDate == null ?  null : new Date(endDate.getTime());
+    public void setCurrency(String currency) {
+        this.currency = currency;
     }
 
-    public void setEndDate(Date endDate) {
-        this.endDate = endDate == null ?  null : new Date(endDate.getTime());
+    public void setQuotaEnabled(boolean quotaEnabled) {
+        this.quotaEnabled = quotaEnabled;
     }
 
-    public String getCurrency() {
-        return currency;
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
     }
 
-    public void setCurrency(String currency) {
-        this.currency = currency;
+    public void setProjectId(String projectId) {
+        this.projectId = projectId;
     }
 
-    public boolean getQuotaEnabled() {
-        return quotaEnabled;
+    public void setProjectRemoved(Boolean projectRemoved) {
+        this.projectRemoved = projectRemoved;
     }
 
-    public void setQuotaEnabled(boolean quotaEnabled) {
-        this.quotaEnabled = quotaEnabled;
+    public void setDomainRemoved(boolean domainRemoved) {
+        this.domainRemoved = domainRemoved;
+    }
+
+    public void setAccountRemoved(boolean accountRemoved) {
+        this.accountRemoved = accountRemoved;
     }
 }
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 f455c3cba14..f39153ae0ac 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
@@ -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;
 
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 3629bf2e3fe..ea88a106b84 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
@@ -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();
diff --git 
a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
 
b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
index 19e756d1d97..259264f3b0e 100644
--- 
a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
+++ 
b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java
@@ -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));
     }
+
 }
diff --git 
a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
 
b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
index 7953a6a27cd..d9f4963165e 100644
--- 
a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
+++ 
b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java
@@ -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
diff --git 
a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java 
b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
index 5a634cc0ca8..0205bfa9d8f 100644
--- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
+++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
@@ -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.
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java 
b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index d9af7af6c33..2011d455646 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -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);

Reply via email to