You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by be...@apache.org on 2018/09/14 11:01:40 UTC

[ambari] branch branch-feature-AMBARI-14714 updated: [AMBARI-24464] Integrate Blueprints with the new MPackAdvisor API for Configuration Recommendations (#2259)

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

benyoka pushed a commit to branch branch-feature-AMBARI-14714
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-feature-AMBARI-14714 by this push:
     new 1edb62b  [AMBARI-24464] Integrate Blueprints with the new MPackAdvisor API for Configuration Recommendations (#2259)
1edb62b is described below

commit 1edb62ba06373f13895d7ba7db0a0d953daa0979
Author: benyoka <be...@users.noreply.github.com>
AuthorDate: Fri Sep 14 13:01:35 2018 +0200

    [AMBARI-24464] Integrate Blueprints with the new MPackAdvisor API for Configuration Recommendations (#2259)
    
    * AMBARI-24464 blueprint mpack advisor WIP (benyoka)
    
    * AMBARI-24464 blueprint mpack advisor WIP #2 (benyoka)
    
    * AMBARI-24464 copy config to service instances
    
    * AMBARI-24464 copy config to service instances reverted (benyoka)
    
    * AMBARI-24464 fix review comments + unit test (benyoka)
    
    * AMBARI-24464 fix some more review comments (benyoka)
    
    * AMBARI-24464 fix some more review comments #2 (benyoka)
    
    * AMBARI-24464 fix some more review comments #3 (benyoka)
    
    * AMBARI-24464 fix failing unit tests (benyoka)
    
    * AMBARI-24464 fix failing unit tests #2 (benyoka)
---
 .../api/services/AdvisorBlueprintProcessor.java    |  50 +++
 .../MpackAdvisorBlueprintProcessor.java            | 298 +++++++++++++
 .../services/mpackadvisor/MpackAdvisorRequest.java |  23 +-
 .../services/mpackadvisor/MpackAdvisorRunner.java  |   2 +-
 .../mpackadvisor/commands/MpackAdvisorCommand.java |  77 ++--
 .../MpackRecommendationResponse.java               |  13 +
 .../StackAdvisorBlueprintProcessor.java            |  18 +-
 .../ambari/server/configuration/Configuration.java |  15 +-
 .../ambari/server/controller/AmbariServer.java     |   2 +
 .../ambari/server/controller/ControllerModule.java |   6 +
 .../internal/ConfigurationResourceProvider.java    |   3 +
 .../internal/MpackAdvisorResourceProvider.java     |  36 +-
 .../internal/ProvisionClusterRequest.java          |   2 +
 .../topology/ClusterConfigurationRequest.java      |  14 +-
 .../ambari/server/topology/ClusterTopology.java    |   4 +
 .../server/topology/ClusterTopologyImpl.java       |  92 ++--
 .../ambari/server/topology/MpackInstance.java      |  22 +
 .../ambari/server/topology/ServiceInstance.java    |   7 +
 .../ambari/server/topology/TopologyManager.java    |   8 +-
 .../apache/ambari/server/utils/ExceptionUtils.java |  84 ++++
 .../MpackAdvisorBlueprintProcessorTest.java        | 485 +++++++++++++++++++++
 .../commands/MpackAdvisorCommandTest.java          |   5 +-
 .../internal/ProvisionClusterRequestTest.java      |  12 +
 23 files changed, 1138 insertions(+), 140 deletions(-)

diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AdvisorBlueprintProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AdvisorBlueprintProcessor.java
new file mode 100644
index 0000000..52f9ecf
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AdvisorBlueprintProcessor.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.api.services;
+
+import java.util.Map;
+
+import org.apache.ambari.server.api.services.mpackadvisor.MpackAdvisorBlueprintProcessor;
+import org.apache.ambari.server.controller.internal.ConfigurationTopologyException;
+import org.apache.ambari.server.topology.ClusterTopology;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * Common interface for topology/configuration recommendation engines. Currently there is a legacy implementation for
+ * stack advisor and a new implementation for mpack advisor.
+ * <p>See:
+ * {@link org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor}
+ * {@link org.apache.ambari.server.api.services.mpackadvisor.MpackAdvisorBlueprintProcessor}
+ * </p>
+ */
+@ImplementedBy(MpackAdvisorBlueprintProcessor.class)
+public interface AdvisorBlueprintProcessor {
+
+  String RECOMMENDATION_FAILED = "Configuration recommendation failed.";
+  String INVALID_RESPONSE = "Configuration recommendation returned with invalid response.";
+
+  /**
+   * Recommend configurations by the advisor, then store the results in cluster topology.
+   * @param clusterTopology cluster topology instance
+   * @param userProvidedConfigurations User configurations of cluster provided in Blueprint + Cluster template
+   */
+  void adviseConfiguration(ClusterTopology clusterTopology, Map<String, Map<String, String>> userProvidedConfigurations) throws ConfigurationTopologyException;
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessor.java
new file mode 100644
index 0000000..6c125c2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessor.java
@@ -0,0 +1,298 @@
+/*
+ * 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.api.services.mpackadvisor;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.ambari.server.utils.ExceptionUtils.unchecked;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.api.services.AdvisorBlueprintProcessor;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.api.services.mpackadvisor.recommendations.MpackRecommendationResponse;
+import org.apache.ambari.server.controller.internal.ConfigurationTopologyException;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.ValueAttributesInfo;
+import org.apache.ambari.server.topology.AdvisedConfiguration;
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.Component;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.ambari.server.topology.HostGroup;
+import org.apache.ambari.server.topology.HostGroupInfo;
+import org.apache.ambari.server.topology.MpackInstance;
+import org.apache.ambari.server.topology.ResolvedComponent;
+import org.apache.ambari.server.topology.ServiceInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.inject.Singleton;
+
+/**
+ * Generate advised configurations for blueprint cluster provisioning by the mpack advisor.
+ */
+@Singleton
+public class MpackAdvisorBlueprintProcessor implements AdvisorBlueprintProcessor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MpackAdvisorBlueprintProcessor.class);
+
+  private static MpackAdvisorHelper mpackAdvisorHelper;
+
+  private static AmbariMetaInfo metaInfo;
+
+  public static void init(MpackAdvisorHelper instance, AmbariMetaInfo ambariMetaInfo) {
+    mpackAdvisorHelper = instance;
+    metaInfo = ambariMetaInfo;
+  }
+
+  private static final Map<String, String> userContext = ImmutableMap.of("operation", "ClusterCreate");
+
+  /**
+   * {@inheritDoc}
+   */
+  public void adviseConfiguration(ClusterTopology clusterTopology, Map<String, Map<String, String>> userProvidedConfigurations) throws ConfigurationTopologyException {
+    MpackAdvisorRequest request = createMpackAdvisorRequest(clusterTopology, MpackAdvisorRequest.MpackAdvisorRequestType.CONFIGURATIONS);
+    try {
+      MpackRecommendationResponse response = mpackAdvisorHelper.recommend(request);
+      addAllAdvisedConfigurationsToTopology(response, clusterTopology, userProvidedConfigurations);
+    } catch (MpackAdvisorException e) {
+      throw new ConfigurationTopologyException(RECOMMENDATION_FAILED, e);
+    } catch (IllegalArgumentException e) {
+      throw new ConfigurationTopologyException(INVALID_RESPONSE, e);
+    }
+  }
+
+  private MpackAdvisorRequest createMpackAdvisorRequest(ClusterTopology clusterTopology,
+                                                        MpackAdvisorRequest.MpackAdvisorRequestType requestType) {
+    Map<String, Map<String, Set<String>>> mpackComponentsHostsMap = gatherMackComponentsHostsMap(clusterTopology);
+    Set<MpackInstance> mpacks = copyAndEnrichMpackInstances(clusterTopology);
+    Configuration configuration = clusterTopology.getConfiguration();
+    return MpackAdvisorRequest.MpackAdvisorRequestBuilder
+      .forStack()
+      .forMpackInstances(mpacks)
+      .forHosts(gatherHosts(clusterTopology))
+      .forHostsGroupBindings(gatherHostGroupBindings(clusterTopology))
+      .forComponentHostsMap(getHostgroups(clusterTopology))
+      .withMpacksToComponentsHostsMap(mpackComponentsHostsMap)
+      .withConfigurations(calculateConfigs(configuration))
+      .withUserContext(userContext)
+      .ofType(requestType)
+      .build();
+  }
+
+  private Set<MpackInstance> copyAndEnrichMpackInstances(ClusterTopology topology) {
+    // Copy mpacks
+    Set<MpackInstance> mpacks = topology.getMpacks().stream().map(MpackInstance::copy).collect(toSet());
+
+    // Add missing service instances
+    Map<StackId, Set<String>> mpackServices = topology.getComponents().collect(
+      groupingBy(ResolvedComponent::stackId,
+        mapping(comp -> comp.serviceInfo().getName(), toSet())));
+    for (MpackInstance mpack: mpacks) {
+      if (!mpackServices.containsKey(mpack.getStackId())) {
+        LOG.warn("No services declared for mpack {}.", mpack.getStackId());
+      }
+      else {
+        Set<String> existingMpackServices = mpack.getServiceInstances().stream().map(ServiceInstance::getType).collect(toSet());
+        for(String service: mpackServices.get(mpack.getStackId())) {
+          if (existingMpackServices.contains(service)) {
+            LOG.debug("Mpack instance {} already contains service {}", mpack.getStackId(), service);
+          }
+          else {
+            LOG.debug("Adding service {} to mpack instance {}", service, mpack.getStackId());
+            mpack.getServiceInstances().add(new ServiceInstance(service, service, null, mpack));
+          }
+        }
+      }
+    }
+    return mpacks;
+  }
+
+  private Collection<MpackRecommendationResponse.HostGroup> getHostgroups(ClusterTopology topology) {
+    // TODO: this will need to rewritten for true multi-everything (multiple mpacks of the same type/version under
+    //  different names)
+    Map<StackId, String> mpackNameByStackId = topology.getMpacks().stream().collect(
+      toMap(
+        MpackInstance::getStackId,
+        MpackInstance::getMpackName
+      ));
+
+    topology.getComponentsByHostGroup().entrySet().stream().collect(
+      toMap(
+  //      Map.Entry::getKey,
+        e -> e.getKey(),
+        components ->
+          components.getValue().stream()
+            .map(comp ->
+              MpackRecommendationResponse.HostGroup.createComponent(comp.componentName(),
+                mpackNameByStackId.get(comp.stackId()),
+                comp.serviceName().orElseGet(() -> comp.serviceType())))
+            .collect(toSet())
+      )
+    );
+
+    return null;
+  }
+
+  private Map<String, Set<String>> gatherHostGroupBindings(ClusterTopology clusterTopology) {
+    Map<String, Set<String>> hgBindings = Maps.newHashMap();
+    for (Map.Entry<String, HostGroupInfo> hgEnrty: clusterTopology.getHostGroupInfo().entrySet()) {
+      hgBindings.put(hgEnrty.getKey(), Sets.newCopyOnWriteArraySet(hgEnrty.getValue().getHostNames()));
+    }
+    return hgBindings;
+  }
+
+  private Map<String, Set<Component>> gatherHostGroupComponents(ClusterTopology clusterTopology) {
+    Map<String, Set<Component>> hgComponentsMap = Maps.newHashMap();
+    for (Map.Entry<String, HostGroup> hgEnrty: clusterTopology.getBlueprint().getHostGroups().entrySet()) {
+      hgComponentsMap.put(hgEnrty.getKey(), Sets.newCopyOnWriteArraySet(hgEnrty.getValue().getComponents()));
+    }
+    return hgComponentsMap;
+  }
+
+  private Map<String, Map<String, Map<String, String>>> calculateConfigs(Configuration configuration) {
+    Map<String, Map<String, Map<String, String>>> result = Maps.newHashMap();
+    Map<String, Map<String, String>> fullProperties = configuration.getFullProperties();
+    for (Map.Entry<String, Map<String, String>> siteEntry : fullProperties.entrySet()) {
+      Map<String, Map<String, String>> propsMap = Maps.newHashMap();
+      propsMap.put("properties", siteEntry.getValue());
+      result.put(siteEntry.getKey(), propsMap);
+    }
+    return result;
+  }
+
+  private Map<String, Map<String, Set<String>>> gatherMackComponentsHostsMap(ClusterTopology topology) {
+    Map<String, Map<String, Set<String>>> mpackComponentsHostsMap = new HashMap<>();
+    for (Map.Entry<String, Set<ResolvedComponent>> hgToComps : topology.getComponentsByHostGroup().entrySet()) {
+      String hostGroup = hgToComps.getKey();
+      Set<ResolvedComponent> components = hgToComps.getValue();
+      Set<String> hosts = topology.getHostGroupInfo().get(hostGroup).getHostNames();
+      for (ResolvedComponent component: components) {
+        String mpackName = component.stackId().getStackName(); // TODO: support multiple mpacks under different names?
+        mpackComponentsHostsMap
+          .computeIfAbsent(mpackName, __ -> new HashMap<>())
+          .put(component.componentName(), hosts);
+      }
+    }
+    return mpackComponentsHostsMap;
+  }
+
+  private Set<String> getMpacksForComponent(Component component, Map<String, Set<String>> componentToMpacks) {
+    Set<String> mpacksForComponent = component.getMpackInstance() != null
+      ? ImmutableSet.of(component.getMpackInstance())
+      : componentToMpacks.getOrDefault(component.getName(), ImmutableSet.of());
+    if (mpacksForComponent.isEmpty()) {
+      LOG.error("No mpack found for component [{}]", component.getName());
+    }
+    return mpacksForComponent;
+  }
+
+  private List<String> gatherHosts(ClusterTopology clusterTopology) {
+    List<String> hosts = Lists.newArrayList();
+    for (Map.Entry<String, HostGroupInfo> entry : clusterTopology.getHostGroupInfo().entrySet()) {
+      hosts.addAll(entry.getValue().getHostNames());
+    }
+    return hosts;
+  }
+
+  private void addAllAdvisedConfigurationsToTopology(MpackRecommendationResponse response,
+                                                     ClusterTopology topology, Map<String, Map<String, String>> userProvidedConfigurations) {
+    Preconditions.checkArgument(response.getRecommendations() != null,
+      "Recommendation response is empty.");
+    Preconditions.checkArgument(response.getRecommendations().getBlueprint() != null,
+      "Blueprint field is missing from the recommendation response.");
+
+    MpackRecommendationResponse.Blueprint blueprint = response.getRecommendations().getBlueprint();
+
+    addAdvisedConfigurationToTopology(blueprint.getConfigurations(), topology, userProvidedConfigurations);
+
+    blueprint.getMpackInstances().forEach( mpack -> {
+      mpack.getServiceInstances().forEach( svc -> {
+        addAdvisedConfigurationToTopology(svc.getConfigurations(), topology, userProvidedConfigurations);
+      });
+    });
+  }
+
+  private void addAdvisedConfigurationToTopology(Map<String, MpackRecommendationResponse.BlueprintConfigurations> recommendedConfigurations,
+                                                  ClusterTopology topology, Map<String, Map<String, String>> userProvidedConfigurations) {
+    if (null != recommendedConfigurations) {
+      for (Map.Entry<String, MpackRecommendationResponse.BlueprintConfigurations> configEntry : recommendedConfigurations.entrySet()) {
+        String configType = configEntry.getKey();
+        // add recommended config type only if related service is present in Blueprint
+        if (topology.isValidConfigType(configType)) {
+          MpackRecommendationResponse.BlueprintConfigurations blueprintConfig = filterBlueprintConfig(configType, configEntry.getValue(),
+            userProvidedConfigurations, topology);
+          topology.getAdvisedConfigurations().put(configType, new AdvisedConfiguration(
+            blueprintConfig.getProperties(), blueprintConfig.getPropertyAttributes()));
+        }
+      }
+    }
+  }
+
+
+  /**
+   * Remove user defined properties from Stack Advisor output in case of ONLY_STACK_DEFAULTS_APPLY or
+   * ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES.
+   */
+  private MpackRecommendationResponse.BlueprintConfigurations filterBlueprintConfig(String configType, MpackRecommendationResponse.BlueprintConfigurations config,
+                                                                               Map<String, Map<String, String>> userProvidedConfigurations,
+                                                                               ClusterTopology topology) {
+    if (topology.getConfigRecommendationStrategy() == ConfigRecommendationStrategy.ONLY_STACK_DEFAULTS_APPLY ||
+      topology.getConfigRecommendationStrategy() == ConfigRecommendationStrategy
+        .ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES) {
+      if (userProvidedConfigurations.containsKey(configType)) {
+        MpackRecommendationResponse.BlueprintConfigurations newConfig = new MpackRecommendationResponse.BlueprintConfigurations();
+        Map<String, String> filteredProps = Maps.filterKeys(config.getProperties(),
+          Predicates.not(Predicates.in(userProvidedConfigurations.get(configType).keySet())));
+        newConfig.setProperties(Maps.newHashMap(filteredProps));
+
+        if (config.getPropertyAttributes() != null) {
+          Map<String, ValueAttributesInfo> filteredAttributes = Maps.filterKeys(config.getPropertyAttributes(),
+            Predicates.not(Predicates.in(userProvidedConfigurations.get(configType).keySet())));
+          newConfig.setPropertyAttributes(Maps.newHashMap(filteredAttributes));
+        }
+        return newConfig;
+      }
+    }
+    return config;
+  }
+
+  Set<String> getStackComponents(StackId stackId) {
+    return unchecked(() -> metaInfo.getStack(stackId)).getServices().stream()
+      .flatMap( svc -> svc.getComponents().stream() )
+      .map(ComponentInfo::getName)
+      .collect(toSet());
+  }
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRequest.java
index f41ce68..f71b8f5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRequest.java
@@ -18,6 +18,8 @@
 
 package org.apache.ambari.server.api.services.mpackadvisor;
 
+import static java.util.stream.Collectors.toSet;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -33,6 +35,7 @@ import org.apache.ambari.server.topology.MpackInstance;
 import org.apache.ambari.server.topology.ServiceInstance;
 
 import org.apache.commons.lang.StringUtils;
+import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
 
 /**
@@ -42,8 +45,8 @@ public class MpackAdvisorRequest {
 
   private MpackAdvisorRequestType requestType;
   private List<String> hosts = new ArrayList<>();
-  //      Mpack Instance Name -> Component Name -> host(s)
-  private Map<String, Map<String, Set<String>>> componentHostsMap = new HashMap<>();
+  // Mpack Instance Name -> Component Name -> host(s)
+  private Map<String, Map<String, Set<String>>> mpackComponentHostsMap = new HashMap<>();
   private Map<String, Map<String, Map<String, String>>> configurations = new HashMap<>();
   private List<ChangedConfigInfo> changedConfigurations = new LinkedList<>();
   private Map<String, String> userContext = new HashMap<>();
@@ -96,6 +99,16 @@ public class MpackAdvisorRequest {
     public Collection<MpackInstance> getMpackInstances() {
       return this.mpacks;
     }
+
+    @JsonIgnore
+    public Collection<String> getServiceInstanceNames() {
+      return mpacks.stream()
+        .flatMap(mpack -> mpack.getServiceInstances().stream())
+        .map(ServiceInstance::getName)
+        .collect(toSet());
+    }
+
+
   }
 
   public Recommendation getRecommendation() {
@@ -180,7 +193,7 @@ public class MpackAdvisorRequest {
 
     public MpackAdvisorRequest.MpackAdvisorRequestBuilder withMpacksToComponentsHostsMap(
         Map<String, Map<String, Set<String>>> componentHostsMap) {
-      this.instance.componentHostsMap = componentHostsMap;
+      this.instance.mpackComponentHostsMap = componentHostsMap;
       return this;
     }
 
@@ -256,8 +269,8 @@ public class MpackAdvisorRequest {
     return StringUtils.join(serviceInstancesTypeList, ",");
   }
 
-  public Map<String, Map<String, Set<String>>> getComponentHostsMap() {
-    return componentHostsMap;
+  public Map<String, Map<String, Set<String>>> getMpackComponentHostsMap() {
+    return mpackComponentHostsMap;
   }
 
   public String getHostsCommaSeparated() {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRunner.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRunner.java
index ec7e94f..cdcaf06 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRunner.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorRunner.java
@@ -100,7 +100,7 @@ public class MpackAdvisorRunner {
     } catch (Exception ioe) {
       String message = "Error executing Mpack Advisor: ";
       LOG.error(message, ioe);
-      throw new MpackAdvisorException(message + ioe.getMessage());
+      throw new MpackAdvisorException(message + ioe.getMessage(), ioe);
     } finally {
       if (process != null) {
         process.destroy();
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommand.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommand.java
index f1ee909..9ae70f6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommand.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommand.java
@@ -27,7 +27,6 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -65,6 +64,8 @@ import org.codehaus.jackson.node.ObjectNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Parent for all commands.
  */
@@ -164,11 +165,13 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
 
   protected abstract void validate(MpackAdvisorRequest request) throws MpackAdvisorException;
 
-  protected ObjectNode adjust(String servicesJSON, MpackAdvisorRequest request) {
+  protected ObjectNode adjust(String servicesJSON, MpackAdvisorRequest request, MpackInstance mpack) {
     try {
       ObjectNode root = (ObjectNode) this.mapper.readTree(servicesJSON);
+      Preconditions.checkNotNull(root.get(SERVICES_PROPERTY),
+        "No services found for mpack %s (%s-%s). Is the mpack installed?", mpack.getMpackName(), mpack.getMpackType(), mpack.getMpackVersion());
 
-      populateComponentHostsMap(root, request.getComponentHostsMap());
+      populateComponentHostsMap(root, request.getMpackComponentHostsMap());
       populateServiceAdvisors(root);
       populateServicesConfigurations(root, request);
       populateClusterLevelConfigurations(root, request);
@@ -180,7 +183,7 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
       // should not happen
       String message = "Error parsing services.json file content: " + e.getMessage();
       LOG.warn(message, e);
-      throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(message).build());
+      throw new WebApplicationException(e, Response.status(Status.BAD_REQUEST).entity(message).build());
     }
   }
 
@@ -222,30 +225,26 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
   }
 
   private void populateServicesConfigurations(ObjectNode root, MpackAdvisorRequest request) {
-    Collection<MpackInstance> mpackInstances = request.getMpackInstances();
-    Iterator<MpackInstance> mpackInstanceItr = mpackInstances.iterator();
     ObjectNode configurationsNode = root.putObject(CONFIGURATIONS_PROPERTY);
-    while (mpackInstanceItr.hasNext()) {
-      MpackInstance mpackInstance = mpackInstanceItr.next();
-      Collection<ServiceInstance> serviceInstances = mpackInstance.getServiceInstances();
-      Iterator<ServiceInstance> serviceInstanceItr = serviceInstances.iterator();
-      while (serviceInstanceItr.hasNext()) {
-        ServiceInstance serviceInstance = serviceInstanceItr.next();
+    for (MpackInstance mpackInstance: request.getMpackInstances()) {
+      for (ServiceInstance serviceInstance: mpackInstance.getServiceInstances()) {
         Configuration configurations = serviceInstance.getConfiguration();
-        // We have read configuration properties in attributes as it allows the following format:
-        // eg: {configType -> {attributeName -> {propName, attributeValue}}}
-        Map<String, Map<String, Map<String, String>>> configProperties = configurations.getAttributes();
-        for (String siteName : configProperties.keySet()) {
-          ObjectNode siteNode = configurationsNode.putObject(siteName);
-
-          Map<String, Map<String, String>> siteMap = configProperties.get(siteName);
-          for (String properties : siteMap.keySet()) {
-            ObjectNode propertiesNode = siteNode.putObject(properties);
-
-            Map<String, String> propertiesMap = siteMap.get(properties);
-            for (String propertyName : propertiesMap.keySet()) {
-              String propertyValue = propertiesMap.get(propertyName);
-              propertiesNode.put(propertyName, propertyValue);
+        if (null != configurations) {
+          // We have read configuration properties in attributes as it allows the following format:
+          // eg: {configType -> {attributeName -> {propName, attributeValue}}}
+          Map<String, Map<String, Map<String, String>>> configProperties = configurations.getAttributes();
+          for (String siteName : configProperties.keySet()) {
+            ObjectNode siteNode = configurationsNode.putObject(siteName);
+
+            Map<String, Map<String, String>> siteMap = configProperties.get(siteName);
+            for (String properties : siteMap.keySet()) {
+              ObjectNode propertiesNode = siteNode.putObject(properties);
+
+              Map<String, String> propertiesMap = siteMap.get(properties);
+              for (String propertyName : propertiesMap.keySet()) {
+                String propertyValue = propertiesMap.get(propertyName);
+                propertiesNode.put(propertyName, propertyValue);
+              }
             }
           }
         }
@@ -270,15 +269,9 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
 
   private void populateComponentHostsMap(ObjectNode root, Map<String, Map<String, Set<String>>> mpacksToComponentsHostsMap) {
     ArrayNode services = (ArrayNode) root.get(SERVICES_PROPERTY);
-    Iterator<JsonNode> servicesIter = services.getElements();
-
-    while (servicesIter.hasNext()) {
-      JsonNode service = servicesIter.next();
+    for (JsonNode service: services) {
       ArrayNode components = (ArrayNode) service.get(SERVICES_COMPONENTS_PROPERTY);
-      Iterator<JsonNode> componentsIter = components.getElements();
-
-      while (componentsIter.hasNext()) {
-        JsonNode component = componentsIter.next();
+      for (JsonNode component: components) {
         ObjectNode componentInfo = (ObjectNode) component.get(COMPONENT_INFO_PROPERTY);
         String componentMpackName = componentInfo.get(COMPONENT_MPACK_NAME_PROPERTY).getTextValue();
         String componentName = componentInfo.get(COMPONENT_NAME_PROPERTY).getTextValue();
@@ -301,14 +294,11 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
 
   protected void populateServiceAdvisors(ObjectNode root) {
     ArrayNode services = (ArrayNode) root.get(SERVICES_PROPERTY);
-    Iterator<JsonNode> servicesIter = services.getElements();
-
     ObjectNode version = (ObjectNode) root.get("Versions");
     String stackName = version.get("stack_name").asText();
     String stackVersion = version.get("stack_version").asText();
 
-    while (servicesIter.hasNext()) {
-      JsonNode service = servicesIter.next();
+    for (JsonNode service: services) {
       ObjectNode serviceVersion = (ObjectNode) service.get(STACK_SERVICES_PROPERTY);
       String serviceName = serviceVersion.get("service_name").getTextValue();
       try {
@@ -439,9 +429,7 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
 
     try {
       JsonNode root = mapper.readTree(hostsJSON);
-      Iterator<JsonNode> iterator = root.get("items").getElements();
-      while (iterator.hasNext()) {
-        JsonNode next = iterator.next();
+      for (JsonNode next: root.get("items")) {
         String hostName = next.get("Hosts").get("host_name").getTextValue();
         registeredHosts.add(hostName);
       }
@@ -491,15 +479,14 @@ public abstract class MpackAdvisorCommand<T extends MpackAdvisorResponse> extend
         LOG.warn(message);
         throw new MpackAdvisorException(message);
       }
-
-      updatedServicesInfoForMpack = adjust(servicesInfoFromMpack, request);
+      updatedServicesInfoForMpack = adjust(servicesInfoFromMpack, request, mpackInstance);
 
       if (LOG.isDebugEnabled()) {
         LOG.debug("Services information: {}", servicesInfoFromMpack);
       }
 
-      for (Iterator<JsonNode> svcInstanceItr = updatedServicesInfoForMpack.get("services").getElements(); svcInstanceItr.hasNext(); ) {
-        updatedServicesAcrossMpacks.add(svcInstanceItr.next());
+      for (JsonNode svcInstance: updatedServicesInfoForMpack.get("services")) {
+        updatedServicesAcrossMpacks.add(svcInstance);
       }
     }
 
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/recommendations/MpackRecommendationResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/recommendations/MpackRecommendationResponse.java
index 9ccf4c5..8ccbe1e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/recommendations/MpackRecommendationResponse.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/mpackadvisor/recommendations/MpackRecommendationResponse.java
@@ -18,6 +18,10 @@
 
 package org.apache.ambari.server.api.services.mpackadvisor.recommendations;
 
+import static org.apache.ambari.server.controller.internal.MpackAdvisorResourceProvider.BLUEPRINT_HOST_GROUPS_COMPONENTS_MPACK_INSTANCE_PROPERTY;
+import static org.apache.ambari.server.controller.internal.MpackAdvisorResourceProvider.BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY;
+import static org.apache.ambari.server.controller.internal.MpackAdvisorResourceProvider.BLUEPRINT_HOST_GROUPS_COMPONENTS_SERVICE_INSTANCE_PROPERTY;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -30,6 +34,8 @@ import org.apache.ambari.server.state.ValueAttributesInfo;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
+import com.google.common.collect.ImmutableMap;
+
 /**
  * Recommendation response POJO.
  */
@@ -300,6 +306,13 @@ public class MpackRecommendationResponse extends MpackAdvisorResponse {
     public void setComponents(Set<Map<String, String>> components) {
       this.components = components;
     }
+
+    public static Map<String, String> createComponent(String componentName, String mpackInstance, String serviceInstance) {
+      return ImmutableMap.of(
+        BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY, componentName,
+        BLUEPRINT_HOST_GROUPS_COMPONENTS_MPACK_INSTANCE_PROPERTY, mpackInstance,
+        BLUEPRINT_HOST_GROUPS_COMPONENTS_SERVICE_INSTANCE_PROPERTY, serviceInstance);
+    }
   }
 
   public static class BlueprintClusterBinding {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorBlueprintProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorBlueprintProcessor.java
index 87b64a5..564b333 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorBlueprintProcessor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorBlueprintProcessor.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.ambari.server.api.services.AdvisorBlueprintProcessor;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.StackAdvisorRequestType;
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.BlueprintConfigurations;
@@ -40,6 +41,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -49,30 +51,20 @@ import com.google.inject.Singleton;
  * Generate advised configurations for blueprint cluster provisioning by the stack advisor.
  */
 @Singleton
-public class StackAdvisorBlueprintProcessor {
+public class StackAdvisorBlueprintProcessor implements AdvisorBlueprintProcessor {
 
   private static final Logger LOG = LoggerFactory.getLogger(StackAdvisorBlueprintProcessor.class);
 
   private static StackAdvisorHelper stackAdvisorHelper;
 
-  static final String RECOMMENDATION_FAILED = "Configuration recommendation failed.";
-  static final String INVALID_RESPONSE = "Configuration recommendation returned with invalid response.";
-
   public static void init(StackAdvisorHelper instance) {
     stackAdvisorHelper = instance;
   }
 
-  private static final Map<String, String> userContext;
-  static
-  {
-    userContext = new HashMap<>();
-    userContext.put("operation", "ClusterCreate");
-  }
+  private static final Map<String, String> userContext = ImmutableMap.of("operation", "ClusterCreate");
 
   /**
-   * Recommend configurations by the stack advisor, then store the results in cluster topology.
-   * @param clusterTopology cluster topology instance
-   * @param userProvidedConfigurations User configurations of cluster provided in Blueprint + Cluster template
+   * {@inheritDoc}
    */
   public void adviseConfiguration(ClusterTopology clusterTopology, Map<String, Map<String, String>> userProvidedConfigurations) throws ConfigurationTopologyException {
     for (StackId stackId : clusterTopology.getStackIds()) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index 7d13e08..ac5fbb0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -445,12 +445,21 @@ public class Configuration {
    * The location and name of the Python mpack advisor script executed when
    * configuring services.
    */
-  @Markdown(description = "The location and name of the Python stack advisor script executed when configuring services.")
+  @Markdown(description = "The location and name of the Python mpack advisor script executed when configuring services.")
   public static final ConfigurationProperty<String> MPACK_ADVISOR_SCRIPT = new ConfigurationProperty<>(
       "mpackadvisor.script",
       AmbariPath.getPath("/var/lib/ambari-server/resources/scripts/mpack_advisor_wrapper.py"));
 
   /**
+   * If set to true, the legacy stack advisor will be used instead of the newer mpack advisor (default value is false).
+   */
+  @Markdown(description = "If set to true, the legacy stack advisor will be used instead of the newer mpack advisor (default value is false).")
+  public static final ConfigurationProperty<Boolean> USE_LEGACY_STACK_ADVISOR = new ConfigurationProperty<>(
+    "use.legacy.stackadvisor",
+    false);
+
+
+  /**
    * The name of the shell script used to wrap all invocations of Python by Ambari.
    */
   @Markdown(description = "The name of the shell script used to wrap all invocations of Python by Ambari. ")
@@ -3377,6 +3386,10 @@ public class Configuration {
     return getProperty(MPACK_ADVISOR_SCRIPT);
   }
 
+  public boolean shouldUseLegacyStackAdvisor() {
+    return Boolean.valueOf(getProperty(USE_LEGACY_STACK_ADVISOR));
+  }
+
   /**
    * @return a list of prefixes. Packages whose name starts with any of these
    * prefixes, should be skipped during upgrade.
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
index 5946977..18afc3f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
@@ -53,6 +53,7 @@ import org.apache.ambari.server.api.services.BaseService;
 import org.apache.ambari.server.api.services.KeyService;
 import org.apache.ambari.server.api.services.PersistKeyValueImpl;
 import org.apache.ambari.server.api.services.PersistKeyValueService;
+import org.apache.ambari.server.api.services.mpackadvisor.MpackAdvisorBlueprintProcessor;
 import org.apache.ambari.server.api.services.mpackadvisor.MpackAdvisorHelper;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor;
 import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper;
@@ -950,6 +951,7 @@ public class AmbariServer {
     AmbariPrivilegeResourceProvider.init(injector.getInstance(ClusterDAO.class));
     ActionManager.setTopologyManager(injector.getInstance(TopologyManager.class));
     StackAdvisorBlueprintProcessor.init(injector.getInstance(StackAdvisorHelper.class));
+    MpackAdvisorBlueprintProcessor.init(injector.getInstance(MpackAdvisorHelper.class), injector.getInstance(AmbariMetaInfo.class));
     ThreadPoolEnabledPropertyProvider.init(injector.getInstance(Configuration.class));
 
     BaseService.init(injector.getInstance(RequestAuditLogger.class));
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index 9fa570f..d99ae863 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -57,6 +57,8 @@ import org.apache.ambari.server.actionmanager.HostRoleCommandFactoryImpl;
 import org.apache.ambari.server.actionmanager.RequestFactory;
 import org.apache.ambari.server.actionmanager.StageFactory;
 import org.apache.ambari.server.actionmanager.StageFactoryImpl;
+import org.apache.ambari.server.api.services.AdvisorBlueprintProcessor;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor;
 import org.apache.ambari.server.checks.DatabaseConsistencyCheckHelper;
 import org.apache.ambari.server.checks.PreUpgradeCheck;
 import org.apache.ambari.server.checks.UpgradeCheckRegistry;
@@ -431,6 +433,10 @@ public class ControllerModule extends AbstractModule {
     InternalAuthenticationInterceptor ambariAuthenticationInterceptor = new InternalAuthenticationInterceptor();
     requestInjection(ambariAuthenticationInterceptor);
     bindInterceptor(any(), annotatedWith(RunWithInternalSecurityContext.class), ambariAuthenticationInterceptor);
+
+    if (configuration.shouldUseLegacyStackAdvisor()) {
+      bind(AdvisorBlueprintProcessor.class).to(StackAdvisorBlueprintProcessor.class);
+    }
   }
 
   // ----- helper methods ----------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigurationResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigurationResourceProvider.java
index 47bc5bd..5b20a9b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigurationResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigurationResourceProvider.java
@@ -254,6 +254,9 @@ public class ConfigurationResourceProvider extends
 
     Set<Resource> resources = new HashSet<>();
     for (ConfigurationResponse response : responses) {
+      if (null == response) {
+        throw new NoSuchResourceException("Could not find configuration resource: " + predicate);
+      }
       // don't use the StackId object here; we just want the stack ID string
       String stackId = response.getStackId().getStackId();
 
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackAdvisorResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackAdvisorResourceProvider.java
index 9f5b740..e0b6491 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackAdvisorResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackAdvisorResourceProvider.java
@@ -22,7 +22,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -79,9 +78,9 @@ public abstract class MpackAdvisorResourceProvider extends ReadOnlyResourceProvi
 
   private static final String BLUEPRINT_HOST_GROUPS_NAME_PROPERTY = "name";
   private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_PROPERTY = "components";
-  private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY = "name";
-  private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_MPACK_INSTANCE_PROPERTY = "mpack_instance";
-  private static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_SERVICE_INSTANCE_PROPERTY = "service_instance";
+  public static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_NAME_PROPERTY = "name";
+  public static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_MPACK_INSTANCE_PROPERTY = "mpack_instance";
+  public static final String BLUEPRINT_HOST_GROUPS_COMPONENTS_SERVICE_INSTANCE_PROPERTY = "service_instance";
 
   private static final String CHANGED_CONFIGURATIONS_PROPERTY = "changed_configurations";
   private static final String OPERATION_PROPERTY = "operation";
@@ -408,33 +407,24 @@ public abstract class MpackAdvisorResourceProvider extends ReadOnlyResourceProvi
 
     Map<String, Map<String, Set<String>>> mpacksToComponentsHostsMap = new HashMap<>();
     if (null != bindingHostGroups && null != hostGroups) {
-      Iterator hgItr = hostGroups.iterator();
-      while (hgItr.hasNext()) {
-        HostGroup hostGrp = (HostGroup) hgItr.next();
+      for (HostGroup hostGrp: hostGroups) {
         String hgName = hostGrp.getName();
         Set<Map<String, String>> components = hostGrp.getComponents();
 
         Set<String> hosts = bindingHostGroups.get(hgName);
-        Iterator compItr = components.iterator();
-        while (compItr.hasNext()) {
-          Map<String, String> compValueMap = (Map<String, String>) compItr.next();
+        for (Map<String, String> compValueMap: components) {
           String compName = compValueMap.get("name");
           String compMpackname = compValueMap.get("mpack_instance");
-          Map<String, Set<String>> mpackToComponentsHostsMap = mpacksToComponentsHostsMap.get(compMpackname);
-          if (mpackToComponentsHostsMap == null) {
-            mpackToComponentsHostsMap = new HashMap<>();
-            mpacksToComponentsHostsMap.put(compMpackname, mpackToComponentsHostsMap);
-          }
+          Map<String, Set<String>> componentsHostsMap = mpacksToComponentsHostsMap.computeIfAbsent(
+            compMpackname,
+            __ -> new HashMap<>());
+
           // Check if 'compName' exists. If exists, fetch and update to its existing hosts.
           // else, add the 'compName' along with its hosts.
-          Set<String> updatedHosts = mpackToComponentsHostsMap.get(compName);
-          if (updatedHosts == null || updatedHosts.isEmpty()) {
-            mpackToComponentsHostsMap.put(compName, hosts);
-          } else {
-            // Fetch and update the existing host(s) Set.
-            updatedHosts.addAll(hosts);
-            mpackToComponentsHostsMap.put(compName, updatedHosts);
-          }
+          Set<String> updatedHosts = componentsHostsMap.computeIfAbsent(
+            compName,
+            __ -> new HashSet<>());
+          updatedHosts.addAll(hosts);
         }
       }
     }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
index 123cdf4..99b7f01 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
@@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Enums;
 import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -202,6 +203,7 @@ public class ProvisionClusterRequest extends BaseClusterRequest implements Provi
     setProvisionAction(parseProvisionAction(properties));
 
     mpackInstances = BlueprintFactory.createMpackInstances(properties);
+    Preconditions.checkArgument(!getAllMpacks().isEmpty(), "No mpacks (stacks) have been defined. Cluster provisioning cannot continue.");
     stackIds = mpackInstances.stream().map(MpackInstance::getStackId).collect(toSet()); // FIXME persist these
 
     try {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterConfigurationRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterConfigurationRequest.java
index 48b55aa..3ee636e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterConfigurationRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterConfigurationRequest.java
@@ -35,7 +35,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.ambari.server.AmbariException;
-import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor;
+import org.apache.ambari.server.api.services.AdvisorBlueprintProcessor;
 import org.apache.ambari.server.controller.ClusterRequest;
 import org.apache.ambari.server.controller.ConfigurationRequest;
 import org.apache.ambari.server.controller.ServiceResponse;
@@ -71,26 +71,26 @@ public class ClusterConfigurationRequest {
   private AmbariContext ambariContext;
   private ClusterTopology clusterTopology;
   private BlueprintConfigurationProcessor configurationProcessor;
-  private StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor;
+  private AdvisorBlueprintProcessor advisorBlueprintProcessor;
   private StackDefinition stack;
   private boolean configureSecurity = false;
 
   public ClusterConfigurationRequest(AmbariContext ambariContext, ClusterTopology topology,
-    StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor, boolean configureSecurity
+                                     AdvisorBlueprintProcessor advisorBlueprintProcessor, boolean configureSecurity
   ) {
-    this(ambariContext, topology, stackAdvisorBlueprintProcessor);
+    this(ambariContext, topology, advisorBlueprintProcessor);
     this.configureSecurity = configureSecurity;
   }
 
   public ClusterConfigurationRequest(AmbariContext ambariContext, ClusterTopology clusterTopology,
-    StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor
+    AdvisorBlueprintProcessor advisorBlueprintProcessor
   ) {
     this.ambariContext = ambariContext;
     this.clusterTopology = clusterTopology;
     this.stack = clusterTopology.getStack();
     // set initial configuration (not topology resolved)
     this.configurationProcessor = new BlueprintConfigurationProcessor(clusterTopology);
-    this.stackAdvisorBlueprintProcessor = stackAdvisorBlueprintProcessor;
+    this.advisorBlueprintProcessor = advisorBlueprintProcessor;
     removeOrphanConfigTypes();
   }
 
@@ -156,7 +156,7 @@ public class ClusterConfigurationRequest {
       // obtain recommended configurations before config updates
       if (clusterTopology.getConfigRecommendationStrategy().shouldUseAdvisor()) {
         // get merged properties form Blueprint & cluster template (this doesn't contains stack default values)
-        stackAdvisorBlueprintProcessor.adviseConfiguration(this.clusterTopology, userProvidedConfigurations);
+        advisorBlueprintProcessor.adviseConfiguration(this.clusterTopology, userProvidedConfigurations);
       }
 
       updatedConfigTypes.addAll(configurationProcessor.doUpdateForClusterCreate());
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
index 9d622d7..dc58899 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopology.java
@@ -148,6 +148,8 @@ public interface ClusterTopology {
    */
   Stream<ResolvedComponent> getComponents();
 
+  Map<String, Set<ResolvedComponent>> getComponentsByHostGroup();
+
   /**
    * Get the components that are included in the specified host group.
    *
@@ -254,4 +256,6 @@ public interface ClusterTopology {
   Set<String> getHostNames();
 
   SecurityConfiguration getSecurity();
+
+  Set<MpackInstance> getMpacks();
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
index 672ffd3..3481da5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ClusterTopologyImpl.java
@@ -63,6 +63,7 @@ public class ClusterTopologyImpl implements ClusterTopology {
   private final Set<StackId> stackIds;
   private final StackDefinition stack;
   private final SecurityConfiguration securityConfig;
+  private final Set<MpackInstance> mpacks;
   private Long clusterId;
   private final Blueprint blueprint;
   private final Configuration configuration;
@@ -76,6 +77,17 @@ public class ClusterTopologyImpl implements ClusterTopology {
   private final Map<String, Set<ResolvedComponent>> resolvedComponents;
   private final Setting setting;
 
+  /**
+   * This is to collect configurations formerly (in Ambari 2.x) belonging to cluster-env and already migrated to
+   * cluster settings. Eventually all configurations from cluster-env should be migrated and this collection
+   * should be removed.
+   */
+  private static final Set<String> SAFE_TO_REMOVE_FROM_CLUSTER_ENV = ImmutableSet.of(
+    ConfigHelper.COMMAND_RETRY_ENABLED,
+    ConfigHelper.COMMAND_RETRY_MAX_TIME_IN_SEC,
+    ConfigHelper.COMMANDS_TO_RETRY
+  );
+
   public ClusterTopologyImpl(
     AmbariContext ambariContext,
     ExportBlueprintRequest topologyRequest,
@@ -88,6 +100,7 @@ public class ClusterTopologyImpl implements ClusterTopology {
     this.configuration = topologyRequest.getConfiguration();
     configRecommendationStrategy = ConfigRecommendationStrategy.getDefault();
     securityConfig = blueprint.getSecurity();
+    this.mpacks = ImmutableSet.of(); // TODO: fix
 
     provisionAction = null;
     provisionRequest = null;
@@ -102,20 +115,37 @@ public class ClusterTopologyImpl implements ClusterTopology {
     adjustTopology();
   }
 
-  /**
-   * This is to collect configurations formerly (in Ambari 2.x) belonging to cluster-env and already migrated to
-   * cluster settings. Eventually all configurations from cluster-env should be migrated and this collection
-   * should be removed.
-   */
-  private static final Set<String> SAFE_TO_REMOVE_FROM_CLUSTER_ENV = ImmutableSet.of(
-    ConfigHelper.COMMAND_RETRY_ENABLED,
-    ConfigHelper.COMMAND_RETRY_MAX_TIME_IN_SEC,
-    ConfigHelper.COMMANDS_TO_RETRY
-  );
+  public ClusterTopologyImpl(
+    AmbariContext ambariContext,
+    BlueprintBasedClusterProvisionRequest request,
+    Map<String, Set<ResolvedComponent>> resolvedComponents
+  ) throws InvalidTopologyException {
+    this.ambariContext = ambariContext;
+    this.clusterId = request.getClusterId();
+    this.blueprint = request.getBlueprint();
+    this.configuration = request.getConfiguration();
+    this.provisionRequest = request;
+    this.resolvedComponents = resolvedComponents;
+    configRecommendationStrategy =
+      Optional.ofNullable(request.getConfigRecommendationStrategy()).orElse(ConfigRecommendationStrategy.getDefault());
+    provisionAction = request.getProvisionAction();
+    securityConfig = request.getSecurity();
+
+    defaultPassword = request.getDefaultPassword();
+    stackIds = request.getStackIds();
+    stack = request.getStack();
+    setting = request.getSetting();
+    blueprint.getConfiguration().setParentConfiguration(stack.getConfiguration(getServiceTypes()));
+
+    this.mpacks = request.getAllMpacks();
+
+    checkForDuplicateHosts(request.getHostGroupInfo());
+    registerHostGroupInfo(request.getHostGroupInfo());
+    adjustTopology();
+  }
 
   /**
-   * This method adjusts cluster topologies coming from the Ambari 2.x blueprint structure for Ambari
-   * 3.x.
+   * This method adjusts cluster topologies coming from the Ambari 2.x blueprint structure for Ambari 3.x.
    * Currently it extract configuration from cluster-env and transforms it into cluster settings.
    */
   private void adjustTopology() {
@@ -151,33 +181,6 @@ public class ClusterTopologyImpl implements ClusterTopology {
     );
   }
 
-  public ClusterTopologyImpl(
-    AmbariContext ambariContext,
-    BlueprintBasedClusterProvisionRequest request,
-    Map<String, Set<ResolvedComponent>> resolvedComponents
-  ) throws InvalidTopologyException {
-    this.ambariContext = ambariContext;
-    this.clusterId = request.getClusterId();
-    this.blueprint = request.getBlueprint();
-    this.configuration = request.getConfiguration();
-    this.provisionRequest = request;
-    this.resolvedComponents = resolvedComponents;
-    configRecommendationStrategy =
-      Optional.ofNullable(request.getConfigRecommendationStrategy()).orElse(ConfigRecommendationStrategy.getDefault());
-    provisionAction = request.getProvisionAction();
-    securityConfig = request.getSecurity();
-
-    defaultPassword = request.getDefaultPassword();
-    stackIds = request.getStackIds();
-    stack = request.getStack();
-    setting = request.getSetting();
-    blueprint.getConfiguration().setParentConfiguration(stack.getConfiguration(getServiceTypes()));
-
-    checkForDuplicateHosts(request.getHostGroupInfo());
-    registerHostGroupInfo(request.getHostGroupInfo());
-    adjustTopology();
-  }
-
   public ClusterTopologyImpl withAdditionalComponents(Map<String, Set<ResolvedComponent>> additionalComponents) throws InvalidTopologyException {
     if (additionalComponents.isEmpty()) {
       return this;
@@ -337,6 +340,11 @@ public class ClusterTopologyImpl implements ClusterTopology {
       .flatMap(Collection::stream);
   }
 
+  @Override
+  public Map<String, Set<ResolvedComponent>> getComponentsByHostGroup() {
+    return resolvedComponents;
+  }
+
   @Override @Nonnull
   public Stream<ResolvedComponent> getComponentsInHostGroup(String hostGroup) {
     return resolvedComponents.computeIfAbsent(hostGroup, __ -> ImmutableSet.of()).stream();
@@ -481,6 +489,12 @@ public class ClusterTopologyImpl implements ClusterTopology {
     }
   }
 
+  @Override
+  public Set<MpackInstance> getMpacks() {
+    return mpacks;
+  }
+
+
   private void registerHostGroupInfo(Map<String, HostGroupInfo> requestedHostGroupInfoMap) throws InvalidTopologyException {
     LOG.debug("Registering requested host group information for {} host groups", requestedHostGroupInfoMap.size());
 
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/MpackInstance.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/MpackInstance.java
index 3497ee5..4c348c2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/MpackInstance.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/MpackInstance.java
@@ -64,6 +64,21 @@ public class MpackInstance implements Configurable {
     this.configuration = configuration;
   }
 
+  public MpackInstance(String mpackName,
+                       String mpackType,
+                       String mpackVersion,
+                       String url,
+                       Configuration configuration,
+                       Collection<ServiceInstance> serviceInstances) {
+    this.mpackName = mpackName;
+    this.mpackType = mpackType;
+    this.mpackVersion = mpackVersion;
+    this.url = url;
+    this.configuration = configuration;
+    this.serviceInstances = serviceInstances;
+  }
+
+
   public MpackInstance(String mpackName, String mpackType, String mpackVersion, Collection<ServiceInstance> serviceInstances) {
     this.mpackName = mpackName;
     this.mpackType = mpackType;
@@ -71,6 +86,13 @@ public class MpackInstance implements Configurable {
     this.serviceInstances = serviceInstances;
   }
 
+  /**
+   * @return a shallow copy of this object
+   */
+  public MpackInstance copy() {
+    return new MpackInstance(mpackName, mpackType, mpackVersion, url, configuration, serviceInstances);
+  }
+
   public MpackInstance() { }
 
   public String getMpackName() {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ServiceInstance.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ServiceInstance.java
index 8fcee98..ce70e33 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ServiceInstance.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ServiceInstance.java
@@ -34,6 +34,13 @@ public class ServiceInstance implements Configurable {
     this.configuration = configuration;
   }
 
+  public ServiceInstance(String name, String type, Configuration configuration, MpackInstance mpackInstance) {
+    this.name = name;
+    this.type = type;
+    this.configuration = configuration;
+    this.mpackInstance = mpackInstance;
+  }
+
   public String getName() {
     return name;
   }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
index d47ed27..29fe62d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
@@ -40,7 +40,7 @@ import javax.inject.Inject;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
-import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorBlueprintProcessor;
+import org.apache.ambari.server.api.services.AdvisorBlueprintProcessor;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.AmbariServer;
 import org.apache.ambari.server.controller.RequestStatusResponse;
@@ -135,7 +135,7 @@ public class TopologyManager {
   private Configuration configuration;
 
   @Inject
-  private StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor;
+  private AdvisorBlueprintProcessor advisorBlueprintProcessor;
 
   @Inject
   private LogicalRequestFactory logicalRequestFactory;
@@ -326,7 +326,7 @@ public class TopologyManager {
     clusterTopologyMap.put(clusterId, topology);
 
     ClusterConfigurationRequest configurationRequest = new ClusterConfigurationRequest(ambariContext, topology,
-      stackAdvisorBlueprintProcessor, securityConfiguration.getType() == SecurityType.KERBEROS
+      advisorBlueprintProcessor, securityConfiguration.getType() == SecurityType.KERBEROS
     );
     configurationRequest.setInitialConfigurations();
     addClusterConfigRequest(logicalRequest, topology, configurationRequest);
@@ -1042,7 +1042,7 @@ public class TopologyManager {
             LOG.info("TopologyManager.replayRequests: no config with TOPOLOGY_RESOLVED found, but provision request is finished, skipping cluster config request");
           } else {
             LOG.info("TopologyManager.replayRequests: no config with TOPOLOGY_RESOLVED found, adding cluster config request");
-            ClusterConfigurationRequest configRequest = new ClusterConfigurationRequest(ambariContext, topology, stackAdvisorBlueprintProcessor);
+            ClusterConfigurationRequest configRequest = new ClusterConfigurationRequest(ambariContext, topology, advisorBlueprintProcessor);
             addClusterConfigRequest(provisionRequest, topology, configRequest);
           }
         } else {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/utils/ExceptionUtils.java b/ambari-server/src/main/java/org/apache/ambari/server/utils/ExceptionUtils.java
new file mode 100644
index 0000000..eed8e27
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/utils/ExceptionUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.utils;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.concurrent.Callable;
+
+/**
+ * Utilities for more convenient exception handling
+ */
+public class ExceptionUtils {
+
+  /**
+   * Utility to simplify the try-catch-rethrow-RuntimeExeption pattern commonly found in the code.
+   * @param throwingLambda A lambda expression that can throw an (ususally checked) exception
+   * @param <R> The return type of the expression
+   * @return The return value of the lamba expression. In case an {@link Exception} is thrown during lambda invocation the
+   *         exception is converted into a {@link RuntimeException} if needed. See {@link #convertToRuntime}
+   *         for conversion logic.
+   */
+  public static <R> R unchecked(Callable<R> throwingLambda) {
+    try {
+      return throwingLambda.call();
+    }
+    catch (Exception ex) {
+      throw convertToRuntime(ex);
+    }
+  }
+
+  /**
+   * Same as {@link #unchecked(Callable)} but for void methods.
+   * @param throwingLambda A void lambda expression that can throw an (ususally checked) exception
+   */
+  public static void uncheckedVoid(ThrowingRunnable throwingLambda) {
+    try {
+      throwingLambda.run();
+    }
+    catch (Exception ex) {
+      throw convertToRuntime(ex);
+    }
+  }
+
+
+  /**
+   * Utility to convert checked exceptions to runtime exceptions.
+   * <ul>
+   *   <li>If the input exception is already of type {@link RuntimeException} the input exception is returned</li>
+   *   <li>If the input exception is of type {@link IOException} the execption will be wrapped into an
+   *   {@link UncheckedIOException}</li>
+   *   <li>Other checked exeptions will be wrapped into a {@link RuntimeException}</li>
+   *   <li>Conversion logic could be improved later</li>
+   * </ul>
+   * @param ex The input exception can be a checked as well as a runtime exception
+   * @return a runtime exception which equals to {@code ex} if ex is a runtime exception or {@code ex} wrapped in a
+   * {@link RuntimeException}.
+   */
+  public static RuntimeException convertToRuntime(Exception ex) {
+    return ex instanceof RuntimeException ? (RuntimeException)ex :
+      ex instanceof IOException ? new UncheckedIOException((IOException)ex) :
+        new RuntimeException(ex);
+  }
+
+  public interface ThrowingRunnable {
+    void run() throws Exception;
+  }
+
+}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessorTest.java
new file mode 100644
index 0000000..5041b8b
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/MpackAdvisorBlueprintProcessorTest.java
@@ -0,0 +1,485 @@
+/*
+ * 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.api.services.mpackadvisor;
+
+
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.ambari.server.api.services.mpackadvisor.MpackAdvisorRequest.MpackAdvisorRequestType.CONFIGURATIONS;
+import static org.apache.ambari.server.utils.ExceptionUtils.uncheckedVoid;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.getCurrentArguments;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.MockType.NICE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.api.services.mpackadvisor.recommendations.MpackRecommendationResponse;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.ValueAttributesInfo;
+import org.apache.ambari.server.topology.AdvisedConfiguration;
+import org.apache.ambari.server.topology.Blueprint;
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.Component;
+import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.ambari.server.topology.HostGroup;
+import org.apache.ambari.server.topology.HostGroupImpl;
+import org.apache.ambari.server.topology.HostGroupInfo;
+import org.apache.ambari.server.topology.MpackInstance;
+import org.apache.ambari.server.topology.ResolvedComponent;
+import org.apache.ambari.server.topology.ServiceInstance;
+import org.easymock.EasyMockRunner;
+import org.easymock.Mock;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+@RunWith(EasyMockRunner.class)
+public class MpackAdvisorBlueprintProcessorTest {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MpackAdvisorBlueprintProcessorTest.class);
+
+  private MpackAdvisorBlueprintProcessor processor;
+
+  private static final String HOSTGROUP_1 = "host_group_1";
+
+  private static final String HOST_1 = "c7401.ambari.apache.org";
+
+  private static final String NAMENODE = "NAMENODE";
+  private static final String DATANODE = "DATANODE";
+  private static final String ZOOKEEPER_SERVER = "ZOOKEPER_SERVER";
+  private static final String HADOOP_CLIENT = "HADOOP_CLIENT";
+  private static final String ZOOKEEPER_CLIENT = "ZOOKEEPER_CLIENT";
+  private static final String HBASE_MASTER = "HBASE_MASTER";
+  private static final String HBASE_REGIONSERVER = "HBASE_REGIONSERVER";
+
+  private static final String ZOOKEEPER = "ZOOKEEPER";
+  private static final String ZOOKEEPER_CLIENTS = "ZOOKEEPER_CLIENTS";
+  private static final String HDFS = "HDFS";
+  private static final String HADOOP_CLIENTS = "HADOOP_CLIENTS";
+  private static final String HBASE = "HBASE";
+
+  private static final StackId STACK_ID_HDP_CORE = new StackId("HDPCORE", "1.0.0");
+  private static final StackId STACK_ID_ODS = new StackId("ODS", "1.0.1");
+
+  private static final Map<String, Set<String>> mpackComponents = ImmutableMap.of(
+    STACK_ID_HDP_CORE.getStackName(), ImmutableSet.of(NAMENODE, DATANODE, HADOOP_CLIENT, ZOOKEEPER_SERVER, ZOOKEEPER_CLIENT),
+    STACK_ID_ODS.getStackName(), ImmutableSet.of(HBASE_MASTER, HBASE_REGIONSERVER, HADOOP_CLIENT, ZOOKEEPER_CLIENT)
+  );
+
+  private static final Map<String, String> configTypeToService = ImmutableMap.of(
+    "zoo.cfg", "ZOOKEEPER",
+    "hdfs-site", "HDFS",
+    "hbase-size", "HBASE"
+  );
+
+  @Mock(type = NICE)
+  private MpackAdvisorHelper helper;
+
+  @Mock(type = NICE)
+  private AmbariMetaInfo metaInfo;
+
+  @Mock(type = NICE)
+  private ClusterTopology topology;
+
+  @Mock(type = NICE)
+  private StackInfo hdpCore;
+
+  @Mock(type = NICE)
+  private StackInfo ods;
+
+  // These test data are reconfigurable to allow for flexibility for later unit tests
+  private Set<MpackInstance> mpacks = Sets.newHashSet();
+  private Map<String, AdvisedConfiguration> advisedConfigurations = new HashMap<>();
+  private Map<String, Set<ResolvedComponent>> componentsByHostgroup = new HashMap<>();
+  private ConfigRecommendationStrategy recommendationStrategy = ConfigRecommendationStrategy.ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES;
+  private List<ServiceInfo> hdpCoreServices = new ArrayList<>();
+  private List<ServiceInfo> odsServices = new ArrayList<>();
+  private Configuration configuration = new Configuration(new HashMap<>(), new HashMap<>());
+  private Map<String, HostGroupInfo> hostGroupInfoMap = new HashMap<>();
+  private Map<String, HostGroup> hostGroupMap = new HashMap<>();
+  private Map<String, Map<String, String>> userConfigs = new HashMap<>();
+  private MpackRecommendationResponse response = createRecommendationResponse();
+
+  private final MpackInstance hdpCoreMpack = new MpackInstance(
+    STACK_ID_HDP_CORE.getStackName(),
+    STACK_ID_HDP_CORE.getStackName(),
+    STACK_ID_HDP_CORE.getStackVersion(),
+    "http://hdpcore.info",
+    new Configuration(new HashMap<>(), new HashMap<>()),
+    new HashSet<>());
+
+  private final MpackInstance odsMpack = new MpackInstance(
+    STACK_ID_ODS.getStackName(),
+    STACK_ID_ODS.getStackName(),
+    STACK_ID_ODS.getStackVersion(),
+    "http://ods.info",
+    new Configuration(new HashMap<>(), new HashMap<>()),
+    new HashSet<>());
+
+
+  public void setUp() throws Exception {
+    // setup topology
+    expect(topology.getMpacks()).andReturn(mpacks).anyTimes();
+    expect(topology.getAdvisedConfigurations()).andReturn(advisedConfigurations).anyTimes();
+    expect(topology.getComponentsByHostGroup()).andReturn(componentsByHostgroup).anyTimes();
+    expect(topology.getComponents()).andAnswer(
+      () -> componentsByHostgroup.values().stream().flatMap(Set::stream)).anyTimes();
+    expect(topology.isValidConfigType(anyString())).andReturn(true).anyTimes();
+    expect(topology.getConfigRecommendationStrategy()).andAnswer(() -> recommendationStrategy).anyTimes();
+    expect(topology.getConfiguration()).andReturn(configuration).anyTimes();
+    expect(topology.getHostGroupInfo()).andReturn(hostGroupInfoMap).anyTimes();
+    Blueprint blueprint = createMock(Blueprint.class);
+    expect(blueprint.getHostGroups()).andReturn(hostGroupMap).anyTimes();
+    replay(blueprint);
+    expect(topology.getBlueprint()).andReturn(blueprint).anyTimes();
+    replay(topology);
+
+    // setup metainfo
+    expect(metaInfo.getStack(STACK_ID_HDP_CORE)).andReturn(hdpCore).anyTimes();
+    expect(metaInfo.getStack(STACK_ID_ODS)).andReturn(ods).anyTimes();
+    replay(metaInfo);
+
+    // setup stacks
+    expect(hdpCore.getServices()).andReturn(hdpCoreServices).anyTimes();
+    replay(hdpCore);
+    expect(ods.getServices()).andReturn(odsServices).anyTimes();
+    replay(ods);
+
+    // setup mpack instances
+    mpacks.add(hdpCoreMpack);
+    mpacks.add(odsMpack);
+
+    // setup default host groups
+    addHostGroup(HOSTGROUP_1,
+      ImmutableList.of(HOST_1),
+      ImmutableSet.of(
+        resolvedComponent(HADOOP_CLIENT, HADOOP_CLIENTS, STACK_ID_HDP_CORE),
+        resolvedComponent(ZOOKEEPER_CLIENT, ZOOKEEPER_CLIENTS, STACK_ID_ODS),
+        resolvedComponent(NAMENODE, HDFS, STACK_ID_HDP_CORE),
+        resolvedComponent(DATANODE, HDFS, STACK_ID_HDP_CORE),
+        resolvedComponent(ZOOKEEPER_SERVER, ZOOKEEPER, STACK_ID_HDP_CORE),
+        resolvedComponent(HBASE_MASTER, HBASE, STACK_ID_ODS),
+        resolvedComponent(HBASE_REGIONSERVER, HBASE, STACK_ID_ODS)
+      )
+    );
+
+    // setup services
+    hdpCoreServices.addAll(ImmutableList.of(
+      serviceInfo("HDFS", "HADOOP_CLIENT", "NAMENODE", "DATANODE"),
+      serviceInfo("ZOOKEEPER", "ZOOKEPER_SERVER"),
+      serviceInfo("HADOOP_CLIENTS", "HADOOP_CLIENT"),
+      serviceInfo("ZOOKEEPER_CLIENTS", "ZOOKEEPER_CLIENT")
+    ));
+    odsServices.addAll(ImmutableList.of(
+      serviceInfo("HBASE", "HBASE_MASTER", "HBASE_REGIONSERVER"),
+      serviceInfo("HADOOP_CLIENTS", "HADOOP_CLIENT"),
+      serviceInfo("ZOOKEEPER_CLIENTS", "ZOOKEEPER_CLIENT")
+    ));
+
+    // setup user supplied configs
+    userConfigs.put("hdfs-site", ImmutableMap.of(
+      "dfs.datanode.data.dir", "/grid/0/hdfs/data",
+      "dfs.namenode.name.dir", "/grid/0/hdfs/namenode"));
+    userConfigs.put("hbase-site", ImmutableMap.of(
+      "hbase.regionserver.global.memstore.size", "0.3",
+      "hbase.coprocessor.region.classes", "org.apache.hadoop.hbase.security.access.CustomClass"));
+
+    // setup helper
+    expect(helper.recommend(anyObject())).andAnswer(() -> {
+      checkMpackRecommendationRequest((MpackAdvisorRequest)getCurrentArguments()[0]);
+      return response;
+    });
+    replay(helper);
+
+    MpackAdvisorBlueprintProcessor.init(helper, metaInfo);
+    processor = new MpackAdvisorBlueprintProcessor();
+  }
+
+  @After
+  public void tearDown() {
+    reset(helper, metaInfo, topology, hdpCore, ods);
+  }
+
+  @Test
+  public void testAdviseConfiguration_OverrideUserConfig() throws Exception {
+    // GIVEN
+    setUp();
+    recommendationStrategy = ConfigRecommendationStrategy.ALWAYS_APPLY;
+    LOG.info("Testing using recommendation strategy: {}", recommendationStrategy);
+
+    // WHEN
+    processor.adviseConfiguration(topology, userConfigs);
+
+    // THEN
+    Map<String, AdvisedConfiguration> advisedConfigurations = topology.getAdvisedConfigurations();
+    assertEquals(ImmutableSet.of("hdfs-site", "zoo.cfg", "hbase-site"), advisedConfigurations.keySet());
+
+    // all recommended configs coming from the advisor must make it into the topology
+    Set<String> expectedAdvisedZooCfgKeys = ImmutableSet.of("dataDir");
+    Set<String> expectedAdvisedHdfsSiteKeys = ImmutableSet.of("dfs.namenode.checkpoint.dir", "dfs.datanode.data.dir");
+    Set<String> expectedAdvisedHbaseSiteKeys = ImmutableSet.of("hbase.regionserver.wal.codec", "hbase.regionserver.global.memstore.size");
+
+    assertEquals(expectedAdvisedZooCfgKeys, advisedConfigurations.get("zoo.cfg").getProperties().keySet());
+    assertEquals(expectedAdvisedHdfsSiteKeys, advisedConfigurations.get("hdfs-site").getProperties().keySet());
+    assertEquals(expectedAdvisedHdfsSiteKeys, advisedConfigurations.get("hdfs-site").getPropertyValueAttributes().keySet());
+    assertEquals(expectedAdvisedHbaseSiteKeys, advisedConfigurations.get("hbase-site").getProperties().keySet());
+    assertEquals(expectedAdvisedHbaseSiteKeys, advisedConfigurations.get("hbase-site").getPropertyValueAttributes().keySet());
+  }
+
+  @Test
+  public void testAdviseConfiguration_KeepUserConfig() throws Exception {
+    // GIVEN
+    EnumSet.of(
+      ConfigRecommendationStrategy.ONLY_STACK_DEFAULTS_APPLY,
+      ConfigRecommendationStrategy.ALWAYS_APPLY_DONT_OVERRIDE_CUSTOM_VALUES).forEach(
+        strategy -> {
+          LOG.info("Testing using recommendation strategy: {}", strategy);
+          recommendationStrategy = strategy;
+          uncheckedVoid(() -> {
+            setUp();
+            // WHEN
+            processor.adviseConfiguration(topology, userConfigs);
+          });
+          // THEN
+
+          // only keys not in the user supplied configs should be among the advised configs.
+          Set<String> expectedAdvisedZooCfgKeys = ImmutableSet.of("dataDir");
+          Set<String> expectedAdvisedHdfsSiteKeys = ImmutableSet.of("dfs.namenode.checkpoint.dir");
+          Set<String> expectedAdvisedHbaseSiteKeys = ImmutableSet.of("hbase.regionserver.wal.codec");
+
+          assertEquals(expectedAdvisedZooCfgKeys, advisedConfigurations.get("zoo.cfg").getProperties().keySet());
+          assertEquals(expectedAdvisedHdfsSiteKeys, advisedConfigurations.get("hdfs-site").getProperties().keySet());
+          assertEquals(expectedAdvisedHdfsSiteKeys, advisedConfigurations.get("hdfs-site").getPropertyValueAttributes().keySet());
+          assertEquals(expectedAdvisedHbaseSiteKeys, advisedConfigurations.get("hbase-site").getProperties().keySet());
+          assertEquals(expectedAdvisedHbaseSiteKeys, advisedConfigurations.get("hbase-site").getPropertyValueAttributes().keySet());
+
+          tearDown();
+        }
+      );
+  }
+
+
+  /**
+   * Check if the MpackAdvisorRequest was created correctly
+   */
+  private void checkMpackRecommendationRequest(MpackAdvisorRequest request) {
+    assertEquals(CONFIGURATIONS, request.getRequestType());
+
+    List<String> expectedHosts =
+      hostGroupInfoMap.values().stream().flatMap(hg -> hg.getHostNames().stream()).collect(toList());
+    assertEquals(expectedHosts, request.getHosts());
+
+    // check mpack -> component -> hosts map
+    assertTrue(!request.getMpackComponentHostsMap().isEmpty());
+    request.getMpackComponentHostsMap().entrySet().forEach(
+      e -> {
+        String mpackName = e.getKey();
+        Map<String, Set<String>> compentsToHosts = e.getValue();
+        assertTrue(!compentsToHosts.isEmpty()); // at least one component is used from the mpack
+        Set<String> validMpackComponents = mpackComponents.get(mpackName);
+        compentsToHosts.entrySet().forEach( compToHosts -> {
+          assertTrue(validMpackComponents.contains(compToHosts.getKey())); // component is valid for that mpack
+          assertTrue(!compToHosts.getValue().isEmpty()); // component is mapped to at least one host
+        });
+      });
+
+    Map<String, Set<String>> expectedHostBindings =
+      hostGroupInfoMap.entrySet().stream().collect( toMap(
+        Map.Entry::getKey,
+        hostGroupToHosts -> hostGroupToHosts.getValue().getHostNames()));
+    assertEquals(expectedHostBindings, request.getRecommendation().getBlueprintClusterBinding());
+
+    MpackAdvisorRequest.Blueprint requestBlueprint = request.getRecommendation().getBlueprint();
+
+    // verify that mpack instances have been enriched with service instances (mpack advisor requires this even when
+    // no explicit service instances have been defined in the blueprint)
+    Set<String> validHdpServiceNames = hdpCoreServices.stream().map(ServiceInfo::getName).collect(toSet());
+    Set<String> validOdsServiceNames = odsServices.stream().map(ServiceInfo::getName).collect(toSet());
+    requestBlueprint.getMpackInstances().forEach( mpack -> {
+      Set<String> validServiceNames =
+        mpack.getMpackType().equals(STACK_ID_HDP_CORE.getStackName()) ? validHdpServiceNames : validOdsServiceNames;
+      Set<String> serviceInstanceNames = mpack.getServiceInstances().stream().map(ServiceInstance::getName).collect(toSet());
+      assertTrue("No service instances has been added to mpack " + mpack.getMpackName(), !serviceInstanceNames.isEmpty());
+      Set<String> unexpectedServices = Sets.difference(serviceInstanceNames, validServiceNames);
+      assertTrue("Unexected service instances: " + unexpectedServices + " for mpack instance: " + mpack.getMpackName(),
+        unexpectedServices.isEmpty());
+    });
+  }
+
+  /**
+   * Creates a recommendation response
+   */
+  private MpackRecommendationResponse createRecommendationResponse() {
+    MpackRecommendationResponse response = new MpackRecommendationResponse();
+    MpackRecommendationResponse.Recommendation recommendation = new MpackRecommendationResponse.Recommendation();
+    MpackRecommendationResponse.Blueprint blueprint = new MpackRecommendationResponse.Blueprint();
+    recommendation.setBlueprint(blueprint);
+    response.setRecommendations(recommendation);
+
+    MpackRecommendationResponse.MpackInstance responseHdpCoreMpack = responseMpack(STACK_ID_HDP_CORE,
+      ImmutableSet.of(
+        serviceInstance("HDFS",
+          ImmutableMap.of(
+            "hdfs-site",
+            config(ImmutableMap.of(
+              "dfs.namenode.checkpoint.dir", "/hadoop/hdfs/namesecondary",
+              "dfs.datanode.data.dir", "/hadoop/hdfs/data")))
+        ),
+        serviceInstance("ZOOKEEPER",
+          ImmutableMap.of(
+            "zoo.cfg",
+            config(ImmutableMap.of("dataDir", "/hadoop/zookeeper"))))
+      )
+    );
+
+    MpackRecommendationResponse.MpackInstance responseOdsMpack = responseMpack(STACK_ID_ODS,
+      ImmutableSet.of(
+        serviceInstance("HBASE",
+          ImmutableMap.of(
+            "hbase-site",
+            config(ImmutableMap.of(
+              "hbase.regionserver.wal.codec", "org.apache.hadoop.hbase.regionserver.wal.WALCellCodec",
+              "hbase.regionserver.global.memstore.size", "0.4")))
+        )
+      )
+    );
+    blueprint.setMpackInstances(ImmutableSet.of(responseHdpCoreMpack, responseOdsMpack));
+    return response;
+  }
+
+  private void addHostGroup(String name, List<String> hosts, Set<ResolvedComponent> components) {
+    hostGroupInfoMap.put(name, hostGroupInfo(name, hosts));
+    List<String> componentNames = components.stream().map(ResolvedComponent::componentName).collect(toList());
+    hostGroupMap.put(name, hostGroup(name, componentNames));
+    componentsByHostgroup.put(name, components);
+  }
+
+
+  // ----- FACTORY METHODS FOR TEST DATA -----
+
+  private ResolvedComponent resolvedComponent(String name, String serviceName, StackId stackId) {
+    return ResolvedComponent.builder(name)
+      .serviceName(serviceName)
+      .serviceInfo(serviceInfo(serviceName))
+      .stackId(stackId)
+      .buildPartial();
+  }
+
+  private MpackRecommendationResponse.MpackInstance responseMpack(StackId stackId,
+                                                                  Set<MpackRecommendationResponse.ServiceInstance> services) {
+    MpackRecommendationResponse.MpackInstance responseMpack = new MpackRecommendationResponse.MpackInstance();
+    responseMpack.setName(stackId.getStackName());
+    responseMpack.setVersion(stackId.getStackVersion());
+    responseMpack.setServiceInstances(services);
+    return responseMpack;
+  }
+
+  private MpackRecommendationResponse.ServiceInstance serviceInstance(String name, Map<String, MpackRecommendationResponse.BlueprintConfigurations> configs) {
+    MpackRecommendationResponse.ServiceInstance instance = new MpackRecommendationResponse.ServiceInstance();
+    instance.setName(name);
+    instance.setType(name);
+    instance.setConfigurations(configs);
+    return instance;
+  }
+
+  /**
+   * @param properties Properties in key-value format. The same keys will be added as attributes as well
+   *                   with the value {@code delete=true} to test the handling of advised attributes
+   * @return an instance of {@link MpackRecommendationResponse.BlueprintConfigurations} object with the given data
+   */
+  private MpackRecommendationResponse.BlueprintConfigurations config(Map<String, String> properties) {
+    MpackRecommendationResponse.BlueprintConfigurations config = new MpackRecommendationResponse.BlueprintConfigurations();
+    config.setProperties(properties);
+    Map<String, ValueAttributesInfo> propertyAttributes = properties.keySet().stream().collect(toMap(
+      Function.identity(),
+      __ -> {
+        ValueAttributesInfo valueAttributesInfo = new ValueAttributesInfo();
+        valueAttributesInfo.setDelete("true");
+        return valueAttributesInfo;
+      }
+    ));
+    config.setPropertyAttributes(propertyAttributes);
+    return config;
+  }
+
+  private ServiceInfo serviceInfo(String name, String... components) {
+    ServiceInfo service = new ServiceInfo();
+    service.setName(name);
+    service.setComponents(
+      Arrays.stream(components).map(cName -> {
+        ComponentInfo component = new ComponentInfo();
+        component.setName(cName);
+        return component;
+      }).collect(toList()));
+    return service;
+  }
+
+  private HostGroupInfo hostGroupInfo(String name, List<String> hosts) {
+    HostGroupInfo info = new HostGroupInfo(name);
+    hosts.forEach(info::addHost);
+    return info;
+  }
+
+  private Component component(String name) {
+    if (name.contains("@")) {
+      List<String> nameAndMpack = Splitter.on('@').splitToList(name);
+      return new Component(nameAndMpack.get(0), nameAndMpack.get(1), null, null);
+    }
+    else {
+      return new Component(name);
+    }
+  }
+
+  private HostGroup hostGroup(String name, List<String> components) {
+    return new HostGroupImpl(name,
+      components.stream().map(cName -> component(cName)).collect(toList()),
+      new Configuration(new HashMap<>(), new HashMap<>()),
+      "1+");
+  }
+
+}
\ No newline at end of file
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommandTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommandTest.java
index 3550db3..74fb3eb 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommandTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/mpackadvisor/commands/MpackAdvisorCommandTest.java
@@ -25,6 +25,7 @@ import static org.easymock.EasyMock.replay;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -126,7 +127,7 @@ public class MpackAdvisorCommandTest {
     MpackAdvisorCommand.MpackAdvisorData data = new MpackAdvisorCommand.MpackAdvisorData(hostsJSON, servicesJSON);
     doReturn(hostsJSON).when(command).getHostsInformation(request);
     doReturn(data).when(command).getServicesInformation(request, hostsJSON);
-    doReturn(objectNode).when(command).adjust(servicesJSON, request);
+    doReturn(objectNode).when(command).adjust(eq(servicesJSON), eq(request), any());
 
     doAnswer(new Answer() {
       public Object answer(InvocationOnMock invocation) throws Throwable {
@@ -218,7 +219,7 @@ public class MpackAdvisorCommandTest {
     ObjectNode objectNode = (ObjectNode) cmd.mapper.readTree(objectNodeStr);
 
     doReturn(hostsJSON).when(command).getHostsInformation(request);
-    doReturn(objectNode).when(command).adjust(servicesJSON, request);
+    doReturn(objectNode).when(command).adjust(eq(servicesJSON), eq(request), any());
     request.getRecommendation().getBlueprint().setMpackInstances(MpackAdvisorHelperTest.createOdsMpackInstance());
 
     response = createNiceMock(Response.class);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
index 18be047..49ee36a 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
@@ -53,6 +53,7 @@ import org.apache.ambari.server.topology.BlueprintFactory;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.HostGroupInfo;
 import org.apache.ambari.server.topology.InvalidTopologyTemplateException;
+import org.apache.ambari.server.topology.MpackInstance;
 import org.apache.ambari.server.topology.SecurityConfiguration;
 import org.apache.ambari.server.topology.TopologyRequest;
 import org.junit.After;
@@ -63,6 +64,7 @@ import org.junit.rules.ExpectedException;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 /**
@@ -98,6 +100,7 @@ public class ProvisionClusterRequestTest {
     expect(blueprint.getConfiguration()).andReturn(blueprintConfig).anyTimes();
     expect(hostResourceProvider.checkPropertyIds(Collections.singleton("Hosts/host_name"))).
         andReturn(Collections.emptySet()).once();
+    expect(blueprint.getMpacks()).andReturn(ImmutableSet.of(createMock(MpackInstance.class))).anyTimes();
 
     replay(blueprintFactory, blueprint, hostResourceProvider);
   }
@@ -282,6 +285,15 @@ public class ProvisionClusterRequestTest {
     assertEquals("someAttributePropValue", clusterScopedTypePropertyAttributes.get("property1"));
   }
 
+  @Test(expected= IllegalArgumentException.class)
+  public void test_NoMpacksDefined() throws Exception {
+    Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+    reset(blueprint);
+    expect(blueprint.getMpacks()).andReturn(ImmutableSet.of());
+    replay(blueprint);
+    new ProvisionClusterRequest(properties, null, true);
+  }
+
   @Test(expected= InvalidTopologyTemplateException.class)
   public void test_NoHostGroupInfo() throws Exception {
     Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);