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 <julien.vaz@scclouds.com.br>
Co-authored-by: Fabricio Duarte <fabricio.duarte.jr@gmail.com>
This commit is contained in:
julien-vaz 2026-04-29 21:09:13 -03:00 committed by GitHub
parent 089eb36e47
commit a73cc9a22c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1218 additions and 254 deletions

View File

@ -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";

View File

@ -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`));

View File

@ -0,0 +1,35 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
-- 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);

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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" />

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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,78 +418,212 @@ 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();
HashMap<Integer, QuotaTypes> quotaTariffMap = new HashMap<Integer, QuotaTypes>();
Collection<QuotaTypes> result = QuotaTypes.listQuotaTypes().values();
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);
}
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());
}
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;
}
});
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());
}
statement.setLineItem(items);
statement.setTotalQuota(totalUsage);
statement.setTotalQuota(items.stream().map(QuotaStatementItemResponse::getQuotaUsed).reduce(BigDecimal.ZERO, BigDecimal::add));
statement.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
statement.setObjectName("statement");
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;
}
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();
}
long accountId = cmd.getEntityOwnerId();
if (accountId != -1) {
return accountId;
}
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>");
}
itemDetails.add(detail);
}
statementItem.setResources(itemDetails);
}
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
public Pair<List<QuotaTariffVO>, Integer> listQuotaTariffPlans(final QuotaTariffListCmd cmd) {
Date startDate = cmd.getEffectiveDate();
@ -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());

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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