This is an automated email from the ASF dual-hosted git repository.

benyoka pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new f3aa02f  AMBARI-25024 Config recommendation for add service request 
(benyoka) (#2704)
f3aa02f is described below

commit f3aa02f7bb4c765885af9d740c7661dd1cdbca80
Author: benyoka <[email protected]>
AuthorDate: Fri Dec 14 00:59:39 2018 +0100

    AMBARI-25024 Config recommendation for add service request (benyoka) (#2704)
    
    * AMBARI-25024 Config recommendation for add service request (benyoka)
    
    * AMBARI-25024 review comments (benyoka)
    
    * AMBARI-25024 fix unit test (benyoka)
    
    * AMBARI-25024 fix unit tests, work without layout recommendation
    
    * AMBARI-25024 fix merge compile issue (benyoka)
    
    * AMBARI-25024 fix NPE (benyoka)
---
 ambari-server/pom.xml                              |   2 +-
 .../services/stackadvisor/StackAdvisorRequest.java |  30 ++
 .../recommendations/RecommendationResponse.java    |  32 ++
 .../server/controller/AddServiceRequest.java       |   2 +-
 .../internal/BlueprintConfigurationProcessor.java  |   2 +
 .../ambari/server/controller/internal/Stack.java   |  18 +-
 .../server/controller/internal/UnitUpdater.java    |  35 +-
 .../ambari/server/state/ValueAttributesInfo.java   |  17 +
 .../topology/ConfigRecommendationStrategy.java     |  30 +-
 .../ambari/server/topology/ConfigurableHelper.java |  17 +
 .../ambari/server/topology/Configuration.java      |  16 +-
 .../server/topology/addservice/AddServiceInfo.java |  20 +-
 .../addservice/AddServiceOrchestrator.java         |   3 +-
 .../addservice/LayoutRecommendationInfo.java       |  53 +++
 .../topology/addservice/RequestValidator.java      |   6 +-
 .../topology/addservice/StackAdvisorAdapter.java   | 197 ++++++++--
 .../server/controller/AddServiceRequestTest.java   |   8 +-
 .../controller/internal/UnitUpdaterTest.java       |  61 ++-
 .../server/testutils/TestCollectionUtils.java      |  50 +++
 .../ambari/server/topology/ConfigurableTest.java   |  16 +
 .../ambari/server/topology/ConfigurationTest.java  |  49 +++
 .../topology/addservice/RequestValidatorTest.java  |   4 +-
 .../addservice/StackAdvisorAdapterTest.java        | 434 +++++++++++++++++++--
 23 files changed, 998 insertions(+), 104 deletions(-)

diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index 535f6b5..fd8db02 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -1323,7 +1323,7 @@
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
-      <version>3.1</version>
+      <version>3.8.1</version>
     </dependency>
     <dependency>
       <groupId>commons-net</groupId>
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
index 10baa33..5fb137c 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
@@ -18,6 +18,8 @@
 
 package org.apache.ambari.server.api.services.stackadvisor;
 
+import static java.util.stream.Collectors.toMap;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -30,9 +32,12 @@ import java.util.Set;
 import 
org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.state.ChangedConfigInfo;
 import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.Configuration;
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 
 /**
  * Stack advisor request.
@@ -151,6 +156,22 @@ public class StackAdvisorRequest {
     this.stackVersion = stackVersion;
   }
 
+  public StackAdvisorRequestBuilder builder() {
+    return StackAdvisorRequestBuilder.forStack(stackName, stackVersion)
+      .ofType(requestType)
+      .forHosts(hosts)
+      .forServices(services)
+      .forHostComponents(hostComponents)
+      .forHostsGroupBindings(hostGroupBindings)
+      .withComponentHostsMap(componentHostsMap)
+      .withConfigurations(configurations)
+      .withChangedConfigurations(changedConfigurations)
+      .withConfigGroups(configGroups)
+      .withUserContext(userContext)
+      .withGPLLicenseAccepted(gplLicenseAccepted)
+      .withLdapConfig(ldapConfig);
+  }
+
   public static class StackAdvisorRequestBuilder {
     StackAdvisorRequest instance;
 
@@ -204,6 +225,15 @@ public class StackAdvisorRequest {
       return this;
     }
 
+    public StackAdvisorRequestBuilder withConfigurations(Configuration 
configuration) {
+      Map<String, Map<String, String>> properties = 
configuration.getFullProperties();
+      this.instance.configurations = properties.entrySet().stream()
+        .map( e -> Pair.of(e.getKey(), ImmutableMap.of("properties", 
e.getValue())))
+        .collect(toMap(Pair::getKey, Pair::getValue));
+      return this;
+    }
+
+
     public StackAdvisorRequestBuilder withChangedConfigurations(
       List<ChangedConfigInfo> changedConfigurations) {
       this.instance.changedConfigurations = changedConfigurations;
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
index 99e9ab2..5d5247e 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/recommendations/RecommendationResponse.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.api.services.stackadvisor.recommendations;
 
+import static com.google.common.collect.Maps.transformValues;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.mapping;
 import static java.util.stream.Collectors.toMap;
@@ -27,13 +28,16 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorResponse;
 import org.apache.ambari.server.state.ValueAttributesInfo;
+import org.apache.ambari.server.topology.ConfigurableHelper;
 import org.apache.commons.lang3.tuple.Pair;
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
 import com.google.common.collect.ImmutableMap;
@@ -150,6 +154,24 @@ public class RecommendationResponse extends 
StackAdvisorResponse {
     @JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
     private Map<String, ValueAttributesInfo> propertyAttributes = null;
 
+    /**
+     *
+     * @param properties properties in <i>name -> value</i> format
+     * @param attributes attributes in <i>attribute name -> property name -> 
value</i> format
+     */
+    public static BlueprintConfigurations create(Map<String, String> 
properties, Map<String, Map<String, String>> attributes) {
+      BlueprintConfigurations config = new BlueprintConfigurations();
+      config.setProperties(properties);
+      if (attributes != null) {
+        // transform map to property name -> attribute name -> value format
+        Map<String, Map<String, String>> transformedAttributes = 
ConfigurableHelper.transformAttributesMap(attributes);
+        ObjectMapper mapper = new ObjectMapper();
+        config.setPropertyAttributes(
+          new HashMap<>(transformValues(transformedAttributes, attr -> 
ValueAttributesInfo.fromMap(attr, Optional.of(mapper)))));
+      }
+      return config;
+    }
+
     public BlueprintConfigurations() {
 
     }
@@ -176,6 +198,16 @@ public class RecommendationResponse extends 
StackAdvisorResponse {
       return propertyAttributes;
     }
 
+    /**
+     * @return value attributes in <i>attribute name -> property name -> 
value</i> format
+     */
+    @JsonIgnore
+    public Map<String, Map<String, String>> getPropertyAttributesAsMap() {
+      ObjectMapper mapper = new ObjectMapper();
+      return null == propertyAttributes ? null :
+        ConfigurableHelper.transformAttributesMap( 
transformValues(propertyAttributes, vaInfo -> 
vaInfo.toMap(Optional.of(mapper))) );
+    }
+
     public void setPropertyAttributes(Map<String, ValueAttributesInfo> 
propertyAttributes) {
       this.propertyAttributes = propertyAttributes;
     }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
index 02577ca..e169f82 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
@@ -124,7 +124,7 @@ public class AddServiceRequest {
     Configuration configuration
   ) {
     this.operationType = null != operationType ? operationType : 
OperationType.ADD_SERVICE;
-    this.recommendationStrategy = null != recommendationStrategy ? 
recommendationStrategy : ConfigRecommendationStrategy.NEVER_APPLY;
+    this.recommendationStrategy = null != recommendationStrategy ? 
recommendationStrategy : ConfigRecommendationStrategy.defaultForAddService();
     this.provisionAction = null != provisionAction ? provisionAction : 
ProvisionAction.INSTALL_AND_START;
     this.validationType = validationType != null ? validationType : 
ValidationType.DEFAULT;
     this.stackName = stackName;
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
index 46d7e9b..12ef314 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
@@ -1023,6 +1023,8 @@ public class BlueprintConfigurationProcessor {
     }
   }
 
+
+
   /**
    * Update configuration properties based on advised configuration properties.
    * @param configuration configuration being processed
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
index c83999b..3bfdeb3 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
@@ -49,7 +49,6 @@ import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.ValueAttributesInfo;
 import org.apache.ambari.server.topology.Cardinality;
 import org.apache.ambari.server.topology.Configuration;
-import org.apache.ambari.server.topology.validators.UnitValidatedProperty;
 
 /**
  * Encapsulates stack information.
@@ -657,23 +656,20 @@ public class Stack {
    * (eg. some properties need a unit to be appended)
    */
   public Configuration getValidDefaultConfig() {
-    Configuration config = getConfiguration();
-
-    for (UnitValidatedProperty p : UnitValidatedProperty.ALL) {
-      if (config.isPropertySet(p.getConfigType(), p.getPropertyName())) {
-        String value = config.getPropertyValue(p.getConfigType(), 
p.getPropertyName());
-        String updatedValue = UnitUpdater.updateForClusterCreate(this, 
p.getServiceName(), p.getConfigType(), p.getPropertyName(), value);
-        config.setProperty(p.getConfigType(), p.getPropertyName(), 
updatedValue);
-      }
-    }
+    Configuration config = getDefaultConfig();
+    UnitUpdater.updateUnits(config, this);
+    return config;
+  }
 
+  public Configuration getDefaultConfig() {
+    Configuration config = getConfiguration();
     config.getProperties().values().forEach(
       each -> each.values().removeIf(Objects::isNull)
     );
-
     return config;
   }
 
+
   /**
    * Parse components for the specified service from the stack definition.
    *
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
index 6691519..c6188b1 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
@@ -22,9 +22,12 @@ import static org.apache.commons.lang.StringUtils.isBlank;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.function.BiFunction;
 
 import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.validators.UnitValidatedProperty;
 
 /**
@@ -65,12 +68,38 @@ public class UnitUpdater implements 
BlueprintConfigurationProcessor.PropertyUpda
     }
   }
 
+  public static void updateUnits(Configuration configuration, Stack stack) {
+    updateAllUnitValidatedProperties(configuration,
+      (property, value) -> updateForClusterCreate(stack, 
property.getServiceName(), property.getConfigType(), 
property.getPropertyName(), value));
+  }
+
+  public static void removeUnits(Configuration configuration, Stack stack) {
+    updateAllUnitValidatedProperties(configuration,
+      (property, value) -> removeStackUnit(stack, property.getServiceName(), 
property.getConfigType(), property.getPropertyName(), value));
+  }
+
+  private static void updateAllUnitValidatedProperties(Configuration 
configuration, BiFunction<UnitValidatedProperty, String, String> valueUpdater) {
+    for (UnitValidatedProperty p : UnitValidatedProperty.ALL) {
+      if (configuration.isPropertySet(p.getConfigType(), p.getPropertyName())) 
{
+        String value = configuration.getPropertyValue(p.getConfigType(), 
p.getPropertyName());
+        String updatedValue = valueUpdater.apply(p, value);
+        if (!Objects.equals(value, updatedValue)) {
+          configuration.setProperty(p.getConfigType(), p.getPropertyName(), 
updatedValue);
+        }
+      }
+    }
+  }
+
   /**
    * @return property value with removed unit
    */
   @Override
   public String updateForBlueprintExport(String propertyName, String 
origValue, Map<String, Map<String, String>> properties, ClusterTopology 
topology) {
-    PropertyUnit stackUnit = 
PropertyUnit.of(topology.getBlueprint().getStack(), serviceName, configType, 
propertyName);
+    return removeStackUnit(topology.getBlueprint().getStack(), serviceName, 
configType, propertyName, origValue);
+  }
+
+  static String removeStackUnit(Stack stack, String serviceName, String 
configType, String propertyName, String origValue) {
+    PropertyUnit stackUnit = PropertyUnit.of(stack, serviceName, configType, 
propertyName);
     PropertyValue value = PropertyValue.of(propertyName, origValue);
     return value.withoutUnit(stackUnit);
   }
@@ -150,6 +179,10 @@ public class UnitUpdater implements 
BlueprintConfigurationProcessor.PropertyUpda
     }
 
     public boolean hasAnyUnit() {
+      return hasAnyUnit(value);
+    }
+
+    static boolean hasAnyUnit(String value) {
       return !Character.isDigit(value.charAt(value.length() -1));
     }
 
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ValueAttributesInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ValueAttributesInfo.java
index 156157a..f8ddf0b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ValueAttributesInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ValueAttributesInfo.java
@@ -19,6 +19,8 @@
 package org.apache.ambari.server.state;
 
 import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -28,7 +30,9 @@ import javax.xml.bind.annotation.XmlElements;
 
 import org.apache.ambari.server.controller.ApiModel;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.type.TypeReference;
 
 import io.swagger.annotations.ApiModelProperty;
 
@@ -396,6 +400,19 @@ public class ValueAttributesInfo implements ApiModel {
     return result;
   }
 
+  public Map<String, String> toMap(Optional<ObjectMapper> mapper) {
+    Map<String, String> map =
+      mapper.orElseGet(ObjectMapper::new).convertValue(this, new 
TypeReference<Map<String, String>>(){});
+    if ( !Boolean.parseBoolean(map.get("keyStore")) ) { // keyStore is 
declared as a primitive value instead of Boolean -> treat false as unset
+      map.remove("keyStore");
+    }
+    return map;
+  }
+
+  public static ValueAttributesInfo fromMap(Map<String, String> attributes, 
Optional<ObjectMapper> mapper) {
+    return mapper.orElseGet(ObjectMapper::new).convertValue(attributes, 
ValueAttributesInfo.class);
+  }
+
   @Override
   public String toString() {
     return "ValueAttributesInfo{" +
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigRecommendationStrategy.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigRecommendationStrategy.java
index bf3eacc..25d89e0 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigRecommendationStrategy.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigRecommendationStrategy.java
@@ -19,24 +19,46 @@
 package org.apache.ambari.server.topology;
 
 public enum ConfigRecommendationStrategy {
+
   /**
    *  Configuration recommendations are always applied, overriding stack 
defaults and
    *  configuration defined by the user in the Blueprint and/or Cluster 
Creation Template.
    */
-  ALWAYS_APPLY,
+  ALWAYS_APPLY(true, true),
   /**
    * Configuration recommendations are ignored with this option, both for 
stack defaults
    * and configuration defined by the user in the Blueprint and/or Cluster 
Creation Template.
    */
-  NEVER_APPLY,
+  NEVER_APPLY(false, false),
+
   /**
    *  Configuration recommendations are always applied for properties listed 
as stack defaults,
    *  but not for configurations defined by the user in the Blueprint and/or 
Cluster Creation Template.
    */
-  ONLY_STACK_DEFAULTS_APPLY,
+  ONLY_STACK_DEFAULTS_APPLY(true, false),
   /**
    *  Configuration recommendations are always applied, overriding stack 
defaults but they don't
    *  override configuration defined by the user in the Blueprint and/or 
Cluster Creation Template.
    */
-  ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES;
+  ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES(true, false);
+
+  public static ConfigRecommendationStrategy defaultForAddService() {
+    return ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES;
+  }
+
+  private final boolean useStackAdvisor;
+  private final boolean overrideCustomValues;
+
+  public boolean shouldUseStackAdvisor() {
+    return useStackAdvisor;
+  }
+
+  public boolean shouldOverrideCustomValues() {
+    return overrideCustomValues;
+  }
+
+  ConfigRecommendationStrategy(boolean useStackAdvisor, boolean 
overrideCustomValues) {
+    this.useStackAdvisor = useStackAdvisor;
+    this.overrideCustomValues = overrideCustomValues;
+  }
 }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurableHelper.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurableHelper.java
index 572f12d..9e75b0e 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurableHelper.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurableHelper.java
@@ -19,6 +19,8 @@
 package org.apache.ambari.server.topology;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toMap;
 import static 
org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_ATTRIBUTES_PROPERTY_ID;
 import static 
org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_PROPERTY_ID;
 import static 
org.apache.ambari.server.topology.ConfigurationFactory.isKeyInLegacyFormat;
@@ -35,6 +37,8 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
+import org.apache.commons.lang3.tuple.Triple;
+
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -182,4 +186,17 @@ public class ConfigurableHelper {
     return null != object ? object.getClass().getName() : null;
   }
 
+
+  /**
+   * Transform attibutes map from <i>attributeName -> propertyName -> 
value</i> to <i>propertyName -> attributeName -> value</i>
+   * or vice versa
+   * @param input the input map
+   * @return the output map
+   */
+  public static Map<String, Map<String, String>> 
transformAttributesMap(Map<String, Map<String, String>> input) {
+    return input.entrySet().stream()
+      .flatMap(outer -> outer.getValue().entrySet().stream().map(inner -> 
Triple.of(outer.getKey(), inner.getKey(), inner.getValue())))
+      .collect(groupingBy(Triple::getMiddle, toMap(Triple::getLeft, 
Triple::getRight)));
+  }
+
 }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
index fc02297..e6b1d88 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
@@ -29,6 +29,7 @@ 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.
  */
@@ -149,8 +150,8 @@ public class Configuration {
   /**
    * Configuration.
    *
-   * @param properties  properties
-   * @param attributes  attributes
+   * @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) {
@@ -441,6 +442,17 @@ public class Configuration {
     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.
    *
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
index 46fe3f3..25b9a3b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
@@ -41,6 +41,7 @@ public final class AddServiceInfo {
   private final Map<String, Map<String, Set<String>>> newServices;
   private final RequestStageContainer stages;
   private final Configuration config;
+  private final LayoutRecommendationInfo recommendationInfo;
 
   public AddServiceInfo(
     AddServiceRequest request,
@@ -49,8 +50,9 @@ public final class AddServiceInfo {
     Configuration config,
     KerberosDescriptor kerberosDescriptor,
     RequestStageContainer stages,
-    Map<String, Map<String, Set<String>>> newServices
-  ) {
+    Map<String, Map<String,
+    Set<String>>> newServices,
+    LayoutRecommendationInfo recommendationInfo) {
     this.request = request;
     this.clusterName = clusterName;
     this.stack = stack;
@@ -58,10 +60,16 @@ public final class AddServiceInfo {
     this.newServices = newServices;
     this.stages = stages;
     this.config = config;
+    this.recommendationInfo = recommendationInfo;
   }
 
-  public AddServiceInfo withNewServices(Map<String, Map<String, Set<String>>> 
services) {
-    return new AddServiceInfo(request, clusterName, stack, config, 
kerberosDescriptor, stages, services);
+  public AddServiceInfo withLayoutRecommendation(Map<String, Map<String, 
Set<String>>> services,
+                                                 LayoutRecommendationInfo 
recommendation) {
+    return new AddServiceInfo(request, clusterName, stack, config, 
kerberosDescriptor, stages, services, recommendation);
+  }
+
+  public AddServiceInfo withConfig(Configuration newConfig) {
+    return new AddServiceInfo(request, clusterName, stack, newConfig, 
kerberosDescriptor, stages, newServices, recommendationInfo);
   }
 
   @Override
@@ -97,6 +105,10 @@ public final class AddServiceInfo {
     return config;
   }
 
+  public Optional<LayoutRecommendationInfo> getRecommendationInfo() {
+    return Optional.ofNullable(recommendationInfo);
+  }
+
   public Optional<KerberosDescriptor> getKerberosDescriptor() {
     return Optional.ofNullable(kerberosDescriptor);
   }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
index c4b6529..4ff1301 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
@@ -143,8 +143,7 @@ public class AddServiceOrchestrator {
    */
   private AddServiceInfo recommendConfiguration(AddServiceInfo request) {
     LOG.info("Recommending configuration for {}", request);
-    // TODO implement
-    return request;
+    return stackAdvisorAdapter.recommendConfigurations(request);
   }
 
   /**
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/LayoutRecommendationInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/LayoutRecommendationInfo.java
new file mode 100644
index 0000000..03257ee
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/LayoutRecommendationInfo.java
@@ -0,0 +1,53 @@
+/*
+ * 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.addservice;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates information from layout recommendation that can be used or 
reused for config recommendation.
+ */
+public class LayoutRecommendationInfo {
+  private final Map<String, Set<String>> hostGroups;
+  private final Map<String, Map<String, Set<String>>> allServiceComponentHosts;
+
+  public LayoutRecommendationInfo(Map<String, Set<String>> hostGroups, 
Map<String, Map<String, Set<String>>> allServiceComponentHosts) {
+    this.hostGroups = hostGroups;
+    this.allServiceComponentHosts = allServiceComponentHosts;
+  }
+
+  public Map<String, Set<String>> getHostGroups() {
+    return hostGroups;
+  }
+
+  public Map<String, Map<String, Set<String>>> getAllServiceLayouts() {
+    return allServiceComponentHosts;
+  }
+
+  public List<String> getHosts() {
+    return getHostsFromHostGroups(hostGroups);
+  }
+
+  public static List<String> getHostsFromHostGroups(Map<String, Set<String>> 
hostGroups) {
+    return hostGroups.values().stream().flatMap(Set::stream).collect(toList());
+  }
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
index 4a8d14c..29ac8c8 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
@@ -37,6 +37,7 @@ import org.apache.ambari.server.controller.AddServiceRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.internal.RequestStageContainer;
 import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.UnitUpdater;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.SecurityType;
@@ -117,7 +118,7 @@ public class RequestValidator {
     RequestStageContainer stages = new 
RequestStageContainer(actionManager.getNextRequestId(), null, requestFactory, 
actionManager);
     AddServiceInfo validatedRequest = new AddServiceInfo(request, 
cluster.getClusterName(),
       state.getStack(), state.getConfig(), state.getKerberosDescriptor(),
-      stages, state.getNewServices());
+      stages, state.getNewServices(), null);
     stages.setRequestContext(validatedRequest.describe());
     return validatedRequest;
   }
@@ -250,9 +251,10 @@ public class RequestValidator {
     }
 
     Configuration clusterConfig = getClusterDesiredConfigs();
-    
clusterConfig.setParentConfiguration(state.getStack().getValidDefaultConfig());
+    clusterConfig.setParentConfiguration(state.getStack().getDefaultConfig());
     config.setParentConfiguration(clusterConfig);
 
+    UnitUpdater.removeUnits(config, state.getStack()); // stack advisor 
doesn't like units; they'll be added back after recommendation
     state = state.with(config);
   }
 
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapter.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapter.java
index 45f0e59..7504d45 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapter.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapter.java
@@ -40,10 +40,11 @@ import 
org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import 
org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import 
org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
-import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.internal.UnitUpdater;
 import org.apache.ambari.server.state.Cluster;
-import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
+import org.apache.ambari.server.topology.Configuration;
 import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -64,7 +65,7 @@ public class StackAdvisorAdapter {
   private StackAdvisorHelper stackAdvisorHelper;
 
   @Inject
-  private Configuration serverConfig;
+  private org.apache.ambari.server.configuration.Configuration serverConfig;
 
   @Inject
   private Injector injector;
@@ -79,19 +80,14 @@ public class StackAdvisorAdapter {
    */
   AddServiceInfo recommendLayout(AddServiceInfo info) {
     try {
-      Cluster cluster = 
managementController.getClusters().getCluster(info.clusterName());
-      Map<String, Map<String, Set<String>>> clusterServices = transformValues(
-        cluster.getServices(),
-        service -> transformValues(service.getServiceComponents(), component 
-> component.getServiceComponentsHosts()));
-
       // Requested component layout will be added to the StackAdvisor input in 
addition to existing
       // component layout.
-      Map<String, Map<String, Set<String>>> allServices = 
mergeDisjunctMaps(clusterServices, info.newServices());
+      Map<String, Map<String, Set<String>>> allServices = getAllServices(info);
 
       Map<String, Set<String>> componentsToHosts = 
getComponentHostMap(allServices);
 
       Map<String, Set<String>> hostsToComponents = 
getHostComponentMap(componentsToHosts);
-      List<String> hosts = ImmutableList.copyOf(cluster.getHostNames());
+      List<String> hosts = 
ImmutableList.copyOf(getCluster(info).getHostNames());
       hosts.forEach( host -> hostsToComponents.putIfAbsent(host, new 
HashSet<>())); // just in case there are hosts that have no components
 
       Map<String, Set<String>> hostGroups = 
getHostGroupStrategy().calculateHostGroups(hostsToComponents);
@@ -104,6 +100,7 @@ public class StackAdvisorAdapter {
         .forHostComponents(hostsToComponents)
         .forHostsGroupBindings(hostGroups)
         .withComponentHostsMap(componentsToHosts)
+        .withConfigurations(info.getConfig())
         .withGPLLicenseAccepted(serverConfig.getGplLicenseAccepted())
         .build();
       RecommendationResponse response = stackAdvisorHelper.recommend(request);
@@ -113,47 +110,162 @@ public class StackAdvisorAdapter {
         
response.getRecommendations().getBlueprint().getHostgroupComponentMap(),
         info.getStack()::getServiceForComponent);
 
-      Set<ValidationResponse.ValidationItem> validationItems = 
validateRecommendedLayout(info.getStack().getStackId(),
-        recommendedLayout,
-        
response.getRecommendations().getBlueprintClusterBinding().getHostgroupHostMap());
-      if (!validationItems.isEmpty()) {
-        LOG.warn("Issues found during recommended topology validation:\n{}", 
Joiner.on('\n').join(validationItems));
-      }
-
-      // Keep the recommendations for new services only
-      keepNewServicesOnly(recommendedLayout, info.newServices());
+      // Validate layout
+      Map<String, Set<String>> recommendedComponentHosts = 
getComponentHostMap(recommendedLayout);
+      StackAdvisorRequest validationRequest = request.builder()
+        
.forHostsGroupBindings(response.getRecommendations().getBlueprintClusterBinding().getHostgroupHostMap())
+        .withComponentHostsMap(recommendedComponentHosts)
+        
.forHostComponents(getHostComponentMap(recommendedComponentHosts)).build();
+      validate(validationRequest);
 
-      return info.withNewServices(recommendedLayout);
+      Map<String,Map<String,Set<String>>> newServiceRecommendations = 
keepNewServicesOnly(recommendedLayout, info.newServices());
+      LayoutRecommendationInfo recommendationInfo = new 
LayoutRecommendationInfo(
+        
response.getRecommendations().getBlueprintClusterBinding().getHostgroupHostMap(),
+        recommendedLayout);
+      return info.withLayoutRecommendation(newServiceRecommendations, 
recommendationInfo);
     }
     catch (AmbariException|StackAdvisorException ex) {
       throw new IllegalArgumentException("Layout recommendation failed.", ex);
     }
   }
 
-  Set<ValidationResponse.ValidationItem> validateRecommendedLayout(StackId 
stackId,
-                                                              
Map<String,Map<String,Set<String>>> recommendedLayout,
-                                                              Map<String, 
Set<String>> recommendedHostgroups) throws StackAdvisorException {
-    Map<String, Set<String>> componentsToHosts = 
getComponentHostMap(recommendedLayout);
-    Map<String, Set<String>> hostsToComponents = 
getHostComponentMap(componentsToHosts);
-    List<String> hosts = ImmutableList.copyOf(hostsToComponents.keySet());
-
-    StackAdvisorRequest request = 
StackAdvisorRequest.StackAdvisorRequestBuilder
-      .forStack(stackId)
-      .ofType(StackAdvisorRequest.StackAdvisorRequestType.HOST_GROUPS)
-      .forHosts(hosts)
-      .forServices(recommendedLayout.keySet())
-      .forHostComponents(hostsToComponents)
-      .forHostsGroupBindings(recommendedHostgroups)
-      .withComponentHostsMap(componentsToHosts)
-      .withGPLLicenseAccepted(serverConfig.getGplLicenseAccepted())
-      .build();
-    ValidationResponse response = stackAdvisorHelper.validate(request);
-
-    return response.getItems();
+  /**
+   * Gets all services from the cluster together together with services from 
AddServiceInfo.
+   * @param info
+   * @return A map of <i>service name -> component name -> hosts</i> for all 
services in the cluster and the input
+   *  AddServiceInfo combined.
+   */
+  Map<String, Map<String, Set<String>>> getAllServices(AddServiceInfo info) 
throws AmbariException {
+    Cluster cluster = 
managementController.getClusters().getCluster(info.clusterName());
+    Map<String, Map<String, Set<String>>> clusterServices = transformValues(
+      cluster.getServices(),
+      service -> transformValues(service.getServiceComponents(), component -> 
component.getServiceComponentsHosts()));
+
+   return  mergeDisjunctMaps(clusterServices, info.newServices());
+  }
+
+  private Cluster getCluster(AddServiceInfo info) throws AmbariException {
+    return managementController.getClusters().getCluster(info.clusterName());
+  }
+
+  AddServiceInfo recommendConfigurations(AddServiceInfo info) {
+    Configuration config = info.getConfig();
+    if (info.getRequest().getRecommendationStrategy().shouldUseStackAdvisor()) 
{
+      LayoutRecommendationInfo layoutInfo = getLayoutRecommendationInfo(info);
+
+      Map<String, Set<String>> componentHostMap = 
getComponentHostMap(layoutInfo.getAllServiceLayouts());
+      Map<String, Set<String>> hostComponentMap = 
getHostComponentMap(componentHostMap);
+      StackAdvisorRequest request = 
StackAdvisorRequest.StackAdvisorRequestBuilder
+        .forStack(info.getStack().getStackId())
+        .ofType(StackAdvisorRequest.StackAdvisorRequestType.CONFIGURATIONS)
+        .forHosts(layoutInfo.getHosts())
+        .forServices(layoutInfo.getAllServiceLayouts().keySet())
+        .forHostComponents(hostComponentMap)
+        .forHostsGroupBindings(layoutInfo.getHostGroups())
+        .withComponentHostsMap(componentHostMap)
+        .withConfigurations(config)
+        .withGPLLicenseAccepted(serverConfig.getGplLicenseAccepted())
+        .build();
+      RecommendationResponse response;
+      try {
+        response = stackAdvisorHelper.recommend(request);
+      }
+      catch (StackAdvisorException|AmbariException ex) {
+        throw new IllegalArgumentException("Configuration recommendation 
failed.", ex);
+      }
+      Map<String, RecommendationResponse.BlueprintConfigurations> 
configRecommendations = 
response.getRecommendations().getBlueprint().getConfigurations();
+
+      // remove recommendations for existing services
+      configRecommendations.keySet().removeIf(configType -> 
!info.newServices().containsKey(info.getStack().getServiceForConfigType(configType)));
+
+      if (info.getRequest().getRecommendationStrategy() == 
ConfigRecommendationStrategy.ONLY_STACK_DEFAULTS_APPLY) {
+        
removeNonStackConfigRecommendations(info.getConfig().getParentConfiguration().getParentConfiguration(),
 configRecommendations);
+      }
+
+      Configuration recommendedConfig = toConfiguration(configRecommendations);
+
+      Configuration userConfig = config;
+      Configuration clusterAndStackConfig = 
userConfig.getParentConfiguration();
+
+      if 
(info.getRequest().getRecommendationStrategy().shouldOverrideCustomValues()) {
+        config = recommendedConfig;
+        config.setParentConfiguration(userConfig);
+      }
+      else {
+        config = userConfig;
+        config.setParentConfiguration(recommendedConfig);
+        recommendedConfig.setParentConfiguration(clusterAndStackConfig);
+      }
+
+      StackAdvisorRequest validationRequest = 
request.builder().withConfigurations(config).build();
+      validate(validationRequest);
+    }
+
+    UnitUpdater.updateUnits(config, info.getStack());
+    return info.withConfig(config);
+  }
+
+  /**
+   * Reuse information from layout recommendation if it happened
+   */
+  LayoutRecommendationInfo getLayoutRecommendationInfo(AddServiceInfo info) {
+    if (info.getRecommendationInfo().isPresent()) {
+      return info.getRecommendationInfo().get();
+    }
+    try {
+      Map<String, Map<String, Set<String>>> allServices = getAllServices(info);
+      Map<String, Set<String>> hostGroups =
+        
getHostGroupStrategy().calculateHostGroups(getHostComponentMap(getComponentHostMap(allServices)));
+      return new LayoutRecommendationInfo(hostGroups, allServices);
+    }
+    catch (AmbariException ex) {
+      throw new IllegalArgumentException("Error gathering host groups and 
services", ex);
+    }
+  }
+
+  static void removeNonStackConfigRecommendations(Configuration stackConfig,  
Map<String, RecommendationResponse.BlueprintConfigurations> 
configRecommendations) {
+    configRecommendations.keySet().removeIf(configType -> 
!stackConfig.containsConfigType(configType));
+    configRecommendations.entrySet().forEach( e -> {
+      String cfgType = e.getKey();
+      RecommendationResponse.BlueprintConfigurations cfg = e.getValue();
+      cfg.getProperties().keySet().removeIf(propName -> 
!stackConfig.containsConfig(cfgType, propName));
+      if (null != cfg.getPropertyAttributes()) {
+        cfg.getPropertyAttributes().keySet().removeIf(propName -> 
!stackConfig.containsConfig(cfgType, propName));
+      }
+    });
+    configRecommendations.values().removeIf(cfg -> 
cfg.getProperties().isEmpty() && cfg.getPropertyAttributes().isEmpty());
   }
 
-  static void keepNewServicesOnly(Map<String,Map<String,Set<String>>> 
recommendedLayout, Map<String,Map<String,Set<String>>> newServices) {
-    recommendedLayout.keySet().retainAll(newServices.keySet());
+  private void validate(StackAdvisorRequest request) {
+    try {
+      Set<ValidationResponse.ValidationItem> items = 
stackAdvisorHelper.validate(request).getItems();
+      if (!items.isEmpty()) {
+        LOG.warn("Issues found during recommended {} validation:\n{}", 
request.getRequestType(), Joiner.on('\n').join(items));
+      }
+    }
+    catch (StackAdvisorException ex) {
+      LOG.error(request.getRequestType() + " validation failed", ex);
+    }
+  }
+
+  static Configuration toConfiguration(Map<String, 
RecommendationResponse.BlueprintConfigurations> configs) {
+    Map<String, Map<String, String>> properties = configs.entrySet().stream()
+      .filter( e -> e.getValue().getProperties() != null && 
!e.getValue().getProperties().isEmpty())
+      .map(e -> Pair.of(e.getKey(), e.getValue().getProperties()))
+      .collect(toMap(Pair::getKey, Pair::getValue));
+
+    Map<String, Map<String, Map<String, String>>> propertyAttributes = 
configs.entrySet().stream()
+        .filter( e -> e.getValue().getPropertyAttributes() != null && 
!e.getValue().getPropertyAttributes().isEmpty())
+        .map(e -> Pair.of(e.getKey(), 
e.getValue().getPropertyAttributesAsMap()))
+        .collect(toMap(Pair::getKey, Pair::getValue));
+
+    return new Configuration(properties, propertyAttributes);
+  }
+
+  static Map<String,Map<String,Set<String>>> 
keepNewServicesOnly(Map<String,Map<String,Set<String>>> recommendedLayout, 
Map<String,Map<String,Set<String>>> newServices) {
+    HashMap<String, java.util.Map<String, Set<String>>> 
newServiceRecommendations = new HashMap<>(recommendedLayout);
+    newServiceRecommendations.keySet().retainAll(newServices.keySet());
+    return newServiceRecommendations;
   }
 
   static Map<String, Map<String, Set<String>>> 
getRecommendedLayout(Map<String, Set<String>> hostGroupHosts,
@@ -170,6 +282,7 @@ public class StackAdvisorAdapter {
         toMap(Map.Entry::getKey, Map.Entry::getValue)));
   }
 
+
   /**
    * Transform a map of component -> hosts to a map of hosts -> components
    * @param componentHostMap the map to transform
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
index 2d97bd0..4b90374 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
@@ -26,7 +26,6 @@ import static 
org.apache.ambari.server.controller.AddServiceRequest.ValidationTy
 import static 
org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_AND_START;
 import static 
org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_ONLY;
 import static 
org.apache.ambari.server.topology.ConfigRecommendationStrategy.ALWAYS_APPLY;
-import static 
org.apache.ambari.server.topology.ConfigRecommendationStrategy.NEVER_APPLY;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -40,6 +39,7 @@ import java.util.Optional;
 
 import org.apache.ambari.server.security.encryption.CredentialStoreType;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.Credential;
 import org.apache.ambari.server.topology.SecurityConfiguration;
@@ -135,7 +135,7 @@ public class AddServiceRequestTest {
 
     // default / empty values
     assertEquals(ADD_SERVICE, request.getOperationType());
-    assertEquals(NEVER_APPLY, request.getRecommendationStrategy());
+    assertEquals(ConfigRecommendationStrategy.defaultForAddService(), 
request.getRecommendationStrategy());
     assertEquals(INSTALL_AND_START, request.getProvisionAction());
     assertEquals(STRICT, request.getValidationType());
     assertNull(request.getStackName());
@@ -159,7 +159,7 @@ public class AddServiceRequestTest {
 
     // default / empty values
     assertEquals(ADD_SERVICE, request.getOperationType());
-    assertEquals(NEVER_APPLY, request.getRecommendationStrategy());
+    assertEquals(ConfigRecommendationStrategy.defaultForAddService(), 
request.getRecommendationStrategy());
     assertEquals(INSTALL_AND_START, request.getProvisionAction());
     assertNull(request.getStackName());
     assertNull(request.getStackVersion());
@@ -182,7 +182,7 @@ public class AddServiceRequestTest {
 
     // default / empty values
     assertEquals(ADD_SERVICE, request.getOperationType());
-    assertEquals(NEVER_APPLY, request.getRecommendationStrategy());
+    assertEquals(ConfigRecommendationStrategy.defaultForAddService(), 
request.getRecommendationStrategy());
     assertEquals(INSTALL_AND_START, request.getProvisionAction());
     assertNull(request.getStackName());
     assertNull(request.getStackVersion());
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UnitUpdaterTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UnitUpdaterTest.java
index 6de6cd1..fbf448d 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UnitUpdaterTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UnitUpdaterTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ambari.server.controller.internal;
 
+import static org.apache.ambari.server.testutils.TestCollectionUtils.map;
 import static org.easymock.EasyMock.expect;
 import static org.junit.Assert.assertEquals;
 
@@ -28,7 +29,7 @@ import 
org.apache.ambari.server.controller.StackConfigurationResponse;
 import org.apache.ambari.server.state.ValueAttributesInfo;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.ClusterTopology;
-import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.Configuration;
 import org.easymock.EasyMockRule;
 import org.easymock.EasyMockSupport;
 import org.easymock.Mock;
@@ -86,10 +87,54 @@ public class UnitUpdaterTest extends EasyMockSupport {
     updateUnit(OOZIE, OOZIE_ENV, HEAPSIZE, "");
   }
 
+  @Test
+  public void updateUnits() {
+    stackUnitIs(HEAPSIZE, "MB");
+    setUpStack(OOZIE, OOZIE_ENV);
+
+    Map<String, Map<String, String>> properties = map(
+      OOZIE_ENV, map(HEAPSIZE, "1024"),
+      "core-site", map("fs.trash.interval", "360"));
+    Configuration configuration = new Configuration(properties, new 
HashMap<>());
+
+    UnitUpdater.updateUnits(configuration, stack);
+
+    Map<String, Map<String, String>> expected = map(
+      OOZIE_ENV, map(HEAPSIZE, "1024m"),
+      "core-site", map("fs.trash.interval", "360"));
+
+    assertEquals(expected, configuration.getProperties());
+  }
+
+  @Test
+  public void removeUnits() {
+    stackUnitIs(HEAPSIZE, "MB");
+    setUpStack(OOZIE, OOZIE_ENV);
+
+    Map<String, Map<String, String>> properties = map(
+      OOZIE_ENV, map(HEAPSIZE, "1024m"),
+      "core-site", map("fs.trash.interval", "360"));
+    Configuration configuration = new Configuration(properties, new 
HashMap<>());
+
+    UnitUpdater.removeUnits(configuration, stack);
+
+    Map<String, Map<String, String>> expected = map(
+      OOZIE_ENV, map(HEAPSIZE, "1024"),
+      "core-site", map("fs.trash.interval", "360"));
+
+    assertEquals(expected, configuration.getProperties());
+  }
+
   private void stackUnitIs(String name, String unit) {
     ValueAttributesInfo propertyValueAttributes = new ValueAttributesInfo();
     propertyValueAttributes.setUnit(unit);
-    stackConfigWithMetadata.put(name, new Stack.ConfigProperty(new 
StackConfigurationResponse(
+    stackConfigWithMetadata.put(name, configProperty(name, unit));
+  }
+
+  public static Stack.ConfigProperty configProperty(String name, String unit) {
+    ValueAttributesInfo propertyValueAttributes = new ValueAttributesInfo();
+    propertyValueAttributes.setUnit(unit);
+    return new Stack.ConfigProperty(new StackConfigurationResponse(
       name,
       "any",
       "any",
@@ -99,16 +144,20 @@ public class UnitUpdaterTest extends EasyMockSupport {
       Collections.emptySet(),
       Collections.emptyMap(),
       propertyValueAttributes,
-      Collections.emptySet()
-    )));
+      Collections.emptySet()));
   }
 
-  private String updateUnit(String serviceName, String configType, String 
propName, String propValue) throws InvalidTopologyException, 
ConfigurationTopologyException {
-    UnitUpdater updater = new UnitUpdater(serviceName, configType);
+  private void setUpStack(String serviceName, String configType) {
     expect(clusterTopology.getBlueprint()).andReturn(blueprint).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
     expect(stack.getConfigurationPropertiesWithMetadata(serviceName, 
configType)).andReturn(stackConfigWithMetadata).anyTimes();
     replayAll();
+  }
+
+  private String updateUnit(String serviceName, String configType, String 
propName, String propValue) {
+    UnitUpdater updater = new UnitUpdater(serviceName, configType);
+    setUpStack(serviceName, configType);
     return updater.updateForClusterCreate(propName, propValue, 
Collections.emptyMap(), clusterTopology);
   }
+
 }
\ No newline at end of file
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/testutils/TestCollectionUtils.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/testutils/TestCollectionUtils.java
new file mode 100644
index 0000000..0a7c5ac
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/testutils/TestCollectionUtils.java
@@ -0,0 +1,50 @@
+/*
+ * 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.testutils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Utilities for collections used in unit tests.
+ */
+public class TestCollectionUtils {
+
+  /**
+   * A simple (but not production quality) way to create mutable hashmaps for 
unit tests
+   * @param firstKey the first key in the map
+   * @param firstValue the first value in the map
+   * @param others further keys and values
+   * @param <K> key type
+   * @param <V> value type
+   * @return the map
+   */
+  @SuppressWarnings("unchecked")
+  public static <K, V> Map<K, V> map(K firstKey, V firstValue, Object... 
others) {
+    Map<K, V> map = new HashMap<>();
+    map.put(firstKey, firstValue);
+    Iterator iterator = Arrays.asList(others).iterator();
+    while (iterator.hasNext()) {
+      map.put((K)iterator.next(), (V)iterator.next());
+    }
+    return map;
+  }
+
+}
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java
index 752e3af..cc4c33b 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.fail;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
 
 import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -135,6 +136,21 @@ public class ConfigurableTest {
       configurable.getConfiguration().getProperties());
   }
 
+  @Test
+  public void testTransformAttributesMap() {
+    Map<String, Map<String, String>> attributes = ImmutableMap.of(
+      "propertyName1", ImmutableMap.of("minimum", "3000", "maximum", "4000"),
+      "propertyName2", ImmutableMap.of("minimum", "3500", "maximum", "4500", 
"hidden", "true"));
+
+    Map<String, Map<String, String>> transformed = ImmutableMap.of(
+      "minimum", ImmutableMap.of("propertyName1", "3000", "propertyName2", 
"3500"),
+      "maximum", ImmutableMap.of("propertyName1", "4000", "propertyName2", 
"4500"),
+      "hidden", ImmutableMap.of("propertyName2", "true"));
+
+    assertEquals(transformed, 
ConfigurableHelper.transformAttributesMap(attributes));
+    assertEquals(attributes, 
ConfigurableHelper.transformAttributesMap(transformed));
+  }
+
   static class TestConfigurable implements Configurable {
     Configuration configuration;
 
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurationTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurationTest.java
index 23b7e81..51b7193 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurationTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurationTest.java
@@ -158,6 +158,55 @@ public class ConfigurationTest {
   }
 
   @Test
+  public void containsConfigType() {
+    Configuration configuration = createConfigurationWithParents_PropsOnly();
+    assertTrue(configuration.containsConfigType("type1"));
+    assertTrue(configuration.containsConfigType("type2"));
+    assertTrue(configuration.containsConfigType("type3"));
+    assertTrue(configuration.containsConfigType("type4"));
+    assertFalse(configuration.containsConfigType("type5"));
+
+    configuration = createConfigurationWithParents_AttributesOnly();
+    assertTrue(configuration.containsConfigType("type1"));
+    assertTrue(configuration.containsConfigType("type2"));
+    assertFalse(configuration.containsConfigType("type3"));
+  }
+
+  @Test
+  public void containsConfig() {
+    Configuration configuration = createConfigurationWithParents_PropsOnly();
+    assertTrue(configuration.containsConfig("type1", "prop1"));
+    assertTrue(configuration.containsConfig("type1", "prop2"));
+    assertTrue(configuration.containsConfig("type1", "prop3"));
+    assertTrue(configuration.containsConfig("type2", "prop4"));
+    assertTrue(configuration.containsConfig("type2", "prop5"));
+    assertTrue(configuration.containsConfig("type1", "prop6"));
+    assertTrue(configuration.containsConfig("type1", "prop9"));
+    assertTrue(configuration.containsConfig("type3", "prop7"));
+    assertTrue(configuration.containsConfig("type3", "prop8"));
+    assertTrue(configuration.containsConfig("type4", "prop10"));
+    assertTrue(configuration.containsConfig("type4", "prop11"));
+    assertFalse(configuration.containsConfig("type1", "prop99"));
+    assertFalse(configuration.containsConfig("core-site", 
"io.file.buffer.size"));
+
+    configuration = createConfigurationWithParents_AttributesOnly();
+    assertTrue(configuration.containsConfig("type1", "prop1"));
+    assertTrue(configuration.containsConfig("type1", "prop2"));
+    assertTrue(configuration.containsConfig("type1", "prop3"));
+    assertTrue(configuration.containsConfig("type1", "prop6"));
+    assertTrue(configuration.containsConfig("type1", "prop7"));
+    assertTrue(configuration.containsConfig("type1", "prop8"));
+    assertTrue(configuration.containsConfig("type1", "prop9"));
+    assertTrue(configuration.containsConfig("type1", "prop10"));
+    assertTrue(configuration.containsConfig("type1", "prop11"));
+    assertTrue(configuration.containsConfig("type2", "prop100"));
+    assertTrue(configuration.containsConfig("type2", "prop101"));
+    assertTrue(configuration.containsConfig("type2", "prop102"));
+    assertFalse(configuration.containsConfig("type1", "prop99"));
+    assertFalse(configuration.containsConfig("core-site", 
"io.file.buffer.size"));
+  }
+
+  @Test
   public void testGetFullProperties_withParent_specifyDepth() {
     Configuration configuration = createConfigurationWithParents_PropsOnly();
     // specify a depth of 1 which means to include only 1 level up the parent 
chain
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
index e9c3ff7..13fc281 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
@@ -48,6 +48,7 @@ import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
 import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.SecurityConfiguration;
 import org.apache.ambari.server.topology.SecurityConfigurationFactory;
@@ -467,6 +468,7 @@ public class RequestValidatorTest extends EasyMockSupport {
     requestConfig.setProperty("kafka-broker", "zookeeper.connect", 
"zookeeper.connect:request");
     requestConfig.setProperty("kafka-env", "custom_property", 
"custom_property:request");
     
expect(request.getConfiguration()).andReturn(requestConfig.copy()).anyTimes();
+    
expect(request.getRecommendationStrategy()).andReturn(ConfigRecommendationStrategy.ALWAYS_APPLY).anyTimes();
 
     Configuration clusterConfig = Configuration.newEmpty();
     clusterConfig.setProperty("zookeeper-env", "zk_user", 
"zk_user:cluster_level");
@@ -477,7 +479,7 @@ public class RequestValidatorTest extends EasyMockSupport {
     stackConfig.setProperty("zookeeper-env", "zk_user", 
"zk_user:stack_default");
     stackConfig.setProperty("zookeeper-env", "zk_log_dir", 
"zk_log_dir:stack_default");
     stackConfig.setProperty("kafka-broker", "zookeeper.connect", 
"zookeeper.connect:stack_default");
-    expect(stack.getValidDefaultConfig()).andReturn(stackConfig).anyTimes();
+    expect(stack.getDefaultConfig()).andReturn(stackConfig).anyTimes();
 
     replayAll();
 
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapterTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapterTest.java
index abd570a..78f481e 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapterTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/StackAdvisorAdapterTest.java
@@ -21,6 +21,9 @@ package org.apache.ambari.server.topology.addservice;
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 import static java.util.stream.Collectors.toMap;
+import static 
org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType.HOST_GROUPS;
+import static 
org.apache.ambari.server.controller.internal.UnitUpdaterTest.configProperty;
+import static org.apache.ambari.server.testutils.TestCollectionUtils.map;
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.expect;
@@ -28,15 +31,18 @@ import static org.easymock.EasyMock.getCurrentArguments;
 import static org.easymock.EasyMock.mock;
 import static org.easymock.EasyMock.replay;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
 import 
org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import 
org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
-import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.AddServiceRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.internal.Stack;
 import org.apache.ambari.server.state.Cluster;
@@ -44,8 +50,11 @@ import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Service;
 import org.apache.ambari.server.state.ServiceComponent;
 import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
+import org.apache.ambari.server.topology.Configuration;
 import org.apache.commons.lang3.tuple.Pair;
 import org.easymock.EasyMockRunner;
+import org.easymock.IExpectationSetters;
 import org.easymock.Mock;
 import org.easymock.TestSubject;
 import org.junit.Before;
@@ -66,7 +75,7 @@ public class StackAdvisorAdapterTest {
   private StackAdvisorHelper stackAdvisorHelper;
 
   @Mock
-  private Configuration serverConfig;
+  private org.apache.ambari.server.configuration.Configuration serverConfig;
 
   @Mock
   private Injector injector;
@@ -158,20 +167,67 @@ public class StackAdvisorAdapterTest {
   }
 
   @Test
+  public void getLayoutRecommendationInfo() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.ALWAYS_APPLY), "c1", stack 
, Configuration.newEmpty(),
+      null, null, newServices, null); // No LayoutReommendationInfo -> needs 
to be calculated
+
+    LayoutRecommendationInfo layoutRecommendationInfo = 
adapter.getLayoutRecommendationInfo(info);
+    layoutRecommendationInfo.getAllServiceLayouts();
+
+    assertEquals(
+      ImmutableMap.of(
+        "host_group_1", ImmutableSet.of("c7401"),
+        "host_group_2", ImmutableSet.of("c7402"),
+        "host_group_3", ImmutableSet.of("c7403", "c7404")),
+      layoutRecommendationInfo.getHostGroups());
+
+    assertEquals(
+      ImmutableMap.<String, Map<String, Set<String>>>builder()
+        .put("KAFKA", ImmutableMap.of(
+          "KAFKA_BROKER", ImmutableSet.of("c7401")))
+        .put("SPARK2", ImmutableMap.of(
+          "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+          "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")))
+        .put("OOZIE", ImmutableMap.of(
+          "OOZIE_SERVER", ImmutableSet.of("c7401"),
+          "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")))
+        .put("HDFS", ImmutableMap.of(
+          "NAMENODE", ImmutableSet.of("c7401"),
+          "HDFS_CLIENT", ImmutableSet.of("c7401", "c7402")))
+        .put("ZOOKEEPER", ImmutableMap.of(
+          "ZOOKEEPER_SERVER", ImmutableSet.of("c7401"),
+          "ZOOKEEPER_CLIENT", ImmutableSet.of("c7401", "c7402")))
+        .put("MAPREDUCE2", ImmutableMap.of(
+          "HISTORYSERVER", ImmutableSet.of("c7401")))
+        .build(),
+      layoutRecommendationInfo.getAllServiceLayouts());
+  }
+
+  @Test
   public void keepNewServicesOnly() {
     Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
       "KAFKA", emptyMap(),
       "PIG", emptyMap());
 
-    Map<String, Map<String, Set<String>>> recommendationForNewServices = 
ImmutableMap.of(
+    Map<String, Map<String, Set<String>>> expectedNewServiceRecommendations = 
ImmutableMap.of(
       "KAFKA", ImmutableMap.of("KAFKA_BROKER", ImmutableSet.of("c7405")),
       "PIG", ImmutableMap.of("PIG_CLIENT", ImmutableSet.of("c7405", "c7406")));
 
     Map<String, Map<String, Set<String>>> recommendations = new 
HashMap<>(SERVICE_COMPONENT_HOST_MAP_1);
-    recommendations.putAll(recommendationForNewServices);
+    recommendations.putAll(expectedNewServiceRecommendations);
 
-    StackAdvisorAdapter.keepNewServicesOnly(recommendations, newServices);
-    assertEquals(recommendationForNewServices, recommendations);
+    Map<String, Map<String, Set<String>>> newServiceRecommendations = 
StackAdvisorAdapter.keepNewServicesOnly(recommendations, newServices);
+    assertEquals(expectedNewServiceRecommendations, newServiceRecommendations);
   }
 
   @Before
@@ -182,39 +238,71 @@ public class StackAdvisorAdapterTest {
       "HDFS",
       service("HDFS", ImmutableMap.of("NAMENODE", ImmutableSet.of("c7401"), 
"HDFS_CLIENT", ImmutableSet.of("c7401", "c7402"))),
       "ZOOKEEPER",
-      service("ZOOKEEPER", ImmutableMap.of("ZOOKEEPER_SERVER", 
ImmutableSet.of("c7401"), "ZOOKEEPER_CLIENT", ImmutableSet.of("c7401", 
"c7402")))));
+      service("ZOOKEEPER", ImmutableMap.of("ZOOKEEPER_SERVER", 
ImmutableSet.of("c7401"), "ZOOKEEPER_CLIENT", ImmutableSet.of("c7401", 
"c7402"))),
+      "MAPREDUCE2",
+      service("MAPREDUCE2", ImmutableMap.of("HISTORYSERVER", 
ImmutableSet.of("c7401")))));
     Clusters clusters = mock(Clusters.class);
     expect(clusters.getCluster(anyString())).andReturn(cluster).anyTimes();
     expect(managementController.getClusters()).andReturn(clusters).anyTimes();
     replay(clusters, cluster, managementController);
 
     
expect(serverConfig.getGplLicenseAccepted()).andReturn(Boolean.FALSE).anyTimes();
-    
expect(serverConfig.getAddServiceHostGroupStrategyClass()).andReturn((Class)GroupByComponentsStrategy.class).anyTimes();
+    @SuppressWarnings("unchecked")
+    IExpectationSetters iExpectationSetters = 
expect(serverConfig.getAddServiceHostGroupStrategyClass()).andReturn((Class) 
GroupByComponentsStrategy.class).anyTimes();
     replay(serverConfig);
 
     
expect(injector.getInstance(GroupByComponentsStrategy.class)).andReturn(new 
GroupByComponentsStrategy()).anyTimes();
     replay(injector);
 
-    RecommendationResponse response = new RecommendationResponse();
-    RecommendationResponse.Recommendation recommendation = new 
RecommendationResponse.Recommendation();
-    response.setRecommendations(recommendation);
     RecommendationResponse.BlueprintClusterBinding binding = 
RecommendationResponse.BlueprintClusterBinding.fromHostGroupHostMap(
       ImmutableMap.of(
         "hostgroup-1", ImmutableSet.of("c7401"),
         "hostgroup-2", ImmutableSet.of("c7402")));
-    recommendation.setBlueprintClusterBinding(binding);
     RecommendationResponse.Blueprint blueprint = new 
RecommendationResponse.Blueprint();
     
blueprint.setHostGroups(RecommendationResponse.HostGroup.fromHostGroupComponents(
       ImmutableMap.of(
-        "hostgroup-1", ImmutableSet.of("NAMENODE", "HDFS_CLIENT", 
"ZOOKEEPER_SERVER", "ZOOKEEPER_CLIENT"),
+        "hostgroup-1", ImmutableSet.of("NAMENODE", "HDFS_CLIENT", 
"ZOOKEEPER_SERVER", "ZOOKEEPER_CLIENT", "HISTORYSERVER"),
         "hostgroup-2", ImmutableSet.of("HDFS_CLIENT", "ZOOKEEPER_CLIENT", 
"KAFKA_BROKER"))
     ));
-    recommendation.setBlueprint(blueprint);
-    expect(stackAdvisorHelper.recommend(anyObject())).andReturn(response);
+    RecommendationResponse layoutResponse = createRecommendation(blueprint, 
binding);
+
+    RecommendationResponse.Blueprint configBlueprint = new 
RecommendationResponse.Blueprint();
+    RecommendationResponse.BlueprintConfigurations kafkaBroker = 
RecommendationResponse.BlueprintConfigurations.create(
+      ImmutableMap.of("log.dirs", "/kafka-logs", 
"offsets.topic.replication.factor", "1"),
+      ImmutableMap.of("maximum", 
ImmutableMap.of("offsets.topic.replication.factor", "10")));
+    RecommendationResponse.BlueprintConfigurations spark2Defaults = 
RecommendationResponse.BlueprintConfigurations.create(
+      ImmutableMap.of("spark.yarn.queue", "default"), null);
+    RecommendationResponse.BlueprintConfigurations mapredSite = 
RecommendationResponse.BlueprintConfigurations.create(
+      ImmutableMap.of("mapreduce.map.memory.mb", "682", 
"mapreduce.reduce.memory.mb", "1364"),
+      ImmutableMap.of(
+        "minimum", ImmutableMap.of("mapreduce.map.memory.mb", "682", 
"mapreduce.reduce.memory.mb", "682"),
+        "maximum" , ImmutableMap.of("mapreduce.map.memory.mb", "2046", 
"mapreduce.reduce.memory.mb", "2046")));
+    configBlueprint.setConfigurations(map(
+      "kafka-broker", kafkaBroker,
+      "spark2-defaults", spark2Defaults,
+      "mapred-site", mapredSite
+    ));
+    RecommendationResponse configResponse = 
createRecommendation(configBlueprint, binding);
+
+    expect(stackAdvisorHelper.recommend(anyObject())).andAnswer(() -> {
+      StackAdvisorRequest request = (StackAdvisorRequest) 
getCurrentArguments()[0];
+      assertNotNull(request.getHosts());
+      assertNotNull(request.getServices());
+      assertNotNull(request.getStackName());
+      assertNotNull(request.getStackVersion());
+      assertNotNull(request.getConfigurations());
+      assertNotNull(request.getHostComponents());
+      assertNotNull(request.getComponentHostsMap());
+      assertNotNull(request.getHostGroupBindings());
+      assertNotNull(request.getLdapConfig());
+      assertNotNull(request.getRequestType());
+      return request.getRequestType() == HOST_GROUPS ? layoutResponse : 
configResponse;
+    });
 
     ValidationResponse validationResponse = new ValidationResponse();
     validationResponse.setItems(emptySet());
     
expect(stackAdvisorHelper.validate(anyObject())).andReturn(validationResponse);
+
     replay(stackAdvisorHelper);
 
     expect(stack.getStackId()).andReturn(new StackId("HDP", "3.0")).anyTimes();
@@ -224,11 +312,32 @@ public class StackAdvisorAdapterTest {
       .put("HDFS_CLIENT", "HDFS")
       .put("ZOOKEEPER_SERVER", "ZOOKEEPER")
       .put("ZOOKEEPER_CLIENT", "ZOOKEEPER")
+      .put("HISTORYSERVER", "MAPREDUCE2")
       .build();
     expect(stack.getServiceForComponent(anyString())).andAnswer(() -> 
serviceComponentMap.get(getCurrentArguments()[0])).anyTimes();
+    ImmutableMap<String, String> configTypeServiceMap = ImmutableMap.<String, 
String>builder()
+      .put("kafka-broker", "KAFKA")
+      .put("spark2-defaults", "SPARK2")
+      .put("mapred-site", "MAPREDUCE2")
+      .build();
+    expect(stack.getServiceForConfigType(anyString())).andAnswer(() -> 
configTypeServiceMap.get(getCurrentArguments()[0])).anyTimes();
+    expect(stack.getConfigurationPropertiesWithMetadata("OOZIE", 
"oozie-env")).andReturn(
+      ImmutableMap.of(
+        "mapreduce.map.memory.mb", configProperty("mapreduce.map.memory.mb", 
"MB"),
+        "mapreduce.reduce.memory.mb", 
configProperty("mapreduce.reduce.memory.mb", "MB"))).anyTimes();
     replay(stack);
   }
 
+  private static RecommendationResponse 
createRecommendation(RecommendationResponse.Blueprint blueprint,
+                                                                            
RecommendationResponse.BlueprintClusterBinding binding) {
+    RecommendationResponse response = new RecommendationResponse();
+    RecommendationResponse.Recommendation recommendation = new 
RecommendationResponse.Recommendation();
+    response.setRecommendations(recommendation);
+    recommendation.setBlueprint(blueprint);
+    recommendation.setBlueprintClusterBinding(binding);
+    return response;
+  }
+
   private static Service service(String name, 
ImmutableMap<String,ImmutableSet<String>> componentHostMap) {
     Service service = mock(Service.class);
     expect(service.getName()).andReturn(name).anyTimes();
@@ -252,7 +361,7 @@ public class StackAdvisorAdapterTest {
       "KAFKA",
       ImmutableMap.of("KAFKA_BROKER", emptySet()));
 
-    AddServiceInfo info = new AddServiceInfo(null, "c1", stack, 
org.apache.ambari.server.topology.Configuration.newEmpty(), null, null, 
newServices);
+    AddServiceInfo info = new AddServiceInfo(null, "c1", stack, 
org.apache.ambari.server.topology.Configuration.newEmpty(), null, null, 
newServices, null);
     AddServiceInfo infoWithRecommendations = adapter.recommendLayout(info);
 
     Map<String, Map<String, Set<String>>> expectedNewLayout = ImmutableMap.of(
@@ -263,13 +372,292 @@ public class StackAdvisorAdapterTest {
     assertEquals(expectedNewLayout, infoWithRecommendations.newServices());
   }
 
+  @Test
+  public void recommendConfigurations_noLayoutInfo() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    Configuration stackConfig = Configuration.newEmpty();
+    Configuration clusterConfig = new Configuration(
+      map("oozie-env", map("oozie_heapsize", "1024", "oozie_permsize", "256")),
+      emptyMap());
+    Configuration userConfig = Configuration.newEmpty();
+    userConfig.setParentConfiguration(clusterConfig);
+    clusterConfig.setParentConfiguration(stackConfig);
+
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.ALWAYS_APPLY), "c1", stack 
, userConfig,
+      null, null, newServices, null); // No LayoutRecommendationInfo
+    AddServiceInfo infoWithConfig = adapter.recommendConfigurations(info);
+
+    Configuration recommendedConfig = infoWithConfig.getConfig();
+    assertSame(userConfig, recommendedConfig.getParentConfiguration());
+    assertSame(clusterConfig, userConfig.getParentConfiguration());
+    assertSame(stackConfig, clusterConfig.getParentConfiguration());
+
+    // Yarn/Mapred config is excpected to be removed as it does not belong to 
newly added services
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "log.dirs", "/kafka-logs",
+          "offsets.topic.replication.factor", "1"),
+        "spark2-defaults", ImmutableMap.of(
+          "spark.yarn.queue", "default"),
+        "oozie-env", ImmutableMap.of(
+          "oozie_heapsize", "1024m",  // unit updates should happen
+          "oozie_permsize", "256m")),
+      recommendedConfig.getProperties());
+
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "maximum", ImmutableMap.of("offsets.topic.replication.factor", 
"10"))),
+      recommendedConfig.getAttributes());
+  }
 
