blob: 4c0a9400951fe8361e35e467263b34561d69a6a3 [file] [log] [blame]
/*
* 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.state;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.AmbariRuntimeException;
import org.apache.ambari.server.HostNotFoundException;
import org.apache.ambari.server.agent.stomp.AgentConfigsHolder;
import org.apache.ambari.server.agent.stomp.MetadataHolder;
import org.apache.ambari.server.agent.stomp.dto.ClusterConfigs;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.AmbariManagementControllerImpl;
import org.apache.ambari.server.events.AgentConfigsUpdateEvent;
import org.apache.ambari.server.events.HostComponentUpdate;
import org.apache.ambari.server.events.HostComponentsUpdateEvent;
import org.apache.ambari.server.events.publishers.STOMPUpdatePublisher;
import org.apache.ambari.server.orm.dao.ClusterDAO;
import org.apache.ambari.server.orm.dao.ServiceConfigDAO;
import org.apache.ambari.server.orm.entities.ClusterConfigEntity;
import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity;
import org.apache.ambari.server.orm.entities.ServiceConfigEntity;
import org.apache.ambari.server.stack.StackDirectory;
import org.apache.ambari.server.state.PropertyInfo.PropertyType;
import org.apache.ambari.server.state.configgroup.ConfigGroup;
import org.apache.ambari.server.utils.SecretReference;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;
/**
* Helper class that works with config traversals.
*/
@Singleton
public class ConfigHelper {
private Clusters clusters = null;
private AmbariMetaInfo ambariMetaInfo = null;
private ClusterDAO clusterDAO = null;
private static final String DELETED = "DELETED_";
public static final String CLUSTER_DEFAULT_TAG = "tag";
private final boolean STALE_CONFIGS_CACHE_ENABLED;
private final int STALE_CONFIGS_CACHE_EXPIRATION_TIME;
/**
* Cache for storing stale config flags. Key for cache is hash of [actualConfigs, desiredConfigs, hostName, serviceName,
* componentName].
*/
private final Cache<Integer, Boolean> staleConfigsCache;
/**
* clusterId -> hostId -> serviceName -> serviceComponentName -> state map to reduce redundant updates sending.
*/
private final Map<Long, Map<Long, Map<String, Map<String, Boolean>>>> stateCache = new HashMap<>();
private final Cache<Integer, String> refreshConfigCommandCache;
private static final Logger LOG =
LoggerFactory.getLogger(ConfigHelper.class);
/**
* List of property prefixes and names. Please keep in alphabetical order.
*/
public static final String HBASE_SITE = "hbase-site";
public static final String HDFS_SITE = "hdfs-site";
public static final String HIVE_SITE = "hive-site";
public static final String YARN_SITE = "yarn-site";
public static final String CLUSTER_ENV = "cluster-env";
public static final String CLUSTER_ENV_ALERT_REPEAT_TOLERANCE = "alerts_repeat_tolerance";
public static final String CLUSTER_ENV_RETRY_ENABLED = "command_retry_enabled";
public static final String SERVICE_CHECK_TYPE = "service_check_type";
public static final String CLUSTER_ENV_RETRY_COMMANDS = "commands_to_retry";
public static final String CLUSTER_ENV_RETRY_MAX_TIME_IN_SEC = "command_retry_max_time_in_sec";
public static final String COMMAND_RETRY_MAX_TIME_IN_SEC_DEFAULT = "600";
public static final String CLUSTER_ENV_STACK_NAME_PROPERTY = "stack_name";
public static final String CLUSTER_ENV_STACK_FEATURES_PROPERTY = "stack_features";
public static final String CLUSTER_ENV_STACK_TOOLS_PROPERTY = "stack_tools";
public static final String CLUSTER_ENV_STACK_ROOT_PROPERTY = "stack_root";
public static final String CLUSTER_ENV_STACK_PACKAGES_PROPERTY = "stack_packages";
public static final String HTTP_ONLY = "HTTP_ONLY";
public static final String HTTPS_ONLY = "HTTPS_ONLY";
public static final String SERVICE_CHECK_MINIMAL = "minimal";
/**
* The tag given to newly created versions.
*/
public static final String FIRST_VERSION_TAG = "version1";
@Inject
private Provider<MetadataHolder> m_metadataHolder;
@Inject
private Provider<AgentConfigsHolder> m_agentConfigsHolder;
@Inject
private Provider<AmbariManagementControllerImpl> m_ambariManagementController;
@Inject
private STOMPUpdatePublisher STOMPUpdatePublisher;
@Inject
private ServiceConfigDAO serviceConfigDAO;
@Inject
public ConfigHelper(Clusters c, AmbariMetaInfo metaInfo, Configuration configuration, ClusterDAO clusterDAO) {
clusters = c;
ambariMetaInfo = metaInfo;
this.clusterDAO = clusterDAO;
STALE_CONFIGS_CACHE_ENABLED = configuration.isStaleConfigCacheEnabled();
STALE_CONFIGS_CACHE_EXPIRATION_TIME = configuration.staleConfigCacheExpiration();
staleConfigsCache = CacheBuilder.newBuilder().
expireAfterWrite(STALE_CONFIGS_CACHE_EXPIRATION_TIME, TimeUnit.SECONDS).build();
refreshConfigCommandCache = CacheBuilder.newBuilder().
expireAfterWrite(STALE_CONFIGS_CACHE_EXPIRATION_TIME, TimeUnit.SECONDS).build();
}
/**
* Gets the desired tags for a cluster and host
*
* @param cluster the cluster
* @param hostName the host name
* @return a map of tag type to tag names with overrides
* @throws AmbariException
*/
public Map<String, Map<String, String>> getEffectiveDesiredTags(
Cluster cluster, String hostName) throws AmbariException {
return getEffectiveDesiredTags(cluster, hostName, null);
}
/**
* Gets the desired tags for a cluster and host
*
* @param cluster
* the cluster
* @param hostName
* the host name
* @return a map of tag type to tag names with overrides
* @throws AmbariException
*/
public Map<String, Map<String, String>> getEffectiveDesiredTags(Cluster cluster, String hostName,
@Nullable Map<String, DesiredConfig> desiredConfigs) throws AmbariException {
Host host = null;
if (hostName != null) {
try {
host = clusters.getHost(hostName);
} catch (HostNotFoundException e) {
LOG.error("Cannot get desired config for unknown host {}", hostName, e);
}
}
Map<String, HostConfig> desiredHostConfigs = (host == null) ? null
: host.getDesiredHostConfigs(cluster, desiredConfigs);
return getEffectiveDesiredTags(cluster, desiredConfigs, desiredHostConfigs);
}
/**
* Gets the desired tags for a cluster and overrides for a host
*
* @param cluster
* the cluster
* @param hostConfigOverrides
* the host overrides applied using config groups
* @param clusterDesired
* the desired configurations for the cluster. Obtaining these can be
* expensive, ans since this method could be called 10,000's of times
* when generating cluster/host responses. Therefore, the caller
* should build these once and pass them in. If {@code null}, then
* this method will retrieve them at runtime, incurring a performance
* penality.
* @return a map of tag type to tag names with overrides
*/
private Map<String, Map<String, String>> getEffectiveDesiredTags(Cluster cluster, Map<String,
DesiredConfig> clusterDesired, Map<String, HostConfig> hostConfigOverrides) {
if (null == cluster) {
clusterDesired = new HashMap<>();
}
// per method contract, lookup if not supplied
if (null == clusterDesired) {
clusterDesired = cluster.getDesiredConfigs();
}
if (null == clusterDesired) {
clusterDesired = new HashMap<>();
}
Map<String, Map<String, String>> resolved = new TreeMap<>();
// Do not use host component config mappings. Instead, the rules are:
// 1) Use the cluster desired config
// 2) override (1) with config-group overrides
for (Entry<String, DesiredConfig> clusterEntry : clusterDesired.entrySet()) {
String type = clusterEntry.getKey();
String tag = clusterEntry.getValue().getTag();
// 1) start with cluster config
if (cluster != null) {
Config config = cluster.getConfig(type, tag);
if (null == config) {
continue;
}
Map<String, String> tags = new LinkedHashMap<>();
tags.put(CLUSTER_DEFAULT_TAG, config.getTag());
// AMBARI-3672. Only consider Config groups for override tags
// tags -> (configGroupId, versionTag)
if (hostConfigOverrides != null) {
HostConfig hostConfig = hostConfigOverrides.get(config.getType());
if (hostConfig != null) {
for (Entry<Long, String> tagEntry : hostConfig
.getConfigGroupOverrides().entrySet()) {
tags.put(tagEntry.getKey().toString(), tagEntry.getValue());
}
}
}
resolved.put(type, tags);
}
}
return resolved;
}
public Set<String> filterInvalidPropertyValues(Map<PropertyInfo, String> properties, String filteredListName) {
Set<String> resultSet = new HashSet<>();
for (Iterator<Entry<PropertyInfo, String>> iterator = properties.entrySet().iterator(); iterator.hasNext();) {
Entry<PropertyInfo, String> property = iterator.next();
PropertyInfo propertyInfo = property.getKey();
String propertyValue = property.getValue();
if (propertyValue == null || propertyValue.toLowerCase().equals("null") || propertyValue.isEmpty()) {
LOG.error(String.format("Excluding property %s from %s, because of invalid or empty value!", propertyInfo.getName(), filteredListName));
iterator.remove();
} else {
resultSet.add(propertyValue);
}
}
return resultSet;
}
/**
* Get all config properties for a cluster given a set of configType to
* versionTags map. This helper method merges all the override tags with a
* the properties from parent cluster config properties
*
* @param cluster
* @param desiredTags
* @return {type : {key, value}}
*/
public Map<String, Map<String, String>> getEffectiveConfigProperties(
Cluster cluster, Map<String, Map<String, String>> desiredTags) {
Map<String, Map<String, String>> properties = new HashMap<>();
if (desiredTags != null) {
for (Entry<String, Map<String, String>> entry : desiredTags.entrySet()) {
String type = entry.getKey();
Map<String, String> propertyMap = properties.get(type);
if (propertyMap == null) {
propertyMap = new HashMap<>();
}
Map<String, String> tags = new HashMap<>(entry.getValue());
String clusterTag = tags.get(CLUSTER_DEFAULT_TAG);
// Overrides is only supported if the config type exists at cluster
// level
if (clusterTag != null) {
Config config = cluster.getConfig(type, clusterTag);
if (config != null) {
propertyMap.putAll(config.getProperties());
}
tags.remove(CLUSTER_DEFAULT_TAG);
// Now merge overrides
for (Entry<String, String> overrideEntry : tags.entrySet()) {
Config overrideConfig = cluster.getConfig(type,
overrideEntry.getValue());
if (overrideConfig != null) {
propertyMap = getMergedConfig(propertyMap, overrideConfig.getProperties());
}
}
}
properties.put(type, propertyMap);
}
}
return properties;
}
public Map<String, Map<String, String>> getEffectiveConfigProperties(String clusterName, String hostName) throws AmbariException {
Cluster cluster = clusters.getCluster(clusterName);
return getEffectiveConfigProperties(cluster, getEffectiveDesiredTags(cluster, hostName));
}
/**
* Get all config attributes for a cluster given a set of configType to
* versionTags map. This helper method merges all the override tags with a
* the attributes from parent cluster config properties
*
* @param cluster
* @param desiredTags
* @return {type : {attribute : {property, attributeValue}}
*/
public Map<String, Map<String, Map<String, String>>> getEffectiveConfigAttributes(
Cluster cluster, Map<String, Map<String, String>> desiredTags) {
Map<String, Map<String, Map<String, String>>> attributes = new HashMap<>();
if (desiredTags != null) {
for (Entry<String, Map<String, String>> entry : desiredTags.entrySet()) {
String type = entry.getKey();
Map<String, Map<String, String>> attributesMap = null;
Map<String, String> tags = new HashMap<>(entry.getValue());
String clusterTag = tags.get(CLUSTER_DEFAULT_TAG);
if (clusterTag != null) {
Config config = cluster.getConfig(type, clusterTag);
if (config != null) {
attributesMap = new TreeMap<>();
cloneAttributesMap(config.getPropertiesAttributes(), attributesMap);
}
tags.remove(CLUSTER_DEFAULT_TAG);
}
for (Entry<String, String> overrideEntry : tags.entrySet()) {
Config overrideConfig = cluster.getConfig(type,
overrideEntry.getValue());
overrideAttributes(overrideConfig, attributesMap);
}
if (attributesMap != null) {
attributes.put(type, attributesMap);
}
}
}
return attributes;
}
/**
* Merge override with original, if original property doesn't exist,
* add it to the properties
*
* @param persistedClusterConfig
* @param override
* @return
*/
public Map<String, String> getMergedConfig(Map<String,
String> persistedClusterConfig, Map<String, String> override) {
Map<String, String> finalConfig = new HashMap<>(persistedClusterConfig);
if (override != null && override.size() > 0) {
for (Entry<String, String> entry : override.entrySet()) {
Boolean deleted = 0 == entry.getKey().indexOf(DELETED);
String nameToUse = deleted ?
entry.getKey().substring(DELETED.length()) : entry.getKey();
if (finalConfig.containsKey(nameToUse)) {
finalConfig.remove(nameToUse);
}
if (!deleted) {
finalConfig.put(nameToUse, entry.getValue());
}
}
}
return finalConfig;
}
/**
* Retrieves effective configurations for specified cluster and tags and merge them with
* present before in {@code configurations}
* @param configurations configurations will be merged with effective cluster configurations
* @param configurationTags configuration tags for cluster's desired configs
* @param cluster cluster to configs retrieving
*/
public void getAndMergeHostConfigs(Map<String, Map<String, String>> configurations,
Map<String, Map<String, String>> configurationTags,
Cluster cluster) {
if (null != configurationTags && !configurationTags.isEmpty()) {
Map<String, Map<String, String>> configProperties =
getEffectiveConfigProperties(cluster, configurationTags);
// Apply the configurations present before on top of derived configs
for (Map.Entry<String, Map<String, String>> entry : configProperties.entrySet()) {
String type = entry.getKey();
Map<String, String> allLevelMergedConfig = entry.getValue();
if (configurations.containsKey(type)) {
Map<String, String> mergedConfig = getMergedConfig(allLevelMergedConfig,
configurations.get(type));
configurations.get(type).clear();
configurations.get(type).putAll(mergedConfig);
} else {
configurations.put(type, new HashMap<>());
configurations.get(type).putAll(allLevelMergedConfig);
}
}
}
}
/**
* Retrieves effective configuration attributes for specified cluster and tags and merge them with
* present before in {@code configurationAttributes}
* @param configurationAttributes configuration attributes will be merged with effective ones on the cluster
* @param configurationTags configuration tags for cluster's desired configs
* @param cluster cluster to config attributes retrieving
*/
public void getAndMergeHostConfigAttributes(Map<String, Map<String, Map<String, String>>> configurationAttributes,
Map<String, Map<String, String>> configurationTags,
Cluster cluster) {
if (null != configurationTags && !configurationTags.isEmpty()) {
Map<String, Map<String, Map<String, String>>> configAttributes =
getEffectiveConfigAttributes(cluster, configurationTags);
for (Map.Entry<String, Map<String, Map<String, String>>> attributesOccurrence : configAttributes.entrySet()) {
String type = attributesOccurrence.getKey();
Map<String, Map<String, String>> attributes = attributesOccurrence.getValue();
if (configurationAttributes != null) {
if (!configurationAttributes.containsKey(type)) {
configurationAttributes.put(type,
new TreeMap<>());
}
cloneAttributesMap(attributes,
configurationAttributes.get(type));
}
}
}
}
/**
* Merge override attributes with original ones.
* If overrideConfig#getPropertiesAttributes does not contain occurrence of override for any of
* properties from overrideConfig#getProperties then persisted attribute should be removed.
*/
public Map<String, Map<String, String>> overrideAttributes(Config overrideConfig,
Map<String, Map<String, String>> persistedAttributes) {
if (overrideConfig != null && persistedAttributes != null) {
Map<String, Map<String, String>> overrideAttributes = overrideConfig.getPropertiesAttributes();
if (overrideAttributes != null) {
cloneAttributesMap(overrideAttributes, persistedAttributes);
Map<String, String> overrideProperties = overrideConfig.getProperties();
if (overrideProperties != null) {
Set<String> overriddenProperties = overrideProperties.keySet();
for (String overriddenProperty : overriddenProperties) {
for (Entry<String, Map<String, String>> persistedAttribute : persistedAttributes.entrySet()) {
String attributeName = persistedAttribute.getKey();
Map<String, String> persistedAttributeValues = persistedAttribute.getValue();
Map<String, String> overrideAttributeValues = overrideAttributes.get(attributeName);
if (overrideAttributeValues == null || !overrideAttributeValues.containsKey(overriddenProperty)) {
persistedAttributeValues.remove(overriddenProperty);
}
}
}
}
}
}
return persistedAttributes;
}
public void cloneAttributesMap(Map<String, Map<String, String>> sourceAttributesMap,
Map<String, Map<String, String>> targetAttributesMap) {
if (sourceAttributesMap != null && targetAttributesMap != null) {
for (Entry<String, Map<String, String>> attributesEntry : sourceAttributesMap.entrySet()) {
String attributeName = attributesEntry.getKey();
if (!targetAttributesMap.containsKey(attributeName)) {
targetAttributesMap.put(attributeName, new TreeMap<>());
}
for (Entry<String, String> attributesValue : attributesEntry.getValue().entrySet()) {
targetAttributesMap.get(attributeName).put(attributesValue.getKey(), attributesValue.getValue());
}
}
}
}
public void applyCustomConfig(Map<String, Map<String, String>> configurations,
String type, String name, String value, Boolean deleted) {
if (!configurations.containsKey(type)) {
configurations.put(type, new HashMap<>());
}
String nameToUse = deleted ? DELETED + name : name;
Map<String, String> properties = configurations.get(type);
if (properties.containsKey(nameToUse)) {
properties.remove(nameToUse);
}
properties.put(nameToUse, value);
}
/**
* The purpose of this method is to determine if a
* {@link ServiceComponentHost}'s known actual configs are different than what
* is set on the cluster (the desired). The following logic is applied:
* <ul>
* <li>Desired type does not exist on the SCH (actual)
* <ul>
* <li>Type does not exist on the stack: <code>false</code></li>
* <li>Type exists on the stack: <code>true</code> if the config key is on the
* stack. otherwise <code>false</code></li>
* </ul>
* </li>
* <li>Desired type exists for the SCH
* <ul>
* <li>Desired tags already set for the SCH (actual): <code>false</code></li>
* <li>Desired tags DO NOT match SCH: <code>true</code> if the changed keys
* exist on the stack, otherwise <code>false</code></li>
* </ul>
* </li>
* </ul>
*
* @param sch
* the SCH to calcualte config staleness for (not {@code null}).
* @param requestDesiredConfigs
* the desired configurations for the cluster. Obtaining these can be
* expensive and since this method operates on SCH's, it could be
* called 10,000's of times when generating cluster/host responses.
* Therefore, the caller should build these once and pass them in. If
* {@code null}, then this method will retrieve them at runtime,
* incurring a performance penality.
*
* @return <code>true</code> if the actual configs are stale
*/
public boolean isStaleConfigs(ServiceComponentHost sch, Map<String, DesiredConfig> requestDesiredConfigs)
throws AmbariException {
HostComponentDesiredStateEntity hostComponentDesiredStateEntity = sch.getDesiredStateEntity();
return isStaleConfigs(sch, requestDesiredConfigs, hostComponentDesiredStateEntity);
}
public boolean isStaleConfigs(ServiceComponentHost sch, Map<String, DesiredConfig> requestDesiredConfigs,
HostComponentDesiredStateEntity hostComponentDesiredStateEntity)
throws AmbariException {
boolean stale = calculateIsStaleConfigs(sch, requestDesiredConfigs, hostComponentDesiredStateEntity);
if (LOG.isDebugEnabled()) {
LOG.debug("Cache configuration staleness for host {} and component {} as {}",
sch.getHostName(), sch.getServiceComponentName(), stale);
}
return stale;
}
/**
* Remove configs by type
*
* @param type config Type
*/
@Transactional
public void removeConfigsByType(Cluster cluster, String type) {
Set<String> globalVersions = cluster.getConfigsByType(type).keySet();
for (String version : globalVersions) {
ClusterConfigEntity clusterConfigEntity = clusterDAO.findConfig
(cluster.getClusterId(), type, version);
clusterDAO.removeConfig(clusterConfigEntity);
}
}
/**
* Gets all the config dictionary where property with the given name is present in stack definitions
*
* @param stackId
* @param propertyName
*/
public Set<String> findConfigTypesByPropertyName(StackId stackId, String propertyName, String clusterName) throws AmbariException {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(),
stackId.getStackVersion());
Set<String> result = new HashSet<>();
for (Service service : clusters.getCluster(clusterName).getServices().values()) {
Set<PropertyInfo> stackProperties = ambariMetaInfo.getServiceProperties(stack.getName(), stack.getVersion(), service.getName());
Set<PropertyInfo> stackLevelProperties = ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion());
stackProperties.addAll(stackLevelProperties);
for (PropertyInfo stackProperty : stackProperties) {
if (stackProperty.getName().equals(propertyName)) {
String configType = fileNameToConfigType(stackProperty.getFilename());
result.add(configType);
}
}
}
return result;
}
/**
* Gets a map of config types to password property names to password property value names,
* that are credential store enabled.
*
* @param stackId
* @param service
* @return
* @throws AmbariException
*/
public Map<String, Map<String, String>> getCredentialStoreEnabledProperties(StackId stackId, Service service)
throws AmbariException {
PropertyType propertyType = PropertyType.PASSWORD;
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
Map<String, Map<String, String>> result = new HashMap<>();
Map<String, String> passwordProperties;
Set<PropertyInfo> serviceProperties = ambariMetaInfo.getServiceProperties(stack.getName(), stack.getVersion(), service.getName());
for (PropertyInfo serviceProperty : serviceProperties) {
if (serviceProperty.getPropertyTypes().contains(propertyType)) {
if (!serviceProperty.getPropertyValueAttributes().isKeyStore()) {
continue;
}
String stackPropertyConfigType = fileNameToConfigType(serviceProperty.getFilename());
passwordProperties = result.get(stackPropertyConfigType);
if (passwordProperties == null) {
passwordProperties = new HashMap<>();
result.put(stackPropertyConfigType, passwordProperties);
}
// If the password property is used by another property, it means the password property
// is a password value name while the use is the password alias name. If the user property
// is from another config type, include that in the password alias name as name:type.
if (serviceProperty.getUsedByProperties().size() > 0) {
for (PropertyDependencyInfo usedByProperty : serviceProperty.getUsedByProperties()) {
String propertyName = usedByProperty.getName();
if (!StringUtils.isEmpty(usedByProperty.getType())) {
propertyName += ':' + usedByProperty.getType();
}
passwordProperties.put(propertyName, serviceProperty.getName());
}
}
else {
passwordProperties.put(serviceProperty.getName(), serviceProperty.getName());
}
}
}
return result;
}
/***
* Fetch user to group mapping from the cluster configs. UserGroupEntries contain information regarding the group that the user is associated to.
* @param stackId
* @param cluster
* @param desiredConfigs
* @return
* @throws AmbariException
*/
public Map<String, Set<String>> createUserGroupsMap(StackId stackId,
Cluster cluster, Map<String, DesiredConfig> desiredConfigs) throws AmbariException {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
Map<String, ServiceInfo> servicesMap = ambariMetaInfo.getServices(stack.getName(), stack.getVersion());
Set<PropertyInfo> stackProperties = ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion());
return createUserGroupsMap(cluster, desiredConfigs, servicesMap, stackProperties);
}
/***
* Fetch user to group mapping from the cluster configs. UserGroupEntries contain information regarding the group that the user is associated to.
* @param cluster
* @param desiredConfigs
* @param servicesMap
* @param stackProperties
* @return
* @throws AmbariException
*/
public Map<String, Set<String>> createUserGroupsMap(
Cluster cluster, Map<String, DesiredConfig> desiredConfigs,
Map<String, ServiceInfo> servicesMap, Set<PropertyInfo> stackProperties) throws AmbariException {
Map<String, Set<String>> userGroupsMap = new HashMap<>();
Map<PropertyInfo, String> userProperties = getPropertiesWithPropertyType(
PropertyType.USER, cluster, desiredConfigs, servicesMap, stackProperties);
Map<PropertyInfo, String> groupProperties = getPropertiesWithPropertyType(
PropertyType.GROUP, cluster, desiredConfigs, servicesMap, stackProperties);
if(userProperties != null && groupProperties != null) {
for(Map.Entry<PropertyInfo, String> userProperty : userProperties.entrySet()) {
PropertyInfo userPropertyInfo = userProperty.getKey();
String userPropertyValue = userProperty.getValue();
if(userPropertyInfo.getPropertyValueAttributes() != null
&& userPropertyInfo.getPropertyValueAttributes().getUserGroupEntries() != null) {
Set<String> groupPropertyValues = new HashSet<>();
Collection<UserGroupInfo> userGroupEntries = userPropertyInfo.getPropertyValueAttributes().getUserGroupEntries();
for (UserGroupInfo userGroupInfo : userGroupEntries) {
boolean found = false;
for(Map.Entry<PropertyInfo, String> groupProperty : groupProperties.entrySet()) {
PropertyInfo groupPropertyInfo = groupProperty.getKey();
String groupPropertyValue = groupProperty.getValue();
if(StringUtils.equals(userGroupInfo.getType(),
ConfigHelper.fileNameToConfigType(groupPropertyInfo.getFilename()))
&& StringUtils.equals(userGroupInfo.getName(), groupPropertyInfo.getName())) {
groupPropertyValues.add(groupPropertyValue);
found = true;
}
}
if(!found) {
//Log error if the user-group mapping is not found
LOG.error("User group mapping property {" + userGroupInfo.getType() + "/" + userGroupInfo.getName() + "} is missing for user property {" + ConfigHelper.fileNameToConfigType(userPropertyInfo.getFilename()) + "/" + userPropertyInfo.getName() + "} (username = " + userPropertyInfo.getValue() +")");
}
}
userGroupsMap.put(userPropertyValue, groupPropertyValues);
}
}
}
return userGroupsMap;
}
/***
* Fetch all the properties of a given PropertyType. For eg: Fetch all cluster configs that are of type "user"
* @param stackId
* @param propertyType
* @param cluster
* @param desiredConfigs
* @return
* @throws AmbariException
*/
public Map<PropertyInfo, String> getPropertiesWithPropertyType(StackId stackId, PropertyType propertyType,
Cluster cluster, Map<String, DesiredConfig> desiredConfigs) throws AmbariException {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
Map<String, ServiceInfo> servicesMap = ambariMetaInfo.getServices(stack.getName(), stack.getVersion());
Set<PropertyInfo> stackProperties = ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion());
return getPropertiesWithPropertyType(propertyType, cluster, desiredConfigs, servicesMap, stackProperties);
}
/***
* Fetch all the properties of a given PropertyType. For eg: Fetch all cluster configs that are of type "user"
* @param propertyType
* @param cluster
* @param desiredConfigs
* @param servicesMap
* @param stackProperties
* @return
*/
public Map<PropertyInfo, String> getPropertiesWithPropertyType(PropertyType propertyType, Cluster cluster,
Map<String, DesiredConfig> desiredConfigs, Map<String, ServiceInfo> servicesMap,
Set<PropertyInfo> stackProperties) throws AmbariException {
Map<String, Config> actualConfigs = new HashMap<>();
Map<PropertyInfo, String> result = new HashMap<>();
for (Map.Entry<String, DesiredConfig> desiredConfigEntry : desiredConfigs.entrySet()) {
String configType = desiredConfigEntry.getKey();
DesiredConfig desiredConfig = desiredConfigEntry.getValue();
actualConfigs.put(configType, cluster.getConfig(configType, desiredConfig.getTag()));
}
for (Service service : cluster.getServices().values()) {
ServiceInfo serviceInfo = servicesMap.get(service.getName());
if (serviceInfo == null) {
continue;
}
Set<PropertyInfo> serviceProperties = new HashSet<>(serviceInfo.getProperties());
for (PropertyInfo serviceProperty : serviceProperties) {
if (serviceProperty.getPropertyTypes().contains(propertyType)) {
String stackPropertyConfigType = fileNameToConfigType(serviceProperty.getFilename());
try {
String property = actualConfigs.get(stackPropertyConfigType).getProperties().get(serviceProperty.getName());
if (null == property){
LOG.error(String.format("Unable to obtain property values for %s with property attribute %s. "
+ "The property does not exist in version %s of %s configuration.",
serviceProperty.getName(),
propertyType,
desiredConfigs.get(stackPropertyConfigType),
stackPropertyConfigType
));
} else {
result.put(serviceProperty, property);
}
} catch (Exception ignored) {
}
}
}
}
for (PropertyInfo stackProperty : stackProperties) {
if (stackProperty.getPropertyTypes().contains(propertyType)) {
String stackPropertyConfigType = fileNameToConfigType(stackProperty.getFilename());
if (actualConfigs.containsKey(stackPropertyConfigType)) {
result.put(stackProperty, actualConfigs.get(stackPropertyConfigType).getProperties().get(stackProperty.getName()));
}
}
}
return result;
}
/***
* Fetch all the property values of a given PropertyType. For eg: Fetch all cluster configs that are of type "user"
* @param stackId
* @param propertyType
* @param cluster
* @param desiredConfigs
* @return
* @throws AmbariException
*/
public Set<String> getPropertyValuesWithPropertyType(StackId stackId, PropertyType propertyType,
Cluster cluster, Map<String,
DesiredConfig> desiredConfigs) throws AmbariException {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
Map<String, ServiceInfo> servicesMap = ambariMetaInfo.getServices(stack.getName(), stack.getVersion());
Set<PropertyInfo> stackProperties = ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion());
return getPropertyValuesWithPropertyType(propertyType, cluster, desiredConfigs, servicesMap, stackProperties);
}
/***
* Fetch all the property values of a given PropertyType. For eg: Fetch all cluster configs that are of type "user"
* @param propertyType
* @param cluster
* @param desiredConfigs
* @param servicesMap
* @param stackProperties
* @return
* @throws AmbariException
*/
public Set<String> getPropertyValuesWithPropertyType(PropertyType propertyType,
Cluster cluster, Map<String, DesiredConfig> desiredConfigs,
Map<String, ServiceInfo> servicesMap,
Set<PropertyInfo> stackProperties) throws AmbariException {
Map<String, Config> actualConfigs = new HashMap<>();
Set<String> result = new HashSet<>();
for (Map.Entry<String, DesiredConfig> desiredConfigEntry : desiredConfigs.entrySet()) {
String configType = desiredConfigEntry.getKey();
DesiredConfig desiredConfig = desiredConfigEntry.getValue();
actualConfigs.put(configType, cluster.getConfig(configType, desiredConfig.getTag()));
}
for (Service service : cluster.getServices().values()) {
// !!! the services map is from the stack, which may not contain services in the cluster.
// This is the case for upgrades where the target stack may remove services
if (!servicesMap.containsKey(service.getName())) {
continue;
}
Set<PropertyInfo> serviceProperties = new HashSet<>(servicesMap.get(service.getName()).getProperties());
for (PropertyInfo serviceProperty : serviceProperties) {
if (serviceProperty.getPropertyTypes().contains(propertyType)) {
String stackPropertyConfigType = fileNameToConfigType(serviceProperty.getFilename());
try {
String property = actualConfigs.get(stackPropertyConfigType).getProperties().get(serviceProperty.getName());
if (null == property){
LOG.error(String.format("Unable to obtain property values for %s with property attribute %s. "
+ "The property does not exist in version %s of %s configuration.",
serviceProperty.getName(),
propertyType,
desiredConfigs.get(stackPropertyConfigType),
stackPropertyConfigType
));
} else {
result.add(property);
}
} catch (Exception ignored) {
}
}
}
}
for (PropertyInfo stackProperty : stackProperties) {
if (stackProperty.getPropertyTypes().contains(propertyType)) {
String stackPropertyConfigType = fileNameToConfigType(stackProperty.getFilename());
if (actualConfigs.containsKey(stackPropertyConfigType)) {
result.add(actualConfigs.get(stackPropertyConfigType).getProperties().get(stackProperty.getName()));
}
}
}
return result;
}
public void checkAllStageConfigsPresentInDesiredConfigs(Cluster cluster) throws AmbariException {
StackId stackId = cluster.getDesiredStackVersion();
Set<String> stackConfigTypes = ambariMetaInfo.getStack(stackId.getStackName(),
stackId.getStackVersion()).getConfigTypeAttributes().keySet();
Map<String, Config> actualConfigs = new HashMap<>();
Map<String, DesiredConfig> desiredConfigs = cluster.getDesiredConfigs();
for (Map.Entry<String, DesiredConfig> desiredConfigEntry : desiredConfigs.entrySet()) {
String configType = desiredConfigEntry.getKey();
DesiredConfig desiredConfig = desiredConfigEntry.getValue();
actualConfigs.put(configType, cluster.getConfig(configType, desiredConfig.getTag()));
}
for (String stackConfigType : stackConfigTypes) {
if (!actualConfigs.containsKey(stackConfigType)) {
LOG.error(String.format("Unable to find stack configuration %s in ambari configs!", stackConfigType));
}
}
}
/***
* Fetch all the config values of a given PropertyType. For eg: Fetch all stack configs that are of type "user"
* @param cluster
* @param configType
* @param propertyName
* @return
* @throws AmbariException
*/
public String getPropertyValueFromStackDefinitions(Cluster cluster, String configType, String propertyName) throws AmbariException {
Set<StackId> stackIds = new HashSet<>();
for (Service service : cluster.getServices().values()) {
stackIds.add(service.getDesiredStackId());
}
for (StackId stackId : stackIds) {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(),
stackId.getStackVersion());
for (ServiceInfo serviceInfo : stack.getServices()) {
Set<PropertyInfo> serviceProperties = ambariMetaInfo.getServiceProperties(stack.getName(), stack.getVersion(), serviceInfo.getName());
Set<PropertyInfo> stackProperties = ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion());
serviceProperties.addAll(stackProperties);
for (PropertyInfo stackProperty : serviceProperties) {
String stackPropertyConfigType = fileNameToConfigType(stackProperty.getFilename());
if (stackProperty.getName().equals(propertyName) && stackPropertyConfigType.equals(configType)) {
return stackProperty.getValue();
}
}
}
}
return null;
}
/**
* Gets the configuration value referenced by the specified placeholder from
* the cluster configuration. This will take a configuration placeholder such
* as {{hdfs-site/foo}} and return the value of {@code foo} defined in
* {@code hdfs-site}.
*
* @param cluster the cluster to use when rendering the placeholder value (not
* {@code null}).
* @param placeholder the placeholder value, such as {{hdfs-site/foobar}} (not
* {@code null} )
* @return the configuration value, or {@code null} if none.
* @throws AmbariException if there was a problem parsing the placeholder or retrieving the
* referenced value.
*/
public String getPlaceholderValueFromDesiredConfigurations(Cluster cluster,
String placeholder) {
// remove the {{ and }} from the placholder
if (placeholder.startsWith("{{") && placeholder.endsWith("}}")) {
placeholder = placeholder.substring(2, placeholder.length() - 2).trim();
}
// break up hdfs-site/foobar into hdfs-site and foobar
int delimiterPosition = placeholder.indexOf("/");
if (delimiterPosition < 0) {
return placeholder;
}
String configType = placeholder.substring(0, delimiterPosition);
String propertyName = placeholder.substring(delimiterPosition + 1,
placeholder.length());
// return the value if it exists, otherwise return the placeholder
String value = getValueFromDesiredConfigurations(cluster, configType, propertyName);
return value != null ? value : placeholder;
}
public String getValueFromDesiredConfigurations(Cluster cluster, String configType, String propertyName) {
Map<String, DesiredConfig> desiredConfigs = cluster.getDesiredConfigs();
DesiredConfig desiredConfig = desiredConfigs.get(configType);
if(desiredConfig != null) {
Config config = cluster.getConfig(configType, desiredConfig.getTag());
Map<String, String> configurationProperties = config.getProperties();
if (null != configurationProperties) {
String value = configurationProperties.get(propertyName);
if (null != value) {
return value;
}
}
}
return null;
}
public ServiceInfo getPropertyOwnerService(Cluster cluster, String configType, String propertyName) throws AmbariException {
for (Service service : cluster.getServices().values()) {
StackId stackId = service.getDesiredStackId();
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
for (ServiceInfo serviceInfo : stack.getServices()) {
Set<PropertyInfo> serviceProperties = ambariMetaInfo.getServiceProperties(stack.getName(), stack.getVersion(), serviceInfo.getName());
for (PropertyInfo stackProperty : serviceProperties) {
String stackPropertyConfigType = fileNameToConfigType(stackProperty.getFilename());
if (stackProperty.getName().equals(propertyName) && stackPropertyConfigType.equals(configType)) {
return serviceInfo;
}
}
}
}
return null;
}
public Set<PropertyInfo> getServiceProperties(Cluster cluster, String serviceName) throws AmbariException {
// The original implementation of this method is to return all properties regardless of whether
// they should be excluded or not. By setting removeExcluded to false in the method invocation
// below, no attempt will be made to remove properties that exist in excluded types.
Service service = cluster.getService(serviceName);
return getServiceProperties(service.getDesiredStackId(), serviceName, false);
}
/**
* Retrieves a Set of PropertyInfo objects containing the relevant properties for the requested
* service.
* <p/>
* If <code>removeExcluded</code> is <code>true</code>, the service's excluded configuration types
* are used to prune off PropertyInfos that should be ignored; else if <code>false</code>, all
* PropertyInfos will be returned.
*
* @param stackId a StackId declaring the relevant stack
* @param serviceName a String containing the requested service's name
* @param removeExcluded a boolean value indicating whether to remove properties from excluded
* configuration types (<code>true</code>) or return the complete set of properties regardless of exclusions (<code>false</code>)
* @return a Set of PropertyInfo objects for the requested service
* @throws AmbariException if the requested stack or the requested service is not found
*/
public Set<PropertyInfo> getServiceProperties(StackId stackId, String serviceName, boolean removeExcluded)
throws AmbariException {
ServiceInfo service = ambariMetaInfo.getService(stackId.getStackName(), stackId.getStackVersion(), serviceName);
Set<PropertyInfo> properties = new HashSet<>(service.getProperties());
if (removeExcluded) {
Set<String> excludedConfigTypes = service.getExcludedConfigTypes();
// excludedConfigTypes can be null since org.apache.ambari.server.state.ServiceInfo.setExcludedConfigTypes()
// allows for null values
if ((excludedConfigTypes != null) && !excludedConfigTypes.isEmpty()) {
// Iterate through the set of found PropertyInfo instances and remove ones that should be
// excluded.
Iterator<PropertyInfo> iterator = properties.iterator();
while (iterator.hasNext()) {
PropertyInfo propertyInfo = iterator.next();
// If the config type for the current PropertyInfo is containing within an excluded type,
// remove it from the set of properties being returned
if (excludedConfigTypes.contains(ConfigHelper.fileNameToConfigType(propertyInfo.getFilename()))) {
iterator.remove();
}
}
}
}
return properties;
}
public Set<PropertyInfo> getStackProperties(Cluster cluster) throws AmbariException {
Set<StackId> stackIds = new HashSet<>();
for (Service service : cluster.getServices().values()) {
stackIds.add(service.getDesiredStackId());
}
Set<PropertyInfo> propertySets = new HashSet<>();
for (StackId stackId : stackIds) {
StackInfo stack = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
propertySets.addAll(ambariMetaInfo.getStackProperties(stack.getName(), stack.getVersion()));
}
return propertySets;
}
public void updateConfigType(Cluster cluster, StackId stackId, AmbariManagementController controller,
String configType, Map<String, String> updates,
Collection<String> removals,
String authenticatedUserName,
String serviceVersionNote) throws AmbariException {
updateConfigType(cluster, stackId, controller, configType, updates, removals, authenticatedUserName,
serviceVersionNote, null, true);
}
public void updateBulkConfigType(Cluster cluster, StackId stackId, AmbariManagementController controller,
Iterable<String> configTypes,
Map<String, Map<String, String>> updates,
Map<String, Collection<String>> removals,
String authenticatedUserName,
String serviceVersionNote) throws AmbariException, AmbariRuntimeException {
Map<String, DesiredConfig> desiredConfig = cluster.getDesiredConfigs();
Boolean[] doUpdateAgentConfigs = {false};
LOG.info("Bulk config update. Starting...");
configTypes.forEach(configType -> {
try {
Boolean updated = updateConfigType(
cluster, stackId, controller,
configType,
updates.get(configType),
removals.get(configType),
authenticatedUserName,
serviceVersionNote,
desiredConfig,
false
);
LOG.info("Bulk config update. Working with {}...{}", configType, updated ? "updated" : "not updated");
doUpdateAgentConfigs[0] = doUpdateAgentConfigs[0] || updated;
} catch (AmbariException e) {
throw new AmbariRuntimeException(e);
}
});
LOG.info("Bulk config update, agent update is {} required", (doUpdateAgentConfigs[0]) ? "" : "not");
if (doUpdateAgentConfigs[0]) {
updateAgentConfigs(Collections.singleton(cluster.getClusterName()));
}
}
/**
* A helper method to create a new {@link Config} for a given configuration
* type and updates to the current values, if any. This method will perform the following tasks:
* <ul>
* <li>Merge the specified updates with the properties of the current version of the
* configuration</li>
* <li>Create a {@link Config} in the cluster for the specified type. This
* will have the proper versions and tags set automatically.</li>
* <li>Set the cluster's {@link DesiredConfig} to the new configuration</li>
* <li>Create an entry in the configuration history with a note and username.</li>
* <ul>
*
* @throws AmbariException
*/
public Boolean updateConfigType(Cluster cluster, StackId stackId, AmbariManagementController controller,
String configType, Map<String, String> updates,
Collection<String> removals,
String authenticatedUserName,
String serviceVersionNote,
@Nullable Map<String, DesiredConfig> desiredConfig,
Boolean doUpdateAgentConfigs) throws AmbariException {
// Nothing to update or remove
if (configType == null ||
(updates == null || updates.isEmpty()) &&
(removals == null || removals.isEmpty())) {
return false;
}
Config oldConfig = (desiredConfig != null && desiredConfig.containsKey(configType))
? cluster.getConfig(configType, desiredConfig.get(configType).getTag())
: cluster.getDesiredConfigByType(configType);
Map<String, String> oldConfigProperties;
Map<String, String> properties = new HashMap<>();
Map<String, Map<String, String>> propertiesAttributes = new HashMap<>();
if (oldConfig == null) {
oldConfigProperties = null;
} else {
oldConfigProperties = oldConfig.getProperties();
if (oldConfigProperties != null) {
properties.putAll(oldConfigProperties);
}
if (oldConfig.getPropertiesAttributes() != null) {
propertiesAttributes.putAll(oldConfig.getPropertiesAttributes());
}
}
if (updates != null) {
properties.putAll(updates);
}
// Remove properties that need to be removed.
if (removals != null) {
for (String propertyName : removals) {
properties.remove(propertyName);
for (Map<String, String> attributesMap: propertiesAttributes.values()) {
attributesMap.remove(propertyName);
}
}
}
if ((oldConfigProperties == null || !Maps.difference(oldConfigProperties, properties).areEqual())
&& createConfigType(cluster, stackId, controller, configType, properties, propertiesAttributes,
authenticatedUserName, serviceVersionNote)) {
if (doUpdateAgentConfigs) {
updateAgentConfigs(Collections.singleton(cluster.getClusterName()));
}
return true;
}
return false;
}
public void createConfigType(Cluster cluster, StackId stackId,
AmbariManagementController controller, String configType, Map<String, String> properties,
String authenticatedUserName, String serviceVersionNote) throws AmbariException {
if (createConfigType(cluster, stackId, controller, configType, properties,
new HashMap<>(), authenticatedUserName, serviceVersionNote)) {
updateAgentConfigs(Collections.singleton(cluster.getClusterName()));
}
}
public boolean createConfigType(Cluster cluster, StackId stackId,
AmbariManagementController controller, String configType, Map<String, String> properties,
Map<String, Map<String, String>> propertyAttributes, String authenticatedUserName,
String serviceVersionNote) throws AmbariException {
// create the configuration history entry
Config baseConfig = createConfig(cluster, stackId, controller, configType, FIRST_VERSION_TAG,
properties, propertyAttributes);
if (baseConfig != null) {
cluster.addDesiredConfig(authenticatedUserName,
Collections.singleton(baseConfig), serviceVersionNote);
return true;
}
return false;
}
/**
* Create configurations and assign them for services.
* @param cluster the cluster
* @param controller the controller
* @param stackId the stack to create the new properties for
* @param batchProperties the type->config map batch of properties
* @param authenticatedUserName the user that initiated the change
* @param serviceVersionNote the service version note
* @return true if configs were created
* @throws AmbariException
*/
public boolean createConfigTypes(Cluster cluster, StackId stackId,
AmbariManagementController controller, Map<String, Map<String, String>> batchProperties,
String authenticatedUserName, String serviceVersionNote) throws AmbariException {
Map<String, Set<Config>> serviceMapped = new HashMap<>();
for (Map.Entry<String, Map<String, String>> entry : batchProperties.entrySet()) {
String type = entry.getKey();
Map<String, String> properties = entry.getValue();
Config baseConfig = createConfig(cluster, stackId, controller, type, FIRST_VERSION_TAG,
properties, Collections.emptyMap());
if (null != baseConfig) {
try {
String service = cluster.getServiceByConfigType(type);
if (!serviceMapped.containsKey(service)) {
serviceMapped.put(service, new HashSet<>());
}
serviceMapped.get(service).add(baseConfig);
} catch (Exception e) {
// !!! ignore
}
}
}
// create the configuration history entries
boolean added = false;
for (Set<Config> configs : serviceMapped.values()) {
if (!configs.isEmpty()) {
cluster.addDesiredConfig(authenticatedUserName, configs, serviceVersionNote);
added = true;
}
}
return added;
}
/**
* Creates a new configuration using the specified tag as the first version
* tag. Otherwise, the configuration will be created with {@literal version}
* along with the current timestamp.
*
* @param cluster
* the cluster (not {@code null}).
* @param stackId
* the stack to create the new properties for
* @param controller
* the controller which actually creates the configuration (not
* {@code null}).
* @param stackId
* the stack to create the new properties for
* @param type
* the new configuration type (not {@code null}).
* @param tag
* the initial tag; if this configuration already exists, it will use
* the timestamp along with {@literal version}.
* @param properties
* the properties to persist (not {@code null}).
* @param propertyAttributes
* the attributes to persist, or {@code null} for none.
* @return
* @throws AmbariException
*/
Config createConfig(Cluster cluster, StackId stackId, AmbariManagementController controller,
String type, String tag, Map<String, String> properties,
Map<String, Map<String, String>> propertyAttributes) throws AmbariException {
// if the configuration is not new, then create a timestamp tag
if (cluster.getConfigsByType(type) != null) {
tag = "version" + System.currentTimeMillis();
}
Map<PropertyType, Set<String>> propertiesTypes = cluster.getConfigPropertiesTypes(type);
if(propertiesTypes.containsKey(PropertyType.PASSWORD)) {
for(String passwordProperty : propertiesTypes.get(PropertyType.PASSWORD)) {
if(properties.containsKey(passwordProperty)) {
String passwordPropertyValue = properties.get(passwordProperty);
if (!SecretReference.isSecret(passwordPropertyValue)) {
continue;
}
SecretReference ref = new SecretReference(passwordPropertyValue, cluster);
String refValue = ref.getValue();
properties.put(passwordProperty, refValue);
}
}
}
return controller.createConfig(cluster, stackId, type, properties, tag, propertyAttributes);
}
/**
* Gets the default properties for the specified service. These properties
* represent those which would be used when a service is first installed.
*
* @param stack
* the stack to pull stack-values from (not {@code null})
* @return a mapping of configuration type to map of key/value pairs for the
* default configurations.
* @throws AmbariException
*/
public Map<String, Map<String, String>> getDefaultStackProperties(StackId stack)
throws AmbariException {
Map<String, Map<String, String>> defaultPropertiesByType = new HashMap<>();
// populate the stack (non-service related) properties
Set<org.apache.ambari.server.state.PropertyInfo> stackConfigurationProperties = ambariMetaInfo.getStackProperties(
stack.getStackName(), stack.getStackVersion());
for (PropertyInfo stackDefaultProperty : stackConfigurationProperties) {
String type = ConfigHelper.fileNameToConfigType(stackDefaultProperty.getFilename());
if (!defaultPropertiesByType.containsKey(type)) {
defaultPropertiesByType.put(type, new HashMap<>());
}
defaultPropertiesByType.get(type).put(stackDefaultProperty.getName(),
stackDefaultProperty.getValue());
}
return defaultPropertiesByType;
}
/**
*
* @param stack
* the stack to pull stack-values from (not {@code null})
* @param serviceName
* the service name {@code null}).
* @return a mapping of configuration type to map of key/value pairs for the
* default configurations.
* @throws AmbariException
*/
public Map<String, Map<String, String>> getDefaultProperties(StackId stack, String serviceName)
throws AmbariException {
Map<String, Map<String, String>> defaultPropertiesByType = new HashMap<>();
// populate the stack (non-service related) properties first
Set<org.apache.ambari.server.state.PropertyInfo> stackConfigurationProperties = ambariMetaInfo.getStackProperties(
stack.getStackName(), stack.getStackVersion());
for (PropertyInfo stackDefaultProperty : stackConfigurationProperties) {
String type = ConfigHelper.fileNameToConfigType(stackDefaultProperty.getFilename());
if (!defaultPropertiesByType.containsKey(type)) {
defaultPropertiesByType.put(type, new HashMap<>());
}
defaultPropertiesByType.get(type).put(stackDefaultProperty.getName(),
stackDefaultProperty.getValue());
}
// for every installed service, populate the default service properties
Set<org.apache.ambari.server.state.PropertyInfo> serviceConfigurationProperties = ambariMetaInfo.getServiceProperties(
stack.getStackName(), stack.getStackVersion(), serviceName);
// !!! use new stack as the basis
for (PropertyInfo serviceDefaultProperty : serviceConfigurationProperties) {
String type = ConfigHelper.fileNameToConfigType(serviceDefaultProperty.getFilename());
if (!defaultPropertiesByType.containsKey(type)) {
defaultPropertiesByType.put(type, new HashMap<>());
}
defaultPropertiesByType.get(type).put(serviceDefaultProperty.getName(),
serviceDefaultProperty.getValue());
}
return defaultPropertiesByType;
}
//TODO remove after UI will start usage of stale configs flag from HostComponentState update event.
private boolean calculateIsStaleConfigs(ServiceComponentHost sch, Map<String, DesiredConfig> desiredConfigs,
HostComponentDesiredStateEntity hostComponentDesiredStateEntity) throws AmbariException {
if (sch.isRestartRequired(hostComponentDesiredStateEntity)) {
return true;
}
Map<String, HostConfig> actual = sch.getActualConfigs();
if (null == actual || actual.isEmpty()) {
return false;
}
Cluster cluster = clusters.getClusterById(sch.getClusterId());
Map<String, Map<String, String>> desired = getEffectiveDesiredTags(cluster, sch.getHostName(),
desiredConfigs);
Boolean stale = null;
int staleHash = 0;
if (STALE_CONFIGS_CACHE_ENABLED){
staleHash = Objects.hashCode(actual.hashCode(),
desired.hashCode(),
sch.getHostName(),
sch.getServiceComponentName(),
sch.getServiceName());
stale = staleConfigsCache.getIfPresent(staleHash);
if(stale != null) {
return stale;
}
}
stale = false;
StackId stackId = sch.getServiceComponent().getDesiredStackId();
StackInfo stackInfo = ambariMetaInfo.getStack(stackId);
ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(),
stackId.getStackVersion(), sch.getServiceName());
ComponentInfo componentInfo = serviceInfo.getComponentByName(sch.getServiceComponentName());
// Configs are considered stale when:
// - desired type DOES NOT exist in actual
// --- desired type DOES NOT exist in stack: not_stale
// --- desired type DOES exist in stack: check stack for any key: stale
// - desired type DOES exist in actual
// --- desired tags DO match actual tags: not_stale
// --- desired tags DO NOT match actual tags
// ---- merge values, determine changed keys, check stack: stale
Iterator<Entry<String, Map<String, String>>> it = desired.entrySet().iterator();
List<String> changedProperties = new LinkedList<>();
while (it.hasNext()) {
boolean staleEntry = false;
Entry<String, Map<String, String>> desiredEntry = it.next();
String type = desiredEntry.getKey();
Map<String, String> tags = desiredEntry.getValue();
if (!actual.containsKey(type)) {
// desired is set, but actual is not
staleEntry = (serviceInfo.hasConfigDependency(type) || componentInfo.hasConfigType(type));
} else {
// desired and actual both define the type
HostConfig hc = actual.get(type);
Map<String, String> actualTags = buildTags(hc);
if (!isTagChanged(tags, actualTags, hasGroupSpecificConfigsForType(cluster, sch.getHostName(), type))) {
staleEntry = false;
} else {
staleEntry = (serviceInfo.hasConfigDependency(type) || componentInfo.hasConfigType(type));
if (staleEntry) {
Collection<String> changedKeys = findChangedKeys(cluster, type, tags.values(), actualTags.values());
changedProperties.addAll(changedKeys);
}
}
}
stale = stale | staleEntry;
}
String refreshCommand = calculateRefreshCommand(stackInfo.getRefreshCommandConfiguration(), sch, changedProperties);
if (STALE_CONFIGS_CACHE_ENABLED) {
staleConfigsCache.put(staleHash, stale);
if (refreshCommand != null) {
refreshConfigCommandCache.put(staleHash, refreshCommand);
}
}
// gather all changed properties and see if we can find a common refreshConfigs command for this component
if (LOG.isDebugEnabled()) {
LOG.debug("Changed properties {} ({}) {} : COMMAND: {}", stale, sch.getServiceComponentName(), sch.getHostName(), refreshCommand);
for (String p : changedProperties) {
LOG.debug(p);
}
}
return stale;
}
/**
* Checks populated services for staled configs and updates agent configs.
* Method retrieves actual agent configs and compares them with just generated to identify stale configs.
* Then config updates are sent to agents.
* @param updatedClusters names of clusters with changed configs
* @throws AmbariException
*/
public void updateAgentConfigs(Set<String> updatedClusters) throws AmbariException {
// get all used clusters in request
List<Cluster> clustersInUse = new ArrayList<>();
for (String clusterName : updatedClusters) {
Cluster cluster;
cluster = clusters.getCluster(clusterName);
clustersInUse.add(cluster);
}
// get all current and previous host configs
Map<Long, AgentConfigsUpdateEvent> currentConfigEvents = new HashMap<>();
Map<Long, AgentConfigsUpdateEvent> previousConfigEvents = new HashMap<>();
for (Cluster cluster : clustersInUse) {
for (Host host : cluster.getHosts()) {
Long hostId = host.getHostId();
if (!currentConfigEvents.containsKey(hostId)) {
currentConfigEvents.put(host.getHostId(), m_agentConfigsHolder.get().getCurrentData(hostId));
}
if (!previousConfigEvents.containsKey(host.getHostId())) {
previousConfigEvents.put(host.getHostId(),
m_agentConfigsHolder.get().initializeDataIfNeeded(hostId, true));
}
}
}
for (Cluster cluster : clustersInUse) {
Map<Long, Map<String, Collection<String>>> changedConfigs = new HashMap<>();
for (Host host : cluster.getHosts()) {
AgentConfigsUpdateEvent currentConfigData = currentConfigEvents.get(host.getHostId());
AgentConfigsUpdateEvent previousConfigsData = previousConfigEvents.get(host.getHostId());
SortedMap<String, SortedMap<String, String>> currentConfigs =
currentConfigData.getClustersConfigs().get(Long.toString(cluster.getClusterId())).getConfigurations();
SortedMap<String, SortedMap<String, String>> previousConfigs =
previousConfigsData.getClustersConfigs().get(Long.toString(cluster.getClusterId())).getConfigurations();
Map<String, Collection<String>> changedConfigsHost = new HashMap<>();
for (String currentConfigType : currentConfigs.keySet()) {
if (previousConfigs.containsKey(currentConfigType)) {
Set<String> changedKeys = new HashSet<>();
Map<String, String> currentTypedConfigs = currentConfigs.get(currentConfigType);
Map<String, String> previousTypedConfigs = previousConfigs.get(currentConfigType);
for (String currentKey : currentTypedConfigs.keySet()) {
if (!previousTypedConfigs.containsKey(currentKey)
|| !currentTypedConfigs.get(currentKey).equals(previousTypedConfigs.get(currentKey))) {
changedKeys.add(currentKey);
}
}
for (String previousKey : previousTypedConfigs.keySet()) {
if (!currentTypedConfigs.containsKey(previousKey)) {
changedKeys.add(previousKey);
}
}
if (!changedKeys.isEmpty()) {
changedConfigsHost.put(currentConfigType, changedKeys);
}
} else {
changedConfigsHost.put(currentConfigType, currentConfigs.get(currentConfigType).keySet());
}
}
for (String previousConfigType : previousConfigs.keySet()) {
if (!currentConfigs.containsKey(previousConfigType)) {
changedConfigsHost.put(previousConfigType, previousConfigs.get(previousConfigType).keySet());
}
}
changedConfigs.put(host.getHostId(), changedConfigsHost);
}
for (String serviceName : cluster.getServices().keySet()) {
checkStaleConfigsStatusOnConfigsUpdate(cluster.getClusterId(), serviceName, changedConfigs);
}
m_metadataHolder.get().updateData(m_ambariManagementController.get().getClusterMetadataOnConfigsUpdate(cluster));
m_agentConfigsHolder.get().updateData(cluster.getClusterId(), null);
}
}
/**
* Checks configs are stale after specified config changes for service's components.
* @param clusterId cluster with changed config
* @param serviceName service for changed config
* @param changedConfigs map of config types to collections of changed properties' names.
* @throws AmbariException
*/
public void checkStaleConfigsStatusOnConfigsUpdate(Long clusterId, String serviceName,
Map<Long, Map<String, Collection<String>>> changedConfigs) throws AmbariException {
if (MapUtils.isEmpty(changedConfigs)) {
return;
}
if (!clusters.getCluster(clusterId).getServices().keySet().contains(serviceName)) {
return;
}
Service service = clusters.getCluster(clusterId).getService(serviceName);
for (ServiceComponent serviceComponent : service.getServiceComponents().values()) {
String serviceComponentHostName = serviceComponent.getName();
for (ServiceComponentHost serviceComponentHost : serviceComponent.getServiceComponentHosts().values()) {
if (changedConfigs.keySet().contains(serviceComponentHost.getHost().getHostId())) {
boolean staleConfigs = checkStaleConfigsStatusForHostComponent(serviceComponentHost,
changedConfigs.get(serviceComponentHost.getHost().getHostId()));
if (wasStaleConfigsStatusUpdated(clusterId, serviceComponentHost.getHost().getHostId(),
serviceName, serviceComponentHostName, staleConfigs)) {
serviceComponentHost.setRestartRequiredWithoutEventPublishing(staleConfigs);
STOMPUpdatePublisher.publish(new HostComponentsUpdateEvent(Collections.singletonList(
HostComponentUpdate.createHostComponentStaleConfigsStatusUpdate(clusterId,
serviceName, serviceComponentHost.getHostName(),
serviceComponentHostName, staleConfigs))));
}
}
}
}
}
/**
* Tries to change cached stale config with new value.
* @param clusterId cluster id.
* @param hostId host id.
* @param serviceName service name.
* @param hostComponentName component name.
* @param staleConfigs value to check.
* @return true if value from cache is different from {@param staleConfigs}.
*/
public boolean wasStaleConfigsStatusUpdated(Long clusterId, Long hostId, String serviceName, String hostComponentName, Boolean staleConfigs) {
if (!stateCache.containsKey(clusterId)) {
stateCache.put(clusterId, new HashMap<>());
}
Map<Long, Map<String, Map<String, Boolean>>> hosts = stateCache.get(clusterId);
if (!hosts.containsKey(hostId)) {
hosts.put(hostId, new HashMap<>());
}
Map<String, Map<String, Boolean>> services = hosts.get(hostId);
if (!services.containsKey(serviceName)) {
services.put(serviceName, new HashMap<>());
}
Map<String, Boolean> hostComponents = services.get(serviceName);
if (staleConfigs.equals(hostComponents.get(hostComponentName))) {
return false;
} else {
hostComponents.put(hostComponentName, staleConfigs);
return true;
}
}
/**
* Checks configs are stale for specified host component.
* @param sch host component to check.
* @param changedConfigs map of config types to collections of changed properties' names.
* @return true if configs are stale.
* @throws AmbariException
*/
public boolean checkStaleConfigsStatusForHostComponent(ServiceComponentHost sch,
Map<String, Collection<String>> changedConfigs) throws AmbariException {
HostComponentDesiredStateEntity hostComponentDesiredStateEntity = sch.getDesiredStateEntity();
if (sch.isRestartRequired(hostComponentDesiredStateEntity)) {
return true;
}
boolean stale = false;
Cluster cluster = clusters.getClusterById(sch.getClusterId());
StackId stackId = sch.getServiceComponent().getDesiredStackId();
StackInfo stackInfo = ambariMetaInfo.getStack(stackId);
ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(),
stackId.getStackVersion(), sch.getServiceName());
ComponentInfo componentInfo = serviceInfo.getComponentByName(sch.getServiceComponentName());
List<String> changedProperties = new LinkedList<>();
for (Map.Entry<String, Collection<String>> changedConfigType : changedConfigs.entrySet()) {
String type = changedConfigType.getKey();
stale |= (serviceInfo.hasConfigDependency(type) || componentInfo.hasConfigType(type));
if (stale) {
for (String propertyName : changedConfigType.getValue()) {
changedProperties.add(type + "/" + propertyName);
}
}
}
String refreshCommand = calculateRefreshCommand(stackInfo.getRefreshCommandConfiguration(), sch, changedProperties);
Map<String, HostConfig> actual = sch.getActualConfigs();
Map<String, Map<String, String>> desired = getEffectiveDesiredTags(cluster, sch.getHostName(),cluster.getDesiredConfigs());
if (STALE_CONFIGS_CACHE_ENABLED) {
if (refreshCommand != null) {
int staleHash = Objects.hashCode(actual.hashCode(),
desired.hashCode(),
sch.getHostName(),
sch.getServiceComponentName(),
sch.getServiceName());
refreshConfigCommandCache.put(staleHash, refreshCommand);
}
}
return stale;
}
/**
* Calculates config types and keys were changed during configs change.
* @param currentServiceConfigEntity service config entity with populated current cluster configs
* @param configGroupId id of config group contained changed configs. Can be null in case group is default
* @param clusterId cluster id
* @param serviceName service name configs were changed for
* @return map of type names to collections of properties' names were changed.
*/
public Map<String, Collection<String>> getChangedConfigTypes(Cluster cluster,
ServiceConfigEntity currentServiceConfigEntity,
Long configGroupId, Long clusterId, String serviceName) {
ServiceConfigEntity previousServiceConfigEntity;
List<ClusterConfigEntity> previousConfigEntities = new ArrayList<>();
List<ClusterConfigEntity> currentConfigEntities = new ArrayList<>();
currentConfigEntities.addAll(currentServiceConfigEntity.getClusterConfigEntities());
// Retrieve group cluster configs
if (configGroupId != null) {
previousServiceConfigEntity =
serviceConfigDAO.getLastServiceConfigVersionsForGroup(configGroupId);
if (previousServiceConfigEntity != null) {
previousConfigEntities.addAll(previousServiceConfigEntity.getClusterConfigEntities());
}
}
// Service config with custom group contains not all config types, so it is needed
// to complement it with configs from default group
previousServiceConfigEntity =
serviceConfigDAO.getLastServiceConfigForServiceDefaultGroup(clusterId, serviceName);
if (previousServiceConfigEntity != null) {
for (ClusterConfigEntity clusterConfigEntity : previousServiceConfigEntity.getClusterConfigEntities()) {
// Add only configs not present yet
ClusterConfigEntity exist =
previousConfigEntities.stream()
.filter(c -> c.getType().equals(clusterConfigEntity.getType())).findAny().orElse(null);
if (exist == null) {
previousConfigEntities.add(clusterConfigEntity);
}
// Complement current custom group service config to correct comparing
if (configGroupId != null) {
exist = currentConfigEntities.stream()
.filter(c -> c.getType().equals(clusterConfigEntity.getType())).findAny().orElse(null);
if (exist == null) {
currentConfigEntities.add(clusterConfigEntity);
}
}
}
}
Map<String, String> previousConfigs = new HashMap<>();
Map<String, String> currentConfigs = new HashMap<>();
for (ClusterConfigEntity clusterConfigEntity : currentConfigEntities) {
currentConfigs.put(clusterConfigEntity.getType(), clusterConfigEntity.getTag());
}
for (ClusterConfigEntity clusterConfigEntity : previousConfigEntities) {
previousConfigs.put(clusterConfigEntity.getType(), clusterConfigEntity.getTag());
}
Map<String, Collection<String>> changedConfigs = new HashMap<>();
for (Map.Entry<String, String> currentConfig : currentConfigs.entrySet()) {
String type = currentConfig.getKey();
String tag = currentConfig.getValue();
Collection<String> changedKeys;
if (previousConfigs.containsKey(type)) {
changedKeys = findChangedKeys(cluster, type, Collections.singletonList(tag),
Collections.singletonList(previousConfigs.get(type)));
} else {
changedKeys = cluster.getConfig(type, tag).getProperties().keySet();
}
if (CollectionUtils.isNotEmpty(changedKeys)) {
changedConfigs.put(type, changedKeys);
}
}
return changedConfigs;
}
public String getRefreshConfigsCommand(Cluster cluster, String hostName, String serviceName, String componentName) throws AmbariException {
ServiceComponent serviceComponent = cluster.getService(serviceName).getServiceComponent(componentName);
ServiceComponentHost sch = serviceComponent.getServiceComponentHost(hostName);
return getRefreshConfigsCommand(cluster, sch);
}
public String getRefreshConfigsCommand(Cluster cluster, ServiceComponentHost sch) throws AmbariException {
String refreshCommand = null;
Map<String, HostConfig> actual = sch.getActualConfigs();
if (STALE_CONFIGS_CACHE_ENABLED) {
Map<String, Map<String, String>> desired = getEffectiveDesiredTags(cluster, sch.getHostName(),
cluster.getDesiredConfigs());
int staleHash = Objects.hashCode(actual.hashCode(),
desired.hashCode(),
sch.getHostName(),
sch.getServiceComponentName(),
sch.getServiceName());
refreshCommand = refreshConfigCommandCache.getIfPresent(staleHash);
}
return refreshCommand;
}
/**
* Calculates refresh command for a set of changed properties as follows:
* - if a property has no refresh command return null
* - in case of multiple refresh commands: as REFRESH_CONFIGS is executed by default in case of any other command as well,
* can be overriden by RELOAD_CONFIGS or any other custom command, however in case of any other different commands return null
* as it's not possible to refresh all properties with one command.
*
* examples:
* {REFRESH_CONFIGS, REFRESH_CONFIGS, RELOAD_CONFIGS} ==> RELOAD_CONFIGS
* {REFRESH_CONFIGS, RELOADPROXYUSERS, RELOAD_CONFIGS} ==> null
*
* @param refreshCommandConfiguration
* @param sch
* @param changedProperties
* @return
*/
private String calculateRefreshCommand(RefreshCommandConfiguration refreshCommandConfiguration,
ServiceComponentHost sch, List<String> changedProperties) {
String finalRefreshCommand = null;
for (String propertyName : changedProperties) {
String refreshCommand = refreshCommandConfiguration.getRefreshCommandForComponent(sch, propertyName);
if (refreshCommand == null) {
return null;
}
if (finalRefreshCommand == null) {
finalRefreshCommand = refreshCommand;
}
if (!finalRefreshCommand.equals(refreshCommand)) {
if (finalRefreshCommand.equals(RefreshCommandConfiguration.REFRESH_CONFIGS)) {
finalRefreshCommand = refreshCommand;
} else if (!refreshCommand.equals(RefreshCommandConfiguration.REFRESH_CONFIGS)) {
return null;
}
}
}
return finalRefreshCommand;
}
/**
* Determines if the hostname has group specific configs for the type specified
*
* @param cluster
* @param hostname of the host to look for
* @param type the type to look for (e.g. flume-conf)
* @return <code>true</code> if the hostname has group specific configuration for the type
*/
private boolean hasGroupSpecificConfigsForType(Cluster cluster, String hostname, String type) {
try {
Map<Long, ConfigGroup> configGroups = cluster.getConfigGroupsByHostname(hostname);
if (configGroups != null && !configGroups.isEmpty()) {
for (ConfigGroup configGroup : configGroups.values()) {
Config config = configGroup.getConfigurations().get(type);
if (config != null) {
return true;
}
}
}
} catch (AmbariException ambariException) {
LOG.warn("Could not determine group configuration for host. Details: " + ambariException.getMessage());
}
return false;
}
/**
* @return the keys that have changed values
*/
private Collection<String> findChangedKeys(Cluster cluster, String type,
Collection<String> desiredTags, Collection<String> actualTags) {
Map<String, String> desiredValues = new HashMap<>();
Map<String, String> actualValues = new HashMap<>();
for (String tag : desiredTags) {
Config config = cluster.getConfig(type, tag);
if (null != config) {
desiredValues.putAll(config.getProperties());
}
}
for (String tag : actualTags) {
Config config = cluster.getConfig(type, tag);
if (null != config) {
actualValues.putAll(config.getProperties());
}
}
List<String> keys = new ArrayList<>();
for (Entry<String, String> entry : desiredValues.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (!actualValues.containsKey(key) || !valuesAreEqual(actualValues.get(key), value)) {
keys.add(type + "/" + key);
}
}
// check for case configs were removed
for (String key : actualValues.keySet()) {
if (!desiredValues.containsKey(key)) {
keys.add(type + "/" + key);
}
}
return keys;
}
/**
* Checks for equality of parsed numbers if both values are numeric,
* otherwise using regular equality.
*/
static boolean valuesAreEqual(String value1, String value2) { // exposed for unit test
if (NumberUtils.isNumber(value1) && NumberUtils.isNumber(value2)) {
try {
Number number1 = NumberUtils.createNumber(value1);
Number number2 = NumberUtils.createNumber(value2);
return Objects.equal(number1, number2) ||
number1.doubleValue() == number2.doubleValue();
} catch (NumberFormatException e) {
// fall back to regular equality
}
}
return Objects.equal(value1, value2);
}
/**
* @return the map of tags for a desired config
*/
private Map<String, String> buildTags(HostConfig hc) {
Map<String, String> map = new LinkedHashMap<>();
map.put(CLUSTER_DEFAULT_TAG, hc.getDefaultVersionTag());
if (hc.getConfigGroupOverrides() != null) {
for (Entry<Long, String> entry : hc.getConfigGroupOverrides().entrySet()) {
map.put(entry.getKey().toString(), entry.getValue());
}
}
return map;
}
/**
* @return true if the tags are different in any way, even if not-specified
*/
private boolean isTagChanged(Map<String, String> desiredTags, Map<String, String> actualTags, boolean groupSpecificConfigs) {
if (!actualTags.get(CLUSTER_DEFAULT_TAG).equals(desiredTags.get(CLUSTER_DEFAULT_TAG))) {
return true;
}
// cluster level configs are already compared for staleness, now they match
// if the host has group specific configs for type we should ignore the cluster level configs and compare specifics
if (groupSpecificConfigs) {
actualTags.remove(CLUSTER_DEFAULT_TAG);
desiredTags.remove(CLUSTER_DEFAULT_TAG);
}
Set<String> desiredSet = new HashSet<>(desiredTags.values());
Set<String> actualSet = new HashSet<>(actualTags.values());
// Both desired and actual should be exactly the same
return !desiredSet.equals(actualSet);
}
public static String fileNameToConfigType(String filename) {
int extIndex = filename.indexOf(StackDirectory.SERVICE_CONFIG_FILE_NAME_POSTFIX);
return filename.substring(0, extIndex);
}
/**
* Removes properties from configurations that marked as hidden for specified component.
* @param configurations cluster configurations
* @param attributes configuration attributes
* @param componentName component name
* @param configDownload indicates if config must be downloaded
*/
public static void processHiddenAttribute(Map<String, Map<String, String>> configurations,
Map<String, Map<String, Map<String, String>>> attributes,
String componentName, boolean configDownload){
if (configurations != null && attributes != null && componentName != null) {
for(Map.Entry<String, Map<String,String>> confEntry : configurations.entrySet()){
String configTag = confEntry.getKey();
Map<String,String> confProperties = confEntry.getValue();
if(attributes.containsKey(configTag)){
Map<String, Map<String, String>> configAttributes = attributes.get(configTag);
if(configAttributes.containsKey("hidden")){
Map<String,String> hiddenProperties = configAttributes.get("hidden");
if(hiddenProperties != null) {
for (Map.Entry<String, String> hiddenEntry : hiddenProperties.entrySet()) {
String propertyName = hiddenEntry.getKey();
String components = hiddenEntry.getValue();
// hide property if we are downloading config & CONFIG_DOWNLOAD defined,
// otherwise - check if we have matching component name
if ((configDownload ? components.contains("CONFIG_DOWNLOAD") : components.contains(componentName))
&& confProperties.containsKey(propertyName)) {
confProperties.remove(propertyName);
}
}
}
}
}
}
}
}
/**
* Merge one attribute map to another.
* @param attributes original map
* @param additionalAttributes map with additional attributes
*/
public static void mergeConfigAttributes(Map<String, Map<String, String>> attributes, Map<String, Map<String, String>> additionalAttributes){
for(Map.Entry<String, Map<String, String>> attrEntry: additionalAttributes.entrySet()){
String attributeName = attrEntry.getKey();
Map<String, String> attributeProperties = attrEntry.getValue();
if(!attributes.containsKey(attributeName)) {
attributes.put(attributeName, attributeProperties);
} else {
attributes.get(attributeName).putAll(attributeProperties);
}
}
}
/**
* Collects actual configurations and configuration attributes for specified host.
* @param hostId host id to collect configurations and configuration attributes
* @return event ready to send to agent
* @throws AmbariException
*/
public AgentConfigsUpdateEvent getHostActualConfigs(Long hostId) throws AmbariException {
return getHostActualConfigsExcludeCluster(hostId, null);
}
public AgentConfigsUpdateEvent getHostActualConfigsExcludeCluster(Long hostId, Long clusterId) throws AmbariException {
TreeMap<String, ClusterConfigs> clustersConfigs = new TreeMap<>();
Host host = clusters.getHostById(hostId);
for (Cluster cl : clusters.getClusters().values()) {
if (clusterId != null && cl.getClusterId() == clusterId) {
continue;
}
Map<String, Map<String, String>> configurations = new HashMap<>();
Map<String, Map<String, Map<String, String>>> configurationAttributes = new HashMap<>();
Map<String, DesiredConfig> clusterDesiredConfigs = cl.getDesiredConfigs(false);
Map<String, Map<String, String>> configTags = getEffectiveDesiredTags(cl, host.getHostName(), clusterDesiredConfigs);
// Logging below creating too much spam and slowing down operations
if (LOG.isDebugEnabled()) {
LOG.debug("For configs update on host {} will be used cluster entity {}", hostId, cl.getClusterEntity().toString());
LOG.debug("For configs update on host {} will be used following cluster desired configs {}", hostId, clusterDesiredConfigs.toString());
LOG.debug("For configs update on host {} will be used following effective desired tags {}", hostId, configTags.toString());
}
getAndMergeHostConfigs(configurations, configTags, cl);
configurations = unescapeConfigNames(configurations);
getAndMergeHostConfigAttributes(configurationAttributes, configTags, cl);
configurationAttributes = unescapeConfigAttributeNames(configurationAttributes);
SortedMap<String, SortedMap<String, String>> configurationsTreeMap = sortConfigutations(configurations);
SortedMap<String, SortedMap<String, SortedMap<String, String>>> configurationAttributesTreeMap =
sortConfigurationAttributes(configurationAttributes);
clustersConfigs.put(Long.toString(cl.getClusterId()),
new ClusterConfigs(configurationsTreeMap, configurationAttributesTreeMap));
}
return new AgentConfigsUpdateEvent(hostId, clustersConfigs);
}
private Map<String, Map<String, String>> unescapeConfigNames(Map<String, Map<String, String>> configurations) {
Map<String, Map<String, String>> unescapedConfigs = new HashMap<>();
for (Entry<String, Map<String, String>> configTypeEntry : configurations.entrySet()) {
Map<String, String> unescapedTypeConfigs = new HashMap<>();
for (Entry<String, String> config : configTypeEntry.getValue().entrySet()) {
unescapedTypeConfigs.put(StringEscapeUtils.unescapeJava(config.getKey()), config.getValue());
}
unescapedConfigs.put(configTypeEntry.getKey(), unescapedTypeConfigs);
}
return unescapedConfigs;
}
private Map<String, Map<String, Map<String, String>>> unescapeConfigAttributeNames(
Map<String, Map<String, Map<String, String>>> configurationAttributes) {
Map<String, Map<String, Map<String, String>>> unescapedConfigAttributes = new HashMap<>();
configurationAttributes.forEach((key, value) -> unescapedConfigAttributes.put(key, unescapeConfigNames(value)));
return unescapedConfigAttributes;
}
public SortedMap<String, SortedMap<String, String>> sortConfigutations(Map<String, Map<String, String>> configurations) {
SortedMap<String, SortedMap<String, String>> configurationsTreeMap = new TreeMap<>();
configurations.forEach((k, v) -> configurationsTreeMap.put(k, new TreeMap<>(v)));
return configurationsTreeMap;
}
public SortedMap<String, SortedMap<String, SortedMap<String, String>>> sortConfigurationAttributes(
Map<String, Map<String, Map<String, String>>> configurationAttributes) {
SortedMap<String, SortedMap<String, SortedMap<String, String>>> configurationAttributesTreeMap = new TreeMap<>();
configurationAttributes.forEach((k, v) -> {
SortedMap<String, SortedMap<String, String>> c = new TreeMap<>();
v.forEach((k1, v1) -> c.put(k1, new TreeMap<>(v1)));
configurationAttributesTreeMap.put(k, c);
});
return configurationAttributesTreeMap;
}
/**
* Determines the existing configurations for the cluster
*
* @param ambariManagementController
* the Ambari management controller
* @param cluster
* the cluster
* @return a map of the existing configurations
* @throws AmbariException
*/
public Map<String, Map<String, String>> calculateExistingConfigurations(AmbariManagementController ambariManagementController, Cluster cluster) throws AmbariException {
final Map<String, Map<String, String>> configurations = new HashMap<>();
for (Host host : cluster.getHosts()) {
configurations.putAll(calculateExistingConfigurations(
ambariManagementController,
cluster,
host.getHostName(),
null
));
}
return configurations;
}
/**
* Determines the existing configurations for the cluster, related to a given hostname (if provided)
*
* @param ambariManagementController the Ambari management controller
* @param cluster the cluster
* @param hostname a hostname
* @param desiredConfigs desired configuration map
* @return a map of the existing configurations
* @throws AmbariException
*/
public Map<String, Map<String, String>> calculateExistingConfigurations(
AmbariManagementController ambariManagementController, Cluster cluster, String hostname,
@Nullable Map<String, DesiredConfig> desiredConfigs) throws AmbariException {
// For a configuration type, both tag and an actual configuration can be stored
// Configurations from the tag is always expanded and then over-written by the actual
// global:version1:{a1:A1,b1:B1,d1:D1} + global:{a1:A2,c1:C1,DELETED_d1:x} ==>
// global:{a1:A2,b1:B1,c1:C1}
final Map<String, Map<String, String>> configurations = new HashMap<>();
final Map<String, Map<String, String>> configurationTags = ambariManagementController
.findConfigurationTagsWithOverrides(cluster, hostname, desiredConfigs);
final Map<String, Map<String, String>> configProperties = getEffectiveConfigProperties(cluster, configurationTags);
// Apply the configurations saved with the Execution Cmd on top of
// derived configs - This will take care of all the hacks
for (Map.Entry<String, Map<String, String>> entry : configProperties.entrySet()) {
String type = entry.getKey();
Map<String, String> allLevelMergedConfig = entry.getValue();
Map<String, String> configuration = configurations.get(type);
if (configuration == null) {
configuration = new HashMap<>(allLevelMergedConfig);
} else {
Map<String, String> mergedConfig = getMergedConfig(allLevelMergedConfig, configuration);
configuration.clear();
configuration.putAll(mergedConfig);
}
configurations.put(type, configuration);
}
return configurations;
}
/**
* Determines the existing configurations for the cluster, both properties and attributes.
*/
public Pair<Map<String, Map<String, String>>, Map<String, Map<String, Map<String, String>>>> calculateExistingConfigs(Cluster cluster) throws AmbariException {
Map<String, Map<String, String>> desiredConfigTags = getEffectiveDesiredTags(cluster, null);
return Pair.of(
getEffectiveConfigProperties(cluster, desiredConfigTags),
getEffectiveConfigAttributes(cluster, desiredConfigTags)
);
}
}