| /* |
| * 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.ambari.server.topology; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| import org.apache.commons.lang3.tuple.Pair; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * Configuration for a topology entity such as a blueprint, hostgroup or cluster. |
| */ |
| public class Configuration { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(Configuration.class); |
| |
| /** |
| * properties for this configuration instance |
| */ |
| private Map<String, Map<String, String>> properties; |
| |
| /** |
| * attributes for this configuration instance |
| */ |
| private Map<String, Map<String, Map<String, String>>> attributes; |
| |
| /** |
| * parent configuration |
| */ |
| private Configuration parentConfiguration; |
| |
| public static Configuration newEmpty() { |
| return new Configuration(new HashMap<>(), new HashMap<>()); |
| } |
| |
| /** |
| * Apply configuration changes from {@code updatedConfigs} to {@code config}, but only |
| * change properties that are either absent in the existing config, or have values that |
| * match the stack default config. |
| * |
| * @return config types that had any updates applied |
| */ |
| public Set<String> applyUpdatesToStackDefaultProperties(Configuration stackDefaultConfig, Map<String, Map<String, String>> existingConfigurations, Map<String, Map<String, String>> updatedConfigs) { |
| Set<String> updatedConfigTypes = new HashSet<>(); |
| |
| Map<String, Map<String, String>> stackDefaults = stackDefaultConfig.getProperties(); |
| |
| for (Map.Entry<String, Map<String, String>> configEntry : updatedConfigs.entrySet()) { |
| String configType = configEntry.getKey(); |
| Map<String, String> propertyMap = configEntry.getValue(); |
| Map<String, String> clusterConfigProperties = existingConfigurations.get(configType); |
| Map<String, String> stackDefaultConfigProperties = stackDefaults.get(configType); |
| for (Map.Entry<String, String> propertyEntry : propertyMap.entrySet()) { |
| String property = propertyEntry.getKey(); |
| String newValue = propertyEntry.getValue(); |
| String currentValue = getPropertyValue(configType, property); |
| |
| if (!propertyHasCustomValue(clusterConfigProperties, stackDefaultConfigProperties, property) && !Objects.equals(currentValue, newValue)) { |
| LOG.debug("Update config property {}/{}: {} -> {}", configType, property, currentValue, newValue); |
| setProperty(configType, property, newValue); |
| updatedConfigTypes.add(configType); |
| } |
| } |
| } |
| |
| return updatedConfigTypes; |
| } |
| |
| /** |
| * Returns true if the property exists in clusterConfigProperties and has a custom user defined value. Property has |
| * custom value in case we there's no stack default value for it or it's not equal to stack default value. |
| */ |
| private static boolean propertyHasCustomValue(Map<String, String> clusterConfigProperties, Map<String, String> stackDefaultConfigProperties, String property) { |
| |
| boolean propertyHasCustomValue = false; |
| if (clusterConfigProperties != null) { |
| String propertyValue = clusterConfigProperties.get(property); |
| if (propertyValue != null) { |
| if (stackDefaultConfigProperties != null) { |
| String stackDefaultValue = stackDefaultConfigProperties.get(property); |
| if (stackDefaultValue != null) { |
| propertyHasCustomValue = !propertyValue.equals(stackDefaultValue); |
| } else { |
| propertyHasCustomValue = true; |
| } |
| } else { |
| propertyHasCustomValue = true; |
| } |
| } |
| } |
| return propertyHasCustomValue; |
| } |
| |
| public Configuration copy() { |
| Configuration parent = parentConfiguration; |
| parentConfiguration = null; |
| Configuration copy = new Configuration(getFullProperties(), getFullAttributes()); |
| parentConfiguration = parent; |
| return copy; |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param properties properties |
| * @param attributes attributes |
| * @param parentConfiguration parent configuration |
| */ |
| public Configuration(Map<String, Map<String, String>> properties, |
| Map<String, Map<String, Map<String, String>>> attributes, |
| Configuration parentConfiguration) { |
| |
| this.properties = properties; |
| this.attributes = attributes; |
| this.parentConfiguration = parentConfiguration; |
| |
| //todo: warning for deprecated global properties |
| // String message = null; |
| // for (BlueprintConfigEntity blueprintConfig: blueprint.getConfigurations()){ |
| // if(blueprintConfig.getType().equals("global")){ |
| // message = "WARNING: Global configurations are deprecated, please use *-env"; |
| // break; |
| // } |
| // } |
| } |
| |
| /** |
| * Configuration. |
| * |
| * @param properties properties in configType -> propertyName -> value format |
| * @param attributes attributes in configType -> attributeName -> propertyName -> attributeValue format |
| */ |
| public Configuration(Map<String, Map<String, String>> properties, |
| Map<String, Map<String, Map<String, String>>> attributes) { |
| |
| this.properties = properties; |
| this.attributes = attributes; |
| } |
| |
| /** |
| * Get the properties for this instance only; parent properties are not included. |
| * |
| * @return map of properties for this configuration instance keyed by config type |
| */ |
| public Map<String, Map<String, String>> getProperties() { |
| return properties; |
| } |
| |
| /** |
| * Get a complete merged map of properties including this instance and the entire parent hierarchy. |
| * Properties are merged so that children override the same property specified in it's parent hierarchy. |
| * This result is re-calculated for each request in case a parent value has changed so the result |
| * should be cached if possible. |
| * |
| * @return complete map of merged properties keyed by config type |
| */ |
| public Map<String, Map<String, String>> getFullProperties() { |
| return getFullProperties(Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Get a merged map of properties including this instance and n levels of the parent hierarchy. |
| * Properties are merged so that children override the same property specified in it's parent hierarchy. |
| * This result is re-calculated for each request in case a parent value has changed so the result |
| * should be cached if possible. |
| * |
| * @param depthLimit the number of parent levels to include in the results. Specifying 0 is the same |
| * as calling {@link #getProperties()} |
| * |
| * @return map of merged properties keyed by config type |
| */ |
| public Map<String, Map<String, String>> getFullProperties(int depthLimit) { |
| if (depthLimit == 0) { |
| HashMap<String, Map<String, String>> propertiesCopy = new HashMap<>(); |
| for (Map.Entry<String, Map<String, String>> typeProperties : properties.entrySet()) { |
| propertiesCopy.put(typeProperties.getKey(), new HashMap<>(typeProperties.getValue())); |
| } |
| return propertiesCopy; |
| } |
| |
| Map<String, Map<String, String>> mergedProperties = parentConfiguration == null ? |
| new HashMap<>() : |
| new HashMap<>(parentConfiguration.getFullProperties(--depthLimit)); |
| |
| for (Map.Entry<String, Map<String, String>> entry : properties.entrySet()) { |
| String configType = entry.getKey(); |
| Map<String, String> typeProps = new HashMap<>(entry.getValue()); |
| |
| if (mergedProperties.containsKey(configType)) { |
| mergedProperties.get(configType).putAll(typeProps); |
| } else { |
| mergedProperties.put(configType, typeProps); |
| } |
| } |
| return mergedProperties; |
| } |
| |
| /** |
| * Get the attributes for this instance only; parent attributes aren't included. |
| * |
| * @return map of attributes {configType -> {attributeName -> {propName, attributeValue}}} |
| */ |
| public Map<String, Map<String, Map<String, String>>> getAttributes() { |
| return attributes; |
| } |
| |
| /** |
| * Get a complete merged map of attributes including this instance and the entire parent hierarchy. |
| * Attributes are merged so that children override the same attribute specified in it's parent hierarchy. |
| * This result is re-calculated for each request in case a parent value has changed so the result |
| * should be cached if possible. |
| * |
| * @return complete map of merged attributes {configType -> {attributeName -> {propName, attributeValue}}} |
| */ |
| public Map<String, Map<String, Map<String, String>>> getFullAttributes() { |
| Map<String, Map<String, Map<String, String>>> mergedAttributeMap = parentConfiguration == null ? |
| new HashMap<>() : |
| new HashMap<>(parentConfiguration.getFullAttributes()); |
| |
| for (Map.Entry<String, Map<String, Map<String, String>>> typeEntry : attributes.entrySet()) { |
| String type = typeEntry.getKey(); |
| Map<String, Map<String, String>> typeAttributes = new HashMap<>(); |
| for (Map.Entry<String, Map<String, String>> attributeEntry : typeEntry.getValue().entrySet()) { |
| typeAttributes.put(attributeEntry.getKey(), new HashMap<>(attributeEntry.getValue())); |
| } |
| |
| if (! mergedAttributeMap.containsKey(type)) { |
| mergedAttributeMap.put(type, typeAttributes); |
| } else { |
| Map<String, Map<String, String>> mergedAttributes = mergedAttributeMap.get(type); |
| for (Map.Entry<String, Map<String, String>> attributeEntry : typeAttributes.entrySet()) { |
| String attribute = attributeEntry.getKey(); |
| if (! mergedAttributes.containsKey(attribute)) { |
| mergedAttributes.put(attribute, attributeEntry.getValue()); |
| } else { |
| Map<String, String> mergedAttributeProps = mergedAttributes.get(attribute); |
| for (Map.Entry<String, String> propEntry : attributeEntry.getValue().entrySet()) { |
| mergedAttributeProps.put(propEntry.getKey(), propEntry.getValue()); |
| } |
| } |
| } |
| } |
| } |
| return mergedAttributeMap; |
| } |
| |
| /** |
| * Get the requested property value from the full merged set of properties. |
| * |
| * @param configType configuration type |
| * @param propertyName property name |
| * |
| * @return requested property value or null if property isn't set in configuration hierarchy |
| */ |
| public String getPropertyValue(String configType, String propertyName) { |
| String value = null; |
| if (properties.containsKey(configType) && properties.get(configType).containsKey(propertyName)) { |
| value = properties.get(configType).get(propertyName); |
| } else if (parentConfiguration != null) { |
| value = parentConfiguration.getPropertyValue(configType, propertyName); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Get the requested attribute value from the full merged set of attributes. |
| * |
| * @param configType configuration type |
| * @param propertyName attribute property name |
| * @param attributeName attribute name |
| * |
| * @return requested attribute value or null if the attribute isn't set in configuration hierarchy |
| */ |
| public String getAttributeValue(String configType, String propertyName, String attributeName) { |
| String value = null; |
| if (attributes.containsKey(configType) && |
| attributes.get(configType).containsKey(attributeName) && |
| attributes.get(configType).get(attributeName).containsKey(propertyName)) { |
| |
| value = attributes.get(configType).get(attributeName).get(propertyName); |
| } else if (parentConfiguration != null) { |
| value = parentConfiguration.getAttributeValue(configType, propertyName, attributeName); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Set a property on the configuration. |
| * The property will be set on this instance so it will override any value specified in |
| * the parent hierarchy. |
| * |
| * @param configType configuration type |
| * @param propertyName property name |
| * @param value property value |
| * |
| * @return the previous value of the property or null if it didn't exist |
| */ |
| public String setProperty(String configType, String propertyName, String value) { |
| String previousValue = getPropertyValue(configType, propertyName); |
| Map<String, String> typeProperties = properties.get(configType); |
| if (typeProperties == null) { |
| typeProperties = new HashMap<>(); |
| properties.put(configType, typeProperties); |
| } |
| typeProperties.put(propertyName, value); |
| return previousValue; |
| } |
| |
| /** |
| * Remove a property from the configuration hierarchy. |
| * All occurrences of the property are removed from the config hierarchy such that |
| * a subsequent call to getPropertyValue() for the removed property will return null. |
| * |
| * @param configType configuration type |
| * @param propertyName property name |
| * |
| * @return the previous value of the removed property or null if it didn't exist in the |
| * configuration hierarchy |
| */ |
| public String removeProperty(String configType, String propertyName) { |
| String previousValue = null; |
| if (properties.containsKey(configType)) { |
| previousValue = properties.get(configType).remove(propertyName); |
| } |
| |
| if (parentConfiguration != null) { |
| String parentPreviousValue = parentConfiguration.removeProperty(configType, propertyName); |
| if (previousValue == null) { |
| previousValue = parentPreviousValue; |
| } |
| } |
| return previousValue; |
| } |
| |
| /** |
| * Moves the given properties from {@code sourceConfigType} to {@code targetConfigType}. |
| * If a property is already present in the target, it will be removed from the source, but not overwritten in the target. |
| * |
| * @param sourceConfigType the config type to move properties from |
| * @param targetConfigType the config type to move properties to |
| * @param propertiesToMove names of properties to be moved |
| * @return property names that were removed from the source |
| */ |
| public Set<String> moveProperties(String sourceConfigType, String targetConfigType, Set<String> propertiesToMove) { |
| Set<String> moved = new HashSet<>(); |
| for (String property : propertiesToMove) { |
| if (isPropertySet(sourceConfigType, property)) { |
| String value = removeProperty(sourceConfigType, property); |
| if (!isPropertySet(targetConfigType, property)) { |
| setProperty(targetConfigType, property, value); |
| } |
| moved.add(property); |
| } |
| } |
| return moved; |
| } |
| |
| /** |
| * General convenience method to determine if a given property has been set in the cluster configuration |
| * |
| * @param configType the config type to check |
| * @param propertyName the property name to check |
| * @return true if the named property has been set |
| * false if the named property has not been set |
| */ |
| public boolean isPropertySet(String configType, String propertyName) { |
| return properties.containsKey(configType) && properties.get(configType).containsKey(propertyName) || |
| parentConfiguration != null && parentConfiguration.isPropertySet(configType, propertyName); |
| } |
| |
| /** |
| * Set an attribute on the hierarchy. |
| * The attribute will be set on this instance so it will override any value specified in |
| * the parent hierarchy. |
| * |
| * @param configType configuration type |
| * @param propertyName attribute property name |
| * @param attributeName attribute name |
| * @param attributeValue attribute property value |
| * |
| * @return the previous value of the attribute or null if it didn't exist |
| */ |
| public String setAttribute(String configType, String propertyName, String attributeName, String attributeValue) { |
| String previousValue = getAttributeValue(configType, propertyName, attributeName); |
| |
| Map<String, Map<String, String>> typeAttributes = attributes.get(configType); |
| if (typeAttributes == null) { |
| typeAttributes = new HashMap<>(); |
| attributes.put(configType, typeAttributes); |
| } |
| |
| Map<String, String> attributes = typeAttributes.get(attributeName); |
| if (attributes == null) { |
| attributes = new HashMap<>(); |
| typeAttributes.put(attributeName, attributes); |
| } |
| |
| attributes.put(propertyName, attributeValue); |
| return previousValue; |
| } |
| |
| /** |
| * Get the complete set of configuration types represented in both full properties and full attributes. |
| * |
| * @return collection of all represented configuration types |
| */ |
| public Collection<String> getAllConfigTypes() { |
| Collection<String> allTypes = new HashSet<>(); |
| allTypes.addAll(getFullProperties().keySet()); |
| allTypes.addAll(getFullAttributes().keySet()); |
| return allTypes; |
| } |
| |
| public boolean containsConfigType(String configType) { |
| return properties.containsKey(configType) || attributes.containsKey(configType) || |
| (parentConfiguration != null && parentConfiguration.containsConfigType(configType)); |
| } |
| |
| public boolean containsConfig(String configType, String propertyName) { |
| return (properties.containsKey(configType) && properties.get(configType).containsKey(propertyName)) |
| || (attributes.containsKey(configType) && attributes.get(configType).values().stream().filter(map -> map.containsKey(propertyName)).findAny().isPresent()) |
| || (parentConfiguration != null && parentConfiguration.containsConfig(configType, propertyName)); |
| } |
| |
| /** |
| * Get the parent configuration. |
| * |
| * @return the parent configuration or null if no parent is set |
| */ |
| public Configuration getParentConfiguration() { |
| return parentConfiguration; |
| } |
| |
| /** |
| * Set the parent configuration. |
| * |
| * @param parent parent configuration to set |
| */ |
| public void setParentConfiguration(Configuration parent) { |
| parentConfiguration = parent; |
| } |
| |
| /** |
| * Remove all occurrences of a config type |
| */ |
| public void removeConfigType(String configType) { |
| if (properties != null) { |
| properties.remove(configType); |
| } |
| if (attributes != null) { |
| attributes.remove(configType); |
| } |
| if (parentConfiguration != null) { |
| parentConfiguration.removeConfigType(configType); |
| } |
| } |
| |
| /** |
| * Create a new {@code Configuration} based on a pair of maps. |
| * (This is just a convenience method to be able to avoid local variables in a few places.) |
| */ |
| public static Configuration of(Pair<Map<String, Map<String, String>>, Map<String, Map<String, Map<String, String>>>> propertiesAndAttributes) { |
| return new Configuration(propertiesAndAttributes.getLeft(), propertiesAndAttributes.getRight()); |
| } |
| |
| /** |
| * @return this configuration's properties and attributes as a pair of maps, |
| * in order to be able to pass around more easily without polluting non-topology code with the Configuration object |
| */ |
| public Pair<Map<String, Map<String, String>>, Map<String, Map<String, Map<String, String>>>> asPair() { |
| return Pair.of(properties, attributes); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| |
| Configuration other = (Configuration) obj; |
| |
| return Objects.equals(properties, other.properties) && |
| Objects.equals(attributes, other.attributes) && |
| Objects.equals(parentConfiguration, other.parentConfiguration); |
| } |
| } |