-  private static Map<String, Map<String, Set<String>>> mutableCopy(Map<String, 
Map<String, Set<String>>> map) {
-    Map<String, Map<String, Set<String>>> copy = new HashMap<>();
-    map.entrySet().forEach( outer -> {
-      Map<String, Set<String>> innerCopy = new HashMap<>(outer.getValue());
-      copy.put(outer.getKey(), innerCopy);
-    });
-    return copy;
+  @Test
+  public void recommendConfigurations_alwaysApply() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    Configuration stackConfig = Configuration.newEmpty();
+    Configuration clusterConfig = new Configuration(
+      map("oozie-env", map("oozie_heapsize", "1024", "oozie_permsize", "256")),
+      emptyMap());
+    Configuration userConfig = Configuration.newEmpty();
+    userConfig.setParentConfiguration(clusterConfig);
+    clusterConfig.setParentConfiguration(stackConfig);
+
+    LayoutRecommendationInfo layoutRecommendationInfo = new 
LayoutRecommendationInfo(new HashMap<>(), new HashMap<>()); // contents doesn't 
matter for the test
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.ALWAYS_APPLY), "c1", stack 
, userConfig,
+      null, null, newServices, layoutRecommendationInfo);
+    AddServiceInfo infoWithConfig = adapter.recommendConfigurations(info);
+
+    Configuration recommendedConfig = infoWithConfig.getConfig();
+    assertSame(userConfig, recommendedConfig.getParentConfiguration());
+    assertSame(clusterConfig, userConfig.getParentConfiguration());
+    assertSame(stackConfig, clusterConfig.getParentConfiguration());
+
+    // Yarn/Mapred config is excpected to be removed as it does not belong to 
newly added services
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "log.dirs", "/kafka-logs",
+          "offsets.topic.replication.factor", "1"),
+        "spark2-defaults", ImmutableMap.of(
+          "spark.yarn.queue", "default"),
+        "oozie-env", ImmutableMap.of(
+          "oozie_heapsize", "1024m",  // unit updates should happen
+          "oozie_permsize", "256m")),
+      recommendedConfig.getProperties());
+
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "maximum", ImmutableMap.of("offsets.topic.replication.factor", 
"10"))),
+      recommendedConfig.getAttributes());
+  }
+
+  @Test
+  public void recommendConfigurations_alwaysDoNotOverrideCustomValues() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    Configuration stackConfig = Configuration.newEmpty();
+    Configuration clusterConfig = new Configuration(
+      map("oozie-env", map("oozie_heapsize", "1024", "oozie_permsize", "256")),
+      emptyMap());
+    Configuration userConfig = Configuration.newEmpty();
+    userConfig.setParentConfiguration(clusterConfig);
+    clusterConfig.setParentConfiguration(stackConfig);
+
+    LayoutRecommendationInfo layoutRecommendationInfo = new 
LayoutRecommendationInfo(new HashMap<>(), new HashMap<>()); // contents doesn't 
matter for the test
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES),
 "c1", stack , userConfig,
