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