Skip to content

Commit f156c4e

Browse files
winterhazelhsato03
andauthored
Refactor type and range validation in configuration update process (#9107)
* Refactor type and range validation in configuration update process * Update server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java Co-authored-by: Henrique Sato <henriquesato2003@gmail.com> * Remove duplicate imports * Fix tests * Address Joao's reviews --------- Co-authored-by: Henrique Sato <henriquesato2003@gmail.com>
1 parent b11897c commit f156c4e

File tree

2 files changed

+402
-150
lines changed

2 files changed

+402
-150
lines changed

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

Lines changed: 140 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
477477

478478
private long _defaultPageSize = Long.parseLong(Config.DefaultPageSize.getDefaultValue());
479479
private static final String DOMAIN_NAME_PATTERN = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{1,63}$";
480-
private Set<String> configValuesForValidation = new HashSet<String>();
481-
private Set<String> weightBasedParametersForValidation = new HashSet<String>();
482-
private Set<String> overprovisioningFactorsForValidation = new HashSet<String>();
480+
private Set<String> configValuesForValidation = new HashSet<>();
481+
private Set<String> weightBasedParametersForValidation = new HashSet<>();
482+
private Set<String> overprovisioningFactorsForValidation = new HashSet<>();
483483

484484
public static final ConfigKey<Boolean> SystemVMUseLocalStorage = new ConfigKey<Boolean>(Boolean.class, "system.vm.use.local.storage", "Advanced", "false",
485485
"Indicates whether to use local storage pools or shared storage pools for system VMs.", false, ConfigKey.Scope.Zone, null);
@@ -577,7 +577,7 @@ protected void populateConfigValuesForValidationSet() {
577577
configValuesForValidation.add(UnmanagedVMsManager.ConvertVmwareInstanceToKvmTimeout.key());
578578
}
579579

580-
private void weightBasedParametersForValidation() {
580+
protected void weightBasedParametersForValidation() {
581581
weightBasedParametersForValidation.add(AlertManager.CPUCapacityThreshold.key());
582582
weightBasedParametersForValidation.add(AlertManager.StorageAllocatedCapacityThreshold.key());
583583
weightBasedParametersForValidation.add(AlertManager.StorageCapacityThreshold.key());
@@ -599,7 +599,7 @@ private void weightBasedParametersForValidation() {
599599
weightBasedParametersForValidation.add(ClusterDrsService.ClusterDrsImbalanceSkipThreshold.key());
600600
}
601601

602-
private void overProvisioningFactorsForValidation() {
602+
protected void overProvisioningFactorsForValidation() {
603603
overprovisioningFactorsForValidation.add(CapacityManager.MemOverprovisioningFactor.key());
604604
overprovisioningFactorsForValidation.add(CapacityManager.CpuOverprovisioningFactor.key());
605605
overprovisioningFactorsForValidation.add(CapacityManager.StorageOverprovisioningFactor.key());
@@ -649,7 +649,7 @@ protected void validateIpAddressRelatedConfigValues(final String configName, fin
649649
}
650650
}
651651
if (err) {
652-
throw new InvalidParameterValueException("Invalid IP address value(s) specified for the config value");
652+
throw new InvalidParameterValueException("Invalid IP address value(s) specified for the config value.");
653653
}
654654
}
655655

@@ -691,9 +691,8 @@ public boolean stop() {
691691
@DB
692692
public String updateConfiguration(final long userId, final String name, final String category, String value, final String scope, final Long resourceId) {
693693
final String validationMsg = validateConfigurationValue(name, value, scope);
694-
695694
if (validationMsg != null) {
696-
logger.error("Invalid configuration option, name: " + name + ", value:" + value);
695+
logger.error("Invalid value [{}] for configuration [{}] due to [{}].", value, name, validationMsg);
697696
throw new InvalidParameterValueException(validationMsg);
698697
}
699698

@@ -956,8 +955,6 @@ public Configuration updateConfiguration(final UpdateCfgCmd cmd) throws InvalidP
956955
category = config.getCategory();
957956
}
958957

959-
validateIpAddressRelatedConfigValues(name, value);
960-
961958
if (value == null) {
962959
return _configDao.findByName(name);
963960
}
@@ -1192,14 +1189,21 @@ public Pair<Configuration, String> resetConfiguration(final ResetCfgCmd cmd) thr
11921189
return new Pair<Configuration, String>(_configDao.findByName(name), newValue);
11931190
}
11941191

1195-
protected String validateConfigurationValue(final String name, String value, final String scope) {
1192+
/**
1193+
* Validates whether a value is valid for the specified configuration. This includes type and range validation.
1194+
* @param name name of the configuration.
1195+
* @param value value to validate.
1196+
* @param scope scope of the configuration.
1197+
* @return null if the value is valid; otherwise, returns an error message.
1198+
*/
1199+
protected String validateConfigurationValue(String name, String value, String scope) {
11961200
final ConfigurationVO cfg = _configDao.findByName(name);
11971201
if (cfg == null) {
11981202
logger.error("Missing configuration variable " + name + " in configuration table");
11991203
return "Invalid configuration variable.";
12001204
}
12011205

1202-
final String configScope = cfg.getScope();
1206+
String configScope = cfg.getScope();
12031207
if (scope != null) {
12041208
if (!configScope.contains(scope) &&
12051209
!(ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN.value() && configScope.contains(ConfigKey.Scope.Account.toString()) &&
@@ -1208,11 +1212,11 @@ protected String validateConfigurationValue(final String name, String value, fin
12081212
return "Invalid scope id provided for the parameter " + name;
12091213
}
12101214
}
1211-
Class<?> type = null;
1212-
final Config configuration = Config.getConfig(name);
1215+
Class<?> type;
1216+
Config configuration = Config.getConfig(name);
12131217
if (configuration == null) {
12141218
logger.warn("Did not find configuration " + name + " in Config.java. Perhaps moved to ConfigDepot");
1215-
final ConfigKey<?> configKey = _configDepot.get(name);
1219+
ConfigKey<?> configKey = _configDepot.get(name);
12161220
if(configKey == null) {
12171221
logger.warn("Did not find configuration " + name + " in ConfigDepot too.");
12181222
return null;
@@ -1221,144 +1225,161 @@ protected String validateConfigurationValue(final String name, String value, fin
12211225
} else {
12221226
type = configuration.getType();
12231227
}
1224-
//no need to validate further if a
1225-
//config can have null value.
1226-
String errMsg = null;
1227-
try {
1228-
if (type.equals(Integer.class)) {
1229-
errMsg = "There was error in trying to parse value: " + value + ". Please enter a valid integer value for parameter " + name;
1230-
Integer.parseInt(value);
1231-
} else if (type.equals(Float.class)) {
1232-
errMsg = "There was error in trying to parse value: " + value + ". Please enter a valid float value for parameter " + name;
1233-
Float.parseFloat(value);
1234-
} else if (type.equals(Long.class)) {
1235-
errMsg = "There was error in trying to parse value: " + value + ". Please enter a valid long value for parameter " + name;
1236-
Long.parseLong(value);
1237-
}
1238-
} catch (final Exception e) {
1239-
// catching generic exception as some throws NullPointerException and some throws NumberFormatExcpeion
1240-
logger.error(errMsg);
1241-
return errMsg;
1228+
1229+
boolean isTypeValid = validateValueType(value, type);
1230+
if (!isTypeValid) {
1231+
return String.format("Value [%s] is not a valid [%s].", value, type);
12421232
}
12431233

1244-
if (value == null) {
1245-
if (type.equals(Boolean.class)) {
1246-
return "Please enter either 'true' or 'false'.";
1247-
}
1248-
if (overprovisioningFactorsForValidation.contains(name)) {
1249-
final String msg = "value cannot be null for the parameter " + name;
1250-
logger.error(msg);
1251-
return msg;
1252-
}
1253-
return null;
1234+
return validateValueRange(name, value, type, configuration);
1235+
}
1236+
1237+
/**
1238+
* Returns whether a value is valid for a configuration of the provided type.
1239+
* Valid configuration values are:
1240+
*
1241+
* <ul>
1242+
* <li>String: any value, including null;</li>
1243+
* <li>Character: any value, including null;</li>
1244+
* <li>Boolean: strings that equal "true" or "false" (case-sensitive);</li>
1245+
* <li>Integer, Short, Long: strings that contain a valid int/short/long;</li>
1246+
* <li>Float, Double: strings that contain a valid float/double, except infinity.</li>
1247+
* </ul>
1248+
*
1249+
* If a type isn't listed here, then the value will be considered invalid.
1250+
* @param value value to validate.
1251+
* @param type type of the configuration.
1252+
* @return boolean indicating whether the value is valid.
1253+
*/
1254+
protected boolean validateValueType(String value, Class<?> type) {
1255+
if (type == String.class || type == Character.class) {
1256+
return true;
12541257
}
12551258

1256-
value = value.trim();
12571259
try {
1258-
if (overprovisioningFactorsForValidation.contains(name) && Float.parseFloat(value) <= 0f) {
1259-
final String msg = name + " should be greater than 0";
1260-
logger.error(msg);
1261-
throw new InvalidParameterValueException(msg);
1260+
if (type == Boolean.class) {
1261+
return value.equals("true") || value.equals("false");
1262+
} else if (type == Integer.class) {
1263+
Integer.parseInt(value);
1264+
} else if (type == Long.class) {
1265+
Long.parseLong(value);
1266+
} else if (type == Short.class) {
1267+
Short.parseShort(value);
1268+
} else if (type == Float.class) {
1269+
float floatValue = Float.parseFloat(value);
1270+
return !Float.isInfinite(floatValue);
1271+
} else if (type == Double.class) {
1272+
double doubleValue = Double.parseDouble(value);
1273+
return !Double.isInfinite(doubleValue);
1274+
} else {
1275+
return false;
12621276
}
1263-
} catch (final NumberFormatException e) {
1264-
final String msg = "There was an error trying to parse the float value for: " + name;
1265-
logger.error(msg);
1266-
throw new InvalidParameterValueException(msg);
1277+
return true;
1278+
} catch (NullPointerException | NumberFormatException e) {
1279+
return false;
12671280
}
1281+
}
12681282

1269-
if (type.equals(Boolean.class)) {
1270-
if (!(value.equals("true") || value.equals("false"))) {
1271-
logger.error("Configuration variable " + name + " is expecting true or false instead of " + value);
1272-
return "Please enter either 'true' or 'false'.";
1283+
/**
1284+
* If the specified configuration contains a range, validates if the value is in that range. If it doesn't contain
1285+
* a range, any value is considered valid.
1286+
* The value must be previously checked by `validateValueType` so there aren't casting exceptions here.
1287+
* @param name name of the configuration.
1288+
* @param value value to validate.
1289+
* @param type type of the value.
1290+
* @param configuration if the configuration uses Config instead of ConfigKey, the Config object; null otherwise.
1291+
* @return if the value is valid, returns null; if not, returns an error message.
1292+
*/
1293+
protected String validateValueRange(String name, String value, Class<?> type, Config configuration) {
1294+
if (type.equals(Float.class)) {
1295+
Float val = Float.parseFloat(value);
1296+
if (overprovisioningFactorsForValidation.contains(name) && val <= 0f) {
1297+
return String.format("Value for configuration [%s] should be greater than 0.", name);
1298+
} else if (weightBasedParametersForValidation.contains(name) && (val < 0f || val > 1f)) {
1299+
return String.format("Please enter a value between 0 and 1 for the configuration parameter: [%s].", name);
12731300
}
1274-
return null;
12751301
}
12761302

12771303
if (type.equals(Integer.class)) {
1278-
try {
1279-
final int val = Integer.parseInt(value);
1280-
if (NetworkModel.MACIdentifier.key().equalsIgnoreCase(name)) {
1281-
//The value need to be between 0 to 255 because the mac generation needs a value of 8 bit
1282-
//0 value is considered as disable.
1283-
if(val < 0 || val > 255){
1284-
throw new InvalidParameterValueException(name + " value should be between 0 and 255. 0 value will disable this feature");
1285-
}
1304+
int val = Integer.parseInt(value);
1305+
if (NetworkModel.MACIdentifier.key().equalsIgnoreCase(name)) {
1306+
// The value needs to be between 0 to 255 because the MAC generation needs a value of 8 bits
1307+
// 0 is considered as disabled.
1308+
if (val < 0 || val > 255){
1309+
return String.format("[%s] value should be between 0 and 255. 0 value will disable this feature.", name);
12861310
}
1287-
1288-
if (UnmanagedVMsManager.ThreadsOnMSToImportVMwareVMFiles.key().equalsIgnoreCase(name) || UnmanagedVMsManager.ThreadsOnKVMHostToImportVMwareVMFiles.key().equalsIgnoreCase(name)) {
1289-
if (val > 10) {
1290-
throw new InvalidParameterValueException("Please enter a value between 0 and 10 for the configuration parameter: " + name + ", -1 will disable it");
1291-
}
1311+
}
1312+
if (UnmanagedVMsManager.ThreadsOnMSToImportVMwareVMFiles.key().equalsIgnoreCase(name) ||
1313+
UnmanagedVMsManager.ThreadsOnKVMHostToImportVMwareVMFiles.key().equalsIgnoreCase(name)) {
1314+
if (val < -1 || val > 10) {
1315+
return String.format("Please enter a value between -1 and 10 for the configuration parameter: [%s]. -1 will disable it.", name);
12921316
}
1293-
1294-
if (configValuesForValidation.contains(name)) {
1295-
if (val <= 0) {
1296-
throw new InvalidParameterValueException("Please enter a positive value for the configuration parameter:" + name);
1297-
}
1298-
if ("vm.password.length".equalsIgnoreCase(name) && val < 6) {
1299-
throw new InvalidParameterValueException("Please enter a value greater than 5 for the configuration parameter:" + name);
1300-
}
1301-
if ("remote.access.vpn.psk.length".equalsIgnoreCase(name)) {
1302-
if (val < 8) {
1303-
throw new InvalidParameterValueException("Please enter a value greater than 7 for the configuration parameter:" + name);
1304-
}
1305-
if (val > 256) {
1306-
throw new InvalidParameterValueException("Please enter a value less than 257 for the configuration parameter:" + name);
1307-
}
1308-
}
1309-
if (UserDataManager.VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name)) {
1310-
if (val > 1048576) {
1311-
throw new InvalidParameterValueException("Please enter a value less than 1048576 for the configuration parameter:" + name);
1312-
}
1313-
}
1317+
} else if (configValuesForValidation.contains(name)) {
1318+
if (val <= 0) {
1319+
return String.format("Please enter a positive value for the configuration parameter: [%s].", name);
13141320
}
1315-
} catch (final NumberFormatException e) {
1316-
logger.error("There was an error trying to parse the integer value for configuration parameter: " + name);
1317-
throw new InvalidParameterValueException("There was an error trying to parse the integer value for configuration parameter: " + name);
1318-
}
1319-
}
1320-
1321-
if (type.equals(Float.class)) {
1322-
try {
1323-
final Float val = Float.parseFloat(value);
1324-
if (weightBasedParametersForValidation.contains(name) && (val < 0f || val > 1f)) {
1325-
throw new InvalidParameterValueException("Please enter a value between 0 and 1 for the configuration parameter: " + name);
1321+
if ("vm.password.length".equalsIgnoreCase(name) && val < 6) {
1322+
return String.format("Please enter a value greater than 5 for the configuration parameter: [%s].", name);
1323+
}
1324+
if ("remote.access.vpn.psk.length".equalsIgnoreCase(name) && (val < 8 || val > 256)) {
1325+
return String.format("Please enter a value greater than 7 and less than 257 for the configuration parameter: [%s].", name);
1326+
}
1327+
if (UserDataManager.VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name) && val > 1048576) {
1328+
return String.format("Please enter a value less than 1048577 for the configuration parameter: [%s].", name);
13261329
}
1327-
} catch (final NumberFormatException e) {
1328-
logger.error("There was an error trying to parse the float value for configuration parameter: " + name);
1329-
throw new InvalidParameterValueException("There was an error trying to parse the float value for configuration parameter: " + name);
13301330
}
13311331
}
13321332

13331333
if (type.equals(String.class)) {
1334-
if (name.equalsIgnoreCase(SecStorageAllowedInternalDownloadSites.key()) && StringUtils.isNotEmpty(value)) {
1334+
if (SecStorageAllowedInternalDownloadSites.key().equalsIgnoreCase(name) && StringUtils.isNotEmpty(value)) {
13351335
final String[] cidrs = value.split(",");
13361336
for (final String cidr : cidrs) {
13371337
if (!NetUtils.isValidIp4(cidr) && !NetUtils.isValidIp6(cidr) && !NetUtils.getCleanIp4Cidr(cidr).equals(cidr)) {
1338-
logger.error(String.format("Invalid CIDR %s value specified for the config %s", cidr, name));
1339-
throw new InvalidParameterValueException(String.format("Invalid CIDR %s value specified for the config %s", cidr, name));
1338+
return String.format("Invalid CIDR %s value specified for the config %s.", cidr, name);
13401339
}
13411340
}
13421341
}
13431342
}
13441343

1345-
if (configuration == null ) {
1346-
//range validation has to be done per case basis, for now
1347-
//return in case of Configkey parameters
1348-
return null;
1349-
}
1344+
validateIpAddressRelatedConfigValues(name, value);
13501345

1351-
if (configuration.getRange() == null) {
1346+
if (!shouldValidateConfigRange(name, value, configuration)) {
13521347
return null;
13531348
}
1354-
String[] range = configuration.getRange().split(",");
13551349

1356-
if (type.equals(String.class)) {
1357-
return validateIfStringValueIsInRange(name, value, range);
1358-
} else if (type.equals(Integer.class)) {
1350+
String[] range = configuration.getRange().split(",");
1351+
if (type.equals(Integer.class)) {
13591352
return validateIfIntValueIsInRange(name, value, range[0]);
13601353
}
1361-
return String.format("Invalid value for configuration [%s].", name);
1354+
return validateIfStringValueIsInRange(name, value, range);
1355+
}
1356+
1357+
/**
1358+
* Returns a boolean indicating whether a Config's range should be validated. It should not be validated when:</br>
1359+
* <ul>
1360+
* <li>The value is null;</li>
1361+
* <li>The configuration uses ConfigKey instead of Config;</li>
1362+
* <li>The Config does not have a specified range.</li>
1363+
* </ul>
1364+
*/
1365+
protected boolean shouldValidateConfigRange(String name, String value, Config configuration) {
1366+
if (value == null) {
1367+
logger.debug("Not proceeding with configuration [{}]'s range validation, as its provided value is null.", name);
1368+
return false;
1369+
}
1370+
1371+
if (configuration == null) {
1372+
logger.debug("Not proceeding with configuration [{}]'s range validation, as it uses ConfigKey instead of Config.", name);
1373+
return false;
1374+
}
1375+
1376+
if (configuration.getRange() == null) {
1377+
logger.debug("Not proceeding with configuration [{}]'s range validation, as it does not have a specified range.", name);
1378+
return false;
1379+
}
1380+
1381+
logger.debug("Proceeding with configuration [{}]'s range validation.", name);
1382+
return true;
13621383
}
13631384

13641385
/**

0 commit comments

Comments
 (0)