+      null, null, newServices, layoutRecommendationInfo);
+    AddServiceInfo infoWithConfig = adapter.recommendConfigurations(info);
+
+    assertSame(userConfig, infoWithConfig.getConfig()); // user config stays 
top priority
+    Configuration recommendedConfig = userConfig.getParentConfiguration();
+    assertSame(clusterConfig, recommendedConfig.getParentConfiguration());
+    assertSame(stackConfig, clusterConfig.getParentConfiguration());
+
+    // Yarn/Mapred config is excpected to be removed as it does not belong to 
newly added services
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "log.dirs", "/kafka-logs",
+          "offsets.topic.replication.factor", "1"),
+        "spark2-defaults", ImmutableMap.of(
+          "spark.yarn.queue", "default")),
+      recommendedConfig.getProperties());
+
+    // the result of unit updates always happen in the top level config
+    assertEquals(
+      ImmutableMap.of(
+        "oozie-env", ImmutableMap.of(
+          "oozie_heapsize", "1024m",
+          "oozie_permsize", "256m")),
+      userConfig.getProperties());
+
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "maximum", ImmutableMap.of("offsets.topic.replication.factor", 
"10"))),
+      recommendedConfig.getAttributes());
+  }
+
+  @Test
+  public void recommendConfigurations_neverApply() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    Configuration stackConfig = Configuration.newEmpty();
+    Configuration clusterConfig = new Configuration(
+      map("oozie-env", map("oozie_heapsize", "1024", "oozie_permsize", "256")),
+      emptyMap());
+    Configuration userConfig = Configuration.newEmpty();
+    userConfig.setParentConfiguration(clusterConfig);
+    clusterConfig.setParentConfiguration(stackConfig);
+
+    LayoutRecommendationInfo layoutRecommendationInfo = new 
LayoutRecommendationInfo(new HashMap<>(), new HashMap<>()); // contents doesn't 
matter for the test
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.NEVER_APPLY), "c1", stack , 
userConfig,
+      null, null, newServices, layoutRecommendationInfo);
+    AddServiceInfo infoWithConfig = adapter.recommendConfigurations(info);
+
+    // No recommended config, no stack config
+    assertSame(userConfig, infoWithConfig.getConfig());
+    assertSame(clusterConfig, userConfig.getParentConfiguration());
+    assertNotNull(clusterConfig.getParentConfiguration());
+
+    // the result of unit updates always happen in the top level config
+    assertEquals(
+      ImmutableMap.of(
+        "oozie-env", ImmutableMap.of(
+          "oozie_heapsize", "1024m",
+          "oozie_permsize", "256m")),
+      userConfig.getProperties());
   }
