Skip to content

Commit 2a4a1f7

Browse files
abh1sarshwstppr
andauthored
Support multi-scope configuration settings (#10300)
This PR introduces the concept of multi-scope configuration settings. In addition to the Global level, currently all configurations can be set at a single scope level. It will be useful if a configuration can be set at multiple scopes. For example, a configuration set at the domain level will apply for all accounts, but it can be set for an account as well. In which case the account level setting will override the domain level setting. This is done by changing the column `scope` of table `configuration` from string (single scope) to bitmask (multiple scopes). ``` public enum Scope { Global(null, 1), Zone(Global, 1 << 1), Cluster(Zone, 1 << 2), StoragePool(Cluster, 1 << 3), ManagementServer(Global, 1 << 4), ImageStore(Zone, 1 << 5), Domain(Global, 1 << 6), Account(Domain, 1 << 7); ``` Each scope is also assigned a parent scope. When a configuration for a given scope is not defined but is available for multiple scope types, the value will be retrieved from the parent scope. If there is no parent scope or if the configuration is defined for a single scope only, the value will fall back to the global level. Hierarchy for different scopes is defined as below : - Global - Zone - Cluster - Storage Pool - Image Store - Management Server - Domain - Account This PR also updates the scope of the following configurations (Storage Pool scope is added in addition to the existing Zone scope): - pool.storage.allocated.capacity.disablethreshold - pool.storage.allocated.resize.capacity.disablethreshold - pool.storage.capacity.disablethreshold Doc PR : apache/cloudstack-documentation#476 Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> Co-authored-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent f8563b8 commit 2a4a1f7

File tree

38 files changed

+879
-124
lines changed

38 files changed

+879
-124
lines changed

engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
// under the License.
1717
package com.cloud.capacity;
1818

19+
import java.util.List;
20+
1921
import org.apache.cloudstack.framework.config.ConfigKey;
2022
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
2123

@@ -67,7 +69,7 @@ public interface CapacityManager {
6769
"0.85",
6870
"Percentage (as a value between 0 and 1) of storage utilization above which allocators will disable using the pool for low storage available.",
6971
true,
70-
ConfigKey.Scope.Zone);
72+
List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone));
7173
static final ConfigKey<Double> StorageOverprovisioningFactor =
7274
new ConfigKey<>(
7375
"Storage",
@@ -85,7 +87,7 @@ public interface CapacityManager {
8587
"0.85",
8688
"Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for low allocated storage available.",
8789
true,
88-
ConfigKey.Scope.Zone);
90+
List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone));
8991
static final ConfigKey<Boolean> StorageOperationsExcludeCluster =
9092
new ConfigKey<>(
9193
Boolean.class,
@@ -125,7 +127,7 @@ public interface CapacityManager {
125127
"Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for volume resize. " +
126128
"This is applicable only when volume.resize.allowed.beyond.allocation is set to true.",
127129
true,
128-
ConfigKey.Scope.Zone);
130+
List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone));
129131

130132
ConfigKey<Integer> CapacityCalculateWorkers = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
131133
"capacity.calculate.workers", "1",

engine/components-api/src/main/java/com/cloud/storage/StorageManager.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public interface StorageManager extends StorageService {
214214
ConfigKey<Boolean> AllowVolumeReSizeBeyondAllocation = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false",
215215
"Determines whether volume size can exceed the pool capacity allocation disable threshold (pool.storage.allocated.capacity.disablethreshold) " +
216216
"when resize a volume upto resize capacity disable threshold (pool.storage.allocated.resize.capacity.disablethreshold)",
217-
true, ConfigKey.Scope.Zone);
217+
true, List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone));
218218

