Skip to content

Commit 449d3c7

Browse files
Create API to list Quota credits (#9590)
Co-authored-by: Bernardo De Marco Gonçalves <bernardomg2004@gmail.com>
1 parent 0c13ded commit 449d3c7

File tree

15 files changed

+572
-85
lines changed

15 files changed

+572
-85
lines changed

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,14 @@ public class ApiConstants {
11921192
"numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " +
11931193
"value will be applied.";
11941194

1195+
public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
1196+
"however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
1197+
"added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone.";
1198+
1199+
public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
1200+
"however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
1201+
"added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone.";
1202+
11951203
/**
11961204
* This enum specifies IO Drivers, each option controls specific policies on I/O.
11971205
* Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).
@@ -1214,14 +1222,6 @@ public String toString() {
12141222
}
12151223
}
12161224

1217-
public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
1218-
"however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
1219-
"added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone.";
1220-
1221-
public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " +
1222-
"however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " +
1223-
"added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone.";
1224-
12251225
public enum BootType {
12261226
UEFI, BIOS;
12271227

engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java

+2
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,7 @@ public interface DomainDao extends GenericDao<DomainVO, Long> {
4242

4343
List<Long> getDomainChildrenIds(String path);
4444

45+
List<Long> getDomainAndChildrenIds(long domainId);
46+
4547
boolean domainIdListContainsAccessibleDomain(String domainIdList, Account caller, Long domainId);
4648
}

engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.sql.PreparedStatement;
2020
import java.sql.ResultSet;
2121
import java.sql.SQLException;
22+
import java.util.ArrayList;
2223
import java.util.HashSet;
2324
import java.util.List;
2425
import java.util.Set;
@@ -238,6 +239,15 @@ public List<Long> getDomainChildrenIds(String path) {
238239
return customSearch(sc, null);
239240
}
240241

242+
@Override
243+
public List<Long> getDomainAndChildrenIds(long domainId) {
244+
DomainVO domain = findById(domainId);
245+
if (domain != null) {
246+
return getDomainChildrenIds(domain.getPath());
247+
}
248+
return new ArrayList<>();
249+
}
250+
241251
@Override
242252
public boolean isChildDomain(Long parentId, Long childId) {
243253
if ((parentId == null) || (childId == null)) {

engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql

+7
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'console_endpoint_
2424

2525
-- Add client_address column to cloud.console_session table
2626
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'client_address', 'VARCHAR(45)');
27+
28+
-- Allow default roles to use quotaCreditsList
29+
INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission, sort_order)
30+
SELECT uuid(), role_id, 'quotaCreditsList', permission, sort_order
31+
FROM `cloud`.`role_permissions` rp
32+
WHERE rp.rule = 'quotaStatement'
33+
AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList');

framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDao.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
public interface QuotaCreditsDao extends GenericDao<QuotaCreditsVO, Long> {
2727

28-
List<QuotaCreditsVO> findCredits(long accountId, long domainId, Date startDate, Date endDate);
28+
List<QuotaCreditsVO> findCredits(Long accountId, Long domainId, Date startDate, Date endDate, boolean recursive);
2929

3030
QuotaCreditsVO saveCredits(QuotaCreditsVO credits);
3131

framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java

+33-21
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@
1616
//under the License.
1717
package org.apache.cloudstack.quota.dao;
1818

19-
import java.util.Collections;
2019
import java.util.Date;
2120
import java.util.List;
2221

2322
import javax.inject.Inject;
2423

24+
import com.cloud.domain.dao.DomainDao;
25+
import com.cloud.utils.db.Filter;
26+
import com.cloud.utils.db.SearchBuilder;
2527
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
2628
import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
29+
import org.apache.commons.lang3.ObjectUtils;
2730
import org.springframework.stereotype.Component;
2831

29-
import com.cloud.utils.db.Filter;
3032
import com.cloud.utils.db.GenericDaoBase;
31-
import com.cloud.utils.db.QueryBuilder;
3233
import com.cloud.utils.db.SearchCriteria;
3334
import com.cloud.utils.db.Transaction;
3435
import com.cloud.utils.db.TransactionCallback;
@@ -39,25 +40,36 @@
3940
public class QuotaCreditsDaoImpl extends GenericDaoBase<QuotaCreditsVO, Long> implements QuotaCreditsDao {
4041

4142
@Inject
42-
QuotaBalanceDao _quotaBalanceDao;
43+
DomainDao domainDao;
44+
@Inject
45+
QuotaBalanceDao quotaBalanceDao;
46+
47+
private SearchBuilder<QuotaCreditsVO> quotaCreditsVoSearch;
48+
49+
public QuotaCreditsDaoImpl() {
50+
quotaCreditsVoSearch = createSearchBuilder();
51+
quotaCreditsVoSearch.and("updatedOn", quotaCreditsVoSearch.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN);
52+
quotaCreditsVoSearch.and("accountId", quotaCreditsVoSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
53+
quotaCreditsVoSearch.and("domainId", quotaCreditsVoSearch.entity().getDomainId(), SearchCriteria.Op.IN);
54+
quotaCreditsVoSearch.done();
55+
}
4356

4457
@Override
45-
public List<QuotaCreditsVO> findCredits(final long accountId, final long domainId, final Date startDate, final Date endDate) {
46-
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<List<QuotaCreditsVO>>() {
47-
@Override
48-
public List<QuotaCreditsVO> doInTransaction(final TransactionStatus status) {
49-
if ((startDate != null) && (endDate != null) && startDate.before(endDate)) {
50-
Filter filter = new Filter(QuotaCreditsVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
51-
QueryBuilder<QuotaCreditsVO> qb = QueryBuilder.create(QuotaCreditsVO.class);
52-
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
53-
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
54-
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate);
55-
return search(qb.create(), filter);
56-
} else {
57-
return Collections.<QuotaCreditsVO> emptyList();
58-
}
59-
}
60-
});
58+
public List<QuotaCreditsVO> findCredits(Long accountId, Long domainId, Date startDate, Date endDate, boolean recursive) {
59+
SearchCriteria<QuotaCreditsVO> sc = quotaCreditsVoSearch.create();
60+
Filter filter = new Filter(QuotaCreditsVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
61+
62+
sc.setParametersIfNotNull("accountId", accountId);
63+
if (domainId != null) {
64+
List<Long> domainIds = recursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId);
65+
sc.setParameters("domainId", domainIds.toArray());
66+
}
67+
68+
if (ObjectUtils.allNotNull(startDate, endDate)) {
69+
sc.setParameters("updatedOn", startDate, endDate);
70+
}
71+
72+
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaCreditsVO>>) status -> search(sc, filter));
6173
}
6274

6375
@Override
@@ -68,7 +80,7 @@ public QuotaCreditsVO doInTransaction(final TransactionStatus status) {
6880
persist(credits);
6981
// make an entry in the balance table
7082
QuotaBalanceVO bal = new QuotaBalanceVO(credits);
71-
_quotaBalanceDao.persist(bal);
83+
quotaBalanceDao.persist(bal);
7284
return credits;
7385
}
7486
});

framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.cloudstack.quota.vo;
1818

1919
import org.apache.cloudstack.api.InternalIdentity;
20+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
2021

2122
import javax.persistence.Column;
2223
import javax.persistence.Entity;
@@ -113,4 +114,9 @@ public void setId(Long id) {
113114
public long getId() {
114115
return this.id;
115116
}
117+
118+
@Override
119+
public String toString() {
120+
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "accountId", "domainId", "credit");
121+
}
116122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//Licensed to the Apache Software Foundation (ASF) under one
2+
//or more contributor license agreements. See the NOTICE file
3+
//distributed with this work for additional information
4+
//regarding copyright ownership. The ASF licenses this file
5+
//to you under the Apache License, Version 2.0 (the
6+
//"License"); you may not use this file except in compliance
7+
//with the License. You may obtain a copy of the License at
8+
//
9+
//http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
//Unless required by applicable law or agreed to in writing,
12+
//software distributed under the License is distributed on an
13+
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
//KIND, either express or implied. See the License for the
15+
//specific language governing permissions and limitations
16+
//under the License.
17+
package org.apache.cloudstack.api.command;
18+
19+
import com.cloud.utils.Pair;
20+
21+
import org.apache.cloudstack.api.ACL;
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiConstants;
24+
import org.apache.cloudstack.api.BaseCmd;
25+
import org.apache.cloudstack.api.Parameter;
26+
import org.apache.cloudstack.api.response.AccountResponse;
27+
import org.apache.cloudstack.api.response.DomainResponse;
28+
import org.apache.cloudstack.api.response.ListResponse;
29+
import org.apache.cloudstack.api.response.QuotaCreditsResponse;
30+
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
31+
import org.apache.commons.lang3.ObjectUtils;
32+
import org.apache.commons.lang3.time.DateUtils;
33+
34+
import java.util.Calendar;
35+
import java.util.Date;
36+
import java.util.List;
37+
38+
import javax.inject.Inject;
39+
40+
@APICommand(name = "quotaCreditsList", responseObject = QuotaCreditsResponse.class, description = "Lists quota credits of an account.", since = "4.21.0",
41+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
42+
public class QuotaCreditsListCmd extends BaseCmd {
43+
44+
@Inject
45+
QuotaResponseBuilder quotaResponseBuilder;
46+
47+
@ACL
48+
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the account for which the credit statement will be generated.")
49+
private Long accountId;
50+
51+
@ACL
52+
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the domain for which credit statement will be generated. " +
53+
"Available only for administrators.")
54+
private Long domainId;
55+
56+
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "End date of the credit statement. If not provided, the current date will be " +
57+
"considered as the end date. " + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
58+
private Date endDate;
59+
60+
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the credit statement. If not provided, the first day of the current month " +
61+
"will be considered as the start date. " + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
62+
private Date startDate;
63+
64+
@Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Whether to generate the credit statement for the provided domain and its children. " +
65+
"Defaults to false.")
66+
private Boolean recursive = false;
67+
68+
public Long getAccountId() {
69+
return accountId;
70+
}
71+
72+
public void setAccountId(Long accountId) {
73+
this.accountId = accountId;
74+
}
75+
76+
public Long getDomainId() {
77+
return domainId;
78+
}
79+
80+
public void setDomainId(Long domainId) {
81+
this.domainId = domainId;
82+
}
83+
84+
public Date getEndDate() {
85+
return ObjectUtils.defaultIfNull(endDate, new Date());
86+
}
87+
88+
public void setEndDate(Date endDate) {
89+
this.endDate = endDate;
90+
}
91+
92+
public Date getStartDate() {
93+
return ObjectUtils.defaultIfNull(startDate, DateUtils.truncate(new Date(), Calendar.MONTH));
94+
}
95+
96+
public void setStartDate(Date startDate) {
97+
this.startDate = startDate;
98+
}
99+
100+
public Boolean getRecursive() {
101+
return recursive;
102+
}
103+
104+
public void setRecursive(Boolean recursive) {
105+
this.recursive = recursive;
106+
}
107+
108+
@Override
109+
public void execute() {
110+
Pair<List<QuotaCreditsResponse>, Integer> responses = quotaResponseBuilder.createQuotaCreditsListResponse(this);
111+
ListResponse<QuotaCreditsResponse> response = new ListResponse<>();
112+
response.setResponses(responses.first(), responses.second());
113+
response.setResponseName(getCommandName());
114+
setResponseObject(response);
115+
}
116+
117+
@Override
118+
public long getEntityOwnerId() {
119+
return -1;
120+
}
121+
122+
}

plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ public void setCredits(List<QuotaCreditsResponse> credits) {
122122

123123
public void addCredits(QuotaBalanceVO credit) {
124124
QuotaCreditsResponse cr = new QuotaCreditsResponse();
125-
cr.setCredits(credit.getCreditBalance());
126-
cr.setUpdatedOn(credit.getUpdatedOn() == null ? null : new Date(credit.getUpdatedOn().getTime()));
125+
cr.setCredit(credit.getCreditBalance());
126+
cr.setCreditedOn(credit.getUpdatedOn());
127127
credits.add(0, cr);
128128
}
129129

0 commit comments

Comments
 (0)