+
+  @Test
+  public void recommendConfigurations_onlyStackDefaultsApply() {
+    Map<String, Map<String, Set<String>>> newServices = ImmutableMap.of(
+      "KAFKA", ImmutableMap.of(
+        "KAFKA_BROKER", ImmutableSet.of("c7401")),
+      "SPARK2", ImmutableMap.of(
+        "SPARK2_JOBHISTORYSERVER", ImmutableSet.of("c7402"),
+        "SPARK2_CLIENT", ImmutableSet.of("c7403", "c7404")),
+      "OOZIE", ImmutableMap.of(
+        "OOZIE_SERVER", ImmutableSet.of("c7401"),
+        "OOZIE_CLIENT", ImmutableSet.of("c7403", "c7404")));
+
+    Configuration stackConfig = new Configuration(
+      ImmutableMap.of("kafka-broker", ImmutableMap.of("log.dirs", 
"/kafka-logs-stackdefault")),
+      ImmutableMap.of());
+    Configuration clusterConfig = new Configuration(
+      ImmutableMap.of("oozie-env", ImmutableMap.of("oozie_heapsize", "1024", 
"oozie_permsize", "256")),
+      emptyMap());
+    Configuration userConfig = Configuration.newEmpty();
+    userConfig.setParentConfiguration(clusterConfig);
+    clusterConfig.setParentConfiguration(stackConfig);
+
+    LayoutRecommendationInfo layoutRecommendationInfo = new 
LayoutRecommendationInfo(new HashMap<>(), new HashMap<>()); // contents doesn't 
matter for the test
+    AddServiceInfo info = new 
AddServiceInfo(request(ConfigRecommendationStrategy.ONLY_STACK_DEFAULTS_APPLY), 
"c1", stack , userConfig,
+      null, null, newServices, layoutRecommendationInfo);
+    AddServiceInfo infoWithConfig = adapter.recommendConfigurations(info);
+    Configuration recommendedConfig = 
infoWithConfig.getConfig().getParentConfiguration();
+
+    // No recommended config
+    assertSame(userConfig, infoWithConfig.getConfig()); // user config is top 
level in this case
+    assertSame(clusterConfig, recommendedConfig.getParentConfiguration());
+    assertSame(stackConfig, clusterConfig.getParentConfiguration());
+
+    assertEquals(
+      ImmutableMap.of(
+        "kafka-broker", ImmutableMap.of(
+          "log.dirs", "/kafka-logs")),
+      recommendedConfig.getProperties());
+
+    // the result of unit updates always happen in the top level config
+    assertEquals(
+      ImmutableMap.of(
+        "oozie-env", ImmutableMap.of(
+          "oozie_heapsize", "1024m",
+          "oozie_permsize", "256m")),
+      userConfig.getProperties());
+  }
+
+  @Test
+  public void removeNonStackConfigRecommendations() {
+    Map<String, Map<String, String>> stackProperties = ImmutableMap.of(
+      "kafka-broker", ImmutableMap.of(
+        "log.dirs", "/kafka-logs",
+        "offsets.topic.replication.factor", "1"),
+      "spark2-defaults", ImmutableMap.of(
+        "spark.yarn.queue", "default"));
+
+    Map<String, Map<String, Map<String, String>>> stackAttributes = 
ImmutableMap.of(
+      "oozie-env",
+      ImmutableMap.of(
+        "miniumum",
+        ImmutableMap.of("oozie_heapsize", "1024", "oozie_permsize", "256")));
+
+    Configuration stackConfig = new Configuration(stackProperties, 
stackAttributes);
+
+    Map<String, RecommendationResponse.BlueprintConfigurations> 
recommendedConfigs =
+      map(
+        "hdfs-site", RecommendationResponse.BlueprintConfigurations.create(
+          map("dfs.namenode.name.dir", "/hadoop/hdfs/namenode"),
+          map("visible", ImmutableMap.of("dfs.namenode.name.dir", "false"))),
+        "oozie-env", RecommendationResponse.BlueprintConfigurations.create(
+          map("oozie_heapsize", "2048"),
+          new HashMap<>()),
+        "spark2-defaults", 
RecommendationResponse.BlueprintConfigurations.create(
+          map("spark.yarn.queue", "spark2"),
+          new HashMap<>()));
+
+    Map<String, RecommendationResponse.BlueprintConfigurations> 
recommendedConfigsForStackDefaults =
+      ImmutableMap.of(
+        "oozie-env", RecommendationResponse.BlueprintConfigurations.create(
+          ImmutableMap.of("oozie_heapsize", "2048"),
+          ImmutableMap.of()),
+        "spark2-defaults", 
RecommendationResponse.BlueprintConfigurations.create(
+          ImmutableMap.of("spark.yarn.queue", "spark2"),
+          ImmutableMap.of()));
+
+    StackAdvisorAdapter.removeNonStackConfigRecommendations(stackConfig, 
recommendedConfigs);
+
+    assertEquals(recommendedConfigsForStackDefaults, recommendedConfigs);
+  }
+
+  private AddServiceRequest request(ConfigRecommendationStrategy strategy) {
+    return new AddServiceRequest(null, strategy, null, null, null, null, null, 
null, null, null, null);
+  }
+
 }
\ No newline at end of file

Reply via email to