This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch 4.16
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.16 by this push:
new 2bd1dc1 Enable resetting config values to default value (#4230)
2bd1dc1 is described below
commit 2bd1dc1e149777ac42fb077bffb64bfa56c68ac4
Author: Rakesh <[email protected]>
AuthorDate: Mon Jan 3 21:55:30 2022 +0100
Enable resetting config values to default value (#4230)
* Enable resetting config values to default value
Provide reset button to zone,cluster,domain,account,
primary and secondary storage so that config values
can be reset to default value
* fix ui issue
* Update test/integration/smoke/test_reset_configuration_settings.py
* Update test/integration/smoke/test_reset_configuration_settings.py
Co-authored-by: Rakesh Venkatesh <[email protected]>
Co-authored-by: dahn <[email protected]>
---
.../cloud/configuration/ConfigurationService.java | 10 +
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../api/command/admin/config/ResetCfgCmd.java | 166 ++++++++++
.../java/com/cloud/dc/ClusterDetailsDaoImpl.java | 28 +-
.../configuration/ConfigurationManagerImpl.java | 204 +++++++++++-
.../com/cloud/server/ManagementServerImpl.java | 2 +
.../cloud/vpc/MockConfigurationManagerImpl.java | 7 +
.../smoke/test_reset_configuration_settings.py | 367 +++++++++++++++++++++
tools/marvin/marvin/lib/base.py | 20 ++
ui/public/locales/en.json | 2 +
ui/src/components/view/ListView.vue | 23 ++
ui/src/components/view/SettingsTab.vue | 28 ++
12 files changed, 849 insertions(+), 9 deletions(-)
diff --git
a/api/src/main/java/com/cloud/configuration/ConfigurationService.java
b/api/src/main/java/com/cloud/configuration/ConfigurationService.java
index 38d9d32..87d2d2f 100644
--- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java
+++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java
@@ -18,6 +18,7 @@ package com.cloud.configuration;
import java.util.List;
+import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
@@ -77,6 +78,15 @@ public interface ConfigurationService {
Configuration updateConfiguration(UpdateCfgCmd cmd) throws
InvalidParameterValueException;
/**
+ * Resets a configuration entry with default value
+ *
+ * @param cmd
+ * - the command wrapping name parameter
+ * @return updated configuration object if successful
+ */
+ Pair<Configuration, String> resetConfiguration(ResetCfgCmd cmd) throws
InvalidParameterValueException;
+
+ /**
* Create a service offering through the API
*
* @param cmd
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index b19819c..b9a809f 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -714,6 +714,7 @@ public class ApiConstants {
public static final String VM_SNAPSHOT_MEMORY = "snapshotmemory";
public static final String VM_SNAPSHOT_QUIESCEVM = "quiescevm";
public static final String IMAGE_STORE_UUID = "imagestoreuuid";
+ public static final String IMAGE_STORE_ID = "imagestoreid";
public static final String GUEST_VM_CIDR = "guestvmcidr";
public static final String NETWORK_CIDR = "networkcidr";
public static final String RESERVED_IP_RANGE = "reservediprange";
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java
new file mode 100644
index 0000000..28a69ce
--- /dev/null
+++
b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java
@@ -0,0 +1,166 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command.admin.config;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.ImageStoreResponse;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.api.response.AccountResponse;
+import org.apache.cloudstack.api.response.ClusterResponse;
+import org.apache.cloudstack.api.response.ConfigurationResponse;
+import org.apache.cloudstack.api.response.DomainResponse;
+import org.apache.cloudstack.api.response.StoragePoolResponse;
+import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.config.Configuration;
+
+import com.cloud.user.Account;
+import com.cloud.utils.Pair;
+
+@APICommand(name = "resetConfiguration", description = "Resets a
configuration. The configuration will be set to default value for global
setting, and removed from account_details or domain_details for Account/Domain
settings", responseObject = ConfigurationResponse.class,
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
since = "4.16.0")
+public class ResetCfgCmd extends BaseCmd {
+ public static final Logger s_logger =
Logger.getLogger(ResetCfgCmd.class.getName());
+ private static final String s_name = "resetconfigurationresponse";
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required =
true, description = "the name of the configuration", validations =
{ApiArgValidator.NotNullOrEmpty})
+ private String cfgName;
+
+ @Parameter(name = ApiConstants.ZONE_ID,
+ type = CommandType.UUID,
+ entityType = ZoneResponse.class,
+ description = "the ID of the Zone to reset the parameter value
for corresponding zone")
+ private Long zoneId;
+
+ @Parameter(name = ApiConstants.CLUSTER_ID,
+ type = CommandType.UUID,
+ entityType = ClusterResponse.class,
+ description = "the ID of the Cluster to reset the parameter
value for corresponding cluster")
+ private Long clusterId;
+
+ @Parameter(name = ApiConstants.STORAGE_ID,
+ type = CommandType.UUID,
+ entityType = StoragePoolResponse.class,
+ description = "the ID of the Storage pool to reset the
parameter value for corresponding storage pool")
+ private Long storagePoolId;
+
+ @Parameter(name = ApiConstants.DOMAIN_ID,
+ type = CommandType.UUID,
+ entityType = DomainResponse.class,
+ description = "the ID of the Domain to reset the parameter value
for corresponding domain")
+ private Long domainId;
+
+ @Parameter(name = ApiConstants.ACCOUNT_ID,
+ type = CommandType.UUID,
+ entityType = AccountResponse.class,
+ description = "the ID of the Account to reset the parameter
value for corresponding account")
+ private Long accountId;
+
+ @Parameter(name = ApiConstants.IMAGE_STORE_ID,
+ type = CommandType.UUID,
+ entityType = ImageStoreResponse.class,
+ description = "the ID of the Image Store to reset the parameter
value for corresponding image store")
+ private Long imageStoreId;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public String getCfgName() {
+ return cfgName;
+ }
+
+ public Long getZoneId() {
+ return zoneId;
+ }
+
+ public Long getClusterId() {
+ return clusterId;
+ }
+
+ public Long getStoragepoolId() {
+ return storagePoolId;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public Long getImageStoreId() {
+ return imageStoreId;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_ID_SYSTEM;
+ }
+
+ @Override
+ public void execute() {
+ Pair<Configuration, String> cfg =
_configService.resetConfiguration(this);
+ if (cfg != null) {
+ ConfigurationResponse response =
_responseGenerator.createConfigurationResponse(cfg.first());
+ response.setResponseName(getCommandName());
+ if (getZoneId() != null) {
+ response.setScope(ConfigKey.Scope.Zone.name());
+ }
+ if (getClusterId() != null) {
+ response.setScope(ConfigKey.Scope.Cluster.name());
+ }
+ if (getStoragepoolId() != null) {
+ response.setScope(ConfigKey.Scope.StoragePool.name());
+ }
+ if (getDomainId() != null) {
+ response.setScope(ConfigKey.Scope.Domain.name());
+ }
+ if (getAccountId() != null) {
+ response.setScope(ConfigKey.Scope.Account.name());
+ }
+ if (getImageStoreId() != null) {
+ response.setScope(ConfigKey.Scope.ImageStore.name());
+ }
+ response.setValue(cfg.second());
+ this.setResponseObject(response);
+ } else {
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed
to reset config");
+ }
+ }
+}
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 a932322..c2058ad 100644
--- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java
@@ -35,6 +35,11 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
protected final SearchBuilder<ClusterDetailsVO> ClusterSearch;
protected final SearchBuilder<ClusterDetailsVO> DetailSearch;
+ private final String CpuOverprovisioningFactor =
"cpu.overprovisioning.factor";
+ private final String MemoryOverprovisioningFactor =
"mem.overprovisioning.factor";
+ private final String CpuOverCommitRatio = "cpuOvercommitRatio";
+ private final String MemoryOverCommitRatio = "memoryOvercommitRatio";
+
protected ClusterDetailsDaoImpl() {
ClusterSearch = createSearchBuilder();
ClusterSearch.and("clusterId", ClusterSearch.entity().getClusterId(),
SearchCriteria.Op.EQ);
@@ -50,12 +55,7 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
public ClusterDetailsVO findDetail(long clusterId, String name) {
SearchCriteria<ClusterDetailsVO> sc = DetailSearch.create();
// This is temporary fix to support list/update configuration api for
cpu and memory overprovisioning ratios
- if (name.equalsIgnoreCase("cpu.overprovisioning.factor")) {
- name = "cpuOvercommitRatio";
- }
- if (name.equalsIgnoreCase("mem.overprovisioning.factor")) {
- name = "memoryOvercommitRatio";
- }
+ name = getCpuMemoryOvercommitRatio(name);
sc.setParameters("clusterId", clusterId);
sc.setParameters("name", name);
@@ -103,11 +103,13 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
expunge(sc);
for (Map.Entry<String, String> detail : details.entrySet()) {
+ String name = detail.getKey();
+ name = getCpuMemoryOvercommitRatio(name);
String value = detail.getValue();
if ("password".equals(detail.getKey())) {
value = DBEncryptionUtil.encrypt(value);
}
- ClusterDetailsVO vo = new ClusterDetailsVO(clusterId,
detail.getKey(), value);
+ ClusterDetailsVO vo = new ClusterDetailsVO(clusterId, name, value);
persist(vo);
}
txn.commit();
@@ -115,6 +117,7 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
@Override
public void persist(long clusterId, String name, String value) {
+ name = getCpuMemoryOvercommitRatio(name);
TransactionLegacy txn = TransactionLegacy.currentTxn();
txn.start();
SearchCriteria<ClusterDetailsVO> sc = DetailSearch.create();
@@ -147,4 +150,15 @@ public class ClusterDetailsDaoImpl extends
GenericDaoBase<ClusterDetailsVO, Long
dcName = tokens[3];
return dcName;
}
+
+ private String getCpuMemoryOvercommitRatio(String name) {
+ if (name.equalsIgnoreCase(CpuOverprovisioningFactor)) {
+ name = CpuOverCommitRatio;
+ }
+ if (name.equalsIgnoreCase(MemoryOverprovisioningFactor)) {
+ name = MemoryOverCommitRatio;
+ }
+
+ return name;
+ }
}
diff --git
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 1380621..1bcc3d9 100755
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -30,10 +30,12 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
@@ -51,6 +53,7 @@ import
org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
@@ -105,6 +108,7 @@ import org.apache.cloudstack.region.dao.RegionDao;
import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailVO;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
@@ -630,9 +634,16 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
if (cluster == null) {
throw new InvalidParameterValueException("unable to find
cluster by id " + resourceId);
}
- ClusterDetailsVO clusterDetailsVO =
_clusterDetailsDao.findDetail(resourceId, name);
+ String newName = name;
+ if (name.equalsIgnoreCase("cpu.overprovisioning.factor")) {
+ newName = "cpuOvercommitRatio";
+ }
+ if (name.equalsIgnoreCase("mem.overprovisioning.factor")) {
+ newName = "memoryOvercommitRatio";
+ }
+ ClusterDetailsVO clusterDetailsVO =
_clusterDetailsDao.findDetail(resourceId, newName);
if (clusterDetailsVO == null) {
- clusterDetailsVO = new ClusterDetailsVO(resourceId, name,
value);
+ clusterDetailsVO = new ClusterDetailsVO(resourceId,
newName, value);
_clusterDetailsDao.persist(clusterDetailsVO);
} else {
clusterDetailsVO.setValue(value);
@@ -879,6 +890,159 @@ public class ConfigurationManagerImpl extends ManagerBase
implements Configurati
}
}
+ private ParamCountPair getParamCount(Map<String, Long> scopeMap) {
+ Long id = null;
+ int paramCount = 0;
+ String scope = ConfigKey.Scope.Global.toString();
+
+ for (var entry : scopeMap.entrySet()) {
+ if (entry.getValue() != null) {
+ id = entry.getValue();
+ scope = entry.getKey();
+ paramCount++;
+ }
+ }
+
+ return new ParamCountPair(id, paramCount, scope);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_CONFIGURATION_VALUE_EDIT,
eventDescription = "resetting configuration")
+ public Pair<Configuration, String> resetConfiguration(final ResetCfgCmd
cmd) throws InvalidParameterValueException {
+ final Long userId = CallContext.current().getCallingUserId();
+ final String name = cmd.getCfgName();
+ final Long zoneId = cmd.getZoneId();
+ final Long clusterId = cmd.getClusterId();
+ final Long storagepoolId = cmd.getStoragepoolId();
+ final Long accountId = cmd.getAccountId();
+ final Long domainId = cmd.getDomainId();
+ final Long imageStoreId = cmd.getImageStoreId();
+ Optional optionalValue;
+ final ConfigKey<?> configKey = _configDepot.get(name);
+ if (configKey == null) {
+ s_logger.warn("Probably the component manager where configuration
variable " + name + " is defined needs to implement Configurable interface");
+ throw new InvalidParameterValueException("Config parameter with
name " + name + " doesn't exist");
+ }
+ String defaultValue = configKey.defaultValue();
+ String category = configKey.category();
+ String configScope = configKey.scope().toString();
+
+ String scope = "";
+ Map<String, Long> scopeMap = new LinkedHashMap<>();
+
+ Long id = null;
+ int paramCountCheck = 0;
+
+ scopeMap.put(ConfigKey.Scope.Zone.toString(), zoneId);
+ scopeMap.put(ConfigKey.Scope.Cluster.toString(), clusterId);
+ scopeMap.put(ConfigKey.Scope.Domain.toString(), domainId);
+ scopeMap.put(ConfigKey.Scope.Account.toString(), accountId);
+ scopeMap.put(ConfigKey.Scope.StoragePool.toString(), storagepoolId);
+ scopeMap.put(ConfigKey.Scope.ImageStore.toString(), imageStoreId);
+
+ ParamCountPair paramCountPair = getParamCount(scopeMap);
+ id = paramCountPair.getId();
+ paramCountCheck = paramCountPair.getParamCount();
+ scope = paramCountPair.getScope();
+
+ if (paramCountCheck > 1) {
+ throw new InvalidParameterValueException("cannot handle multiple
IDs, provide only one ID corresponding to the scope");
+ }
+
+ if (scope != null && !scope.equals(ConfigKey.Scope.Global.toString())
&& !configScope.contains(scope)) {
+ throw new InvalidParameterValueException("Invalid scope id
provided for the parameter " + name);
+ }
+
+ String newValue = null;
+ switch (ConfigKey.Scope.valueOf(scope)) {
+ case Zone:
+ final DataCenterVO zone = _zoneDao.findById(id);
+ if (zone == null) {
+ throw new InvalidParameterValueException("unable to find
zone by id " + id);
+ }
+ _dcDetailsDao.removeDetail(id, name);
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ case Cluster:
+ final ClusterVO cluster = _clusterDao.findById(id);
+ if (cluster == null) {
+ throw new InvalidParameterValueException("unable to find
cluster by id " + id);
+ }
+ ClusterDetailsVO clusterDetailsVO =
_clusterDetailsDao.findDetail(id, name);
+ newValue = configKey.value().toString();
+ if (name.equalsIgnoreCase("cpu.overprovisioning.factor") ||
name.equalsIgnoreCase("mem.overprovisioning.factor")) {
+ _clusterDetailsDao.persist(id, name, newValue);
+ } else if (clusterDetailsVO != null) {
+ _clusterDetailsDao.remove(clusterDetailsVO.getId());
+ }
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ case StoragePool:
+ final StoragePoolVO pool = _storagePoolDao.findById(id);
+ if (pool == null) {
+ throw new InvalidParameterValueException("unable to find
storage pool by id " + id);
+ }
+ _storagePoolDetailsDao.removeDetail(id, name);
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ case Domain:
+ final DomainVO domain = _domainDao.findById(id);
+ if (domain == null) {
+ throw new InvalidParameterValueException("unable to find
domain by id " + id);
+ }
+ DomainDetailVO domainDetailVO =
_domainDetailsDao.findDetail(id, name);
+ if (domainDetailVO != null) {
+ _domainDetailsDao.remove(domainDetailVO.getId());
+ }
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ case Account:
+ final AccountVO account = _accountDao.findById(id);
+ if (account == null) {
+ throw new InvalidParameterValueException("unable to find
account by id " + id);
+ }
+ AccountDetailVO accountDetailVO =
_accountDetailsDao.findDetail(id, name);
+ if (accountDetailVO != null) {
+ _accountDetailsDao.remove(accountDetailVO.getId());
+ }
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ case ImageStore:
+ final ImageStoreVO imageStoreVO = _imageStoreDao.findById(id);
+ if (imageStoreVO == null) {
+ throw new InvalidParameterValueException("unable to find
the image store by id " + id);
+ }
+ ImageStoreDetailVO imageStoreDetailVO =
_imageStoreDetailsDao.findDetail(id, name);
+ if (imageStoreDetailVO != null) {
+ _imageStoreDetailsDao.remove(imageStoreDetailVO.getId());
+ }
+ optionalValue = Optional.ofNullable(configKey.valueIn(id));
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ break;
+
+ default:
+ if (!_configDao.update(name, category, defaultValue)) {
+ s_logger.error("Failed to reset configuration option,
name: " + name + ", defaultValue:" + defaultValue);
+ throw new CloudRuntimeException("Failed to reset
configuration value. Please contact Cloud Support.");
+ }
+ optionalValue = Optional.ofNullable(configKey.value());
+ newValue = optionalValue.isPresent() ?
optionalValue.get().toString() : defaultValue;
+ }
+
+ CallContext.current().setEventDetails(" Name: " + name + " New Value:
" + (name.toLowerCase().contains("password") ? "*****" : defaultValue == null ?
"" : defaultValue));
+ return new Pair<Configuration, String>(_configDao.findByName(name),
newValue);
+ }
+
private String validateConfigurationValue(final String name, String value,
final String scope) {
final ConfigurationVO cfg = _configDao.findByName(name);
@@ -7056,4 +7220,40 @@ public class ConfigurationManagerImpl extends
ManagerBase implements Configurati
ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN,
ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN
};
}
+
+ static class ParamCountPair {
+ private Long id;
+ private int paramCount;
+ private String scope;
+
+ public ParamCountPair(Long id, int paramCount, String scope) {
+ this.id = id;
+ this.paramCount = paramCount;
+ this.scope = scope;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public int getParamCount() {
+ return paramCount;
+ }
+
+ public void setParamCount(int paramCount) {
+ this.paramCount = paramCount;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+ }
}
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 2b79ff0..790a570 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -74,6 +74,7 @@ import
org.apache.cloudstack.api.command.admin.cluster.UpdateClusterCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import
org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd;
import
org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd;
+import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import
org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
import
org.apache.cloudstack.api.command.admin.direct.download.RevokeTemplateDirectDownloadCertificateCmd;
@@ -3009,6 +3010,7 @@ public class ManagementServerImpl extends ManagerBase
implements ManagementServe
cmdList.add(ListHypervisorCapabilitiesCmd.class);
cmdList.add(UpdateCfgCmd.class);
cmdList.add(UpdateHypervisorCapabilitiesCmd.class);
+ cmdList.add(ResetCfgCmd.class);
cmdList.add(CreateDomainCmd.class);
cmdList.add(DeleteDomainCmd.class);
cmdList.add(ListDomainChildrenCmd.class);
diff --git
a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
index 112d20b..81831e3 100644
--- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
@@ -23,6 +23,7 @@ import java.util.Set;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
+import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import
org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
@@ -615,4 +616,10 @@ public class MockConfigurationManagerImpl extends
ManagerBase implements Configu
return null;
}
+ @Override
+ public Pair<Configuration, String> resetConfiguration(ResetCfgCmd cmd)
throws InvalidParameterValueException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
}
diff --git a/test/integration/smoke/test_reset_configuration_settings.py
b/test/integration/smoke/test_reset_configuration_settings.py
new file mode 100644
index 0000000..edec6e4
--- /dev/null
+++ b/test/integration/smoke/test_reset_configuration_settings.py
@@ -0,0 +1,367 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Import Local Modules
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase, unittest
+from marvin.lib.utils import (validateList,
+ cleanup_resources)
+from marvin.lib.base import (Account,
+ Configurations,
+ Domain,
+ Cluster,
+ StoragePool)
+from marvin.lib.common import (get_domain,
+ get_zone)
+
+class TestRestConfigurationSettings(cloudstackTestCase):
+ @classmethod
+ def setUpClass(self):
+ self.testClient = super(
+ TestRestConfigurationSettings,
+ self).getClsTestClient()
+ self.apiclient = self.testClient.getApiClient()
+ self.testdata = self.testClient.getParsedTestDataConfig()
+
+ # Get Zone, Domain
+ self.domain = get_domain(self.apiclient)
+ self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())
+ self._cleanup = []
+ return
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestRestConfigurationSettings, cls).tearDownClass()
+ return
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+
+ return
+
+ def tearDown(self):
+ super(TestRestConfigurationSettings, self).tearDown()
+ return
+
+ @attr(tags=["advanced", "advancedsg"], required_hardware="false")
+ def test_01_test_settings_for_domain(self):
+ """
+ 1. Get the default value for the setting in domain scope
+ 2. Change the default value to new value
+ 3. Make sure updated value is same as new value
+ 4. Reset the config value
+ 5. Make sure that current value is same as default value
+ :return:
+ """
+ config_name="ldap.basedn"
+ #1. Get default value
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name
+ )
+ self.assertIsNotNone(configs, "Fail to get domain setting %s " %
config_name)
+
+ orig_value = str(configs[0].value)
+ new_value = "testing"
+
+ #2. Update to new value
+ Configurations.update(
+ self.apiclient,
+ name=config_name,
+ value=new_value,
+ domainid=self.domain.id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ domainid=self.domain.id
+ )
+ self.assertIsNotNone(configs, "Fail to get domain setting %s " %
config_name)
+
+ #3. validate they are same
+ self.assertEqual(new_value,
+ str(configs[0].value),
+ "Failed to set new config value")
+
+ #4. Reset the value
+ Configurations.reset(
+ self.apiclient,
+ name=config_name,
+ domainid=self.domain.id
+ )
+
+ #5. Make sure its same as original value
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ domainid=self.domain.id
+ )
+ self.assertIsNotNone(configs, "Fail to get domain setting %s " %
config_name)
+
+ self.assertEqual(orig_value,
+ str(configs[0].value),
+ "Failed to reset the value")
+
+ @attr(tags=["advanced", "advancedsg"], required_hardware="false")
+ def test_02_test_settings_for_account(self):
+ """
+ 1. Get the default value for the setting in account scope
+ 2. Change the default value to new value
+ 3. Make sure updated value is same as new value
+ 4. Reset the config value
+ 5. Make sure that current value is same as default value
+ :return:
+ """
+ accounts = Account.list(
+ self.apiclient,
+ domainid=self.domain.id,
+ listall=True
+ )
+
+ self.assertIsNotNone(accounts[0],
+ "There should be atleast 1 account in the zone")
+
+ config_name = "enable.additional.vm.configuration"
+ #1. Get the default value
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name
+ )
+ self.assertIsNotNone(configs, "Fail to get account setting %s " %
config_name)
+
+ orig_value = str(configs[0].value)
+ new_value = "true"
+
+ Configurations.update(
+ self.apiclient,
+ name=config_name,
+ value=new_value,
+ accountid=accounts[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ accountid=accounts[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get account setting %s " %
config_name)
+
+ self.assertEqual(new_value,
+ str(configs[0].value),
+ "Failed to set new config value")
+
+ Configurations.reset(
+ self.apiclient,
+ name=config_name,
+ accountid=accounts[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ accountid=accounts[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get account setting %s " %
config_name)
+
+ self.assertEqual(orig_value,
+ str(configs[0].value),
+ "Failed to reset the value")
+
+ @attr(tags=["advanced", "advancedsg"], required_hardware="false")
+ def test_03_test_settings_for_cluster(self):
+ """
+ 1. Get the default value for the setting in cluster scope
+ 2. Change the default value to new value
+ 3. Make sure updated value is same as new value
+ 4. Reset the config value
+ 5. Make sure that current value is same as default value
+ :return:
+ """
+ cluster = Cluster.list(
+ self.apiclient
+ )
+
+ self.assertIsNotNone(cluster[0],
+ "There should be atleast 1 cluster in the zone")
+
+ config_name = "cluster.storage.operations.exclude"
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ clusterid=cluster[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get cluster setting %s " %
config_name)
+
+ orig_value = str(configs[0].value)
+ new_value = "true"
+
+ Configurations.update(
+ self.apiclient,
+ name=config_name,
+ value=new_value,
+ clusterid=cluster[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ clusterid=cluster[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get cluster setting %s " %
config_name)
+
+ self.assertEqual(new_value,
+ str(configs[0].value),
+ "Failed to set new config value")
+
+ Configurations.reset(
+ self.apiclient,
+ name=config_name,
+ clusterid=cluster[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ clusterid=cluster[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get cluster setting %s " %
config_name)
+
+ self.assertEqual(orig_value,
+ str(configs[0].value),
+ "Failed to reset the value")
+
+ @attr(tags=["advanced", "advancedsg"], required_hardware="false")
+ def test_04_test_settings_for_storage(self):
+ """
+ 1. Get the default value for the setting in storage scope
+ 2. Change the default value to new value
+ 3. Make sure updated value is same as new value
+ 4. Reset the config value
+ 5. Make sure that current value is same as default value
+ :return:
+ """
+ storage = StoragePool.list(
+ self.apiclient
+ )
+
+ self.assertIsNotNone(storage[0],
+ "There should be atleast 1 primary storage pool
in the zone")
+
+ config_name = "vmware.create.full.clone"
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ storageid=storage[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get storage pool setting %s " %
config_name)
+
+ orig_value = str(configs[0].value)
+ new_value = 'false'
+
+ Configurations.update(
+ self.apiclient,
+ name=config_name,
+ value=new_value,
+ storageid=storage[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ storageid=storage[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get storage pool setting %s " %
config_name)
+
+ self.assertEqual(new_value,
+ (configs[0].value),
+ "Failed to set new config value")
+
+ Configurations.reset(
+ self.apiclient,
+ name=config_name,
+ storageid=storage[0].id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ storage=storage[0].id
+ )
+ self.assertIsNotNone(configs, "Fail to get storage pool setting %s " %
config_name)
+
+ self.assertEqual(orig_value,
+ (configs[0].value),
+ "Failed to reset the value for storage pool")
+
+ @attr(tags=["advanced", "advancedsg"], required_hardware="false")
+ def test_05_test_settings_for_zone(self):
+ """
+ 1. Get the default value for the setting in zone scope
+ 2. Change the default value to new value
+ 3. Make sure updated value is same as new value
+ 4. Reset the config value
+ 5. Make sure that current value is same as default value
+ :return:
+ """
+ config_name = "enable.dynamic.scale.vm"
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ zoneid=self.zone.id
+ )
+ self.assertIsNotNone(configs, "Fail to get zone setting %s " %
config_name)
+
+ orig_value = str(configs[0].value)
+ new_value = 'true'
+
+ Configurations.update(
+ self.apiclient,
+ name=config_name,
+ value=new_value,
+ zoneid=self.zone.id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ zoneid=self.zone.id
+ )
+ self.assertIsNotNone(configs, "Fail to get ol setting %s " %
config_name)
+
+ self.assertEqual(new_value,
+ (configs[0].value),
+ "Failed to set new config value")
+
+ Configurations.reset(
+ self.apiclient,
+ name=config_name,
+ zoneid=self.zone.id
+ )
+
+ configs = Configurations.list(
+ self.apiclient,
+ name=config_name,
+ zoneid=self.zone.id
+ )
+ self.assertIsNotNone(configs, "Fail to get zone setting %s " %
config_name)
+
+ self.assertEqual(orig_value,
+ (configs[0].value),
+ "Failed to reset the value for zone")
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index b8c1138..910e8bd 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -4273,6 +4273,26 @@ class Configurations:
return (apiclient.listCapabilities(cmd))
+ @classmethod
+ def reset(cls, apiclient, name, zoneid=None, clusterid=None,
storageid=None, domainid=None, accountid=None):
+ """Resets the specified configuration to original value"""
+
+ cmd = resetConfiguration.resetConfigurationCmd()
+ cmd.name = name
+
+ if zoneid:
+ cmd.zoneid = zoneid
+ if clusterid:
+ cmd.clusterid = clusterid
+ if storageid:
+ cmd.storageid = storageid
+ if domainid:
+ cmd.domainid = domainid
+ if accountid:
+ cmd.accountid = accountid
+
+ apiclient.resetConfiguration(cmd)
+
class NetScaler:
"""Manage external netscaler device"""
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 135630e..9dbbd85 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1863,6 +1863,7 @@
"label.reservedsystemnetmask": "Reserved system netmask",
"label.reservedsystemstartip": "Start Reserved system IP",
"label.reset": "Reset",
+"label.reset.config.value": "Reset to default value",
"label.reset.ssh.key.pair": "Reset SSH Key Pair",
"label.reset.ssh.key.pair.on.vm": "Reset SSH Key Pair on VM",
"label.reset.to.default": "Reset to default",
@@ -2943,6 +2944,7 @@
"message.error.rados.user": "Please enter RADOS User",
"message.error.remove.nic": "There was an error",
"message.error.remove.secondary.ipaddress": "There was an error removing the
secondary IP Address",
+"message.error.reset.config": "Unable to reset config to default value",
"message.error.required.input": "Please enter input",
"message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes cluster
config",
"message.error.s3nfs.path": "Please enter S3 NFS Path",
diff --git a/ui/src/components/view/ListView.vue
b/ui/src/components/view/ListView.vue
index 7ea3e39..2665035 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -348,6 +348,12 @@
v-if="editableValueKey === record.key"
iconType="check-circle"
iconTwoToneColor="#52c41a" />
+ <tooltip-button
+ :tooltip="$t('label.reset.config.value')"
+ @click="resetConfig(record)"
+ v-if="editableValueKey !== record.key"
+ icon="reload"
+ :disabled="!('updateConfiguration' in $store.getters.apis)" />
</template>
<template slot="tariffActions" slot-scope="text, record">
<tooltip-button
@@ -526,6 +532,23 @@ export default {
this.$emit('refresh')
})
},
+ resetConfig (item) {
+ api('resetConfiguration', {
+ name: item.name
+ }).then(() => {
+ const message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ this.$message.success(message)
+ }).catch(error => {
+ console.error(error)
+ this.$message.error(this.$t('message.error.reset.config'))
+ this.$notification.error({
+ message: this.$t('label.error'),
+ description: this.$t('message.error.reset.config')
+ })
+ }).finally(() => {
+ this.$emit('refresh')
+ })
+ },
editValue (record) {
this.editableValueKey = record.key
this.editableValue = record.value
diff --git a/ui/src/components/view/SettingsTab.vue
b/ui/src/components/view/SettingsTab.vue
index 349d516..36bf11d 100644
--- a/ui/src/components/view/SettingsTab.vue
+++ b/ui/src/components/view/SettingsTab.vue
@@ -64,6 +64,12 @@
v-if="editableValueKey === index"
iconType="check-circle"
iconTwoToneColor="#52c41a" />
+ <tooltip-button
+ :tooltip="$t('label.reset.config.value')"
+ @click="resetConfig(item)"
+ v-if="editableValueKey !== index"
+ icon="reload"
+ :disabled="!('updateConfiguration' in $store.getters.apis)" />
</div>
</a-list-item>
</a-list>
@@ -182,6 +188,28 @@ export default {
handleSearch (value) {
this.filter = value
this.fetchData()
+ },
+ resetConfig (item) {
+ this.tabLoading = true
+ api('resetConfiguration', {
+ [this.scopeKey]: this.resource.id,
+ name: item.name
+ }).then(() => {
+ const message = `${this.$t('label.setting')} ${item.name}
${this.$t('label.reset.config.value')}`
+ this.$message.success(message)
+ }).catch(error => {
+ console.error(error)
+ this.$message.error(this.$t('message.error.reset.config'))
+ this.$notification.error({
+ message: this.$t('label.error'),
+ description: this.$t('message.error.reset.config')
+ })
+ }).finally(() => {
+ this.tabLoading = false
+ this.fetchData(() => {
+ this.editableValueKey = null
+ })
+ })
}
}
}