/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.customconfigcontroller.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.customconfigcontroller.CustomConfigConstants; import com.emc.storageos.customconfigcontroller.CustomConfigConstraint; import com.emc.storageos.customconfigcontroller.CustomConfigResolver; import com.emc.storageos.customconfigcontroller.CustomConfigType; import com.emc.storageos.customconfigcontroller.CustomConfigTypeProvider; import com.emc.storageos.customconfigcontroller.DataSource; import com.emc.storageos.customconfigcontroller.DataSourceFactory; import com.emc.storageos.customconfigcontroller.DataSourceVariable; import com.emc.storageos.customconfigcontroller.exceptions.CustomConfigControllerException; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.PrefixConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.CustomConfig; import com.emc.storageos.db.client.model.StringMap; public class CustomConfigHandler { private static final Logger logger = LoggerFactory.getLogger(CustomConfigHandler.class); private DbClient dbClient; private Map<String, CustomConfigResolver> configResolvers; private CustomConfigTypeProvider configTypeProvider; private DataSourceFactory datasourceFactory; public DataSourceFactory getDatasourceFactory() { return datasourceFactory; } public void setDatasourceFactory(DataSourceFactory datasourceFactory) { this.datasourceFactory = datasourceFactory; } public void setDbClient(DbClient dbc) { dbClient = dbc; } public Map<String, CustomConfigResolver> getConfigResolvers() { return configResolvers; } public void setConfigResolvers(Map<String, CustomConfigResolver> configResolvers) { this.configResolvers = configResolvers; } public CustomConfigTypeProvider getConfigTypeProvider() { return configTypeProvider; } public void setConfigTypeProvider(CustomConfigTypeProvider configTypeProvider) { this.configTypeProvider = configTypeProvider; } /** * Get all the defined configuration typess. * * @return a list of all configuration typess defined in the system. */ public List<CustomConfigType> getCustomConfigTypes() { return configTypeProvider.getCustomConfigTypes(); } /** * Get the custom configuration type for a given configuration name. * * @param name the configuration type name * @return the custom configuration type for the configuration name * @throws CustomConfigControllerException is a type could not be found * for the configuration name */ public CustomConfigType getCustomConfigType(String name) { return configTypeProvider.getCustomConfigType(name); } /** * Gets the resolved value for a given configuration name, * and scope. * * @param configName the configuration type name * @param scope the scope to be used to find the configuration value. * This is determined by first trying to find a configuration with * exact scope, if one cannot be found then the global scope, and * this cannot be found then the system default defined {@link CustomConfigType} * @return the resolved value for a given configuration name, * and scope. This function is guaranteed to return a value for * all valid inputs. */ public String getCustomConfigValue(String configName, StringMap scope) throws CustomConfigControllerException { CustomConfig customConfig = null; CustomConfig globalConfig = null; CustomConfig systemDefaultConfig = null; String value = null; URIQueryResultList results = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory.getCustomConfigByConfigType(configName), results); while (results.iterator().hasNext()) { CustomConfig tmpConfig = dbClient.queryObject(CustomConfig.class, results.iterator().next()); if (tmpConfig == null || tmpConfig.getInactive() == true || !tmpConfig.getRegistered()) { continue; } StringMap tmpscope = tmpConfig.getScope(); if (tmpscope != null && scope != null && tmpscope.equals(scope)) { if (tmpConfig.getSystemDefault()) { systemDefaultConfig = tmpConfig; } else { customConfig = tmpConfig; break; } } else if (tmpscope != null && tmpscope.containsKey(CustomConfigConstants.GLOBAL_KEY)) { if (tmpConfig.getSystemDefault()) { if (systemDefaultConfig == null) { systemDefaultConfig = tmpConfig; } } else { globalConfig = tmpConfig; } } } String scopeKey = "none"; String scopeValue = "none"; if (scope != null && scope.keySet() != null && scope.keySet().iterator() != null && scope.keySet().iterator().hasNext()) { scopeKey = scope.keySet().iterator().next(); scopeValue = scope.get(scopeKey) != null ? scope.get(scopeKey) : "no value"; } if (customConfig != null) { logger.info(String.format("Found the custom config %s for %s:%s", configName, scopeKey, scopeValue)); value = customConfig.getValue(); } else if (globalConfig != null) { logger.info(String.format("Could not find custom config %s for %s:%s. The global custom config will be used.", configName, scopeKey, scopeValue)); value = globalConfig.getValue(); } else if (systemDefaultConfig != null) { logger.info(String.format("Could not find custom config %s for %s:%s. The system default config will be used.", configName, scopeKey, scopeValue)); value = systemDefaultConfig.getValue(); } else { throw CustomConfigControllerException.exceptions.customConfigScopeWithNoDefault(configName, scopeKey, scopeValue); } return value; } /** * Gets the resolved custom name for a given CustomName configuration type * name, a scope and the list of values specified in the data source. * * @param name the configuration type name * @param scope the scope to be used to find the configuration value. See {@link #getCustomConfigValue(String, StringMap)} for details. * @param dataSource the object containing the variable values * to be used replaced in the CustomName mask. * @return resolved custom name for a given CustomName configuration */ public String getComputedCustomConfigValue(String name, StringMap scope, DataSource dataSource) throws CustomConfigControllerException { CustomConfigType item = getCustomConfigType(name); String value = getCustomConfigValue(name, scope); CustomConfigResolver resolver = configResolvers.get(item.getConfigType()); String result = resolver.resolve(item, scope, value, dataSource); // Apply the constraints List<CustomConfigConstraint> constraints = item.getConstraints(); String systemType = CustomConfigConstants.DEFAULT_KEY; if (scope != null) { systemType = scope.get(CustomConfigConstants.SYSTEM_TYPE_SCOPE); // if the system type scope is not available, check for host type scope. // host type scope is only available for Hitachi Host Mode Option if (systemType == null) { systemType = scope.get(CustomConfigConstants.HOST_TYPE_SCOPE); } } for (CustomConfigConstraint constraint : constraints) { result = constraint.applyConstraint(result, systemType); } return result; } /** * Resolves custom name for a given CustomName configuration type * name, a scope and the list of values specified in the data source * * @param name the configuration type name * @param scope the scope to be used to find the configuration value. * @param dataSource the object containing the variable values * to be used replaced in the CustomName mask. * @return resolved custom name for a given CustomName configuration */ public String resolve(String name, String scope, DataSource dataSource) throws CustomConfigControllerException { StringMap scopeMap = new StringMap(); CustomConfigType item = getCustomConfigType(name); if (item != null) { for (String key : item.getScope().keySet()) { List<String> scopeVals = java.util.Arrays.asList(item.getScope().get(key).split(",")); if (scopeVals.contains(scope)) { scopeMap.put(key, scope); } } } String value = getCustomConfigValue(name, scopeMap); CustomConfigResolver resolver = configResolvers.get(item.getConfigType()); String result = resolver.resolve(item, scopeMap, value, dataSource); return result; } /** * Performs the necessary checks to ensure the user-specified value for a * configuration is valid. * <ol> * <li>if isCheckDuplicate is set to true, ensure that a user-created configuration does not already exist for this configuration type * and scope</li> * <li>check that a configuration type exists for the name</li> * <li>check that the scope is supported based on the definition of the scope in the type of this configuration</li> * <li>check that the value is of the right type and that it is compliant with the constraints defined for this configuration type</li> * </ol> * * @param name the configuration type name * @param scope the scope of the configuration item * @param value the value of the configuration item in a string form * @param isCheckDuplicate true when uniqueness check is requested * @throws CustomConfigControllerException if any of the checks fail. */ public void validate(String name, StringMap scope, String value, boolean isCheckDuplicate) { // check this is a supported config CustomConfigType item = getCustomConfigType(name); if (item == null) { throw CustomConfigControllerException.exceptions.configTypeNotFound(name); } // if this is a create, check for duplicates and also validate the scope if (isCheckDuplicate) { CustomConfig config = getUserDefinedCustomConfig(constructConfigName(name, scope)); if (config != null && config.getScope().equals(scope)) { throw CustomConfigControllerException.exceptions.customConfigAlreadyExists(config.getLabel()); } // check this is a supported scope for (String key : scope.keySet()) { if (!item.getScope().containsKey(key)) { throw CustomConfigControllerException.exceptions.scopeTypeNotSupported(key, name); } List<String> scopeVals = java.util.Arrays.asList(item.getScope().get(key).split(",")); if (!scopeVals.contains(scope.get(key))) { throw CustomConfigControllerException.exceptions.scopeValueNotSupported(scope.get(key), key, name); } } } // this performs additional validations value = getCustomConfigPreviewValue(name, value, scope, null); // check the value against each constraint for (CustomConfigConstraint constraint : item.getConstraints()) { constraint.validate(value, scope.values().iterator().next()); } } /** * Get the custom configuration item for the given label. * * @param label which is the unique and fully qualified name of the configuration * that includes the configuration name and its full scope. * @return the custom configuration item if a configuration item is found. Return * null otherwise. */ public CustomConfig getUserDefinedCustomConfig(String label) { CustomConfig value = null; URIQueryResultList results = new URIQueryResultList(); dbClient.queryByConstraint(PrefixConstraint.Factory.getLabelPrefixConstraint(CustomConfig.class, label), results); while (results.iterator().hasNext()) { CustomConfig tmpConfig = dbClient.queryObject(CustomConfig.class, results.iterator().next()); if (!tmpConfig.getSystemDefault()) { value = tmpConfig; break; } } return value; } /** * Uses the samples provided in the configuration item to generate a resolved * name for a given mask, configuration and scope. This can be used to preview * and validate a name mask prior to setting it. * * @param name the configuration type name * @param value the name mask to be resolved. * @param scope the scope to be used to for deciding which constraints to apply. * @param variables a map of variable-name-to-variable-value. This map can contain * a value for all or only some of the name mask variables, or it can be * empty. Any missing variable will be replaced with the variable default. * @return the resolved name. * @throws CustomConfigControllerException for invalid input. */ public String getCustomConfigPreviewValue(String name, String value, StringMap scope, Map<String, String> variables) { CustomConfigType item = getCustomConfigType(name); Map<DataSourceVariable, Boolean> dsVariables = item.getDataSourceVariables(); DataSource sampleDatasource = null; if (dsVariables != null && !dsVariables.isEmpty()) { sampleDatasource = datasourceFactory.getSampleDataSource(name); } CustomConfigResolver resolver = configResolvers.get(item.getConfigType()); if (variables != null && !variables.isEmpty()) { for (Map.Entry<String, String> entry : variables.entrySet()) { sampleDatasource.addProperty(entry.getKey(), entry.getValue()); } } // validate the computed value resolver.validate(item, scope, value); // Resolve the config value String computedName = resolver.resolve(item, scope, value, sampleDatasource); // validate against the constraints List<CustomConfigConstraint> constraints = item.getConstraints(); String systemType = CustomConfigConstants.DEFAULT_KEY; if (scope != null) { systemType = scope.get(CustomConfigConstants.SYSTEM_TYPE_SCOPE); // if the system type scope is not available, check for host type scope. // host type scope is only available for Hitachi Host Mode Option if (systemType == null) { systemType = scope.get(CustomConfigConstants.HOST_TYPE_SCOPE); } } for (CustomConfigConstraint constraint : constraints) { constraint.validate(computedName, systemType); } return computedName; } /** * This is a short cut for the controller code to avoid having to create * a scope. In the future a scope factory can be added. */ public String getComputedCustomConfigValue(String name, String scope, DataSource sources) throws CustomConfigControllerException { StringMap map = new StringMap(); CustomConfigType item = getCustomConfigType(name); if (item != null) { for (String key : item.getScope().keySet()) { List<String> scopeVals = java.util.Arrays.asList(item.getScope().get(key).split(",")); if (scopeVals.contains(scope)) { map.put(key, scope); } } } return getComputedCustomConfigValue(name, map, sources); } /** * Short cut to getting the value as a boolean. */ public Boolean getComputedCustomConfigBooleanValue(String name, String scope, DataSource sources) throws CustomConfigControllerException { return Boolean.valueOf(getComputedCustomConfigValue(name, scope, sources)); } /** * Build the unique and fully qualified name of the configuration * that includes the configuration name and its full scope. Example: * systemtype.mds.ZoneName * * @param configName the configuration type name * @param scope scope the scope of the configuration * @return the unique and fully qualified name of the configuration */ public static String constructConfigName(String configName, StringMap scope) { StringBuilder builder = new StringBuilder(); for (Map.Entry<String, String> entry : scope.entrySet()) { builder.append(entry.getKey()); builder.append("."); builder.append(entry.getValue()); builder.append("."); } builder.append(configName); return builder.toString(); } /** * This function is called when the controller service is started. It reads * the custom configuration types, computes the list of system-defined * configurations that should be persisted in the database and updates the DB accordingly. * */ public void loadSystemCustomConfigs() { logger.debug("loadSystemCustomConfigs started"); long start = System.currentTimeMillis(); // get all existing system-define configs in the database Map<String, CustomConfig> dbSystemConfigsByLabel = getSystemConfigsFromDb(); // compute the list of required system-define configs from the types Map<String, CustomConfig> templateConfigsByLabel = getTemplateConfigs(); // Diff the two maps and update List<CustomConfig> created = new ArrayList<CustomConfig>(); List<CustomConfig> updated = new ArrayList<CustomConfig>(); List<CustomConfig> deleted = new ArrayList<CustomConfig>(); CustomConfig curConfig = null; CustomConfig newConfig = null; for (String label : templateConfigsByLabel.keySet()) { if (dbSystemConfigsByLabel.containsKey(label)) { curConfig = dbSystemConfigsByLabel.get(label); newConfig = templateConfigsByLabel.get(label); // check if we need to update if (!newConfig.getValue().equals(curConfig.getValue())) { curConfig.setValue(newConfig.getValue()); logger.info("System-defined CustomConfig {} will be updated", label); updated.add(curConfig); } dbSystemConfigsByLabel.remove(label); } else { newConfig = templateConfigsByLabel.get(label); newConfig.setId(URIUtil.createId(CustomConfig.class)); logger.info("System-defined CustomConfig {} will be created", label); created.add(newConfig); } } // any remaining system-defined custom configs should be deleted // any user-defined instances should also be deleted for (String label : dbSystemConfigsByLabel.keySet()) { logger.info("System-defined CustomConfig {} will be deleted.", label); deleted.add(dbSystemConfigsByLabel.get(label)); curConfig = getUserDefinedCustomConfig(label); if (curConfig != null) { logger.info("User-defined CustomConfig {} will be delete with user-defined instance", label); deleted.add(curConfig); } } dbClient.markForDeletion(deleted); dbClient.createObject(created); dbClient.persistObject(updated); logger.info("loadSystemCustomConfigs results: Created: {}, Updated: {}, Deleted: {}", new Object[] { created.size(), updated.size(), deleted.size() }); logger.debug("loadSystemCustomConfigs ended and took {}", (System.currentTimeMillis() - start)); } /** * Retrieves the list of system-defined custom configurations from the * data base and create a map keyed by the custom configuration unique * and fully qualified label. * * @return a map of db system-defined custom configurations keyed by the custom * configuration unique and fully qualified label */ private Map<String, CustomConfig> getSystemConfigsFromDb() { // get all existing configs in the database logger.debug("getSystemConfigsFromDb started"); Map<String, CustomConfig> systemConfigsByName = new HashMap<String, CustomConfig>(); Iterator<CustomConfig> curConfigsItr = dbClient.queryIterativeObjects(CustomConfig.class, dbClient.queryByType(CustomConfig.class, true), true); CustomConfig curConfig = null; while (curConfigsItr.hasNext()) { curConfig = curConfigsItr.next(); if (curConfig != null && curConfig.getSystemDefault()) { systemConfigsByName.put(curConfig.getLabel(), curConfig); } } logger.debug("getSystemConfigsFromDb ended and found a total of {}" + " system-defined configs in the database", systemConfigsByName.size()); return systemConfigsByName; } /** * Computes the list of system-defined custom configurations that should * be exist in the database from the custom configuration templates. Each * scope must have a system-defined default custom configuration . If none * is specified in the template, than the template 'default' will be used. * * @return a map of the system-defined custom configurations that need to be * in the database keyed by the custom configuration unique and fully qualified label */ private Map<String, CustomConfig> getTemplateConfigs() { logger.debug("getTemplateConfigs started"); CustomConfig customConfig; // the default value for the scope. String defaultValue; // the 'default' default value as defined in the template String masterDefaultValue; Map<String, String> defaultValuesMap; List<CustomConfigType> templates = configTypeProvider.getCustomConfigTypes(); Map<String, CustomConfig> customConfigsMap = new HashMap<String, CustomConfig>(); for (CustomConfigType template : templates) { defaultValuesMap = template.getDefaultValues(); // if the template defines a 'default' default value, get it masterDefaultValue = defaultValuesMap.get(CustomConfigConstants.DEFAULT_KEY); for (String key : template.getScope().keySet()) { String[] vals = template.getScope().get(key).split(","); for (String val : vals) { if (defaultValuesMap.containsKey(val)) { // if a default value is specified in the template for this scope, use it defaultValue = defaultValuesMap.get(val); } else if (masterDefaultValue != null) { // if a default value is NOT specified in the template for this scope, // use the template 'default' default value defaultValue = masterDefaultValue; } else { // A mistake made in the config file, either a scope or a 'default' default must be specified throw CustomConfigControllerException.exceptions.customConfigScopeWithNoDefault( template.getName(), key, val); } logger.debug("System-define CutomConfig item for {} " + " and scope {}:{} is computed", new Object[] { template.getName(), key, val }); customConfig = createSystemConfig(template, key, val, defaultValue); customConfigsMap.put(customConfig.getLabel(), customConfig); } } } logger.debug("getTemplateConfigs ended and found a total of {} " + " system-defined configs in the templates", customConfigsMap.size()); return customConfigsMap; } private CustomConfig createSystemConfig(CustomConfigType template, String scopeType, String scopevalue, String value) { // create one CustomConfig curConfig = new CustomConfig(); curConfig.setConfigType(template.getName()); curConfig.setSystemDefault(true); curConfig.setValue(value); StringMap scope = new StringMap(); scope.put(scopeType, scopevalue); curConfig.setScope(scope); curConfig.setLabel(CustomConfigHandler.constructConfigName(template.getName(), scope)); curConfig.setRegistered(true); return curConfig; } }