This is an automated email from the ASF dual-hosted git repository.
winterhazel 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 a73cc9a22c0 Improve Quota Statement (#10506)
a73cc9a22c0 is described below
commit a73cc9a22c09954c1631c36d2a11064764cc8355
Author: julien-vaz <[email protected]>
AuthorDate: Wed Apr 29 21:09:13 2026 -0300
Improve Quota Statement (#10506)
* Improve Quota Statement
* Removes unused import
* Fix QuotaUsageJoinDao, QuotaResponseBuilderImpl, QuotaServiceImpl e
QuotaServiceImplTest
* Reorganize imports
* Updates QuotaStatementCmd responseBuilder scope to default
* Fix log4j syntax
* Address reviews + other improvements
* Add missing SQL scripts and injections
* Change accountid and domainid logic + add unit tests
* Rename QuotaUsageDetail to QuotaTariffUsage
* Fix out of bounds exception
---------
Co-authored-by: Julien Hervot de Mattos Vaz <[email protected]>
Co-authored-by: Fabricio Duarte <[email protected]>
---
.../org/apache/cloudstack/api/ApiConstants.java | 6 +
.../resources/META-INF/db/schema-42210to42300.sql | 10 +
.../db/views/cloud_usage.quota_usage_view.sql | 35 +++
.../java/com/cloud/utils/db/SearchCriteria.java | 6 +
.../cloudstack/quota/dao/QuotaTariffUsageDao.java | 29 ++
.../quota/dao/QuotaTariffUsageDaoImpl.java | 56 ++++
.../cloudstack/quota/dao/QuotaUsageJoinDao.java | 31 ++
.../quota/dao/QuotaUsageJoinDaoImpl.java | 94 ++++++
.../cloudstack/quota/vo/QuotaTariffUsageVO.java | 86 ++++++
.../cloudstack/quota/vo/QuotaUsageJoinVO.java | 179 ++++++++++++
.../cloudstack/quota/vo/QuotaUsageResourceVO.java | 62 ++++
.../quota/spring-framework-quota-context.xml | 4 +-
.../cloudstack/api/command/QuotaStatementCmd.java | 74 +++--
.../api/response/QuotaResponseBuilder.java | 5 +-
.../api/response/QuotaResponseBuilderImpl.java | 322 +++++++++++++++------
.../QuotaStatementItemResourceResponse.java | 61 ++++
.../api/response/QuotaStatementItemResponse.java | 69 ++---
.../api/response/QuotaStatementResponse.java | 65 ++---
.../org/apache/cloudstack/quota/QuotaService.java | 4 +-
.../apache/cloudstack/quota/QuotaServiceImpl.java | 30 +-
.../api/command/QuotaStatementCmdTest.java | 23 +-
.../api/response/QuotaResponseBuilderImplTest.java | 199 +++++++++++++
.../cloudstack/quota/QuotaServiceImplTest.java | 10 +-
23 files changed, 1212 insertions(+), 248 deletions(-)
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 7eae16a2a37..6fb7cb0612c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -157,6 +157,7 @@ public class ApiConstants {
public static final String CUSTOM_ID = "customid";
public static final String CUSTOM_ACTION_ID = "customactionid";
public static final String CUSTOM_JOB_ID = "customjobid";
+ public static final String CURRENCY = "currency";
public static final String CURRENT_START_IP = "currentstartip";
public static final String CURRENT_END_IP = "currentendip";
public static final String ENCRYPT = "encrypt";
@@ -541,6 +542,7 @@ public class ApiConstants {
public static final String SESSIONKEY = "sessionkey";
public static final String SHOW_CAPACITIES = "showcapacities";
public static final String SHOW_REMOVED = "showremoved";
+ public static final String SHOW_RESOURCES = "showresources";
public static final String SHOW_RESOURCE_ICON = "showicon";
public static final String SHOW_INACTIVE = "showinactive";
public static final String SHOW_UNIQUE = "showunique";
@@ -606,9 +608,11 @@ public class ApiConstants {
public static final String TENANT_NAME = "tenantname";
public static final String TOTAL = "total";
public static final String TOTAL_SUBNETS = "totalsubnets";
+ public static final String TOTAL_QUOTA = "totalquota";
public static final String TYPE = "type";
public static final String TRUST_STORE = "truststore";
public static final String TRUST_STORE_PASSWORD = "truststorepass";
+ public static final String UNIT = "unit";
public static final String URL = "url";
public static final String USAGE_INTERFACE = "usageinterface";
public static final String USED = "used";
@@ -1300,6 +1304,8 @@ public class ApiConstants {
public static final String OBJECT_LOCKING = "objectlocking";
public static final String ENCRYPTION = "encryption";
public static final String QUOTA = "quota";
+ public static final String QUOTA_CONSUMED = "quotaconsumed";
+ public static final String QUOTA_USAGE = "quotausage";
public static final String ACCESS_KEY = "accesskey";
public static final String SOURCE_NAT_IP = "sourcenatipaddress";
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
index 4cb9eb7cb2c..c0feb06e76a 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
@@ -117,3 +117,13 @@ CALL
`cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tin
--- Disable/enable NICs
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT
NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
+
+--- Quota tariff/usage mapping
+CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `tariff_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the tariff of the
Quota usage detail calculated, foreign key to quota_tariff table',
+ `quota_usage_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the
aggregation of Quota usage details, foreign key to quota_usage table',
+ `quota_used` decimal(20,8) NOT NULL COMMENT 'Amount of quota used',
+ PRIMARY KEY (`id`),
+ CONSTRAINT `fk_quota_tariff_usage__tariff_id` FOREIGN KEY (`tariff_id`)
REFERENCES `cloud_usage`.`quota_tariff` (`id`),
+ CONSTRAINT `fk_quota_tariff_usage__quota_usage_id` FOREIGN KEY
(`quota_usage_id`) REFERENCES `cloud_usage`.`quota_usage` (`id`));
diff --git
a/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql
b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql
new file mode 100644
index 00000000000..7ac001384e8
--- /dev/null
+++
b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql
@@ -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.
+
+-- VIEW `cloud_usage`.`quota_usage_view`;
+
+DROP VIEW IF EXISTS `cloud_usage`.`quota_usage_view`;
+CREATE VIEW `cloud_usage`.`quota_usage_view` AS
+SELECT qu.id,
+ qu.usage_item_id,
+ qu.zone_id,
+ qu.account_id,
+ qu.domain_id,
+ qu.usage_type,
+ qu.quota_used,
+ qu.start_date,
+ qu.end_date,
+ cu.usage_id AS resource_id,
+ cu.network_id as network_id,
+ cu.offering_id as offering_id
+FROM `cloud_usage`.`quota_usage` qu
+INNER JOIN `cloud_usage`.`cloud_usage` cu ON (cu.id = qu.usage_item_id);
diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
index 15807eb26d4..3323d5c4d82 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java
@@ -205,6 +205,12 @@ public class SearchCriteria<K> {
}
+ public void setJoinParametersIfNotNull(String joinName, String
conditionName, Object... params) {
+ if (ArrayUtils.isNotEmpty(params) && (params.length > 1 || params[0]
!= null)) {
+ setJoinParameters(joinName, conditionName, params);
+ }
+ }
+
public SearchCriteria<?> getJoin(String joinName) {
return _joins.get(joinName).getT();
}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDao.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDao.java
new file mode 100644
index 00000000000..9684ca117b5
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDao.java
@@ -0,0 +1,29 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements. See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership. The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License. You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied. See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.quota.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
+import java.util.List;
+
+public interface QuotaTariffUsageDao extends GenericDao<QuotaTariffUsageVO,
Long> {
+
+ void persistQuotaTariffUsage(QuotaTariffUsageVO quotaTariffUsage);
+
+ List<QuotaTariffUsageVO> listQuotaTariffUsages(Long quotaUsageId);
+
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java
new file mode 100644
index 00000000000..556f552fed6
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java
@@ -0,0 +1,56 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements. See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership. The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License. You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied. See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.quota.dao;
+
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
+import org.springframework.stereotype.Component;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.utils.db.TransactionLegacy;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+
+@Component
+public class QuotaTariffUsageDaoImpl extends
GenericDaoBase<QuotaTariffUsageVO, Long> implements QuotaTariffUsageDao {
+ private SearchBuilder<QuotaTariffUsageVO> searchQuotaTariffUsages;
+
+ @PostConstruct
+ public void init() {
+ searchQuotaTariffUsages = createSearchBuilder();
+ searchQuotaTariffUsages.and("quotaUsageId",
searchQuotaTariffUsages.entity().getQuotaUsageId(), SearchCriteria.Op.EQ);
+ searchQuotaTariffUsages.done();
+ }
+
+ @Override
+ public void persistQuotaTariffUsage(final QuotaTariffUsageVO
quotaTariffUsage) {
+ logger.trace("Persisting quota tariff usage [{}].", quotaTariffUsage);
+ Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<QuotaTariffUsageVO>) status -> persist(quotaTariffUsage));
+ }
+
+ @Override
+ public List<QuotaTariffUsageVO> listQuotaTariffUsages(Long quotaUsageId) {
+ SearchCriteria<QuotaTariffUsageVO> sc =
searchQuotaTariffUsages.create();
+ sc.setParameters("quotaUsageId", quotaUsageId);
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<List<QuotaTariffUsageVO>>) status -> listBy(sc));
+ }
+
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDao.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDao.java
new file mode 100644
index 00000000000..126fa11413f
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDao.java
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.quota.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
+
+import java.util.Date;
+import java.util.List;
+
+public interface QuotaUsageJoinDao extends GenericDao<QuotaUsageJoinVO, Long> {
+
+ List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, Long domainId,
Integer usageType, Long resourceId, Long networkId, Long offeringId, Date
startDate, Date endDate, Long tariffId);
+
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java
new file mode 100644
index 00000000000..b98ea2b3a5d
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java
@@ -0,0 +1,94 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package org.apache.cloudstack.quota.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import com.cloud.utils.db.JoinBuilder;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.utils.db.TransactionLegacy;
+import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
+import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class QuotaUsageJoinDaoImpl extends GenericDaoBase<QuotaUsageJoinVO,
Long> implements QuotaUsageJoinDao {
+
+ private SearchBuilder<QuotaUsageJoinVO> searchQuotaUsages;
+
+ private SearchBuilder<QuotaUsageJoinVO> searchQuotaUsagesJoinTariffUsages;
+
+ @Inject
+ private QuotaTariffUsageDao quotaTariffUsageDao;
+
+ @PostConstruct
+ public void init() {
+ searchQuotaUsages = createSearchBuilder();
+ prepareQuotaUsageSearchBuilder(searchQuotaUsages);
+ searchQuotaUsages.done();
+
+ SearchBuilder<QuotaTariffUsageVO> searchQuotaTariffUsages =
quotaTariffUsageDao.createSearchBuilder();
+ searchQuotaTariffUsages.and("tariffId",
searchQuotaTariffUsages.entity().getTariffId(), SearchCriteria.Op.EQ);
+ searchQuotaUsagesJoinTariffUsages = createSearchBuilder();
+ prepareQuotaUsageSearchBuilder(searchQuotaUsagesJoinTariffUsages);
+ searchQuotaUsagesJoinTariffUsages.join("searchQuotaTariffUsages",
searchQuotaTariffUsages, searchQuotaUsagesJoinTariffUsages.entity().getId(),
+ searchQuotaTariffUsages.entity().getQuotaUsageId(),
JoinBuilder.JoinType.INNER);
+ searchQuotaUsagesJoinTariffUsages.done();
+ }
+
+ private void
prepareQuotaUsageSearchBuilder(SearchBuilder<QuotaUsageJoinVO> searchBuilder) {
+ searchBuilder.and("accountId", searchBuilder.entity().getAccountId(),
SearchCriteria.Op.EQ);
+ searchBuilder.and("domainId", searchBuilder.entity().getDomainId(),
SearchCriteria.Op.EQ);
+ searchBuilder.and("usageType", searchBuilder.entity().getUsageType(),
SearchCriteria.Op.EQ);
+ searchBuilder.and("resourceId",
searchBuilder.entity().getResourceId(), SearchCriteria.Op.EQ);
+ searchBuilder.and("networkId", searchBuilder.entity().getNetworkId(),
SearchCriteria.Op.EQ);
+ searchBuilder.and("offeringId",
searchBuilder.entity().getOfferingId(), SearchCriteria.Op.EQ);
+ searchBuilder.and("startDate", searchBuilder.entity().getStartDate(),
SearchCriteria.Op.BETWEEN);
+ searchBuilder.and("endDate", searchBuilder.entity().getEndDate(),
SearchCriteria.Op.BETWEEN);
+ }
+
+ @Override
+ public List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, Long
domainId, Integer usageType, Long resourceId, Long networkId, Long offeringId,
Date startDate, Date endDate, Long tariffId) {
+ SearchCriteria<QuotaUsageJoinVO> sc = tariffId == null ?
searchQuotaUsages.create() : searchQuotaUsagesJoinTariffUsages.create();
+
+ sc.setParametersIfNotNull("accountId", accountId);
+ sc.setParametersIfNotNull("domainId", domainId);
+ sc.setParametersIfNotNull("usageType", usageType);
+ sc.setParametersIfNotNull("resourceId", resourceId);
+ sc.setParametersIfNotNull("networkId", networkId);
+ sc.setParametersIfNotNull("offeringId", offeringId);
+
+ if (ObjectUtils.allNotNull(startDate, endDate)) {
+ sc.setParameters("startDate", startDate, endDate);
+ sc.setParameters("endDate", startDate, endDate);
+ }
+
+ sc.setJoinParametersIfNotNull("searchQuotaTariffUsages", "tariffId",
tariffId);
+
+ return Transaction.execute(TransactionLegacy.USAGE_DB,
(TransactionCallback<List<QuotaUsageJoinVO>>) status -> listBy(sc));
+ }
+
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java
new file mode 100644
index 00000000000..4fa9e771713
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java
@@ -0,0 +1,86 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements. See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership. The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License. You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied. See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.quota.vo;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+@Entity
+@Table(name = "quota_tariff_usage")
+public class QuotaTariffUsageVO implements InternalIdentity {
+ @Id
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "tariff_id")
+ private Long tariffId;
+
+ @Column(name = "quota_usage_id")
+ private Long quotaUsageId;
+
+ @Column(name = "quota_used")
+ private BigDecimal quotaUsed;
+
+ public QuotaTariffUsageVO() {
+ quotaUsed = new BigDecimal(0);
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public Long getTariffId() {
+ return tariffId;
+ }
+
+ public Long getQuotaUsageId() {
+ return quotaUsageId;
+ }
+
+ public BigDecimal getQuotaUsed() {
+ return quotaUsed;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public void setTariffId(Long tariffId) {
+ this.tariffId = tariffId;
+ }
+
+ public void setQuotaUsageId(Long quotaUsageId) {
+ this.quotaUsageId = quotaUsageId;
+ }
+
+ public void setQuotaUsed(BigDecimal quotaUsed) {
+ this.quotaUsed = quotaUsed;
+ }
+
+ @Override
+ public String toString() {
+ return new ReflectionToStringBuilder(this,
ToStringStyle.JSON_STYLE).toString();
+ }
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java
new file mode 100644
index 00000000000..df9577e23c3
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java
@@ -0,0 +1,179 @@
+//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 org.apache.cloudstack.api.InternalIdentity;
+import
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Entity
+@Table(name = "quota_usage_view")
+public class QuotaUsageJoinVO implements InternalIdentity {
+
+ @Id
+ @Column(name = "id", updatable = false, nullable = false)
+ private Long id;
+
+ @Column(name = "zone_id")
+ private Long zoneId = null;
+
+ @Column(name = "account_id")
+ private Long accountId = null;
+
+ @Column(name = "domain_id")
+ private Long domainId = null;
+
+ @Column(name = "usage_item_id")
+ private Long usageItemId;
+
+ @Column(name = "usage_type")
+ private int usageType;
+
+ @Column(name = "quota_used")
+ private BigDecimal quotaUsed;
+
+ @Column(name = "start_date")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date startDate = null;
+
+ @Column(name = "end_date")
+ @Temporal(value = TemporalType.TIMESTAMP)
+ private Date endDate = null;
+
+ @Column(name = "resource_id")
+ private Long resourceId = null;
+
+ @Column(name = "network_id")
+ private Long networkId = null;
+
+ @Column(name = "offering_id")
+ private Long offeringId = null;
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(Long zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public void setDomainId(Long domainId) {
+ this.domainId = domainId;
+ }
+
+ public Long getUsageItemId() {
+ return usageItemId;
+ }
+
+ public void setUsageItemId(Long usageItemId) {
+ this.usageItemId = usageItemId;
+ }
+
+ public int getUsageType() {
+ return usageType;
+ }
+
+ public void setUsageType(int usageType) {
+ this.usageType = usageType;
+ }
+
+ public BigDecimal getQuotaUsed() {
+ return quotaUsed;
+ }
+
+ public void setQuotaUsed(BigDecimal quotaUsed) {
+ this.quotaUsed = quotaUsed;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public Date getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(Date endDate) {
+ this.endDate = endDate;
+ }
+
+ public Long getResourceId() {
+ return resourceId;
+ }
+
+ public void setResourceId(Long resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ public Long getNetworkId() {
+ return networkId;
+ }
+
+ public void setNetworkId(Long networkId) {
+ this.networkId = networkId;
+ }
+
+ public Long getOfferingId() {
+ return offeringId;
+ }
+
+ public void setOfferingId(Long offeringId) {
+ this.offeringId = offeringId;
+ }
+
+ public QuotaUsageJoinVO () {
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this,
"id", "zoneId", "accountId", "domainId", "usageItemId", "usageType",
"quotaUsed", "startDate",
+ "endDate", "resourceId");
+ }
+}
diff --git
a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageResourceVO.java
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageResourceVO.java
new file mode 100644
index 00000000000..92482423100
--- /dev/null
+++
b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageResourceVO.java
@@ -0,0 +1,62 @@
+//
+// 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.util.Date;
+
+public class QuotaUsageResourceVO {
+ private String uuid;
+ private String name;
+ private Date removed;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Date getRemoved() {
+ return removed;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ public boolean isRemoved() {
+ return this.removed != null;
+ }
+
+ public QuotaUsageResourceVO(String uuid, String name, Date removed) {
+ this.uuid = uuid;
+ this.name = name;
+ this.removed = removed;
+ }
+}
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 304b23b7220..5ca2679c388 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
@@ -26,7 +26,9 @@
<bean id="QuotaEmailTemplatesDao"
class="org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDaoImpl" />
<bean id="QuotaUsageDao"
class="org.apache.cloudstack.quota.dao.QuotaUsageDaoImpl" />
- <bean id="UserVmDetailsDao"
class="org.apache.cloudstack.quota.dao.VMInstanceDetailsDaoImpl" />
+ <bean id="QuotaUsageJoinDao"
class="org.apache.cloudstack.quota.dao.QuotaUsageJoinDaoImpl"/>
+ <bean id="QuotaTariffUsageDao"
class="org.apache.cloudstack.quota.dao.QuotaTariffUsageDaoImpl" />
+ <bean id="UserVmDetailsDao"
class="org.apache.cloudstack.quota.dao.VMInstanceDetailsDaoImpl" />
<bean id="QuotaManager"
class="org.apache.cloudstack.quota.QuotaManagerImpl" />
<bean id="QuotaAlertManager"
class="org.apache.cloudstack.quota.QuotaAlertManagerImpl" />
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java
index d3bd3868ed1..bfe26a9f425 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java
@@ -17,7 +17,6 @@
package org.apache.cloudstack.api.command;
import java.util.Date;
-import java.util.List;
import javax.inject.Inject;
@@ -28,24 +27,25 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
import org.apache.cloudstack.api.response.QuotaStatementResponse;
-import org.apache.cloudstack.quota.vo.QuotaUsageVO;
-import com.cloud.user.Account;
+import org.apache.commons.lang3.ObjectUtils;
-@APICommand(name = "quotaStatement", responseObject =
QuotaStatementItemResponse.class, description = "Create a quota statement",
since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo =
false,
- httpMethod = "GET")
+@APICommand(name = "quotaStatement", responseObject =
QuotaStatementItemResponse.class, description = "Create a Quota statement for
the provided Account, Project, or Domain.",
+ since = "4.7.0", requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false, httpMethod = "GET")
public class QuotaStatementCmd extends BaseCmd {
-
-
- @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
required = true, description = "Optional, Account Id for which statement needs
to be generated")
+ @ACL
+ @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
+ description = "Name of the Account for which the Quota statement
will be generated. Deprecated, please use accountid instead.")
private String accountName;
@ACL
- @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID,
required = true, entityType = DomainResponse.class, description = "Optional, If
domain Id is given and the caller is domain admin then the statement is
generated for domain.")
+ @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID,
entityType = DomainResponse.class,
+ description = "ID of the Domain for which the Quota statement will
be generated. May be used individually or with account.")
private Long domainId;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required
= true, description = "End of the period of the Quota statement. " +
@@ -56,15 +56,25 @@ public class QuotaStatementCmd extends BaseCmd {
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
- @Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER,
description = "List quota usage records for the specified usage type")
+ @Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER,
+ description = "Consider only Quota usage records for the specified
usage type in the statement.")
private Integer usageType;
@ACL
- @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID,
entityType = AccountResponse.class, description = "List usage records for the
specified Account")
+ @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID,
entityType = AccountResponse.class,
+ description = "ID of the Account for which the Quota statement
will be generated. Can not be specified with projectid.")
private Long accountId;
+ @ACL
+ @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID,
entityType = ProjectResponse.class,
+ description = "ID of the Project for which the Quota statement
will be generated. Can not be specified with accountid.", since = "4.23.0")
+ private Long projectId;
+
+ @Parameter(name = ApiConstants.SHOW_RESOURCES, type = CommandType.BOOLEAN,
description = "List the resources of each Quota type in the period.", since =
"4.23.0")
+ private boolean showResources;
+
@Inject
- private QuotaResponseBuilder _responseBuilder;
+ QuotaResponseBuilder responseBuilder;
public Long getAccountId() {
return accountId;
@@ -99,43 +109,47 @@ public class QuotaStatementCmd extends BaseCmd {
}
public Date getEndDate() {
- return _responseBuilder.startOfNextDay(endDate == null ? new Date() :
new Date(endDate.getTime()));
+ return endDate;
}
public void setEndDate(Date endDate) {
- this.endDate = endDate == null ? null : new Date(endDate.getTime());
+ this.endDate = endDate;
}
public Date getStartDate() {
- return startDate == null ? null : new Date(startDate.getTime());
+ return startDate;
}
public void setStartDate(Date startDate) {
- this.startDate = startDate == null ? null : new
Date(startDate.getTime());
+ this.startDate = startDate;
+ }
+
+ public boolean isShowResources() {
+ return showResources;
+ }
+
+ public void setShowResources(boolean showResources) {
+ this.showResources = showResources;
+ }
+
+ public Long getProjectId() {
+ return projectId;
}
@Override
public long getEntityOwnerId() {
- if (accountId != null) {
- return accountId;
+ if (ObjectUtils.allNull(accountId, accountName, projectId)) {
+ return -1;
}
- Account activeAccountByName =
_accountService.getActiveAccountByName(accountName, domainId);
- if (activeAccountByName != null) {
- return activeAccountByName.getAccountId();
- }
- return Account.ACCOUNT_ID_SYSTEM;
+ return _accountService.finalizeAccountId(accountId, accountName,
domainId, projectId);
}
@Override
public void execute() {
- List<QuotaUsageVO> quotaUsage = _responseBuilder.getQuotaUsage(this);
-
- QuotaStatementResponse response =
_responseBuilder.createQuotaStatementResponse(quotaUsage);
- response.setStartDate(startDate == null ? null : new
Date(startDate.getTime()));
- response.setEndDate(endDate == null ? null : new
Date(endDate.getTime()));
-
+ QuotaStatementResponse response =
responseBuilder.createQuotaStatementResponse(this);
+ response.setStartDate(startDate);
+ response.setEndDate(endDate);
response.setResponseName(getCommandName());
setResponseObject(response);
}
-
}
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 177fb00d4b5..bde905c487b 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
@@ -32,7 +32,6 @@ import
org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
-import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import java.util.Date;
import java.util.List;
@@ -49,7 +48,7 @@ public interface QuotaResponseBuilder {
boolean isUserAllowedToSeeActivationRules(User user);
- QuotaStatementResponse createQuotaStatementResponse(List<QuotaUsageVO>
quotaUsage);
+ QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd);
QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO>
quotaUsage, Date startDate, Date endDate);
@@ -57,8 +56,6 @@ public interface QuotaResponseBuilder {
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO>
quotaBalance, Date startDate);
- List<QuotaUsageVO> getQuotaUsage(QuotaStatementCmd cmd);
-
List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd);
QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double
amount, Long updatedBy, Boolean enforce);
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 2d6ec3255f4..173c0723731 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
@@ -21,13 +21,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
-import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@@ -36,6 +34,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -43,12 +42,36 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.domain.Domain;
+import com.cloud.domain.DomainVO;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
-import com.cloud.projects.dao.ProjectDao;
+import com.cloud.network.dao.IPAddressDao;
+import com.cloud.network.dao.IPAddressVO;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.offerings.dao.NetworkOfferingDao;
+import com.cloud.offerings.NetworkOfferingVO;
+import com.cloud.storage.dao.VMTemplateDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.user.dao.UserDao;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
+import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
@@ -94,7 +117,8 @@ 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.quota.vo.QuotaUsageJoinVO;
+import org.apache.cloudstack.quota.vo.QuotaUsageResourceVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections4.CollectionUtils;
@@ -106,18 +130,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
-import com.cloud.domain.DomainVO;
-import com.cloud.domain.dao.DomainDao;
-import com.cloud.event.ActionEvent;
-import com.cloud.event.EventTypes;
-import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.user.Account;
-import com.cloud.user.AccountManager;
-import com.cloud.user.AccountVO;
-import com.cloud.user.dao.AccountDao;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.Pair;
-
@Component
public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
protected Logger logger = LogManager.getLogger(getClass());
@@ -140,8 +152,6 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
@Inject
private AccountDao _accountDao;
@Inject
- private ProjectDao projectDao;
- @Inject
private QuotaAccountDao quotaAccountDao;
@Inject
private DomainDao domainDao;
@@ -159,6 +169,21 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
private JsInterpreterHelper jsInterpreterHelper;
@Inject
private ApiDiscoveryService apiDiscoveryService;
+ @Inject
+ private IPAddressDao ipAddressDao;
+ @Inject
+ private NetworkDao networkDao;
+ @Inject
+ private NetworkOfferingDao networkOfferingDao;
+ @Inject
+ private SnapshotDao snapshotDao;
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+ @Inject
+ private VMTemplateDao vmTemplateDao;
+ @Inject
+ private VolumeDao volumeDao;
+
private final Class<?>[] assignableClasses = {GenericPresetVariable.class,
ComputingResources.class};
@@ -393,76 +418,210 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
}
@Override
- public QuotaStatementResponse createQuotaStatementResponse(final
List<QuotaUsageVO> quotaUsage) {
- if (quotaUsage == null || quotaUsage.isEmpty()) {
- throw new InvalidParameterValueException("There is no usage data
found for period mentioned.");
- }
+ public QuotaStatementResponse
createQuotaStatementResponse(QuotaStatementCmd cmd) {
+ Long accountId = getAccountIdForQuotaStatement(cmd);
+ Long domainId = getDomainIdForQuotaStatement(cmd, accountId);
+ List<QuotaUsageJoinVO> quotaUsages =
_quotaService.getQuotaUsage(accountId, null, domainId, cmd.getUsageType(),
cmd.getStartDate(), cmd.getEndDate());
+
+ logger.debug("Creating quota statement from [{}] usage records for
parameters [{}].", quotaUsages.size(),
+ ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd,
"accountName", "accountId", "projectId", "domainId", "startDate", "endDate",
"usageType", "showResources"));
+ createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(quotaUsages,
cmd.getUsageType());
+
+ Map<Integer, List<QuotaUsageJoinVO>> recordsPerUsageTypes =
quotaUsages.stream()
+
.sorted(Comparator.comparingInt(QuotaUsageJoinVO::getUsageType))
+
.collect(Collectors.groupingBy(QuotaUsageJoinVO::getUsageType));
+
+ List<QuotaStatementItemResponse> items = new ArrayList<>();
+ recordsPerUsageTypes.forEach((key, value) ->
items.add(createStatementItem(key, value, cmd.isShowResources())));
QuotaStatementResponse statement = new QuotaStatementResponse();
+ statement.setLineItem(items);
+
statement.setTotalQuota(items.stream().map(QuotaStatementItemResponse::getQuotaUsed).reduce(BigDecimal.ZERO,
BigDecimal::add));
+ statement.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
+ statement.setObjectName("statement");
- HashMap<Integer, QuotaTypes> quotaTariffMap = new HashMap<Integer,
QuotaTypes>();
- Collection<QuotaTypes> result = QuotaTypes.listQuotaTypes().values();
+ if (accountId != null) {
+ Account account = _accountDao.findByIdIncludingRemoved(accountId);
+ statement.setAccountId(account.getUuid());
+ statement.setAccountName(account.getAccountName());
+ domainId = account.getDomainId();
+ }
+ if (domainId != null) {
+ DomainVO domain = domainDao.findByIdIncludingRemoved(domainId);
+ statement.setDomainId(domain.getUuid());
+ }
+
+ return statement;
+ }
- for (QuotaTypes quotaTariff : result) {
- quotaTariffMap.put(quotaTariff.getQuotaType(), quotaTariff);
- // add dummy record for each usage type
- QuotaUsageVO dummy = new QuotaUsageVO(quotaUsage.get(0));
- dummy.setUsageType(quotaTariff.getQuotaType());
- dummy.setQuotaUsed(new BigDecimal(0));
- quotaUsage.add(dummy);
+ protected Long getAccountIdForQuotaStatement(QuotaStatementCmd cmd) {
+ if
(Account.Type.NORMAL.equals(CallContext.current().getCallingAccount().getType()))
{
+ logger.debug("Limiting the Quota statement for the calling
Account, as they are a User Account.");
+ return CallContext.current().getCallingAccountId();
}
- if (logger.isDebugEnabled()) {
- logger.debug(
- "createQuotaStatementResponse Type=" +
quotaUsage.get(0).getUsageType() + " usage=" +
quotaUsage.get(0).getQuotaUsed().setScale(2, RoundingMode.HALF_EVEN)
- + " rec.id=" + quotaUsage.get(0).getUsageItemId() + " SD="
+ quotaUsage.get(0).getStartDate() + " ED=" + quotaUsage.get(0).getEndDate());
+ long accountId = cmd.getEntityOwnerId();
+ if (accountId != -1) {
+ return accountId;
}
- Collections.sort(quotaUsage, new Comparator<QuotaUsageVO>() {
- @Override
- public int compare(QuotaUsageVO o1, QuotaUsageVO o2) {
- if (o1.getUsageType() == o2.getUsageType()) {
- return 0;
- }
- return o1.getUsageType() < o2.getUsageType() ? -1 : 1;
- }
- });
+ if (cmd.getDomainId() == null) {
+ logger.debug("Limiting the Quota statement for the calling
Account, as 'domainid' was not informed.");
+ return CallContext.current().getCallingAccountId();
+ }
+
+ logger.debug("Allowing admin/domain admin to generate the Quota
statement for the provided Domain.");
+ return null;
+ }
+
+ protected Long getDomainIdForQuotaStatement(QuotaStatementCmd cmd, Long
accountId) {
+ if (accountId != null) {
+ logger.debug("Quota statement is already limited to Account
[{}].", accountId);
+ Account account = _accountDao.findByIdIncludingRemoved(accountId);
+ return account.getDomainId();
+ }
+
+ Long domainId = cmd.getDomainId();
+ if (domainId != null) {
+ return domainId;
+ }
+
+ logger.debug("Limiting the Quota statement for the calling Account's
Domain.");
+ return CallContext.current().getCallingAccount().getDomainId();
+ }
+
+ protected void
createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(List<QuotaUsageJoinVO>
quotaUsages, Integer usageType) {
+ if (usageType != null) {
+ logger.debug("As the usage type [{}] was informed as parameter of
the API quotaStatement, we will not create dummy records.", usageType);
+ return;
+
+ }
+
+ for (Integer quotaType : QuotaTypes.listQuotaTypes().keySet()) {
+ QuotaUsageJoinVO dummy = new QuotaUsageJoinVO();
+ dummy.setUsageType(quotaType);
+ dummy.setQuotaUsed(BigDecimal.ZERO);
+ quotaUsages.add(dummy);
+ }
+ }
+
+ protected QuotaStatementItemResponse createStatementItem(int usageType,
List<QuotaUsageJoinVO> usageRecords, boolean showResources) {
+ QuotaUsageJoinVO firstRecord = usageRecords.get(0);
+ int type = firstRecord.getUsageType();
+
+ QuotaTypes quotaType = QuotaTypes.listQuotaTypes().get(type);
+
+ QuotaStatementItemResponse item = new QuotaStatementItemResponse(type);
+
item.setQuotaUsed(usageRecords.stream().map(QuotaUsageJoinVO::getQuotaUsed).filter(Objects::nonNull).reduce(BigDecimal.ZERO,
BigDecimal::add));
+ item.setUsageUnit(quotaType.getQuotaUnit());
+ item.setUsageName(quotaType.getQuotaName());
+
+ setStatementItemResources(item, usageType, usageRecords,
showResources);
+ return item;
+ }
+
+ protected void setStatementItemResources(QuotaStatementItemResponse
statementItem, int usageType, List<QuotaUsageJoinVO> quotaUsageRecords, boolean
showResources) {
+ if (!showResources) {
+ return;
+ }
+
+ List<QuotaStatementItemResourceResponse> itemDetails = new
ArrayList<>();
+
+ Map<Long, BigDecimal> quotaUsagesValuesAggregatedById =
quotaUsageRecords
+ .stream()
+ .filter(quotaUsageJoinVo ->
getResourceIdByUsageType(quotaUsageJoinVo, usageType) != null)
+ .collect(Collectors.groupingBy(
+ quotaUsageJoinVo ->
getResourceIdByUsageType(quotaUsageJoinVo, usageType),
+ Collectors.reducing(new BigDecimal(0),
QuotaUsageJoinVO::getQuotaUsed, BigDecimal::add)
+ ));
+
+ for (Map.Entry<Long, BigDecimal> entry :
quotaUsagesValuesAggregatedById.entrySet()) {
+ QuotaStatementItemResourceResponse detail = new
QuotaStatementItemResourceResponse();
+
+ detail.setQuotaUsed(entry.getValue());
+
+ QuotaUsageResourceVO resource =
getResourceFromIdAndType(entry.getKey(), usageType);
+ if (resource != null) {
+ detail.setResourceId(resource.getUuid());
+ detail.setDisplayName(resource.getName());
+ detail.setRemoved(resource.isRemoved());
+ } else {
+ detail.setDisplayName("<untraceable>");
- List<QuotaStatementItemResponse> items = new
ArrayList<QuotaStatementItemResponse>();
- QuotaStatementItemResponse lineitem;
- int type = -1;
- BigDecimal usage = new BigDecimal(0);
- BigDecimal totalUsage = new BigDecimal(0);
- quotaUsage.add(new QuotaUsageVO());// boundary
- QuotaUsageVO prev = quotaUsage.get(0);
- if (logger.isDebugEnabled()) {
- logger.debug("createQuotaStatementResponse record count=" +
quotaUsage.size());
- }
- for (final QuotaUsageVO quotaRecord : quotaUsage) {
- if (type != quotaRecord.getUsageType()) {
- if (type != -1) {
- lineitem = new QuotaStatementItemResponse(type);
- lineitem.setQuotaUsed(usage);
- lineitem.setAccountId(prev.getAccountId());
- lineitem.setDomainId(prev.getDomainId());
-
lineitem.setUsageUnit(quotaTariffMap.get(type).getQuotaUnit());
-
lineitem.setUsageName(quotaTariffMap.get(type).getQuotaName());
- lineitem.setObjectName("quotausage");
- items.add(lineitem);
- totalUsage = totalUsage.add(usage);
- usage = new BigDecimal(0);
- }
- type = quotaRecord.getUsageType();
}
- prev = quotaRecord;
- usage = usage.add(quotaRecord.getQuotaUsed());
+ itemDetails.add(detail);
}
+ statementItem.setResources(itemDetails);
+ }
- statement.setLineItem(items);
- statement.setTotalQuota(totalUsage);
- statement.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
- statement.setObjectName("statement");
- return statement;
+ protected Long getResourceIdByUsageType(QuotaUsageJoinVO quotaUsageJoinVo,
int usageType) {
+ switch (usageType) {
+ case QuotaTypes.NETWORK_BYTES_SENT:
+ case QuotaTypes.NETWORK_BYTES_RECEIVED:
+ return quotaUsageJoinVo.getNetworkId();
+ case QuotaTypes.NETWORK_OFFERING:
+ return quotaUsageJoinVo.getOfferingId();
+ default:
+ return quotaUsageJoinVo.getResourceId();
+ }
+ }
+
+ protected QuotaUsageResourceVO getResourceFromIdAndType(long resourceId,
int usageType) {
+ switch (usageType) {
+ case QuotaTypes.ALLOCATED_VM:
+ case QuotaTypes.RUNNING_VM:
+ VMInstanceVO vmInstance =
vmInstanceDao.findByIdIncludingRemoved(resourceId);
+ if (vmInstance != null) {
+ return new QuotaUsageResourceVO(vmInstance.getUuid(),
vmInstance.getHostName(), vmInstance.getRemoved());
+ }
+ break;
+ case QuotaTypes.VOLUME:
+ case QuotaTypes.VOLUME_SECONDARY:
+ case QuotaTypes.VM_DISK_BYTES_READ:
+ case QuotaTypes.VM_DISK_BYTES_WRITE:
+ case QuotaTypes.VM_DISK_IO_READ:
+ case QuotaTypes.VM_DISK_IO_WRITE:
+ VolumeVO volume =
volumeDao.findByIdIncludingRemoved(resourceId);
+ if (volume != null) {
+ return new QuotaUsageResourceVO(volume.getUuid(),
volume.getName(), volume.getRemoved());
+ }
+ break;
+ case QuotaTypes.VM_SNAPSHOT_ON_PRIMARY:
+ case QuotaTypes.VM_SNAPSHOT:
+ case QuotaTypes.SNAPSHOT:
+ SnapshotVO snapshot =
snapshotDao.findByIdIncludingRemoved(resourceId);
+ if (snapshot != null) {
+ return new QuotaUsageResourceVO(snapshot.getUuid(),
snapshot.getName(), snapshot.getRemoved());
+ }
+ break;
+ case QuotaTypes.NETWORK_BYTES_SENT:
+ case QuotaTypes.NETWORK_BYTES_RECEIVED:
+ NetworkVO network =
networkDao.findByIdIncludingRemoved(resourceId);
+ if (network != null) {
+ return new QuotaUsageResourceVO(network.getUuid(),
network.getName(), network.getRemoved());
+ }
+ break;
+ case QuotaTypes.TEMPLATE:
+ case QuotaTypes.ISO:
+ VMTemplateVO vmTemplate =
vmTemplateDao.findByIdIncludingRemoved(resourceId);
+ if (vmTemplate != null) {
+ return new QuotaUsageResourceVO(vmTemplate.getUuid(),
vmTemplate.getName(), vmTemplate.getRemoved());
+ }
+ break;
+ case QuotaTypes.NETWORK_OFFERING:
+ NetworkOfferingVO networkOffering =
networkOfferingDao.findByIdIncludingRemoved(resourceId);
+ if (networkOffering != null) {
+ return new QuotaUsageResourceVO(networkOffering.getUuid(),
networkOffering.getName(), networkOffering.getRemoved());
+ }
+ break;
+ case QuotaTypes.IP_ADDRESS:
+ IPAddressVO ipAddress =
ipAddressDao.findByIdIncludingRemoved(resourceId);
+ if (ipAddress != null) {
+ return new QuotaUsageResourceVO(ipAddress.getUuid(),
ipAddress.getName(), ipAddress.getRemoved());
+ }
+ break;
+ }
+ return null;
}
@Override
@@ -518,14 +677,14 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
}
protected void warnQuotaTariffUpdateDeprecatedFields(QuotaTariffUpdateCmd
cmd) {
- String warnMessage = "The parameter 's%s' for API 'quotaTariffUpdate'
is no longer needed and it will be removed in future releases.";
+ String warnMessage = "The parameter '{}' for API 'quotaTariffUpdate'
is no longer needed and it will be removed in future releases.";
if (cmd.getStartDate() != null) {
- logger.warn(String.format(warnMessage,"startdate"));
+ logger.warn(warnMessage, "startdate");
}
if (cmd.getUsageType() != null) {
- logger.warn(String.format(warnMessage,"usagetype"));
+ logger.warn(warnMessage, "usagetype");
}
}
@@ -712,11 +871,6 @@ public class QuotaResponseBuilderImpl implements
QuotaResponseBuilder {
return resp;
}
- @Override
- public List<QuotaUsageVO> getQuotaUsage(QuotaStatementCmd cmd) {
- return _quotaService.getQuotaUsage(cmd.getAccountId(),
cmd.getAccountName(), cmd.getDomainId(), cmd.getUsageType(),
cmd.getStartDate(), cmd.getEndDate());
- }
-
@Override
public List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd) {
return _quotaService.findQuotaBalanceVO(cmd.getAccountId(),
cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate());
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResourceResponse.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResourceResponse.java
new file mode 100644
index 00000000000..3e052f73339
--- /dev/null
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResourceResponse.java
@@ -0,0 +1,61 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements. See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership. The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License. You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied. See the License for the
+//specific language governing permissions and limitations
+//under the License.
+package org.apache.cloudstack.api.response;
+
+import java.math.BigDecimal;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+import com.cloud.serializer.Param;
+
+public class QuotaStatementItemResourceResponse extends BaseResponse {
+
+ @SerializedName(ApiConstants.QUOTA_CONSUMED)
+ @Param(description = "Quota consumed.")
+ private BigDecimal quotaUsed;
+
+ @SerializedName(ApiConstants.RESOURCE_ID)
+ @Param(description = "Resources's ID.")
+ private String resourceId;
+
+ @SerializedName(ApiConstants.DISPLAY_NAME)
+ @Param(description = "Resource's display name.")
+ private String displayName;
+
+ @SerializedName(ApiConstants.REMOVED)
+ @Param(description = "Indicates whether the resource is removed or
active.")
+ private boolean removed;
+
+ public void setQuotaUsed(BigDecimal quotaUsed) {
+ this.quotaUsed = quotaUsed;
+ }
+
+ public void setResourceId(String resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public void setRemoved(boolean removed) {
+ this.removed = removed;
+ }
+}
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java
index c370d82b3cc..0747c5a9172 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java
@@ -17,72 +17,41 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
-import java.math.RoundingMode;
+import java.util.List;
import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
public class QuotaStatementItemResponse extends BaseResponse {
- @SerializedName("type")
- @Param(description = "Usage type")
+ @SerializedName(ApiConstants.TYPE)
+ @Param(description = "Usage type.")
private int usageType;
- @SerializedName("accountid")
- @Param(description = "Account id")
- private Long accountId;
-
- @SerializedName("account")
- @Param(description = "Account name")
- private String accountName;
-
- @SerializedName("domain")
- @Param(description = "Domain id")
- private Long domainId;
-
- @SerializedName("name")
- @Param(description = "Usage type name")
+ @SerializedName(ApiConstants.NAME)
+ @Param(description = "Name of the Usage type.")
private String usageName;
- @SerializedName("unit")
- @Param(description = "Usage unit")
+ @SerializedName(ApiConstants.UNIT)
+ @Param(description = "Unit of the Usage type.")
private String usageUnit;
- @SerializedName("quota")
- @Param(description = "Quota consumed")
+ @SerializedName(ApiConstants.QUOTA)
+ @Param(description = "Quota consumed.")
private BigDecimal quotaUsed;
+ @SerializedName(ApiConstants.RESOURCES)
+ @Param(description = "Item's resources.")
+ private List<QuotaStatementItemResourceResponse> resources;
+
public QuotaStatementItemResponse(final int usageType) {
this.usageType = usageType;
}
- public Long getAccountId() {
- return accountId;
- }
-
- public void setAccountId(Long accountId) {
- this.accountId = accountId;
- }
-
- public String getAccountName() {
- return accountName;
- }
-
- public void setAccountName(String accountName) {
- this.accountName = accountName;
- }
-
- public Long getDomainId() {
- return domainId;
- }
-
- public void setDomainId(Long domainId) {
- this.domainId = domainId;
- }
-
public String getUsageName() {
return usageName;
}
@@ -112,7 +81,15 @@ public class QuotaStatementItemResponse extends
BaseResponse {
}
public void setQuotaUsed(BigDecimal quotaUsed) {
- this.quotaUsed = quotaUsed.setScale(2, RoundingMode.HALF_EVEN);
+ this.quotaUsed = quotaUsed;
+ }
+
+ public List<QuotaStatementItemResourceResponse> getResources() {
+ return resources;
+ }
+
+ public void setResources(List<QuotaStatementItemResourceResponse>
resources) {
+ this.resources = resources;
}
}
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementResponse.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementResponse.java
index 0a7ba4dbeb9..81cb1011182 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementResponse.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaStatementResponse.java
@@ -18,56 +18,56 @@ package org.apache.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import java.math.BigDecimal;
-import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
public class QuotaStatementResponse extends BaseResponse {
- @SerializedName("accountid")
- @Param(description = "Account ID")
- private Long accountId;
+ @SerializedName(ApiConstants.ACCOUNT_ID)
+ @Param(description = "ID of the Account.")
+ private String accountId;
- @SerializedName("account")
- @Param(description = "Account name")
+ @SerializedName(ApiConstants.ACCOUNT)
+ @Param(description = "Name of the Account.")
private String accountName;
- @SerializedName("domain")
- @Param(description = "Domain ID")
- private Long domainId;
+ @SerializedName(ApiConstants.DOMAIN)
+ @Param(description = "ID of the Domain.")
+ private String domainId;
- @SerializedName("quotausage")
- @Param(description = "List of quota usage under various types",
responseObject = QuotaStatementItemResponse.class)
+ @SerializedName(ApiConstants.QUOTA_USAGE)
+ @Param(description = "List of Quota usage under various types.",
responseObject = QuotaStatementItemResponse.class)
private List<QuotaStatementItemResponse> lineItem;
- @SerializedName("totalquota")
- @Param(description = "Total quota used during this period")
+ @SerializedName(ApiConstants.TOTAL_QUOTA)
+ @Param(description = "Total Quota consumed during this period.")
private BigDecimal totalQuota;
- @SerializedName("startdate")
- @Param(description = "Start date")
+ @SerializedName(ApiConstants.START_DATE)
+ @Param(description = "Start date of the Quota statement.")
private Date startDate = null;
- @SerializedName("enddate")
- @Param(description = "End date")
+ @SerializedName(ApiConstants.END_DATE)
+ @Param(description = "End date of the Quota statement.")
private Date endDate = null;
- @SerializedName("currency")
- @Param(description = "Currency")
+ @SerializedName(ApiConstants.CURRENCY)
+ @Param(description = "Currency of the Quota statement.")
private String currency;
public QuotaStatementResponse() {
super();
}
- public Long getAccountId() {
+ public String getAccountId() {
return accountId;
}
- public void setAccountId(Long accountId) {
+ public void setAccountId(String accountId) {
this.accountId = accountId;
}
@@ -79,45 +79,36 @@ public class QuotaStatementResponse extends BaseResponse {
this.accountName = accountName;
}
- public Long getDomainId() {
+ public String getDomainId() {
return domainId;
}
- public void setDomainId(Long domainId) {
+ public void setDomainId(String domainId) {
this.domainId = domainId;
}
- public List<QuotaStatementItemResponse> getLineItem() {
- return lineItem;
- }
-
public void setLineItem(List<QuotaStatementItemResponse> lineItem) {
this.lineItem = lineItem;
}
public Date getStartDate() {
- return startDate == null ? null : new Date(startDate.getTime());
+ return startDate;
}
public void setStartDate(Date startDate) {
- this.startDate = startDate == null ? null : new
Date(startDate.getTime());
+ this.startDate = startDate;
}
public Date getEndDate() {
- return endDate == null ? null : new Date(endDate.getTime());
+ return endDate;
}
public void setEndDate(Date endDate) {
- this.endDate = endDate == null ? null : new Date(endDate.getTime());
- }
-
-
- public BigDecimal getTotalQuota() {
- return totalQuota;
+ this.endDate = endDate;
}
public void setTotalQuota(BigDecimal totalQuota) {
- this.totalQuota = totalQuota.setScale(2, RoundingMode.HALF_EVEN);
+ this.totalQuota = totalQuota;
}
public String getCurrency() {
diff --git
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
index a421d0f066a..78acfc11682 100644
---
a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
+++
b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java
@@ -21,14 +21,14 @@ import java.util.Date;
import java.util.List;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
-import org.apache.cloudstack.quota.vo.QuotaUsageVO;
+import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import com.cloud.user.AccountVO;
import com.cloud.utils.component.PluggableService;
public interface QuotaService extends PluggableService {
- List<QuotaUsageVO> getQuotaUsage(Long accountId, String accountName, Long
domainId, Integer usageType, Date startDate, Date endDate);
+ List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName,
Long domainId, Integer usageType, Date startDate, Date endDate);
List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String
accountName, Long domainId, Date startDate, Date endDate);
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 2d7d623d1d9..a0ba2fbc751 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
@@ -53,10 +53,10 @@ import
org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.quota.constant.QuotaConfig;
import org.apache.cloudstack.quota.dao.QuotaAccountDao;
import org.apache.cloudstack.quota.dao.QuotaBalanceDao;
-import org.apache.cloudstack.quota.dao.QuotaUsageDao;
+import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
-import org.apache.cloudstack.quota.vo.QuotaUsageVO;
+import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
@@ -80,7 +80,7 @@ public class QuotaServiceImpl extends ManagerBase implements
QuotaService, Confi
@Inject
private QuotaAccountDao _quotaAcc;
@Inject
- private QuotaUsageDao _quotaUsageDao;
+ private QuotaUsageJoinDao quotaUsageJoinDao;
@Inject
private DomainDao _domainDao;
@Inject
@@ -213,27 +213,7 @@ public class QuotaServiceImpl extends ManagerBase
implements QuotaService, Confi
}
@Override
- public List<QuotaUsageVO> getQuotaUsage(Long accountId, String
accountName, Long domainId, Integer usageType, Date startDate, Date endDate) {
- // if accountId is not specified, use accountName and domainId
- if ((accountId == null) && (accountName != null) && (domainId !=
null)) {
- Account userAccount = null;
- Account caller = CallContext.current().getCallingAccount();
- if (_domainDao.isChildDomain(caller.getDomainId(), domainId)) {
- Filter filter = new Filter(AccountVO.class, "id",
Boolean.FALSE, null, null);
- List<AccountVO> accounts =
_accountDao.listAccounts(accountName, domainId, filter);
- if (!accounts.isEmpty()) {
- userAccount = accounts.get(0);
- }
- if (userAccount != null) {
- accountId = userAccount.getId();
- } else {
- throw new InvalidParameterValueException("Unable to find
account " + accountName + " in domain " + domainId);
- }
- } else {
- throw new PermissionDeniedException("Invalid Domain Id or
Account");
- }
- }
-
+ public List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String
accountName, Long domainId, Integer usageType, Date startDate, Date endDate) {
if (startDate.after(endDate)) {
throw new InvalidParameterValueException("Incorrect Date Range.
Start date: " + startDate + " is after end date:" + endDate);
}
@@ -241,7 +221,7 @@ public class QuotaServiceImpl extends ManagerBase
implements QuotaService, Confi
logger.debug("Getting quota records of type [{}] for account [{}] in
domain [{}], between [{}] and [{}].",
usageType, accountId, domainId, startDate, endDate);
- return _quotaUsageDao.findQuotaUsage(accountId, domainId, usageType,
startDate, endDate);
+ return quotaUsageJoinDao.findQuotaUsage(accountId, domainId,
usageType, null, null, null, startDate, endDate, null);
}
@Override
diff --git
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java
index d6f9f747fa8..e67638db632 100644
---
a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java
+++
b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java
@@ -16,38 +16,29 @@
// under the License.
package org.apache.cloudstack.api.command;
-import junit.framework.TestCase;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaStatementResponse;
-import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-
@RunWith(MockitoJUnitRunner.class)
-public class QuotaStatementCmdTest extends TestCase {
+public class QuotaStatementCmdTest {
@Mock
- QuotaResponseBuilder responseBuilder;
+ QuotaResponseBuilder responseBuilderMock;
@Test
- public void testQuotaStatementCmd() throws NoSuchFieldException,
IllegalAccessException {
+ public void executeTestVerifyCalls() {
QuotaStatementCmd cmd = new QuotaStatementCmd();
cmd.setAccountName("admin");
+ cmd.responseBuilder = responseBuilderMock;
- Field rbField =
QuotaStatementCmd.class.getDeclaredField("_responseBuilder");
- rbField.setAccessible(true);
- rbField.set(cmd, responseBuilder);
+ Mockito.doReturn(new
QuotaStatementResponse()).when(responseBuilderMock).createQuotaStatementResponse(Mockito.any());
- List<QuotaUsageVO> quotaUsageVOList = new ArrayList<QuotaUsageVO>();
-
Mockito.when(responseBuilder.getQuotaUsage(Mockito.eq(cmd))).thenReturn(quotaUsageVOList);
-
Mockito.when(responseBuilder.createQuotaStatementResponse(Mockito.eq(quotaUsageVOList))).thenReturn(new
QuotaStatementResponse());
cmd.execute();
- Mockito.verify(responseBuilder,
Mockito.times(1)).getQuotaUsage(Mockito.eq(cmd));
+
+ Mockito.verify(responseBuilderMock).createQuotaStatementResponse(cmd);
}
}
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 ea88a106b84..81b4992082d 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
@@ -17,6 +17,8 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -42,6 +44,7 @@ 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.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.context.CallContext;
@@ -67,6 +70,7 @@ import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
+import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.commons.lang3.time.DateUtils;
@@ -914,4 +918,199 @@ public class QuotaResponseBuilderImplTest extends
TestCase {
Assert.assertTrue(formattedVariables.containsValue("accountname"));
Assert.assertTrue(formattedVariables.containsValue("zonename"));
}
+
+ @Test
+ public void
createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeDifferentFromNullDoNothing()
{
+ List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
+
+
quotaResponseBuilderSpy.createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(listUsage,
1);
+
+ Assert.assertTrue(listUsage.isEmpty());
+ }
+
+ @Test
+ public void
createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeIsNullAddDummyForAllQuotaTypes()
{
+ List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
+ listUsage.add(new QuotaUsageJoinVO());
+
+
quotaResponseBuilderSpy.createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformed(listUsage,
null);
+
+ Assert.assertEquals(QuotaTypes.listQuotaTypes().size() + 1,
listUsage.size());
+
+ QuotaTypes.listQuotaTypes().entrySet().forEach(entry -> {
+ Assert.assertTrue(listUsage.stream().anyMatch(usage ->
usage.getUsageType() == entry.getKey() &&
usage.getQuotaUsed().equals(BigDecimal.ZERO)));
+ });
+ }
+
+ private List<QuotaUsageJoinVO> getQuotaUsagesForTest() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+ List<QuotaUsageJoinVO> quotaUsages = new ArrayList<>();
+
+ QuotaUsageJoinVO quotaUsage = new QuotaUsageJoinVO();
+ quotaUsage.setAccountId(1l);
+ quotaUsage.setDomainId(2l);
+ quotaUsage.setUsageType(3);
+ quotaUsage.setQuotaUsed(BigDecimal.valueOf(10));
+ try {
+ quotaUsage.setStartDate(sdf.parse("2022-01-01"));
+ quotaUsage.setEndDate(sdf.parse("2022-01-02"));
+ } catch (ParseException ignored) {
+ }
+ quotaUsages.add(quotaUsage);
+
+ quotaUsage = new QuotaUsageJoinVO();
+ quotaUsage.setAccountId(4l);
+ quotaUsage.setDomainId(5l);
+ quotaUsage.setUsageType(3);
+ quotaUsage.setQuotaUsed(null);
+ try {
+ quotaUsage.setStartDate(sdf.parse("2022-01-03"));
+ quotaUsage.setEndDate(sdf.parse("2022-01-04"));
+ } catch (ParseException ignored) {
+ }
+ quotaUsages.add(quotaUsage);
+
+ quotaUsage = new QuotaUsageJoinVO();
+ quotaUsage.setAccountId(6l);
+ quotaUsage.setDomainId(7l);
+ quotaUsage.setUsageType(3);
+ quotaUsage.setQuotaUsed(BigDecimal.valueOf(5));
+ try {
+ quotaUsage.setStartDate(sdf.parse("2022-01-05"));
+ quotaUsage.setEndDate(sdf.parse("2022-01-06"));
+ } catch (ParseException ignored) {
+ }
+ quotaUsages.add(quotaUsage);
+
+ return quotaUsages;
+ }
+
+ @Test
+ public void createStatementItemTestReturnItem() {
+ List<QuotaUsageJoinVO> quotaUsages = getQuotaUsagesForTest();
+
Mockito.doNothing().when(quotaResponseBuilderSpy).setStatementItemResources(Mockito.any(),
Mockito.anyInt(), Mockito.any(), Mockito.anyBoolean());
+
+ QuotaStatementItemResponse result =
quotaResponseBuilderSpy.createStatementItem(0, quotaUsages, false);
+
+ QuotaUsageJoinVO expected = quotaUsages.get(0);
+ QuotaTypes quotaTypeExpected =
QuotaTypes.listQuotaTypes().get(expected.getUsageType());
+ Assert.assertEquals(BigDecimal.valueOf(15), result.getQuotaUsed());
+ Assert.assertEquals(quotaTypeExpected.getQuotaUnit(),
result.getUsageUnit());
+ Assert.assertEquals(quotaTypeExpected.getQuotaName(),
result.getUsageName());
+ }
+
+ @Test
+ public void setStatementItemResourcesTestDoNotShowResourcesDoNothing() {
+ QuotaStatementItemResponse item = new QuotaStatementItemResponse(1);
+
+ quotaResponseBuilderSpy.setStatementItemResources(item, 0,
getQuotaUsagesForTest(), false);
+
+ Assert.assertNull(item.getResources());
+ }
+
+ @Test
+ public void
getAccountIdForQuotaStatementTestLimitsToCallingAccountForNormalUser() {
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+
+
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
+ Mockito.doReturn(Account.Type.NORMAL).when(accountMock).getType();
+
+ try (MockedStatic<CallContext> callContextMocked =
Mockito.mockStatic(CallContext.class)) {
+
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+
+ Long result =
quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
+
+
Assert.assertEquals(Long.valueOf(callerAccountMock.getAccountId()), result);
+ }
+ }
+
+ @Test
+ public void
getAccountIdForQuotaStatementTestReturnsEntityOwnerIdWhenProvided() {
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+
+ Mockito.doReturn(42L).when(cmd).getEntityOwnerId();
+
+ Long result =
quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
+
+ Assert.assertEquals(Long.valueOf(42L), result);
+ }
+
+ @Test
+ public void
getAccountIdForQuotaStatementTestLimitsToCallingAccountWhenCallerIsAdminAndDomainIsNotProvided()
{
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+
+
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
+ Mockito.doReturn(Account.Type.ADMIN).when(accountMock).getType();
+ Mockito.doReturn(-1L).when(cmd).getEntityOwnerId();
+ Mockito.doReturn(null).when(cmd).getDomainId();
+
+ try (MockedStatic<CallContext> callContextMocked =
Mockito.mockStatic(CallContext.class)) {
+
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+
+ Long result =
quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
+
+
Assert.assertEquals(Long.valueOf(callerAccountMock.getAccountId()), result);
+ }
+ }
+
+ @Test
+ public void
getAccountIdForQuotaStatementTestReturnsNullWhenCallerIsAdminAndDomainIsProvided()
{
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+
+
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
+ Mockito.doReturn(Account.Type.ADMIN).when(accountMock).getType();
+ Mockito.doReturn(-1L).when(cmd).getEntityOwnerId();
+ Mockito.doReturn(10L).when(cmd).getDomainId();
+
+ try (MockedStatic<CallContext> callContextMocked =
Mockito.mockStatic(CallContext.class)) {
+
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+
+ Long result =
quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
+
+ Assert.assertNull(result);
+ }
+ }
+
+ @Test
+ public void
getDomainIdForQuotaStatementTestReturnsAccountDomainIdWhenAccountIdIsProvided()
{
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+ AccountVO account = Mockito.mock(AccountVO.class);
+
+
Mockito.doReturn(account).when(accountDaoMock).findByIdIncludingRemoved(55L);
+ Mockito.doReturn(77L).when(account).getDomainId();
+
+ Long result =
quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, 55L);
+
+ Assert.assertEquals(Long.valueOf(77L), result);
+ }
+
+ @Test
+ public void
getDomainIdForQuotaStatementTestReturnsProvidedDomainIdWhenAccountIdIsNull() {
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+
+ Mockito.doReturn(99L).when(cmd).getDomainId();
+
+ Long result =
quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
+
+ Assert.assertEquals(Long.valueOf(99L), result);
+ }
+
+ @Test
+ public void
getDomainIdForQuotaStatementTestFallsBackToCallingAccountDomainIdWhenNeitherAccountNorDomainIsProvided()
{
+ QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
+ Account account = Mockito.mock(Account.class);
+
+ Mockito.doReturn(null).when(cmd).getDomainId();
+ Mockito.doReturn(123L).when(account).getDomainId();
+ Mockito.doReturn(account).when(callContextMock).getCallingAccount();
+
+ try (MockedStatic<CallContext> callContextMocked =
Mockito.mockStatic(CallContext.class)) {
+
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
+
+ Long result =
quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
+
+ Assert.assertEquals(123L, result.longValue());
+ }
+ }
}
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 259264f3b0e..a0fe63de851 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
@@ -28,6 +28,7 @@ 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.QuotaUsageDao;
+import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.joda.time.DateTime;
@@ -63,6 +64,8 @@ public class QuotaServiceImplTest extends TestCase {
@Mock
QuotaBalanceDao quotaBalanceDao;
@Mock
+ QuotaUsageJoinDao quotaUsageJoinDaoMock;
+ @Mock
QuotaResponseBuilder respBldr;
@Mock
private AccountVO accountVoMock;
@@ -85,9 +88,9 @@ public class QuotaServiceImplTest extends TestCase {
quotaAccountDaoField.setAccessible(true);
quotaAccountDaoField.set(quotaServiceImplSpy, quotaAcc);
- Field quotaUsageDaoField =
QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao");
+ Field quotaUsageDaoField =
QuotaServiceImpl.class.getDeclaredField("quotaUsageJoinDao");
quotaUsageDaoField.setAccessible(true);
- quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageDao);
+ quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageJoinDaoMock);
Field domainDaoField =
QuotaServiceImpl.class.getDeclaredField("_domainDao");
domainDaoField.setAccessible(true);
@@ -142,7 +145,8 @@ public class QuotaServiceImplTest extends TestCase {
final Date endDate = new Date();
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));
+ Mockito.verify(quotaUsageJoinDaoMock,
Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId),
Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(),
+ Mockito.any(), Mockito.any(), Mockito.any(Date.class),
Mockito.any(Date.class), Mockito.any());
}
@Test