Introduce Quota resource statement API (#13236)

This commit is contained in:
Fabricio Duarte 2026-06-12 08:16:10 -03:00 committed by GitHub
parent b0601e5478
commit 2fd83e13b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 813 additions and 206 deletions

View File

@ -602,6 +602,8 @@ public class ApiConstants {
public static final String SUITABLE_FOR_VM = "suitableforvirtualmachine";
public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot";
public static final String TARGET_IQN = "targetiqn";
public static final String TARIFF_ID = "tariffid";
public static final String TARIFF_NAME = "tariffname";
public static final String TASKS_FILTER = "tasksfilter";
public static final String TEMPLATE_FILTER = "templatefilter";
public static final String TEMPLATE_ID = "templateid";
@ -663,6 +665,7 @@ public class ApiConstants {
public static final String VIRTUAL_MACHINE_STATE = "vmstate";
public static final String VIRTUAL_MACHINES = "virtualmachines";
public static final String USAGE_ID = "usageid";
public static final String USAGE_NAME = "usagename";
public static final String USAGE_TYPE = "usagetype";
public static final String INCLUDE_TAGS = "includetags";
@ -879,6 +882,7 @@ public class ApiConstants {
public static final String IS_SOURCE_NAT = "issourcenat";
public static final String IS_STATIC_NAT = "isstaticnat";
public static final String ITERATIONS = "iterations";
public static final String ITEMS = "items";
public static final String SORT_BY = "sortby";
public static final String CHANGE_CIDR = "changecidr";
public static final String PURPOSE = "purpose";

View File

@ -202,3 +202,9 @@ CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`(
CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE,
INDEX `i_image_transfer__backup_id`(`backup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--- Quota resource statement
INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order)
SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order
FROM cloud.role_permissions rp
WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement');

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -32,6 +33,9 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.quota.activationrule.presetvariables.Configuration;
import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable;
@ -43,9 +47,11 @@ 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.QuotaTariffDao;
import org.apache.cloudstack.quota.dao.QuotaTariffUsageDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.usage.UsageUnitTypes;
@ -86,7 +92,8 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
private QuotaBalanceDao _quotaBalanceDao;
@Inject
private ConfigurationDao _configDao;
@Inject
private QuotaTariffUsageDao quotaTariffUsageDao;
@Inject
protected PresetVariableHelper presetVariableHelper;
@ -311,14 +318,14 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
String accountToString = account.reflectionToString();
logger.info("Calculating quota usage of [{}] usage records for account [{}].", usageRecords.size(), accountToString);
List<Pair<UsageVO, QuotaUsageVO>> pairsUsageAndQuotaUsage = new ArrayList<>();
Map<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> mapUsageAndQuotaUsage = new LinkedHashMap<>();
try (JsInterpreter jsInterpreter = new JsInterpreter(QuotaConfig.QuotaActivationRuleTimeout.value())) {
for (UsageVO usageRecord : usageRecords) {
int usageType = usageRecord.getUsageType();
if (!shouldCalculateUsageRecord(account, usageRecord)) {
pairsUsageAndQuotaUsage.add(new Pair<>(usageRecord, null));
mapUsageAndQuotaUsage.put(usageRecord, null);
continue;
}
@ -326,18 +333,31 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
List<QuotaTariffVO> quotaTariffs = pairQuotaTariffsPerUsageTypeAndHasActivationRule.first();
boolean hasAnyQuotaTariffWithActivationRule = pairQuotaTariffsPerUsageTypeAndHasActivationRule.second();
BigDecimal aggregatedQuotaTariffsValue = aggregateQuotaTariffsValues(usageRecord, quotaTariffs, hasAnyQuotaTariffWithActivationRule, jsInterpreter, accountToString);
Map<QuotaTariffVO, BigDecimal> aggregatedQuotaTariffsAndValues = aggregateQuotaTariffsValues(usageRecord,
quotaTariffs, hasAnyQuotaTariffWithActivationRule, jsInterpreter, accountToString);
BigDecimal aggregatedQuotaTariffsValue = aggregatedQuotaTariffsAndValues.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
logger.debug("The aggregation of the quota tariffs of account [{}] resulted in [{}] for the usage record [{}].",
account, aggregatedQuotaTariffsValue, usageRecord);
QuotaUsageVO quotaUsage = createQuotaUsageAccordingToUsageUnit(usageRecord, aggregatedQuotaTariffsValue, accountToString);
if (quotaUsage == null) {
mapUsageAndQuotaUsage.put(usageRecord, null);
continue;
}
pairsUsageAndQuotaUsage.add(new Pair<>(usageRecord, quotaUsage));
List<QuotaTariffUsageVO> quotaTariffUsages = new ArrayList<>();
for (Map.Entry<QuotaTariffVO, BigDecimal> entry : aggregatedQuotaTariffsAndValues.entrySet()) {
QuotaTariffUsageVO quotaTariffUsage = createQuotaTariffUsage(usageRecord, entry.getKey(), entry.getValue());
quotaTariffUsages.add(quotaTariffUsage);
}
mapUsageAndQuotaUsage.put(usageRecord, new Pair<>(quotaUsage, quotaTariffUsages));
}
} catch (Exception e) {
logger.error(String.format("Failed to calculate the quota usage for account [%s] due to [%s].", accountToString, e.getMessage()), e);
return new ArrayList<>();
}
return persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(pairsUsageAndQuotaUsage);
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaUsageVO>>) status -> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(mapUsageAndQuotaUsage));
}
protected boolean shouldCalculateUsageRecord(AccountVO accountVO, UsageVO usageRecord) {
@ -349,31 +369,41 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
return true;
}
protected List<QuotaUsageVO> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(List<Pair<UsageVO, QuotaUsageVO>> pairsUsageAndQuotaUsage) {
protected List<QuotaUsageVO> persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(Map<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> mapUsageAndQuotaTariffUsage) {
List<QuotaUsageVO> quotaUsages = new ArrayList<>();
for (Pair<UsageVO, QuotaUsageVO> pairUsageAndQuotaUsage : pairsUsageAndQuotaUsage) {
UsageVO usageVo = pairUsageAndQuotaUsage.first();
for (Map.Entry<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> usageAndTariffUsage : mapUsageAndQuotaTariffUsage.entrySet()) {
UsageVO usageVo = usageAndTariffUsage.getKey();
usageVo.setQuotaCalculated(1);
_usageDao.persistUsage(usageVo);
QuotaUsageVO quotaUsageVo = pairUsageAndQuotaUsage.second();
if (quotaUsageVo != null) {
_quotaUsageDao.persistQuotaUsage(quotaUsageVo);
quotaUsages.add(quotaUsageVo);
Pair<QuotaUsageVO, List<QuotaTariffUsageVO>> pairUsageAndTariffUsages = usageAndTariffUsage.getValue();
if (pairUsageAndTariffUsages != null) {
QuotaUsageVO quotaUsage = pairUsageAndTariffUsages.first();
_quotaUsageDao.persistQuotaUsage(quotaUsage);
quotaUsages.add(quotaUsage);
persistQuotaTariffUsages(pairUsageAndTariffUsages.second(), quotaUsage.getId());
}
}
return quotaUsages;
}
protected BigDecimal aggregateQuotaTariffsValues(UsageVO usageRecord, List<QuotaTariffVO> quotaTariffs, boolean hasAnyQuotaTariffWithActivationRule,
JsInterpreter jsInterpreter, String accountToString) {
protected void persistQuotaTariffUsages(List<QuotaTariffUsageVO> quotaTariffUsages, Long quotaUsageId) {
for (QuotaTariffUsageVO quotaTariffUsage : quotaTariffUsages) {
quotaTariffUsage.setQuotaUsageId(quotaUsageId);
quotaTariffUsageDao.persistQuotaTariffUsage(quotaTariffUsage);
}
}
protected Map<QuotaTariffVO, BigDecimal> aggregateQuotaTariffsValues(UsageVO usageRecord, List<QuotaTariffVO> quotaTariffs, boolean hasAnyQuotaTariffWithActivationRule,
JsInterpreter jsInterpreter, String accountToString) {
String usageRecordToString = usageRecord.toString(usageAggregationTimeZone);
logger.debug("Validating usage record [{}] for account [{}] against [{}] quota tariffs.", usageRecordToString, accountToString, quotaTariffs.size());
PresetVariables presetVariables = getPresetVariables(hasAnyQuotaTariffWithActivationRule, usageRecord);
BigDecimal aggregatedQuotaTariffsValue = BigDecimal.ZERO;
Map<QuotaTariffVO, BigDecimal> aggregatedQuotaTariffsAndValues = new HashMap<>();
quotaTariffs.sort(Comparator.comparing(QuotaTariffVO::getPosition));
@ -382,10 +412,9 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
for (QuotaTariffVO quotaTariff : quotaTariffs) {
if (isQuotaTariffInPeriodToBeApplied(usageRecord, quotaTariff, accountToString)) {
BigDecimal tariffValue = getQuotaTariffValueToBeApplied(quotaTariff, jsInterpreter, presetVariables, lastTariffs);
aggregatedQuotaTariffsValue = aggregatedQuotaTariffsValue.add(tariffValue);
aggregatedQuotaTariffsAndValues.put(quotaTariff, tariffValue);
Tariff tariffPresetVariable = new Tariff();
tariffPresetVariable.setId(quotaTariff.getUuid());
@ -394,10 +423,10 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
}
}
logger.debug(String.format("The aggregation of the quota tariffs resulted in the value [%s] for the usage record [%s]. We will use this value to calculate the final"
+ " usage value.", aggregatedQuotaTariffsValue, usageRecordToString));
logger.debug("The aggregation of the quota tariffs resulted in [{}] quota tariffs for the usage record [{}]. The values of the quota tariffs will be used"
+ " to calculate the final usage value.", aggregatedQuotaTariffsAndValues.size(), usageRecordToString);
return aggregatedQuotaTariffsValue;
return aggregatedQuotaTariffsAndValues;
}
protected PresetVariables getPresetVariables(boolean hasAnyQuotaTariffWithActivationRule, UsageVO usageRecord) {
@ -408,6 +437,26 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
return null;
}
protected QuotaTariffUsageVO createQuotaTariffUsage(UsageVO usageRecord, QuotaTariffVO quotaTariff, BigDecimal quotaTariffValue) {
BigDecimal quotaUsageValue = BigDecimal.ZERO;
if (!quotaTariffValue.equals(BigDecimal.ZERO)) {
String quotaUnit = QuotaTypes.getQuotaType(usageRecord.getUsageType()).getQuotaUnit();
logger.trace("Calculating the value of the quota tariff [{}] according to its value [{}] and its quota unit [{}].", quotaTariff, quotaTariffValue, quotaUnit);
quotaUsageValue = getUsageValueAccordingToUsageUnitType(usageRecord, quotaTariffValue, quotaUnit);
logger.debug("The calculation of the value of the quota tariff [{}] according to its value [{}] and its usage unit [{}] resulted in the value [{}].",
quotaTariff, quotaTariffValue, quotaUnit, quotaUsageValue);
} else {
logger.debug("Quota tariff [{}] has no value to be calculated; therefore, it will be marked as value zero.", quotaTariff);
}
QuotaTariffUsageVO quotaTariffUsage = new QuotaTariffUsageVO();
quotaTariffUsage.setTariffId(quotaTariff.getId());
quotaTariffUsage.setQuotaUsed(quotaUsageValue);
return quotaTariffUsage;
}
/**
* Returns the quota tariff value according to the result of the activation rule.<br/>
* <ul>

View File

@ -20,11 +20,23 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.network.IpAddress;
import com.cloud.network.Network;
import com.cloud.network.VpnUser;
import com.cloud.network.rules.LoadBalancer;
import com.cloud.network.rules.PortForwardingRule;
import com.cloud.network.security.SecurityGroup;
import com.cloud.network.vpc.Vpc;
import com.cloud.offering.NetworkOffering;
import com.cloud.storage.Snapshot;
import com.cloud.storage.Volume;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.usage.UsageTypes;
import org.apache.cloudstack.usage.UsageUnitTypes;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.lang3.StringUtils;
public class QuotaTypes extends UsageTypes {
private final Integer quotaType;
@ -32,44 +44,46 @@ public class QuotaTypes extends UsageTypes {
private final String quotaUnit;
private final String description;
private final String discriminator;
private final Class<?> clazz;
private final static Map<Integer, QuotaTypes> quotaTypeMap;
static {
final HashMap<Integer, QuotaTypes> quotaTypeList = new HashMap<Integer, QuotaTypes>();
quotaTypeList.put(RUNNING_VM, new QuotaTypes(RUNNING_VM, "RUNNING_VM", UsageUnitTypes.COMPUTE_MONTH.toString(), "Running Vm Usage"));
quotaTypeList.put(ALLOCATED_VM, new QuotaTypes(ALLOCATED_VM, "ALLOCATED_VM", UsageUnitTypes.COMPUTE_MONTH.toString(), "Allocated Vm Usage"));
quotaTypeList.put(IP_ADDRESS, new QuotaTypes(IP_ADDRESS, "IP_ADDRESS", UsageUnitTypes.IP_MONTH.toString(), "IP Address Usage"));
quotaTypeList.put(NETWORK_BYTES_SENT, new QuotaTypes(NETWORK_BYTES_SENT, "NETWORK_BYTES_SENT", UsageUnitTypes.GB.toString(), "Network Usage (Bytes Sent)"));
quotaTypeList.put(NETWORK_BYTES_RECEIVED, new QuotaTypes(NETWORK_BYTES_RECEIVED, "NETWORK_BYTES_RECEIVED", UsageUnitTypes.GB.toString(), "Network Usage (Bytes Received)"));
quotaTypeList.put(VOLUME, new QuotaTypes(VOLUME, "VOLUME", UsageUnitTypes.GB_MONTH.toString(), "Volume Usage"));
quotaTypeList.put(TEMPLATE, new QuotaTypes(TEMPLATE, "TEMPLATE", UsageUnitTypes.GB_MONTH.toString(), "Template Usage"));
quotaTypeList.put(ISO, new QuotaTypes(ISO, "ISO", UsageUnitTypes.GB_MONTH.toString(), "ISO Usage"));
quotaTypeList.put(SNAPSHOT, new QuotaTypes(SNAPSHOT, "SNAPSHOT", UsageUnitTypes.GB_MONTH.toString(), "Snapshot Usage"));
quotaTypeList.put(SECURITY_GROUP, new QuotaTypes(SECURITY_GROUP, "SECURITY_GROUP", UsageUnitTypes.POLICY_MONTH.toString(), "Security Group Usage"));
quotaTypeList.put(LOAD_BALANCER_POLICY, new QuotaTypes(LOAD_BALANCER_POLICY, "LOAD_BALANCER_POLICY", UsageUnitTypes.POLICY_MONTH.toString(), "Load Balancer Usage"));
quotaTypeList.put(PORT_FORWARDING_RULE, new QuotaTypes(PORT_FORWARDING_RULE, "PORT_FORWARDING_RULE", UsageUnitTypes.POLICY_MONTH.toString(), "Port Forwarding Usage"));
quotaTypeList.put(NETWORK_OFFERING, new QuotaTypes(NETWORK_OFFERING, "NETWORK_OFFERING", UsageUnitTypes.POLICY_MONTH.toString(), "Network Offering Usage"));
quotaTypeList.put(VPN_USERS, new QuotaTypes(VPN_USERS, "VPN_USERS", UsageUnitTypes.POLICY_MONTH.toString(), "VPN users usage"));
quotaTypeList.put(VM_DISK_IO_READ, new QuotaTypes(VM_DISK_IO_READ, "VM_DISK_IO_READ", UsageUnitTypes.IOPS.toString(), "VM Disk usage(I/O Read)"));
quotaTypeList.put(VM_DISK_IO_WRITE, new QuotaTypes(VM_DISK_IO_WRITE, "VM_DISK_IO_WRITE", UsageUnitTypes.IOPS.toString(), "VM Disk usage(I/O Write)"));
quotaTypeList.put(VM_DISK_BYTES_READ, new QuotaTypes(VM_DISK_BYTES_READ, "VM_DISK_BYTES_READ", UsageUnitTypes.BYTES.toString(), "VM Disk usage(Bytes Read)"));
quotaTypeList.put(VM_DISK_BYTES_WRITE, new QuotaTypes(VM_DISK_BYTES_WRITE, "VM_DISK_BYTES_WRITE", UsageUnitTypes.BYTES.toString(), "VM Disk usage(Bytes Write)"));
quotaTypeList.put(VM_SNAPSHOT, new QuotaTypes(VM_SNAPSHOT, "VM_SNAPSHOT", UsageUnitTypes.GB_MONTH.toString(), "VM Snapshot storage usage"));
quotaTypeList.put(VOLUME_SECONDARY, new QuotaTypes(VOLUME_SECONDARY, "VOLUME_SECONDARY", UsageUnitTypes.GB_MONTH.toString(), "Volume secondary storage usage"));
quotaTypeList.put(VM_SNAPSHOT_ON_PRIMARY, new QuotaTypes(VM_SNAPSHOT_ON_PRIMARY, "VM_SNAPSHOT_ON_PRIMARY", UsageUnitTypes.GB_MONTH.toString(), "VM Snapshot primary storage usage"));
quotaTypeList.put(BACKUP, new QuotaTypes(BACKUP, "BACKUP", UsageUnitTypes.GB_MONTH.toString(), "Backup storage usage"));
quotaTypeList.put(BUCKET, new QuotaTypes(BUCKET, "BUCKET", UsageUnitTypes.GB_MONTH.toString(), "Object Store bucket usage"));
quotaTypeList.put(NETWORK, new QuotaTypes(NETWORK, "NETWORK", UsageUnitTypes.COMPUTE_MONTH.toString(), "Network usage"));
quotaTypeList.put(VPC, new QuotaTypes(VPC, "VPC", UsageUnitTypes.COMPUTE_MONTH.toString(), "VPC usage"));
quotaTypeList.put(RUNNING_VM, new QuotaTypes(RUNNING_VM, "RUNNING_VM", UsageUnitTypes.COMPUTE_MONTH.toString(), "Running Vm Usage", VirtualMachine.class));
quotaTypeList.put(ALLOCATED_VM, new QuotaTypes(ALLOCATED_VM, "ALLOCATED_VM", UsageUnitTypes.COMPUTE_MONTH.toString(), "Allocated Vm Usage", VirtualMachine.class));
quotaTypeList.put(IP_ADDRESS, new QuotaTypes(IP_ADDRESS, "IP_ADDRESS", UsageUnitTypes.IP_MONTH.toString(), "IP Address Usage", IpAddress.class));
quotaTypeList.put(NETWORK_BYTES_SENT, new QuotaTypes(NETWORK_BYTES_SENT, "NETWORK_BYTES_SENT", UsageUnitTypes.GB.toString(), "Network Usage (Bytes Sent)", Network.class));
quotaTypeList.put(NETWORK_BYTES_RECEIVED, new QuotaTypes(NETWORK_BYTES_RECEIVED, "NETWORK_BYTES_RECEIVED", UsageUnitTypes.GB.toString(), "Network Usage (Bytes Received)", Network.class));
quotaTypeList.put(VOLUME, new QuotaTypes(VOLUME, "VOLUME", UsageUnitTypes.GB_MONTH.toString(), "Volume Usage", Volume.class));
quotaTypeList.put(TEMPLATE, new QuotaTypes(TEMPLATE, "TEMPLATE", UsageUnitTypes.GB_MONTH.toString(), "Template Usage", VirtualMachineTemplate.class));
quotaTypeList.put(ISO, new QuotaTypes(ISO, "ISO", UsageUnitTypes.GB_MONTH.toString(), "ISO Usage", VirtualMachineTemplate.class));
quotaTypeList.put(SNAPSHOT, new QuotaTypes(SNAPSHOT, "SNAPSHOT", UsageUnitTypes.GB_MONTH.toString(), "Snapshot Usage", Snapshot.class));
quotaTypeList.put(SECURITY_GROUP, new QuotaTypes(SECURITY_GROUP, "SECURITY_GROUP", UsageUnitTypes.POLICY_MONTH.toString(), "Security Group Usage", SecurityGroup.class));
quotaTypeList.put(LOAD_BALANCER_POLICY, new QuotaTypes(LOAD_BALANCER_POLICY, "LOAD_BALANCER_POLICY", UsageUnitTypes.POLICY_MONTH.toString(), "Load Balancer Usage", LoadBalancer.class));
quotaTypeList.put(PORT_FORWARDING_RULE, new QuotaTypes(PORT_FORWARDING_RULE, "PORT_FORWARDING_RULE", UsageUnitTypes.POLICY_MONTH.toString(), "Port Forwarding Usage", PortForwardingRule.class));
quotaTypeList.put(NETWORK_OFFERING, new QuotaTypes(NETWORK_OFFERING, "NETWORK_OFFERING", UsageUnitTypes.POLICY_MONTH.toString(), "Network Offering Usage", NetworkOffering.class));
quotaTypeList.put(VPN_USERS, new QuotaTypes(VPN_USERS, "VPN_USERS", UsageUnitTypes.POLICY_MONTH.toString(), "VPN users usage", VpnUser.class));
quotaTypeList.put(VM_DISK_IO_READ, new QuotaTypes(VM_DISK_IO_READ, "VM_DISK_IO_READ", UsageUnitTypes.IOPS.toString(), "VM Disk usage(I/O Read)", Volume.class));
quotaTypeList.put(VM_DISK_IO_WRITE, new QuotaTypes(VM_DISK_IO_WRITE, "VM_DISK_IO_WRITE", UsageUnitTypes.IOPS.toString(), "VM Disk usage(I/O Write)", Volume.class));
quotaTypeList.put(VM_DISK_BYTES_READ, new QuotaTypes(VM_DISK_BYTES_READ, "VM_DISK_BYTES_READ", UsageUnitTypes.BYTES.toString(), "VM Disk usage(Bytes Read)", Volume.class));
quotaTypeList.put(VM_DISK_BYTES_WRITE, new QuotaTypes(VM_DISK_BYTES_WRITE, "VM_DISK_BYTES_WRITE", UsageUnitTypes.BYTES.toString(), "VM Disk usage(Bytes Write)", Volume.class));
quotaTypeList.put(VM_SNAPSHOT, new QuotaTypes(VM_SNAPSHOT, "VM_SNAPSHOT", UsageUnitTypes.GB_MONTH.toString(), "VM Snapshot storage usage", Snapshot.class));
quotaTypeList.put(VOLUME_SECONDARY, new QuotaTypes(VOLUME_SECONDARY, "VOLUME_SECONDARY", UsageUnitTypes.GB_MONTH.toString(), "Volume secondary storage usage", Volume.class));
quotaTypeList.put(VM_SNAPSHOT_ON_PRIMARY, new QuotaTypes(VM_SNAPSHOT_ON_PRIMARY, "VM_SNAPSHOT_ON_PRIMARY", UsageUnitTypes.GB_MONTH.toString(), "VM Snapshot primary storage usage", Snapshot.class));
quotaTypeList.put(BACKUP, new QuotaTypes(BACKUP, "BACKUP", UsageUnitTypes.GB_MONTH.toString(), "Backup storage usage", BackupOffering.class));
quotaTypeList.put(BUCKET, new QuotaTypes(BUCKET, "BUCKET", UsageUnitTypes.GB_MONTH.toString(), "Object Store bucket usage", Bucket.class));
quotaTypeList.put(NETWORK, new QuotaTypes(NETWORK, "NETWORK", UsageUnitTypes.COMPUTE_MONTH.toString(), "Network usage", Network.class));
quotaTypeList.put(VPC, new QuotaTypes(VPC, "VPC", UsageUnitTypes.COMPUTE_MONTH.toString(), "VPC usage", Vpc.class));
quotaTypeMap = Collections.unmodifiableMap(quotaTypeList);
}
private QuotaTypes(Integer quotaType, String name, String unit, String description) {
private QuotaTypes(Integer quotaType, String name, String unit, String description, Class<?> clazz) {
this.quotaType = quotaType;
this.description = description;
this.quotaName = name;
this.quotaUnit = unit;
this.discriminator = "None";
this.clazz = clazz;
}
public static Map<Integer, QuotaTypes> listQuotaTypes() {
@ -104,22 +118,20 @@ public class QuotaTypes extends UsageTypes {
return null;
}
public Class<?> getClazz() {
return clazz;
}
static public QuotaTypes getQuotaType(int quotaType) {
return quotaTypeMap.get(quotaType);
}
static public QuotaTypes getQuotaTypeByName(String name) {
if (StringUtils.isBlank(name)) {
throw new CloudRuntimeException("Could not retrieve Quota type by name because the value passed as parameter is null, empty, or blank.");
static public Class<?> getClazz(int quotaType) {
QuotaTypes t = quotaTypeMap.get(quotaType);
if (t != null) {
return t.getClazz();
}
for (QuotaTypes type : quotaTypeMap.values()) {
if (type.getQuotaName().equals(name)) {
return type;
}
}
throw new CloudRuntimeException(String.format("Could not find Quota type with name [%s].", name));
return null;
}
@Override

View File

@ -26,6 +26,6 @@ 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);
List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, List<Long> domainIds, Integer usageType, Long resourceId, Long networkId, Long offeringId, Date startDate, Date endDate, Long tariffId);
}

View File

@ -61,7 +61,7 @@ public class QuotaUsageJoinDaoImpl extends GenericDaoBase<QuotaUsageJoinVO, Long
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("domainIds", searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN);
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);
@ -71,11 +71,13 @@ public class QuotaUsageJoinDaoImpl extends GenericDaoBase<QuotaUsageJoinVO, Long
}
@Override
public List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, Long domainId, Integer usageType, Long resourceId, Long networkId, Long offeringId, Date startDate, Date endDate, Long tariffId) {
public List<QuotaUsageJoinVO> findQuotaUsage(Long accountId, List<Long> domainIds, 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);
if (domainIds != null) {
sc.setParameters("domainIds", domainIds.toArray());
}
sc.setParametersIfNotNull("usageType", usageType);
sc.setParametersIfNotNull("resourceId", resourceId);
sc.setParametersIfNotNull("networkId", networkId);

View File

@ -22,6 +22,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -33,7 +34,9 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.Tariff;
import org.apache.cloudstack.quota.activationrule.presetvariables.Value;
import org.apache.cloudstack.quota.constant.QuotaTypes;
import org.apache.cloudstack.quota.dao.QuotaTariffDao;
import org.apache.cloudstack.quota.dao.QuotaTariffUsageDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.usage.UsageUnitTypes;
@ -89,6 +92,9 @@ public class QuotaManagerImplTest {
@Mock
PresetVariables presetVariablesMock;
@Mock
QuotaTariffUsageDao quotaTariffUsageDaoMock;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Test
@ -484,47 +490,49 @@ public class QuotaManagerImplTest {
}
@Test
public void aggregateQuotaTariffsValuesTestTariffsWereNotInPeriodToBeAppliedReturnZero() {
public void aggregateQuotaTariffsValuesTestTariffsWereNotInPeriodToBeAppliedReturnEmptyMap() {
List<QuotaTariffVO> tariffs = createTariffList();
Mockito.doReturn(false).when(quotaManagerImplSpy).isQuotaTariffInPeriodToBeApplied(Mockito.any(), Mockito.any(), Mockito.anyString());
BigDecimal result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, jsInterpreterMock, "");
Map<QuotaTariffVO, BigDecimal> result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, jsInterpreterMock, "");
Assert.assertEquals(BigDecimal.ZERO, result);
Assert.assertTrue(result.isEmpty());
}
@Test
public void aggregateQuotaTariffsValuesTestTariffsIsEmptyReturnZero() {
BigDecimal result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, new ArrayList<>(), false, jsInterpreterMock, "");
public void aggregateQuotaTariffsValuesTestTariffsIsEmptyReturnEmptyMap() {
Map<QuotaTariffVO, BigDecimal> result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, new ArrayList<>(), false, jsInterpreterMock, "");
Assert.assertEquals(BigDecimal.ZERO, result);
Assert.assertTrue(result.isEmpty());
}
@Test
public void aggregateQuotaTariffsValuesTestTariffsAreInPeriodToBeAppliedReturnAggregation() {
public void aggregateQuotaTariffsValuesTestTariffsAreInPeriodToBeAppliedReturnTariffsWithTheirValues() {
List<QuotaTariffVO> tariffs = createTariffList();
Mockito.doReturn(true, false, true).when(quotaManagerImplSpy).isQuotaTariffInPeriodToBeApplied(Mockito.any(), Mockito.any(), Mockito.anyString());
Mockito.doReturn(BigDecimal.TEN).when(quotaManagerImplSpy).getQuotaTariffValueToBeApplied(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
BigDecimal result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, jsInterpreterMock, "");
Map<QuotaTariffVO, BigDecimal> result = quotaManagerImplSpy.aggregateQuotaTariffsValues(usageVoMock, tariffs, false, jsInterpreterMock, "");
Assert.assertEquals(BigDecimal.TEN.multiply(new BigDecimal(2)), result);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.values().stream().allMatch(value -> value.equals(BigDecimal.TEN)));
}
@Test
public void persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsagesTestReturnOnlyPersistedQuotaUsageVo() {
List<Pair<UsageVO, QuotaUsageVO>> listPair = new ArrayList<>();
Map<UsageVO, Pair<QuotaUsageVO, List<QuotaTariffUsageVO>>> mapUsageQuotaUsage = new LinkedHashMap<>();
QuotaUsageVO quotaUsageVoMock1 = Mockito.mock(QuotaUsageVO.class);
QuotaUsageVO quotaUsageVoMock2 = Mockito.mock(QuotaUsageVO.class);
listPair.add(new Pair<>(new UsageVO(), quotaUsageVoMock1));
listPair.add(new Pair<>(new UsageVO(), null));
listPair.add(new Pair<>(new UsageVO(), quotaUsageVoMock2));
mapUsageQuotaUsage.put(new UsageVO(), new Pair<>(quotaUsageVoMock1, new ArrayList<>()));
mapUsageQuotaUsage.put(new UsageVO(), null);
mapUsageQuotaUsage.put(new UsageVO(), new Pair<>(quotaUsageVoMock2, new ArrayList<>()));
Mockito.doReturn(null).when(usageDaoMock).persistUsage(Mockito.any());
Mockito.doReturn(null).when(quotaUsageDaoMock).persistQuotaUsage(Mockito.any());
Mockito.doNothing().when(quotaManagerImplSpy).persistQuotaTariffUsages(Mockito.any(), Mockito.any());
List<QuotaUsageVO> result = quotaManagerImplSpy.persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(listPair);
List<QuotaUsageVO> result = quotaManagerImplSpy.persistUsagesAndQuotaUsagesAndRetrievePersistedQuotaUsages(mapUsageQuotaUsage);
Assert.assertEquals(2, result.size());
Assert.assertEquals(quotaUsageVoMock1, result.get(0));
@ -551,4 +559,41 @@ public class QuotaManagerImplTest {
return lastTariffs;
}
@Test
public void createQuotaTariffUsageTestTariffValueIsNotZeroReturnDetailedVo() {
Mockito.doReturn(1).when(usageVoMock).getUsageType();
Mockito.doReturn(BigDecimal.TEN).when(quotaManagerImplSpy).getUsageValueAccordingToUsageUnitType(Mockito.any(), Mockito.any(), Mockito.any());
Mockito.doReturn(1L).when(quotaTariffVoMock).getId();
QuotaTariffUsageVO result = quotaManagerImplSpy.createQuotaTariffUsage(usageVoMock, quotaTariffVoMock, BigDecimal.TEN);
Assert.assertEquals(1L, result.getTariffId().longValue());
Assert.assertEquals(BigDecimal.TEN, result.getQuotaUsed());
}
@Test
public void persistQuotaTariffUsagesTestZeroQuotaTariffUsagesZeroPersisted() {
List<QuotaTariffUsageVO> quotaTariffUsages = new ArrayList<>();
quotaManagerImplSpy.persistQuotaTariffUsages(quotaTariffUsages, 1L);
Mockito.verify(quotaTariffUsageDaoMock, Mockito.never()).persistQuotaTariffUsage(Mockito.any());
}
@Test
public void persistQuotaTariffUsagesTestTwoQuotaUsageDetailsTwoPersisted() {
List<QuotaTariffUsageVO> quotaUsageDetails = new ArrayList<>();
QuotaTariffUsageVO quotaUsageDetailVoMock1 = Mockito.mock(QuotaTariffUsageVO.class);
QuotaTariffUsageVO quotaUsageDetailVoMock2 = Mockito.mock(QuotaTariffUsageVO.class);
quotaUsageDetails.add(quotaUsageDetailVoMock1);
quotaUsageDetails.add(quotaUsageDetailVoMock2);
Mockito.doNothing().when(quotaTariffUsageDaoMock).persistQuotaTariffUsage(Mockito.any());
quotaManagerImplSpy.persistQuotaTariffUsages(quotaUsageDetails, 1L);
Mockito.verify(quotaTariffUsageDaoMock, Mockito.times(2)).persistQuotaTariffUsage(Mockito.any());
}
}

View File

@ -0,0 +1,118 @@
//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.command;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
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.QuotaResourceStatementResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import javax.inject.Inject;
import java.util.Date;
@APICommand(name = "quotaResourceStatement", responseObject = QuotaResourceStatementResponse.class, since = "4.23.0.0",
description = "Generates a detailed Quota statement for a specific resource.", requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false)
public class QuotaResourceStatementCmd extends BaseCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.STRING, required = true, description = "ID of the resource.")
private String id;
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class,
description = "Generate the statement for this Account. A resource may have belonged to different owners at distinct points in time, " +
"so this parameter can be used to only consider the period for which it belonged to a specific Account.")
private Long accountId;
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
description = "Generate the statement for this Project. A resource may have belonged to different owners at distinct points in time, " +
"so this parameter can be used to only consider the period for which it belonged to a specific Project.")
private Long projectId;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
description = "ID of the Domain for which the Quota statement will be generated.")
private Long domainId;
@Parameter(name = ApiConstants.USAGE_TYPE, type = CommandType.INTEGER, required = true, description = "Usage type of the Quota usage.")
private Integer usageType;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = true, description = "Start date for the query. " +
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = true, description = "End date for the query. " +
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
private Date endDate;
@Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Whether to include usage records from children of the filtered domain. "
+ " Defaults to false.")
private Boolean recursive;
@Inject
QuotaResponseBuilder responseBuilder;
@Override
public void execute() {
QuotaResourceStatementResponse response = responseBuilder.createQuotaResourceStatement(this);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public long getEntityOwnerId() {
if (ObjectUtils.allNull(accountId, projectId)) {
return -1;
}
return _accountService.finalizeAccountId(accountId, null, null, projectId);
}
public String getId() {
return id;
}
public Integer getUsageType() {
return usageType;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Long getAccountId() {
return accountId;
}
public Long getDomainId() {
return domainId;
}
public boolean isRecursive() {
return BooleanUtils.isTrue(recursive);
}
}

View File

@ -20,7 +20,6 @@ import java.util.Date;
import javax.inject.Inject;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
@ -32,18 +31,17 @@ import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
import org.apache.cloudstack.api.response.QuotaStatementResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
@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 {
@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, 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;
@ -60,16 +58,18 @@ public class QuotaStatementCmd extends BaseCmd {
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 = "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.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Whether to include usage records from children of the filtered domain. "
+ " Defaults to false.")
private Boolean recursive;
@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;
@ -152,4 +152,9 @@ public class QuotaStatementCmd extends BaseCmd {
response.setResponseName(getCommandName());
setResponseObject(response);
}
public boolean isRecursive() {
return BooleanUtils.isTrue(recursive);
}
}

View File

@ -0,0 +1,82 @@
//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 java.util.Date;
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 QuotaResourceStatementItemResponse extends BaseResponse {
@SerializedName(ApiConstants.TARIFF_ID)
@Param(description = "ID of the tariff.")
private String tariffId;
@SerializedName(ApiConstants.TARIFF_NAME)
@Param(description = "Name of the tariff.")
private String tariffName;
@SerializedName(ApiConstants.QUOTA_CONSUMED)
@Param(description = "Amount of quota used.")
private BigDecimal quotaUsed;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "Item's start date.")
private Date startDate;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "Item's end date.")
private Date endDate;
@SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "UUID of the resource's owner.")
private String accountId;
public QuotaResourceStatementItemResponse() {
super("quotaresourcestatementitem");
}
public void setTariffId(String tariffId) {
this.tariffId = tariffId;
}
public void setTariffName(String tariffName) {
this.tariffName = tariffName;
}
public void setQuotaUsed(BigDecimal quotaUsed) {
this.quotaUsed = quotaUsed;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@ -0,0 +1,66 @@
//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 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.util.List;
public class QuotaResourceStatementResponse extends BaseResponse {
@SerializedName(ApiConstants.USAGE_NAME)
@Param(description = "Name of the usage type.")
private String usageName;
@SerializedName(ApiConstants.UNIT)
@Param(description = "Unit of the usage type.")
private String unit;
@SerializedName(ApiConstants.ITEMS)
@Param(description = "List of Quota tariff usages.", responseObject = QuotaResourceStatementItemResponse.class)
private List<QuotaResourceStatementItemResponse> items;
@SerializedName(ApiConstants.TOTAL_QUOTA)
@Param(description = "Total amount of quota used.")
private BigDecimal totalQuotaUsed;
public QuotaResourceStatementResponse() {
super("quotaresourcestatement");
}
public void setUsageName(String usageName) {
this.usageName = usageName;
}
public void setUnit(String unit) {
this.unit = unit;
}
public void setQuotaUsageDetails(List<QuotaResourceStatementItemResponse> items) {
this.items = items;
}
public void setTotalQuotaUsed(BigDecimal totalQuotaUsed) {
this.totalQuotaUsed = totalQuotaUsed;
}
}

View File

@ -23,6 +23,7 @@ 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.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaResourceStatementCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
@ -83,4 +84,6 @@ public interface QuotaResponseBuilder {
Pair<List<QuotaCreditsResponse>, Integer> createQuotaCreditsListResponse(QuotaCreditsListCmd cmd);
QuotaValidateActivationRuleResponse validateActivationRule(QuotaValidateActivationRuleCmd cmd);
QuotaResourceStatementResponse createQuotaResourceStatement(QuotaResourceStatementCmd cmd);
}

View File

@ -66,11 +66,14 @@ import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
@ -78,6 +81,7 @@ 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.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaResourceStatementCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
@ -107,16 +111,20 @@ import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao;
import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao;
import org.apache.cloudstack.quota.dao.QuotaSummaryDao;
import org.apache.cloudstack.quota.dao.QuotaTariffDao;
import org.apache.cloudstack.quota.dao.QuotaTariffUsageDao;
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.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import org.apache.cloudstack.quota.vo.QuotaTariffUsageVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
import org.apache.cloudstack.quota.vo.QuotaUsageResourceVO;
import org.apache.cloudstack.usage.UsageTypes;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections4.CollectionUtils;
@ -181,7 +189,12 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
private VMTemplateDao vmTemplateDao;
@Inject
private VolumeDao volumeDao;
@Inject
private QuotaUsageJoinDao quotaUsageJoinDao;
@Inject
private QuotaTariffUsageDao quotaTariffUsageDao;
@Inject
private EntityManager entityMgr;
private final Class<?>[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class, ResourceCounting.class};
@ -342,9 +355,9 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Override
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());
Long accountId = getAccountIdForQuotaStatement(cmd.getEntityOwnerId(), null);
Pair<Long, List<Long>> baseDomainAndFilteredDomains = getDomainIdsForQuotaStatement(accountId, cmd.getDomainId(), cmd.isRecursive());
List<QuotaUsageJoinVO> quotaUsages = _quotaService.getQuotaUsage(accountId, null, baseDomainAndFilteredDomains.second(), 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"));
@ -367,52 +380,16 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
Account account = _accountDao.findByIdIncludingRemoved(accountId);
statement.setAccountId(account.getUuid());
statement.setAccountName(account.getAccountName());
domainId = account.getDomainId();
}
if (domainId != null) {
DomainVO domain = domainDao.findByIdIncludingRemoved(domainId);
Long baseDomainId = baseDomainAndFilteredDomains.first();
if (baseDomainId != null) {
DomainVO domain = domainDao.findByIdIncludingRemoved(baseDomainId);
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);
@ -1158,6 +1135,184 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
return createValidateActivationRuleResponse(activationRule, quotaName, false, message);
}
@Override
public QuotaResourceStatementResponse createQuotaResourceStatement(QuotaResourceStatementCmd cmd) {
String resourceUuid = cmd.getId();
Integer usageType = cmd.getUsageType();
Date startDate = cmd.getStartDate();
Date endDate = cmd.getEndDate();
if (startDate.after(endDate)) {
throw new InvalidParameterValueException(String.format("The start date [%s] must be before the end date [%s].", startDate, endDate));
}
InternalIdentity resource = retrieveResource(resourceUuid, usageType);
if (resource == null) {
logger.error("Could not find resource [{}] of type [{}]. Returning an empty list.", resourceUuid, usageType);
return createQuotaResourceStatementResponse(resourceUuid, usageType, new ArrayList<>(), new BigDecimal(0));
}
long id = resource.getId();
Long currentOwnerId = null;
if (resource instanceof ControlledEntity) {
currentOwnerId = ((ControlledEntity) resource).getAccountId();
}
Long accountId = getAccountIdForQuotaStatement(cmd.getEntityOwnerId(), currentOwnerId);
Pair<Long, List<Long>> baseDomainAndFilteredDomains = getDomainIdsForQuotaStatement(accountId, cmd.getDomainId(), cmd.isRecursive());
Long resourceId = null;
Long networkId = null;
Long offeringId = null;
switch (usageType) {
case UsageTypes.NETWORK_OFFERING:
case UsageTypes.BACKUP:
offeringId = id;
break;
case UsageTypes.NETWORK_BYTES_RECEIVED:
case UsageTypes.NETWORK_BYTES_SENT:
networkId = id;
break;
default:
resourceId = id;
}
logger.debug("Attempting to find quota usages with parameters usage type [{}], usage id [{}], network id [{}], offering id [{}], and between [{}] and [{}].",
usageType, resourceId, networkId, offeringId, startDate, endDate);
List<QuotaUsageJoinVO> quotaUsageJoinList = quotaUsageJoinDao.findQuotaUsage(accountId, baseDomainAndFilteredDomains.second(), usageType, resourceId, networkId, offeringId, startDate, endDate, null);
logger.debug("Found [{}] quota usages using as parameter usage type [{}], usage id [{}], network id [{}], offering id [{}], and between [{}] and [{}].",
quotaUsageJoinList.size(), usageType, resourceId, networkId, offeringId, startDate, endDate);
List<QuotaResourceStatementItemResponse> quotaResourceStatementItemResponseList = new ArrayList<>();
BigDecimal totalQuotaUsed = new BigDecimal(0);
List<QuotaTariffVO> quotaTariffs = _quotaTariffDao.listQuotaTariffs(null, null, usageType, null, null, true, null, null).first();
for (QuotaUsageJoinVO quotaUsageJoin : quotaUsageJoinList) {
Account account = _accountMgr.getAccount(quotaUsageJoin.getAccountId());
String accountUuid = account.getUuid();
List<QuotaTariffUsageVO> quotaTariffUsageList = quotaTariffUsageDao.listQuotaTariffUsages(quotaUsageJoin.getId());
logger.debug("Found [{}] quota tariff usages associated to the quota usage [{}] of resource [{}] and type [{}] between [{}] and [{}].",
quotaTariffUsageList.size(), quotaUsageJoin, resourceUuid, usageType, startDate, endDate);
for (QuotaTariffUsageVO quotaTariffUsage: quotaTariffUsageList) {
quotaResourceStatementItemResponseList.add(createQuotaResourceStatementItemResponse(quotaTariffUsage, quotaTariffs, quotaUsageJoin.getStartDate(),
quotaUsageJoin.getEndDate(), accountUuid));
totalQuotaUsed = totalQuotaUsed.add(quotaTariffUsage.getQuotaUsed());
}
}
logger.debug("The total quota used of type [{}] between [{}] and [{}] for the resource [{}] was [{}].", usageType, startDate, endDate, resourceUuid, totalQuotaUsed);
return createQuotaResourceStatementResponse(resourceUuid, usageType, quotaResourceStatementItemResponseList, totalQuotaUsed);
}
protected Long getAccountIdForQuotaStatement(long providedAccountId, Long fallbackAccountId) {
Account caller = CallContext.current().getCallingAccount();
if (providedAccountId != -1L) {
Account account = _accountDao.findByIdIncludingRemoved(providedAccountId);
_accountMgr.checkAccess(caller, null, false, account);
logger.debug("Limiting the Quota resource statement for the provided Account [{}].", providedAccountId);
return providedAccountId;
}
Account.Type callerType = caller.getType();
if (Account.Type.ADMIN.equals(callerType) || Account.Type.DOMAIN_ADMIN.equals(callerType)) {
logger.debug("Not limiting the Quota resource statement for a specific Account, as no specific Account was provided and the caller is either an admin or domain admin.");
return null;
}
if (fallbackAccountId != null) {
Account fallbackAccount = _accountDao.findByIdIncludingRemoved(fallbackAccountId);
_accountMgr.checkAccess(caller, null, false, fallbackAccount);
logger.debug("Limiting the Quota statement for the fallback Account [{}], as no specific Account was provided.", fallbackAccountId);
return fallbackAccountId;
}
logger.debug("Limiting the Quota resource statement for the calling account, as no specific Account was provided and no fallback account was provided.");
return caller.getAccountId();
}
protected Pair<Long, List<Long>> getDomainIdsForQuotaStatement(Long finalAccountId, Long providedDomainId, boolean isRecursive) {
if (finalAccountId != null) {
// Access to the provided account has already been validated
logger.debug("Not limiting the Quota statement for a specific Domain, as we are already limiting by Account.");
return new Pair<>(null, null);
}
// User accounts will have already been limited to themselves
Account caller = CallContext.current().getCallingAccount();
Long domainId = providedDomainId;
if (domainId != null) {
Domain domain = domainDao.findByIdIncludingRemoved(domainId);
_accountMgr.checkAccess(caller, domain);
logger.debug("Limiting the Quota statement for the provided Domain [{}].", domainId);
} else {
domainId = caller.getDomainId();
logger.debug("Limiting the Quota statement for the caller's Domain [{}], as no 'domainid' was provided.", domainId);
}
if (isRecursive) {
logger.debug("Allowing the Quota statement for the Domain's children, as the 'isrecursive' parameter was provided.");
return new Pair<>(domainId, domainDao.getDomainAndChildrenIds(domainId));
}
return new Pair<>(domainId, List.of(domainId));
}
protected InternalIdentity retrieveResource(String resourceUuid, Integer usageType) {
Class<?> clazz = QuotaTypes.getClazz(usageType);
if (clazz == null) {
throw new InvalidParameterValueException(String.format("Invalid usage type [%s] provided.", usageType));
}
logger.debug("Attempting to find a resource with ID [{}] and of type [{}].", resourceUuid, usageType);
Object object = entityMgr.findByUuidIncludingRemoved(clazz, resourceUuid);
if (object == null) {
return null;
}
return (InternalIdentity) object;
}
protected QuotaResourceStatementItemResponse createQuotaResourceStatementItemResponse(QuotaTariffUsageVO quotaTariffUsage, List<QuotaTariffVO> quotaTariffs,
Date startDate, Date endDate, String accountUuid) {
logger.trace("Creating quota resource statement item associated to quota tariff usage [{}].", quotaTariffUsage);
QuotaResourceStatementItemResponse quotaResourceStatementItemResponse = new QuotaResourceStatementItemResponse();
QuotaTariffVO quotaTariff = quotaTariffs.stream().filter(quotaTariffVO -> quotaTariffUsage.getTariffId().equals(quotaTariffVO.getId())).findAny().orElse(null);
quotaResourceStatementItemResponse.setQuotaUsed(quotaTariffUsage.getQuotaUsed());
quotaResourceStatementItemResponse.setStartDate(startDate);
quotaResourceStatementItemResponse.setEndDate(endDate);
quotaResourceStatementItemResponse.setAccountId(accountUuid);
if (quotaTariff != null) {
logger.trace("Quota usage details item will be associated to the quota tariff [{}].", quotaTariff);
quotaResourceStatementItemResponse.setTariffId(quotaTariff.getUuid());
quotaResourceStatementItemResponse.setTariffName(quotaTariff.getName());
}
return quotaResourceStatementItemResponse;
}
protected QuotaResourceStatementResponse createQuotaResourceStatementResponse(String resourceUuid, Integer usageType,
List<QuotaResourceStatementItemResponse> quotaUsageDetailsItems, BigDecimal totalQuotaUsed) {
logger.trace("Creating quota usage details list response associated to the resource of UUID [{}], with an usage type of [{}], [{}] quota" +
" usage details items, and a total quota used of [{}].", resourceUuid, usageType, quotaUsageDetailsItems.size(), totalQuotaUsed);
QuotaResourceStatementResponse quotaResourceStatementResponse = new QuotaResourceStatementResponse();
QuotaTypes quotaType = QuotaTypes.getQuotaType(usageType);
quotaResourceStatementResponse.setUsageName(quotaType.getQuotaName());
quotaResourceStatementResponse.setUnit(quotaType.getQuotaUnit());
quotaResourceStatementResponse.setQuotaUsageDetails(quotaUsageDetailsItems);
quotaResourceStatementResponse.setTotalQuotaUsed(totalQuotaUsed);
return quotaResourceStatementResponse;
}
/**
* Checks whether script variables are compatible with the usage type. First, we remove all script variables that correspond to the script's usage type variables.
* Then, returns true if none of the remaining script variables match any usage types variables, and false otherwise.

View File

@ -31,11 +31,11 @@ public class QuotaStatementResponse extends BaseResponse {
@Param(description = "ID of the Account.")
private String accountId;
@SerializedName(ApiConstants.ACCOUNT)
@SerializedName(ApiConstants.ACCOUNT_NAME)
@Param(description = "Name of the Account.")
private String accountName;
@SerializedName(ApiConstants.DOMAIN)
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "ID of the Domain.")
private String domainId;

View File

@ -28,7 +28,7 @@ import com.cloud.utils.component.PluggableService;
public interface QuotaService extends PluggableService {
List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate);
List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, List<Long> domainIds, Integer usageType, Date startDate, Date endDate);
List<QuotaBalanceVO> listQuotaBalancesForAccount(Long accountId, Date startDate, Date endDate);

View File

@ -38,6 +38,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaEnabledCmd;
import org.apache.cloudstack.api.command.QuotaListEmailConfigurationCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaResourceStatementCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
@ -131,6 +132,7 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
cmdList.add(QuotaListEmailConfigurationCmd.class);
cmdList.add(QuotaPresetVariablesListCmd.class);
cmdList.add(QuotaValidateActivationRuleCmd.class);
cmdList.add(QuotaResourceStatementCmd.class);
return cmdList;
}
@ -203,15 +205,15 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
}
@Override
public List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate) {
public List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, List<Long> domainIds, Integer usageType, Date startDate, Date endDate) {
if (startDate.after(endDate)) {
throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate);
}
logger.debug("Getting quota records of type [{}] for account [{}] in domain [{}], between [{}] and [{}].",
usageType, accountId, domainId, startDate, endDate);
logger.debug("Getting quota records of type [{}] for account [{}] in domains [{}], between [{}] and [{}].",
usageType, accountId, domainIds, startDate, endDate);
return quotaUsageJoinDao.findQuotaUsage(accountId, domainId, usageType, null, null, null, startDate, endDate, null);
return quotaUsageJoinDao.findQuotaUsage(accountId, domainIds, usageType, null, null, null, startDate, endDate, null);
}
@Override

View File

@ -38,14 +38,15 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.AccountManager;
import com.cloud.user.UserVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
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;
@ -156,7 +157,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Date date = new Date();
@Mock
Account accountMock;
AccountVO accountMock;
@Mock
DomainVO domainVoMock;
@ -188,6 +189,9 @@ public class QuotaResponseBuilderImplTest extends TestCase {
@Mock
User callerUserMock;
@Mock
EntityManager entityManagerMock;
@Before
public void setup() {
CallContext.register(callerUserMock, callerAccountMock);
@ -1024,107 +1028,161 @@ public class QuotaResponseBuilderImplTest extends TestCase {
}
@Test
public void getAccountIdForQuotaStatementTestLimitsToCallingAccountForNormalUser() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
public void getAccountIdForQuotaStatementTestReturnsProvidedAccount() {
long providedAccountId = 200L;
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(Account.Type.NORMAL).when(accountMock).getType();
Mockito.when(accountDaoMock.findByIdIncludingRemoved(providedAccountId)).thenReturn(accountMock);
Mockito.doNothing().when(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(providedAccountId, null);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertEquals(Long.valueOf(callerAccountMock.getAccountId()), result);
}
Assert.assertEquals(200L, result);
Mockito.verify(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
}
@Test
public void getAccountIdForQuotaStatementTestReturnsEntityOwnerIdWhenProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
public void getAccountIdForQuotaStatementTestReturnsNullWhenCallerIsAdminWithoutProvidedAccount() {
Mockito.when(callerAccountMock.getType()).thenReturn(Account.Type.ADMIN);
Mockito.doReturn(42L).when(cmd).getEntityOwnerId();
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(-1L, null);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertEquals(Long.valueOf(42L), result);
assertNull(result);
Mockito.verify(accountManagerMock, Mockito.never()).getAccount(Mockito.anyLong());
}
@Test
public void getAccountIdForQuotaStatementTestLimitsToCallingAccountWhenCallerIsAdminAndDomainIsNotProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
public void getAccountIdForQuotaStatementTestReturnsNullWhenCallerIsDomainAdminWithoutProvidedAccount() {
Mockito.when(callerAccountMock.getType()).thenReturn(Account.Type.DOMAIN_ADMIN);
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();
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(-1L, null);
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);
}
assertNull(result);
Mockito.verify(accountManagerMock, Mockito.never()).getAccount(Mockito.anyLong());
}
@Test
public void getAccountIdForQuotaStatementTestReturnsNullWhenCallerIsAdminAndDomainIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
public void getAccountIdForQuotaStatementTestReturnsFallbackAccountWhenNoAccountProvidedAndCallerIsNotAdmin() {
Mockito.when(callerAccountMock.getType()).thenReturn(Account.Type.NORMAL);
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();
Mockito.when(accountDaoMock.findByIdIncludingRemoved(300L)).thenReturn(accountMock);
Mockito.doNothing().when(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(-1L, 300L);
Long result = quotaResponseBuilderSpy.getAccountIdForQuotaStatement(cmd);
Assert.assertNull(result);
}
assertEquals(300L, result);
Mockito.verify(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
}
@Test
public void getDomainIdForQuotaStatementTestReturnsAccountDomainIdWhenAccountIdIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
AccountVO account = Mockito.mock(AccountVO.class);
public void getAccountIdForQuotaStatementTestAccessDeniedForProvidedAccount() {
Mockito.when(accountDaoMock.findByIdIncludingRemoved(200L)).thenReturn(accountMock);
Mockito.doThrow(new PermissionDeniedException("Access denied"))
.when(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
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);
Assert.assertThrows(PermissionDeniedException.class,
() -> quotaResponseBuilderSpy.getAccountIdForQuotaStatement(200L, null));
Mockito.verify(accountManagerMock).checkAccess(callerAccountMock, null, false, accountMock);
}
@Test
public void getDomainIdForQuotaStatementTestReturnsProvidedDomainIdWhenAccountIdIsNull() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
public void getDomainIdsForQuotaStatementTestReturnsNullPairWhenAccountIsProvided() {
Pair<Long, List<Long>> result = quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(100L, null, false);
Mockito.doReturn(99L).when(cmd).getDomainId();
Long result = quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
Assert.assertEquals(Long.valueOf(99L), result);
assertNull(result.first());
assertNull(result.second());
Mockito.verify(domainDaoMock, Mockito.never()).findByIdIncludingRemoved(Mockito.anyLong());
}
@Test
public void getDomainIdForQuotaStatementTestFallsBackToCallingAccountDomainIdWhenNeitherAccountNorDomainIsProvided() {
QuotaStatementCmd cmd = Mockito.mock(QuotaStatementCmd.class);
Account account = Mockito.mock(Account.class);
public void getDomainIdsForQuotaStatementTestReturnsProvidedDomainIdNonRecursively() {
Mockito.when(domainDaoMock.findByIdIncludingRemoved(5L)).thenReturn(domainVoMock);
Mockito.doNothing().when(accountManagerMock).checkAccess(callerAccountMock, domainVoMock);
Mockito.doReturn(null).when(cmd).getDomainId();
Mockito.doReturn(123L).when(account).getDomainId();
Mockito.doReturn(account).when(callContextMock).getCallingAccount();
Pair<Long, List<Long>> result = quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(null, 5L, false);
try (MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
assertEquals(5L, (long) result.first());
assertEquals(List.of(5L), result.second());
Mockito.verify(accountManagerMock).checkAccess(callerAccountMock, domainVoMock);
}
Long result = quotaResponseBuilderSpy.getDomainIdForQuotaStatement(cmd, null);
@Test
public void getDomainIdsForQuotaStatementTestReturnsCallerDomainNonRecursively() {
Mockito.when(callerAccountMock.getDomainId()).thenReturn(7L);
Assert.assertEquals(123L, result.longValue());
}
Pair<Long, List<Long>> result = quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(null, null, false);
assertEquals(7L, (long) result.first());
assertEquals(List.of(7L), result.second());
Mockito.verify(domainDaoMock, Mockito.never()).findByIdIncludingRemoved(Mockito.anyLong());
}
@Test
public void getDomainIdsForQuotaStatementTestReturnsProvidedDomainRecursively() {
List<Long> domainAndChildren = List.of(5L, 10L, 15L);
Mockito.when(domainDaoMock.findByIdIncludingRemoved(5L)).thenReturn(domainVoMock);
Mockito.when(domainDaoMock.getDomainAndChildrenIds(5L)).thenReturn(domainAndChildren);
Mockito.doNothing().when(accountManagerMock).checkAccess(callerAccountMock, domainVoMock);
Pair<Long, List<Long>> result = quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(null, 5L, true);
assertEquals(5L, (long) result.first());
assertEquals(domainAndChildren, result.second());
Mockito.verify(domainDaoMock).getDomainAndChildrenIds(5L);
}
@Test
public void getDomainIdsForQuotaStatementReturnsCallerDomainRecursively() {
List<Long> domainAndChildren = List.of(1L, 2L, 3L);
Mockito.when(callerAccountMock.getDomainId()).thenReturn(1L);
Mockito.when(domainDaoMock.getDomainAndChildrenIds(1L)).thenReturn(domainAndChildren);
Pair<Long, List<Long>> result = quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(null, null, true);
assertEquals(1L, (long) result.first());
assertEquals(domainAndChildren, result.second());
Mockito.verify(domainDaoMock).getDomainAndChildrenIds(1L);
}
@Test
public void getDomainIdsForQuotaStatementTestThrowsAccessDeniedForProvidedDomain() {
Mockito.when(domainDaoMock.findByIdIncludingRemoved(5L)).thenReturn(domainVoMock);
Mockito.doThrow(new PermissionDeniedException("Access denied"))
.when(accountManagerMock).checkAccess(callerAccountMock, domainVoMock);
Assert.assertThrows(PermissionDeniedException.class,
() -> quotaResponseBuilderSpy.getDomainIdsForQuotaStatement(null, 5L, false));
Mockito.verify(accountManagerMock).checkAccess(callerAccountMock, domainVoMock);
}
@Test(expected = InvalidParameterValueException.class)
public void retrieveResourceTestThrowsExceptionForInvalidUsageType() {
Integer invalidUsageType = 999;
quotaResponseBuilderSpy.retrieveResource("validUuid", invalidUsageType);
}
@Test
public void retrieveResourceTestReturnsNullForNonexistentResource() {
String invalidUuid = "nonexistentUuid";
Integer validUsageType = QuotaTypes.ALLOCATED_VM;
Mockito.doReturn(null).when(entityManagerMock).findByUuidIncludingRemoved(Mockito.any(), Mockito.eq(invalidUuid));
InternalIdentity result = quotaResponseBuilderSpy.retrieveResource(invalidUuid, validUsageType);
Assert.assertNull(result);
}
@Test
public void retrieveResourceTestReturnsCorrectResource() {
String validUuid = "validUuid";
Integer validUsageType = QuotaTypes.ALLOCATED_VM;
InternalIdentity mockResource = Mockito.mock(InternalIdentity.class);
Mockito.doReturn(mockResource).when(entityManagerMock).findByUuidIncludingRemoved(Mockito.any(), Mockito.eq(validUuid));
InternalIdentity result = quotaResponseBuilderSpy.retrieveResource(validUuid, validUsageType);
Assert.assertNotNull(result);
Assert.assertEquals(mockResource, result);
}
}

View File

@ -117,12 +117,12 @@ public class QuotaServiceImplTest extends TestCase {
public void testGetQuotaUsage() {
final long accountId = 2L;
final String accountName = "admin123";
final long domainId = 1L;
final List<Long> domainIds = List.of(1L);
final Date startDate = new DateTime().minusDays(2).toDate();
final Date endDate = new Date();
quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
Mockito.verify(quotaUsageJoinDaoMock, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(),
quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainIds, QuotaTypes.IP_ADDRESS, startDate, endDate);
Mockito.verify(quotaUsageJoinDaoMock, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainIds), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(),
Mockito.any(), Mockito.any(), Mockito.any(Date.class), Mockito.any(Date.class), Mockito.any());
}