Skip to content

Commit bbc1260

Browse files
Resource reservation framework (#6694)
This PR addresses parallel resource allocation as a generalization of the problem and solution described in #6644. Instead of the Global lock on the resources a reservation record is created which is added in the resource check count in the ResourceLimitService/ResourceLimitManagerImpl. As a convenience a CheckedReservation is created. This is an implementation of AutoClosable and can be used as a guard in a try-with-resource fashion. The close method of the CheckedReservation wil delete the reservation record. Co-authored-by: Boris Stoyanov - a.k.a Bobby <bss.stoyanov@gmail.com>
1 parent e39b7ac commit bbc1260

File tree

14 files changed

+965
-277
lines changed

14 files changed

+965
-277
lines changed

api/src/main/java/com/cloud/user/ResourceLimitService.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.cloud.domain.Domain;
2525
import com.cloud.exception.ResourceAllocationException;
2626
import org.apache.cloudstack.framework.config.ConfigKey;
27+
import org.apache.cloudstack.user.ResourceReservation;
2728

2829
public interface ResourceLimitService {
2930

@@ -91,7 +92,7 @@ public interface ResourceLimitService {
9192
/**
9293
* This call should be used when we have already queried resource limit for an account. This is to handle
9394
* some corner cases where queried limit may be null.
94-
* @param accountType
95+
* @param accountId
9596
* @param limit
9697
* @param type
9798
* @return
@@ -102,7 +103,7 @@ public interface ResourceLimitService {
102103
* Finds the resource limit for a specified domain and type. If the domain has an infinite limit, will check
103104
* up the domain hierarchy
104105
*
105-
* @param account
106+
* @param domain
106107
* @param type
107108
* @return resource limit
108109
*/
@@ -197,4 +198,16 @@ public interface ResourceLimitService {
197198
* @param delta
198199
*/
199200
void decrementResourceCount(long accountId, ResourceType type, Boolean displayResource, Long... delta);
201+
202+
/**
203+
* Adds a reservation that will be counted in subsequent calls to {count}getResourceCount{code} until {code}this[code}
204+
* is closed. It will create a reservation record that will be counted when resource limits are checked.
205+
* @param account The account for which the reservation is.
206+
* @param displayResource whether this resource is shown to users at all (if not it is not counted to limits)
207+
* @param type resource type
208+
* @param delta amount to reserve (will not be <+ 0)
209+
* @return a {code}AutoClosable{Code} object representing the resource the user needs
210+
*/
211+
ResourceReservation getReservation(Account account, Boolean displayResource, ResourceType type, Long delta) throws ResourceAllocationException;
212+
200213
}

api/src/main/java/org/apache/cloudstack/acl/APILimitChecker.java renamed to api/src/main/java/org/apache/cloudstack/user/ResourceReservation.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//
12
// Licensed to the Apache Software Foundation (ASF) under one
23
// or more contributor license agreements. See the NOTICE file
34
// distributed with this work for additional information
@@ -14,17 +15,23 @@
1415
// KIND, either express or implied. See the License for the
1516
// specific language governing permissions and limitations
1617
// under the License.
17-
package org.apache.cloudstack.acl;
18-
19-
import org.apache.cloudstack.api.ServerApiException;
18+
//
19+
package org.apache.cloudstack.user;
2020

21-
import com.cloud.user.Account;
22-
import com.cloud.utils.component.Adapter;
21+
import com.cloud.configuration.Resource;
22+
import org.apache.cloudstack.api.InternalIdentity;
2323

2424
/**
25-
* APILimitChecker checks if we should block an API request based on pre-set account based api limit.
25+
* an interface defining an {code}AutoClosable{code} reservation object
2626
*/
27-
public interface APILimitChecker extends Adapter {
28-
// Interface for checking if the account is over its api limit
29-
void checkLimit(Account account) throws ServerApiException;
27+
public interface
28+
ResourceReservation extends InternalIdentity {
29+
30+
Long getAccountId();
31+
32+
Long getDomainId();
33+
34+
Resource.ResourceType getResourceType();
35+
36+
Long getReservedAmount();
3037
}

core/src/test/java/com/cloud/storage/template/LocalTemplateDownloaderTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
package com.cloud.storage.template;
2121

22-
import static org.junit.Assert.fail;
22+
import static org.junit.Assert.assertTrue;
2323

2424
import java.io.File;
2525

@@ -33,9 +33,7 @@ public void localTemplateDownloaderTest() throws Exception {
3333
String url = new File("pom.xml").toURI().toURL().toString();
3434
TemplateDownloader td = new LocalTemplateDownloader(null, url, System.getProperty("java.io.tmpdir"), TemplateDownloader.DEFAULT_MAX_TEMPLATE_SIZE_IN_BYTES, null);
3535
long bytes = td.download(true, null);
36-
if (!(bytes > 0)) {
37-
fail("Failed download");
38-
}
36+
assertTrue("Failed download", bytes > 0);
3937
}
4038

4139
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package org.apache.cloudstack.reservation;
20+
21+
import com.cloud.configuration.Resource;
22+
import org.apache.cloudstack.user.ResourceReservation;
23+
import com.cloud.utils.exception.CloudRuntimeException;
24+
25+
import javax.persistence.Column;
26+
import javax.persistence.Entity;
27+
import javax.persistence.GeneratedValue;
28+
import javax.persistence.GenerationType;
29+
import javax.persistence.Id;
30+
import javax.persistence.Table;
31+
32+
@Entity
33+
@Table(name = "resource_reservation")
34+
public class ReservationVO implements ResourceReservation {
35+
36+
@Id
37+
@GeneratedValue(strategy = GenerationType.IDENTITY)
38+
@Column(name = "id")
39+
Long id;
40+
41+
@Column(name = "account_id")
42+
long accountId;
43+
44+
@Column(name = "domain_id")
45+
long domainId;
46+
47+
@Column(name = "resource_type", nullable = false)
48+
Resource.ResourceType resourceType;
49+
50+
@Column(name = "amount")
51+
long amount;
52+
53+
protected ReservationVO()
54+
{}
55+
56+
public ReservationVO(Long accountId, Long domainId, Resource.ResourceType resourceType, Long delta) {
57+
if (delta == null || delta <= 0) {
58+
throw new CloudRuntimeException("resource reservations can not be made for no resources");
59+
}
60+
this.accountId = accountId;
61+
this.domainId = domainId;
62+
this.resourceType = resourceType;
63+
this.amount = delta;
64+
}
65+
66+
@Override
67+
public long getId() {
68+
return this.id;
69+
}
70+
71+
@Override
72+
public Long getAccountId() {
73+
return accountId;
74+
}
75+
76+
@Override
77+
public Long getDomainId() {
78+
return domainId;
79+
}
80+
81+
@Override
82+
public Resource.ResourceType getResourceType() {
83+
return resourceType;
84+
}
85+
86+
@Override
87+
public Long getReservedAmount() {
88+
return amount;
89+
}
90+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package org.apache.cloudstack.reservation.dao;
20+
21+
import com.cloud.configuration.Resource;
22+
import org.apache.cloudstack.reservation.ReservationVO;
23+
import com.cloud.utils.db.GenericDao;
24+
25+
public interface ReservationDao extends GenericDao<ReservationVO, Long> {
26+
long getAccountReservation(Long account, Resource.ResourceType resourceType);
27+
long getDomainReservation(Long domain, Resource.ResourceType resourceType);
28+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package org.apache.cloudstack.reservation.dao;
20+
21+
import com.cloud.configuration.Resource;
22+
import com.cloud.utils.db.GenericDaoBase;
23+
import com.cloud.utils.db.SearchBuilder;
24+
import com.cloud.utils.db.SearchCriteria;
25+
import org.apache.cloudstack.reservation.ReservationVO;
26+
27+
import java.util.List;
28+
29+
public class ReservationDaoImpl extends GenericDaoBase<ReservationVO, Long> implements ReservationDao {
30+
31+
private static final String RESOURCE_TYPE = "resourceType";
32+
private static final String ACCOUNT_ID = "accountId";
33+
private static final String DOMAIN_ID = "domainId";
34+
private final SearchBuilder<ReservationVO> listAccountAndTypeSearch;
35+
36+
private final SearchBuilder<ReservationVO> listDomainAndTypeSearch;
37+
38+
public ReservationDaoImpl() {
39+
listAccountAndTypeSearch = createSearchBuilder();
40+
listAccountAndTypeSearch.and(ACCOUNT_ID, listAccountAndTypeSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
41+
listAccountAndTypeSearch.and(RESOURCE_TYPE, listAccountAndTypeSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
42+
listAccountAndTypeSearch.done();
43+
44+
listDomainAndTypeSearch = createSearchBuilder();
45+
listDomainAndTypeSearch.and(DOMAIN_ID, listDomainAndTypeSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
46+
listDomainAndTypeSearch.and(RESOURCE_TYPE, listDomainAndTypeSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
47+
listDomainAndTypeSearch.done();
48+
}
49+
50+
@Override
51+
public long getAccountReservation(Long accountId, Resource.ResourceType resourceType) {
52+
long total = 0;
53+
SearchCriteria<ReservationVO> sc = listAccountAndTypeSearch.create();
54+
sc.setParameters(ACCOUNT_ID, accountId);
55+
sc.setParameters(RESOURCE_TYPE, resourceType);
56+
List<ReservationVO> reservations = listBy(sc);
57+
for (ReservationVO reservation : reservations) {
58+
total += reservation.getReservedAmount();
59+
}
60+
return total;
61+
}
62+
63+
@Override
64+
public long getDomainReservation(Long domainId, Resource.ResourceType resourceType) {
65+
long total = 0;
66+
SearchCriteria<ReservationVO> sc = listAccountAndTypeSearch.create();
67+
sc.setParameters(DOMAIN_ID, domainId);
68+
sc.setParameters(RESOURCE_TYPE, resourceType);
69+
List<ReservationVO> reservations = listBy(sc);
70+
for (ReservationVO reservation : reservations) {
71+
total += reservation.getReservedAmount();
72+
}
73+
return total;
74+
}
75+
}

engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
<bean id="projectJoinDaoImpl" class="com.cloud.api.query.dao.ProjectJoinDaoImpl" />
176176
<bean id="regionDaoImpl" class="org.apache.cloudstack.region.dao.RegionDaoImpl" />
177177
<bean id="remoteAccessVpnDaoImpl" class="com.cloud.network.dao.RemoteAccessVpnDaoImpl" />
178+
<bean id="reservationDao" class="org.apache.cloudstack.reservation.dao.ReservationDaoImpl" />
178179
<bean id="resourceCountDaoImpl" class="com.cloud.configuration.dao.ResourceCountDaoImpl" />
179180
<bean id="resourceIconDaoImpl" class="com.cloud.resource.icon.dao.ResourceIconDaoImpl" />
180181
<bean id="resourceLimitDaoImpl" class="com.cloud.configuration.dao.ResourceLimitDaoImpl" />

engine/schema/src/main/resources/META-INF/db/schema-41710to41800.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ WHERE so.default_use = 1 AND so.vm_type IN ('domainrouter', 'secondarystoragevm'
2727
ALTER TABLE `cloud`.`load_balancing_rules`
2828
ADD cidr_list VARCHAR(4096);
2929

30+
-- savely add resources in parallel
31+
-- PR#5984 Create table to persist VM stats.
32+
DROP TABLE IF EXISTS `cloud`.`resource_reservation`;
33+
CREATE TABLE `cloud`.`resource_reservation` (
34+
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
35+
`account_id` bigint unsigned NOT NULL,
36+
`domain_id` bigint unsigned NOT NULL,
37+
`resource_type` varchar(255) NOT NULL,
38+
`amount` bigint unsigned NOT NULL,
39+
PRIMARY KEY (`id`)
40+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
41+
3042
-- Alter networks table to add ip6dns1 and ip6dns2
3143
ALTER TABLE `cloud`.`networks`
3244
ADD COLUMN `ip6dns1` varchar(255) DEFAULT NULL COMMENT 'first IPv6 DNS for the network' AFTER `dns2`,

0 commit comments

Comments
 (0)