diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 5365e14230c..742d2f42abf 100644 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -273,6 +273,7 @@ public class ApiConstants { public static final String VIRTUAL_MACHINE_ID_IP = "vmidipmap"; public static final String VIRTUAL_MACHINE_COUNT = "virtualmachinecount"; public static final String USAGE_ID = "usageid"; + public static final String USAGE_TYPE = "usagetype"; public static final String VLAN = "vlan"; public static final String VLAN_RANGE = "vlanrange"; diff --git a/api/src/org/apache/cloudstack/api/BaseCmd.java b/api/src/org/apache/cloudstack/api/BaseCmd.java index ad3a88c2749..360b277d897 100644 --- a/api/src/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/org/apache/cloudstack/api/BaseCmd.java @@ -95,7 +95,7 @@ public abstract class BaseCmd { GET, POST, PUT, DELETE } public static enum CommandType { - BOOLEAN, DATE, FLOAT, INTEGER, SHORT, LIST, LONG, OBJECT, MAP, STRING, TZDATE, UUID + BOOLEAN, DATE, FLOAT, DOUBLE, INTEGER, SHORT, LIST, LONG, OBJECT, MAP, STRING, TZDATE, UUID } private Object _responseObject; diff --git a/api/src/org/apache/cloudstack/api/command/admin/usage/GetUsageRecordsCmd.java b/api/src/org/apache/cloudstack/api/command/admin/usage/GetUsageRecordsCmd.java index fd8173d416d..4cceb3b5f9f 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/usage/GetUsageRecordsCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/usage/GetUsageRecordsCmd.java @@ -111,6 +111,30 @@ public class GetUsageRecordsCmd extends BaseListCmd { public String getUsageId() { return usageId; } + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public void setUsageId(String usageId) { + this.usageId = usageId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// diff --git a/api/src/org/apache/cloudstack/usage/UsageTypes.java b/api/src/org/apache/cloudstack/usage/UsageTypes.java index 3edfd0b66aa..d9cfc132e15 100644 --- a/api/src/org/apache/cloudstack/usage/UsageTypes.java +++ b/api/src/org/apache/cloudstack/usage/UsageTypes.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.cloudstack.api.response.UsageTypeResponse; public class UsageTypes { + /* Any changes here should also reflect in cloud_usage.quota_mapping table */ public static final int RUNNING_VM = 1; public static final int ALLOCATED_VM = 2; // used for tracking how long storage has been allocated for a VM public static final int IP_ADDRESS = 3; diff --git a/api/test/org/apache/cloudstack/api/command/test/UsageCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/UsageCmdTest.java index cadf2754dbe..e5f3e27aa56 100644 --- a/api/test/org/apache/cloudstack/api/command/test/UsageCmdTest.java +++ b/api/test/org/apache/cloudstack/api/command/test/UsageCmdTest.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.test; import java.util.ArrayList; +import java.util.Date; import java.util.List; import junit.framework.TestCase; @@ -70,4 +71,22 @@ public class UsageCmdTest extends TestCase { } + @Test + public void testCrud() { + getUsageRecordsCmd.setDomainId(1L); + assertTrue(getUsageRecordsCmd.getDomainId().equals(1L)); + + getUsageRecordsCmd.setAccountName("someAccount"); + assertTrue(getUsageRecordsCmd.getAccountName().equals("someAccount")); + + Date d = new Date(); + getUsageRecordsCmd.setStartDate(d); + getUsageRecordsCmd.setEndDate(d); + assertTrue(getUsageRecordsCmd.getStartDate().equals(d)); + assertTrue(getUsageRecordsCmd.getEndDate().equals(d)); + + getUsageRecordsCmd.setUsageId("someId"); + assertTrue(getUsageRecordsCmd.getUsageId().equals("someId")); + } + } diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index 9c45015ca99..e7beaa9304d 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -1423,6 +1423,47 @@ label.show.advanced.settings=Show advanced settings label.delete.OpenDaylight.device=Delete OpenDaylight Controller label.polling.interval.sec=Polling Interval (in sec) label.quiet.time.sec=Quiet Time (in sec) +label.usage.type=Usage Type +label.usage.unit=Unit +label.quota.value=Quota Value +label.quota.description=Quota Description +label.quota.configuration=Quota Configuration +label.quota.configure=Configure Quota +label.quota.remove=Remove Quota +label.quota.totalusage=Total Usage +label.quota.balance=Balance +label.quota.minbalance=Min Balance +label.quota.enforcequota=Enforce Quota +label.quota.summary=Summary +label.quota.fullsummary=All Accounts +label.quota.tariff=Tariff +label.quota.state=State +label.quota.startdate=Start Date +label.quota.enddate=End Date +label.quota.total=Total +label.quota.startquota=Start Quota +label.quota.endquota=End Quota +label.quota.type.name=Usage Type +label.quota.type.unit=Usage Unit +label.quota.usage=Quota Consumption +label.quota.add.credits=Add Credits +label.quota.email.template=Email Template +label.quota.statement=Statement +label.quota.statement.balance=Quota Balance +label.quota.statement.quota=Quota Usage +label.quota.statement.tariff=Quota Tariff +label.quota.tariff.value=Tariff Value +label.quota.tariff.edit=Edit Tariff +label.quota.tariff.effectivedate=Effective Date +label.quota.date=Date +label.quota.dates=Update Dates +label.quota.credit=Credit +label.quota.credits=Credits +label.quota.value=Quota Value +label.quota.statement.bydates=Statement +label.quota.email.subject=Subject +label.quota.email.body=Body +label.quota.email.lastupdated=Last Update label.destroy.vm.graceperiod=Destroy VM Grace Period label.SNMP.community=SNMP Community label.SNMP.port=SNMP Port diff --git a/client/pom.xml b/client/pom.xml index aca7747c3eb..d4478304c43 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -256,6 +256,11 @@ cloud-framework-ipc ${project.version} + + org.apache.cloudstack + cloud-framework-quota + ${project.version} + org.apache.cloudstack cloud-framework-rest @@ -366,6 +371,11 @@ cloud-plugin-network-globodns ${project.version} + + org.apache.cloudstack + cloud-plugin-database-quota + ${project.version} + diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index b40841b2bf9..485abea099c 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -787,3 +787,14 @@ addGloboDnsHost=1 ### volume/template post upload getUploadParamsForVolume=15 getUploadParamsForTemplate=15 + +### Quota Service +quotaStatement=15 +quotaBalance=15 +quotaSummary=15 +quotaUpdate=1 +quotaTariffList=15 +quotaTariffUpdate=1 +quotaCredits=1 +quotaEmailTemplateList=1 +quotaEmailTemplateUpdate=1 diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade452to460.java b/engine/schema/src/com/cloud/upgrade/dao/Upgrade452to460.java index 524ee2eef0f..6b78a7ed611 100644 --- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade452to460.java +++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade452to460.java @@ -340,4 +340,4 @@ public class Upgrade452to460 implements DbUpgrade { } s_logger.debug("Updating System Vm Template IDs Complete"); } -} \ No newline at end of file +} diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade461to470.java b/engine/schema/src/com/cloud/upgrade/dao/Upgrade461to470.java index 4868680b50e..8dbbdb2e9ec 100644 --- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade461to470.java +++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade461to470.java @@ -23,6 +23,8 @@ import org.apache.log4j.Logger; import java.io.File; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; public class Upgrade461to470 implements DbUpgrade { final static Logger s_logger = Logger.getLogger(Upgrade461to470.class); @@ -51,8 +53,23 @@ public class Upgrade461to470 implements DbUpgrade { return new File[] {new File(script)}; } + public void alterAddColumnToCloudUsage(final Connection conn) { + final String alterTableSql = "ALTER TABLE `cloud_usage`.`cloud_usage` ADD COLUMN `quota_calculated` tinyint(1) DEFAULT 0 NOT NULL COMMENT 'quota calculation status'"; + try (PreparedStatement pstmt = conn.prepareStatement(alterTableSql)) { + pstmt.executeUpdate(); + s_logger.info("Altered cloud_usage.cloud_usage table and added column quota_calculated"); + } catch (SQLException e) { + if (e.getMessage().contains("quota_calculated")) { + s_logger.warn("cloud_usage.cloud_usage table already has a column called quota_calculated"); + } else { + throw new CloudRuntimeException("Unable to create column quota_calculated in table cloud_usage.cloud_usage", e); + } + } + } + @Override public void performDataMigration(Connection conn) { + alterAddColumnToCloudUsage(conn); } @Override diff --git a/engine/schema/src/com/cloud/usage/UsageVO.java b/engine/schema/src/com/cloud/usage/UsageVO.java index c46abb3a9b5..cc90d71c8b2 100644 --- a/engine/schema/src/com/cloud/usage/UsageVO.java +++ b/engine/schema/src/com/cloud/usage/UsageVO.java @@ -103,6 +103,17 @@ public class UsageVO implements Usage, InternalIdentity { @Temporal(value = TemporalType.TIMESTAMP) private Date endDate = null; + @Column(name = "quota_calculated") + private Integer quotaCalculated = 0; + + public Integer getQuotaCalculated() { + return quotaCalculated; + } + + public void setQuotaCalculated(Integer quotaCalculated) { + this.quotaCalculated = quotaCalculated; + } + public UsageVO() { } @@ -121,8 +132,8 @@ public class UsageVO implements Usage, InternalIdentity { this.templateId = templateId; this.usageId = usageId; this.size = size; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } public UsageVO(Long zoneId, Long accountId, Long domainId, String description, String usageDisplay, int usageType, Double rawUsage, Long vmId, String vmName, @@ -141,8 +152,8 @@ public class UsageVO implements Usage, InternalIdentity { this.usageId = usageId; this.size = size; this.virtualSize = virtualSize; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } public UsageVO(Long zoneId, Long accountId, Long domainId, String description, String usageDisplay, int usageType, Double rawUsage, Long usageId, String type, @@ -157,8 +168,8 @@ public class UsageVO implements Usage, InternalIdentity { this.usageId = usageId; this.type = type; this.networkId = networkId; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } public UsageVO(Long zoneId, Long accountId, Long domainId, String description, String usageDisplay, int usageType, Double rawUsage, Long vmId, String vmName, @@ -176,8 +187,8 @@ public class UsageVO implements Usage, InternalIdentity { this.templateId = templateId; this.usageId = usageId; this.type = type; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } public UsageVO(Long zoneId, Long accountId, Long domainId, String description, String usageDisplay, int usageType, Double rawUsage, Long vmId, String vmName, @@ -198,8 +209,8 @@ public class UsageVO implements Usage, InternalIdentity { this.templateId = templateId; this.usageId = usageId; this.type = type; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } //IPAddress Usage @@ -215,8 +226,8 @@ public class UsageVO implements Usage, InternalIdentity { this.usageId = usageId; this.size = size; this.type = type; - this.startDate = startDate; - this.endDate = endDate; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); } @Override @@ -321,11 +332,55 @@ public class UsageVO implements Usage, InternalIdentity { @Override public Date getStartDate() { - return startDate; + return startDate == null ? null : new Date(startDate.getTime()); } @Override public Date getEndDate() { - return endDate; + return endDate == null ? null : new Date(endDate.getTime()); + } + + public void setId(Long id) { + this.id = id; + } + + public void setType(String type) { + this.type = type; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public void setUsageType(int usageType) { + this.usageType = usageType; + } + + public void setRawUsage(Double rawUsage) { + this.rawUsage = rawUsage; + } + + public void setSize(Long size) { + this.size = size; + } + + public void setVirtualSize(Long virtualSize) { + this.virtualSize = virtualSize; } } diff --git a/engine/schema/src/com/cloud/usage/dao/UsageDao.java b/engine/schema/src/com/cloud/usage/dao/UsageDao.java index f9e9d23369a..4822dd60acf 100644 --- a/engine/schema/src/com/cloud/usage/dao/UsageDao.java +++ b/engine/schema/src/com/cloud/usage/dao/UsageDao.java @@ -55,4 +55,8 @@ public interface UsageDao extends GenericDao { void saveUsageRecords(List usageRecords); void removeOldUsageRecords(int days); + + UsageVO persistUsage(final UsageVO usage); + + Pair, Integer> getUsageRecordsPendingQuotaAggregation(long accountId, long domainId); } diff --git a/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java b/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java index 69ea0fbcbad..9c9ab0bbc08 100644 --- a/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java +++ b/engine/schema/src/com/cloud/usage/dao/UsageDaoImpl.java @@ -24,9 +24,14 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; 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 com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; + import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -45,29 +50,24 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage private static final String DELETE_ALL_BY_ACCOUNTID = "DELETE FROM cloud_usage WHERE account_id = ?"; private static final String DELETE_ALL_BY_INTERVAL = "DELETE FROM cloud_usage WHERE end_date < DATE_SUB(CURRENT_DATE(), INTERVAL ? DAY)"; private static final String INSERT_ACCOUNT = "INSERT INTO cloud_usage.account (id, account_name, type, domain_id, removed, cleanup_needed) VALUES (?,?,?,?,?,?)"; - private static final String INSERT_USER_STATS = - "INSERT INTO cloud_usage.user_statistics (id, data_center_id, account_id, public_ip_address, device_id, device_type, network_id, net_bytes_received," - + " net_bytes_sent, current_bytes_received, current_bytes_sent, agg_bytes_received, agg_bytes_sent) VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?)"; + private static final String INSERT_USER_STATS = "INSERT INTO cloud_usage.user_statistics (id, data_center_id, account_id, public_ip_address, device_id, device_type, network_id, net_bytes_received," + + " net_bytes_sent, current_bytes_received, current_bytes_sent, agg_bytes_received, agg_bytes_sent) VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?)"; private static final String UPDATE_ACCOUNT = "UPDATE cloud_usage.account SET account_name=?, removed=? WHERE id=?"; - private static final String UPDATE_USER_STATS = - "UPDATE cloud_usage.user_statistics SET net_bytes_received=?, net_bytes_sent=?, current_bytes_received=?, current_bytes_sent=?, agg_bytes_received=?, agg_bytes_sent=? WHERE id=?"; + private static final String UPDATE_USER_STATS = "UPDATE cloud_usage.user_statistics SET net_bytes_received=?, net_bytes_sent=?, current_bytes_received=?, current_bytes_sent=?, agg_bytes_received=?, agg_bytes_sent=? WHERE id=?"; private static final String GET_LAST_ACCOUNT = "SELECT id FROM cloud_usage.account ORDER BY id DESC LIMIT 1"; private static final String GET_LAST_USER_STATS = "SELECT id FROM cloud_usage.user_statistics ORDER BY id DESC LIMIT 1"; private static final String GET_PUBLIC_TEMPLATES_BY_ACCOUNTID = "SELECT id FROM cloud.vm_template WHERE account_id = ? AND public = '1' AND removed IS NULL"; private static final String GET_LAST_VM_DISK_STATS = "SELECT id FROM cloud_usage.vm_disk_statistics ORDER BY id DESC LIMIT 1"; - private static final String INSERT_VM_DISK_STATS = - "INSERT INTO cloud_usage.vm_disk_statistics (id, data_center_id, account_id, vm_id, volume_id, net_io_read, net_io_write, current_io_read, " - + "current_io_write, agg_io_read, agg_io_write, net_bytes_read, net_bytes_write, current_bytes_read, current_bytes_write, agg_bytes_read, agg_bytes_write) " - + " VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?, ?,?, ?, ?)"; - private static final String UPDATE_VM_DISK_STATS = - "UPDATE cloud_usage.vm_disk_statistics SET net_io_read=?, net_io_write=?, current_io_read=?, current_io_write=?, agg_io_read=?, agg_io_write=?, " - + "net_bytes_read=?, net_bytes_write=?, current_bytes_read=?, current_bytes_write=?, agg_bytes_read=?, agg_bytes_write=? WHERE id=?"; + private static final String INSERT_VM_DISK_STATS = "INSERT INTO cloud_usage.vm_disk_statistics (id, data_center_id, account_id, vm_id, volume_id, net_io_read, net_io_write, current_io_read, " + + "current_io_write, agg_io_read, agg_io_write, net_bytes_read, net_bytes_write, current_bytes_read, current_bytes_write, agg_bytes_read, agg_bytes_write) " + + " VALUES (?,?,?,?,?,?,?,?,?,?, ?, ?, ?, ?,?, ?, ?)"; + private static final String UPDATE_VM_DISK_STATS = "UPDATE cloud_usage.vm_disk_statistics SET net_io_read=?, net_io_write=?, current_io_read=?, current_io_write=?, agg_io_read=?, agg_io_write=?, " + + "net_bytes_read=?, net_bytes_write=?, current_bytes_read=?, current_bytes_write=?, agg_bytes_read=?, agg_bytes_write=? WHERE id=?"; private static final String INSERT_USAGE_RECORDS = "INSERT INTO cloud_usage.cloud_usage (zone_id, account_id, domain_id, description, usage_display, " - + - "usage_type, raw_usage, vm_instance_id, vm_name, offering_id, template_id, " + + "usage_type, raw_usage, vm_instance_id, vm_name, offering_id, template_id, " + "usage_id, type, size, network_id, start_date, end_date, virtual_size) VALUES (?,?,?,?,?,?,?,?,?, ?, ?, ?,?,?,?,?,?,?)"; protected final static TimeZone s_gmtTimeZone = TimeZone.getTimeZone("GMT"); @@ -213,7 +213,7 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage txn.start(); String sql = UPDATE_USER_STATS; PreparedStatement pstmt = null; - pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection + pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection for (UserStatisticsVO userStat : userStats) { pstmt.setLong(1, userStat.getNetBytesReceived()); pstmt.setLong(2, userStat.getNetBytesSent()); @@ -310,7 +310,7 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage txn.start(); String sql = UPDATE_VM_DISK_STATS; PreparedStatement pstmt = null; - pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection + pstmt = txn.prepareAutoCloseStatement(sql); // in reality I just want CLOUD_USAGE dataSource connection for (VmDiskStatisticsVO vmDiskStat : vmDiskStats) { pstmt.setLong(1, vmDiskStat.getNetIORead()); pstmt.setLong(2, vmDiskStat.getNetIOWrite()); @@ -467,4 +467,40 @@ public class UsageDaoImpl extends GenericDaoBase implements Usage txn.close(); } } + + public UsageVO persistUsage(final UsageVO usage) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public UsageVO doInTransaction(final TransactionStatus status) { + return persist(usage); + } + }); + } + + public Pair, Integer> getUsageRecordsPendingQuotaAggregation(final long accountId, final long domainId) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Getting usage records for account: " + accountId + ", domainId: " + domainId); + } + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback, Integer>>() { + @Override + public Pair, Integer> doInTransaction(final TransactionStatus status) { + Pair, Integer> usageRecords = new Pair, Integer>(new ArrayList(), 0); + Filter usageFilter = new Filter(UsageVO.class, "startDate", true, 0L, Long.MAX_VALUE); + QueryBuilder qb = QueryBuilder.create(UsageVO.class); + if (accountId != -1) { + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + } + if (domainId != -1) { + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + } + qb.and(qb.entity().getQuotaCalculated(), SearchCriteria.Op.NEQ, 1); + qb.and(qb.entity().getRawUsage(), SearchCriteria.Op.GT, 0); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Getting usage records" + usageFilter.getOrderBy()); + } + usageRecords = searchAndCountAllRecords(qb.create(), usageFilter); + return new Pair, Integer>(usageRecords.first(), usageRecords.second()); + } + }); + } } diff --git a/framework/db/src/com/cloud/utils/db/Transaction.java b/framework/db/src/com/cloud/utils/db/Transaction.java index dd91a967a06..c6a491a216d 100644 --- a/framework/db/src/com/cloud/utils/db/Transaction.java +++ b/framework/db/src/com/cloud/utils/db/Transaction.java @@ -35,18 +35,11 @@ public class Transaction { if (currentTxn != null) { databaseId = currentTxn.getDatabaseId(); } - TransactionLegacy txn = TransactionLegacy.open(name, databaseId, false); - try { -// if (txn.dbTxnStarted()){ -// String warnMsg = "Potential Wrong Usage: TRANSACTION.EXECUTE IS WRAPPED INSIDE ANOTHER DB TRANSACTION!"; -// s_logger.warn(warnMsg, new CloudRuntimeException(warnMsg)); -// } + try (final TransactionLegacy txn = TransactionLegacy.open(name, databaseId, false)) { txn.start(); T result = callback.doInTransaction(STATUS); txn.commit(); return result; - } finally { - txn.close(); } } @@ -59,4 +52,28 @@ public class Transaction { }); } + @SuppressWarnings("deprecation") + public static T execute(final short databaseId, TransactionCallbackWithException callback) throws E { + String name = "tx-" + counter.incrementAndGet(); + TransactionLegacy currentTxn = TransactionLegacy.currentTxn(false); + short outer_txn_databaseId = (currentTxn != null ? currentTxn.getDatabaseId() : databaseId); + try (final TransactionLegacy txn = TransactionLegacy.open(name, databaseId, true)) { + txn.start(); + T result = callback.doInTransaction(STATUS); + txn.commit(); + return result; + } finally { + TransactionLegacy.open(outer_txn_databaseId).close(); + } + } + + public static T execute(final short databaseId, final TransactionCallback callback) { + return execute(databaseId, new TransactionCallbackWithException() { + @Override + public T doInTransaction(TransactionStatus status) throws RuntimeException { + return callback.doInTransaction(status); + } + }); + } + } diff --git a/framework/pom.xml b/framework/pom.xml index 1b4e17e51a6..3cfc6d0a3ce 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -47,6 +47,7 @@ rest events jobs + quota cluster db config diff --git a/framework/quota/pom.xml b/framework/quota/pom.xml new file mode 100644 index 00000000000..c0ed3c84986 --- /dev/null +++ b/framework/quota/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + cloud-framework-quota + Apache CloudStack Framework - Quota + + org.apache.cloudstack + cloudstack-framework + 4.7.0-SNAPSHOT + ../pom.xml + + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.apache.cloudstack + cloud-engine-schema + ${project.version} + + + junit + junit + ${cs.junit.version} + test + + + org.mockito + mockito-all + ${cs.mockito.version} + test + + + org.powermock + powermock-module-junit4 + ${cs.powermock.version} + + + org.powermock + powermock-api-mockito + ${cs.powermock.version} + test + + + org.apache.commons + commons-lang3 + ${cs.commons-lang3.version} + + + javax.mail + mail + + + diff --git a/framework/quota/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml b/framework/quota/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml new file mode 100644 index 00000000000..f7a3accdc39 --- /dev/null +++ b/framework/quota/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManager.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManager.java new file mode 100644 index 00000000000..44204e8d116 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManager.java @@ -0,0 +1,26 @@ +//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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.utils.component.Manager; + +import org.apache.cloudstack.quota.QuotaAlertManagerImpl.DeferredQuotaEmail; + +public interface QuotaAlertManager extends Manager { + void checkAndSendQuotaAlertEmails(); + void sendQuotaAlert(DeferredQuotaEmail emailToBeSent); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java new file mode 100644 index 00000000000..a57e0c27db9 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -0,0 +1,418 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.user.Account; +import com.cloud.user.Account.State; +import com.cloud.user.AccountVO; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.base.Strings; +import com.sun.mail.smtp.SMTPMessage; +import com.sun.mail.smtp.SMTPSSLTransport; +import com.sun.mail.smtp.SMTPTransport; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.constant.QuotaConfig.QuotaEmailTemplateTypes; +import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; +import org.apache.cloudstack.quota.dao.QuotaUsageDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.apache.commons.lang3.text.StrSubstitutor; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.URLName; +import javax.mail.internet.InternetAddress; +import javax.naming.ConfigurationException; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +@Component +@Local(value = QuotaAlertManager.class) +public class QuotaAlertManagerImpl extends ManagerBase implements QuotaAlertManager { + private static final Logger s_logger = Logger.getLogger(QuotaAlertManagerImpl.class); + + @Inject + private AccountDao _accountDao; + @Inject + private QuotaAccountDao _quotaAcc; + @Inject + private UserDao _userDao; + @Inject + private DomainDao _domainDao; + @Inject + private QuotaEmailTemplatesDao _quotaEmailTemplateDao; + @Inject + private ConfigurationDao _configDao; + @Inject + private QuotaUsageDao _quotaUsage; + + private EmailQuotaAlert _emailQuotaAlert; + private boolean _lockAccountEnforcement = false; + + boolean _smtpDebug = false; + + public QuotaAlertManagerImpl() { + super(); + } + + private void mergeConfigs(Map dbParams, Map xmlParams) { + for (Map.Entry param : xmlParams.entrySet()) { + dbParams.put(param.getKey(), (String)param.getValue()); + } + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + Map configs = _configDao.getConfiguration(params); + + if (params != null) { + mergeConfigs(configs, params); + } + + final String smtpHost = configs.get(QuotaConfig.QuotaSmtpHost.key()); + int smtpPort = NumbersUtil.parseInt(configs.get(QuotaConfig.QuotaSmtpPort.key()), 25); + String useAuthStr = configs.get(QuotaConfig.QuotaSmtpAuthType.key()); + boolean useAuth = ((useAuthStr != null) && Boolean.parseBoolean(useAuthStr)); + String smtpUsername = configs.get(QuotaConfig.QuotaSmtpUser.key()); + String smtpPassword = configs.get(QuotaConfig.QuotaSmtpPassword.key()); + String emailSender = configs.get(QuotaConfig.QuotaSmtpSender.key()); + _lockAccountEnforcement = "true".equalsIgnoreCase(configs.get(QuotaConfig.QuotaEnableEnforcement.key())); + _emailQuotaAlert = new EmailQuotaAlert(smtpHost, smtpPort, useAuth, smtpUsername, smtpPassword, emailSender, _smtpDebug); + + return true; + } + + @Override + public boolean start() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Starting Alert Manager"); + } + return true; + } + + @Override + public boolean stop() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Stopping Alert Manager"); + } + return true; + } + + @Override + public void checkAndSendQuotaAlertEmails() { + List deferredQuotaEmailList = new ArrayList(); + final BigDecimal zeroBalance = new BigDecimal(0); + for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("checkAndSendQuotaAlertEmails accId=" + quotaAccount.getId()); + } + BigDecimal accountBalance = quotaAccount.getQuotaBalance(); + Date balanceDate = quotaAccount.getQuotaBalanceDate(); + Date alertDate = quotaAccount.getQuotaAlertDate(); + int lockable = quotaAccount.getQuotaEnforce(); + BigDecimal thresholdBalance = quotaAccount.getQuotaMinBalance(); + if (accountBalance != null) { + AccountVO account = _accountDao.findById(quotaAccount.getId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("checkAndSendQuotaAlertEmails: Check id=" + account.getId() + " bal=" + accountBalance + ", alertDate=" + alertDate + ", lockable=" + lockable); + } + if (accountBalance.compareTo(zeroBalance) < 0) { + if (_lockAccountEnforcement && (lockable == 1)) { + if (account.getType() == Account.ACCOUNT_TYPE_NORMAL) { + s_logger.info("Locking account " + account.getAccountName() + " due to quota < 0."); + lockAccount(account.getId()); + } + } + if (alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1)) { + s_logger.info("Sending alert " + account.getAccountName() + " due to quota < 0."); + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY)); + } + } else if (accountBalance.compareTo(thresholdBalance) < 0) { + if (alertDate == null || (balanceDate.after(alertDate) && getDifferenceDays(alertDate, new Date()) > 1)) { + s_logger.info("Sending alert " + account.getAccountName() + " due to quota below threshold."); + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW)); + } + } + } + } + + for (DeferredQuotaEmail emailToBeSent : deferredQuotaEmailList) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("checkAndSendQuotaAlertEmails: Attempting to send quota alert email to users of account: " + emailToBeSent.getAccount().getAccountName()); + } + sendQuotaAlert(emailToBeSent); + } + } + + public void sendQuotaAlert(DeferredQuotaEmail emailToBeSent) { + final AccountVO account = emailToBeSent.getAccount(); + final BigDecimal balance = emailToBeSent.getQuotaBalance(); + final BigDecimal usage = emailToBeSent.getQuotaUsage(); + final QuotaConfig.QuotaEmailTemplateTypes emailType = emailToBeSent.getEmailTemplateType(); + + final List emailTemplates = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(emailType.toString()); + if (emailTemplates != null && emailTemplates.get(0) != null) { + final QuotaEmailTemplatesVO emailTemplate = emailTemplates.get(0); + + final DomainVO accountDomain = _domainDao.findByIdIncludingRemoved(account.getDomainId()); + final List usersInAccount = _userDao.listByAccount(account.getId()); + + String userNames = ""; + final List emailRecipients = new ArrayList(); + for (UserVO user : usersInAccount) { + userNames += String.format("%s <%s>,", user.getUsername(), user.getEmail()); + emailRecipients.add(user.getEmail()); + } + if (userNames.endsWith(",")) { + userNames = userNames.substring(0, userNames.length() - 1); + } + + final Map optionMap = new HashMap(); + optionMap.put("accountName", account.getAccountName()); + optionMap.put("accountID", account.getUuid()); + optionMap.put("accountUsers", userNames); + optionMap.put("domainName", accountDomain.getName()); + optionMap.put("domainID", accountDomain.getUuid()); + optionMap.put("quotaBalance", QuotaConfig.QuotaCurrencySymbol.value() + " " + balance.toString()); + if (emailType == QuotaEmailTemplateTypes.QUOTA_STATEMENT) { + optionMap.put("quotaUsage", QuotaConfig.QuotaCurrencySymbol.value() + " " + usage.toString()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("accountName" + account.getAccountName() + "accountID" + account.getUuid() + "accountUsers" + userNames + "domainName" + accountDomain.getName() + + "domainID" + accountDomain.getUuid()); + } + + final StrSubstitutor templateEngine = new StrSubstitutor(optionMap); + final String subject = templateEngine.replace(emailTemplate.getTemplateSubject()); + final String body = templateEngine.replace(emailTemplate.getTemplateBody()); + try { + _emailQuotaAlert.sendQuotaAlert(emailRecipients, subject, body); + emailToBeSent.sentSuccessfully(_quotaAcc); + } catch (Exception e) { + s_logger.error(String.format("Unable to send quota alert email (subject=%s; body=%s) to account %s (%s) recipients (%s) due to error (%s)", subject, body, + account.getAccountName(), account.getUuid(), emailRecipients, e)); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Exception", e); + } + } + } else { + s_logger.error(String.format("No quota email template found for type %s, cannot send quota alert email to account %s(%s)", emailType, account.getAccountName(), + account.getUuid())); + } + } + + public static long getDifferenceDays(Date d1, Date d2) { + long diff = d2.getTime() - d1.getTime(); + return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); + } + + protected boolean lockAccount(long accountId) { + final short opendb = TransactionLegacy.currentTxn().getDatabaseId(); + boolean success = false; + try (TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB)) { + Account account = _accountDao.findById(accountId); + if (account != null) { + if (account.getState() == State.locked) { + return true; // already locked, no-op + } else if (account.getState() == State.enabled) { + AccountVO acctForUpdate = _accountDao.createForUpdate(); + acctForUpdate.setState(State.locked); + success = _accountDao.update(Long.valueOf(accountId), acctForUpdate); + } else { + if (s_logger.isInfoEnabled()) { + s_logger.info("Attempting to lock a non-enabled account, current state is " + account.getState() + " (accountId: " + accountId + "), locking failed."); + } + } + } else { + s_logger.warn("Failed to lock account " + accountId + ", account not found."); + } + } catch (Exception e) { + s_logger.error("Exception occured while locking account by Quota Alert Manager", e); + throw e; + } finally { + TransactionLegacy.open(opendb).close(); + } + return success; + } + + public static class DeferredQuotaEmail { + private AccountVO account; + private QuotaAccountVO quotaAccount; + private QuotaConfig.QuotaEmailTemplateTypes emailTemplateType; + private BigDecimal quotaUsage; + + public DeferredQuotaEmail(AccountVO account, QuotaAccountVO quotaAccount, BigDecimal quotaUsage, QuotaConfig.QuotaEmailTemplateTypes emailTemplateType) { + this.account = account; + this.quotaAccount = quotaAccount; + this.emailTemplateType = emailTemplateType; + this.quotaUsage = quotaUsage; + } + + public DeferredQuotaEmail(AccountVO account, QuotaAccountVO quotaAccount, QuotaConfig.QuotaEmailTemplateTypes emailTemplateType) { + this.account = account; + this.quotaAccount = quotaAccount; + this.emailTemplateType = emailTemplateType; + this.quotaUsage = new BigDecimal(-1); + } + + public AccountVO getAccount() { + return account; + } + + public BigDecimal getQuotaBalance() { + return quotaAccount.getQuotaBalance(); + } + + public BigDecimal getQuotaUsage() { + return quotaUsage; + } + + public Date getSendDate() { + if (emailTemplateType == QuotaEmailTemplateTypes.QUOTA_STATEMENT) { + return quotaAccount.getLastStatementDate(); + } else { + return quotaAccount.getQuotaAlertDate(); + } + } + + public QuotaConfig.QuotaEmailTemplateTypes getEmailTemplateType() { + return emailTemplateType; + } + + public void sentSuccessfully(final QuotaAccountDao quotaAccountDao) { + if (emailTemplateType == QuotaEmailTemplateTypes.QUOTA_STATEMENT) { + quotaAccount.setLastStatementDate(new Date()); + } else { + quotaAccount.setQuotaAlertDate(new Date()); + quotaAccount.setQuotaAlertType(emailTemplateType.ordinal()); + } + quotaAccountDao.updateQuotaAccount(quotaAccount.getAccountId(), quotaAccount); + } + }; + + static class EmailQuotaAlert { + private final Session _smtpSession; + private final String _smtpHost; + private final int _smtpPort; + private final boolean _smtpUseAuth; + private final String _smtpUsername; + private final String _smtpPassword; + private final String _emailSender; + + public EmailQuotaAlert(String smtpHost, int smtpPort, boolean smtpUseAuth, final String smtpUsername, final String smtpPassword, String emailSender, boolean smtpDebug) { + _smtpHost = smtpHost; + _smtpPort = smtpPort; + _smtpUseAuth = smtpUseAuth; + _smtpUsername = smtpUsername; + _smtpPassword = smtpPassword; + _emailSender = emailSender; + + if (!Strings.isNullOrEmpty(_smtpHost)) { + Properties smtpProps = new Properties(); + smtpProps.put("mail.smtp.host", smtpHost); + smtpProps.put("mail.smtp.port", smtpPort); + smtpProps.put("mail.smtp.auth", "" + smtpUseAuth); + if (smtpUsername != null) { + smtpProps.put("mail.smtp.user", smtpUsername); + } + + smtpProps.put("mail.smtps.host", smtpHost); + smtpProps.put("mail.smtps.port", smtpPort); + smtpProps.put("mail.smtps.auth", "" + smtpUseAuth); + if (!Strings.isNullOrEmpty(smtpUsername)) { + smtpProps.put("mail.smtps.user", smtpUsername); + } + + if (!Strings.isNullOrEmpty(smtpUsername) && !Strings.isNullOrEmpty(smtpPassword)) { + _smtpSession = Session.getInstance(smtpProps, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(smtpUsername, smtpPassword); + } + }); + } else { + _smtpSession = Session.getInstance(smtpProps); + } + _smtpSession.setDebug(smtpDebug); + } else { + _smtpSession = null; + } + } + + public void sendQuotaAlert(List emails, String subject, String body) throws MessagingException, UnsupportedEncodingException { + if (_smtpSession == null) { + throw new CloudRuntimeException("Unable to create smtp session."); + } + SMTPMessage msg = new SMTPMessage(_smtpSession); + msg.setSender(new InternetAddress(_emailSender, _emailSender)); + msg.setFrom(new InternetAddress(_emailSender, _emailSender)); + + for (String email : emails) { + if (email != null && !email.isEmpty()) { + try { + InternetAddress address = new InternetAddress(email, email); + msg.addRecipient(Message.RecipientType.TO, address); + } catch (Exception pokemon) { + s_logger.error("Exception in creating address for:" + email, pokemon); + } + } + } + + msg.setSubject(subject); + msg.setSentDate(new Date()); + msg.setContent(body, "text/html; charset=utf-8"); + msg.saveChanges(); + + SMTPTransport smtpTrans = null; + if (_smtpUseAuth) { + smtpTrans = new SMTPSSLTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); + } else { + smtpTrans = new SMTPTransport(_smtpSession, new URLName("smtp", _smtpHost, _smtpPort, null, _smtpUsername, _smtpPassword)); + } + smtpTrans.connect(); + smtpTrans.sendMessage(msg, msg.getAllRecipients()); + smtpTrans.close(); + } + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaManager.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaManager.java new file mode 100644 index 00000000000..1cda3b22e44 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaManager.java @@ -0,0 +1,25 @@ +//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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.utils.component.Manager; + +public interface QuotaManager extends Manager { + + boolean calculateQuotaUsage(); + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaManagerImpl.java new file mode 100644 index 00000000000..d7c301e1bea --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaManagerImpl.java @@ -0,0 +1,464 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.usage.UsageVO; +import com.cloud.usage.dao.UsageDao; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +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.QuotaUsageDao; +import org.apache.cloudstack.quota.dao.ServiceOfferingDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.cloudstack.quota.vo.ServiceOfferingVO; +import org.apache.cloudstack.utils.usage.UsageUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +@Component +@Local(value = QuotaManager.class) +public class QuotaManagerImpl extends ManagerBase implements QuotaManager { + private static final Logger s_logger = Logger.getLogger(QuotaManagerImpl.class.getName()); + + @Inject + private AccountDao _accountDao; + @Inject + private QuotaAccountDao _quotaAcc; + @Inject + private UsageDao _usageDao; + @Inject + private QuotaTariffDao _quotaTariffDao; + @Inject + private QuotaUsageDao _quotaUsageDao; + @Inject + private ServiceOfferingDao _serviceOfferingDao; + @Inject + private QuotaBalanceDao _quotaBalanceDao; + @Inject + private ConfigurationDao _configDao; + + private TimeZone _usageTimezone; + private int _aggregationDuration = 0; + + final static BigDecimal s_hoursInMonth = new BigDecimal(30 * 24); + final static BigDecimal s_minutesInMonth = new BigDecimal(30 * 24 * 60); + final static BigDecimal s_gb = new BigDecimal(1024 * 1024 * 1024); + + public QuotaManagerImpl() { + super(); + } + + private void mergeConfigs(Map dbParams, Map xmlParams) { + for (Map.Entry param : xmlParams.entrySet()) { + dbParams.put(param.getKey(), (String)param.getValue()); + } + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + Map configs = _configDao.getConfiguration(params); + + if (params != null) { + mergeConfigs(configs, params); + } + + String aggregationRange = configs.get("usage.stats.job.aggregation.range"); + String timeZoneStr = configs.get("usage.aggregation.timezone"); + + if (timeZoneStr == null) { + timeZoneStr = "GMT"; + } + _usageTimezone = TimeZone.getTimeZone(timeZoneStr); + + _aggregationDuration = Integer.parseInt(aggregationRange); + if (_aggregationDuration < UsageUtils.USAGE_AGGREGATION_RANGE_MIN) { + s_logger.warn("Usage stats job aggregation range is to small, using the minimum value of " + UsageUtils.USAGE_AGGREGATION_RANGE_MIN); + _aggregationDuration = UsageUtils.USAGE_AGGREGATION_RANGE_MIN; + } + s_logger.info("Usage timezone = " + _usageTimezone + " AggregationDuration=" + _aggregationDuration); + + return true; + } + + @Override + public boolean start() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Starting Quota Manager"); + } + return true; + } + + @Override + public boolean stop() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Stopping Quota Manager"); + } + return true; + } + + public List aggregatePendingQuotaRecordsForAccount(final AccountVO account, final Pair, Integer> usageRecords) { + List quotaListForAccount = new ArrayList<>(); + if (usageRecords == null || usageRecords.first() == null || usageRecords.first().isEmpty()) { + return quotaListForAccount; + } + s_logger.info("Getting pending quota records for account=" + account.getAccountName()); + for (UsageVO usageRecord : usageRecords.first()) { + BigDecimal aggregationRatio = new BigDecimal(_aggregationDuration).divide(s_minutesInMonth, 8, RoundingMode.HALF_EVEN); + switch (usageRecord.getUsageType()) { + case QuotaTypes.RUNNING_VM: + List lq = updateQuotaRunningVMUsage(usageRecord, aggregationRatio); + if (!lq.isEmpty()) { + quotaListForAccount.addAll(lq); + } + break; + case QuotaTypes.ALLOCATED_VM: + QuotaUsageVO qu = updateQuotaAllocatedVMUsage(usageRecord, aggregationRatio); + if (qu != null) { + quotaListForAccount.add(qu); + } + break; + case QuotaTypes.SNAPSHOT: + case QuotaTypes.TEMPLATE: + case QuotaTypes.ISO: + case QuotaTypes.VOLUME: + case QuotaTypes.VM_SNAPSHOT: + qu = updateQuotaDiskUsage(usageRecord, aggregationRatio, usageRecord.getUsageType()); + if (qu != null) { + quotaListForAccount.add(qu); + } + break; + case QuotaTypes.LOAD_BALANCER_POLICY: + case QuotaTypes.PORT_FORWARDING_RULE: + case QuotaTypes.IP_ADDRESS: + case QuotaTypes.NETWORK_OFFERING: + case QuotaTypes.SECURITY_GROUP: + case QuotaTypes.VPN_USERS: + qu = updateQuotaRaw(usageRecord, aggregationRatio, usageRecord.getUsageType()); + if (qu != null) { + quotaListForAccount.add(qu); + } + break; + case QuotaTypes.NETWORK_BYTES_RECEIVED: + case QuotaTypes.NETWORK_BYTES_SENT: + qu = updateQuotaNetwork(usageRecord, usageRecord.getUsageType()); + if (qu != null) { + quotaListForAccount.add(qu); + } + break; + case QuotaTypes.VM_DISK_IO_READ: + case QuotaTypes.VM_DISK_IO_WRITE: + case QuotaTypes.VM_DISK_BYTES_READ: + case QuotaTypes.VM_DISK_BYTES_WRITE: + default: + break; + } + } + return quotaListForAccount; + } + + public void processQuotaBalanceForAccount(final AccountVO account, final List quotaListForAccount) { + if (quotaListForAccount == null || quotaListForAccount.isEmpty()) { + return; + } + if (s_logger.isDebugEnabled()) { + s_logger.debug(quotaListForAccount.get(0)); + } + Date startDate = quotaListForAccount.get(0).getStartDate(); + Date endDate = quotaListForAccount.get(0).getEndDate(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("processQuotaBalanceForAccount startDate " + startDate + " endDate=" + endDate); + s_logger.debug("processQuotaBalanceForAccount last items startDate " + quotaListForAccount.get(quotaListForAccount.size() - 1).getStartDate() + " items endDate=" + + quotaListForAccount.get(quotaListForAccount.size() - 1).getEndDate()); + } + quotaListForAccount.add(new QuotaUsageVO()); + BigDecimal aggrUsage = new BigDecimal(0); + List creditsReceived = null; + + //bootstrapping + QuotaUsageVO lastQuotaUsage = _quotaUsageDao.findLastQuotaUsageEntry(account.getAccountId(), account.getDomainId(), startDate); + if (lastQuotaUsage == null) { + creditsReceived = _quotaBalanceDao.findCreditBalance(account.getAccountId(), account.getDomainId(), new Date(0), startDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Credit entries count " + creditsReceived.size() + " on Before Date=" + startDate); + } + if (creditsReceived != null) { + for (QuotaBalanceVO credit : creditsReceived) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Credit entry found " + credit); + s_logger.debug("Total = " + aggrUsage); + } + aggrUsage = aggrUsage.add(credit.getCreditBalance()); + } + } + // create a balance entry for these accumulated credits + QuotaBalanceVO firstBalance = new QuotaBalanceVO(account.getAccountId(), account.getDomainId(), aggrUsage, startDate); + _quotaBalanceDao.saveQuotaBalance(firstBalance); + } + else { + QuotaBalanceVO lastRealBalanceEntry = _quotaBalanceDao.findLastBalanceEntry(account.getAccountId(), account.getDomainId(), endDate); + aggrUsage = aggrUsage.add(lastRealBalanceEntry.getCreditBalance()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Last balance entry " + lastRealBalanceEntry + " AggrUsage=" + aggrUsage); + } + } + + for (QuotaUsageVO entry : quotaListForAccount) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Usage entry found " + entry); + } + if (entry.getQuotaUsed().compareTo(BigDecimal.ZERO) == 0) { + // check if there were credits + creditsReceived = _quotaBalanceDao.findCreditBalance(account.getAccountId(), account.getDomainId(), entry.getStartDate(), entry.getEndDate()); + if (creditsReceived != null) { + for (QuotaBalanceVO credit : creditsReceived) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Credit entry found " + credit); + s_logger.debug("Total = " + aggrUsage); + } + aggrUsage = aggrUsage.add(credit.getCreditBalance()); + } + } + continue; + } + if (startDate.compareTo(entry.getStartDate()) != 0) { + QuotaBalanceVO newBalance = new QuotaBalanceVO(account.getAccountId(), account.getDomainId(), aggrUsage, endDate); + _quotaBalanceDao.saveQuotaBalance(newBalance); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Saving Balance" + newBalance); + } + + //New balance entry + aggrUsage = new BigDecimal(0); + startDate = entry.getStartDate(); + endDate = entry.getEndDate(); + + QuotaBalanceVO lastRealBalanceEntry = _quotaBalanceDao.findLastBalanceEntry(account.getAccountId(), account.getDomainId(), endDate); + Date lastBalanceDate = new Date(0); + if (lastRealBalanceEntry != null) { + lastBalanceDate = lastRealBalanceEntry.getUpdatedOn(); + aggrUsage = aggrUsage.add(lastRealBalanceEntry.getCreditBalance()); + } + creditsReceived = _quotaBalanceDao.findCreditBalance(account.getAccountId(), account.getDomainId(), lastBalanceDate, endDate); + if (creditsReceived != null) { + for (QuotaBalanceVO credit : creditsReceived) { + aggrUsage = aggrUsage.add(credit.getCreditBalance()); + } + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Getting Balance" + account.getAccountName() + ",Balance entry=" + aggrUsage + " on Date=" + endDate); + } + } + aggrUsage = aggrUsage.subtract(entry.getQuotaUsed()); + } + QuotaBalanceVO newBalance = new QuotaBalanceVO(account.getAccountId(), account.getDomainId(), aggrUsage, endDate); + _quotaBalanceDao.saveQuotaBalance(newBalance); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Saving Balance" + newBalance); + } + + // update quota_accounts + QuotaAccountVO quota_account = _quotaAcc.findByIdQuotaAccount(account.getAccountId()); + + if (quota_account == null) { + quota_account = new QuotaAccountVO(account.getAccountId()); + quota_account.setQuotaBalance(aggrUsage); + quota_account.setQuotaBalanceDate(endDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug(quota_account); + } + _quotaAcc.persistQuotaAccount(quota_account); + } else { + quota_account.setQuotaBalance(aggrUsage); + quota_account.setQuotaBalanceDate(endDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug(quota_account); + } + _quotaAcc.updateQuotaAccount(account.getAccountId(), quota_account); + } + } + + @Override + public boolean calculateQuotaUsage() { + List accounts = _accountDao.listAll(); + for (AccountVO account : accounts) { + Pair, Integer> usageRecords = _usageDao.getUsageRecordsPendingQuotaAggregation(account.getAccountId(), account.getDomainId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Usage entries size = " + usageRecords.second().intValue() + ", accId" + account.getAccountId() + ", domId" + account.getDomainId()); + } + List quotaListForAccount = aggregatePendingQuotaRecordsForAccount(account, usageRecords); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Quota entries size = " + quotaListForAccount.size() + ", accId" + account.getAccountId() + ", domId" + account.getDomainId()); + } + processQuotaBalanceForAccount(account, quotaListForAccount); + } + return true; + } + + public QuotaUsageVO updateQuotaDiskUsage(UsageVO usageRecord, final BigDecimal aggregationRatio, final int quotaType) { + QuotaUsageVO quota_usage = null; + QuotaTariffVO tariff = _quotaTariffDao.findTariffPlanByUsageType(quotaType, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal quotaUsgage; + BigDecimal onehourcostpergb; + BigDecimal noofgbinuse; + onehourcostpergb = tariff.getCurrencyValue().multiply(aggregationRatio); + noofgbinuse = new BigDecimal(usageRecord.getSize()).divide(s_gb, 8, RoundingMode.HALF_EVEN); + quotaUsgage = new BigDecimal(usageRecord.getRawUsage()).multiply(onehourcostpergb).multiply(noofgbinuse); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), usageRecord.getUsageType(), + quotaUsgage, usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + } + usageRecord.setQuotaCalculated(1); + _usageDao.persistUsage(usageRecord); + return quota_usage; + } + + public List updateQuotaRunningVMUsage(UsageVO usageRecord, final BigDecimal aggregationRatio) { + List quotalist = new ArrayList(); + QuotaUsageVO quota_usage; + BigDecimal cpuquotausgage, speedquotausage, memoryquotausage, vmusage; + BigDecimal onehourcostpercpu, onehourcostper100mhz, onehourcostper1mb, onehourcostforvmusage; + BigDecimal rawusage; + // get service offering details + ServiceOfferingVO serviceoffering = _serviceOfferingDao.findServiceOffering(usageRecord.getVmInstanceId(), usageRecord.getOfferingId()); + rawusage = new BigDecimal(usageRecord.getRawUsage()); + + QuotaTariffVO tariff = _quotaTariffDao.findTariffPlanByUsageType(QuotaTypes.CPU_NUMBER, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal cpu = new BigDecimal(serviceoffering.getCpu()); + onehourcostpercpu = tariff.getCurrencyValue().multiply(aggregationRatio); + cpuquotausgage = rawusage.multiply(onehourcostpercpu).multiply(cpu); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), QuotaTypes.CPU_NUMBER, + cpuquotausgage, usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + quotalist.add(quota_usage); + } + tariff = _quotaTariffDao.findTariffPlanByUsageType(QuotaTypes.CPU_CLOCK_RATE, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal speed = new BigDecimal(serviceoffering.getSpeed() / 100.00); + onehourcostper100mhz = tariff.getCurrencyValue().multiply(aggregationRatio); + speedquotausage = rawusage.multiply(onehourcostper100mhz).multiply(speed); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), QuotaTypes.CPU_CLOCK_RATE, + speedquotausage, usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + quotalist.add(quota_usage); + } + tariff = _quotaTariffDao.findTariffPlanByUsageType(QuotaTypes.MEMORY, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal memory = new BigDecimal(serviceoffering.getRamSize()); + onehourcostper1mb = tariff.getCurrencyValue().multiply(aggregationRatio); + memoryquotausage = rawusage.multiply(onehourcostper1mb).multiply(memory); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), QuotaTypes.MEMORY, memoryquotausage, + usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + quotalist.add(quota_usage); + } + tariff = _quotaTariffDao.findTariffPlanByUsageType(QuotaTypes.RUNNING_VM, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + onehourcostforvmusage = tariff.getCurrencyValue().multiply(aggregationRatio); + vmusage = rawusage.multiply(onehourcostforvmusage); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), QuotaTypes.RUNNING_VM, vmusage, + usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + quotalist.add(quota_usage); + } + + usageRecord.setQuotaCalculated(1); + _usageDao.persistUsage(usageRecord); + return quotalist; + } + + public QuotaUsageVO updateQuotaAllocatedVMUsage(UsageVO usageRecord, final BigDecimal aggregationRatio) { + QuotaUsageVO quota_usage = null; + QuotaTariffVO tariff = _quotaTariffDao.findTariffPlanByUsageType(QuotaTypes.ALLOCATED_VM, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal vmusage; + BigDecimal onehourcostforvmusage; + onehourcostforvmusage = tariff.getCurrencyValue().multiply(aggregationRatio); + vmusage = new BigDecimal(usageRecord.getRawUsage()).multiply(onehourcostforvmusage); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), QuotaTypes.ALLOCATED_VM, vmusage, + usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + } + + usageRecord.setQuotaCalculated(1); + _usageDao.persistUsage(usageRecord); + return quota_usage; + } + + public QuotaUsageVO updateQuotaRaw(UsageVO usageRecord, final BigDecimal aggregationRatio, final int ruleType) { + QuotaUsageVO quota_usage = null; + QuotaTariffVO tariff = _quotaTariffDao.findTariffPlanByUsageType(ruleType, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal ruleusage; + BigDecimal onehourcost; + onehourcost = tariff.getCurrencyValue().multiply(aggregationRatio); + ruleusage = new BigDecimal(usageRecord.getRawUsage()).multiply(onehourcost); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), ruleType, ruleusage, + usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + } + + usageRecord.setQuotaCalculated(1); + _usageDao.persistUsage(usageRecord); + return quota_usage; + } + + public QuotaUsageVO updateQuotaNetwork(UsageVO usageRecord, final int transferType) { + QuotaUsageVO quota_usage = null; + QuotaTariffVO tariff = _quotaTariffDao.findTariffPlanByUsageType(transferType, usageRecord.getEndDate()); + if (tariff != null && tariff.getCurrencyValue().compareTo(BigDecimal.ZERO) != 0) { + BigDecimal onegbcost; + BigDecimal rawusageingb; + BigDecimal networkusage; + onegbcost = tariff.getCurrencyValue(); + rawusageingb = new BigDecimal(usageRecord.getRawUsage()).divide(s_gb, 8, RoundingMode.HALF_EVEN); + networkusage = rawusageingb.multiply(onegbcost); + quota_usage = new QuotaUsageVO(usageRecord.getId(), usageRecord.getZoneId(), usageRecord.getAccountId(), usageRecord.getDomainId(), transferType, networkusage, + usageRecord.getStartDate(), usageRecord.getEndDate()); + _quotaUsageDao.persistQuotaUsage(quota_usage); + } + + usageRecord.setQuotaCalculated(1); + _usageDao.persistUsage(usageRecord); + return quota_usage; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaStatement.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaStatement.java new file mode 100644 index 00000000000..e6f5e25eaea --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaStatement.java @@ -0,0 +1,26 @@ +//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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import java.util.Calendar; + +import com.cloud.utils.component.Manager; + +public interface QuotaStatement extends Manager { + void sendStatement(); + Calendar[] getCurrentStatementTime(); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/QuotaStatementImpl.java b/framework/quota/src/org/apache/cloudstack/quota/QuotaStatementImpl.java new file mode 100644 index 00000000000..682b2ef0366 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/QuotaStatementImpl.java @@ -0,0 +1,376 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.quota.QuotaAlertManagerImpl.DeferredQuotaEmail; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaUsageDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ManagerBase; + +@Component +@Local(value = QuotaStatement.class) +public class QuotaStatementImpl extends ManagerBase implements QuotaStatement { + private static final Logger s_logger = Logger.getLogger(QuotaStatementImpl.class); + + @Inject + private AccountDao _accountDao; + @Inject + private QuotaAccountDao _quotaAcc; + @Inject + private QuotaUsageDao _quotaUsage; + @Inject + private QuotaAlertManager _quotaAlert; + @Inject + private ConfigurationDao _configDao; + + final public static int s_LAST_STATEMENT_SENT_DAYS = 6; //ideally should be less than 7 days + + public enum STATEMENT_PERIODS { + BIMONTHLY, MONTHLY, QUATERLY, HALFYEARLY, YEARLY + }; + + private STATEMENT_PERIODS _period = STATEMENT_PERIODS.MONTHLY; + + public QuotaStatementImpl() { + super(); + } + + private void mergeConfigs(Map dbParams, Map xmlParams) { + for (Map.Entry param : xmlParams.entrySet()) { + dbParams.put(param.getKey(), (String)param.getValue()); + } + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + Map configs = _configDao.getConfiguration(params); + + if (params != null) { + mergeConfigs(configs, params); + } + String period_str = configs.get(QuotaConfig.QuotaStatementPeriod.key()); + int period = period_str == null ? 1 : Integer.valueOf(period_str); + + STATEMENT_PERIODS _period = STATEMENT_PERIODS.values()[period]; + return true; + } + + @Override + public boolean start() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Starting Statement Manager"); + } + return true; + } + + @Override + public boolean stop() { + if (s_logger.isInfoEnabled()) { + s_logger.info("Stopping Statement Manager"); + } + return true; + } + + @Override + public void sendStatement() { + + List deferredQuotaEmailList = new ArrayList(); + for (final QuotaAccountVO quotaAccount : _quotaAcc.listAllQuotaAccount()) { + if (quotaAccount.getQuotaBalance() == null) { + continue; // no quota usage for this account ever, ignore + } + + //check if it is statement time + Calendar interval[] = statementTime(Calendar.getInstance(), _period); + + Date lastStatementDate = quotaAccount.getLastStatementDate(); + if (interval != null) { + AccountVO account = _accountDao.findById(quotaAccount.getId()); + if (lastStatementDate == null || getDifferenceDays(lastStatementDate, new Date()) >= s_LAST_STATEMENT_SENT_DAYS + 1) { + BigDecimal quotaUsage = _quotaUsage.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, interval[0].getTime(), interval[1].getTime()); + s_logger.info("For account=" + quotaAccount.getId() + ", quota used = " + quotaUsage); + // send statement + deferredQuotaEmailList.add(new DeferredQuotaEmail(account, quotaAccount, quotaUsage, QuotaConfig.QuotaEmailTemplateTypes.QUOTA_STATEMENT)); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("For " + quotaAccount.getId() + " the statement has been sent recently"); + + } + } + } else if (lastStatementDate != null) { + s_logger.info("For " + quotaAccount.getId() + " it is already more than " + getDifferenceDays(lastStatementDate, new Date()) + + " days, will send statement in next cycle"); + } + } + + for (DeferredQuotaEmail emailToBeSent : deferredQuotaEmailList) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Attempting to send quota STATEMENT email to users of account: " + emailToBeSent.getAccount().getAccountName()); + } + _quotaAlert.sendQuotaAlert(emailToBeSent); + } + } + + @Override + public Calendar[] getCurrentStatementTime() { + final Calendar today = Calendar.getInstance(); + int day_of_month = today.get(Calendar.DAY_OF_MONTH); + int month_of_year = today.get(Calendar.MONTH); + + Calendar firstDateOfCurrentPeriod, lastDateOfCurrentPeriod; + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + lastDateOfCurrentPeriod = aCalendar; + + switch (_period) { + case BIMONTHLY: + if (day_of_month < 16) { + aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, 0); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } else { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.DATE, 16); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } + case MONTHLY: + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + case QUATERLY: + if (month_of_year < Calendar.APRIL) { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.JANUARY); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } else if (month_of_year < Calendar.JULY) { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.APRIL); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } else if (month_of_year < Calendar.OCTOBER) { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.JULY); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } else { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.OCTOBER); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } + case HALFYEARLY: + // statements are sent in Jan=1, Jul 7, + if (month_of_year < Calendar.JULY) { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.JANUARY); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } else { + aCalendar = (Calendar)today.clone(); + aCalendar.set(Calendar.MONTH, Calendar.JULY); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + } + case YEARLY: + aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, Calendar.JANUARY); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfCurrentPeriod = aCalendar; + return new Calendar[] {firstDateOfCurrentPeriod, lastDateOfCurrentPeriod}; + default: + break; + } + return null; + } + + public Calendar[] statementTime(final Calendar today, final STATEMENT_PERIODS period) { + //check if it is statement time + int day_of_month = today.get(Calendar.DAY_OF_MONTH); + int month_of_year = today.get(Calendar.MONTH); + Calendar firstDateOfPreviousPeriod, lastDateOfPreviousPeriod; + switch (period) { + case BIMONTHLY: + if (day_of_month < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, 0); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.set(Calendar.DATE, 15); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } else if (day_of_month > 15 && (day_of_month - 15) < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, -1); + aCalendar.set(Calendar.DATE, 16); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } + return null; + case MONTHLY: + if (day_of_month < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, -1); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } + return null; + case QUATERLY: + // statements are sent in Jan=1, Apr 4, Jul 7, Oct 10 + if (month_of_year == Calendar.JANUARY || month_of_year == Calendar.APRIL || month_of_year == Calendar.JULY || month_of_year == Calendar.OCTOBER) { + if (day_of_month < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, -3); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.add(Calendar.MONTH, 2); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } + } + return null; + case HALFYEARLY: + // statements are sent in Jan=1, Jul 7, + if (month_of_year == Calendar.JANUARY || month_of_year == Calendar.JULY) { + if (day_of_month < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, -6); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.add(Calendar.MONTH, 5); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } + } + return null; + case YEARLY: + // statements are sent in Jan=1 + if (month_of_year == Calendar.JANUARY) { + if (day_of_month < s_LAST_STATEMENT_SENT_DAYS) { + Calendar aCalendar = (Calendar)today.clone(); + aCalendar.add(Calendar.MONTH, -12); + aCalendar.set(Calendar.DATE, 1); + aCalendar.set(Calendar.HOUR, 0); + aCalendar.set(Calendar.MINUTE, 0); + aCalendar.set(Calendar.SECOND, 0); + firstDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + aCalendar.add(Calendar.MONTH, 11); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + lastDateOfPreviousPeriod = (Calendar)aCalendar.clone(); + return new Calendar[] {firstDateOfPreviousPeriod, lastDateOfPreviousPeriod}; + } + } + return null; + default: + break; + } + return null; + } + + public static long getDifferenceDays(Date d1, Date d2) { + long diff = d2.getTime() - d1.getTime(); + return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaConfig.java b/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaConfig.java new file mode 100644 index 00000000000..73c9a80e3c9 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaConfig.java @@ -0,0 +1,57 @@ +//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.constant; + +import org.apache.cloudstack.framework.config.ConfigKey; + +public interface QuotaConfig { + + public static final ConfigKey QuotaPluginEnabled = new ConfigKey("Advanced", Boolean.class, "quota.enable.service", "false", + "Indicates whether Quota plugin is enabled or not", true); + + public static final ConfigKey QuotaEnableEnforcement = new ConfigKey("Advanced", String.class, "quota.enable.enforcement", "false", + "Enable the usage quota enforcement, i.e. on true when exceeding quota the respective account will be locked.", true); + + public static final ConfigKey QuotaCurrencySymbol = new ConfigKey("Advanced", String.class, "quota.currency.symbol", "$", + "The symbol for the currency in use to measure usage.", true); + + public static final ConfigKey QuotaStatementPeriod = new ConfigKey("Advanced", Integer.class, "quota.statement.period", "1", + "This variables define the statement generation interval. Values correspond to bimonthly=0, monthly=1, quarterly=2, half-yearly=3 and yearly=4.", true); + + public static final ConfigKey QuotaSmtpHost = new ConfigKey("Advanced", String.class, "quota.usage.smtp.host", "", "Quota SMTP host for quota related emails", + true); + + public static final ConfigKey QuotaSmtpTimeout = new ConfigKey("Advanced", String.class, "quota.usage.smtp.connection.timeout", "60", + "Quota SMTP server connection timeout duration", true); + + public static final ConfigKey QuotaSmtpUser = new ConfigKey("Advanced", String.class, "quota.usage.smtp.user", "", "Quota SMTP server username", true); + + public static final ConfigKey QuotaSmtpPassword = new ConfigKey("Advanced", String.class, "quota.usage.smtp.password", "", "Quota SMTP server password", true); + + public static final ConfigKey QuotaSmtpPort = new ConfigKey("Advanced", String.class, "quota.usage.smtp.port", "", "Quota SMTP port", true); + + public static final ConfigKey QuotaSmtpAuthType = new ConfigKey("Advanced", String.class, "quota.usage.smtp.useAuth", "", + "If true, use secure SMTP authentication when sending emails.", true); + + public static final ConfigKey QuotaSmtpSender = new ConfigKey("Advanced", String.class, "quota.usage.smtp.sender", "", + "Sender of quota alert email (will be in the From header of the email)", true); + + enum QuotaEmailTemplateTypes { + QUOTA_LOW, QUOTA_EMPTY, QUOTA_UNLOCK_ACCOUNT, QUOTA_STATEMENT + } +} \ No newline at end of file diff --git a/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaTypes.java b/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaTypes.java new file mode 100644 index 00000000000..36910f47741 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/constant/QuotaTypes.java @@ -0,0 +1,103 @@ +// 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.constant; + +import org.apache.cloudstack.usage.UsageTypes; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class QuotaTypes extends UsageTypes { + public static final int CPU_CLOCK_RATE = 15; + public static final int CPU_NUMBER = 16; + public static final int MEMORY = 17; + + private final Integer quotaType; + private final String quotaName; + private final String quotaUnit; + private final String description; + private final String discriminator; + private final static Map quotaTypeMap; + + static { + final HashMap quotaTypeList = new HashMap(); + quotaTypeList.put(RUNNING_VM, new QuotaTypes(RUNNING_VM, "RUNNING_VM", "Compute-Month", "Running Vm Usage")); + quotaTypeList.put(ALLOCATED_VM, new QuotaTypes(ALLOCATED_VM, "ALLOCATED_VM", "Compute-Month", "Allocated Vm Usage")); + quotaTypeList.put(IP_ADDRESS, new QuotaTypes(IP_ADDRESS, "IP_ADDRESS", "IP-Month", "IP Address Usage")); + quotaTypeList.put(NETWORK_BYTES_SENT, new QuotaTypes(NETWORK_BYTES_SENT, "NETWORK_BYTES_SENT", "GB", "Network Usage (Bytes Sent)")); + quotaTypeList.put(NETWORK_BYTES_RECEIVED, new QuotaTypes(NETWORK_BYTES_RECEIVED, "NETWORK_BYTES_RECEIVED", "GB", "Network Usage (Bytes Received)")); + quotaTypeList.put(VOLUME, new QuotaTypes(VOLUME, "VOLUME", "GB-Month", "Volume Usage")); + quotaTypeList.put(TEMPLATE, new QuotaTypes(TEMPLATE, "TEMPLATE", "GB-Month", "Template Usage")); + quotaTypeList.put(ISO, new QuotaTypes(ISO, "ISO", "GB-Month", "ISO Usage")); + quotaTypeList.put(SNAPSHOT, new QuotaTypes(SNAPSHOT, "SNAPSHOT", "GB-Month", "Snapshot Usage")); + quotaTypeList.put(SECURITY_GROUP, new QuotaTypes(SECURITY_GROUP, "SECURITY_GROUP", "Policy-Month", "Security Group Usage")); + quotaTypeList.put(LOAD_BALANCER_POLICY, new QuotaTypes(LOAD_BALANCER_POLICY, "LOAD_BALANCER_POLICY", "Policy-Month", "Load Balancer Usage")); + quotaTypeList.put(PORT_FORWARDING_RULE, new QuotaTypes(PORT_FORWARDING_RULE, "PORT_FORWARDING_RULE", "Policy-Month", "Port Forwarding Usage")); + quotaTypeList.put(NETWORK_OFFERING, new QuotaTypes(NETWORK_OFFERING, "NETWORK_OFFERING", "Policy-Month", "Network Offering Usage")); + quotaTypeList.put(VPN_USERS, new QuotaTypes(VPN_USERS, "VPN_USERS", "Policy-Month", "VPN users usage")); + quotaTypeList.put(VM_DISK_IO_READ, new QuotaTypes(VM_DISK_IO_READ, "VM_DISK_IO_READ", "GB", "VM Disk usage(I/O Read)")); + quotaTypeList.put(VM_DISK_IO_WRITE, new QuotaTypes(VM_DISK_IO_WRITE, "VM_DISK_IO_WRITE", "GB", "VM Disk usage(I/O Write)")); + quotaTypeList.put(VM_DISK_BYTES_READ, new QuotaTypes(VM_DISK_BYTES_READ, "VM_DISK_BYTES_READ", "GB", "VM Disk usage(Bytes Read)")); + quotaTypeList.put(VM_DISK_BYTES_WRITE, new QuotaTypes(VM_DISK_BYTES_WRITE, "VPN_USERS", "GB", "VM Disk usage(Bytes Write)")); + quotaTypeList.put(VM_SNAPSHOT, new QuotaTypes(VM_SNAPSHOT, "VM_SNAPSHOT", "GB-Month", "VM Snapshot storage usage")); + quotaTypeList.put(CPU_CLOCK_RATE, new QuotaTypes(CPU_CLOCK_RATE, "CPU_CLOCK_RATE", "Compute-Month", "Quota tariff for using 1 CPU of clock rate 100MHz")); + quotaTypeList.put(CPU_NUMBER, new QuotaTypes(CPU_NUMBER, "CPU_NUMBER", "Compute-Month", "Quota tariff for running VM that has 1vCPU")); + quotaTypeList.put(MEMORY, new QuotaTypes(MEMORY, "MEMORY", "Compute-Month", "Quota tariff for using 1MB or RAM for 1 hour")); + quotaTypeMap = Collections.unmodifiableMap(quotaTypeList); + } + + private QuotaTypes(Integer quotaType, String name, String unit, String description) { + this.quotaType = quotaType; + this.description = description; + this.quotaName = name; + this.quotaUnit = unit; + this.discriminator = "None"; + } + + public static Map listQuotaTypes() { + return quotaTypeMap; + } + + public String getDiscriminator() { + return discriminator; + } + + public String getQuotaName() { + return quotaName; + } + + public String getQuotaUnit() { + return quotaUnit; + } + + public String getDescription() { + return description; + } + + public Integer getQuotaType() { + return quotaType; + } + + static public String getDescription(int quotaType) { + QuotaTypes t = quotaTypeMap.get(quotaType); + if (t != null) { + return t.getDescription(); + } + return null; + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDao.java new file mode 100644 index 00000000000..d1b441b831f --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDao.java @@ -0,0 +1,35 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.List; + +import org.apache.cloudstack.quota.vo.QuotaAccountVO; + +import com.cloud.utils.db.GenericDao; + +public interface QuotaAccountDao extends GenericDao { + + List listAllQuotaAccount(); + + QuotaAccountVO findByIdQuotaAccount(Long id); + + QuotaAccountVO persistQuotaAccount(QuotaAccountVO entity); + + boolean updateQuotaAccount(Long id, QuotaAccountVO entity); + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java new file mode 100644 index 00000000000..e3de1889de9 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaAccountDaoImpl.java @@ -0,0 +1,74 @@ +//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.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; + +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; + +import java.util.List; + +@Component +@Local(value = { QuotaAccountDao.class }) +public class QuotaAccountDaoImpl extends GenericDaoBase implements QuotaAccountDao { + public static final Logger s_logger = Logger.getLogger(QuotaAccountDaoImpl.class); + + public List listAllQuotaAccount() { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + return listAll(); + } + }); + } + + public QuotaAccountVO findByIdQuotaAccount(final Long id) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaAccountVO doInTransaction(final TransactionStatus status) { + return findById(id); + } + }); + } + + public QuotaAccountVO persistQuotaAccount(final QuotaAccountVO entity) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaAccountVO doInTransaction(final TransactionStatus status) { + return persist(entity); + } + }); + } + + public boolean updateQuotaAccount(final Long id, final QuotaAccountVO entity) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + return update(id, entity); + } + }); + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java new file mode 100644 index 00000000000..c694eaeefbe --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java @@ -0,0 +1,43 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.dao; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; + +import com.cloud.utils.db.GenericDao; + +public interface QuotaBalanceDao extends GenericDao { + + QuotaBalanceVO saveQuotaBalance(QuotaBalanceVO qb); + + List findCreditBalance(Long accountId, Long domainId, Date startDate, Date endDate); + + QuotaBalanceVO findLastBalanceEntry(Long accountId, Long domainId, Date beforeThis); + + QuotaBalanceVO findLaterBalanceEntry(Long accountId, Long domainId, Date afterThis); + + List findQuotaBalance(Long accountId, Long domainId, Date startDate, Date endDate); + + List lastQuotaBalanceVO(Long accountId, Long domainId, Date startDate); + + BigDecimal lastQuotaBalance(Long accountId, Long domainId, Date startDate); + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java new file mode 100644 index 00000000000..aa650e165ad --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java @@ -0,0 +1,189 @@ +//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.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +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 com.cloud.utils.db.TransactionStatus; + +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Component +@Local(value = {QuotaBalanceDao.class}) +public class QuotaBalanceDaoImpl extends GenericDaoBase implements QuotaBalanceDao { + private static final Logger s_logger = Logger.getLogger(QuotaBalanceDaoImpl.class.getName()); + + public QuotaBalanceVO findLastBalanceEntry(final Long accountId, final Long domainId, final Date beforeThis) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaBalanceVO doInTransaction(final TransactionStatus status) { + List quotaBalanceEntries = new ArrayList<>(); + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 1L); + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LT, beforeThis); + quotaBalanceEntries = search(qb.create(), filter); + return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null; + } + }); + } + + public QuotaBalanceVO findLaterBalanceEntry(final Long accountId, final Long domainId, final Date afterThis) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaBalanceVO doInTransaction(final TransactionStatus status) { + List quotaBalanceEntries = new ArrayList<>(); + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, 1L); + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT, afterThis); + quotaBalanceEntries = search(qb.create(), filter); + return quotaBalanceEntries.size() > 0 ? quotaBalanceEntries.get(0) : null; + } + }); + } + + public QuotaBalanceVO saveQuotaBalance(final QuotaBalanceVO qb) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaBalanceVO doInTransaction(final TransactionStatus status) { + return persist(qb); + } + }); + } + + public List findCreditBalance(final Long accountId, final Long domainId, final Date lastbalancedate, final Date beforeThis) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + if ((lastbalancedate != null) && (beforeThis != null) && lastbalancedate.before(beforeThis)) { + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, Long.MAX_VALUE); + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, lastbalancedate, beforeThis); + return search(qb.create(), filter); + } else { + return new ArrayList(); + } + } + }); + } + + public List findQuotaBalance(final Long accountId, final Long domainId, final Date startDate, final Date endDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + List quotaUsageRecords = null; + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + if (accountId != null) { + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + } + if (domainId != null) { + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + } + if ((startDate != null) && (endDate != null) && startDate.before(endDate)) { + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate); + } else { + return Collections. emptyList(); + } + quotaUsageRecords = listBy(qb.create()); + if (quotaUsageRecords.size() == 0) { + quotaUsageRecords.addAll(lastQuotaBalanceVO(accountId, domainId, startDate)); + } + return quotaUsageRecords; + + } + }); + + } + + public List lastQuotaBalanceVO(final Long accountId, final Long domainId, final Date pivotDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + List quotaUsageRecords = null; + List trimmedRecords = new ArrayList(); + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 100L); + // ASSUMPTION there will be less than 100 continuous credit + // transactions + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + if (accountId != null) { + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + } + if (domainId != null) { + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + } + if ((pivotDate != null)) { + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ, pivotDate); + } + quotaUsageRecords = search(qb.create(), filter); + + // get records before startDate to find start balance + for (QuotaBalanceVO entry : quotaUsageRecords) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("FindQuotaBalIance Entry=" + entry); + } + if (entry.getCreditsId() > 0) { + trimmedRecords.add(entry); + } else { + trimmedRecords.add(entry); + break; // add only consecutive credit entries and last balance entry + } + } + return trimmedRecords; + } + }); + } + + public BigDecimal lastQuotaBalance(final Long accountId, final Long domainId, Date startDate) { + List quotaBalance = lastQuotaBalanceVO(accountId, domainId, startDate); + BigDecimal finalBalance = new BigDecimal(0); + if (quotaBalance.isEmpty()) { + s_logger.info("There are no balance entries on or before the requested date."); + return finalBalance; + } + for (QuotaBalanceVO entry : quotaBalance) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("lastQuotaBalance Entry=" + entry); + } + finalBalance = finalBalance.add(entry.getCreditBalance()); + } + return finalBalance; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java new file mode 100644 index 00000000000..f08d8f96ca8 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java @@ -0,0 +1,32 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.quota.vo.QuotaCreditsVO; + +import com.cloud.utils.db.GenericDao; + +public interface QuotaCreditsDao extends GenericDao { + + List findCredits(long accountId, long domainId, Date startDate, Date endDate); + + QuotaCreditsVO saveCredits(QuotaCreditsVO credits); + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java new file mode 100644 index 00000000000..4b777104145 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java @@ -0,0 +1,78 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaCreditsVO; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +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 com.cloud.utils.db.TransactionStatus; + +@Component +@Local(value = { QuotaCreditsDao.class }) +public class QuotaCreditsDaoImpl extends GenericDaoBase implements QuotaCreditsDao { + + @Inject + QuotaBalanceDao _quotaBalanceDao; + + @Override + public List findCredits(final long accountId, final long domainId, final Date startDate, final Date endDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + if ((startDate != null) && (endDate != null) && startDate.before(endDate)) { + Filter filter = new Filter(QuotaCreditsVO.class, "updatedOn", true, 0L, Long.MAX_VALUE); + QueryBuilder qb = QueryBuilder.create(QuotaCreditsVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate); + return search(qb.create(), filter); + } else { + return Collections. emptyList(); + } + } + }); + } + + @Override + public QuotaCreditsVO saveCredits(final QuotaCreditsVO credits) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaCreditsVO doInTransaction(final TransactionStatus status) { + persist(credits); + // make an entry in the balance table + QuotaBalanceVO bal = new QuotaBalanceVO(credits); + _quotaBalanceDao.persist(bal); + return credits; + } + }); + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java new file mode 100644 index 00000000000..573a7539744 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDao.java @@ -0,0 +1,27 @@ +//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.QuotaEmailTemplatesVO; + +import java.util.List; + +public interface QuotaEmailTemplatesDao extends GenericDao { + List listAllQuotaEmailTemplates(String templateName); + boolean updateQuotaEmailTemplate(QuotaEmailTemplatesVO template); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java new file mode 100644 index 00000000000..a971603c577 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaEmailTemplatesDaoImpl.java @@ -0,0 +1,74 @@ +//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.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 com.cloud.utils.db.TransactionStatus; +import com.google.common.base.Strings; + +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; + +import java.util.List; + +@Component +@Local(value = { QuotaEmailTemplatesDao.class }) +public class QuotaEmailTemplatesDaoImpl extends GenericDaoBase implements QuotaEmailTemplatesDao { + private static final Logger s_logger = Logger.getLogger(QuotaEmailTemplatesDaoImpl.class); + + protected SearchBuilder QuotaEmailTemplateSearch; + + public QuotaEmailTemplatesDaoImpl() { + super(); + + QuotaEmailTemplateSearch = createSearchBuilder(); + QuotaEmailTemplateSearch.and("template_name", QuotaEmailTemplateSearch.entity().getTemplateName(), SearchCriteria.Op.EQ); + QuotaEmailTemplateSearch.done(); + } + + @Override + public List listAllQuotaEmailTemplates(final String templateName) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + SearchCriteria sc = QuotaEmailTemplateSearch.create(); + if (!Strings.isNullOrEmpty(templateName)) { + sc.setParameters("template_name", templateName); + } + return listBy(sc); + } + }); + } + + @Override + public boolean updateQuotaEmailTemplate(final QuotaEmailTemplatesVO template) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + return update(template.getId(), template); + } + }); + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDao.java new file mode 100644 index 00000000000..fda2cf67caf --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDao.java @@ -0,0 +1,37 @@ +//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.QuotaTariffVO; + +import java.util.Date; +import java.util.List; + +public interface QuotaTariffDao extends GenericDao { + + QuotaTariffVO findTariffPlanByUsageType(int quotaType, Date onOrBefore); + + List listAllTariffPlans(); + + List listAllTariffPlans(Date onOrBefore); + + Boolean updateQuotaTariff(QuotaTariffVO plan); + + QuotaTariffVO addQuotaTariff(QuotaTariffVO plan); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java new file mode 100644 index 00000000000..294b404d928 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaTariffDaoImpl.java @@ -0,0 +1,133 @@ +//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.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.TransactionStatus; +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Component +@Local(value = {QuotaTariffDao.class}) +public class QuotaTariffDaoImpl extends GenericDaoBase implements QuotaTariffDao { + private static final Logger s_logger = Logger.getLogger(QuotaTariffDaoImpl.class.getName()); + + private final SearchBuilder searchUsageType; + private final SearchBuilder listAllIncludedUsageType; + + public QuotaTariffDaoImpl() { + super(); + searchUsageType = createSearchBuilder(); + searchUsageType.and("usage_type", searchUsageType.entity().getUsageType(), SearchCriteria.Op.EQ); + searchUsageType.done(); + + listAllIncludedUsageType = createSearchBuilder(); + listAllIncludedUsageType.and("onorbefore", listAllIncludedUsageType.entity().getEffectiveOn(), SearchCriteria.Op.LTEQ); + listAllIncludedUsageType.and("quotatype", listAllIncludedUsageType.entity().getUsageType(), SearchCriteria.Op.EQ); + listAllIncludedUsageType.done(); + } + + public QuotaTariffVO findTariffPlanByUsageType(final int quotaType, final Date effectiveDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaTariffVO doInTransaction(final TransactionStatus status) { + List result = new ArrayList<>(); + final Filter filter = new Filter(QuotaTariffVO.class, "updatedOn", false, 0L, 1L); + final SearchCriteria sc = listAllIncludedUsageType.create(); + sc.setParameters("onorbefore", effectiveDate); + sc.setParameters("quotatype", quotaType); + result = search(sc, filter); + if (result != null && !result.isEmpty()) { + return result.get(0); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("QuotaTariffDaoImpl::findTariffPlanByUsageType: Missing quota type " + quotaType); + } + return null; + } + } + }); + } + + public List listAllTariffPlans() { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + return listAll(); + } + }); + } + + public List listAllTariffPlans(final Date effectiveDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + List tariffs = new ArrayList(); + final Filter filter = new Filter(QuotaTariffVO.class, "updatedOn", false, 0L, 1L); + final SearchCriteria sc = listAllIncludedUsageType.create(); + sc.setParameters("onorbefore", effectiveDate); + for (Integer quotaType : QuotaTypes.listQuotaTypes().keySet()) { + sc.setParameters("quotatype", quotaType); + List result = search(sc, filter); + if (result != null && !result.isEmpty()) { + tariffs.add(result.get(0)); + if (s_logger.isDebugEnabled()) { + s_logger.debug("ListAllTariffPlans on or before " + effectiveDate + " quota type " + result.get(0).getDescription() + " , effective Date=" + + result.get(0).getEffectiveOn() + " val=" + result.get(0).getCurrencyValue()); + } + } + } + return tariffs; + } + }); + } + + public Boolean updateQuotaTariff(final QuotaTariffVO plan) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + return update(plan.getId(), plan); + } + }); + } + + public QuotaTariffVO addQuotaTariff(final QuotaTariffVO plan) { + if (plan.getIdObj() != null) { + throw new IllegalStateException("The QuotaTariffVO being added should not have an Id set "); + } + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaTariffVO doInTransaction(final TransactionStatus status) { + return persist(plan); + } + }); + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDao.java new file mode 100644 index 00000000000..d0c984c6f6b --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDao.java @@ -0,0 +1,35 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +public interface QuotaUsageDao extends GenericDao { + + QuotaUsageVO persistQuotaUsage(QuotaUsageVO quotaUsage); + + List findQuotaUsage(Long accountId, Long domainId, Integer usageType, Date startDate, Date endDate); + + BigDecimal findTotalQuotaUsage(Long accountId, Long domainId, Integer usageType, Date startDate, Date endDate); + + QuotaUsageVO findLastQuotaUsageEntry(Long accountId, Long domainId, Date beforeThis); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDaoImpl.java new file mode 100644 index 00000000000..8c0fae6391e --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/QuotaUsageDaoImpl.java @@ -0,0 +1,116 @@ +//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.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +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 com.cloud.utils.db.TransactionStatus; + +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Component +@Local(value = {QuotaUsageDao.class}) +public class QuotaUsageDaoImpl extends GenericDaoBase implements QuotaUsageDao { + private static final Logger s_logger = Logger.getLogger(QuotaUsageDaoImpl.class); + + public BigDecimal findTotalQuotaUsage(final Long accountId, final Long domainId, final Integer usageType, final Date startDate, final Date endDate) { + List quotaUsage = findQuotaUsage(accountId, domainId, null, startDate, endDate); + BigDecimal total = new BigDecimal(0); + for (QuotaUsageVO quotaRecord : quotaUsage) { + total = total.add(quotaRecord.getQuotaUsed()); + } + return total; + } + + public List findQuotaUsage(final Long accountId, final Long domainId, final Integer usageType, final Date startDate, final Date endDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { + @Override + public List doInTransaction(final TransactionStatus status) { + List quv; + if ((startDate != null) && (endDate != null) && startDate.before(endDate)) { + QueryBuilder qb = QueryBuilder.create(QuotaUsageVO.class); + if (accountId != null) { + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + } + if (domainId != null) { + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + } + if (usageType != null) { + qb.and(qb.entity().getUsageType(), SearchCriteria.Op.EQ, usageType); + } + qb.and(qb.entity().getStartDate(), SearchCriteria.Op.BETWEEN, startDate, endDate); + qb.and(qb.entity().getEndDate(), SearchCriteria.Op.BETWEEN, startDate, endDate); + quv = listBy(qb.create()); + } else { + quv = new ArrayList(); + } + if (quv.isEmpty()){ + //add a dummy entry + QuotaUsageVO qu = new QuotaUsageVO(); + qu.setAccountId(accountId); + qu.setDomainId(domainId); + qu.setStartDate(startDate); + qu.setEndDate(endDate); + qu.setQuotaUsed(new BigDecimal(0)); + qu.setUsageType(-1); + quv.add(qu); + } + return quv; + } + }); + } + + public QuotaUsageVO findLastQuotaUsageEntry(final Long accountId, final Long domainId, final Date beforeThis) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaUsageVO doInTransaction(final TransactionStatus status) { + List quotaUsageEntries = new ArrayList<>(); + Filter filter = new Filter(QuotaUsageVO.class, "startDate", false, 0L, 1L); + QueryBuilder qb = QueryBuilder.create(QuotaUsageVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + qb.and(qb.entity().getStartDate(), SearchCriteria.Op.LT, beforeThis); + quotaUsageEntries = search(qb.create(), filter); + return !quotaUsageEntries.isEmpty() ? quotaUsageEntries.get(0) : null; + } + }); + } + + public QuotaUsageVO persistQuotaUsage(final QuotaUsageVO quotaUsage) { + return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { + @Override + public QuotaUsageVO doInTransaction(final TransactionStatus status) { + return persist(quotaUsage); + } + }); + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDao.java new file mode 100644 index 00000000000..8353977aa0d --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDao.java @@ -0,0 +1,25 @@ +// 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 org.apache.cloudstack.quota.vo.ServiceOfferingVO; + +import com.cloud.utils.db.GenericDao; + +public interface ServiceOfferingDao extends GenericDao { + ServiceOfferingVO findServiceOffering(Long vmId, long serviceOfferingId); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDaoImpl.java new file mode 100644 index 00000000000..1d8b1b6b648 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/ServiceOfferingDaoImpl.java @@ -0,0 +1,84 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.Map; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; +import org.apache.cloudstack.quota.vo.ServiceOfferingVO; + +import com.cloud.event.UsageEventVO; +import com.cloud.utils.db.DB; +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 com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +@Local(value = {ServiceOfferingDao.class}) +@DB() +public class ServiceOfferingDaoImpl extends GenericDaoBase implements ServiceOfferingDao { + protected static final Logger s_logger = Logger.getLogger(ServiceOfferingDaoImpl.class); + + @Inject + UserVmDetailsDao userVmDetailsDao; + + public ServiceOfferingVO findServiceOffering(final Long vmId, final long serviceOfferingId) { + return Transaction.execute(TransactionLegacy.CLOUD_DB, new TransactionCallback() { + @Override + public ServiceOfferingVO doInTransaction(final TransactionStatus status) { + ServiceOfferingVO offering = findById(serviceOfferingId); + if (offering.isDynamic()) { + if (vmId == null) { + throw new CloudRuntimeException("missing argument vmId"); + } + offering.setDynamicFlag(true); + Map dynamicOffering = userVmDetailsDao.listDetailsKeyPairs(vmId); + return getcomputeOffering(offering, dynamicOffering); + } + return offering; + } + }); + } + + private ServiceOfferingVO getcomputeOffering(final ServiceOfferingVO serviceOffering, final Map customParameters) { + return Transaction.execute(TransactionLegacy.CLOUD_DB, new TransactionCallback() { + @Override + public ServiceOfferingVO doInTransaction(final TransactionStatus status) { + ServiceOfferingVO dummyoffering = new ServiceOfferingVO(serviceOffering); + dummyoffering.setDynamicFlag(true); + if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuNumber.name())) { + dummyoffering.setCpu(Integer.parseInt(customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()))); + } + if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { + dummyoffering.setSpeed(Integer.parseInt(customParameters.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))); + } + if (customParameters.containsKey(UsageEventVO.DynamicParameters.memory.name())) { + dummyoffering.setRamSize(Integer.parseInt(customParameters.get(UsageEventVO.DynamicParameters.memory.name()))); + } + return dummyoffering; + } + }); + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDao.java b/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDao.java new file mode 100644 index 00000000000..f8ab3b9dbc4 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDao.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.Map; + +import com.cloud.utils.db.GenericDao; + +import org.apache.cloudstack.quota.vo.UserVmDetailVO; + +public interface UserVmDetailsDao extends GenericDao { + Map listDetailsKeyPairs(long resourceId); +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDaoImpl.java b/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDaoImpl.java new file mode 100644 index 00000000000..beb3cdfbcb1 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/dao/UserVmDetailsDaoImpl.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import org.springframework.stereotype.Component; +import org.apache.cloudstack.quota.vo.UserVmDetailVO; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@Local(value = UserVmDetailsDao.class) +public class UserVmDetailsDaoImpl extends GenericDaoBase implements UserVmDetailsDao { + private SearchBuilder AllFieldsSearch; + + public UserVmDetailsDaoImpl() { + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("resourceId", AllFieldsSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("value", AllFieldsSearch.entity().getValue(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("display", AllFieldsSearch.entity().isDisplay(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + } + + @Override + public Map listDetailsKeyPairs(long resourceId) { + Map details = new HashMap(); + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("resourceId", resourceId); + + List results = search(sc, null); + for (UserVmDetailVO result : results) { + details.put(result.getName(), result.getValue()); + } + return details; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaAccountVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaAccountVO.java new file mode 100644 index 00000000000..00bc33a98dc --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaAccountVO.java @@ -0,0 +1,149 @@ +//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 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_account") +public class QuotaAccountVO implements InternalIdentity { + + private static final long serialVersionUID = -7112846845287653210L; + + @Id + @Column(name = "account_id") + private Long accountId = null; + + @Column(name = "quota_enforce") + private Integer quotaEnforce = 0; + + @Column(name = "quota_balance") + private BigDecimal quotaBalance; + + @Column(name = "quota_balance_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date quotaBalanceDate = null; + + @Column(name = "quota_min_balance") + private BigDecimal quotaMinBalance; + + @Column(name = "quota_alert_type") + private Integer quotaAlertType = null; + + @Column(name = "quota_alert_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date quotaAlertDate = null; + + @Column(name = "last_statement_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date lastStatementDate = null; + + public QuotaAccountVO() { + } + + public QuotaAccountVO(Long accountId) { + super(); + this.accountId = accountId; + } + + @Override + public long getId() { + return accountId; + } + + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public Integer getQuotaEnforce() { + return quotaEnforce == null ? 0 : quotaEnforce; + } + + public void setQuotaEnforce(Integer quotaEnforce) { + this.quotaEnforce = quotaEnforce; + } + + public BigDecimal getQuotaBalance() { + return quotaBalance; + } + + public void setQuotaBalance(BigDecimal quotaBalance) { + this.quotaBalance = quotaBalance; + } + + public BigDecimal getQuotaMinBalance() { + return quotaMinBalance == null ? new BigDecimal(0) : quotaMinBalance; + } + + public void setQuotaMinBalance(BigDecimal quotaMinBalance) { + this.quotaMinBalance = quotaMinBalance; + } + + public Integer getQuotaAlertType() { + return quotaAlertType; + } + + public void setQuotaAlertType(Integer quotaAlertType) { + this.quotaAlertType = quotaAlertType; + } + + public Date getQuotaAlertDate() { + return quotaAlertDate == null ? null : new Date(quotaAlertDate.getTime()); + } + + public void setQuotaAlertDate(Date quotaAlertDate) { + this.quotaAlertDate = quotaAlertDate == null ? null : new Date(quotaAlertDate.getTime()); + } + + public Date getQuotaBalanceDate() { + return quotaBalanceDate == null ? null : new Date(quotaBalanceDate.getTime()); + } + + public void setQuotaBalanceDate(Date quotaBalanceDate) { + this.quotaBalanceDate = quotaBalanceDate == null ? null : new Date(quotaBalanceDate.getTime()); + } + + public Date getLastStatementDate() { + return lastStatementDate == null ? null : new Date(lastStatementDate.getTime()); + } + + public void setLastStatementDate(Date lastStatementDate) { + this.lastStatementDate = lastStatementDate == null ? null : new Date(lastStatementDate.getTime()); + } + + @Override + public String toString() { + return "QuotaAccountVO [accountId=" + accountId + ", quotaEnforce=" + quotaEnforce + ", quotaBalance=" + quotaBalance + ", quotaBalanceDate=" + quotaBalanceDate + + ", quotaMinBalance=" + quotaMinBalance + ", quotaAlertType=" + quotaAlertType + ", quotaAlertDate=" + quotaAlertDate + ", lastStatementDate=" + lastStatementDate + + "]"; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java new file mode 100644 index 00000000000..b454a14b925 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java @@ -0,0 +1,133 @@ +//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 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_balance") +public class QuotaBalanceVO implements InternalIdentity { + + private static final long serialVersionUID = -7112846845287653210L; + + @Id + @Column(name = "id") + private Long id; + + @Column(name = "account_id") + private Long accountId = null; + + @Column(name = "domain_id") + private Long domainId = null; + + @Column(name = "credit_balance") + private BigDecimal creditBalance; + + @Column(name = "credits_id") + private Long creditsId; + + @Column(name = "updated_on") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updatedOn = null; + + public QuotaBalanceVO() { + } + + public QuotaBalanceVO(final QuotaCreditsVO credit) { + super(); + this.accountId = credit.getAccountId(); + this.domainId = credit.getDomainId(); + this.creditBalance = credit.getCredit(); + this.updatedOn = credit.getUpdatedOn() == null ? null : new Date(credit.getUpdatedOn().getTime()); + this.creditsId = credit.getId(); + } + + public QuotaBalanceVO(final Long accountId, final Long domainId, final BigDecimal creditBalance, final Date updatedOn) { + super(); + this.accountId = accountId; + this.domainId = domainId; + this.creditBalance = creditBalance; + this.creditsId = 0L; + this.updatedOn = updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + @Override + public long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + 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 getCreditsId() { + return creditsId; + } + + public void setCreditsId(Long creditsId) { + this.creditsId = creditsId; + } + + public BigDecimal getCreditBalance() { + return creditBalance; + } + + public void setCreditBalance(BigDecimal creditBalance) { + this.creditBalance = creditBalance; + } + + public Date getUpdatedOn() { + return updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + public void setUpdatedOn(Date updatedOn) { + this.updatedOn = updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + @Override + public String toString() { + return "QuotaBalanceVO [id=" + id + ", accountId=" + accountId + ", domainId=" + domainId + ", creditBalance=" + creditBalance + ", creditsId=" + creditsId + ", updatedOn=" + + updatedOn + "]"; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java new file mode 100644 index 00000000000..f9c7b45b8a4 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java @@ -0,0 +1,116 @@ +//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 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_credits") +public class QuotaCreditsVO implements InternalIdentity { + + private static final long serialVersionUID = -3576833845287653210L; + + @Id + @Column(name = "id") + private Long id; + + @Column(name = "account_id") + private Long accountId = null; + + @Column(name = "domain_id") + private Long domainId = null; + + @Column(name = "credit") + private BigDecimal credit; + + @Column(name = "updated_on") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updatedOn = null; + + public QuotaCreditsVO() { + } + + public QuotaCreditsVO(long accountId, long domainId, BigDecimal credit, long updatedBy) { + super(); + this.accountId = accountId; + this.domainId = domainId; + this.credit = credit; + this.updatedBy = updatedBy; + } + + 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 BigDecimal getCredit() { + return credit; + } + + public void setCredit(BigDecimal credit) { + this.credit = credit; + } + + public Date getUpdatedOn() { + return updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + public void setUpdatedOn(Date updatedOn) { + this.updatedOn = updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + public Long getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(Long updatedBy) { + this.updatedBy = updatedBy; + } + + public void setId(Long id) { + this.id = id; + } + + // User ID of the creditor + @Column(name = "updated_by") + private Long updatedBy = null; + + @Override + public long getId() { + return this.id; + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java new file mode 100644 index 00000000000..1ad4b379b56 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java @@ -0,0 +1,109 @@ +//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 javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "quota_email_templates") +public class QuotaEmailTemplatesVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "template_name") + private String templateName; + + @Column(name = "template_subject") + private String templateSubject; + + @Column(name = "template_body") + private String templateBody; + + @Column(name = "locale") + private String locale; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + private Date lastUpdated = null; + + public QuotaEmailTemplatesVO() { + } + + public QuotaEmailTemplatesVO(String templateName, String templateSubject, String templateBody) { + super(); + this.templateName = templateName; + this.templateSubject = templateSubject; + this.templateBody = templateBody; + } + + @Override + public long getId() { + return id; + } + + public String getTemplateName() { + return templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + public String getTemplateSubject() { + return templateSubject; + } + + public void setTemplateSubject(String templateSubject) { + this.templateSubject = templateSubject; + } + + public String getTemplateBody() { + return templateBody; + } + + public void setTemplateBody(String templateBody) { + this.templateBody = templateBody; + } + + public Date getLastUpdated() { + return lastUpdated == null ? null : new Date(lastUpdated.getTime()); + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated == null ? null : new Date(lastUpdated.getTime()); + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaTariffVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaTariffVO.java new file mode 100644 index 00000000000..8450d09e1e5 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaTariffVO.java @@ -0,0 +1,170 @@ +//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.quota.constant.QuotaTypes; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +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_tariff") +public class QuotaTariffVO implements InternalIdentity { + private static final long serialVersionUID = -7117933766387653203L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "usage_type") + private int usageType; + + @Column(name = "usage_name") + private String usageName; + + @Column(name = "usage_unit") + private String usageUnit; + + @Column(name = "usage_discriminator") + private String usageDiscriminator; + + @Column(name = "currency_value") + private BigDecimal currencyValue; + + @Column(name = "effective_on") + @Temporal(value = TemporalType.TIMESTAMP) + private Date effectiveOn = null; + + @Column(name = "updated_on") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updatedOn = null; + + @Column(name = "updated_by") + private Long updatedBy = null; + + public QuotaTariffVO() { + } + + public QuotaTariffVO(final int usagetype) { + this.usageType = usagetype; + } + + public QuotaTariffVO(final int usagetype, final String usagename, final String usageunit, final String usagediscriminator, final BigDecimal currencyvalue, + final Date effectiveOn, final Date updatedOn, final long updatedBy) { + this.usageType = usagetype; + this.usageName = usagename; + this.usageUnit = usageunit; + this.usageDiscriminator = usagediscriminator; + this.currencyValue = currencyvalue; + this.effectiveOn = effectiveOn; + this.updatedOn = updatedOn == null ? null : new Date(updatedOn.getTime()); + this.updatedBy = updatedBy; + } + + + public void setId(Long id) { + this.id = id; + } + + public Date getEffectiveOn() { + return effectiveOn == null ? null : new Date(effectiveOn.getTime()); + } + + public void setEffectiveOn(Date effectiveOn) { + this.effectiveOn = effectiveOn == null ? null : new Date(effectiveOn.getTime()); + } + + public Date getUpdatedOn() { + return updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + public void setUpdatedOn(Date updatedOn) { + this.updatedOn = updatedOn == null ? null : new Date(updatedOn.getTime()); + } + + public Long getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(Long updatedBy) { + this.updatedBy = updatedBy; + } + + public int getUsageType() { + return usageType; + } + + public void setUsageType(int usageType) { + this.usageType = usageType; + } + + public String getUsageName() { + return usageName; + } + + public void setUsageName(String usageName) { + this.usageName = usageName; + } + + public String getUsageUnit() { + return usageUnit; + } + + public void setUsageUnit(String usageUnit) { + this.usageUnit = usageUnit; + } + + public String getUsageDiscriminator() { + return usageDiscriminator; + } + + public void setUsageDiscriminator(String usageDiscriminator) { + this.usageDiscriminator = usageDiscriminator; + } + + public BigDecimal getCurrencyValue() { + return currencyValue; + } + + public void setCurrencyValue(BigDecimal currencyValue) { + this.currencyValue = currencyValue; + } + + public String getDescription() { + return QuotaTypes.getDescription(usageType); + } + + public Long getIdObj(){ + return id; + } + + @Override + public long getId() { + return this.id; + } +} \ No newline at end of file diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaUsageVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaUsageVO.java new file mode 100644 index 00000000000..2a26951237e --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/QuotaUsageVO.java @@ -0,0 +1,177 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota.vo; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "quota_usage") +public class QuotaUsageVO implements InternalIdentity { + + private static final long serialVersionUID = -7117933845287204781L; + + @Id + @Column(name = "id") + 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; + + public QuotaUsageVO() { + usageType = -1; + quotaUsed = new BigDecimal(0); + endDate = new Date(); + startDate = new Date(); + } + + public QuotaUsageVO(Long usageItemId, Long zoneId, Long accountId, Long domainId, int usageType, BigDecimal quotaUsed, Date startDate, Date endDate) { + super(); + this.usageItemId = usageItemId; + this.zoneId = zoneId; + this.accountId = accountId; + this.domainId = domainId; + this.usageType = usageType; + this.quotaUsed = quotaUsed; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public QuotaUsageVO(QuotaUsageVO toclone) { + super(); + this.usageItemId = toclone.usageItemId; + this.zoneId = toclone.zoneId; + this.accountId = toclone.accountId; + this.domainId = toclone.domainId; + this.usageType = toclone.usageType; + this.quotaUsed = toclone.quotaUsed; + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + 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; + } + + @Override + public long getId() { + return id; + } + + 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 == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public Date getEndDate() { + return endDate == null ? null : new Date(endDate.getTime()); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String toString() { + return "QuotaUsageVO [id=" + id + ", zoneId=" + zoneId + ", accountId=" + accountId + ", domainId=" + domainId + ", usageItemId=" + usageItemId + ", usageType=" + usageType + + ", quotaUsed=" + quotaUsed + ", startDate=" + startDate + ", endDate=" + endDate + "]"; + } + +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java new file mode 100644 index 00000000000..2d11edda779 --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/ServiceOfferingVO.java @@ -0,0 +1,336 @@ +//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.Map; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; +import javax.persistence.Transient; + +import com.cloud.offering.ServiceOffering; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.Storage.ProvisioningType; +import com.cloud.vm.VirtualMachine; + +@Entity +@Table(name = "service_offering") +@DiscriminatorValue(value = "Service") +@PrimaryKeyJoinColumn(name = "id") +public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering { + @Column(name = "cpu") + private Integer cpu; + + @Column(name = "speed") + private Integer speed; + + @Column(name = "ram_size") + private Integer ramSize; + + @Column(name = "nw_rate") + private Integer rateMbps; + + @Column(name = "mc_rate") + private Integer multicastRateMbps; + + @Column(name = "ha_enabled") + private boolean offerHA; + + @Column(name = "limit_cpu_use") + private boolean limitCpuUse; + + @Column(name = "is_volatile") + private boolean volatileVm; + + @Column(name = "host_tag") + private String hostTag; + + @Column(name = "default_use") + private boolean defaultUse; + + @Column(name = "vm_type") + private String vmType; + + @Column(name = "sort_key") + int sortKey; + + @Column(name = "deployment_planner") + private String deploymentPlanner = null; + + // This is a delayed load value. If the value is null, + // then this field has not been loaded yet. + // Call service offering dao to load it. + @Transient + Map details; + + // This flag is required to tell if the offering is dynamic once the cpu, memory and speed are set. + // In some cases cpu, memory and speed are set to non-null values even if the offering is dynamic. + @Transient + boolean isDynamic; + + protected ServiceOfferingVO() { + super(); + } + + public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, String displayText, + ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, boolean defaultUse) { + super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true); + this.cpu = cpu; + this.ramSize = ramSize; + this.speed = speed; + this.rateMbps = rateMbps; + this.multicastRateMbps = multicastRateMbps; + this.offerHA = offerHA; + limitCpuUse = false; + volatileVm = false; + this.defaultUse = defaultUse; + this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); + } + + public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, boolean limitCpuUse, + boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, VirtualMachine.Type vmType, Long domainId) { + super(name, displayText, provisioningType, false, tags, recreatable, useLocalStorage, systemUse, true, domainId); + this.cpu = cpu; + this.ramSize = ramSize; + this.speed = speed; + this.rateMbps = rateMbps; + this.multicastRateMbps = multicastRateMbps; + this.offerHA = offerHA; + this.limitCpuUse = limitCpuUse; + this.volatileVm = volatileVm; + this.vmType = vmType == null ? null : vmType.toString().toLowerCase(); + } + + public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, + boolean limitResourceUse, boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, + VirtualMachine.Type vmType, Long domainId, String hostTag) { + this(name, + cpu, + ramSize, + speed, + rateMbps, + multicastRateMbps, + offerHA, + limitResourceUse, + volatileVm, + displayText, + provisioningType, + useLocalStorage, + recreatable, + tags, + systemUse, + vmType, + domainId); + this.hostTag = hostTag; + } + + public ServiceOfferingVO(String name, Integer cpu, Integer ramSize, Integer speed, Integer rateMbps, Integer multicastRateMbps, boolean offerHA, + boolean limitResourceUse, boolean volatileVm, String displayText, ProvisioningType provisioningType, boolean useLocalStorage, boolean recreatable, String tags, boolean systemUse, + VirtualMachine.Type vmType, Long domainId, String hostTag, String deploymentPlanner) { + this(name, + cpu, + ramSize, + speed, + rateMbps, + multicastRateMbps, + offerHA, + limitResourceUse, + volatileVm, + displayText, + provisioningType, + useLocalStorage, + recreatable, + tags, + systemUse, + vmType, + domainId, + hostTag); + this.deploymentPlanner = deploymentPlanner; + } + + public ServiceOfferingVO(ServiceOfferingVO offering) { + super(offering.getId(), + offering.getName(), + offering.getDisplayText(), + offering.getProvisioningType(), + false, + offering.getTags(), + offering.isRecreatable(), + offering.getUseLocalStorage(), + offering.getSystemUse(), + true, + offering.isCustomizedIops()== null ? false:offering.isCustomizedIops(), + offering.getDomainId(), + offering.getMinIops(), + offering.getMaxIops()); + cpu = offering.getCpu(); + ramSize = offering.getRamSize(); + speed = offering.getSpeed(); + rateMbps = offering.getRateMbps(); + multicastRateMbps = offering.getMulticastRateMbps(); + offerHA = offering.getOfferHA(); + limitCpuUse = offering.getLimitCpuUse(); + volatileVm = offering.getVolatileVm(); + hostTag = offering.getHostTag(); + vmType = offering.getSystemVmType(); + } + + @Override + public boolean getOfferHA() { + return offerHA; + } + + public void setOfferHA(boolean offerHA) { + this.offerHA = offerHA; + } + + @Override + public boolean getLimitCpuUse() { + return limitCpuUse; + } + + public void setLimitResourceUse(boolean limitCpuUse) { + this.limitCpuUse = limitCpuUse; + } + + @Override + public boolean getDefaultUse() { + return defaultUse; + } + + @Override + @Transient + public String[] getTagsArray() { + String tags = getTags(); + if (tags == null || tags.length() == 0) { + return new String[0]; + } + + return tags.split(","); + } + + @Override + public Integer getCpu() { + return cpu; + } + + public void setCpu(int cpu) { + this.cpu = cpu; + } + + public void setSpeed(int speed) { + this.speed = speed; + } + + public void setRamSize(int ramSize) { + this.ramSize = ramSize; + } + + @Override + public Integer getSpeed() { + return speed; + } + + @Override + public Integer getRamSize() { + return ramSize; + } + + public void setRateMbps(Integer rateMbps) { + this.rateMbps = rateMbps; + } + + @Override + public Integer getRateMbps() { + return rateMbps; + } + + public void setMulticastRateMbps(Integer multicastRateMbps) { + this.multicastRateMbps = multicastRateMbps; + } + + @Override + public Integer getMulticastRateMbps() { + return multicastRateMbps; + } + + public void setHostTag(String hostTag) { + this.hostTag = hostTag; + } + + @Override + public String getHostTag() { + return hostTag; + } + + @Override + public String getSystemVmType() { + return vmType; + } + + @Override + public void setSortKey(int key) { + sortKey = key; + } + + @Override + public int getSortKey() { + return sortKey; + } + + @Override + public boolean getVolatileVm() { + return volatileVm; + } + + @Override + public String getDeploymentPlanner() { + return deploymentPlanner; + } + + public Map getDetails() { + return details; + } + + public String getDetail(String name) { + assert (details != null) : "Did you forget to load the details?"; + + return details != null ? details.get(name) : null; + } + + public void setDetail(String name, String value) { + assert (details != null) : "Did you forget to load the details?"; + + details.put(name, value); + } + + public void setDetails(Map details) { + this.details = details; + } + + @Override + public boolean isDynamic() { + return cpu == null || speed == null || ramSize == null || isDynamic; + } + + public void setDynamicFlag(boolean isdynamic) { + isDynamic = isdynamic; + } +} diff --git a/framework/quota/src/org/apache/cloudstack/quota/vo/UserVmDetailVO.java b/framework/quota/src/org/apache/cloudstack/quota/vo/UserVmDetailVO.java new file mode 100644 index 00000000000..21fcdbdb52a --- /dev/null +++ b/framework/quota/src/org/apache/cloudstack/quota/vo/UserVmDetailVO.java @@ -0,0 +1,83 @@ +//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 javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "user_vm_details") +public class UserVmDetailVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "vm_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value", length = 5120) + private String value; + + @Column(name = "display") + private boolean display = true; + + public UserVmDetailVO() { + } + + public UserVmDetailVO(long vmId, String name, String value, boolean display) { + this.resourceId = vmId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public boolean isDisplay() { + return display; + } + +} diff --git a/framework/quota/test/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/test/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java new file mode 100644 index 00000000000..14244fc204d --- /dev/null +++ b/framework/quota/test/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java @@ -0,0 +1,197 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota; + +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.db.TransactionLegacy; +import junit.framework.TestCase; +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.QuotaEmailTemplatesDao; +import org.apache.cloudstack.quota.dao.QuotaUsageDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.mail.MessagingException; +import javax.naming.ConfigurationException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaAlertManagerImplTest extends TestCase { + + @Mock + AccountDao accountDao; + @Mock + QuotaAccountDao quotaAcc; + @Mock + UserDao userDao; + @Mock + DomainDao domainDao; + @Mock + QuotaEmailTemplatesDao quotaEmailTemplateDao; + @Mock + ConfigurationDao configDao; + @Mock + QuotaUsageDao quotaUsage; + @Mock + QuotaAlertManagerImpl.EmailQuotaAlert emailQuotaAlert; + + @Spy + QuotaAlertManagerImpl quotaAlertManager = new QuotaAlertManagerImpl(); + + private void injectMockToField(Object mock, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field f = QuotaAlertManagerImpl.class.getDeclaredField(fieldName); + f.setAccessible(true); + f.set(quotaAlertManager, mock); + } + + @Before + public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { + // Dummy transaction stack setup + TransactionLegacy.open("QuotaAlertManagerImplTest"); + + injectMockToField(accountDao, "_accountDao"); + injectMockToField(quotaAcc, "_quotaAcc"); + injectMockToField(userDao, "_userDao"); + injectMockToField(domainDao, "_domainDao"); + injectMockToField(quotaEmailTemplateDao, "_quotaEmailTemplateDao"); + injectMockToField(configDao, "_configDao"); + injectMockToField(quotaUsage, "_quotaUsage"); + injectMockToField(emailQuotaAlert, "_emailQuotaAlert"); + } + + @Test + public void testCheckAndSendQuotaAlertEmails() { + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + accountVO.setType(Account.ACCOUNT_TYPE_NORMAL); + Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(accountVO); + + QuotaAccountVO acc = new QuotaAccountVO(2L); + acc.setQuotaBalance(new BigDecimal(404)); + acc.setQuotaMinBalance(new BigDecimal(100)); + acc.setQuotaBalanceDate(new Date()); + acc.setQuotaAlertDate(null); + acc.setQuotaEnforce(0); + List accounts = new ArrayList<>(); + accounts.add(acc); + Mockito.when(quotaAcc.listAllQuotaAccount()).thenReturn(accounts); + + // Don't test sendQuotaAlert yet + Mockito.doNothing().when(quotaAlertManager).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + Mockito.doReturn(true).when(quotaAlertManager).lockAccount(Mockito.anyLong()); + + // call real method on send monthly statement + Mockito.doCallRealMethod().when(quotaAlertManager).checkAndSendQuotaAlertEmails(); + + // Case1: valid balance, no email should be sent + quotaAlertManager.checkAndSendQuotaAlertEmails(); + Mockito.verify(quotaAlertManager, Mockito.times(0)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + + // Case2: low balance, email should be sent + accounts.get(0).setQuotaBalance(new BigDecimal(99)); + //Mockito.when(quotaAcc.listAll()).thenReturn(accounts); + quotaAlertManager.checkAndSendQuotaAlertEmails(); + Mockito.verify(quotaAlertManager, Mockito.times(1)).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + } + + @Test + public void testSendQuotaAlert() throws UnsupportedEncodingException, MessagingException { + Mockito.doCallRealMethod().when(quotaAlertManager).sendQuotaAlert(Mockito.any(QuotaAlertManagerImpl.DeferredQuotaEmail.class)); + + AccountVO account = new AccountVO(); + account.setId(2L); + account.setDomainId(1L); + account.setType(Account.ACCOUNT_TYPE_NORMAL); + account.setAccountName("admin"); + account.setUuid("uuid"); + + QuotaAccountVO quotaAccount = new QuotaAccountVO(2L); + quotaAccount.setQuotaBalance(new BigDecimal(404)); + quotaAccount.setQuotaMinBalance(new BigDecimal(100)); + quotaAccount.setQuotaBalanceDate(new Date()); + quotaAccount.setQuotaAlertDate(null); + quotaAccount.setQuotaEnforce(0); + + QuotaAlertManagerImpl.DeferredQuotaEmail email = new QuotaAlertManagerImpl.DeferredQuotaEmail(account, quotaAccount, new BigDecimal(100), + QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW); + + QuotaEmailTemplatesVO quotaEmailTemplatesVO = new QuotaEmailTemplatesVO(); + quotaEmailTemplatesVO.setTemplateSubject("Low quota"); + quotaEmailTemplatesVO.setTemplateBody("Low quota {{accountID}}"); + List emailTemplates = new ArrayList<>(); + emailTemplates.add(quotaEmailTemplatesVO); + Mockito.when(quotaEmailTemplateDao.listAllQuotaEmailTemplates(Mockito.anyString())).thenReturn(emailTemplates); + + DomainVO domain = new DomainVO(); + domain.setUuid("uuid"); + domain.setName("/domain"); + Mockito.when(domainDao.findByIdIncludingRemoved(Mockito.anyLong())).thenReturn(new DomainVO()); + + UserVO user = new UserVO(); + user.setUsername("user1"); + user.setEmail("user1@apache.org"); + List users = new ArrayList<>(); + users.add(user); + Mockito.when(userDao.listByAccount(Mockito.anyLong())).thenReturn(users); + + quotaAlertManager.sendQuotaAlert(email); + assertTrue(email.getSendDate()!= null); + Mockito.verify(emailQuotaAlert, Mockito.times(1)).sendQuotaAlert(Mockito.anyList(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void testGetDifferenceDays() { + Date now = new Date(); + assertTrue(QuotaAlertManagerImpl.getDifferenceDays(now, now) == 0L); + assertTrue(QuotaAlertManagerImpl.getDifferenceDays(now, new DateTime(now).plusDays(1).toDate()) == 1L); + } + + @Test + public void testLockAccount() { + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + accountVO.setType(Account.ACCOUNT_TYPE_NORMAL); + accountVO.setState(Account.State.enabled); + Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(accountVO); + Mockito.when(accountDao.createForUpdate()).thenReturn(accountVO); + Mockito.when(accountDao.update(Mockito.eq(accountVO.getId()), Mockito.eq(accountVO))).thenReturn(true); + assertTrue(quotaAlertManager.lockAccount(accountVO.getId())); + } +} diff --git a/framework/quota/test/org/apache/cloudstack/quota/QuotaManagerImplTest.java b/framework/quota/test/org/apache/cloudstack/quota/QuotaManagerImplTest.java new file mode 100644 index 00000000000..792728f5e88 --- /dev/null +++ b/framework/quota/test/org/apache/cloudstack/quota/QuotaManagerImplTest.java @@ -0,0 +1,200 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota; + +import com.cloud.usage.UsageVO; +import com.cloud.usage.dao.UsageDao; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.TransactionLegacy; +import junit.framework.TestCase; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +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.QuotaUsageDao; +import org.apache.cloudstack.quota.dao.ServiceOfferingDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.cloudstack.usage.UsageTypes; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.naming.ConfigurationException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaManagerImplTest extends TestCase { + + @Mock + private AccountDao accountDao; + @Mock + private QuotaAccountDao quotaAcc; + @Mock + private UsageDao usageDao; + @Mock + private QuotaTariffDao quotaTariffDao; + @Mock + private QuotaUsageDao quotaUsageDao; + @Mock + private ServiceOfferingDao serviceOfferingDao; + @Mock + private QuotaBalanceDao quotaBalanceDao; + @Mock + private ConfigurationDao configDao; + + @Spy + QuotaManagerImpl quotaManager = new QuotaManagerImpl(); + + private void injectMockToField(Object mock, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field f = QuotaManagerImpl.class.getDeclaredField(fieldName); + f.setAccessible(true); + f.set(quotaManager, mock); + } + + @Before + public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { + // Dummy transaction stack setup + TransactionLegacy.open("QuotaManagerImplTest"); + + injectMockToField(accountDao, "_accountDao"); + injectMockToField(quotaAcc, "_quotaAcc"); + injectMockToField(usageDao, "_usageDao"); + injectMockToField(quotaTariffDao, "_quotaTariffDao"); + injectMockToField(quotaUsageDao, "_quotaUsageDao"); + injectMockToField(serviceOfferingDao, "_serviceOfferingDao"); + injectMockToField(quotaBalanceDao, "_quotaBalanceDao"); + injectMockToField(configDao, "_configDao"); + } + + @Test + public void testConfig() throws ConfigurationException { + Mockito.when(configDao.getConfiguration(Mockito.anyMapOf(String.class, Object.class))).thenReturn(new HashMap()); + Map map = new HashMap<>(); + map.put("usage.stats.job.aggregation.range", "0"); + assertTrue(quotaManager.configure("quotaManager", map)); + } + + @Test + public void testCalculateQuotaUsage() { + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + accountVO.setType(Account.ACCOUNT_TYPE_NORMAL); + List accountVOList = new ArrayList<>(); + accountVOList.add(accountVO); + Mockito.when(accountDao.listAll()).thenReturn(accountVOList); + + UsageVO usageVO = new UsageVO(); + usageVO.setQuotaCalculated(0); + List usageVOList = new ArrayList(); + usageVOList.add(usageVO); + Pair, Integer> usageRecords = new Pair, Integer>(usageVOList, usageVOList.size()); + Mockito.when(usageDao.getUsageRecordsPendingQuotaAggregation(Mockito.anyLong(), Mockito.anyLong())).thenReturn(usageRecords); + + QuotaUsageVO quotaUsageVO = new QuotaUsageVO(); + quotaUsageVO.setAccountId(2L); + List quotaListForAccount = new ArrayList<>(); + quotaListForAccount.add(quotaUsageVO); + Mockito.doReturn(quotaListForAccount).when(quotaManager).aggregatePendingQuotaRecordsForAccount(Mockito.eq(accountVO), Mockito.eq(usageRecords)); + Mockito.doNothing().when(quotaManager).processQuotaBalanceForAccount(Mockito.eq(accountVO), Mockito.eq(quotaListForAccount)); + + assertTrue(quotaManager.calculateQuotaUsage()); + } + + @Test + public void testAggregatePendingQuotaRecordsForAccount() { + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + accountVO.setType(Account.ACCOUNT_TYPE_NORMAL); + + UsageVO usageVO = new UsageVO(); + usageVO.setQuotaCalculated(0); + usageVO.setUsageType(UsageTypes.ALLOCATED_VM); + List usageVOList = new ArrayList(); + usageVOList.add(usageVO); + Pair, Integer> usageRecords = new Pair, Integer>(usageVOList, usageVOList.size()); + + QuotaUsageVO quotaUsageVO = new QuotaUsageVO(); + quotaUsageVO.setAccountId(2L); + Mockito.doReturn(quotaUsageVO).when(quotaManager).updateQuotaAllocatedVMUsage(Mockito.eq(usageVO), Mockito.any(BigDecimal.class)); + + assertTrue(quotaManager.aggregatePendingQuotaRecordsForAccount(accountVO, new Pair, Integer>(null, 0)).size() == 0); + assertTrue(quotaManager.aggregatePendingQuotaRecordsForAccount(accountVO, usageRecords).size() == 1); + } + + @Test + public void testUpdateQuotaRecords() { + UsageVO usageVO = new UsageVO(); + usageVO.setId(100L); + usageVO.setQuotaCalculated(0); + usageVO.setUsageType(UsageTypes.NETWORK_BYTES_SENT); + usageVO.setRawUsage(9000000000.0); + usageVO.setSize(1010101010L); + + QuotaTariffVO tariffVO = new QuotaTariffVO(); + tariffVO.setCurrencyValue(new BigDecimal(1)); + Mockito.when(quotaTariffDao.findTariffPlanByUsageType(Mockito.anyInt(), Mockito.any(Date.class))).thenReturn(tariffVO); + + QuotaUsageVO qu = quotaManager.updateQuotaNetwork(usageVO, UsageTypes.NETWORK_BYTES_SENT); + assertTrue(qu.getQuotaUsed().compareTo(BigDecimal.ZERO) > 0); + qu = quotaManager.updateQuotaAllocatedVMUsage(usageVO, new BigDecimal(0.5)); + assertTrue(qu.getQuotaUsed().compareTo(BigDecimal.ZERO) > 0); + qu = quotaManager.updateQuotaDiskUsage(usageVO, new BigDecimal(0.5), UsageTypes.VOLUME); + assertTrue(qu.getQuotaUsed().compareTo(BigDecimal.ZERO) > 0); + qu = quotaManager.updateQuotaRaw(usageVO, new BigDecimal(0.5), UsageTypes.VPN_USERS); + assertTrue(qu.getQuotaUsed().compareTo(BigDecimal.ZERO) > 0); + + Mockito.verify(quotaUsageDao, Mockito.times(4)).persistQuotaUsage(Mockito.any(QuotaUsageVO.class)); + Mockito.verify(usageDao, Mockito.times(4)).persistUsage(Mockito.any(UsageVO.class)); + } + + @Test + public void testProcessQuotaBalanceForAccount() { + Date now = new Date(); + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + accountVO.setType(Account.ACCOUNT_TYPE_NORMAL); + + QuotaUsageVO quotaUsageVO = new QuotaUsageVO(); + quotaUsageVO.setAccountId(2L); + quotaUsageVO.setStartDate(new Date(now.getTime())); + quotaUsageVO.setEndDate(new Date(now.getTime())); + List quotaListForAccount = new ArrayList<>(); + quotaListForAccount.add(quotaUsageVO); + + quotaManager.processQuotaBalanceForAccount(accountVO, quotaListForAccount); + Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); + } + +} diff --git a/framework/quota/test/org/apache/cloudstack/quota/QuotaStatementTest.java b/framework/quota/test/org/apache/cloudstack/quota/QuotaStatementTest.java new file mode 100644 index 00000000000..f2a0deda3c7 --- /dev/null +++ b/framework/quota/test/org/apache/cloudstack/quota/QuotaStatementTest.java @@ -0,0 +1,255 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota; + +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.db.TransactionLegacy; +import junit.framework.TestCase; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.quota.QuotaStatementImpl.STATEMENT_PERIODS; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaUsageDao; +import org.apache.cloudstack.quota.vo.QuotaAccountVO; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.mail.MessagingException; +import javax.naming.ConfigurationException; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaStatementTest extends TestCase { + + @Mock + AccountDao accountDao; + @Mock + QuotaAccountDao quotaAcc; + @Mock + ConfigurationDao configDao; + @Mock + QuotaUsageDao quotaUsage; + @Mock + QuotaAlertManager alertManager; + + @Spy + QuotaStatementImpl quotaStatement = new QuotaStatementImpl(); + + private void injectMockToField(Object mock, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field f = QuotaStatementImpl.class.getDeclaredField(fieldName); + f.setAccessible(true); + f.set(quotaStatement, mock); + } + + @Before + public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { + // Dummy transaction stack setup + TransactionLegacy.open("QuotaStatementImplTest"); + + injectMockToField(accountDao, "_accountDao"); + injectMockToField(quotaAcc, "_quotaAcc"); + injectMockToField(configDao, "_configDao"); + injectMockToField(quotaUsage, "_quotaUsage"); + injectMockToField(alertManager, "_quotaAlert"); + } + + @Test + public void testStatementPeriodBIMONTHLY() { + Calendar date = Calendar.getInstance(); + + //BIMONTHLY - first statement of month + date.set(Calendar.DATE, QuotaStatementImpl.s_LAST_STATEMENT_SENT_DAYS + 1); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.BIMONTHLY); + assertTrue(period == null); + + //1 of this month + date.set(Calendar.DATE, 1); + period = quotaStatement.statementTime(date, STATEMENT_PERIODS.BIMONTHLY); + assertTrue(period != null); + assertTrue(period.length == 2); + assertTrue(period[0].toString(), period[0].before(period[1])); + assertTrue(period[0].toString(), period[0].get(Calendar.DATE) == 1); + assertTrue(period[1].toString(), period[1].get(Calendar.DATE) == 15); + + //BIMONTHLY - second statement of month + date = Calendar.getInstance(); + date.set(Calendar.DATE, QuotaStatementImpl.s_LAST_STATEMENT_SENT_DAYS + 16); + period = quotaStatement.statementTime(date, STATEMENT_PERIODS.BIMONTHLY); + assertTrue(period == null); + + //17 of this month + date.set(Calendar.DATE, 17); + period = quotaStatement.statementTime(date, STATEMENT_PERIODS.BIMONTHLY); + assertTrue(period != null); + assertTrue(period.length == 2); + assertTrue(period[0].toString(), period[0].before(period[1])); + assertTrue(period[0].toString(), period[0].get(Calendar.DATE) == 16); + + //get last day of the previous month + Calendar aCalendar = Calendar.getInstance(); + aCalendar.add(Calendar.MONTH, -1); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + + assertTrue(period[1].toString(), period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)); + + } + + @Test + public void testStatementPeriodMONTHLY() { + Calendar date = Calendar.getInstance(); + Calendar aCalendar = Calendar.getInstance(); + + //MONTHLY + date = Calendar.getInstance(); + date.set(Calendar.DATE, QuotaStatementImpl.s_LAST_STATEMENT_SENT_DAYS + 1); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.MONTHLY); + assertTrue(period == null); + + //1 of this month + date.set(Calendar.DATE, QuotaStatementImpl.s_LAST_STATEMENT_SENT_DAYS - 1); + period = quotaStatement.statementTime(date, STATEMENT_PERIODS.MONTHLY); + assertTrue(period != null); + assertTrue(period.length == 2); + assertTrue(period[0].toString(), period[0].before(period[1])); + assertTrue(period[0].toString(), period[0].get(Calendar.DATE) == 1); + + //get last day of the previous month + aCalendar = Calendar.getInstance(); + aCalendar.add(Calendar.MONTH, -1); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + + assertTrue(period[1].toString(), period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)); + + } + + @Test + public void testStatementPeriodQUATERLY() { + Calendar date = Calendar.getInstance(); + Calendar aCalendar = Calendar.getInstance(); + + //QUATERLY + date = Calendar.getInstance(); + date.set(Calendar.MONTH, Calendar.JANUARY); // 1 Jan + date.set(Calendar.DATE, 1); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.QUATERLY); + assertTrue(period != null); + assertTrue(period.length == 2); + assertTrue("period[0].before(period[1])" + period[0].toString(), period[0].before(period[1])); + assertTrue("period[0].get(Calendar.DATE) == 1" + period[0].toString(), period[0].get(Calendar.DATE) == 1); + assertTrue("period[0].get(Calendar.MONTH) == Calendar.OCTOBER" + period[0].toString(), period[0].get(Calendar.MONTH) == Calendar.OCTOBER); //october + + //get last day of the previous month + aCalendar = Calendar.getInstance(); + aCalendar.set(Calendar.MONTH, Calendar.DECEMBER); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + assertTrue(" period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)" + period[1].toString(), period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)); + assertTrue("period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)" + period[1].toString(), period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)); + + } + + @Test + public void testStatementPeriodHALFYEARLY() { + Calendar date = Calendar.getInstance(); + Calendar aCalendar = Calendar.getInstance(); + + //QUATERLY + date = Calendar.getInstance(); + date.set(Calendar.MONTH, Calendar.JANUARY); // 1 Jan + date.set(Calendar.DATE, 1); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.HALFYEARLY); + assertTrue(period != null); + assertTrue(period.length == 2); + assertTrue("period[0].before(period[1])" + period[0].toString(), period[0].before(period[1])); + assertTrue("period[0].get(Calendar.DATE) == 1" + period[0].toString(), period[0].get(Calendar.DATE) == 1); + assertTrue("period[0].get(Calendar.MONTH) == Calendar.JULY" + period[0].toString(), period[0].get(Calendar.MONTH) == Calendar.JULY); //july + + //get last day of the previous month + aCalendar = Calendar.getInstance(); + aCalendar.set(Calendar.MONTH, Calendar.DECEMBER); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + assertTrue(" period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)" + period[1].toString(), period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)); + assertTrue("period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)" + period[1].toString(), period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)); + + } + + @Test + public void testStatementPeriodYEARLY() { + Calendar date = Calendar.getInstance(); + Calendar aCalendar = Calendar.getInstance(); + + //QUATERLY + date = Calendar.getInstance(); + date.set(Calendar.MONTH, Calendar.JANUARY); // 1 Jan + date.set(Calendar.DATE, 1); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.YEARLY); + assertTrue("period != null", period != null); + assertTrue(period.length == 2); + assertTrue("period[0].before(period[1])" + period[0].toString(), period[0].before(period[1])); + assertTrue("period[0].get(Calendar.DATE) == 1" + period[0].toString(), period[0].get(Calendar.DATE) == 1); + assertTrue("period[0].get(Calendar.MONTH) == Calendar.JANUARY" + period[0].toString(), period[0].get(Calendar.MONTH) == Calendar.JANUARY); //january + + //get last day of the previous month + aCalendar = Calendar.getInstance(); + aCalendar.set(Calendar.MONTH, Calendar.DECEMBER); + aCalendar.set(Calendar.DATE, aCalendar.getActualMaximum(Calendar.DAY_OF_MONTH) + 1); + assertTrue(" period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)" + period[1].toString(), period[1].get(Calendar.DATE) == aCalendar.get(Calendar.DATE)); + assertTrue("period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)" + period[1].toString(), period[1].get(Calendar.MONTH) == aCalendar.get(Calendar.MONTH)); + + } + + @Test + public void testSendStatement() throws UnsupportedEncodingException, MessagingException { + Calendar date = Calendar.getInstance(); + AccountVO accountVO = new AccountVO(); + accountVO.setId(2L); + accountVO.setDomainId(1L); + Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(accountVO); + + QuotaAccountVO acc = new QuotaAccountVO(2L); + acc.setQuotaBalance(new BigDecimal(404)); + acc.setLastStatementDate(null); + List accounts = new ArrayList<>(); + accounts.add(acc); + Mockito.when(quotaAcc.listAllQuotaAccount()).thenReturn(accounts); + + Mockito.when(quotaUsage.findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyInt(), Mockito.any(Date.class), Mockito.any(Date.class))) + .thenReturn(new BigDecimal(100)); + + QuotaAlertManagerImpl.DeferredQuotaEmail email = new QuotaAlertManagerImpl.DeferredQuotaEmail(accountVO, acc, new BigDecimal(100), + QuotaConfig.QuotaEmailTemplateTypes.QUOTA_LOW); + // call real method on send monthly statement + Mockito.doCallRealMethod().when(quotaStatement).sendStatement(); + Calendar period[] = quotaStatement.statementTime(date, STATEMENT_PERIODS.MONTHLY); + if (period != null){ + Mockito.verify(alertManager, Mockito.times(1)).sendQuotaAlert(email); + } + } + +} diff --git a/framework/quota/test/org/apache/cloudstack/quota/constant/QuotaTypesTest.java b/framework/quota/test/org/apache/cloudstack/quota/constant/QuotaTypesTest.java new file mode 100644 index 00000000000..427043951a1 --- /dev/null +++ b/framework/quota/test/org/apache/cloudstack/quota/constant/QuotaTypesTest.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota.constant; + +import junit.framework.TestCase; + +import org.apache.cloudstack.api.response.UsageTypeResponse; +import org.apache.cloudstack.usage.UsageTypes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.List; +import java.util.Map; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaTypesTest extends TestCase { + + @Test + public void testQuotaTypesList() { + Map quotaTypes = QuotaTypes.listQuotaTypes(); + List usageTypesResponseList = UsageTypes.listUsageTypes(); + for (UsageTypeResponse usageTypeResponse : usageTypesResponseList) { + final Integer usageTypeInt = usageTypeResponse.getUsageType(); + assertTrue(quotaTypes.containsKey(usageTypeInt)); + } + } + + @Test + public void testQuotaTypeDescription() { + assertNull(QuotaTypes.getDescription(-1)); + assertNotNull(QuotaTypes.getDescription(QuotaTypes.MEMORY)); + } +} \ No newline at end of file diff --git a/plugins/database/quota/pom.xml b/plugins/database/quota/pom.xml new file mode 100644 index 00000000000..ee0a04baeee --- /dev/null +++ b/plugins/database/quota/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + cloud-plugin-database-quota + Apache CloudStack Plugin - Quota Service + + org.apache.cloudstack + cloudstack-plugins + 4.7.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-engine-schema + ${project.version} + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.apache.cloudstack + cloud-framework-quota + ${project.version} + + + mysql + mysql-connector-java + provided + + + org.apache.commons + commons-lang3 + ${cs.commons-lang3.version} + + + joda-time + joda-time + ${cs.joda-time.version} + + + junit + junit + ${cs.junit.version} + test + + + org.hamcrest + hamcrest-library + ${cs.hamcrest.version} + test + + + org.mockito + mockito-all + ${cs.mockito.version} + test + + + org.powermock + powermock-module-junit4 + ${cs.powermock.version} + + + org.powermock + powermock-api-mockito + ${cs.powermock.version} + test + + + org.springframework + spring-test + ${org.springframework.version} + test + + + javax.inject + javax.inject + 1 + + + diff --git a/plugins/database/quota/resources/META-INF/cloudstack/quota/module.properties b/plugins/database/quota/resources/META-INF/cloudstack/quota/module.properties new file mode 100644 index 00000000000..7332f151828 --- /dev/null +++ b/plugins/database/quota/resources/META-INF/cloudstack/quota/module.properties @@ -0,0 +1,18 @@ +# 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. +name=quota +parent=api diff --git a/plugins/database/quota/resources/META-INF/cloudstack/quota/spring-quota-context.xml b/plugins/database/quota/resources/META-INF/cloudstack/quota/spring-quota-context.xml new file mode 100644 index 00000000000..15bc144e31a --- /dev/null +++ b/plugins/database/quota/resources/META-INF/cloudstack/quota/spring-quota-context.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaBalanceCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaBalanceCmd.java new file mode 100644 index 00000000000..ef9d49a3beb --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaBalanceCmd.java @@ -0,0 +1,125 @@ +//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 java.util.Date; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.log4j.Logger; +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.QuotaBalanceResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.api.response.QuotaStatementItemResponse; + +@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaBalanceCmd extends BaseCmd { + + public static final Logger s_logger = Logger.getLogger(QuotaBalanceCmd.class); + + private static final String s_name = "quotabalanceresponse"; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which statement needs to be generated") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "If domain Id is given and the caller is domain admin then the statement is generated for domain.") + private Long domainId; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "End date range for quota query. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-03.") + private Date endDate; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date range quota query. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-01.") + private Date startDate; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified account") + private Long accountId; + + @Inject + QuotaResponseBuilder _responseBuilder; + + 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 Date getEndDate() { + return endDate == null ? null : _responseBuilder.startOfNextDay(endDate == null ? null : new Date(endDate.getTime())); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return _accountService.getActiveAccountByName(accountName, domainId).getAccountId(); + } + + @Override + public void execute() { + List quotaUsage = _responseBuilder.getQuotaBalance(this); + + QuotaBalanceResponse response; + if (getEndDate() == null) { + response = _responseBuilder.createQuotaLastBalanceResponse(quotaUsage, getStartDate()); + } else { + response = _responseBuilder.createQuotaBalanceResponse(quotaUsage, getStartDate(), endDate == null ? null : new Date(endDate.getTime())); + } + response.setResponseName(getCommandName()); + setResponseObject(response); + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaCreditsCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaCreditsCmd.java new file mode 100644 index 00000000000..ce00e23e5c9 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaCreditsCmd.java @@ -0,0 +1,147 @@ +//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 com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.QuotaCreditsResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.quota.QuotaService; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +@APICommand(name = "quotaCredits", responseObject = QuotaCreditsResponse.class, description = "Add +-credits to an account", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaCreditsCmd extends BaseCmd { + + @Inject + QuotaResponseBuilder _responseBuilder; + + @Inject + QuotaService _quotaService; + + public static final Logger s_logger = Logger.getLogger(QuotaStatementCmd.class); + + private static final String s_name = "quotacreditsresponse"; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which quota credits need to be added") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "Domain for which quota credits need to be added") + private Long domainId; + + @Parameter(name = ApiConstants.VALUE, type = CommandType.DOUBLE, required = true, description = "Value of the credits to be added+, subtracted-") + private Double value; + + @Parameter(name = "min_balance", type = CommandType.DOUBLE, required = false, description = "Minimum balance threshold of the account") + private Double minBalance; + + @Parameter(name = "quota_enforce", type = CommandType.BOOLEAN, required = false, description = "Account for which quota enforce is set to false will not be locked when there is no credit balance") + private Boolean quotaEnforce; + + public Double getMinBalance() { + return minBalance; + } + + public void setMinBalance(Double minBalance) { + this.minBalance = minBalance; + } + + public Boolean getQuotaEnforce() { + return quotaEnforce; + } + + public void setQuotaEnforce(Boolean quotaEnforce) { + this.quotaEnforce = quotaEnforce; + } + + 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 Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public QuotaCreditsCmd() { + super(); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + Long accountId = null; + Account account = _accountService.getActiveAccountByName(accountName, domainId); + if (account != null) { + accountId = account.getAccountId(); + } + if (accountId == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "The account does not exists or has been removed/disabled"); + } + if (getValue() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please send a valid non-empty quota value"); + } + if (getQuotaEnforce() != null && getQuotaEnforce()) { + _quotaService.setLockAccount(accountId, getQuotaEnforce()); + } + if (getMinBalance() != null) { + _quotaService.setMinBalance(accountId, getMinBalance()); + } + else { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please set a value for min balance"); + } + + final QuotaCreditsResponse response = _responseBuilder.addQuotaCredits(accountId, getDomainId(), getValue(), CallContext.current().getCallingUserId()); + response.setResponseName(getCommandName()); + response.setObjectName("quotacredits"); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java new file mode 100644 index 00000000000..8b717eb41ec --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java @@ -0,0 +1,60 @@ +//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.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.QuotaEmailTemplateResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +@APICommand(name = "quotaEmailTemplateList", responseObject = QuotaEmailTemplateResponse.class, description = "Lists all quota email templates", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaEmailTemplateListCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(QuotaEmailTemplateListCmd.class); + private static final String s_name = "quotaemailtemplatelistresponse"; + + @Inject + QuotaResponseBuilder _quotaResponseBuilder; + + @Parameter(name = "templatetype", type = CommandType.STRING, description = "List by type of the quota email template, allowed types: QUOTA_LOW, QUOTA_EMPTY") + private String templateName; + + public String getTemplateName() { + return templateName; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + @Override + public void execute() { + final ListResponse response = new ListResponse(); + response.setResponses(_quotaResponseBuilder.listQuotaEmailTemplates(this)); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java new file mode 100644 index 00000000000..469fd4dd002 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java @@ -0,0 +1,122 @@ +//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 com.cloud.user.Account; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.util.Arrays; + +@APICommand(name = "quotaEmailTemplateUpdate", responseObject = SuccessResponse.class, description = "Updates existing email templates for quota alerts", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaEmailTemplateUpdateCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(QuotaEmailTemplateUpdateCmd.class); + private static final String s_name = "quotaemailtemplateupdateresponse"; + + @Inject + QuotaResponseBuilder _quotaResponseBuilder; + + @Parameter(name = "templatetype", type = CommandType.STRING, required=true, description = "Type of the quota email template, allowed types: QUOTA_LOW, QUOTA_EMPTY") + private String templateName; + + @Parameter(name = "templatesubject", type = CommandType.STRING, required=true, description = "The quota email template subject, max: 77 characters", length = 77) + private String templateSubject; + + @Parameter(name = "templatebody", type = CommandType.STRING, required=true, description = "The quota email template body, max: 500k characters", length = 512000) + private String templateBody; + + @Parameter(name = "locale", type = CommandType.STRING, description = "The locale of the email text") + private String locale; + + @Override + public void execute() { + final String templateName = getTemplateName(); + if (templateName == null || getTemplateSubject() == null || getTemplateBody() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Failed to update quota email template due to empty or invalid template name or text"); + } + + boolean isValidTemplateName = false; + for (QuotaConfig.QuotaEmailTemplateTypes e: QuotaConfig.QuotaEmailTemplateTypes.values()) { + if (e.toString().equalsIgnoreCase(templateName)) { + isValidTemplateName = true; + setTemplateName(e.toString()); + break; + } + } + if (!isValidTemplateName) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid quota email template type, allowed values are: " + Arrays.toString(QuotaConfig.QuotaEmailTemplateTypes.values())); + } + + if (!_quotaResponseBuilder.updateQuotaEmailTemplate(this)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to update quota email template due to an internal error"); + } + final SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + response.setSuccess(true); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + public void setTemplateName(String templateName) { + this.templateName = templateName; + } + + public String getTemplateName() { + return templateName; + } + + public String getTemplateSubject() { + return templateSubject; + } + + public String getTemplateBody() { + return templateBody; + } + + public String getLocale() { + return locale; + } + + public void setTemplateSubject(String templateSubject) { + this.templateSubject = templateSubject; + } + + public void setTemplateBody(String templateBody) { + this.templateBody = templateBody; + } + + public void setLocale(String locale) { + this.locale = locale; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaStatementCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaStatementCmd.java new file mode 100644 index 00000000000..fa9796009b7 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaStatementCmd.java @@ -0,0 +1,141 @@ +//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 java.util.Date; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.log4j.Logger; +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.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaStatementResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.cloudstack.api.response.QuotaStatementItemResponse; + +import com.cloud.user.Account; + +@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaStatementCmd extends BaseCmd { + + public static final Logger s_logger = Logger.getLogger(QuotaStatementCmd.class); + + private static final String s_name = "quotastatementresponse"; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Optional, Account Id for which statement needs to be generated") + private String accountName; + + @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.") + private Long domainId; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = true, description = "End date range for quota query. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-03.") + private Date endDate; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = true, description = "Start date range quota query. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-01.") + private Date startDate; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER, description = "List quota usage records for the specified usage type") + private Integer usageType; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified account") + private Long accountId; + + @Inject + QuotaResponseBuilder _responseBuilder; + + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public Integer getUsageType() { + return usageType; + } + + public void setUsageType(Integer usageType) { + this.usageType = usageType; + } + + 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 Date getEndDate() { + return _responseBuilder.startOfNextDay(endDate == null ? new Date() : new Date(endDate.getTime())); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.getActiveAccountByName(accountName, domainId).getAccountId(); + if (accountId == null) { + return CallContext.current().getCallingAccount().getId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List 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())); + + response.setResponseName(getCommandName()); + setResponseObject(response); + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaSummaryCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaSummaryCmd.java new file mode 100644 index 00000000000..773bac6e0e4 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaSummaryCmd.java @@ -0,0 +1,110 @@ +//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 com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.BaseCmd.CommandType; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaSummaryResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import java.util.List; + +import javax.inject.Inject; + +@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all accounts", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaSummaryCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(QuotaSummaryCmd.class); + private static final String s_name = "quotasummaryresponse"; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Optional, Account Id for which statement needs to be generated") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "Optional, If domain Id is given and the caller is domain admin then the statement is generated for domain.") + private Long domainId; + + @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, required = false, description = "Optional, to list all accounts irrespective of the quota activity") + private Boolean listAll; + + @Inject + QuotaResponseBuilder _responseBuilder; + + public QuotaSummaryCmd() { + super(); + } + + @Override + public void execute() { + Account caller = CallContext.current().getCallingAccount(); + List responses; + if (caller.getAccountId() <= 2) { //non root admin or system + if (getAccountName() != null && getDomainId() != null) + responses = _responseBuilder.createQuotaSummaryResponse(caller.getAccountName(), caller.getDomainId()); + else + responses = _responseBuilder.createQuotaSummaryResponse(getListAll()); + } else { + responses = _responseBuilder.createQuotaSummaryResponse(caller.getAccountName(), caller.getDomainId()); + } + final ListResponse response = new ListResponse(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + 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; + } + + @Override + public String getCommandName() { + return s_name; + } + + public Boolean getListAll() { + return listAll == null ? false: listAll; + } + + public void setListAll(Boolean listAll) { + this.listAll = listAll; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffListCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffListCmd.java new file mode 100644 index 00000000000..c1905944c52 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffListCmd.java @@ -0,0 +1,95 @@ +//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 com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@APICommand(name = "quotaTariffList", responseObject = QuotaTariffResponse.class, description = "Lists all quota tariff plans", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaTariffListCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(QuotaTariffListCmd.class); + private static final String s_name = "quotatarifflistresponse"; + + @Inject + QuotaResponseBuilder _responseBuilder; + + @Parameter(name = ApiConstants.USAGE_TYPE, type = CommandType.INTEGER, required = false, description = "Usage type of the resource") + private Integer usageType; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "The effective start date on/after which the quota tariff is effective and older tariffs are no longer used for the usage type. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-03.") + private Date effectiveDate; + + public QuotaTariffListCmd() { + super(); + } + + @Override + public void execute() { + final List result = _responseBuilder.listQuotaTariffPlans(this); + + final List responses = new ArrayList(); + for (final QuotaTariffVO resource : result) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Result desc=" + resource.getDescription() + " date=" + resource.getEffectiveOn() + " val=" + resource.getCurrencyValue()); + } + responses.add(_responseBuilder.createQuotaTariffResponse(resource)); + } + + final ListResponse response = new ListResponse(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + public Date getEffectiveDate() { + return effectiveDate ==null ? null : new Date(effectiveDate.getTime()); + } + + public Integer getUsageType() { + return usageType; + } + + public void setUsageType(Integer usageType) { + this.usageType = usageType; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java new file mode 100644 index 00000000000..04af3eca1ee --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java @@ -0,0 +1,102 @@ +//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 com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +import java.util.Date; + +@APICommand(name = "quotaTariffUpdate", responseObject = QuotaTariffResponse.class, description = "Update the tariff plan for a resource", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaTariffUpdateCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(QuotaTariffUpdateCmd.class); + private static final String s_name = "quotatariffupdateresponse"; + + @Inject + QuotaResponseBuilder _responseBuilder; + + @Parameter(name = ApiConstants.USAGE_TYPE, type = CommandType.INTEGER, required = true, description = "Integer value for the usage type of the resource") + private Integer usageType; + + @Parameter(name = "value", type = CommandType.DOUBLE, required = true, description = "The quota tariff value of the resource as per the default unit") + private Double value; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = true, description = "The effective start date on/after which the quota tariff is effective and older tariffs are no longer used for the usage type. Use yyyy-MM-dd as the date format, e.g. startDate=2009-06-03.") + private Date startDate; + + public int getUsageType() { + return usageType; + } + + public void setUsageType(int usageType) { + this.usageType = usageType; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public QuotaTariffUpdateCmd() { + super(); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + final QuotaTariffVO result = _responseBuilder.updateQuotaTariffPlan(this); + if (result == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update quota tariff plan"); + } + final QuotaTariffResponse response = _responseBuilder.createQuotaTariffResponse(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaUpdateCmd.java b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaUpdateCmd.java new file mode 100644 index 00000000000..e3c0fd279fe --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/command/QuotaUpdateCmd.java @@ -0,0 +1,72 @@ +//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 com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.response.QuotaUpdateResponse; +import org.apache.cloudstack.quota.QuotaAlertManager; +import org.apache.cloudstack.quota.QuotaManager; +import org.apache.cloudstack.quota.QuotaStatement; +import org.apache.log4j.Logger; + +import java.util.Calendar; + +import javax.inject.Inject; + +@APICommand(name = "quotaUpdate", responseObject = QuotaUpdateResponse.class, description = "Update quota calculations, alerts and statements", since = "4.6.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class QuotaUpdateCmd extends BaseCmd { + + public static final Logger s_logger = Logger.getLogger(QuotaUpdateCmd.class); + + private static final String s_name = "quotaupdateresponse"; + + @Inject + QuotaManager _manager; + @Inject + QuotaStatement _statement; + @Inject + QuotaAlertManager _alert; + + public QuotaUpdateCmd() { + super(); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + _manager.calculateQuotaUsage(); + _statement.sendStatement(); + _alert.checkAndSendQuotaAlertEmails(); + QuotaUpdateResponse response = new QuotaUpdateResponse(Calendar.getInstance()); + response.setResponseName(getCommandName()); + response.setObjectName("quotacredits"); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaBalanceResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaBalanceResponse.java new file mode 100644 index 00000000000..fca6b6cbb1a --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaBalanceResponse.java @@ -0,0 +1,153 @@ +//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.math.RoundingMode; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; + +import com.cloud.serializer.Param; + +public class QuotaBalanceResponse extends BaseResponse { + + @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("startquota") + @Param(description = "quota started with") + private BigDecimal startQuota; + + @SerializedName("endquota") + @Param(description = "quota by end of this period") + private BigDecimal endQuota; + + @SerializedName("credits") + @Param(description = "list of credits made during this period") + private List credits = null; + + @SerializedName("startdate") + @Param(description = "start date") + private Date startDate = null; + + @SerializedName("enddate") + @Param(description = "end date") + private Date endDate = null; + + @SerializedName("currency") + @Param(description = "currency") + private String currency; + + public QuotaBalanceResponse() { + super(); + credits = new ArrayList(); + } + + 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 BigDecimal getStartQuota() { + return startQuota; + } + + public void setStartQuota(BigDecimal startQuota) { + this.startQuota = startQuota.setScale(2, RoundingMode.HALF_EVEN); + } + + public BigDecimal getEndQuota() { + return endQuota; + } + + public void setEndQuota(BigDecimal endQuota) { + this.endQuota = endQuota.setScale(2, RoundingMode.HALF_EVEN); + } + + public List getCredits() { + return credits; + } + + public void setCredits(List credits) { + this.credits = credits; + } + + public void addCredits(QuotaBalanceVO credit) { + QuotaCreditsResponse cr = new QuotaCreditsResponse(); + cr.setCredits(credit.getCreditBalance()); + cr.setUpdatedOn(credit.getUpdatedOn() == null ? null : new Date(credit.getUpdatedOn().getTime())); + credits.add(0, cr); + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public Date getEndDate() { + return endDate == null ? null : new Date(endDate.getTime()); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaCreditsResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaCreditsResponse.java new file mode 100644 index 00000000000..2c16cf44b7c --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaCreditsResponse.java @@ -0,0 +1,91 @@ +//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.BaseResponse; +import org.apache.cloudstack.quota.vo.QuotaCreditsVO; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Date; + +public class QuotaCreditsResponse extends BaseResponse { + + @SerializedName("credits") + @Param(description = "the credit deposited") + private BigDecimal credits; + + @SerializedName("updated_by") + @Param(description = "the user name of the admin who updated the credits") + private String updatedBy; + + @SerializedName("updated_on") + @Param(description = "the account name of the admin who updated the credits") + private Date updatedOn; + + @SerializedName("currency") + @Param(description = "currency") + private String currency; + + public QuotaCreditsResponse() { + super(); + } + + public QuotaCreditsResponse(QuotaCreditsVO result, String updatedBy) { + super(); + if (result != null) { + setCredits(result.getCredit()); + setUpdatedBy(updatedBy); + setUpdatedOn(new Date()); + } + } + + public BigDecimal getCredits() { + return credits; + } + + public void setCredits(BigDecimal credits) { + this.credits = credits.setScale(2, RoundingMode.HALF_EVEN); + } + + public String getUpdatedBy() { + return updatedBy; + } + + public void setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + } + + public Date getUpdatedOn() { + return updatedOn; + } + + public void setUpdatedOn(Date updatedOn) { + this.updatedOn = updatedOn; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } +} \ No newline at end of file diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaEmailTemplateResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaEmailTemplateResponse.java new file mode 100644 index 00000000000..c4a2b7c3a60 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaEmailTemplateResponse.java @@ -0,0 +1,90 @@ +//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.BaseResponse; + +import java.util.Date; + +public class QuotaEmailTemplateResponse extends BaseResponse { + @SerializedName("templatetype") + @Param(description = "Template type") + private String templateType; + + @SerializedName("templatesubject") + @Param(description = "The quota email template subject") + private String templateSubject; + + @SerializedName("templatebody") + @Param(description = "The quota email template content") + private String templateText; + + @SerializedName("locale") + @Param(description = "The quota email template locale") + private String locale; + + @SerializedName("last_updated") + @Param(description = "Last date/time when template was updated") + private Date lastUpdatedOn; + + public QuotaEmailTemplateResponse() { + super(); + this.setObjectName("quotaemailtemplate"); + } + + public String getTemplateType() { + return templateType; + } + + public void setTemplateType(String templateType) { + this.templateType = templateType; + } + + public String getTemplateSubject() { + return templateSubject; + } + + public void setTemplateSubject(String templateSubject) { + this.templateSubject = templateSubject; + } + + public String getTemplateText() { + return templateText; + } + + public void setTemplateText(String templateText) { + this.templateText = templateText; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public Date getLastUpdatedOn() { + return lastUpdatedOn; + } + + public void setLastUpdatedOn(Date lastUpdatedOn) { + this.lastUpdatedOn = lastUpdatedOn; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java new file mode 100644 index 00000000000..a4260cb937d --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilder.java @@ -0,0 +1,65 @@ +//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 org.apache.cloudstack.api.command.QuotaBalanceCmd; +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.QuotaTariffListCmd; +import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; + +import java.util.Date; +import java.util.List; + +public interface QuotaResponseBuilder { + + QuotaTariffVO updateQuotaTariffPlan(QuotaTariffUpdateCmd cmd); + + List listQuotaTariffPlans(QuotaTariffListCmd cmd); + + QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO configuration); + + QuotaStatementResponse createQuotaStatementResponse(List quotaUsage); + + QuotaBalanceResponse createQuotaBalanceResponse(List quotaUsage, Date startDate, Date endDate); + + List createQuotaSummaryResponse(Boolean listAll); + + List createQuotaSummaryResponse(String accountName, Long domainId); + + QuotaBalanceResponse createQuotaLastBalanceResponse(List quotaBalance, Date startDate); + + QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Date despositedOn); + + List getQuotaUsage(QuotaStatementCmd cmd); + + List getQuotaBalance(QuotaBalanceCmd cmd); + + QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy); + + List listQuotaEmailTemplates(QuotaEmailTemplateListCmd cmd); + + boolean updateQuotaEmailTemplate(QuotaEmailTemplateUpdateCmd cmd); + + Date startOfNextDay(Date dt); + + Date startOfNextDay(); +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java new file mode 100644 index 00000000000..23b136363c1 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java @@ -0,0 +1,516 @@ +//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.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; + +import org.apache.cloudstack.api.command.QuotaBalanceCmd; +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.QuotaTariffListCmd; +import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; +import org.apache.cloudstack.quota.QuotaService; +import org.apache.cloudstack.quota.QuotaStatement; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.dao.QuotaAccountDao; +import org.apache.cloudstack.quota.dao.QuotaBalanceDao; +import org.apache.cloudstack.quota.dao.QuotaCreditsDao; +import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; +import org.apache.cloudstack.quota.dao.QuotaTariffDao; +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.QuotaCreditsVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.cloudstack.region.RegionManager; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; +import javax.inject.Inject; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +@Component +@Local(value = QuotaResponseBuilderImpl.class) +public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { + private static final Logger s_logger = Logger.getLogger(QuotaResponseBuilderImpl.class); + + @Inject + private QuotaTariffDao _quotaTariffDao; + @Inject + private QuotaBalanceDao _quotaBalanceDao; + @Inject + private QuotaCreditsDao _quotaCreditsDao; + @Inject + private QuotaUsageDao _quotaUsageDao; + @Inject + private QuotaEmailTemplatesDao _quotaEmailTemplateDao; + + @Inject + private UserDao _userDao; + @Inject + private QuotaService _quotaService; + @Inject + private AccountDao _accountDao; + @Inject + private QuotaAccountDao _quotaAccountDao; + @Inject + private DomainDao _domainDao; + @Inject + private RegionManager _regionMgr; + @Inject + private QuotaStatement _statement; + + @Override + public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff) { + final QuotaTariffResponse response = new QuotaTariffResponse(); + response.setUsageType(tariff.getUsageType()); + response.setUsageName(tariff.getUsageName()); + response.setUsageUnit(tariff.getUsageUnit()); + response.setUsageDiscriminator(tariff.getUsageDiscriminator()); + response.setTariffValue(tariff.getCurrencyValue()); + response.setEffectiveOn(tariff.getEffectiveOn()); + response.setDescription(tariff.getDescription()); + response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + return response; + } + + @Override + public List createQuotaSummaryResponse(final String accountName, final Long domainId) { + List result = new ArrayList(); + + if (accountName != null && domainId != null) { + Account account = _accountDao.findActiveAccount(accountName, domainId); + QuotaSummaryResponse qr = getQuotaSummaryResponse(account); + result.add(qr); + } + + return result; + } + + @Override + public List createQuotaSummaryResponse(Boolean listAll) { + List result = new ArrayList(); + + if (listAll) { + for (final AccountVO account : _accountDao.listAll()) { + QuotaSummaryResponse qr = getQuotaSummaryResponse(account); + result.add(qr); + } + } else { + for (final QuotaAccountVO quotaAccount : _quotaAccountDao.listAllQuotaAccount()) { + AccountVO account = _accountDao.findById(quotaAccount.getId()); + QuotaSummaryResponse qr = getQuotaSummaryResponse(account); + result.add(qr); + } + } + return result; + } + + private QuotaSummaryResponse getQuotaSummaryResponse(final Account account) { + Calendar[] period = _statement.getCurrentStatementTime(); + + if (account != null) { + QuotaSummaryResponse qr = new QuotaSummaryResponse(); + DomainVO domain = _domainDao.findById(account.getDomainId()); + BigDecimal curBalance = _quotaBalanceDao.lastQuotaBalance(account.getAccountId(), account.getDomainId(), period[1].getTime()); + BigDecimal quotaUsage = _quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, period[0].getTime(), period[1].getTime()); + + qr.setAccountId(account.getAccountId()); + qr.setAccountName(account.getAccountName()); + qr.setDomainId(account.getDomainId()); + qr.setDomainName(domain.getName()); + qr.setBalance(curBalance); + qr.setQuotaUsage(quotaUsage); + qr.setState(account.getState()); + qr.setStartDate(period[0].getTime()); + qr.setEndDate(period[1].getTime()); + qr.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + qr.setObjectName("summary"); + return qr; + } else { + throw new InvalidParameterValueException("Quota summary response for an account requires a valid account."); + } + } + + @Override + public QuotaBalanceResponse createQuotaBalanceResponse(List quotaBalance, Date startDate, Date endDate) { + if (quotaBalance == null || quotaBalance.isEmpty()) { + new InvalidParameterValueException("The request period does not contain balance entries."); + } + Collections.sort(quotaBalance, new Comparator() { + public int compare(QuotaBalanceVO o1, QuotaBalanceVO o2) { + return o2.getUpdatedOn().compareTo(o1.getUpdatedOn()); // desc + } + }); + + boolean have_balance_entries = false; + //check that there is at least one balance entry + for (Iterator it = quotaBalance.iterator(); it.hasNext();) { + QuotaBalanceVO entry = it.next(); + if (entry.getCreditsId() > 0) { + have_balance_entries = true; + break; + } + } + //if last entry is a credit deposit then remove that as that is already + //accounted for in the starting balance after that entry, note the sort is desc + if (have_balance_entries) { + ListIterator li = quotaBalance.listIterator(quotaBalance.size()); + // Iterate in reverse. + while (li.hasPrevious()) { + QuotaBalanceVO entry = li.previous(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("createQuotaBalanceResponse: Entry=" + entry); + } + if (entry.getCreditsId() > 0) { + li.remove(); + } else { + break; + } + } + } + + int quota_activity = quotaBalance.size(); + QuotaBalanceResponse resp = new QuotaBalanceResponse(); + BigDecimal lastCredits = new BigDecimal(0); + boolean consecutive = true; + for (Iterator it = quotaBalance.iterator(); it.hasNext();) { + QuotaBalanceVO entry = it.next(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("createQuotaBalanceResponse: All Credit Entry=" + entry); + } + if (entry.getCreditsId() > 0) { + if (consecutive) { + lastCredits = lastCredits.add(entry.getCreditBalance()); + } + resp.addCredits(entry); + it.remove(); + } else { + consecutive = false; + } + } + + if (quota_activity > 0 && quotaBalance.size() > 0) { + // order is desc last item is the start item + QuotaBalanceVO startItem = quotaBalance.get(quotaBalance.size() - 1); + QuotaBalanceVO endItem = quotaBalance.get(0); + resp.setStartDate(startItem.getUpdatedOn()); + resp.setStartQuota(startItem.getCreditBalance()); + resp.setEndDate(endItem.getUpdatedOn()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("createQuotaBalanceResponse: Start Entry=" + startItem); + s_logger.debug("createQuotaBalanceResponse: End Entry=" + endItem); + } + resp.setEndQuota(endItem.getCreditBalance().add(lastCredits)); + } else if (quota_activity > 0) { + // order is desc last item is the start item + resp.setStartDate(startDate); + resp.setStartQuota(new BigDecimal(0)); + resp.setEndDate(endDate); + resp.setEndQuota(new BigDecimal(0).add(lastCredits)); + } else { + resp.setStartDate(startDate); + resp.setEndDate(endDate); + resp.setStartQuota(new BigDecimal(0)); + resp.setEndQuota(new BigDecimal(0)); + } + resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + resp.setObjectName("balance"); + return resp; + } + + @Override + public QuotaStatementResponse createQuotaStatementResponse(final List quotaUsage) { + if (quotaUsage == null || quotaUsage.isEmpty()) { + throw new InvalidParameterValueException("There is no usage data found for period mentioned."); + } + + QuotaStatementResponse statement = new QuotaStatementResponse(); + + HashMap quotaTariffMap = new HashMap(); + Collection 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 (s_logger.isDebugEnabled()) { + s_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() { + public int compare(QuotaUsageVO o1, QuotaUsageVO o2) { + if (o1.getUsageType() == o2.getUsageType()) + return 0; + return o1.getUsageType() < o2.getUsageType() ? -1 : 1; + } + }); + + List items = new ArrayList(); + 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 (s_logger.isDebugEnabled()) { + s_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.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + statement.setObjectName("statement"); + return statement; + } + + @Override + public List listQuotaTariffPlans(final QuotaTariffListCmd cmd) { + List result = new ArrayList(); + Date effectiveDate = cmd.getEffectiveDate() == null ? new Date() : cmd.getEffectiveDate(); + Date adjustedEffectiveDate = _quotaService.computeAdjustedTime(effectiveDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Effective datec=" + effectiveDate + " quotatype=" + cmd.getUsageType() + " Adjusted date=" + adjustedEffectiveDate); + } + if (cmd.getUsageType() != null) { + QuotaTariffVO tariffPlan = _quotaTariffDao.findTariffPlanByUsageType(cmd.getUsageType(), adjustedEffectiveDate); + if (tariffPlan != null) { + result.add(tariffPlan); + } + } else { + result = _quotaTariffDao.listAllTariffPlans(adjustedEffectiveDate); + } + return result; + } + + @Override + public QuotaTariffVO updateQuotaTariffPlan(QuotaTariffUpdateCmd cmd) { + final int quotaType = cmd.getUsageType(); + final BigDecimal quotaCost = new BigDecimal(cmd.getValue()); + final Date effectiveDate = _quotaService.computeAdjustedTime(cmd.getStartDate()); + final Date now = _quotaService.computeAdjustedTime(new Date()); + // if effective date is in the past return error + if (effectiveDate.compareTo(now) < 0) { + throw new InvalidParameterValueException("Incorrect effective date for tariff " + effectiveDate + " is less than now " + now); + } + QuotaTypes quotaConstant = QuotaTypes.listQuotaTypes().get(quotaType); + if (quotaConstant == null) { + throw new InvalidParameterValueException("Quota type does not exists " + quotaType); + } + + QuotaTariffVO result = null; + result = new QuotaTariffVO(quotaType); + result.setUsageName(quotaConstant.getQuotaName()); + result.setUsageUnit(quotaConstant.getQuotaUnit()); + result.setUsageDiscriminator(quotaConstant.getDiscriminator()); + result.setCurrencyValue(quotaCost); + result.setEffectiveOn(effectiveDate); + result.setUpdatedOn(now); + result.setUpdatedBy(cmd.getEntityOwnerId()); + + if (s_logger.isDebugEnabled()) { + s_logger.debug(String.format("Updating Quota Tariff Plan: New value=%s for resource type=%d effective on date=%s", quotaCost, quotaType, effectiveDate)); + } + _quotaTariffDao.addQuotaTariff(result); + + return result; + } + + @Override + public QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy) { + Date depositDate = new Date(); + Date adjustedStartDate = _quotaService.computeAdjustedTime(depositDate); + QuotaBalanceVO qb = _quotaBalanceDao.findLaterBalanceEntry(accountId, domainId, adjustedStartDate); + + if (qb != null) { + throw new InvalidParameterValueException("Incorrect deposit date: " + adjustedStartDate + " there are balance entries after this date"); + } + + return addQuotaCredits(accountId, domainId, amount, updatedBy, adjustedStartDate); + } + + @Override + public QuotaCreditsResponse addQuotaCredits(final Long accountId, final Long domainId, final Double amount, final Long updatedBy, final Date despositedOn) { + + QuotaCreditsVO credits = new QuotaCreditsVO(accountId, domainId, new BigDecimal(amount), updatedBy); + s_logger.debug("AddQuotaCredits: Depositing " + amount + " on adjusted date " + despositedOn); + credits.setUpdatedOn(despositedOn); + QuotaCreditsVO result = _quotaCreditsDao.saveCredits(credits); + + final AccountVO account = _accountDao.findById(accountId); + final boolean lockAccountEnforcement = "true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value()); + final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(despositedOn)); + if (lockAccountEnforcement && (currentAccountBalance.compareTo(new BigDecimal(0)) >= 0)) { + if (account.getState() == Account.State.locked) { + _regionMgr.enableAccount(account.getAccountName(), domainId, accountId); + } + } + + String creditor = String.valueOf(Account.ACCOUNT_ID_SYSTEM); + User creditorUser = _userDao.getUser(updatedBy); + if (creditorUser != null) { + creditor = creditorUser.getUsername(); + } + QuotaCreditsResponse response = new QuotaCreditsResponse(result, creditor); + response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + return response; + } + + private QuotaEmailTemplateResponse createQuotaEmailResponse(QuotaEmailTemplatesVO template) { + QuotaEmailTemplateResponse response = new QuotaEmailTemplateResponse(); + response.setTemplateType(template.getTemplateName()); + response.setTemplateSubject(template.getTemplateSubject()); + response.setTemplateText(template.getTemplateBody()); + response.setLocale(template.getLocale()); + response.setLastUpdatedOn(template.getLastUpdated()); + return response; + } + + @Override + public List listQuotaEmailTemplates(QuotaEmailTemplateListCmd cmd) { + final String templateName = cmd.getTemplateName(); + List templates = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(templateName); + final List responses = new ArrayList(); + for (final QuotaEmailTemplatesVO template : templates) { + responses.add(createQuotaEmailResponse(template)); + } + return responses; + } + + @Override + public boolean updateQuotaEmailTemplate(QuotaEmailTemplateUpdateCmd cmd) { + final String templateName = cmd.getTemplateName(); + final String templateSubject = StringEscapeUtils.escapeJavaScript(cmd.getTemplateSubject()); + final String templateBody = StringEscapeUtils.escapeJavaScript(cmd.getTemplateBody()); + final String locale = cmd.getLocale(); + + final List templates = _quotaEmailTemplateDao.listAllQuotaEmailTemplates(templateName); + if (templates.size() == 1) { + final QuotaEmailTemplatesVO template = templates.get(0); + template.setTemplateSubject(templateSubject); + template.setTemplateBody(templateBody); + if (locale != null) { + template.setLocale(locale); + } + return _quotaEmailTemplateDao.updateQuotaEmailTemplate(template); + } + return false; + } + + @Override + public QuotaBalanceResponse createQuotaLastBalanceResponse(List quotaBalance, Date startDate) { + if (quotaBalance == null) { + throw new InvalidParameterValueException("There are no balance entries on or before the requested date."); + } + if (startDate == null) { + startDate = new Date(); + } + QuotaBalanceResponse resp = new QuotaBalanceResponse(); + BigDecimal lastCredits = new BigDecimal(0); + for (QuotaBalanceVO entry : quotaBalance) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("createQuotaLastBalanceResponse Date=" + entry.getUpdatedOn() + " balance=" + entry.getCreditBalance() + " credit=" + entry.getCreditsId()); + } + lastCredits = lastCredits.add(entry.getCreditBalance()); + } + resp.setStartQuota(lastCredits); + resp.setStartDate(_quotaService.computeAdjustedTime(startDate)); + resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + resp.setObjectName("balance"); + return resp; + } + + @Override + public List getQuotaUsage(QuotaStatementCmd cmd) { + return _quotaService.getQuotaUsage(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getUsageType(), cmd.getStartDate(), cmd.getEndDate()); + } + + @Override + public List getQuotaBalance(QuotaBalanceCmd cmd) { + return _quotaService.findQuotaBalanceVO(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate()); + } + + @Override + public Date startOfNextDay(Date dt) { + Calendar c = Calendar.getInstance(); + c.setTime(dt); + c.add(Calendar.DATE, 1); + dt = c.getTime(); + return dt; + } + + @Override + public Date startOfNextDay() { + Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + c.add(Calendar.DATE, 1); + Date dt = c.getTime(); + return dt; + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java new file mode 100644 index 00000000000..dec67606a16 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementItemResponse.java @@ -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.response; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class QuotaStatementItemResponse extends BaseResponse { + + @SerializedName("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") + private String usageName; + + @SerializedName("unit") + @Param(description = "usage unit") + private String usageUnit; + + @SerializedName("quota") + @Param(description = "quota consumed") + private BigDecimal quotaUsed; + + 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; + } + + public void setUsageName(String usageName) { + this.usageName = usageName; + } + + public int getUsageType() { + return usageType; + } + + public void setUsageType(int usageType) { + this.usageType = usageType; + } + + public String getUsageUnit() { + return usageUnit; + } + + public void setUsageUnit(String usageUnit) { + this.usageUnit = usageUnit; + } + + public BigDecimal getQuotaUsed() { + return quotaUsed; + } + + public void setQuotaUsed(BigDecimal quotaUsed) { + this.quotaUsed = quotaUsed.setScale(2, RoundingMode.HALF_EVEN); + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementResponse.java new file mode 100644 index 00000000000..efe8e8ecac0 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaStatementResponse.java @@ -0,0 +1,130 @@ +//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.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("account") + @Param(description = "account name") + private String accountName; + + @SerializedName("domain") + @Param(description = "domain id") + private Long domainId; + + @SerializedName("quotausage") + @Param(description = "list of quota usage under various types", responseObject = QuotaStatementItemResponse.class) + private List lineItem; + + @SerializedName("totalquota") + @Param(description = "total quota used during this period") + private BigDecimal totalQuota; + + @SerializedName("startdate") + @Param(description = "start date") + private Date startDate = null; + + @SerializedName("enddate") + @Param(description = "end date") + private Date endDate = null; + + @SerializedName("currency") + @Param(description = "currency") + private String currency; + + public QuotaStatementResponse() { + super(); + } + + 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 List getLineItem() { + return lineItem; + } + + public void setLineItem(List lineItem) { + this.lineItem = lineItem; + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public Date getEndDate() { + return endDate == null ? null : new Date(endDate.getTime()); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + + public BigDecimal getTotalQuota() { + return totalQuota; + } + + public void setTotalQuota(BigDecimal totalQuota) { + this.totalQuota = totalQuota.setScale(2, RoundingMode.HALF_EVEN); + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaSummaryResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaSummaryResponse.java new file mode 100644 index 00000000000..4e6f684c606 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaSummaryResponse.java @@ -0,0 +1,155 @@ +//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.math.RoundingMode; +import java.util.Date; + +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.cloud.user.Account.State; + +public class QuotaSummaryResponse extends BaseResponse { + + @SerializedName("accountid") + @Param(description = "account id") + private Long accountId; + + @SerializedName("account") + @Param(description = "account name") + private String accountName; + + @SerializedName("domainid") + @Param(description = "domain id") + private Long domainId; + + @SerializedName("domain") + @Param(description = "domain name") + private String domainName; + + @SerializedName("balance") + @Param(description = "account balance") + private BigDecimal balance; + + @SerializedName("state") + @Param(description = "account state") + private State state; + + @SerializedName("quota") + @Param(description = "quota usage of this period") + private BigDecimal quotaUsage; + + @SerializedName("startdate") + @Param(description = "start date") + private Date startDate = null; + + @SerializedName("enddate") + @Param(description = "end date") + private Date endDate = null; + + @SerializedName("currency") + @Param(description = "currency") + private String currency; + + public QuotaSummaryResponse() { + super(); + } + + 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 getDomainName() { + return domainName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public BigDecimal getQuotaUsage() { + return quotaUsage; + } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public void setQuotaUsage(BigDecimal startQuota) { + this.quotaUsage = startQuota.setScale(2, RoundingMode.HALF_EVEN); + } + + public BigDecimal getBalance() { + return balance; + } + + public void setBalance(BigDecimal balance) { + this.balance = balance.setScale(2, RoundingMode.HALF_EVEN); + } + + public Date getStartDate() { + return startDate == null ? null : new Date(startDate.getTime()); + } + + public void setStartDate(Date startDate) { + this.startDate = startDate == null ? null : new Date(startDate.getTime()); + } + + public Date getEndDate() { + return endDate == null ? null : new Date(endDate.getTime()); + } + + public void setEndDate(Date endDate) { + this.endDate = endDate == null ? null : new Date(endDate.getTime()); + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTariffResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTariffResponse.java new file mode 100644 index 00000000000..48697fd2a82 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTariffResponse.java @@ -0,0 +1,134 @@ +//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.BaseResponse; + +import java.math.BigDecimal; +import java.util.Date; + +public class QuotaTariffResponse extends BaseResponse { + + @SerializedName("usageType") + @Param(description = "usageType") + private int usageType; + + @SerializedName("usageName") + @Param(description = "usageName") + private String usageName; + + @SerializedName("usageUnit") + @Param(description = "usageUnit") + private String usageUnit; + + @SerializedName("usageDiscriminator") + @Param(description = "usageDiscriminator") + private String usageDiscriminator; + + @SerializedName("tariffValue") + @Param(description = "tariffValue") + private BigDecimal tariffValue; + + @SerializedName("effectiveDate") + @Param(description = "the date on/after which this quota value will be effective") + private Date effectiveOn = null; + + @SerializedName("description") + @Param(description = "description") + private String description; + + @SerializedName("currency") + @Param(description = "currency") + private String currency; + + public QuotaTariffResponse() { + super(); + this.setObjectName("quotatariff"); + } + + public QuotaTariffResponse(final int usageType) { + super(); + this.usageType = usageType; + } + + public String getUsageName() { + return usageName; + } + + public void setUsageName(String usageName) { + this.usageName = usageName; + } + + public int getUsageType() { + return usageType; + } + + public void setUsageType(int usageType) { + this.usageType = usageType; + } + + public String getUsageUnit() { + return usageUnit; + } + + public void setUsageUnit(String usageUnit) { + this.usageUnit = usageUnit; + } + + public String getUsageDiscriminator() { + return usageDiscriminator; + } + + public void setUsageDiscriminator(String usageDiscriminator) { + this.usageDiscriminator = usageDiscriminator; + } + + public BigDecimal getTariffValue() { + return tariffValue; + } + + public void setTariffValue(BigDecimal tariffValue) { + this.tariffValue = tariffValue; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getEffectiveOn() { + return effectiveOn; + } + + public void setEffectiveOn(Date effectiveOn) { + this.effectiveOn = effectiveOn; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTypeResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTypeResponse.java new file mode 100644 index 00000000000..989fba54e7f --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaTypeResponse.java @@ -0,0 +1,58 @@ +// 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.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class QuotaTypeResponse extends BaseResponse { + + @SerializedName("quotatypeid") + @Param(description = "quota type") + private Integer quotaType; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of usage type") + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getQuotaType() { + return quotaType; + } + + public void setQuotaType(Integer quotaType) { + this.quotaType = quotaType; + } + + public QuotaTypeResponse(Integer quotaType, String description) { + this.quotaType = quotaType; + this.description = description; + setObjectName(ApiConstants.USAGE_TYPE); + } + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaUpdateResponse.java b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaUpdateResponse.java new file mode 100644 index 00000000000..e98da9b0004 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/api/response/QuotaUpdateResponse.java @@ -0,0 +1,38 @@ +//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.BaseResponse; + +import java.util.Calendar; +import java.util.Date; + +public class QuotaUpdateResponse extends BaseResponse { + + @SerializedName("updated_on") + @Param(description = "timestamp when the run got over") + private Date updatedOn; + + public QuotaUpdateResponse(Calendar now) { + super(); + updatedOn=now.getTime(); + } + +} \ No newline at end of file diff --git a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java new file mode 100644 index 00000000000..8ee39b878af --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaService.java @@ -0,0 +1,39 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; + +import java.util.Date; +import java.util.List; + +public interface QuotaService extends PluggableService { + + List getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate); + + List findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate); + + Date computeAdjustedTime(Date date); + + void setLockAccount(Long accountId, Boolean state); + + void setMinBalance(Long accountId, Double balance); + +} diff --git a/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java new file mode 100644 index 00000000000..1b4c98f6997 --- /dev/null +++ b/plugins/database/quota/src/org/apache/cloudstack/quota/QuotaServiceImpl.java @@ -0,0 +1,299 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.quota; + +import com.cloud.configuration.Config; +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.Filter; + +import org.apache.cloudstack.api.command.QuotaBalanceCmd; +import org.apache.cloudstack.api.command.QuotaCreditsCmd; +import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; +import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; +import org.apache.cloudstack.api.command.QuotaStatementCmd; +import org.apache.cloudstack.api.command.QuotaSummaryCmd; +import org.apache.cloudstack.api.command.QuotaTariffListCmd; +import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; +import org.apache.cloudstack.api.command.QuotaUpdateCmd; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +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.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaUsageVO; +import org.apache.cloudstack.utils.usage.UsageUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +@Component +@Local(value = QuotaService.class) +public class QuotaServiceImpl extends ManagerBase implements QuotaService, Configurable, QuotaConfig { + private static final Logger s_logger = Logger.getLogger(QuotaServiceImpl.class); + + @Inject + private AccountDao _accountDao; + @Inject + private QuotaAccountDao _quotaAcc; + @Inject + private QuotaUsageDao _quotaUsageDao; + @Inject + private DomainDao _domainDao; + @Inject + private ConfigurationDao _configDao; + @Inject + private QuotaBalanceDao _quotaBalanceDao; + @Inject + private QuotaResponseBuilder _respBldr; + + private TimeZone _usageTimezone; + private int _aggregationDuration = 0; + + public QuotaServiceImpl() { + super(); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + String timeZoneStr = _configDao.getValue(Config.UsageAggregationTimezone.toString()); + String aggregationRange = _configDao.getValue(Config.UsageStatsJobAggregationRange.toString()); + if (timeZoneStr == null) { + timeZoneStr = "GMT"; + } + _usageTimezone = TimeZone.getTimeZone(timeZoneStr); + + _aggregationDuration = Integer.parseInt(aggregationRange); + if (_aggregationDuration < UsageUtils.USAGE_AGGREGATION_RANGE_MIN) { + s_logger.warn("Usage stats job aggregation range is to small, using the minimum value of " + UsageUtils.USAGE_AGGREGATION_RANGE_MIN); + _aggregationDuration = UsageUtils.USAGE_AGGREGATION_RANGE_MIN; + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Usage timezone = " + _usageTimezone + " AggregationDuration=" + _aggregationDuration); + } + return true; + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + if (!isQuotaServiceEnabled()) { + return cmdList; + } + cmdList.add(QuotaStatementCmd.class); + cmdList.add(QuotaBalanceCmd.class); + cmdList.add(QuotaSummaryCmd.class); + cmdList.add(QuotaUpdateCmd.class); + cmdList.add(QuotaTariffListCmd.class); + cmdList.add(QuotaTariffUpdateCmd.class); + cmdList.add(QuotaCreditsCmd.class); + cmdList.add(QuotaEmailTemplateListCmd.class); + cmdList.add(QuotaEmailTemplateUpdateCmd.class); + return cmdList; + } + + @Override + public String getConfigComponentName() { + return "QUOTA-PLUGIN"; + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { QuotaPluginEnabled, QuotaEnableEnforcement, QuotaCurrencySymbol, QuotaStatementPeriod, QuotaSmtpHost, QuotaSmtpPort, QuotaSmtpTimeout, QuotaSmtpUser, QuotaSmtpPassword, + QuotaSmtpAuthType, QuotaSmtpSender }; + } + + public Boolean isQuotaServiceEnabled() { + return QuotaPluginEnabled.value(); + } + + @Override + public List findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate) { + 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 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"); + } + } + + startDate = startDate == null ? new Date() : startDate; + + if (endDate == null) { + // adjust start date to end of day as there is no end date + Date adjustedStartDate = computeAdjustedTime(_respBldr.startOfNextDay(startDate)); + if (s_logger.isDebugEnabled()) { + s_logger.debug("getQuotaBalance1: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", on or before " + adjustedStartDate); + } + List qbrecords = _quotaBalanceDao.lastQuotaBalanceVO(accountId, domainId, adjustedStartDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Found records size=" + qbrecords.size()); + } + if (qbrecords.isEmpty()) { + s_logger.info("Incorrect Date there are no quota records before this date " + adjustedStartDate); + return qbrecords; + } else { + return qbrecords; + } + } else { + Date adjustedStartDate = computeAdjustedTime(startDate); + if (endDate.after(_respBldr.startOfNextDay())) { + throw new InvalidParameterValueException("Incorrect Date Range. End date:" + endDate + " should not be in future. "); + } else if (startDate.before(endDate)) { + Date adjustedEndDate = computeAdjustedTime(endDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("getQuotaBalance2: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", between " + adjustedStartDate + " and " + + adjustedEndDate); + } + List qbrecords = _quotaBalanceDao.findQuotaBalance(accountId, domainId, adjustedStartDate, adjustedEndDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("getQuotaBalance3: Found records size=" + qbrecords.size()); + } + if (qbrecords.isEmpty()) { + s_logger.info("There are no quota records between these dates start date " + adjustedStartDate + " and end date:" + endDate); + return qbrecords; + } else { + return qbrecords; + } + } else { + throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate); + } + } + } + + @Override + public List 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 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"); + } + } + + if (startDate.after(endDate)) { + throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate); + } + if (endDate.after(_respBldr.startOfNextDay())) { + throw new InvalidParameterValueException("Incorrect Date Range. End date:" + endDate + " should not be in future. "); + } + Date adjustedEndDate = computeAdjustedTime(endDate); + Date adjustedStartDate = computeAdjustedTime(startDate); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Getting quota records for account: " + accountId + ", domainId: " + domainId + ", between " + startDate + " and " + endDate); + } + return _quotaUsageDao.findQuotaUsage(accountId, domainId, usageType, adjustedStartDate, adjustedEndDate); + } + + @Override + public Date computeAdjustedTime(final Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + TimeZone localTZ = cal.getTimeZone(); + int timezoneOffset = cal.get(Calendar.ZONE_OFFSET); + if (localTZ.inDaylightTime(date)) { + timezoneOffset += (60 * 60 * 1000); + } + cal.add(Calendar.MILLISECOND, timezoneOffset); + + Date newTime = cal.getTime(); + + Calendar calTS = Calendar.getInstance(_usageTimezone); + calTS.setTime(newTime); + timezoneOffset = calTS.get(Calendar.ZONE_OFFSET); + if (_usageTimezone.inDaylightTime(date)) { + timezoneOffset += (60 * 60 * 1000); + } + + calTS.add(Calendar.MILLISECOND, -1 * timezoneOffset); + + return calTS.getTime(); + } + + @Override + public void setLockAccount(Long accountId, Boolean state) { + QuotaAccountVO acc = _quotaAcc.findByIdQuotaAccount(accountId); + if (acc == null) { + acc = new QuotaAccountVO(accountId); + acc.setQuotaEnforce(state ? 1 : 0); + _quotaAcc.persistQuotaAccount(acc); + } else { + acc.setQuotaEnforce(state ? 1 : 0); + _quotaAcc.updateQuotaAccount(accountId, acc); + } + } + + @Override + public void setMinBalance(Long accountId, Double balance) { + QuotaAccountVO acc = _quotaAcc.findByIdQuotaAccount(accountId); + if (acc == null) { + acc = new QuotaAccountVO(accountId); + acc.setQuotaMinBalance(new BigDecimal(balance)); + _quotaAcc.persistQuotaAccount(acc); + } else { + acc.setQuotaMinBalance(new BigDecimal(balance)); + _quotaAcc.updateQuotaAccount(accountId, acc); + } + } + +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java new file mode 100644 index 00000000000..4369a8c9f0c --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java @@ -0,0 +1,65 @@ +// 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 junit.framework.TestCase; +import org.apache.cloudstack.api.response.QuotaBalanceResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaBalanceCmdTest extends TestCase { + + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaBalanceCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaBalanceCmd cmd = new QuotaBalanceCmd(); + Field rbField = QuotaBalanceCmd.class.getDeclaredField("_responseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + List quotaBalanceVOList = new ArrayList(); + Mockito.when(responseBuilder.getQuotaBalance(Mockito.any(cmd.getClass()))).thenReturn(quotaBalanceVOList); + Mockito.when(responseBuilder.createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse()); + Mockito.when(responseBuilder.createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse()); + Mockito.when(responseBuilder.startOfNextDay(Mockito.any(Date.class))).thenReturn(new Date()); + + // end date not specified + cmd.setStartDate(new Date()); + cmd.setEndDate(null); + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class)); + Mockito.verify(responseBuilder, Mockito.times(0)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class)); + + // end date specified + cmd.setEndDate(new Date()); + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class)); + } +} \ No newline at end of file diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaCreditsCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaCreditsCmdTest.java new file mode 100644 index 00000000000..f4d16b7b68e --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaCreditsCmdTest.java @@ -0,0 +1,87 @@ +// 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 com.cloud.user.AccountService; +import com.cloud.user.AccountVO; + +import junit.framework.TestCase; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.QuotaCreditsResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.quota.QuotaService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaCreditsCmdTest extends TestCase { + @Mock + QuotaResponseBuilder responseBuilder; + @Mock + QuotaService quotaService; + @Mock + AccountService accountService; + + @Test + public void testQuotaCreditsCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaCreditsCmd cmd = new QuotaCreditsCmd(); + cmd.setAccountName("admin"); + cmd.setMinBalance(200.0); + + Field rbField = QuotaCreditsCmd.class.getDeclaredField("_responseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + Field qsbField = QuotaCreditsCmd.class.getDeclaredField("_quotaService"); + qsbField.setAccessible(true); + qsbField.set(cmd, quotaService); + + Field asField = BaseCmd.class.getDeclaredField("_accountService"); + asField.setAccessible(true); + asField.set(cmd, accountService); + + AccountVO acc = new AccountVO(); + acc.setId(2L); + Mockito.when(accountService.getActiveAccountByName(Mockito.anyString(), Mockito.anyLong())).thenReturn(acc); + Mockito.when(responseBuilder.addQuotaCredits(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyDouble(), Mockito.anyLong())).thenReturn(new QuotaCreditsResponse()); + + // No value provided test + try { + cmd.execute(); + } catch (ServerApiException e) { + assertTrue(e.getErrorCode().equals(ApiErrorCode.PARAM_ERROR)); + } + + // With value provided test + cmd.setValue(11.80); + cmd.execute(); + Mockito.verify(quotaService, Mockito.times(0)).setLockAccount(Mockito.anyLong(), Mockito.anyBoolean()); + Mockito.verify(quotaService, Mockito.times(1)).setMinBalance(Mockito.anyLong(), Mockito.anyDouble()); + Mockito.verify(responseBuilder, Mockito.times(1)).addQuotaCredits(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyDouble(), Mockito.anyLong()); + + + } + +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmdTest.java new file mode 100644 index 00000000000..aecee6ba84c --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmdTest.java @@ -0,0 +1,50 @@ +// 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 junit.framework.TestCase; +import org.apache.cloudstack.api.response.QuotaEmailTemplateResponse; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaEmailTemplateListCmdTest extends TestCase { + + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaEmailTemplateListCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaEmailTemplateListCmd cmd = new QuotaEmailTemplateListCmd(); + Field rbField = QuotaEmailTemplateListCmd.class.getDeclaredField("_quotaResponseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + List responses = new ArrayList(); + Mockito.when(responseBuilder.listQuotaEmailTemplates(Mockito.eq(cmd))).thenReturn(responses); + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).listQuotaEmailTemplates(cmd); + } +} \ No newline at end of file diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmdTest.java new file mode 100644 index 00000000000..a357a181c38 --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmdTest.java @@ -0,0 +1,68 @@ +// 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 junit.framework.TestCase; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.quota.constant.QuotaConfig; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaEmailTemplateUpdateCmdTest extends TestCase { + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaEmailTemplateUpdateCmd () throws NoSuchFieldException, IllegalAccessException { + QuotaEmailTemplateUpdateCmd cmd = new QuotaEmailTemplateUpdateCmd(); + + Field rbField = QuotaEmailTemplateUpdateCmd.class.getDeclaredField("_quotaResponseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + // templatename parameter check + try { + cmd.execute(); + } catch (ServerApiException e) { + assertTrue(e.getErrorCode().equals(ApiErrorCode.PARAM_ERROR)); + } + + // invalid template name test + cmd.setTemplateName("randomTemplate"); + cmd.setTemplateBody("some body"); + cmd.setTemplateSubject("some subject"); + try { + cmd.execute(); + } catch (ServerApiException e) { + assertTrue(e.getErrorCode().equals(ApiErrorCode.PARAM_ERROR)); + } + + // valid template test + cmd.setTemplateName(QuotaConfig.QuotaEmailTemplateTypes.QUOTA_EMPTY.toString()); + Mockito.when(responseBuilder.updateQuotaEmailTemplate(Mockito.eq(cmd))).thenReturn(true); + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).updateQuotaEmailTemplate(Mockito.eq(cmd)); + } +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java new file mode 100644 index 00000000000..0492ae84b8d --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaStatementCmdTest.java @@ -0,0 +1,53 @@ +// 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 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.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaStatementCmdTest extends TestCase { + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaStatementCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaStatementCmd cmd = new QuotaStatementCmd(); + cmd.setAccountName("admin"); + + Field rbField = QuotaStatementCmd.class.getDeclaredField("_responseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + List quotaUsageVOList = new ArrayList(); + 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)); + } +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java new file mode 100644 index 00000000000..5781103f027 --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffListCmdTest.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import junit.framework.TestCase; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaTariffListCmdTest extends TestCase { + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaTariffListCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaTariffListCmd cmd = new QuotaTariffListCmd(); + + Field rbField = QuotaTariffListCmd.class.getDeclaredField("_responseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + List quotaTariffVOList = new ArrayList(); + QuotaTariffVO tariff = new QuotaTariffVO(); + tariff.setEffectiveOn(new Date()); + tariff.setCurrencyValue(new BigDecimal(100)); + tariff.setUsageType(QuotaTypes.MEMORY); + + quotaTariffVOList.add(new QuotaTariffVO()); + Mockito.when(responseBuilder.listQuotaTariffPlans(Mockito.eq(cmd))).thenReturn(quotaTariffVOList); + Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class))).thenReturn(new QuotaTariffResponse()); + + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.any(QuotaTariffVO.class)); + } +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java new file mode 100644 index 00000000000..dc50af1c28d --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/command/QuotaTariffUpdateCmdTest.java @@ -0,0 +1,67 @@ +// 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 junit.framework.TestCase; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.api.response.QuotaTariffResponse; +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.Date; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaTariffUpdateCmdTest extends TestCase { + + @Mock + QuotaResponseBuilder responseBuilder; + + @Test + public void testQuotaTariffUpdateCmd() throws NoSuchFieldException, IllegalAccessException { + QuotaTariffUpdateCmd cmd = new QuotaTariffUpdateCmd(); + + Field rbField = QuotaTariffUpdateCmd.class.getDeclaredField("_responseBuilder"); + rbField.setAccessible(true); + rbField.set(cmd, responseBuilder); + + QuotaTariffVO tariff = new QuotaTariffVO(); + tariff.setEffectiveOn(new Date()); + tariff.setCurrencyValue(new BigDecimal(100)); + tariff.setUsageType(QuotaTypes.MEMORY); + + Mockito.when(responseBuilder.updateQuotaTariffPlan(Mockito.eq(cmd))).thenReturn(null); + try { + cmd.execute(); + } catch (ServerApiException e) { + assertTrue(e.getErrorCode().equals(ApiErrorCode.INTERNAL_ERROR)); + } + + Mockito.when(responseBuilder.updateQuotaTariffPlan(Mockito.eq(cmd))).thenReturn(tariff); + Mockito.when(responseBuilder.createQuotaTariffResponse(Mockito.eq(tariff))).thenReturn(new QuotaTariffResponse()); + cmd.execute(); + Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaTariffResponse(Mockito.eq(tariff)); + } +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java new file mode 100644 index 00000000000..65aadd80dee --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java @@ -0,0 +1,228 @@ +// 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.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.db.TransactionLegacy; +import junit.framework.TestCase; +import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; +import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; +import org.apache.cloudstack.quota.QuotaService; +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.cloudstack.quota.dao.QuotaBalanceDao; +import org.apache.cloudstack.quota.dao.QuotaCreditsDao; +import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; +import org.apache.cloudstack.quota.dao.QuotaTariffDao; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.cloudstack.quota.vo.QuotaCreditsVO; +import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.apache.cloudstack.quota.vo.QuotaTariffVO; +import org.apache.cloudstack.region.RegionManager; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaResponseBuilderImplTest extends TestCase { + + @Mock + QuotaTariffDao quotaTariffDao; + @Mock + QuotaBalanceDao quotaBalanceDao; + @Mock + QuotaCreditsDao quotaCreditsDao; + @Mock + QuotaEmailTemplatesDao quotaEmailTemplateDao; + + @Mock + UserDao userDao; + @Mock + QuotaService quotaService; + @Mock + AccountDao accountDao; + @Mock + RegionManager regionMgr; + + QuotaResponseBuilderImpl quotaResponseBuilder = new QuotaResponseBuilderImpl(); + + @Before + public void setup() throws IllegalAccessException, NoSuchFieldException { + // Dummy transaction stack setup + TransactionLegacy.open("QuotaResponseBuilderImplTest"); + + Field tariffDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_quotaTariffDao"); + tariffDaoField.setAccessible(true); + tariffDaoField.set(quotaResponseBuilder, quotaTariffDao); + + Field balanceDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_quotaBalanceDao"); + balanceDaoField.setAccessible(true); + balanceDaoField.set(quotaResponseBuilder, quotaBalanceDao); + + Field quotaCreditsDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_quotaCreditsDao"); + quotaCreditsDaoField.setAccessible(true); + quotaCreditsDaoField.set(quotaResponseBuilder, quotaCreditsDao); + + Field quotaEmailTemplateDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_quotaEmailTemplateDao"); + quotaEmailTemplateDaoField.setAccessible(true); + quotaEmailTemplateDaoField.set(quotaResponseBuilder, quotaEmailTemplateDao); + + Field userDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_userDao"); + userDaoField.setAccessible(true); + userDaoField.set(quotaResponseBuilder, userDao); + + Field quotaServiceField = QuotaResponseBuilderImpl.class.getDeclaredField("_quotaService"); + quotaServiceField.setAccessible(true); + quotaServiceField.set(quotaResponseBuilder, quotaService); + + Field accountDaoField = QuotaResponseBuilderImpl.class.getDeclaredField("_accountDao"); + accountDaoField.setAccessible(true); + accountDaoField.set(quotaResponseBuilder, accountDao); + + Field regionMgrField = QuotaResponseBuilderImpl.class.getDeclaredField("_regionMgr"); + regionMgrField.setAccessible(true); + regionMgrField.set(quotaResponseBuilder, regionMgr); + } + + private QuotaTariffVO makeTariffTestData() { + QuotaTariffVO tariffVO = new QuotaTariffVO(); + tariffVO.setUsageType(QuotaTypes.IP_ADDRESS); + tariffVO.setUsageName("ip address"); + tariffVO.setUsageUnit("IP-Month"); + tariffVO.setCurrencyValue(new BigDecimal(100.19)); + tariffVO.setEffectiveOn(new Date()); + tariffVO.setUsageDiscriminator(""); + return tariffVO; + } + + @Test + public void testQuotaResponse() { + QuotaTariffVO tariffVO = makeTariffTestData(); + QuotaTariffResponse response = quotaResponseBuilder.createQuotaTariffResponse(tariffVO); + assertTrue(tariffVO.getUsageType() == response.getUsageType()); + assertTrue(tariffVO.getCurrencyValue().equals(response.getTariffValue())); + } + + @Test + public void testAddQuotaCredits() { + final long accountId = 2L; + final long domainId = 2L; + final double amount = 11.0; + final long updatedBy = 2L; + final Date now = new Date(); + + QuotaCreditsVO credit = new QuotaCreditsVO(); + credit.setCredit(new BigDecimal(amount)); + + Mockito.when(quotaCreditsDao.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit); + Mockito.when(quotaBalanceDao.lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111)); + + AccountVO account = new AccountVO(); + account.setState(Account.State.locked); + Mockito.when(accountDao.findById(Mockito.anyLong())).thenReturn(account); + + QuotaCreditsResponse resp = quotaResponseBuilder.addQuotaCredits(accountId, domainId, amount, updatedBy, now); + assertTrue(resp.getCredits().compareTo(credit.getCredit()) == 0); + } + + @Test + public void testListQuotaEmailTemplates() { + QuotaEmailTemplateListCmd cmd = new QuotaEmailTemplateListCmd(); + cmd.setTemplateName("some name"); + List templates = new ArrayList<>(); + QuotaEmailTemplatesVO template = new QuotaEmailTemplatesVO(); + template.setTemplateName("template"); + templates.add(template); + Mockito.when(quotaEmailTemplateDao.listAllQuotaEmailTemplates(Mockito.anyString())).thenReturn(templates); + + assertTrue(quotaResponseBuilder.listQuotaEmailTemplates(cmd).size() == 1); + } + + @Test + public void testUpdateQuotaEmailTemplate() { + QuotaEmailTemplateUpdateCmd cmd = new QuotaEmailTemplateUpdateCmd(); + cmd.setTemplateBody("some body"); + cmd.setTemplateName("some name"); + cmd.setTemplateSubject("some subject"); + + List templates = new ArrayList<>(); + + Mockito.when(quotaEmailTemplateDao.listAllQuotaEmailTemplates(Mockito.anyString())).thenReturn(templates); + Mockito.when(quotaEmailTemplateDao.updateQuotaEmailTemplate(Mockito.any(QuotaEmailTemplatesVO.class))).thenReturn(true); + + // invalid template test + assertFalse(quotaResponseBuilder.updateQuotaEmailTemplate(cmd)); + + // valid template test + QuotaEmailTemplatesVO template = new QuotaEmailTemplatesVO(); + template.setTemplateName("template"); + templates.add(template); + assertTrue(quotaResponseBuilder.updateQuotaEmailTemplate(cmd)); + } + + @Test + public void testCreateQuotaLastBalanceResponse() { + List quotaBalance = new ArrayList<>(); + // null balance test + try { + quotaResponseBuilder.createQuotaLastBalanceResponse(null, new Date()); + } catch (InvalidParameterValueException e) { + assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date.")); + } + + // empty balance test + try { + quotaResponseBuilder.createQuotaLastBalanceResponse(quotaBalance, new Date()); + } catch (InvalidParameterValueException e) { + assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date.")); + } + + // valid balance test + QuotaBalanceVO entry = new QuotaBalanceVO(); + entry.setAccountId(2L); + entry.setCreditBalance(new BigDecimal(100)); + quotaBalance.add(entry); + quotaBalance.add(entry); + Mockito.when(quotaService.computeAdjustedTime(Mockito.any(Date.class))).thenReturn(new Date()); + QuotaBalanceResponse resp = quotaResponseBuilder.createQuotaLastBalanceResponse(quotaBalance, null); + assertTrue(resp.getStartQuota().compareTo(new BigDecimal(200)) == 0); + } + + @Test + public void testStartOfNextDay() { + DateTime now = new DateTime(); + DateTime nextDay = new DateTime(quotaResponseBuilder.startOfNextDay(now.toDate())); + DateTime nextDay2 = new DateTime(quotaResponseBuilder.startOfNextDay()); + assertTrue(now.toLocalDate().equals(nextDay.minusDays(1).toLocalDate())); + assertTrue(now.toLocalDate().equals(nextDay2.minusDays(1).toLocalDate())); + } + + +} diff --git a/plugins/database/quota/test/org/apache/cloudstack/quota/QuotaServiceImplTest.java b/plugins/database/quota/test/org/apache/cloudstack/quota/QuotaServiceImplTest.java new file mode 100644 index 00000000000..310e9ced72f --- /dev/null +++ b/plugins/database/quota/test/org/apache/cloudstack/quota/QuotaServiceImplTest.java @@ -0,0 +1,183 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota; + +import com.cloud.configuration.Config; +import com.cloud.domain.dao.DomainDao; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.db.TransactionLegacy; +import junit.framework.TestCase; +import org.apache.cloudstack.api.response.QuotaResponseBuilder; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +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.vo.QuotaAccountVO; +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.naming.ConfigurationException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaServiceImplTest extends TestCase { + + @Mock + AccountDao accountDao; + @Mock + QuotaAccountDao quotaAcc; + @Mock + QuotaUsageDao quotaUsageDao; + @Mock + DomainDao domainDao; + @Mock + ConfigurationDao configDao; + @Mock + QuotaBalanceDao quotaBalanceDao; + @Mock + QuotaResponseBuilder respBldr; + + QuotaServiceImpl quotaService = new QuotaServiceImpl(); + + @Before + public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { + // Dummy transaction stack setup + TransactionLegacy.open("QuotaServiceImplTest"); + + Field accountDaoField = QuotaServiceImpl.class.getDeclaredField("_accountDao"); + accountDaoField.setAccessible(true); + accountDaoField.set(quotaService, accountDao); + + Field quotaAccountDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaAcc"); + quotaAccountDaoField.setAccessible(true); + quotaAccountDaoField.set(quotaService, quotaAcc); + + Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao"); + quotaUsageDaoField.setAccessible(true); + quotaUsageDaoField.set(quotaService, quotaUsageDao); + + Field domainDaoField = QuotaServiceImpl.class.getDeclaredField("_domainDao"); + domainDaoField.setAccessible(true); + domainDaoField.set(quotaService, domainDao); + + Field configDaoField = QuotaServiceImpl.class.getDeclaredField("_configDao"); + configDaoField.setAccessible(true); + configDaoField.set(quotaService, configDao); + + Field balanceDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaBalanceDao"); + balanceDaoField.setAccessible(true); + balanceDaoField.set(quotaService, quotaBalanceDao); + + Field QuotaResponseBuilderField = QuotaServiceImpl.class.getDeclaredField("_respBldr"); + QuotaResponseBuilderField.setAccessible(true); + QuotaResponseBuilderField.set(quotaService, respBldr); + + Mockito.when(configDao.getValue(Mockito.eq(Config.UsageAggregationTimezone.toString()))).thenReturn("IST"); + Mockito.when(configDao.getValue(Mockito.eq(Config.UsageStatsJobAggregationRange.toString()))).thenReturn("1"); + quotaService.configure("randomName", null); + } + + @Test + public void testComputeAdjustedTime() { + DateTime now = new DateTime(DateTimeZone.UTC); + DateTime result = new DateTime(quotaService.computeAdjustedTime(now.toDate())); + // FIXME: fix this test + } + + @Test + public void testFindQuotaBalanceVO() { + final long accountId = 2L; + final String accountName = "admin123"; + final long domainId = 1L; + final Date startDate = new DateTime().minusDays(2).toDate(); + final Date endDate = new Date(); + + List records = new ArrayList<>(); + QuotaBalanceVO qb = new QuotaBalanceVO(); + qb.setCreditBalance(new BigDecimal(100)); + qb.setAccountId(accountId); + records.add(qb); + + Mockito.when(respBldr.startOfNextDay()).thenReturn(endDate); + Mockito.when(respBldr.startOfNextDay(Mockito.any(Date.class))).thenReturn(startDate); + Mockito.when(quotaBalanceDao.findQuotaBalance(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(records); + Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records); + + // with enddate + assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb)); + // without enddate + assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb)); + } + + @Test + public void testGetQuotaUsage() { + final long accountId = 2L; + final String accountName = "admin123"; + final long domainId = 1L; + final Date startDate = new DateTime().minusDays(2).toDate(); + final Date endDate = new Date(); + + Mockito.when(respBldr.startOfNextDay()).thenReturn(endDate); + quotaService.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)); + } + + @Test + public void testSetLockAccount() { + // existing account + QuotaAccountVO quotaAccountVO = new QuotaAccountVO(); + Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO); + quotaService.setLockAccount(2L, true); + Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); + Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class)); + + // new account + Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null); + quotaService.setLockAccount(2L, true); + Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); + } + + @Test + public void testSetMinBalance() { + final long accountId = 2L; + final double balance = 10.3F; + + // existing account setting + QuotaAccountVO quotaAccountVO = new QuotaAccountVO(); + Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO); + quotaService.setMinBalance(accountId, balance); + Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); + Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class)); + + // no account with limit set + Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null); + quotaService.setMinBalance(accountId, balance); + Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); + } +} diff --git a/plugins/hypervisors/ovm3/pom.xml b/plugins/hypervisors/ovm3/pom.xml index f594672db4e..8afd3712c30 100644 --- a/plugins/hypervisors/ovm3/pom.xml +++ b/plugins/hypervisors/ovm3/pom.xml @@ -45,7 +45,7 @@ org.apache.commons commons-lang3 - ${cs.lang3.version} + ${cs.commons-lang3.version} log4j diff --git a/plugins/network-elements/nuage-vsp/pom.xml b/plugins/network-elements/nuage-vsp/pom.xml index c1268e4b42b..6c11acf19ed 100644 --- a/plugins/network-elements/nuage-vsp/pom.xml +++ b/plugins/network-elements/nuage-vsp/pom.xml @@ -32,7 +32,7 @@ org.apache.commons commons-lang3 - ${cs.lang3.version} + ${cs.commons-lang3.version} diff --git a/plugins/pom.xml b/plugins/pom.xml index 5660380a070..5305d943fee 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -101,6 +101,7 @@ network-elements/internal-loadbalancer network-elements/vxlan network-elements/globodns + database/quota diff --git a/pom.xml b/pom.xml index de7bf7d3acd..9ff47e26cef 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ 1.10.34 2.6.3 2.6 - 3.4 + 3.4 2.4 1.4.0 0.9.9 @@ -116,6 +116,7 @@ 2.10.1 2.6.1 1.4.01 + 2.8.1 @@ -714,6 +715,19 @@ + + + org.apache.maven.plugins + maven-checkstyle-plugin + [2.11,) + + check + + + + + + diff --git a/server/src/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java index 3c81ff6b46c..099c0c91e66 100644 --- a/server/src/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java @@ -294,6 +294,14 @@ public class ParamProcessWorker implements DispatchWorker { field.set(cmdObj, Float.valueOf(paramObj.toString())); } break; + case DOUBLE: + // Assuming that the parameters have been checked for required before now, + // we ignore blank or null values and defer to the command to set a default + // value for optional parameters ... + if (paramObj != null && isNotBlank(paramObj.toString())) { + field.set(cmdObj, Double.valueOf(paramObj.toString())); + } + break; case INTEGER: // Assuming that the parameters have been checked for required before now, // we ignore blank or null values and defer to the command to set a default diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index a0e661bac9b..d3eb40c7db6 100644 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -2184,8 +2184,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M if (s_logger.isInfoEnabled()) { s_logger.info("User " + username + " in domain " + domainName + " is disabled/locked (or account is disabled/locked)"); } - throw new CloudAuthenticationException("User " + username + " in domain " + domainName + " is disabled/locked (or account is disabled/locked)"); - // return null; + throw new CloudAuthenticationException("User " + username + " (or their account) in domain " + domainName + " is disabled/locked. Please contact the administrator."); } // Whenever the user is able to log in successfully, reset the login attempts to zero if (!isInternalAccount(userAccount.getId())) diff --git a/server/test/com/cloud/api/dispatch/ParamProcessWorkerTest.java b/server/test/com/cloud/api/dispatch/ParamProcessWorkerTest.java index 12051a6f743..7ac982db623 100644 --- a/server/test/com/cloud/api/dispatch/ParamProcessWorkerTest.java +++ b/server/test/com/cloud/api/dispatch/ParamProcessWorkerTest.java @@ -62,6 +62,9 @@ public class ParamProcessWorkerTest { @Parameter(name = "boolparam1", type = CommandType.BOOLEAN) boolean boolparam1; + @Parameter(name = "doubleparam1", type = CommandType.DOUBLE) + double doubleparam1; + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { @@ -98,10 +101,12 @@ public class ParamProcessWorkerTest { params.put("strparam1", "foo"); params.put("intparam1", "100"); params.put("boolparam1", "true"); + params.put("doubleparam1", "11.89"); final TestCmd cmd = new TestCmd(); paramProcessWorker.processParameters(cmd, params); Assert.assertEquals("foo", cmd.strparam1); Assert.assertEquals(100, cmd.intparam1); + Assert.assertTrue(Double.compare(cmd.doubleparam1, 11.89) == 0); } } diff --git a/setup/db/db/schema-421to430-cleanup.sql b/setup/db/db/schema-421to430-cleanup.sql index ce5a220f70d..4a58e65477d 100644 --- a/setup/db/db/schema-421to430-cleanup.sql +++ b/setup/db/db/schema-421to430-cleanup.sql @@ -19,4 +19,3 @@ -- Schema cleanup from 4.2.0 to 4.3.0; --; - diff --git a/setup/db/db/schema-461to470.sql b/setup/db/db/schema-461to470.sql index 15e9b38ed4e..d738545c2ca 100644 --- a/setup/db/db/schema-461to470.sql +++ b/setup/db/db/schema-461to470.sql @@ -30,3 +30,113 @@ CREATE TABLE IF NOT EXISTS `cloud`.`domain_vlan_map` ( INDEX `i_account_vlan_map__vlan_id`(`vlan_db_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- Quota + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_account` ( + `account_id` int(11) NOT NULL, + `quota_balance` decimal(15,2) NULL, + `quota_balance_date` datetime NULL, + `quota_enforce` int(1) DEFAULT NULL, + `quota_min_balance` decimal(15,2) DEFAULT NULL, + `quota_alert_date` datetime DEFAULT NULL, + `quota_alert_type` int(11) DEFAULT NULL, + `last_statement_date` datetime DEFAULT NULL, + PRIMARY KEY (`account_id`), + CONSTRAINT `account_id` FOREIGN KEY (`account_id`) REFERENCES `cloud_usage`.`account` (`quota_enforce`) + ON DELETE NO ACTION + ON UPDATE NO ACTION +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `usage_type` int(2) unsigned DEFAULT NULL, + `usage_name` varchar(255) NOT NULL COMMENT 'usage type', + `usage_unit` varchar(255) NOT NULL COMMENT 'usage type', + `usage_discriminator` varchar(255) NOT NULL COMMENT 'usage type', + `currency_value` decimal(15,2) NOT NULL COMMENT 'usage type', + `effective_on` datetime NOT NULL COMMENT 'date time on which this quota values will become effective', + `updated_on` datetime NOT NULL COMMENT 'date this entry was updated on', + `updated_by` bigint unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + +LOCK TABLES `cloud_usage`.`quota_tariff` WRITE; +INSERT IGNORE INTO `cloud_usage`.`quota_tariff` (`usage_type`, `usage_name`, `usage_unit`, `usage_discriminator`, `currency_value`, `effective_on`, `updated_on`, `updated_by`) VALUES + (1,'RUNNING_VM','Compute-Month','',0.00,'2010-05-04', '2010-05-04',1), + (2,'ALLOCATED_VM','Compute-Month','',0.00,'2010-05-04', '2010-05-04',1), + (3,'IP_ADDRESS','IP-Month','',0.00,'2010-05-04', '2010-05-04',1), + (4,'NETWORK_BYTES_SENT','GB','',0.00,'2010-05-04', '2010-05-04',1), + (5,'NETWORK_BYTES_RECEIVED','GB','',0.00,'2010-05-04', '2010-05-04',1), + (6,'VOLUME','GB-Month','',0.00,'2010-05-04', '2010-05-04',1), + (7,'TEMPLATE','GB-Month','',0.00,'2010-05-04', '2010-05-04',1), + (8,'ISO','GB-Month','',0.00,'2010-05-04', '2010-05-04',1), + (9,'SNAPSHOT','GB-Month','',0.00,'2010-05-04', '2010-05-04',1), + (10,'SECURITY_GROUP','Policy-Month','',0.00,'2010-05-04', '2010-05-04',1), + (11,'LOAD_BALANCER_POLICY','Policy-Month','',0.00,'2010-05-04', '2010-05-04',1), + (12,'PORT_FORWARDING_RULE','Policy-Month','',0.00,'2010-05-04', '2010-05-04',1), + (13,'NETWORK_OFFERING','Policy-Month','',0.00,'2010-05-04', '2010-05-04',1), + (14,'VPN_USERS','Policy-Month','',0.00,'2010-05-04', '2010-05-04',1), + (15,'CPU_SPEED','Compute-Month','100MHz',0.00,'2010-05-04', '2010-05-04',1), + (16,'vCPU','Compute-Month','1VCPU',0.00,'2010-05-04', '2010-05-04',1), + (17,'MEMORY','Compute-Month','1MB',0.00,'2010-05-04', '2010-05-04',1), + (21,'VM_DISK_IO_READ','GB','1',0.00,'2010-05-04', '2010-05-04',1), + (22,'VM_DISK_IO_WRITE','GB','1',0.00,'2010-05-04', '2010-05-04',1), + (23,'VM_DISK_BYTES_READ','GB','1',0.00,'2010-05-04', '2010-05-04',1), + (24,'VM_DISK_BYTES_WRITE','GB','1',0.00,'2010-05-04', '2010-05-04',1), + (25,'VM_SNAPSHOT','GB-Month','',0.00,'2010-05-04', '2010-05-04',1); +UNLOCK TABLES; + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_credits` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `account_id` bigint unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `credit` decimal(15,4) COMMENT 'amount credited', + `updated_on` datetime NOT NULL COMMENT 'date created', + `updated_by` bigint unsigned NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_usage` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `usage_item_id` bigint(20) unsigned NOT NULL, + `zone_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `usage_type` varchar(64) DEFAULT NULL, + `quota_used` decimal(15,8) unsigned NOT NULL, + `start_date` datetime NOT NULL COMMENT 'start time for this usage item', + `end_date` datetime NOT NULL COMMENT 'end time for this usage item', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; + + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_balance` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `account_id` bigint unsigned NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `credit_balance` decimal(15,8) COMMENT 'amount of credits remaining', + `credits_id` bigint unsigned COMMENT 'if not null then this entry corresponds to credit change quota_credits', + `updated_on` datetime NOT NULL COMMENT 'date updated on', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_templates` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `template_name` varchar(64) NOT NULL UNIQUE, + `template_subject` longtext, + `template_body` longtext, + `locale` varchar(25) DEFAULT 'en_US', + `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `cloud_usage`.`quota_email_templates` WRITE; +INSERT IGNORE INTO `cloud_usage`.`quota_email_templates` (`template_name`, `template_subject`, `template_body`) VALUES + ('QUOTA_LOW', 'Quota Usage Threshold crossed by your account ${accountName}', 'Your account ${accountName} in the domain ${domainName} has reached quota usage threshold, your current quota balance is ${quotaBalance}.'), + ('QUOTA_EMPTY', 'Quota Exhausted, account ${accountName} has no quota left.', 'Your account ${accountName} in the domain ${domainName} has exhausted allocated quota, please contact the administrator.'), + ('QUOTA_UNLOCK_ACCOUNT', 'Quota credits added, account ${accountName} is unlocked now, if it was locked', 'Your account ${accountName} in the domain ${domainName} has enough quota credits now with the current balance of ${quotaBalance}.'), + ('QUOTA_STATEMENT', 'Quota Statement for your account ${accountName}', 'Monthly quota statement of your account ${accountName} in the domain ${domainName}:
Balance = ${quotaBalance}
Total Usage = ${quotaUsage}.'); +UNLOCK TABLES; diff --git a/test/integration/smoke/test_quota.py b/test/integration/smoke/test_quota.py new file mode 100644 index 00000000000..d4e4323e9b3 --- /dev/null +++ b/test/integration/smoke/test_quota.py @@ -0,0 +1,204 @@ +# 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. +""" Test cases for checking quota API +""" + +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.lib.utils import * +from marvin.lib.base import * +from marvin.lib.common import * +from marvin.lib.utils import (random_gen) +from nose.plugins.attrib import attr + +#Import System modules +import time + +#ENABLE THE QUOTA PLUGIN AND RESTART THE MANAGEMENT SERVER TO RUN QUOTA TESTS + +class TestQuota(cloudstackTestCase): + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() + self.dbclient = self.testClient.getDbConnection() + self.services = self.testClient.getParsedTestDataConfig() + self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) + self.pod = get_pod(self.apiclient, self.zone.id) + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + #Check quotaTariffList API returning 22 items + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_01_quota(self): + cmd = quotaTariffList.quotaTariffListCmd() + response = self.apiclient.quotaTariffList(cmd) + + self.debug("Number of quota usage types: %s" % len(response)) + self.assertEqual( + len(response), 22 + ) + for quota in response: + self.debug("Usage Name: %s" % quota.usageName) + self.assertEqual( + hasattr(quota, 'usageName'), + True, + "Check whether usgaeName field is there" + ) + + return + + #Check quota tariff on a particualr day + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_02_quota(self): + cmd = quotaTariffList.quotaTariffListCmd() + cmd.startdate='2015-07-06' + response = self.apiclient.quotaTariffList(cmd) + + self.debug("Number of quota usage types: %s" % len(response)) + self.assertEqual( + len(response), 22 + ) + + return + + #check quota tariff of a particular item + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_03_quota(self): + cmd = quotaTariffList.quotaTariffListCmd() + cmd.startdate='2015-07-06' + cmd.usagetype='10' + response = self.apiclient.quotaTariffList(cmd) + + self.debug("Number of quota usage types: %s" % len(response)) + self.assertEqual( + len(response), 1 + ) + return + + + #check quota tariff + #Change it + #Check on affective date the new tariff should be applicable + #check the old tariff it should be same + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_04_quota(self): + cmd = quotaTariffList.quotaTariffListCmd() + cmd.startdate='2015-07-06' + cmd.usagetype='10' + response = self.apiclient.quotaTariffList(cmd) + + self.debug("Number of quota usage types: %s" % len(response)) + self.assertEqual( + len(response), 1 + ) + quota = response[0] + self.debug("Tariff Value for 10: %s" % quota.tariffValue) + + cmd = quotaTariffUpdate.quotaTariffUpdateCmd() + tomorrow = datetime.date.today() + datetime.timedelta(days=1) + cmd.startdate=tomorrow + cmd.usagetype='10' + cmd.value='2.9' + response = self.apiclient.quotaTariffUpdate(cmd) + + cmd = quotaTariffList.quotaTariffListCmd() + cmd.startdate=tomorrow + cmd.usagetype='10' + response = self.apiclient.quotaTariffList(cmd) + self.assertEqual( + len(response), 1 + ) + quota = response[0] + self.debug("Tariff Value for 10: %s" % quota.tariffValue) + + self.assertEqual( quota.tariffValue, 2.9) + + + cmd = quotaTariffList.quotaTariffListCmd() + cmd.startdate='2015-07-07' + cmd.usagetype='10' + response = self.apiclient.quotaTariffList(cmd) + self.assertEqual( + len(response), 1 + ) + quota = response[0] + self.debug("Tariff Value for 10: %s" % quota.tariffValue) + + self.assertEqual( quota.tariffValue, 0) + + return + + + #Make credit deposit + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_05_quota(self): + cmd = quotaCredits.quotaCreditsCmd() + cmd.domainid = '1' + cmd.account = 'admin' + cmd.value = '10' + cmd.quota_enforce = '1' + cmd.min_balance = '9' + response = self.apiclient.quotaCredits(cmd) + + self.debug("Credit response update on: %s" % response.updated_on) + + return + + + #Make credit deposit and check today balance + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_06_quota(self): + cmd = quotaBalance.quotaBalanceCmd() + today = datetime.date.today() + cmd.domainid = '1' + cmd.account = 'admin' + cmd.startdate = today + response = self.apiclient.quotaBalance(cmd) + + self.debug("Quota Balance on: %s" % response.startdate) + self.debug("is: %s" % response.startquota) + + self.assertGreater( response.startquota, 9) + return + + #make credit deposit and check start and end date balances + @attr(tags=["smoke", "advanced"], required_hardware="false") + def test_07_quota(self): + cmd = quotaBalance.quotaBalanceCmd() + today = datetime.date.today() + cmd.domainid = '1' + cmd.account = 'admin' + cmd.startdate = today - datetime.timedelta(days=2) + cmd.enddate = today + response = self.apiclient.quotaBalance(cmd) + + self.debug("Quota Balance on: %s" % response.startdate) + self.debug("is: %s" % response.startquota) + + self.assertGreater( response.endquota, 9) + return diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 311e55b0159..6974fd74231 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -118,6 +118,8 @@ known_categories = { 'listIdps': 'Authentication', 'authorizeSamlSso': 'Authentication', 'listSamlAuthorization': 'Authentication', + 'quota': 'Quota', + 'emailTemplate': 'Quota', 'Capacity': 'System Capacity', 'NetworkDevice': 'Network Device', 'ExternalLoadBalancer': 'Ext Load Balancer', diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index d47da27d742..e82ae19ebbc 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -1024,6 +1024,50 @@ dictionary = { 'label.purpose': '', 'label.Pxe.server.type': '', 'label.quickview': '', +'label.usage.type': '', +'label.usage.unit': '', +'label.quota.value': '', +'label.quota.description': '', +'label.quota.configuration': '', +'label.quota.configure': '', +'label.quota.remove': '', +'label.quota.totalusage': '', +'label.quota.balance': '', +'label.quota.summary': '', +'label.quota.fullsummary': '', +'label.quota.minbalance': '', +'label.quota.enforcequota': '', +'label.quota.tariff': '', +'label.quota.state': '', +'label.quota.startdate': '', +'label.quota.enddate': '', +'label.quota.total': '', +'label.quota.type.name': '', +'label.quota.type.unit': '', +'label.quota.usage': '', +'label.quota.startquota': '', +'label.quota.endquota': '', +'label.quota.statement.quota': '', +'label.quota.add.credits': '', +'label.quota.date': '', +'label.quota.dates': '', +'label.quota.credit': '', +'label.quota.credits': '', +'label.quota.value': '', +'label.quota.statement.bydates': '', +'label.quota.email.template': '', +'label.quota.statement': '', +'label.quota.statement.balance': '', +'label.quota.statement.tariff': '', +'label.quota.statement.balance': '', +'label.quota.statement.tariff': '', +'label.quota.tariff.edit': '', +'label.quota.tariff.effectivedate': '', +'label.quota.email.subject': '', +'label.quota.tariff.value': '', +'label.quota.email.subject': '', +'label.quota.email.body': '', +'label.quota.email.lastupdated': '', 'label.rbd': '', 'label.rbd.monitor': '', 'label.rbd.pool': '', diff --git a/ui/plugins/plugins.js b/ui/plugins/plugins.js index 386ec062bb0..b629e3095b6 100644 --- a/ui/plugins/plugins.js +++ b/ui/plugins/plugins.js @@ -16,6 +16,7 @@ // under the License. (function($, cloudStack) { cloudStack.plugins = [ - // 'testPlugin' + 'quota', + //'testPlugin' ]; }(jQuery, cloudStack)); diff --git a/ui/plugins/quota/config.js b/ui/plugins/quota/config.js new file mode 100644 index 00000000000..186d5077840 --- /dev/null +++ b/ui/plugins/quota/config.js @@ -0,0 +1,25 @@ +// 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. +(function (cloudStack) { + cloudStack.plugins.quota.config = { + title: 'Quota', + desc: 'CloudStack Quota', + externalLink: 'http://www.cloudstack.org/', + authorName: 'Apache CloudStack', + authorEmail: 'dev@cloudstack.apache.org' + }; +}(cloudStack)); diff --git a/ui/plugins/quota/icon.png b/ui/plugins/quota/icon.png new file mode 100644 index 00000000000..a7ef5521d0f Binary files /dev/null and b/ui/plugins/quota/icon.png differ diff --git a/ui/plugins/quota/quota.css b/ui/plugins/quota/quota.css new file mode 100644 index 00000000000..e9c0d8ff358 --- /dev/null +++ b/ui/plugins/quota/quota.css @@ -0,0 +1,68 @@ +/* +* 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. +*/ + +.quota-tariff-edit { + float: right; +} + +.quota-bold { + font-size: 12px; + font-weight: bold; + color: #6D6D6D; +} + +.quota-text { + font-size: 11px; + color: #282828; +} + +.quota-element { + margin: 5px; + margin-left: 13px; +} + +.quota-input { + background: #F6F6F6; + border: 1px solid #AFAFAF; + border-radius: 4px; +} + +.quota-button { + border:1px solid #0077c7; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; + font-size:11px; + padding: 8px; + margin-top: 12px; + text-shadow: -1px -1px 0 rgba(0,0,0,0.3);font-weight:bold; color: #FFFFFF; + background-color: #0099FF; background-image: -webkit-gradient(linear, left top, left bottom, from(#0099FF), to(#004FF7)); + background-image: -webkit-linear-gradient(top, #0099FF, #004FF7); + background-image: -moz-linear-gradient(top, #0099FF, #004FF7); + background-image: -ms-linear-gradient(top, #0099FF, #004FF7); + background-image: -o-linear-gradient(top, #0099FF, #004FF7); + background-image: linear-gradient(to bottom, #0099FF, #004FF7);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=#0099FF, endColorstr=#004FF7); +} + +.quota-button:hover { + border:1px solid #005c99; + background-color: #0039B3; background-image: -webkit-gradient(linear, left top, left bottom, from(#0039B3), to(#004FF7)); + background-image: -webkit-linear-gradient(top, #0039B3, #004FF7); + background-image: -moz-linear-gradient(top, #0039B3, #004FF7); + background-image: -ms-linear-gradient(top, #0039B3, #004FF7); + background-image: -o-linear-gradient(top, #0039B3, #004FF7); + background-image: linear-gradient(to bottom, #0039B3, #004FF7);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=#0039B3, endColorstr=#004FF7); +} diff --git a/ui/plugins/quota/quota.js b/ui/plugins/quota/quota.js new file mode 100644 index 00000000000..6051b2c8fc8 --- /dev/null +++ b/ui/plugins/quota/quota.js @@ -0,0 +1,971 @@ +// 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. + +(function (cloudStack) { + var now = new Date(); + var newstartdate; + var newenddate; + var currency; + cloudStack.plugins.quota = function(plugin) { + plugin.ui.addSection({ + id: 'quota', + title: 'Quota', + preFilter: function(args) { + var retval = $.ajax({ + url: createURL("listConfigurations&name=quota.enable.service"), + async: false + }); + var json = JSON.parse(retval.responseText); + return json.listconfigurationsresponse.configuration[0].value == 'true'; + }, + showOnNavigation: true, + sectionSelect: { + label: 'label.select-view', + preFilter: function(args) { + if (isAdmin()) + return ['summary', 'fullSummary', 'tariffEdit', 'emailTemplates']; + else + return ['summary', 'tariffView']; + } + }, + sections: { + + summary: { + type: 'select', + title: 'label.quota.summary', + listView: { + label: 'label.quota.summary', + disableInfiniteScrolling: true, + fields: { + account: { + label: 'label.account', + truncate: true + }, + domain: { + label: 'label.domain' + }, + balance: { + label: 'label.quota.balance', + limitcolor: true, + limits: { + upperlimit: 'upperlimit', + lowerlimit: 'lowerlimit' + }, + converter: function(args){ + return currency + args.toString(); + } + }, + quota: { + label: 'label.quota.totalusage', + editable: true, + truncate: true + }, + state: { + label: 'label.quota.state', + indicator: { + 'enabled': 'on', + 'disabled': 'off', + 'locked': 'off', + } + }, + }, + dataProvider: function(args) { + var data = { + page: args.page, + pagesize: pageSize + }; + + // TODO: add logic in mgmt server to filter by account + if (args.filterBy.search.value) { + data.search = args.filterBy.search.value; + } + + $.ajax({ + url: createURL('quotaSummary'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var items = json.quotasummaryresponse.summary; + if(items){ + var array=[]; + for (var i = 0; i < items.length; i++) { + if (typeof data.search != 'undefine' && items[i].account.search(data.search) < 0 && items[i].domain.search(data.search) < 0) { + continue; + } + currency = items[i].currency; + items[i].quota = currency + ' ' + items[i].quota; + items[i].lowerlimit = -1; + items[i].upperlimit = 0; + array.push(items[i]); + } + args.response.success({ + data: array + }); + } + else { + args.response.success({ + data: 0 + }); + } + }, + error: function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + }, + detailView: { + viewAll: [{ + path: 'quota.quotastatement', + label: 'label.quota.statement.quota' + },{ + path: 'quota.balancestatement', + label: 'label.quota.statement.balance' + }], + actions: { + add: { + label: 'label.quota.add.credits', + preFilter: function(args) { return isAdmin(); }, + messages: { + confirm: function(args) { + return 'label.quota.add.credits'; + }, + notification: function(args) { + return 'label.quota.add.credits'; + } + }, + + createForm: { + title: 'label.quota.credits', + desc: '', + fields: { + value: { + label: 'label.quota.credits', + validation: { + required: true + } + }, + min_balance: { + label: 'label.quota.minbalance', + validation: { + required: true + } + }, + quota_enforce: { + label: 'label.quota.enforcequota', + isBoolean: true, + isChecked: false + }, + } + + }, + + action: function(args) { + var enforce=args.data.quota_enforce == 'on' ? true: false; + $.ajax({ + url: createURL('quotaCredits'), + data: { + domainid: args.context.summary[0].domainid, + account: args.context.summary[0].account, + value: args.data.value, + min_balance: args.data.min_balance, + quota_enforce: enforce + }, + async: false, + success: function(json) { + args.response.success({ + data: json.quotacreditsresponse.totalquota + }); + } + }); + $(window).trigger('cloudStack.fullRefresh'); + } + }, + }, + tabs: { + details: { + title: 'label.details', + fields: [{ + id: { + label: 'label.quota.statement.balance' + } + }, { + startdate: { + label: 'label.quota.date', + }, + startquota: { + label: 'label.quota.value', + } + }], + dataProvider: function(args) { + $.ajax({ + url: createURL('quotaBalance'), + dataType: 'json', + async: true, + data: { + domainid: args.context.summary[0].domainid, + account: args.context.summary[0].account + }, + success: function(json) { + var item = json.quotabalanceresponse.balance; + item.startdate = now.toJSON().slice(0,10); + item.startquota = item.currency + ' ' + item.startquota; + args.response.success({ + data: item + }); + } , + error:function(data) { + args.response.error(parseXMLHttpResponse(data)); + } + }); + } + }, + + } + } + } + }, + + fullSummary: { + type: 'select', + title: 'label.quota.fullsummary', + listView: { + label: 'label.quota.fullsummary', + disableInfiniteScrolling: true, + fields: { + account: { + label: 'label.account', + truncate: true + }, + domain: { + label: 'label.domain' + }, + balance: { + label: 'label.quota.balance', + limitcolor: true, + limits: { + upperlimit: 'upperlimit', + lowerlimit: 'lowerlimit' + }, + converter: function(args){ + return currency + args.toString(); + } + }, + quota: { + label: 'label.quota.totalusage', + editable: true, + truncate: true + }, + state: { + label: 'label.quota.state', + indicator: { + 'enabled': 'on', + 'disabled': 'off', + 'locked': 'off', + } + }, + }, + dataProvider: function(args) { + var data = { + page: args.page, + pagesize: pageSize + }; + + // TODO: add logic in mgmt server to filter by account + if (args.filterBy.search.value) { + data.search = args.filterBy.search.value; + } + + $.ajax({ + url: createURL('quotaSummary&listall=true'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var items = json.quotasummaryresponse.summary; + if(items){ + var array=[]; + for (var i = 0; i < items.length; i++) { + if (typeof data.search != 'undefine' && items[i].account.search(data.search) < 0 && items[i].domain.search(data.search) < 0) { + continue; + } + currency = items[i].currency; + items[i].quota = currency + ' ' + items[i].quota; + items[i].lowerlimit = -1; + items[i].upperlimit = 0; + array.push(items[i]); + } + args.response.success({ + data: array + }); + } + else { + args.response.success({ + data: 0 + }); + } + }, + error: function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + }, + detailView: { + viewAll: [{ + path: 'quota.quotastatement', + label: 'label.quota.statement.quota' + },{ + path: 'quota.balancestatement', + label: 'label.quota.statement.balance' + }], + actions: { + add: { + label: 'label.quota.add.credits', + preFilter: function(args) { return isAdmin(); }, + messages: { + confirm: function(args) { + return 'label.quota.add.credits'; + }, + notification: function(args) { + return 'label.quota.add.credits'; + } + }, + + createForm: { + title: 'label.quota.credits', + desc: '', + fields: { + value: { + label: 'label.quota.credits', + validation: { + required: true + } + }, + min_balance: { + label: 'label.quota.minbalance', + validation: { + required: true + } + }, + quota_enforce: { + label: 'label.quota.enforcequota', + isBoolean: true, + isChecked: false + }, + } + + }, + + action: function(args) { + var enforce=args.data.quota_enforce == 'on' ? true: false; + $.ajax({ + url: createURL('quotaCredits'), + data: { + domainid: args.context.fullSummary[0].domainid, + account: args.context.fullSummary[0].account, + value: args.data.value, + min_balance: args.data.min_balance, + quota_enforce: enforce + }, + async: false, + success: function(json) { + args.response.success({ + data: json.quotacreditsresponse.totalquota + }); + } + }); + $(window).trigger('cloudStack.fullRefresh'); + } + }, + }, + tabs: { + details: { + title: 'label.details', + fields: [{ + id: { + label: 'label.quota.statement.balance' + } + }, { + startdate: { + label: 'label.quota.date', + }, + startquota: { + label: 'label.quota.value', + } + }], + dataProvider: function(args) { + $.ajax({ + url: createURL('quotaBalance'), + dataType: 'json', + async: true, + data: { + domainid: args.context.fullSummary[0].domainid, + account: args.context.fullSummary[0].account + }, + success: function(json) { + var item = json.quotabalanceresponse.balance; + item.startdate = now.toJSON().slice(0,10); + item.startquota = item.currency + ' ' + item.startquota; + args.response.success({ + data: item + }); + } , + error:function(data) { + args.response.error(parseXMLHttpResponse(data)); + } + }); + } + }, + } + } + } + }, + + quotastatement:{ + id: 'statementbalance', + title: 'label.quota.statement', + preFilter: function(args) { + return false; + }, + listView: { + label: 'label.quota.statement.quota', + disableInfiniteScrolling: true, + fields: { + name: { + label: 'label.quota.type.name' + }, + unit: { + label: 'label.quota.type.unit' + }, + quota: { + label: 'label.quota.usage' + } + }, + actions: { + add: { + label: 'label.quota.dates', + createForm: { + title: 'label.quota.dates', + fields: { + startdate: { + label: 'label.quota.startdate', + isDatepicker: true, + maxDate: '+0d', + validation: { + required: true + } + }, + enddate: { + label: 'label.quota.enddate', + isDatepicker: true, + maxDate: '+0d', + validation: { + required: true + } + } + } + }, + action: function(args) { + newstartdate= args.data.startdate.slice(0,10); + newenddate= args.data.enddate.slice(0,10); + $(window).trigger('cloudStack.fullRefresh'); + } + } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL('quotaStatement'), + dataType: 'json', + async: true, + data: { + domainid: args.context.summary[0].domainid, + account: args.context.summary[0].account, + startdate: function() { + if (typeof newstartdate == 'undefined') { + return args.context.summary[0].startdate.slice(0,10); + } else { + return newstartdate; + } + }, + enddate: function() { + if (typeof newenddate == 'undefined') { + return now.toJSON().slice(0,10); + } else { + return newenddate; + } + } + }, + success: function(json) { + var usages = json.quotastatementresponse.statement.quotausage; + var currency = json.quotastatementresponse.statement.currency; + $.each(usages, function(idx, item) { + usages[idx].quota = currency + ' ' + usages[idx].quota; + }); + usages.push({ + name: _l('label.quota.total') + ' : ', + unit:'', + quota: currency + ' ' + json.quotastatementresponse.statement.totalquota + }); + + usages.unshift({ + name: _l('label.quota.startdate') + ' : ' + json.quotastatementresponse.statement.startdate.slice(0,10), + unit: _l('label.quota.enddate') + ' : ' + json.quotastatementresponse.statement.enddate.slice(0,10), + quota: '' + }); + + + args.response.success({ + data: usages + }); + }, + error:function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + } + } // end list view + }, // end statement + + + balancestatement:{ + id: 'balancestatement', + title: 'label.quota.statement.balance', + listView: { + label: 'label.quota.statement.balance', + disableInfiniteScrolling: true, + fields: { + date: { + label: 'label.quota.date' + }, + quota: { + label: 'label.quota.value' + }, + credit: { + label: 'label.quota.credit' + } + }, + + actions: { + add: { + label: 'label.quota.dates', + createForm: { + title: 'label.quota.dates', + fields: { + startdate: { + label: 'label.quota.startdate', + isDatepicker: true, + maxDate: '+0d', + validation: { + required: true + } + }, + enddate: { + label: 'label.quota.enddate', + isDatepicker: true, + maxDate: '+0d', + validation: { + required: true + } + } + } + }, + action: function(args) { + newstartdate= args.data.startdate.slice(0,10); + newenddate= args.data.enddate.slice(0,10); + $(window).trigger('cloudStack.fullRefresh'); + } + } + }, + + dataProvider: function(args) { + $.ajax({ + url: createURL('quotaBalance'), + dataType: 'json', + async: true, + data: { + domainid: args.context.summary[0].domainid, + account: args.context.summary[0].account, + startdate: function() { + if (typeof newstartdate == 'undefined') { + return args.context.summary[0].startdate.slice(0,10); + } else { + return newstartdate; + } + }, + enddate: function() { + if (typeof newenddate == 'undefined') { + return now.toJSON().slice(0,10); + } else { + return newenddate; + } + } + }, + success: function(json) { + var bal = json.quotabalanceresponse.balance; + var currency = bal.currency; + var array=[{ + date: bal.startdate.slice(0,10), + quota: currency + ' ' + bal.startquota, + credit: '' + }]; + //now add all credits + for (var i = 0; i < bal.credits.length; i++) { + array.push({ + date: bal.credits[i].updated_on.slice(0,10), + quota: '', + credit: currency + ' ' + bal.credits[i].credits + }); + } + array.push({ + date: bal.enddate.slice(0,10), + quota: currency + ' ' + bal.endquota, + credit: '' + }); + args.response.success({ + data: array + }); + }, + error:function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + } + } // end list view + }, // end statement + + + tariffEdit: { + type: 'select', + title: 'label.quota.tariff', + listView: { + label: 'label.quota.tariff', + disableInfiniteScrolling: true, + actions: { + edit: { + label: 'label.change.value', + action: function(args) { + if (isAdmin()) { + var data = { + usagetype: args.data.jsonObj.usageType, + }; + var tariffVal = args.data.tariffValue.split(' '); + if (tariffVal.length==2){ + data.value = tariffVal[1]; + } + else { + data.value = tariffVal[0]; + } + if (!isNaN(parseFloat(data.value)) && isFinite(data.value)){ + var updateTariffForm = cloudStack.dialog.createForm({ + form: { + title: 'label.quota.configuration', + fields: { + tariffValue: { + label: 'label.quota.value', + number: true, + validation: { + required: true + } + }, + effectiveDate: { + label: 'label.quota.tariff.effectivedate', + isDatepicker: true, + dependsOn: 'startdate', + minDate: '+1d', + validation: { + required: true + } + }, + } + }, + after: function(argsLocal) { + data.startDate = argsLocal.data.effectiveDate; + data.value = argsLocal.data.tariffValue; + $.ajax({ + url: createURL('quotaTariffUpdate'), + data: data, + type: "POST", + success: function(json) { + // Refresh listings on chosen date to reflect new tariff + $($.find('div.search-bar input')).val(data.startDate); + $('#basic_search').click(); + } + }); + } + }); + updateTariffForm.find('input[name=tariffValue]').val(data.value); + updateTariffForm.find('input[name=effectiveDate]').focus(); + } + else { + alert("Bad tariff value, this should be a number " + data.value); + $(window).trigger('cloudStack.fullRefresh'); + }//bad data.value - NaN + } // if is ADMIN + } // end action + }//edits + },//actions + fields: { + usageName: { + label: 'label.usage.type', + id: true, + truncate: true + }, + usageUnit: { + label: 'label.usage.unit' + }, + tariffValue: { + label: 'label.quota.tariff.value', + editable: true + }, + description: { + label: 'label.quota.description', + truncate: true + } + }, + dataProvider: function(args) { + var data = {}; + if (args.filterBy.search.value) { + data.startdate = args.filterBy.search.value; + } + $.ajax({ + url: createURL('quotaTariffList'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var items = json.quotatarifflistresponse.quotatariff; + $.each(items, function(idx, item) { + items[idx].tariffValue = items[idx].currency + ' ' + items[idx].tariffValue; + }); + args.response.success({ + data: items + }); + + $($.find('.list-view')).data('end-of-table', true); + // Hook up date picker + var input = $($.find('div.search-bar input')); + input.datepicker({ + defaultDate: new Date(), + changeMonth: true, + dateFormat: "yy-mm-dd", + }); + input.parent().attr('title', _l('label.quota.effectivedate')); + }, + error: function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + } + } + }, + + + tariffView: { + type: 'select', + title: 'label.quota.tariff', + listView: { + label: 'label.quota.tariff', + disableInfiniteScrolling: true, + fields: { + usageName: { + label: 'label.usage.type', + id: true, + truncate: true + }, + usageUnit: { + label: 'label.usage.unit' + }, + tariffValue: { + label: 'label.quota.tariff.value', + }, + description: { + label: 'label.quota.description', + truncate: true + } + }, + dataProvider: function(args) { + var data = {}; + if (args.filterBy.search.value) { + data.startdate = args.filterBy.search.value; + } + $.ajax({ + url: createURL('quotaTariffList'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var items = json.quotatarifflistresponse.quotatariff; + $.each(items, function(idx, item) { + items[idx].tariffValue = items[idx].currency + ' ' + items[idx].tariffValue; + }); + args.response.success({ + data: items + }); + + $($.find('.list-view')).data('end-of-table', true); + // Hook up date picker + var input = $($.find('div.search-bar input')); + input.datepicker({ + defaultDate: new Date(), + changeMonth: true, + dateFormat: "yy-mm-dd", + }); + input.parent().attr('title', _l('label.quota.effectivedate')); + }, + error: function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + } + } + }, + + emailTemplates: { + type: 'select', + title: 'label.quota.email.template', + listView: { + label: 'label.quota.email.template', + disableInfiniteScrolling: true, + fields: { + templatetype: { + label: 'label.quota.email.template', + }, + templatesubject: { + label: 'label.quota.email.subject', + truncate: true + }, + templatebody: { + label: 'label.quota.email.body', + truncate: true + }, + last_updated: { + label: 'label.quota.email.lastupdated', + truncate: true + }, + }, + dataProvider: function(args) { + var data = {}; + if (args.filterBy.search.value) { + data.templatetype = args.filterBy.search.value; + } + + $.ajax({ + url: createURL('quotaEmailTemplateList'), + data: data, + dataType: "json", + async: true, + success: function(json) { + if (!json.hasOwnProperty('quotaemailtemplatelistresponse') || !json.quotaemailtemplatelistresponse.hasOwnProperty('quotaemailtemplate')) { + return; + } + var items = json.quotaemailtemplatelistresponse.quotaemailtemplate; + args.response.success({ + data: items + }); + }, + error: function(data) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(data) + }); + } + }); + }, + detailView: { + actions: { + edit: { + label: 'label.quota.email.edit', + messages: { + notification: function(args) { + return 'label.quota.email.edit'; + } + }, + action: function(args) { + args.data.templatetype = args.context.emailTemplates[0].templatetype; + $.ajax({ + url: createURL('quotaEmailTemplateUpdate'), + data: args.data, + success: function(json) { + args.response.success({ + _custom: { + success: true + } + }); + } + }); + } + } + }, + + tabs: { + details: { + title: 'label.details', + fields: [{ + templatetype: { + label: 'label.quota.email.template' + } + }, { + templatesubject: { + label: 'label.quota.email.subject', + isEditable: true, + textArea: true + }, + templatebody: { + label: 'label.quota.email.body', + isEditable: true, + textArea: true + }, + last_updated: { + label: 'label.quota.email.lastupdated', + }, + }], + + dataProvider: function(args) { + $.ajax({ + url: createURL('quotaEmailTemplateList'), + data: { + templatetype: args.context.emailTemplates[0].templatetype + }, + success: function(json) { + var item = json.quotaemailtemplatelistresponse.quotaemailtemplate[0]; + args.response.success({ + data: item + }); + } + }); + } + } + } + } + } + } + + } + }); + }; +}(cloudStack)); diff --git a/ui/scripts/ui/dialog.js b/ui/scripts/ui/dialog.js index 4079936412f..5e28ba3a10d 100644 --- a/ui/scripts/ui/dialog.js +++ b/ui/scripts/ui/dialog.js @@ -527,7 +527,9 @@ } $input.addClass("disallowSpecialCharacters"); $input.datepicker({ - dateFormat: 'yy-mm-dd' + dateFormat: 'yy-mm-dd', + maxDate: field.maxDate, + minDate: field.minDate }); } else if (field.range) { //2 text fields on the same line (e.g. port range: startPort - endPort) diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js index 8a3fcd7f425..ddd72657747 100644 --- a/ui/scripts/ui/widgets/detailView.js +++ b/ui/scripts/ui/widgets/detailView.js @@ -442,7 +442,7 @@ if ($detailView.find('.button.done').size()) return false; // Convert value TDs - var $inputs = $detailView.find('input, select').filter(function() { + var $inputs = $detailView.find('input, select, textarea').filter(function() { return !$(this).closest('.tagger').size() && !$(this).attr('type') == 'submit'; }); var action = args.actions[args.actionName]; @@ -507,11 +507,17 @@ $input.find('option:selected').html() )); $value.data('detail-view-selected-option', _s($input.find('option:selected').val())); + } else if ($input.is('textarea')) { + $value.html(_s( + $input.val() + )); + $value.data('detail-view-editable-textarea', _s($input.find('option:selected').val())); } }); - if ($token != null) + if ($token) { $token.html(_s(tags_value)); + } }; var removeEditForm = function() { @@ -627,7 +633,7 @@ }; $editButton.click(function() { - var $inputs = $detailView.find('input, select').filter(function() { + var $inputs = $detailView.find('input, select, textarea').filter(function() { return !$(this).closest('.tagger').size(); }); var $form = $detailView.find('form').filter(function() { @@ -673,6 +679,7 @@ // Turn into form field var selectData = $value.data('detail-view-editable-select'); var isBoolean = $value.data('detail-view-editable-boolean'); + var textArea = $value.data('detail-view-editable-textarea'); var isTokenInput = $value.data('detail-view-is-token-input'); var data = !isBoolean ? cloudStack.sanitizeReverse($value.html()) : $value.data('detail-view-boolean-value'); var rules = $value.data('validation-rules') ? $value.data('validation-rules') : {}; @@ -761,6 +768,15 @@ $value.append($input); token_value = data; $value.data('value-token').dataProvider(selectArgs); + } else if (textArea) { + // Text area + $value.append( + $('