219219
ConfigKey<Integer> StoragePoolHostConnectWorkers = new ConfigKey<>("Storage", Integer.class,
220220
"storage.pool.host.connect.workers", "1",

engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java

+17
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@
2222
import java.util.Map;
2323
import java.util.stream.Collectors;
2424

25+
import javax.inject.Inject;
26+
2527
import org.apache.cloudstack.framework.config.ConfigKey;
2628
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
2729
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
2830
import org.apache.commons.collections.CollectionUtils;
2931

32+
import com.cloud.dc.dao.ClusterDao;
33+
import com.cloud.org.Cluster;
34+
import com.cloud.utils.Pair;
3035
import com.cloud.utils.crypt.DBEncryptionUtil;
3136
import com.cloud.utils.db.SearchBuilder;
3237
import com.cloud.utils.db.SearchCriteria;
@@ -35,6 +40,9 @@
3540

3641
public class ClusterDetailsDaoImpl extends ResourceDetailsDaoBase<ClusterDetailsVO> implements ClusterDetailsDao, ScopedConfigStorage {
3742

43+
@Inject
44+
ClusterDao clusterDao;
45+
3846
protected final SearchBuilder<ClusterDetailsVO> ClusterSearch;
3947
protected final SearchBuilder<ClusterDetailsVO> DetailSearch;
4048

@@ -186,4 +194,13 @@ private String getCpuMemoryOvercommitRatio(String name) {
186194

187195
return name;
188196
}
197+
198+
@Override
199+
public Pair<Scope, Long> getParentScope(long id) {
200+
Cluster cluster = clusterDao.findById(id);
201+
if (cluster == null) {
202+
return null;
203+
}
204+
return new Pair<>(getScope().getParent(), cluster.getDataCenterId());
205+
}
189206
}

engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java

+15
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
3131
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
3232

33+
import com.cloud.utils.Pair;
34+
3335
public class StoragePoolDetailsDaoImpl extends ResourceDetailsDaoBase<StoragePoolDetailVO> implements StoragePoolDetailsDao, ScopedConfigStorage {
3436

3537
@Inject
@@ -57,4 +59,17 @@ public void addDetail(long resourceId, String key, String value, boolean display
5759
}
5860
super.addDetail(new StoragePoolDetailVO(resourceId, key, value, display));
5961
}
62+
63+
@Override
64+
public Pair<Scope, Long> getParentScope(long id) {
65+
StoragePoolVO pool = _storagePoolDao.findById(id);
66+
if (pool != null) {
67+
if (pool.getClusterId() != null) {
68+
return new Pair<>(getScope().getParent(), pool.getClusterId());
69+
} else {
70+
return new Pair<>(ConfigKey.Scope.Zone, pool.getDataCenterId());
71+
}
72+
}
73+
return null;
74+
}
6075
}

engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public ConfigurationGroupsAggregator() {
5454

5555
public void updateConfigurationGroups() {
5656
LOG.debug("Updating configuration groups");
57-
List<ConfigurationVO> configs = configDao.listAllIncludingRemoved();
57+
List<ConfigurationVO> configs = configDao.searchPartialConfigurations();
5858
if (CollectionUtils.isEmpty(configs)) {
5959
return;
6060
}

engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java

+30
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,36 @@ public boolean columnExists(Connection conn, String tableName, String columnName
8787
return columnExists;
8888
}
8989

90+
public String getColumnType(Connection conn, String tableName, String columnName) {
91+
try (PreparedStatement pstmt = conn.prepareStatement(String.format("DESCRIBE %s %s", tableName, columnName));){
92+
ResultSet rs = pstmt.executeQuery();
93+
if (rs.next()) {
94+
return rs.getString("Type");
95+
}
96+
} catch (SQLException e) {
97+
logger.warn("Type for column {} can not be retrieved in {} ignoring exception: {}", columnName, tableName, e.getMessage());
98+
}
99+
return null;
100+
}
101+
102+
public void addColumn(Connection conn, String tableName, String columnName, String columnDefinition) {
103+
try (PreparedStatement pstmt = conn.prepareStatement(String.format("ALTER TABLE %s ADD COLUMN %s %s", tableName, columnName, columnDefinition));){
104+
pstmt.executeUpdate();
105+
logger.debug("Column {} is added successfully from the table {}", columnName, tableName);
106+
} catch (SQLException e) {
107+
logger.warn("Unable to add column {} to table {} due to exception", columnName, tableName, e);
108+
}
109+
}
110+
111+
public void changeColumn(Connection conn, String tableName, String oldColumnName, String newColumnName, String columnDefinition) {
112+
try (PreparedStatement pstmt = conn.prepareStatement(String.format("ALTER TABLE %s CHANGE COLUMN %s %s %s", tableName, oldColumnName, newColumnName, columnDefinition));){
113+
pstmt.executeUpdate();
114+
logger.debug("Column {} is changed successfully to {} from the table {}", oldColumnName, newColumnName, tableName);
115+
} catch (SQLException e) {
116+
logger.warn("Unable to add column {} to {} from the table {} due to exception", oldColumnName, newColumnName, tableName, e);
117+
}
118+
}
119+
90120
public String generateIndexName(String tableName, String... columnName) {
91121
return String.format("i_%s__%s", tableName, StringUtils.join(columnName, "__"));
92122
}

engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java

+16
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,20 @@ public static void dropTableColumnsIfExist(Connection conn, String tableName, Li
5858
}
5959
}
6060

61+
public static String getTableColumnType(Connection conn, String tableName, String columnName) {
62+
return dao.getColumnType(conn, tableName, columnName);
63+
}
64+
65+
public static void addTableColumnIfNotExist(Connection conn, String tableName, String columnName, String columnDefinition) {
66+
if (!dao.columnExists(conn, tableName, columnName)) {
67+
dao.addColumn(conn, tableName, columnName, columnDefinition);
68+
}
69+
}
70+
71+
public static void changeTableColumnIfNotExist(Connection conn, String tableName, String oldColumnName, String newColumnName, String columnDefinition) {
72+
if (dao.columnExists(conn, tableName, oldColumnName)) {
73+
dao.changeColumn(conn, tableName, oldColumnName, newColumnName, columnDefinition);
74+
}
75+
}
76+
6177
}

engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42010to42100.java

+38
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@
1717
package com.cloud.upgrade.dao;
1818

1919
import com.cloud.upgrade.SystemVmTemplateRegistration;
20+
import com.cloud.utils.db.TransactionLegacy;
2021
import com.cloud.utils.exception.CloudRuntimeException;
2122

2223
import java.io.InputStream;
2324
import java.sql.Connection;
25+
import java.sql.PreparedStatement;
26+
import java.sql.SQLException;
27+
import java.util.List;
28+
29+
import org.apache.cloudstack.framework.config.ConfigKey;
2430

2531
public class Upgrade42010to42100 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate {
2632
private SystemVmTemplateRegistration systemVmTemplateRegistration;
@@ -53,6 +59,7 @@ public InputStream[] getPrepareScripts() {
5359

5460
@Override
5561
public void performDataMigration(Connection conn) {
62+
migrateConfigurationScopeToBitmask(conn);
5663
}
5764

5865
@Override
@@ -80,4 +87,35 @@ public void updateSystemVmTemplates(Connection conn) {
8087
throw new CloudRuntimeException("Failed to find / register SystemVM template(s)");
8188
}
8289
}
90+
91+
protected void migrateConfigurationScopeToBitmask(Connection conn) {
92+
String scopeDataType = DbUpgradeUtils.getTableColumnType(conn, "configuration", "scope");
93+
logger.info("Data type of the column scope of table configuration is {}", scopeDataType);
94+
if (!"varchar(255)".equals(scopeDataType)) {
95+
return;
96+
}
97+
DbUpgradeUtils.addTableColumnIfNotExist(conn, "configuration", "new_scope", "BIGINT DEFAULT 0");
98+
migrateExistingConfigurationScopeValues(conn);
99+
DbUpgradeUtils.dropTableColumnsIfExist(conn, "configuration", List.of("scope"));
100+
DbUpgradeUtils.changeTableColumnIfNotExist(conn, "configuration", "new_scope", "scope", "BIGINT NOT NULL DEFAULT 0 COMMENT 'Bitmask for scope(s) of this parameter'");
101+
}
102+
103+
protected void migrateExistingConfigurationScopeValues(Connection conn) {
104+
StringBuilder sql = new StringBuilder("UPDATE configuration\n" +
105+
"SET new_scope = " +
106+
" CASE ");
107+
for (ConfigKey.Scope scope : ConfigKey.Scope.values()) {
108+
sql.append(" WHEN scope = '").append(scope.name()).append("' THEN ").append(scope.getBitValue()).append(" ");
109+
}
110+
sql.append(" ELSE 0 " +
111+
" END " +
112+
"WHERE scope IS NOT NULL;");
113+
TransactionLegacy txn = TransactionLegacy.currentTxn();
114+
try (PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql.toString())) {
115+
pstmt.executeUpdate();
116+
} catch (SQLException e) {
117+
logger.error("Failed to migrate existing configuration scope values to bitmask", e);
118+
throw new CloudRuntimeException(String.format("Failed to migrate existing configuration scope values to bitmask due to: %s", e.getMessage()));
119+
}
120+
}
83121
}

engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.cloud.domain.dao.DomainDao;
3434
import com.cloud.domain.dao.DomainDetailsDao;
3535
import com.cloud.user.dao.AccountDao;
36+
import com.cloud.utils.Pair;
3637
import com.cloud.utils.db.QueryBuilder;
3738
import com.cloud.utils.db.SearchBuilder;
3839
import com.cloud.utils.db.SearchCriteria;
@@ -156,4 +157,13 @@ public String getConfigValue(long id, String key) {
156157
}
157158
return value;
158159
}
160+
161+
@Override
162+
public Pair<Scope, Long> getParentScope(long id) {
163+
Account account = _accountDao.findById(id);
164+
if (account == null) {
165+
return null;
166+
}
167+
return new Pair<>(getScope().getParent(), account.getDomainId());
168+
}
159169
}

engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23+
import javax.inject.Inject;
24+
2325
import org.apache.cloudstack.api.ApiConstants;
2426
import org.apache.cloudstack.framework.config.ConfigKey;
2527
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
2628
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
2729
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
2830
import org.springframework.stereotype.Component;
2931

32+
import com.cloud.storage.ImageStore;
33+
import com.cloud.utils.Pair;
3034
import com.cloud.utils.crypt.DBEncryptionUtil;
3135
import com.cloud.utils.db.QueryBuilder;
3236
import com.cloud.utils.db.SearchBuilder;
@@ -36,6 +40,9 @@
3640

3741
@Component
3842
public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase<ImageStoreDetailVO> implements ImageStoreDetailsDao, ScopedConfigStorage {
43+
@Inject
44+
ImageStoreDao imageStoreDao;
45+
3946
protected final SearchBuilder<ImageStoreDetailVO> storeSearch;
4047

4148
public ImageStoreDetailsDaoImpl() {
@@ -113,10 +120,20 @@ public String getConfigValue(long id, String key) {
113120
public String getConfigValue(long id, ConfigKey<?> key) {
114121
ImageStoreDetailVO vo = findDetail(id, key.key());
115122
return vo == null ? null : getActualValue(vo);
116-
}
123+
}
117124

118125
@Override
119126
public void addDetail(long resourceId, String key, String value, boolean display) {
120127
super.addDetail(new ImageStoreDetailVO(resourceId, key, value, display));
121128
}
129+
130+
@Override
131+
public Pair<Scope, Long> getParentScope(long id) {
132+
ImageStore store = imageStoreDao.findById(id);
133+
if (store == null) {
134+
return null;
135+
}
136+
return new Pair<>(getScope().getParent(), store.getDataCenterId());
137+
}
138+
122139
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 com.cloud.upgrade;
18+
19+
import static org.mockito.Mockito.when;
20+
21+
import java.util.Collections;
22+
23+
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
24+
import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao;
25+
import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao;
26+
import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO;
27+
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
28+
import org.apache.logging.log4j.Logger;
29+
import org.junit.Assert;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.mockito.InjectMocks;
33+
import org.mockito.Mock;
34+
import org.mockito.Mockito;
35+
import org.mockito.junit.MockitoJUnitRunner;
36+
37+
@RunWith(MockitoJUnitRunner.class)
38+
public class ConfigurationGroupsAggregatorTest {
39+
@InjectMocks
40+
private ConfigurationGroupsAggregator configurationGroupsAggregator = new ConfigurationGroupsAggregator();
41+
42+
@Mock
43+
private ConfigurationDao configDao;
44+
45+
@Mock
46+
private ConfigurationGroupDao configGroupDao;
47+
48+
@Mock
49+
private ConfigurationSubGroupDao configSubGroupDao;
50+
51+
@Mock
52+
private Logger logger;
53+
54+
@Test
55+
public void testUpdateConfigurationGroups() {
56+
ConfigurationVO config = new ConfigurationVO("Advanced", "DEFAULT", "management-server",
57+
"test.config.name", null, "description");
58+
config.setGroupId(1L);
59+
config.setSubGroupId(1L);
60+
61+
when(configDao.searchPartialConfigurations()).thenReturn(Collections.singletonList(config));
62+
63+
ConfigurationSubGroupVO configSubGroup = Mockito.mock(ConfigurationSubGroupVO.class);
64+
when(configSubGroupDao.findByName("name")).thenReturn(configSubGroup);
65+
Mockito.when(configSubGroup.getId()).thenReturn(10L);
66+
Mockito.when(configSubGroup.getGroupId()).thenReturn(5L);
67+
68+
configurationGroupsAggregator.updateConfigurationGroups();
69+
70+
Assert.assertEquals(Long.valueOf(5), config.getGroupId());
71+
Assert.assertEquals(Long.valueOf(10), config.getSubGroupId());
72+
Mockito.verify(configDao, Mockito.times(1)).persist(config);
73+
Mockito.verify(logger, Mockito.times(1)).debug("Updating configuration groups");
74+
Mockito.verify(logger, Mockito.times(1)).debug("Successfully updated configuration groups.");
75+
}
76+
}

0 commit comments

Comments
 (0)