This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 31b0ed0a18c framework/config,server: configkey caching (#9628)
31b0ed0a18c is described below
commit 31b0ed0a18c5aa7caa1bee0c7c4e6090eac7e588
Author: Abhishek Kumar <[email protected]>
AuthorDate: Thu Sep 5 09:32:56 2024 +0530
framework/config,server: configkey caching (#9628)
Added caching for ConfigKey value retrievals based on the Caffeine
in-memory caching library.
https://github.com/ben-manes/caffeine
Currently, expire time for a cache is 30s and each update of the
config key invalidates the cache. On any update or reset of the
configuration, cache automatically invalidates for it.
Signed-off-by: Abhishek Kumar <[email protected]>
---
.../cloud/vm/VirtualMachineManagerImplTest.java | 11 +--
.../java/com/cloud/dc/ClusterDetailsDaoImpl.java | 5 +-
.../com/cloud/dc/dao/DataCenterDetailsDaoImpl.java | 4 +-
.../com/cloud/domain/dao/DomainDetailsDaoImpl.java | 16 ++--
.../storage/dao/StoragePoolDetailsDaoImpl.java | 11 +--
.../java/com/cloud/user/AccountDetailsDaoImpl.java | 15 ++--
.../datastore/db/ImageStoreDetailsDaoImpl.java | 15 ++--
.../cloudstack/framework/config/ConfigDepot.java | 2 +
.../cloudstack/framework/config/ConfigKey.java | 31 ++++----
.../framework/config/ScopedConfigStorage.java | 6 +-
.../framework/config/impl/ConfigDepotImpl.java | 53 ++++++++++++-
.../framework/config/impl/ConfigDepotImplTest.java | 50 ++++++++++++
pom.xml | 6 ++
.../configuration/ConfigurationManagerImpl.java | 31 ++++----
.../java/com/cloud/vm/FirstFitPlannerTest.java | 90 +++++++++++-----------
ui/public/locales/en.json | 1 +
ui/src/components/view/ListView.vue | 11 ++-
ui/src/components/view/SettingsTab.vue | 10 ++-
ui/src/views/setting/ConfigurationValue.vue | 12 ++-
19 files changed, 255 insertions(+), 125 deletions(-)
diff --git
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
index 906cded455e..47f9b9f33e2 100644
---
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
+++
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
@@ -43,7 +43,6 @@ import java.util.stream.Collectors;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.ScopedConfigStorage;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -1256,12 +1255,10 @@ public class VirtualMachineManagerImplTest {
ConfigKey configKey = VirtualMachineManager.VmMetadataManufacturer;
this.configDepotImpl =
(ConfigDepotImpl)ReflectionTestUtils.getField(configKey, "s_depot");
ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class);
- ScopedConfigStorage storage = Mockito.mock(ScopedConfigStorage.class);
- Mockito.when(storage.getConfigValue(Mockito.anyLong(),
Mockito.eq(configKey))).thenReturn(manufacturer);
- Mockito.when(storage.getConfigValue(Mockito.anyLong(),
Mockito.eq(VirtualMachineManager.VmMetadataProductName)))
- .thenReturn(product);
-
Mockito.when(configDepot.findScopedConfigStorage(configKey)).thenReturn(storage);
-
Mockito.when(configDepot.findScopedConfigStorage(VirtualMachineManager.VmMetadataProductName)).thenReturn(storage);
+
Mockito.when(configDepot.getConfigStringValue(Mockito.eq(configKey.key()),
+ Mockito.eq(ConfigKey.Scope.Zone),
Mockito.anyLong())).thenReturn(manufacturer);
+
Mockito.when(configDepot.getConfigStringValue(Mockito.eq(VirtualMachineManager.VmMetadataProductName.key()),
+ Mockito.eq(ConfigKey.Scope.Zone),
Mockito.anyLong())).thenReturn(product);
ReflectionTestUtils.setField(configKey, "s_depot", configDepot);
updatedConfigKeyDepot = true;
}
diff --git
a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
index c2058ad5644..0e40f8475c1 100644
--- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
@@ -20,7 +20,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
@@ -136,8 +135,8 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
- ClusterDetailsVO vo = findDetail(id, key.key());
+ public String getConfigValue(long id, String key) {
+ ClusterDetailsVO vo = findDetail(id, key);
return vo == null ? null : vo.getValue();
}
diff --git
a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java
b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java
index e36c8ebd6c7..bb03a96d02e 100644
--- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java
@@ -44,8 +44,8 @@ public class DataCenterDetailsDaoImpl extends
ResourceDetailsDaoBase<DataCenterD
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
- ResourceDetail vo = findDetail(id, key.key());
+ public String getConfigValue(long id, String key) {
+ ResourceDetail vo = findDetail(id, key);
return vo == null ? null : vo.getValue();
}
diff --git
a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java
b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java
index 50097d154f5..b9721a2e58c 100644
--- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java
@@ -22,6 +22,11 @@ import java.util.Map;
import javax.inject.Inject;
+import org.apache.cloudstack.framework.config.ConfigKey.Scope;
+import org.apache.cloudstack.framework.config.ScopedConfigStorage;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
+
import com.cloud.domain.DomainDetailVO;
import com.cloud.domain.DomainVO;
import com.cloud.utils.crypt.DBEncryptionUtil;
@@ -31,11 +36,6 @@ import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.TransactionLegacy;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.ConfigKey.Scope;
-import org.apache.cloudstack.framework.config.ScopedConfigStorage;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
public class DomainDetailsDaoImpl extends GenericDaoBase<DomainDetailVO, Long>
implements DomainDetailsDao, ScopedConfigStorage {
protected final SearchBuilder<DomainDetailVO> domainSearch;
@@ -108,17 +108,17 @@ public class DomainDetailsDaoImpl extends
GenericDaoBase<DomainDetailVO, Long> i
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
+ public String getConfigValue(long id, String key) {
DomainDetailVO vo = null;
String enableDomainSettingsForChildDomain =
_configDao.getValue("enable.domain.settings.for.child.domain");
if (!Boolean.parseBoolean(enableDomainSettingsForChildDomain)) {
- vo = findDetail(id, key.key());
+ vo = findDetail(id, key);
return vo == null ? null : getActualValue(vo);
}
DomainVO domain = _domainDao.findById(id);
// if value is not configured in domain then check its parent domain
till ROOT
while (domain != null) {
- vo = findDetail(domain.getId(), key.key());
+ vo = findDetail(domain.getId(), key);
if (vo != null) {
break;
} else if (domain.getParent() != null) {
diff --git
a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java
b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java
index 0c39a8c581a..376933f92e7 100644
---
a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java
+++
b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java
@@ -17,6 +17,10 @@
package com.cloud.storage.dao;
+import java.util.List;
+
+import javax.inject.Inject;
+
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
@@ -26,9 +30,6 @@ import
org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
-import javax.inject.Inject;
-import java.util.List;
-
public class StoragePoolDetailsDaoImpl extends
ResourceDetailsDaoBase<StoragePoolDetailVO> implements StoragePoolDetailsDao,
ScopedConfigStorage {
@Inject
@@ -43,8 +44,8 @@ public class StoragePoolDetailsDaoImpl extends
ResourceDetailsDaoBase<StoragePoo
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
- StoragePoolDetailVO vo = findDetail(id, key.key());
+ public String getConfigValue(long id, String key) {
+ StoragePoolDetailVO vo = findDetail(id, key);
return vo == null ? null : vo.getValue();
}
diff --git
a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java
b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java
index de562e27f9e..510270ad7bf 100644
--- a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java
@@ -23,25 +23,24 @@ import java.util.Optional;
import javax.inject.Inject;
-import com.cloud.utils.crypt.DBEncryptionUtil;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
import org.apache.cloudstack.framework.config.ScopedConfigStorage;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import com.cloud.domain.DomainDetailVO;
import com.cloud.domain.DomainVO;
-import com.cloud.domain.dao.DomainDetailsDao;
import com.cloud.domain.dao.DomainDao;
+import com.cloud.domain.dao.DomainDetailsDao;
import com.cloud.user.dao.AccountDao;
-
+import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.QueryBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.TransactionLegacy;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
public class AccountDetailsDaoImpl extends GenericDaoBase<AccountDetailVO,
Long> implements AccountDetailsDao, ScopedConfigStorage {
protected final SearchBuilder<AccountDetailVO> accountSearch;
@@ -118,9 +117,9 @@ public class AccountDetailsDaoImpl extends
GenericDaoBase<AccountDetailVO, Long>
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
+ public String getConfigValue(long id, String key) {
// check if account level setting is configured
- AccountDetailVO vo = findDetail(id, key.key());
+ AccountDetailVO vo = findDetail(id, key);
String value = vo == null ? null : getActualValue(vo);
if (value != null) {
return value;
@@ -140,7 +139,7 @@ public class AccountDetailsDaoImpl extends
GenericDaoBase<AccountDetailVO, Long>
if (account.isPresent()) {
DomainVO domain =
_domainDao.findById(account.get().getDomainId());
while (domain != null) {
- DomainDetailVO domainVO =
_domainDetailsDao.findDetail(domain.getId(), key.key());
+ DomainDetailVO domainVO =
_domainDetailsDao.findDetail(domain.getId(), key);
if (domainVO != null) {
value = _domainDetailsDao.getActualValue(domainVO);
break;
diff --git
a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java
b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java
index 8e5ce770f45..14830490600 100644
---
a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java
+++
b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java
@@ -20,6 +20,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.ConfigKey.Scope;
+import org.apache.cloudstack.framework.config.ScopedConfigStorage;
+import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
import org.springframework.stereotype.Component;
import com.cloud.utils.crypt.DBEncryptionUtil;
@@ -29,12 +34,6 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.TransactionLegacy;
-import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.ConfigKey.Scope;
-import org.apache.cloudstack.framework.config.ScopedConfigStorage;
-import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
-
@Component
public class ImageStoreDetailsDaoImpl extends
ResourceDetailsDaoBase<ImageStoreDetailVO> implements ImageStoreDetailsDao,
ScopedConfigStorage {
@@ -106,8 +105,8 @@ public class ImageStoreDetailsDaoImpl extends
ResourceDetailsDaoBase<ImageStoreD
}
@Override
- public String getConfigValue(long id, ConfigKey<?> key) {
- ImageStoreDetailVO vo = findDetail(id, key.key());
+ public String getConfigValue(long id, String key) {
+ ImageStoreDetailVO vo = findDetail(id, key);
return vo == null ? null : vo.getValue();
}
diff --git
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepot.java
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepot.java
index b38b30e88b8..5ee5f9dec48 100644
---
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepot.java
+++
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepot.java
@@ -32,4 +32,6 @@ public interface ConfigDepot {
<T> void createOrUpdateConfigObject(String componentName, ConfigKey<T>
key, String value);
boolean isNewConfig(ConfigKey<?> configKey);
+ String getConfigStringValue(String key, ConfigKey.Scope scope, Long
scopeId);
+ void invalidateConfigCache(String key, ConfigKey.Scope scope, Long
scopeId);
}
diff --git
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
index fa570e0e8fb..36a8050754c 100644
---
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
+++
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java
@@ -19,7 +19,6 @@ package org.apache.cloudstack.framework.config;
import java.sql.Date;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
-import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
@@ -215,42 +214,38 @@ public class ConfigKey<T> {
public T value() {
if (_value == null || isDynamic()) {
- ConfigurationVO vo = (s_depot != null && s_depot.global() != null)
? s_depot.global().findById(key()) : null;
- final String value = (vo != null && vo.getValue() != null) ?
vo.getValue() : defaultValue();
- _value = ((value == null) ? (T)defaultValue() : valueOf(value));
+ String value = s_depot != null ?
s_depot.getConfigStringValue(_name, Scope.Global, null) : null;
+ _value = valueOf((value == null) ? defaultValue() : value);
}
return _value;
}
- public T valueIn(Long id) {
+ protected T valueInScope(Scope scope, Long id) {
if (id == null) {
return value();
}
- String value = s_depot != null ?
s_depot.findScopedConfigStorage(this).getConfigValue(id, this) : null;
+ String value = s_depot != null ? s_depot.getConfigStringValue(_name,
scope, id) : null;
if (value == null) {
return value();
- } else {
- return valueOf(value);
}
+ return valueOf(value);
}
- public T valueInDomain(Long domainId) {
- if (domainId == null) {
- return value();
- }
+ public T valueIn(Long id) {
+ return valueInScope(_scope, id);
+ }
- String value = s_depot != null ?
s_depot.getDomainScope(this).getConfigValue(domainId, this) : null;
- if (value == null) {
- return value();
- } else {
- return valueOf(value);
- }
+ public T valueInDomain(Long domainId) {
+ return valueInScope(Scope.Domain, domainId);
}
@SuppressWarnings("unchecked")
protected T valueOf(String value) {
+ if (value == null) {
+ return null;
+ }
Number multiplier = 1;
if (multiplier() != null) {
multiplier = (Number)multiplier();
diff --git
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ScopedConfigStorage.java
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ScopedConfigStorage.java
index f990278b45c..8126b9510a2 100644
---
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ScopedConfigStorage.java
+++
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ScopedConfigStorage.java
@@ -26,5 +26,9 @@ import org.apache.cloudstack.framework.config.ConfigKey.Scope;
public interface ScopedConfigStorage {
Scope getScope();
- String getConfigValue(long id, ConfigKey<?> key);
+ String getConfigValue(long id, String key);
+
+ default String getConfigValue(long id, ConfigKey<?> key) {
+ return getConfigValue(id, key.key());
+ }
}
diff --git
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java
index 6884043cae2..b47370d9205 100644
---
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java
+++
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@@ -37,12 +38,14 @@ import
org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao;
import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
/**
* ConfigDepotImpl implements the ConfigDepot and ConfigDepotAdmin interface.
@@ -73,6 +76,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
*/
public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin {
protected Logger logger = LogManager.getLogger(getClass());
+ protected final static long CONFIG_CACHE_EXPIRE_SECONDS = 30;
@Inject
ConfigurationDao _configDao;
@Inject
@@ -83,12 +87,17 @@ public class ConfigDepotImpl implements ConfigDepot,
ConfigDepotAdmin {
List<ScopedConfigStorage> _scopedStorages;
Set<Configurable> _configured = Collections.synchronizedSet(new
HashSet<Configurable>());
Set<String> newConfigs = Collections.synchronizedSet(new HashSet<>());
+ Cache<String, String> configCache;
private HashMap<String, Pair<String, ConfigKey<?>>> _allKeys = new
HashMap<String, Pair<String, ConfigKey<?>>>(1007);
HashMap<ConfigKey.Scope, Set<ConfigKey<?>>> _scopeLevelConfigsMap = new
HashMap<ConfigKey.Scope, Set<ConfigKey<?>>>();
public ConfigDepotImpl() {
+ configCache = Caffeine.newBuilder()
+ .maximumSize(512)
+ .expireAfterWrite(CONFIG_CACHE_EXPIRE_SECONDS,
TimeUnit.SECONDS)
+ .build();
ConfigKey.init(this);
createEmptyScopeLevelMappings();
}
@@ -268,6 +277,48 @@ public class ConfigDepotImpl implements ConfigDepot,
ConfigDepotAdmin {
return _configDao;
}
+ protected String getConfigStringValueInternal(String cacheKey) {
+ String[] parts = cacheKey.split("-");
+ String key = parts[0];
+ ConfigKey.Scope scope = ConfigKey.Scope.Global;
+ Long scopeId = null;
+ try {
+ scope = ConfigKey.Scope.valueOf(parts[1]);
+ scopeId = Long.valueOf(parts[2]);
+ } catch (IllegalArgumentException ignored) {}
+ if (!ConfigKey.Scope.Global.equals(scope) && scopeId != null) {
+ ScopedConfigStorage scopedConfigStorage = null;
+ for (ScopedConfigStorage storage : _scopedStorages) {
+ if (storage.getScope() == scope) {
+ scopedConfigStorage = storage;
+ }
+ }
+ if (scopedConfigStorage == null) {
+ throw new CloudRuntimeException("Unable to find config storage
for this scope: " + scope + " for " + key);
+ }
+ return scopedConfigStorage.getConfigValue(scopeId, key);
+ }
+ ConfigurationVO configurationVO = _configDao.findById(key);
+ if (configurationVO != null) {
+ return configurationVO.getValue();
+ }
+ return null;
+ }
+
+ private String getConfigCacheKey(String key, ConfigKey.Scope scope, Long
scopeId) {
+ return String.format("%s-%s-%d", key, scope, (scopeId == null ? 0 :
scopeId));
+ }
+
+ @Override
+ public String getConfigStringValue(String key, ConfigKey.Scope scope, Long
scopeId) {
+ return configCache.get(getConfigCacheKey(key, scope, scopeId),
this::getConfigStringValueInternal);
+ }
+
+ @Override
+ public void invalidateConfigCache(String key, ConfigKey.Scope scope, Long
scopeId) {
+ configCache.invalidate(getConfigCacheKey(key, scope, scopeId));
+ }
+
public ScopedConfigStorage findScopedConfigStorage(ConfigKey<?> config) {
for (ScopedConfigStorage storage : _scopedStorages) {
if (storage.getScope() == config.scope()) {
diff --git
a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java
b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java
index 8dd6f71af3c..8a7da795345 100644
---
a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java
+++
b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImplTest.java
@@ -23,12 +23,23 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.junit.Assert;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
+@RunWith(MockitoJUnitRunner.class)
public class ConfigDepotImplTest {
+ @Mock
+ ConfigurationDao _configDao;
+
+ @InjectMocks
private ConfigDepotImpl configDepotImpl = new ConfigDepotImpl();
@Test
@@ -57,4 +68,43 @@ public class ConfigDepotImplTest {
Assert.assertFalse(configDepotImpl.isNewConfig(invalidNewConfig));
}
+ private void runTestGetConfigStringValue(String key, String value) {
+ ConfigurationVO configurationVO = Mockito.mock(ConfigurationVO.class);
+ Mockito.when(configurationVO.getValue()).thenReturn(value);
+ Mockito.when(_configDao.findById(key)).thenReturn(configurationVO);
+ String result = configDepotImpl.getConfigStringValue(key,
ConfigKey.Scope.Global, null);
+ Assert.assertEquals(value, result);
+ }
+
+ @Test
+ public void testGetConfigStringValue() {
+ runTestGetConfigStringValue("test", "value");
+ }
+
+ private void runTestGetConfigStringValueExpiry(long wait, int
configDBRetrieval) {
+ String key = "test1";
+ String value = "expiry";
+ runTestGetConfigStringValue(key, value);
+ try {
+ Thread.sleep(wait);
+ } catch (InterruptedException ie) {
+ Assert.fail(ie.getMessage());
+ }
+ String result = configDepotImpl.getConfigStringValue(key,
ConfigKey.Scope.Global, null);
+ Assert.assertEquals(value, result);
+ Mockito.verify(_configDao,
Mockito.times(configDBRetrieval)).findById(key);
+
+ }
+
+ @Test
+ public void testGetConfigStringValueWithinExpiry() {
+
runTestGetConfigStringValueExpiry((ConfigDepotImpl.CONFIG_CACHE_EXPIRE_SECONDS
* 1000 ) / 4,
+ 1);
+ }
+
+ @Test
+ public void testGetConfigStringValueAfterExpiry() {
+
runTestGetConfigStringValueExpiry(((ConfigDepotImpl.CONFIG_CACHE_EXPIRE_SECONDS)
+ 5) * 1000,
+ 2);
+ }
}
diff --git a/pom.xml b/pom.xml
index 32811897fa1..2ed3f8301cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -187,6 +187,7 @@
<cs.xstream.version>1.4.20</cs.xstream.version>
<org.springframework.version>5.3.26</org.springframework.version>
<cs.ini.version>0.5.4</cs.ini.version>
+ <cs.caffeine.version>3.1.7</cs.caffeine.version>
</properties>
<distributionManagement>
@@ -769,6 +770,11 @@
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
+ <dependency>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ <version>${cs.caffeine.version}</version>
+ </dependency>
</dependencies>
<repositories>
diff --git
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 47be195ad23..25c866d8609 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -45,17 +45,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
-
-import com.cloud.dc.VlanDetailsVO;
-import com.cloud.dc.dao.VlanDetailsDao;
-import com.cloud.hypervisor.HypervisorGuru;
-import com.cloud.network.dao.NsxProviderDao;
-import com.cloud.network.element.NsxProviderVO;
-import com.cloud.utils.crypt.DBEncryptionUtil;
-import com.cloud.host.HostTagVO;
-import com.cloud.storage.StoragePoolTagVO;
-import com.cloud.storage.VolumeApiServiceImpl;
-import com.googlecode.ipv6.IPv6Address;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupService;
@@ -172,6 +161,7 @@ import com.cloud.dc.Pod;
import com.cloud.dc.PodVlanMapVO;
import com.cloud.dc.Vlan;
import com.cloud.dc.Vlan.VlanType;
+import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.AccountVlanMapDao;
import com.cloud.dc.dao.ClusterDao;
@@ -185,6 +175,7 @@ import com.cloud.dc.dao.DomainVlanMapDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.dc.dao.PodVlanMapDao;
import com.cloud.dc.dao.VlanDao;
+import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.dc.dao.VsphereStoragePolicyDao;
import com.cloud.deploy.DataCenterDeployment;
import com.cloud.deploy.DeploymentClusterPlanner;
@@ -203,10 +194,12 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.gpu.GPU;
+import com.cloud.host.HostTagVO;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostTagsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.hypervisor.HypervisorGuru;
import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
import com.cloud.network.IpAddress;
import com.cloud.network.IpAddressManager;
@@ -229,11 +222,13 @@ import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.NsxProviderDao;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao;
import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.dao.UserIpv6AddressDao;
+import com.cloud.network.element.NsxProviderVO;
import com.cloud.network.rules.LoadBalancerContainer.Scheme;
import com.cloud.network.vpc.VpcManager;
import com.cloud.offering.DiskOffering;
@@ -261,7 +256,9 @@ import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ProvisioningType;
import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePoolTagVO;
import com.cloud.storage.Volume;
+import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
@@ -281,6 +278,7 @@ import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.UriUtils;
import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.Filter;
@@ -305,6 +303,7 @@ import com.google.common.base.Enums;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
+import com.googlecode.ipv6.IPv6Address;
import com.googlecode.ipv6.IPv6Network;
import static
com.cloud.configuration.Config.SecStorageAllowedInternalDownloadSites;
@@ -705,7 +704,8 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
value = DBEncryptionUtil.encrypt(value);
}
- switch (ConfigKey.Scope.valueOf(scope)) {
+ ConfigKey.Scope scopeVal = ConfigKey.Scope.valueOf(scope);
+ switch (scopeVal) {
case Zone:
final DataCenterVO zone = _zoneDao.findById(resourceId);
if (zone == null) {
@@ -796,6 +796,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
throw new InvalidParameterValueException("Scope provided is
invalid");
}
+ _configDepot.invalidateConfigCache(name, scopeVal, resourceId);
return valueEncrypted ? DBEncryptionUtil.decrypt(value) : value;
}
@@ -808,6 +809,7 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
logger.error("Failed to update configuration option, name: " +
name + ", value:" + value);
throw new CloudRuntimeException("Failed to update configuration
value. Please contact Cloud Support.");
}
+ _configDepot.invalidateConfigCache(name, ConfigKey.Scope.Global, null);
PreparedStatement pstmt = null;
if (Config.XenServerGuestNetwork.key().equalsIgnoreCase(name)) {
@@ -1095,7 +1097,8 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
}
String newValue = null;
- switch (ConfigKey.Scope.valueOf(scope)) {
+ ConfigKey.Scope scopeVal = ConfigKey.Scope.valueOf(scope);
+ switch (scopeVal) {
case Zone:
final DataCenterVO zone = _zoneDao.findById(id);
if (zone == null) {
@@ -1180,6 +1183,8 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
}
+ _configDepot.invalidateConfigCache(name, scopeVal, id);
+
CallContext.current().setEventDetails(" Name: " + name + " New Value:
" + (name.toLowerCase().contains("password") ? "*****" : defaultValue == null ?
"" : defaultValue));
return new Pair<Configuration, String>(_configDao.findByName(name),
newValue);
}
diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
index 0852c20010b..981649758cb 100644
--- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
+++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java
@@ -16,6 +16,49 @@
// under the License.
package com.cloud.vm;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.framework.config.ConfigDepot;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.ScopedConfigStorage;
+import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao;
+import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao;
+import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.test.utils.SpringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
import com.cloud.capacity.Capacity;
import com.cloud.capacity.CapacityManager;
import com.cloud.capacity.dao.CapacityDao;
@@ -54,48 +97,6 @@ import com.cloud.utils.component.ComponentContext;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
-import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
-import org.apache.cloudstack.framework.config.ConfigDepot;
-import org.apache.cloudstack.framework.config.ConfigKey;
-import org.apache.cloudstack.framework.config.ScopedConfigStorage;
-import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
-import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao;
-import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao;
-import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
-import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
-import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
-import org.apache.cloudstack.test.utils.SpringUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mockito;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.ComponentScan.Filter;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.FilterType;
-import org.springframework.core.type.classreading.MetadataReader;
-import org.springframework.core.type.classreading.MetadataReaderFactory;
-import org.springframework.core.type.filter.TypeFilter;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
-import javax.inject.Inject;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@@ -243,9 +244,8 @@ public class FirstFitPlannerTest {
}
private List<Long> initializeForClusterThresholdDisabled() {
- ConfigurationVO config = mock(ConfigurationVO.class);
- when(config.getValue()).thenReturn(String.valueOf(false));
-
when(configDao.findById(DeploymentClusterPlanner.ClusterThresholdEnabled.key())).thenReturn(config);
+
when(configDepot.getConfigStringValue(DeploymentClusterPlanner.ClusterThresholdEnabled.key(),
+ ConfigKey.Scope.Global,
null)).thenReturn(Boolean.FALSE.toString());
List<Long> clustersCrossingThreshold = new ArrayList<Long>();
clustersCrossingThreshold.add(3L);
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 1bdcc675346..7ebf9e25734 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -3290,6 +3290,7 @@
"message.set.default.nic": "Please confirm that you would like to make this
NIC the default for this Instance.",
"message.set.default.nic.manual": "Please manually update the default NIC on
the Instance now.",
"message.setting.updated": "Setting Updated:",
+"message.setting.update.delay": "The new value will take effect within 30
seconds.",
"message.setup.physical.network.during.zone.creation": "When adding a zone,
you need to set up one or more physical networks. Each physical network can
carry one or more types of traffic, with certain restrictions on how they may
be combined. Add or remove one or more traffic types onto each physical
network.",
"message.setup.physical.network.during.zone.creation.basic": "When adding a
basic zone, you can set up one physical Network, which corresponds to a NIC on
the hypervisor. The Network carries several types of traffic.<br/><br/>You may
also <strong>add</strong> other traffic types onto the physical Network.",
"message.shared.network.offering.warning": "Domain admins and regular Users
can only create shared Networks from Network offering with the setting
specifyvlan=false. Please contact an administrator to create a Network offering
if this list is empty.",
diff --git a/ui/src/components/view/ListView.vue
b/ui/src/components/view/ListView.vue
index 2f94d6436a4..9bef9349fdb 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -746,7 +746,11 @@ export default {
}).then(json => {
this.editableValueKey = null
this.$store.dispatch('RefreshFeatures')
- this.$message.success(`${this.$t('message.setting.updated')}
${record.name}`)
+ var message = `${this.$t('message.setting.updated')} ${record.name}`
+ if (record.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
+ this.$message.success(message)
if (json.updateconfigurationresponse &&
json.updateconfigurationresponse.configuration &&
!json.updateconfigurationresponse.configuration.isdynamic &&
@@ -767,7 +771,10 @@ export default {
api('resetConfiguration', {
name: item.name
}).then(() => {
- const message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ var message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ if (item.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
this.$message.success(message)
}).catch(error => {
console.error(error)
diff --git a/ui/src/components/view/SettingsTab.vue
b/ui/src/components/view/SettingsTab.vue
index cddbe56b83a..f0812c3a8ad 100644
--- a/ui/src/components/view/SettingsTab.vue
+++ b/ui/src/components/view/SettingsTab.vue
@@ -174,7 +174,10 @@ export default {
name: item.name,
value: this.editableValue
}).then(() => {
- const message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.update.to')} ${this.editableValue}`
+ var message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.update.to')} ${this.editableValue}`
+ if (item.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
this.handleSuccessMessage(item.name, this.$route.meta.name, message)
}).catch(error => {
console.error(error)
@@ -204,7 +207,10 @@ export default {
[this.scopeKey]: this.resource.id,
name: item.name
}).then(() => {
- const message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ var message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ if (item.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
this.handleSuccessMessage(item.name, this.$route.meta.name, message)
}).catch(error => {
console.error(error)
diff --git a/ui/src/views/setting/ConfigurationValue.vue
b/ui/src/views/setting/ConfigurationValue.vue
index 24dd0385741..da6bf399fe2 100644
--- a/ui/src/views/setting/ConfigurationValue.vue
+++ b/ui/src/views/setting/ConfigurationValue.vue
@@ -258,7 +258,11 @@ export default {
this.actualValue = this.editableValue
this.$emit('change-config', { value: newValue })
this.$store.dispatch('RefreshFeatures')
- this.$message.success(`${this.$t('message.setting.updated')}
${configrecord.name}`)
+ var message = `${this.$t('message.setting.updated')}
${configrecord.name}`
+ if (configrecord.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
+ this.$message.success(message)
if (json.updateconfigurationresponse &&
json.updateconfigurationresponse.configuration &&
!json.updateconfigurationresponse.configuration.isdynamic &&
@@ -295,7 +299,11 @@ export default {
}
this.$emit('change-config', { value: newValue })
this.$store.dispatch('RefreshFeatures')
- this.$message.success(`${this.$t('label.setting')}
${configrecord.name} ${this.$t('label.reset.config.value')}`)
+ var message = `${this.$t('label.setting')} ${configrecord.name}
${this.$t('label.reset.config.value')}`
+ if (configrecord.isdynamic) {
+ message += `. ${this.$t('message.setting.update.delay')}`
+ }
+ this.$message.success(message)
if (json.resetconfigurationresponse &&
json.resetconfigurationresponse.configuration &&
!json.resetconfigurationresponse.configuration.isdynamic &&