You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ad...@apache.org on 2018/02/01 19:45:41 UTC

[ambari] branch branch-feature-AMBARI-14714 updated: [AMBARI-22875] Blueprint cluster creation using manually installed mpacks (#231)

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

adoroszlai 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 00f29b9  [AMBARI-22875] Blueprint cluster creation using manually installed mpacks (#231)
00f29b9 is described below

commit 00f29b9a4f7d5829f1c5a45fad6da587f599a224
Author: Doroszlai, Attila <64...@users.noreply.github.com>
AuthorDate: Thu Feb 1 20:45:38 2018 +0100

    [AMBARI-22875] Blueprint cluster creation using manually installed mpacks (#231)
---
 .../api/query/render/ClusterBlueprintRenderer.java |  13 +-
 .../StackAdvisorBlueprintProcessor.java            |  28 +-
 .../services/stackadvisor/StackAdvisorHelper.java  |   5 +-
 .../services/stackadvisor/StackAdvisorRequest.java |   5 +
 .../services/stackadvisor/StackAdvisorRunner.java  |   2 +-
 .../ambari/server/controller/AmbariServer.java     |   3 -
 .../ambari/server/controller/ControllerModule.java |   5 +
 .../server/controller/ResourceProviderFactory.java |   6 +
 .../AbstractControllerResourceProvider.java        |   2 +-
 .../internal/BlueprintConfigurationProcessor.java  |  59 +--
 .../internal/BlueprintResourceProvider.java        | 164 +++----
 .../server/controller/internal/CompositeStack.java | 306 ++++++++++++
 .../internal/ExportBlueprintRequest.java           |   8 +-
 .../PropertyValueTrimmingStrategyDefiner.java      |   2 +-
 .../internal/ServiceResourceProvider.java          |  19 +-
 .../ambari/server/controller/internal/Stack.java   | 489 +++++++------------
 .../controller/internal/StackDefinition.java       | 278 +++++++++++
 .../server/controller/internal/UnitUpdater.java    |   6 +-
 .../ambari/server/metadata/RoleCommandOrder.java   |  34 ++
 .../orm/entities/BlueprintMpackInstanceEntity.java |   6 +-
 .../ambari/server/stack/NoSuchStackException.java  |   9 +-
 .../ambari/server/topology/AmbariContext.java      | 174 +++----
 .../apache/ambari/server/topology/Blueprint.java   |  54 +--
 .../ambari/server/topology/BlueprintFactory.java   | 190 ++++----
 .../ambari/server/topology/BlueprintImpl.java      | 241 ++++------
 .../ambari/server/topology/BlueprintValidator.java |  10 +-
 .../server/topology/BlueprintValidatorImpl.java    | 125 ++---
 .../topology/ClusterConfigurationRequest.java      |   4 +-
 .../ambari/server/topology/Configurable.java       |  10 +-
 .../ambari/server/topology/Configuration.java      |  20 +-
 .../server/topology/ConfigurationFactory.java      |  14 +-
 .../apache/ambari/server/topology/HostGroup.java   |  14 +-
 .../ambari/server/topology/HostGroupImpl.java      | 120 +----
 .../apache/ambari/server/topology/HostRequest.java |   4 +-
 .../ambari/server/topology/MpackInstance.java      |   4 +-
 .../server/topology/SecurityConfiguration.java     |   2 +
 .../ambari/server/topology/TopologyManager.java    |   9 +-
 .../validators/RequiredPasswordValidator.java      |   3 +-
 .../server/topology/validators/UnitValidator.java  |  12 +-
 .../org/apache/ambari/server/utils/JsonUtils.java  |  31 ++
 .../src/main/resources/Ambari-DDL-Derby-CREATE.sql |   2 +-
 .../src/main/resources/Ambari-DDL-MySQL-CREATE.sql |   2 +-
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql    |   2 +-
 .../main/resources/Ambari-DDL-Postgres-CREATE.sql  |   2 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql    |   2 +-
 .../main/resources/Ambari-DDL-SQLServer-CREATE.sql |   2 +-
 .../src/main/resources/key_properties.json         |   3 -
 ambari-server/src/main/resources/properties.json   |  13 -
 .../stack-hooks/before-ANY/scripts/params.py       |   2 +-
 .../stackadvisor/StackAdvisorHelperTest.java       |   9 +-
 .../internal/BaseBlueprintProcessorTest.java       | 325 +------------
 .../BlueprintConfigurationProcessorTest.java       |   4 +-
 .../internal/BlueprintResourceProviderTest.java    |  87 ++--
 .../controller/internal/CompositeStackTest.java    | 110 +++++
 .../internal/ExportBlueprintRequestTest.java       |  19 +-
 .../server/controller/internal/StackTest.java      | 516 ++++++++++-----------
 .../server/metadata/RoleCommandOrderTest.java      |   7 +
 .../ambari/server/topology/AmbariContextTest.java  |   8 +-
 .../server/topology/BlueprintFactoryTest.java      |  98 ++--
 .../ambari/server/topology/BlueprintImplTest.java  |  65 ++-
 .../topology/BlueprintValidatorImplTest.java       |  87 +---
 .../topology/ClusterConfigurationRequestTest.java  |  16 +-
 .../topology/ClusterDeployWithStartOnlyTest.java   |  20 +-
 ...terInstallWithoutStartOnComponentLevelTest.java |  26 +-
 .../topology/ClusterInstallWithoutStartTest.java   |  20 +-
 .../server/topology/TopologyManagerTest.java       |  20 +-
 66 files changed, 2058 insertions(+), 1899 deletions(-)

diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
index 6be0ff2..deb13db 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/query/render/ClusterBlueprintRenderer.java
@@ -45,7 +45,6 @@ import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ExportBlueprintRequest;
 import org.apache.ambari.server.controller.internal.RequestImpl;
 import org.apache.ambari.server.controller.internal.ResourceImpl;
-import org.apache.ambari.server.controller.internal.Stack;
 import org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
@@ -56,6 +55,7 @@ import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.topology.AmbariContext;
 import org.apache.ambari.server.topology.ClusterTopology;
 import org.apache.ambari.server.topology.ClusterTopologyImpl;
@@ -68,6 +68,8 @@ import org.apache.ambari.server.topology.SecurityConfigurationFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Iterables;
+
 /**
  * Renderer which renders a cluster resource as a blueprint.
  */
@@ -195,9 +197,12 @@ public class ClusterBlueprintRenderer extends BaseRenderer implements Renderer {
     BlueprintConfigurationProcessor configProcessor = new BlueprintConfigurationProcessor(topology);
     configProcessor.doUpdateForBlueprintExport();
 
-    Stack stack = topology.getBlueprint().getStack();
-    blueprintResource.setProperty("Blueprints/stack_name", stack.getName());
-    blueprintResource.setProperty("Blueprints/stack_version", stack.getVersion());
+    Set<StackId> stackIds = topology.getBlueprint().getStackIds();
+    if (stackIds.size() == 1) {
+      StackId stackId = Iterables.getOnlyElement(stackIds);
+      blueprintResource.setProperty("Blueprints/stack_name", stackId.getStackName());
+      blueprintResource.setProperty("Blueprints/stack_version", stackId.getStackVersion());
+    }
 
     if (topology.isClusterKerberosEnabled()) {
       Map<String, Object> securityConfigMap = new LinkedHashMap<>();
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 ddd746b..94ba563 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
@@ -18,7 +18,6 @@
 
 package org.apache.ambari.server.api.services.stackadvisor;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -29,7 +28,7 @@ import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest.St
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.BlueprintConfigurations;
 import org.apache.ambari.server.controller.internal.ConfigurationTopologyException;
-import org.apache.ambari.server.controller.internal.Stack;
+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.Blueprint;
@@ -77,26 +76,27 @@ public class StackAdvisorBlueprintProcessor {
    * @param userProvidedConfigurations User configurations of cluster provided in Blueprint + Cluster template
    */
   public void adviseConfiguration(ClusterTopology clusterTopology, Map<String, Map<String, String>> userProvidedConfigurations) throws ConfigurationTopologyException {
-    StackAdvisorRequest request = createStackAdvisorRequest(clusterTopology, StackAdvisorRequestType.CONFIGURATIONS);
-    try {
-      RecommendationResponse response = stackAdvisorHelper.recommend(request);
-      addAdvisedConfigurationsToTopology(response, clusterTopology, userProvidedConfigurations);
-    } catch (StackAdvisorException e) {
-      throw new ConfigurationTopologyException(RECOMMENDATION_FAILED, e);
-    } catch (IllegalArgumentException e) {
-      throw new ConfigurationTopologyException(INVALID_RESPONSE, e);
+    for (StackId stackId : clusterTopology.getBlueprint().getStackIds()) {
+      StackAdvisorRequest request = createStackAdvisorRequest(clusterTopology, stackId, StackAdvisorRequestType.CONFIGURATIONS);
+      try {
+        RecommendationResponse response = stackAdvisorHelper.recommend(request);
+        addAdvisedConfigurationsToTopology(response, clusterTopology, userProvidedConfigurations);
+      } catch (StackAdvisorException e) {
+        throw new ConfigurationTopologyException(RECOMMENDATION_FAILED, e);
+      } catch (IllegalArgumentException e) {
+        throw new ConfigurationTopologyException(INVALID_RESPONSE, e);
+      }
     }
   }
 
-  private StackAdvisorRequest createStackAdvisorRequest(ClusterTopology clusterTopology, StackAdvisorRequestType requestType) {
-    Stack stack = clusterTopology.getBlueprint().getStack(); // TODO: implement multi-stack
+  private StackAdvisorRequest createStackAdvisorRequest(ClusterTopology clusterTopology, StackId stackId, StackAdvisorRequestType requestType) {
     Map<String, Set<String>> hgComponentsMap = gatherHostGroupComponents(clusterTopology);
     Map<String, Set<String>> hgHostsMap = gatherHostGroupBindings(clusterTopology);
     Map<String, Set<String>> componentHostsMap = gatherComponentsHostsMap(hgComponentsMap,
             hgHostsMap);
     return StackAdvisorRequest.StackAdvisorRequestBuilder
-      .forStack(stack.getName(), stack.getVersion())
-      .forServices(new ArrayList<>(clusterTopology.getBlueprint().getServices()))
+      .forStack(stackId)
+      .forServices(clusterTopology.getBlueprint().getStack().getServices(stackId))
       .forHosts(gatherHosts(clusterTopology))
       .forHostsGroupBindings(gatherHostGroupBindings(clusterTopology))
       .forHostComponents(gatherHostGroupComponents(clusterTopology))
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
index 3e5599c..f826ff3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
@@ -33,7 +33,6 @@ import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorC
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
 import org.apache.ambari.server.configuration.Configuration;
-
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -122,9 +121,7 @@ public class StackAdvisorHelper {
       throws StackAdvisorException {
       requestId = generateRequestId();
 
-    // TODO, need to pass the service Name that was modified.
-    // For now, hardcode
-    String serviceName = "ZOOKEEPER";
+    String serviceName = request.getServices().stream().findAny().orElse(null);
 
     ServiceInfo.ServiceAdvisorType serviceAdvisorType = getServiceAdvisorType(request.getStackName(), request.getStackVersion(), serviceName);
     StackAdvisorCommand<RecommendationResponse> command = createRecommendationCommand(serviceName, request);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
index b30eec6..be76cdc 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
@@ -29,6 +29,7 @@ import java.util.Set;
 
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.state.ChangedConfigInfo;
+import org.apache.ambari.server.state.StackId;
 import org.apache.commons.lang.StringUtils;
 
 import com.google.common.base.Preconditions;
@@ -146,6 +147,10 @@ public class StackAdvisorRequest {
       return new StackAdvisorRequestBuilder(stackName, stackVersion);
     }
 
+    public static StackAdvisorRequestBuilder forStack(StackId stackId) {
+      return new StackAdvisorRequestBuilder(stackId.getStackName(), stackId.getStackVersion());
+    }
+
     public StackAdvisorRequestBuilder ofType(StackAdvisorRequestType requestType) {
       this.instance.requestType = requestType;
       return this;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
index 3ba9f6b..6e7bf43 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRunner.java
@@ -52,7 +52,7 @@ public class StackAdvisorRunner {
    */
   public void runScript(ServiceInfo.ServiceAdvisorType serviceAdvisorType, StackAdvisorCommandType saCommandType, File actionDirectory)
       throws StackAdvisorException {
-    LOG.info(String.format("StackAdvisorRunner. serviceAdvisorType=%s, actionDirectory=%s, command=%s", serviceAdvisorType.toString(), actionDirectory,
+    LOG.info(String.format("StackAdvisorRunner. serviceAdvisorType=%s, actionDirectory=%s, command=%s", serviceAdvisorType, actionDirectory,
         saCommandType));
 
     String outputFile = actionDirectory + File.separator + "stackadvisor.out";
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 5bad0f2..4a0d8d1 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
@@ -63,7 +63,6 @@ import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider;
 import org.apache.ambari.server.controller.internal.AmbariPrivilegeResourceProvider;
 import org.apache.ambari.server.controller.internal.BaseClusterRequest;
-import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterPrivilegeResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
 import org.apache.ambari.server.controller.internal.HostResourceProvider;
@@ -940,8 +939,6 @@ public class AmbariServer {
     SecurityFilter.init(injector.getInstance(Configuration.class));
     StackDefinedPropertyProvider.init(injector);
     AbstractControllerResourceProvider.init(injector.getInstance(ResourceProviderFactory.class));
-    BlueprintResourceProvider.init(injector.getInstance(BlueprintFactory.class),
-        injector.getInstance(BlueprintDAO.class), injector.getInstance(SecurityConfigurationFactory.class), ambariMetaInfo);
     StackDependencyResourceProvider.init(ambariMetaInfo);
     ClusterResourceProvider.init(injector.getInstance(TopologyManager.class),
         injector.getInstance(TopologyRequestFactoryImpl.class), injector.getInstance(SecurityConfigurationFactory
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 b99fcf1..357f1b6 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
@@ -63,6 +63,7 @@ import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.configuration.Configuration.ConnectionPoolType;
 import org.apache.ambari.server.configuration.Configuration.DatabaseType;
 import org.apache.ambari.server.controller.internal.AlertTargetResourceProvider;
+import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterSettingResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterStackVersionResourceProvider;
 import org.apache.ambari.server.controller.internal.ComponentResourceProvider;
@@ -168,6 +169,8 @@ import org.apache.ambari.server.state.scheduler.RequestExecutionImpl;
 import org.apache.ambari.server.state.stack.OsFamily;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostImpl;
 import org.apache.ambari.server.topology.BlueprintFactory;
+import org.apache.ambari.server.topology.BlueprintValidator;
+import org.apache.ambari.server.topology.BlueprintValidatorImpl;
 import org.apache.ambari.server.topology.PersistedState;
 import org.apache.ambari.server.topology.PersistedStateImpl;
 import org.apache.ambari.server.topology.SecurityConfigurationFactory;
@@ -506,6 +509,7 @@ public class ControllerModule extends AbstractModule {
         .implement(ResourceProvider.class, Names.named("alertTarget"), AlertTargetResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("viewInstance"), ViewInstanceResourceProvider.class)
         .implement(ResourceProvider.class, Names.named("rootServiceHostComponentConfiguration"), RootServiceComponentConfigurationResourceProvider.class)
+        .implement(ResourceProvider.class, Names.named(BlueprintResourceProvider.NAME), BlueprintResourceProvider.class)
         .build(ResourceProviderFactory.class));
 
     install(new FactoryModuleBuilder().implement(
@@ -537,6 +541,7 @@ public class ControllerModule extends AbstractModule {
     bind(RegistryFactory.class).to(RegistryFactoryImpl.class);
     bind(HostRoleCommandFactory.class).to(HostRoleCommandFactoryImpl.class);
     bind(SecurityHelper.class).toInstance(SecurityHelperImpl.getInstance());
+    bind(BlueprintValidator.class).to(BlueprintValidatorImpl.class);
     bind(BlueprintFactory.class);
 
     install(new FactoryModuleBuilder().implement(AmbariEvent.class, Names.named("userCreated"), UserCreatedEvent.class).build(AmbariEventFactory.class));
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
index ec5f656..7c316aa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ResourceProviderFactory.java
@@ -22,6 +22,7 @@ package org.apache.ambari.server.controller;
 import javax.inject.Named;
 
 import org.apache.ambari.server.controller.internal.AlertTargetResourceProvider;
+import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.ClusterStackVersionResourceProvider;
 import org.apache.ambari.server.controller.internal.UpgradeResourceProvider;
 import org.apache.ambari.server.controller.internal.ViewInstanceResourceProvider;
@@ -89,4 +90,9 @@ public interface ResourceProviderFactory {
   @Named("viewInstance")
   ViewInstanceResourceProvider getViewInstanceResourceProvider();
 
+  @Named(BlueprintResourceProvider.NAME)
+  BlueprintResourceProvider getBlueprintResourceProvider(
+    AmbariManagementController managementController
+  );
+
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
index 212a372..2d1d1a1 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
@@ -226,7 +226,7 @@ public abstract class AbstractControllerResourceProvider extends AbstractAuthori
       case HostComponentProcess:
         return new HostComponentProcessResourceProvider(managementController);
       case Blueprint:
-        return new BlueprintResourceProvider(managementController);
+        return resourceProviderFactory.getBlueprintResourceProvider(managementController);
       case KerberosDescriptor:
         return resourceProviderFactory.getKerberosDescriptorResourceProvider(managementController);
       case Recommendation:
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
index 5b75532..8da1f51 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessor.java
@@ -58,6 +58,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -65,6 +66,7 @@ import com.google.common.collect.Sets;
  * Updates configuration properties based on cluster topology.  This is done when exporting
  * a blueprint and when a cluster is provisioned via a blueprint.
  */
+// TODO move to topology package
 public class BlueprintConfigurationProcessor {
 
   private static final Logger LOG = LoggerFactory.getLogger(BlueprintConfigurationProcessor.class);
@@ -437,7 +439,7 @@ public class BlueprintConfigurationProcessor {
 
   private void trimProperties(Configuration clusterConfig, ClusterTopology clusterTopology) {
     Blueprint blueprint = clusterTopology.getBlueprint();
-    Stack stack = blueprint.getStack();
+    StackDefinition stack = blueprint.getStack();
 
     Map<String, Map<String, String>> configTypes = clusterConfig.getFullProperties();
     for (String configType : configTypes.keySet()) {
@@ -448,7 +450,7 @@ public class BlueprintConfigurationProcessor {
     }
   }
 
-  private void trimPropertyValue(Configuration clusterConfig, Stack stack, String configType, Map<String, String> properties, String propertyName) {
+  private void trimPropertyValue(Configuration clusterConfig, StackDefinition stack, String configType, Map<String, String> properties, String propertyName) {
     if (propertyName != null && properties.get(propertyName) != null) {
 
       TrimmingStrategy trimmingStrategy =
@@ -2880,7 +2882,7 @@ public class BlueprintConfigurationProcessor {
    * @param configTypesUpdated
    * @param stack
    */
-  private void addExcludedConfigProperties(Configuration configuration, Set<String> configTypesUpdated, Stack stack) {
+  private void addExcludedConfigProperties(Configuration configuration, Set<String> configTypesUpdated, StackDefinition stack) {
     Collection<String> blueprintServices = clusterTopology.getBlueprint().getServices();
 
     LOG.debug("Handling excluded properties for blueprint services: {}", blueprintServices);
@@ -2971,38 +2973,41 @@ public class BlueprintConfigurationProcessor {
    * @param configTypesUpdated
    *          the list of configuration types updated (cluster-env will be added
    *          to this).
-   * @throws ConfigurationTopologyException
    */
   private void setStackToolsAndFeatures(Configuration configuration, Set<String> configTypesUpdated)
       throws ConfigurationTopologyException {
     ConfigHelper configHelper = clusterTopology.getAmbariContext().getConfigHelper();
-    Stack stack = clusterTopology.getBlueprint().getStack();
-    String stackName = stack.getName();
-    String stackVersion = stack.getVersion();
 
-    StackId stackId = new StackId(stackName, stackVersion);
-
-    Set<String> properties = Sets.newHashSet(ConfigHelper.CLUSTER_ENV_STACK_NAME_PROPERTY,
-        ConfigHelper.CLUSTER_ENV_STACK_ROOT_PROPERTY, ConfigHelper.CLUSTER_ENV_STACK_TOOLS_PROPERTY,
-        ConfigHelper.CLUSTER_ENV_STACK_FEATURES_PROPERTY,
-        ConfigHelper.CLUSTER_ENV_STACK_PACKAGES_PROPERTY);
+    Set<String> properties = ImmutableSet.of(
+      ConfigHelper.CLUSTER_ENV_STACK_NAME_PROPERTY,
+      ConfigHelper.CLUSTER_ENV_STACK_ROOT_PROPERTY,
+      ConfigHelper.CLUSTER_ENV_STACK_TOOLS_PROPERTY,
+      ConfigHelper.CLUSTER_ENV_STACK_FEATURES_PROPERTY,
+      ConfigHelper.CLUSTER_ENV_STACK_PACKAGES_PROPERTY
+    );
 
     try {
-      Map<String, Map<String, String>> defaultStackProperties = configHelper.getDefaultStackProperties(stackId);
-      Map<String,String> clusterEnvDefaultProperties = defaultStackProperties.get(CLUSTER_ENV_CONFIG_TYPE_NAME);
-
-      for( String property : properties ){
-        if (clusterEnvDefaultProperties.containsKey(property)) {
-          configuration.setProperty(CLUSTER_ENV_CONFIG_TYPE_NAME, property,
-              clusterEnvDefaultProperties.get(property));
+      for (StackId stackId : clusterTopology.getBlueprint().getStackIds()) {
+        Map<String, Map<String, String>> defaultStackProperties = configHelper.getDefaultStackProperties(stackId);
+        if (defaultStackProperties.containsKey(CLUSTER_ENV_CONFIG_TYPE_NAME)) {
+          Map<String, String> clusterEnvDefaultProperties = defaultStackProperties.get(CLUSTER_ENV_CONFIG_TYPE_NAME);
+
+          for (String property : properties) {
+            if (clusterEnvDefaultProperties.containsKey(property)) {
+              configuration.setProperty(CLUSTER_ENV_CONFIG_TYPE_NAME, property,
+                clusterEnvDefaultProperties.get(property)
+              );
+
+              // make sure to include the configuration type as being updated
+              configTypesUpdated.add(CLUSTER_ENV_CONFIG_TYPE_NAME);
+            }
+          }
 
-          // make sure to include the configuration type as being updated
-          configTypesUpdated.add(CLUSTER_ENV_CONFIG_TYPE_NAME);
+          break;
         }
       }
-    } catch( AmbariException ambariException ){
-      throw new ConfigurationTopologyException("Unable to retrieve the stack tools and features",
-          ambariException);
+    } catch (AmbariException e) {
+      throw new ConfigurationTopologyException("Unable to retrieve the stack tools and features", e);
     }
   }
 
@@ -3101,7 +3106,7 @@ public class BlueprintConfigurationProcessor {
      */
     @Override
     public boolean isPropertyIncluded(String propertyName, String propertyValue, String configType, ClusterTopology topology) {
-        Stack stack = topology.getBlueprint().getStack();
+        StackDefinition stack = topology.getBlueprint().getStack();
         final String serviceName = stack.getServiceForConfigType(configType);
         return !(stack.isPasswordProperty(serviceName, configType, propertyName) ||
                 stack.isKerberosPrincipalNameProperty(serviceName, configType, propertyName));
@@ -3198,7 +3203,7 @@ public class BlueprintConfigurationProcessor {
      */
     @Override
     public boolean isPropertyIncluded(String propertyName, String propertyValue, String configType, ClusterTopology topology) {
-      Stack stack = topology.getBlueprint().getStack();
+      StackDefinition stack = topology.getBlueprint().getStack();
       Configuration configuration = topology.getConfiguration();
 
       final String serviceName = stack.getServiceForConfigType(configType);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintResourceProvider.java
index 1a4221e..760d90f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BlueprintResourceProvider.java
@@ -20,10 +20,7 @@ package org.apache.ambari.server.controller.internal;
 
 import static java.util.stream.Collectors.toList;
 
-import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -33,6 +30,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.inject.Inject;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.DuplicateResourceException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
@@ -60,20 +59,23 @@ import org.apache.ambari.server.state.SecurityType;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.BlueprintFactory;
+import org.apache.ambari.server.topology.BlueprintValidator;
 import org.apache.ambari.server.topology.GPLLicenseNotAcceptedException;
 import org.apache.ambari.server.topology.InvalidTopologyException;
 import org.apache.ambari.server.topology.MpackInstance;
 import org.apache.ambari.server.topology.SecurityConfiguration;
 import org.apache.ambari.server.topology.SecurityConfigurationFactory;
+import org.apache.ambari.server.utils.JsonUtils;
 import org.apache.ambari.server.utils.SecretReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.assistedinject.Assisted;
 
 
 /**
@@ -82,21 +84,19 @@ import com.google.common.collect.Sets;
 public class BlueprintResourceProvider extends AbstractControllerResourceProvider {
 
   private static final Logger LOG = LoggerFactory.getLogger(BlueprintResourceProvider.class);
+  public static final String NAME = "blueprint";
 
   // ----- Property ID constants ---------------------------------------------
 
-  // Blueprints
-  public static final String BLUEPRINT_NAME_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "blueprint_name");
-  public static final String STACK_NAME_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "stack_name");
-  public static final String STACK_VERSION_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "stack_version");
+  public static final String RESPONSE_KEY = "Blueprints";
+  public static final String BLUEPRINTS_PROPERTY_ID = "Blueprints";
 
-  public static final String BLUEPRINT_SECURITY_PROPERTY_ID =
-    PropertyHelper.getPropertyId("Blueprints", "security");
 
-  public static final String BLUEPRINTS_PROPERTY_ID = "Blueprints";
+  // Blueprints
+  public static final String BLUEPRINT_NAME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "blueprint_name";
+  public static final String STACK_NAME_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "stack_name";
+  public static final String STACK_VERSION_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "stack_version";
+  public static final String BLUEPRINT_SECURITY_PROPERTY_ID = RESPONSE_KEY + PropertyHelper.EXTERNAL_PATH_SEP + "security";
 
   // Host Groups
   public static final String HOST_GROUP_PROPERTY_ID = "host_groups";
@@ -107,8 +107,8 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
   public static final String COMPONENT_PROPERTY_ID ="components";
   public static final String COMPONENT_NAME_PROPERTY_ID ="name";
   public static final String COMPONENT_PROVISION_ACTION_PROPERTY_ID = "provision_action";
-  protected static final String COMPONENT_MPACK_INSTANCE_PROPERTY = "mpack_instance";
-  protected static final String COMPONENT_SERVICE_INSTANCE_PROPERTY = "service_instance";
+  public static final String COMPONENT_MPACK_INSTANCE_PROPERTY = "mpack_instance";
+  public static final String COMPONENT_SERVICE_INSTANCE_PROPERTY = "service_instance";
 
   // Configurations
   public static final String CONFIGURATION_PROPERTY_ID = "configurations";
@@ -116,8 +116,10 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
   // Setting
   public static final String SETTING_PROPERTY_ID = "settings";
 
+  public static final String VALIDATE_TOPOLOGY_PROPERTY_ID = "validate_topology";
   public static final String PROPERTIES_PROPERTY_ID = "properties";
   public static final String PROPERTIES_ATTRIBUTES_PROPERTY_ID = "properties_attributes";
+
   public static final String SCHEMA_IS_NOT_SUPPORTED_MESSAGE =
     "Configuration format provided in Blueprint is not supported";
   public static final String REQUEST_BODY_EMPTY_ERROR_MESSAGE =
@@ -130,50 +132,51 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
     "Configuration Maps must hold a single configuration type each";
   public static final String MPACK_INSTANCES_PROPERTY_ID = "mpack_instances";
 
-  // Primary Key Fields
-  private static Set<String> pkPropertyIds =
-    new HashSet<>(Arrays.asList(new String[]{
-      BLUEPRINT_NAME_PROPERTY_ID}));
-
   /**
    * The key property ids for a Blueprint resource.
    */
-  private static Map<Resource.Type, String> keyPropertyIds = ImmutableMap.<Resource.Type, String>builder()
-      .put(Resource.Type.Blueprint, BLUEPRINT_NAME_PROPERTY_ID)
-      .build();
+  private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = ImmutableMap.of(Resource.Type.Blueprint, BLUEPRINT_NAME_PROPERTY_ID);
+  private static final Set<String> PK_PROPERTY_IDS = ImmutableSet.copyOf(KEY_PROPERTY_IDS.values());
 
   /**
    * The property ids for a Blueprint resource.
    */
-  private static Set<String> propertyIds = Sets.newHashSet(
-      BLUEPRINT_NAME_PROPERTY_ID,
-      STACK_NAME_PROPERTY_ID,
-      STACK_VERSION_PROPERTY_ID,
-      BLUEPRINT_SECURITY_PROPERTY_ID,
-      HOST_GROUP_PROPERTY_ID,
-      CONFIGURATION_PROPERTY_ID,
-      SETTING_PROPERTY_ID,
-      MPACK_INSTANCES_PROPERTY_ID);
+  private static final Set<String> PROPERTY_IDS = ImmutableSet.of(
+    BLUEPRINT_NAME_PROPERTY_ID,
+    STACK_NAME_PROPERTY_ID,
+    STACK_VERSION_PROPERTY_ID,
+    BLUEPRINT_SECURITY_PROPERTY_ID,
+    HOST_GROUP_PROPERTY_ID,
+    HOST_GROUP_PROPERTY_ID + PropertyHelper.EXTERNAL_PATH_SEP + COMPONENT_PROPERTY_ID,
+    HOST_GROUP_PROPERTY_ID + PropertyHelper.EXTERNAL_PATH_SEP + HOST_GROUP_CARDINALITY_PROPERTY_ID,
+    CONFIGURATION_PROPERTY_ID,
+    VALIDATE_TOPOLOGY_PROPERTY_ID,
+    SETTING_PROPERTY_ID,
+    MPACK_INSTANCES_PROPERTY_ID
+  );
 
   /**
    * Used to create Blueprint instances
    */
-  private static BlueprintFactory blueprintFactory;
+  private final BlueprintFactory blueprintFactory;
 
   /**
    * Used to create SecurityConfiguration instances
    */
-  private static SecurityConfigurationFactory securityConfigurationFactory;
+  private final SecurityConfigurationFactory securityConfigurationFactory;
 
   /**
    * Blueprint Data Access Object
    */
-  private static BlueprintDAO blueprintDAO;
+  private final BlueprintDAO blueprintDAO;
+
+  private final BlueprintValidator validator;
 
   /**
-   * Used to serialize to/from json.
+   * Used to get stack metainfo.
    */
-  private static ObjectMapper jsonSerializer = new ObjectMapper();
+  private final AmbariMetaInfo ambariMetaInfo;
+
 
   // ----- Constructors ----------------------------------------------------
 
@@ -182,18 +185,17 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
    *
    * @param controller      management controller
    */
-  BlueprintResourceProvider(AmbariManagementController controller) {
-    super(Resource.Type.Blueprint, propertyIds, keyPropertyIds, controller);
-  }
-
-  /**
-   * Static initialization.
-   *
-   * @param factory   blueprint factory
-   * @param dao       blueprint data access object
-   */
-  public static void init(BlueprintFactory factory, BlueprintDAO dao, SecurityConfigurationFactory
-    securityFactory, AmbariMetaInfo metaInfo) {
+  @Inject
+  BlueprintResourceProvider(
+    BlueprintValidator validator,
+    BlueprintFactory factory,
+    BlueprintDAO dao,
+    SecurityConfigurationFactory securityFactory,
+    AmbariMetaInfo metaInfo,
+    @Assisted AmbariManagementController controller
+  ) {
+    super(Resource.Type.Blueprint, PROPERTY_IDS, KEY_PROPERTY_IDS, controller);
+    this.validator = validator;
     blueprintFactory = factory;
     blueprintDAO = dao;
     securityConfigurationFactory = securityFactory;
@@ -204,7 +206,7 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
 
   @Override
   protected Set<String> getPKPropertyIds() {
-    return new HashSet<>(keyPropertyIds.values());
+    return PK_PROPERTY_IDS;
   }
 
   @Override
@@ -306,11 +308,6 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
     return getRequestStatus(null);
   }
 
-  /**
-   * Used to get stack metainfo.
-   */
-  private static AmbariMetaInfo ambariMetaInfo;
-
   // ----- Instance Methods ------------------------------------------------
 
   /**
@@ -339,7 +336,7 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
         Map<String, String> mapComponentProps = new HashMap<>();
         mapComponentProps.put(COMPONENT_NAME_PROPERTY_ID, component.getName());
         if (component.getProvisionAction() != null) {
-          mapComponentProps.put(COMPONENT_PROVISION_ACTION_PROPERTY_ID, component.getProvisionAction().toString());
+          mapComponentProps.put(COMPONENT_PROVISION_ACTION_PROPERTY_ID, component.getProvisionAction());
         }
         if (component.getMpackName() != null) {
           mapComponentProps.put(COMPONENT_MPACK_INSTANCE_PROPERTY,
@@ -372,35 +369,13 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
 
     Collection<Map<String, Object>> mpacks = entity.getMpackInstances().stream().map(mpackEntity -> {
       MpackInstance mpack = MpackInstance.fromEntity(mpackEntity);
-      Map<String, Object> mpackAsMap = fromJson(toJson(mpack), Map.class);
-      return mpackAsMap;
+      return JsonUtils.fromJson(JsonUtils.toJson(mpack), new TypeReference<Map<String, Object>>(){});
     } ).collect(toList());
     setResourceProperty(resource, MPACK_INSTANCES_PROPERTY_ID, mpacks, requestedIds);
 
     return resource;
   }
 
-  private static <T> T fromJson(String json, Class<? extends T> valueType) {
-    if (null == json) {
-      return  null;
-    }
-    try {
-      return jsonSerializer.readValue(json, valueType);
-    }
-    catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
-  }
-
-  private static String toJson(Object object) {
-    try {
-      return jsonSerializer.writeValueAsString(object);
-    }
-    catch (IOException ex) {
-      throw new UncheckedIOException(ex);
-    }
-  }
-
   /**
    * Populate a list of configuration property maps from a collection of configuration entities.
    *
@@ -418,7 +393,7 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
       String type = config.getType();
 
       if(config instanceof BlueprintConfigEntity) {
-        Map<String, String> properties = fromJson(config.getConfigData(), Map.class);
+        Map<String, String> properties = JsonUtils.fromJson(config.getConfigData(), new TypeReference<Map<String, String>>(){});
 
         // TODO: use multiple mpacks
         BlueprintMpackInstanceEntity mpack =
@@ -434,15 +409,15 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
         Map<org.apache.ambari.server.state.PropertyInfo.PropertyType, Set<String>> propertiesTypes =
           metaInfoStack.getConfigPropertiesTypes(type);
 
-        SecretReference.replacePasswordsWithReferences(propertiesTypes, properties, type, -1l);
+        SecretReference.replacePasswordsWithReferences(propertiesTypes, properties, type, -1L);
 
         configTypeDefinition.put(PROPERTIES_PROPERTY_ID, properties);
       } else {
-        Map<String, Object> properties = fromJson(config.getConfigData(), Map.class);
+        Map<String, Object> properties = JsonUtils.fromJson(config.getConfigData(), new TypeReference<Map<String, Object>>(){});
         configTypeDefinition.put(PROPERTIES_PROPERTY_ID, properties);
       }
 
-      Map<String, Map<String, String>> attributes = fromJson(config.getConfigAttributes(), Map.class);
+      Map<String, Map<String, String>> attributes = JsonUtils.fromJson(config.getConfigAttributes(), new TypeReference<Map<String, Map<String, String>>>(){});
       if (attributes != null && !attributes.isEmpty()) {
         configTypeDefinition.put(PROPERTIES_ATTRIBUTES_PROPERTY_ID, attributes);
       }
@@ -460,13 +435,13 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
    *
    * @return list of setting property maps
    */
-  public static List<Map<String, Object>> populateSettingList(
+  public List<Map<String, Object>> populateSettingList(
           Collection<? extends BlueprintSettingEntity> settings) throws NoSuchResourceException {
     List<Map<String, Object>> listSettings = new ArrayList<>();
 
     if (settings != null) {
       for (BlueprintSettingEntity setting : settings) {
-        List<Map<String, String>> propertiesList = fromJson(setting.getSettingData(), List.class);
+        List<Map<String, String>> propertiesList = JsonUtils.fromJson(setting.getSettingData(), new TypeReference<List<Map<String, String>>>(){});
         Map<String, Object> settingMap = new HashMap<>();
         settingMap.put(setting.getSettingName(), propertiesList);
         listSettings.add(settingMap);
@@ -539,13 +514,12 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
    */
   private Command<Void> getCreateCommand(final Map<String, Object> properties, final Map<String, String> requestInfoProps) {
     return new Command<Void>() {
-      @SuppressWarnings("rawtypes")
       @Override
       public Void invoke() throws AmbariException {
         String rawRequestBody = requestInfoProps.get(Request.REQUEST_INFO_BODY_PROPERTY);
         Preconditions.checkArgument(!Strings.isNullOrEmpty(rawRequestBody), REQUEST_BODY_EMPTY_ERROR_MESSAGE);
 
-        Map<String, Object> rawBodyMap = fromJson(rawRequestBody, Map.class);
+        Map<String, Object> rawBodyMap = JsonUtils.fromJson(rawRequestBody, new TypeReference<Map<String, Object>>(){});
         Object configurationData = rawBodyMap.get(CONFIGURATION_PROPERTY_ID);
 
         if (configurationData != null) {
@@ -572,22 +546,22 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
         }
 
         try {
-          blueprint.validateRequiredProperties();
+          validator.validateRequiredProperties(blueprint);
         } catch (InvalidTopologyException | GPLLicenseNotAcceptedException e) {
           throw new IllegalArgumentException("Blueprint configuration validation failed: " + e.getMessage(), e);
         }
 
-        String validateTopology =  requestInfoProps.get("validate_topology");
+        String validateTopology =  requestInfoProps.get(VALIDATE_TOPOLOGY_PROPERTY_ID);
         if (validateTopology == null || ! validateTopology.equalsIgnoreCase("false")) {
           try {
-            blueprint.validateTopology();
+            validator.validateTopology(blueprint);
           } catch (InvalidTopologyException e) {
             throw new IllegalArgumentException(e.getMessage());
           }
         }
 
         LOG.info("Creating Blueprint, name=" + blueprint.getName());
-        String blueprintSetting = blueprint.getSetting() == null ? "(null)" : toJson(blueprint.getSetting().getProperties());
+        String blueprintSetting = blueprint.getSetting() == null ? "(null)" : JsonUtils.toJson(blueprint.getSetting().getProperties());
         LOG.info("Blueprint setting=" + blueprintSetting);
 
         try {
@@ -629,8 +603,8 @@ public class BlueprintResourceProvider extends AbstractControllerResourceProvide
         }
       }
 
-      blueprintConfiguration.setConfigData(toJson(configData));
-      blueprintConfiguration.setConfigAttributes(toJson(configAttributes));
+      blueprintConfiguration.setConfigData(JsonUtils.toJson(configData));
+      blueprintConfiguration.setConfigAttributes(JsonUtils.toJson(configAttributes));
     }
 
     protected abstract void addProperty(Map<String, String> configData,
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CompositeStack.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CompositeStack.java
new file mode 100644
index 0000000..75baf30
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/CompositeStack.java
@@ -0,0 +1,306 @@
+/*
+ * 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.controller.internal;
+
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.ambari.server.state.AutoDeployInfo;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.DependencyInfo;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.Cardinality;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.google.common.collect.ImmutableSet;
+
+/** Combines multiple mpacks into a single stack. */
+// TODO move to topology package
+public class CompositeStack implements StackDefinition {
+
+  private final Set<Stack> stacks;
+
+  CompositeStack(Set<Stack> stacks) {
+    this.stacks = stacks;
+  }
+
+  @Override
+  public Set<StackId> getStacksForService(String serviceName) {
+    return stacks.stream()
+      .map(m -> Pair.of(m.getStackId(), m.getServices()))
+      .filter(p -> p.getRight().contains(serviceName))
+      .map(Pair::getLeft)
+      .collect(toSet());
+  }
+
+  @Override
+  public Set<StackId> getStacksForComponent(String componentName) {
+    return stacks.stream()
+      .map(m -> m.getStacksForComponent(componentName))
+      .filter(s -> !s.isEmpty())
+      .flatMap(Collection::stream)
+      .collect(toSet());
+  }
+
+  @Override
+  public Set<String> getServices(StackId stackId) {
+    return stacks.stream()
+      .filter(m -> stackId.equals(m.getStackId()))
+      .findAny()
+      .flatMap(m -> Optional.of(ImmutableSet.copyOf(m.getServices())))
+      .orElse(ImmutableSet.of());
+  }
+
+  @Override
+  public Set<StackId> getStackIds() {
+    return stacks.stream()
+      .map(Stack::getStackId)
+      .collect(toSet());
+  }
+
+  @Override
+  public Collection<String> getServices() {
+    return stacks.stream()
+      .flatMap(s -> s.getServices().stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public Collection<String> getComponents(String service) {
+    return stacks.stream()
+      .map(s -> s.getComponents(service))
+      .filter(Objects::nonNull)
+      .flatMap(Collection::stream)
+      .collect(toSet());
+  }
+
+  @Override
+  public Collection<String> getComponents() {
+    return stacks.stream()
+      .flatMap(s -> s.getComponents().stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public ComponentInfo getComponentInfo(String component) {
+    return stacks.stream()
+      .map(m -> m.getComponentInfo(component))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public Collection<String> getAllConfigurationTypes(String service) {
+    return stacks.stream()
+      .flatMap(m -> m.getAllConfigurationTypes(service).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public Collection<String> getConfigurationTypes(String service) {
+    return stacks.stream()
+      .flatMap(m -> m.getConfigurationTypes(service).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public Set<String> getExcludedConfigurationTypes(String service) {
+    return stacks.stream()
+      .flatMap(m -> m.getExcludedConfigurationTypes(service).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public Map<String, String> getConfigurationProperties(String service, String type) {
+    return stacks.stream()
+      .flatMap(m -> m.getConfigurationProperties(service, type).entrySet().stream())
+      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  @Override
+  public Map<String, Stack.ConfigProperty> getConfigurationPropertiesWithMetadata(String service, String type) {
+    return stacks.stream()
+      .flatMap(m -> m.getConfigurationPropertiesWithMetadata(service, type).entrySet().stream())
+      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  @Override
+  public Collection<Stack.ConfigProperty> getRequiredConfigurationProperties(String service) {
+    return stacks.stream()
+      .flatMap(m -> m.getRequiredConfigurationProperties(service).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public Collection<Stack.ConfigProperty> getRequiredConfigurationProperties(String service, PropertyInfo.PropertyType propertyType) {
+    return stacks.stream()
+      .flatMap(m -> m.getRequiredConfigurationProperties(service, propertyType).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public boolean isPasswordProperty(String service, String type, String propertyName) {
+    return stacks.stream()
+      .anyMatch(s -> s.isPasswordProperty(service, type, propertyName));
+  }
+
+  @Override
+  public Map<String, String> getStackConfigurationProperties(String type) {
+    return stacks.stream()
+      .flatMap(m -> m.getStackConfigurationProperties(type).entrySet().stream())
+      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  @Override
+  public boolean isKerberosPrincipalNameProperty(String service, String type, String propertyName) {
+    return stacks.stream()
+      .anyMatch(s -> s.isKerberosPrincipalNameProperty(service, type, propertyName));
+  }
+
+  @Override
+  public Map<String, Map<String, String>> getConfigurationAttributes(String service, String type) {
+    return stacks.stream()
+      .flatMap(m -> m.getConfigurationAttributes(service, type).entrySet().stream())
+      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  @Override
+  public Map<String, Map<String, String>> getStackConfigurationAttributes(String type) {
+    return stacks.stream()
+      .flatMap(m -> m.getStackConfigurationAttributes(type).entrySet().stream())
+      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  @Override
+  public String getServiceForComponent(String component) {
+    return stacks.stream()
+      .map(m -> m.getServiceForComponent(component))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public Collection<String> getServicesForComponents(Collection<String> components) {
+    return stacks.stream()
+      .flatMap(m -> m.getServicesForComponents(components).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public String getServiceForConfigType(String config) {
+    if (ConfigHelper.CLUSTER_ENV.equals(config)) { // for backwards compatibility
+      return null;
+    }
+    return getServicesForConfigType(config)
+      .findAny()
+      .orElseThrow(() -> new IllegalArgumentException(Stack.formatMissingServiceForConfigType(config, "ANY")));
+  }
+
+  @Override
+  public Stream<String> getServicesForConfigType(String config) {
+    if (ConfigHelper.CLUSTER_ENV.equals(config)) { // for backwards compatibility
+      return Stream.empty();
+    }
+    return stacks.stream()
+      .map(m -> {
+        try {
+          return m.getServiceForConfigType(config);
+        } catch (IllegalArgumentException e) {
+          return null;
+        }
+      })
+      .filter(Objects::nonNull);
+  }
+
+  @Override
+  public Collection<DependencyInfo> getDependenciesForComponent(String component) {
+    return stacks.stream()
+      .flatMap(m -> m.getDependenciesForComponent(component).stream())
+      .collect(toSet());
+  }
+
+  @Override
+  public String getConditionalServiceForDependency(DependencyInfo dependency) {
+    return stacks.stream()
+      .map(m -> m.getConditionalServiceForDependency(dependency))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public String getExternalComponentConfig(String component) {
+    return stacks.stream()
+      .map(m -> m.getExternalComponentConfig(component))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public Cardinality getCardinality(String component) {
+    return stacks.stream()
+      .map(m -> m.getCardinality(component))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public AutoDeployInfo getAutoDeployInfo(String component) {
+    return stacks.stream()
+      .map(m -> m.getAutoDeployInfo(component))
+      .filter(Objects::nonNull)
+      .findAny()
+      .orElse(null);
+  }
+
+  @Override
+  public boolean isMasterComponent(String component) {
+    return stacks.stream()
+      .anyMatch(s -> s.isMasterComponent(component));
+  }
+
+  @Override
+  public Configuration getConfiguration(Collection<String> services) {
+    // FIXME probably too costly
+    return stacks.stream()
+      .map(m -> m.getConfiguration(services))
+      .reduce(Configuration.createEmpty(), Configuration::combine);
+  }
+
+  @Override
+  public Configuration getConfiguration() {
+    // FIXME probably too costly
+    return stacks.stream()
+      .map(StackDefinition::getConfiguration)
+      .reduce(Configuration.createEmpty(), Configuration::combine);
+  }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequest.java
index ed6b5fd..13ae732 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequest.java
@@ -38,6 +38,7 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.state.DesiredConfig;
 import org.apache.ambari.server.state.HostConfig;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.BlueprintImpl;
 import org.apache.ambari.server.topology.Component;
@@ -50,6 +51,8 @@ import org.apache.ambari.server.topology.TopologyRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Request to export a blueprint from an existing cluster.
  */
@@ -132,10 +135,11 @@ public class ExportBlueprintRequest implements TopologyRequest {
         componentList.add(new Component(component));
       }
 
-      hostGroups.add(new HostGroupImpl(exportedHostGroup.getName(), bpName, Collections.singleton(stack), componentList,
+      hostGroups.add(new HostGroupImpl(exportedHostGroup.getName(), bpName, stack, componentList,
           exportedHostGroup.getConfiguration(), String.valueOf(exportedHostGroup.getCardinality())));
     }
-    blueprint = new BlueprintImpl(bpName, hostGroups, stack, configuration, null, null);
+    ImmutableSet<StackId> stackIds = ImmutableSet.of(stack.getStackId());
+    blueprint = new BlueprintImpl(bpName, hostGroups, stack, stackIds, Collections.emptySet(), configuration, null, null);
   }
 
   private void createHostGroupInfo(Collection<ExportedHostGroup> exportedHostGroups) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PropertyValueTrimmingStrategyDefiner.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PropertyValueTrimmingStrategyDefiner.java
index 0d9b095..860a208 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PropertyValueTrimmingStrategyDefiner.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/PropertyValueTrimmingStrategyDefiner.java
@@ -60,7 +60,7 @@ public class PropertyValueTrimmingStrategyDefiner {
     }
   }
 
-  public static TrimmingStrategy defineTrimmingStrategy(Stack stack, String propertyName, String configType) {
+  public static TrimmingStrategy defineTrimmingStrategy(StackDefinition stack, String propertyName, String configType) {
     TrimmingStrategy result = null;
     String service = stack.getServiceForConfigType(configType);
     if (service != null) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
index 1aadf3a..5e14445 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
@@ -1102,7 +1102,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
         LOG.debug("Received a createService request, clusterId={}, serviceName={}, request={}", clusterName, serviceName, request);
       }
 
-      if(!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
+      if (!AuthorizationHelper.isAuthorized(ResourceType.CLUSTER, getClusterResourceId(clusterName), RoleAuthorization.SERVICE_ADD_DELETE_SERVICES)) {
         throw new AuthorizationException("The user is not authorized to create services");
       }
 
@@ -1121,8 +1121,8 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
         State state = State.valueOf(request.getDesiredState());
         if (!state.isValidDesiredState() || state != State.INIT) {
           throw new IllegalArgumentException("Invalid desired state"
-                  + " only INIT state allowed during creation"
-                  + ", providedDesiredState=" + request.getDesiredState());
+            + " only INIT state allowed during creation"
+            + ", providedDesiredState=" + request.getDesiredState());
         }
       }
 
@@ -1148,7 +1148,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
       if (null == desiredRepositoryVersion) {
         Set<Long> repoIds = new HashSet<>();
 
-        if(desiredStackId != null) {
+        if (desiredStackId != null) {
           //Todo : How to filter out the right repoversion entity based on the stack id?
           List<RepositoryVersionEntity> list = repositoryVersionDAO.findByStack(desiredStackId);
           RepositoryVersionEntity serviceRepo = list.remove(0);
@@ -1169,13 +1169,14 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
         }
 
         LOG.info("{} was not specified; the following repository ids were found: {}",
-            SERVICE_DESIRED_REPO_VERSION_ID_PROPERTY_ID, StringUtils.join(repoIds, ','));
+          SERVICE_DESIRED_REPO_VERSION_ID_PROPERTY_ID, StringUtils.join(repoIds, ',')
+        );
 
         if (CollectionUtils.isEmpty(repoIds)) {
           throw new IllegalArgumentException("No repositories were found for service installation");
         } else if (repoIds.size() > 1) {
           throw new IllegalArgumentException(String.format("%s was not specified, and the cluster " +
-              "contains more than one standard-type repository", SERVICE_DESIRED_REPO_VERSION_ID_PROPERTY_ID));
+            "contains more than one standard-type repository", SERVICE_DESIRED_REPO_VERSION_ID_PROPERTY_ID));
         } else {
           desiredRepositoryVersion = repoIds.iterator().next();
         }
@@ -1191,8 +1192,10 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
         throw new IllegalArgumentException(String.format("Could not find any repositories defined by %d", desiredRepositoryVersion));
       }
 
-      StackId stackId = repositoryVersion.getStackId();
-      //StackId stackId = desiredStackId; //Todo Replace after UI is ready
+      StackId stackId = desiredStackId;
+      if (stackId == null) {
+        stackId = repositoryVersion.getStackId();
+      }
 
       request.setResolvedRepository(repositoryVersion);
 
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
index b54dd20..77422eb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
@@ -18,47 +18,44 @@
 
 package org.apache.ambari.server.controller.internal;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.ReadOnlyConfigurationResponse;
-import org.apache.ambari.server.controller.StackConfigurationRequest;
-import org.apache.ambari.server.controller.StackLevelConfigurationRequest;
-import org.apache.ambari.server.controller.StackServiceComponentRequest;
-import org.apache.ambari.server.controller.StackServiceComponentResponse;
-import org.apache.ambari.server.controller.StackServiceRequest;
-import org.apache.ambari.server.controller.StackServiceResponse;
-import org.apache.ambari.server.orm.entities.StackEntity;
 import org.apache.ambari.server.state.AutoDeployInfo;
 import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.DependencyInfo;
 import org.apache.ambari.server.state.PropertyDependencyInfo;
 import org.apache.ambari.server.state.PropertyInfo;
+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.Cardinality;
 import org.apache.ambari.server.topology.Configuration;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
 /**
- * Encapsulates stack information.
+ * Encapsulates a single, identifiable stack definition.
  */
-public class Stack {
-  /**
-   * Stack name
-   */
-  private String name;
+// TODO move to topology package
+public class Stack implements StackDefinition {
 
   /**
-   * Stack version
+   * Stack info
    */
-  private String version;
+  private final StackInfo stackInfo;
 
   /**
    * Map of service name to components
@@ -130,53 +127,25 @@ public class Stack {
   private Map<String, Set<String>> excludedConfigurationTypes =
     new HashMap<>();
 
-  /**
-   * Ambari Management Controller, used to obtain Stack definitions
-   */
-  private final AmbariManagementController controller;
+  public Stack(String name, String version, AmbariManagementController ctrl) throws AmbariException { // FIXME remove or at least change to use metainfo directly
+    this(ctrl.getAmbariMetaInfo().getStack(name, version));
+  }
 
+  public Stack(StackInfo stackInfo) {
+    Preconditions.checkNotNull(stackInfo);
+    this.stackInfo = stackInfo;
 
-  /**
-   * Constructor.
-   *
-   * @param stack
-   *          the stack (not {@code null}).
-   * @param ambariManagementController
-   *          the management controller (not {@code null}).
-   * @throws AmbariException
-   */
-  public Stack(StackEntity stack, AmbariManagementController ambariManagementController) throws AmbariException {
-    this(stack.getStackName(), stack.getStackVersion(), ambariManagementController);
-  }
+    parseStackConfigurations();
 
-  /**
-   * Constructor.
-   *
-   * @param name     stack name
-   * @param version  stack version
-   *
-   * @throws AmbariException an exception occurred getting stack information
-   *                         for the specified name and version
-   */
-  //todo: don't pass management controller in constructor
-  public Stack(String name, String version, AmbariManagementController controller) throws AmbariException {
-    this.name = name;
-    this.version = version;
-    this.controller = controller;
-
-    Set<StackServiceResponse> stackServices = controller.getStackServices(
-        Collections.singleton(new StackServiceRequest(name, version, null)));
-
-    for (StackServiceResponse stackService : stackServices) {
-      String serviceName = stackService.getServiceName();
-      parseComponents(serviceName);
+    for (ServiceInfo stackService : stackInfo.getServices()) {
+      parseComponents(stackService);
       parseExcludedConfigurations(stackService);
       parseConfigurations(stackService);
-      registerConditionalDependencies();
     }
 
-    //todo: already done for each service
-    parseStackConfigurations();
+    if (!stackInfo.getServices().isEmpty()) {
+      registerConditionalDependencies();
+    }
   }
 
   /**
@@ -185,7 +154,7 @@ public class Stack {
    * @return stack name
    */
   public String getName() {
-    return name;
+    return stackInfo.getName();
   }
 
   /**
@@ -194,140 +163,107 @@ public class Stack {
    * @return stack version
    */
   public String getVersion() {
-    return version;
+    return stackInfo.getVersion();
   }
 
+  public StackId getStackId() {
+    return new StackId(getName(), getVersion());
+  }
 
   Map<DependencyInfo, String> getDependencyConditionalServiceMap() {
     return dependencyConditionalServiceMap;
   }
 
-  /**
-   * Get services contained in the stack.
-   *
-   * @return collection of all services for the stack
-   */
+  @Override
+  public Set<StackId> getStackIds() {
+    return ImmutableSet.of(getStackId());
+  }
+
+  @Override
+  public Set<StackId> getStacksForService(String serviceName) {
+    return serviceComponents.keySet().contains(serviceName)
+      ? ImmutableSet.of(getStackId())
+      : ImmutableSet.of();
+  }
+
+  @Override
+  public Set<StackId> getStacksForComponent(String componentName) {
+    return componentService.keySet().contains(componentName)
+      ? ImmutableSet.of(getStackId())
+      : ImmutableSet.of();
+  }
+
+  @Override
+  public Set<String> getServices(StackId stackId) {
+    return stackId.equals(getStackId())
+      ? ImmutableSet.copyOf(getServices())
+      : ImmutableSet.of();
+  }
+
+  @Override
   public Collection<String> getServices() {
     return serviceComponents.keySet();
   }
 
-  /**
-   * Get components contained in the stack for the specified service.
-   *
-   * @param service  service name
-   *
-   * @return collection of component names for the specified service
-   */
+  @Override
   public Collection<String> getComponents(String service) {
     return serviceComponents.get(service);
   }
 
-  /**
-   * Get all service components
-   *
-   * @return map of service to associated components
-   */
-  public Map<String, Collection<String>> getComponents() {
-    Map<String, Collection<String>> serviceComponents = new HashMap<>();
-    for (String service : getServices()) {
-      Collection<String> components = new HashSet<>();
-      components.addAll(getComponents(service));
-      serviceComponents.put(service, components);
-    }
-    return serviceComponents;
+  @Override
+  public Collection<String> getComponents() {
+    return componentService.keySet();
   }
 
-  /**
-   * Get info for the specified component.
-   *
-   * @param component  component name
-   *
-   * @return component information for the requested component
-   *         or null if the component doesn't exist in the stack
-   */
+  @Override
   public ComponentInfo getComponentInfo(String component) {
-    ComponentInfo componentInfo = null;
     String service = getServiceForComponent(component);
     if (service != null) {
-      try {
-        componentInfo = controller.getAmbariMetaInfo().getComponent(
-            getName(), getVersion(), service, component);
-      } catch (AmbariException e) {
-        // just return null if component doesn't exist
+      ServiceInfo serviceInfo = stackInfo.getService(service);
+      if (serviceInfo != null) {
+        return serviceInfo.getComponentByName(component);
       }
     }
-    return componentInfo;
+    return null;
   }
 
-  /**
-   * Get all configuration types, including excluded types for the specified service.
-   *
-   * @param service  service name
-   *
-   * @return collection of all configuration types for the specified service
-   */
+  @Override
   public Collection<String> getAllConfigurationTypes(String service) {
-    return serviceConfigurations.get(service).keySet();
+    Map<String, Map<String, ConfigProperty>> serviceConfigs = getServiceConfigurations(service);
+    return serviceConfigs != null ? serviceConfigs.keySet() : ImmutableSet.of();
   }
 
-  /**
-   * Get configuration types for the specified service.
-   * This doesn't include any service excluded types.
-   *
-   * @param service  service name
-   *
-   * @return collection of all configuration types for the specified service
-   */
+  @Override
   public Collection<String> getConfigurationTypes(String service) {
-    Set<String> serviceTypes = new HashSet<>(serviceConfigurations.get(service).keySet());
+    Set<String> serviceTypes = new HashSet<>(getAllConfigurationTypes(service));
     serviceTypes.removeAll(getExcludedConfigurationTypes(service));
-
     return serviceTypes;
   }
 
-  /**
-   * Get the set of excluded configuration types for this service.
-   *
-   * @param service service name
-   *
-   * @return Set of names of excluded config types. Will not return null.
-   */
+  @Override
   public Set<String> getExcludedConfigurationTypes(String service) {
     return excludedConfigurationTypes.containsKey(service) ?
         excludedConfigurationTypes.get(service) :
         Collections.emptySet();
   }
 
-  /**
-   * Get config properties for the specified service and configuration type.
-   *
-   * @param service  service name
-   * @param type     configuration type
-   *
-   * @return map of property names to values for the specified service and configuration type
-   */
+  @Override
   public Map<String, String> getConfigurationProperties(String service, String type) {
     Map<String, String> configMap = new HashMap<>();
-    Map<String, ConfigProperty> configProperties = serviceConfigurations.get(service).get(type);
-    if (configProperties != null) {
-      for (Map.Entry<String, ConfigProperty> configProperty : configProperties.entrySet()) {
-        configMap.put(configProperty.getKey(), configProperty.getValue().getValue());
-      }
+    Map<String, ConfigProperty> configProperties = getConfigurationPropertiesWithMetadata(service, type);
+    for (Map.Entry<String, ConfigProperty> configProperty : configProperties.entrySet()) {
+      configMap.put(configProperty.getKey(), configProperty.getValue().getValue());
     }
     return configMap;
   }
 
+  @Override
   public Map<String, ConfigProperty> getConfigurationPropertiesWithMetadata(String service, String type) {
-    return serviceConfigurations.get(service).get(type);
+    Map<String, ConfigProperty> map = getServiceConfigurations(service).get(type);
+    return map != null ? ImmutableMap.copyOf(map) : ImmutableMap.of();
   }
 
-  /**
-   * Get all required config properties for the specified service.
-   *
-   * @param service  service name
-   *
-   * @return collection of all required properties for the given service
-   */
+  @Override
   public Collection<ConfigProperty> getRequiredConfigurationProperties(String service) {
     Collection<ConfigProperty> requiredConfigProperties = new HashSet<>();
     Map<String, Map<String, ConfigProperty>> serviceProperties = requiredServiceConfigurations.get(service);
@@ -339,14 +275,7 @@ public class Stack {
     return requiredConfigProperties;
   }
 
-  /**
-   * Get required config properties for the specified service which belong to the specified property type.
-   *
-   * @param service       service name
-   * @param propertyType  property type
-   *
-   * @return collection of required properties for the given service and property type
-   */
+  @Override
   public Collection<ConfigProperty> getRequiredConfigurationProperties(String service, PropertyInfo.PropertyType propertyType) {
     Collection<ConfigProperty> matchingProperties = new HashSet<>();
     Map<String, Map<String, ConfigProperty>> requiredProperties = requiredServiceConfigurations.get(service);
@@ -363,15 +292,17 @@ public class Stack {
     return matchingProperties;
   }
 
+  @Override
   public boolean isPasswordProperty(String service, String type, String propertyName) {
-    return (serviceConfigurations.containsKey(service) &&
-            serviceConfigurations.get(service).containsKey(type) &&
-            serviceConfigurations.get(service).get(type).containsKey(propertyName) &&
-            serviceConfigurations.get(service).get(type).get(propertyName).getPropertyTypes().
+    Map<String, Map<String, ConfigProperty>> serviceConfigurations = getServiceConfigurations(service);
+    return (serviceConfigurations.containsKey(type) &&
+            serviceConfigurations.get(type).containsKey(propertyName) &&
+            serviceConfigurations.get(type).get(propertyName).getPropertyTypes().
                 contains(PropertyInfo.PropertyType.PASSWORD));
   }
 
   //todo
+  @Override
   public Map<String, String> getStackConfigurationProperties(String type) {
     Map<String, String> configMap = new HashMap<>();
     Map<String, ConfigProperty> configProperties = stackConfigurations.get(type);
@@ -383,25 +314,19 @@ public class Stack {
     return configMap;
   }
 
+  @Override
   public boolean isKerberosPrincipalNameProperty(String service, String type, String propertyName) {
-    return (serviceConfigurations.containsKey(service) &&
-            serviceConfigurations.get(service).containsKey(type) &&
-            serviceConfigurations.get(service).get(type).containsKey(propertyName) &&
-            serviceConfigurations.get(service).get(type).get(propertyName).getPropertyTypes().
+    Map<String, Map<String, ConfigProperty>> serviceConfigurations = getServiceConfigurations(service);
+    return (serviceConfigurations.containsKey(type) &&
+            serviceConfigurations.get(type).containsKey(propertyName) &&
+            serviceConfigurations.get(type).get(propertyName).getPropertyTypes().
                 contains(PropertyInfo.PropertyType.KERBEROS_PRINCIPAL));
   }
-  /**
-   * Get config attributes for the specified service and configuration type.
-   *
-   * @param service  service name
-   * @param type     configuration type
-   *
-   * @return  map of attribute names to map of property names to attribute values
-   *          for the specified service and configuration type
-   */
+
+  @Override
   public Map<String, Map<String, String>> getConfigurationAttributes(String service, String type) {
     Map<String, Map<String, String>> attributesMap = new HashMap<>();
-    Map<String, ConfigProperty> configProperties = serviceConfigurations.get(service).get(type);
+    Map<String, ConfigProperty> configProperties = getServiceConfigurations(service).get(type);
     if (configProperties != null) {
       for (Map.Entry<String, ConfigProperty> configProperty : configProperties.entrySet()) {
         String propertyName = configProperty.getKey();
@@ -411,12 +336,9 @@ public class Stack {
             String attributeName = propertyAttribute.getKey();
             String attributeValue = propertyAttribute.getValue();
             if (attributeValue != null) {
-              Map<String, String> attributes = attributesMap.get(attributeName);
-              if (attributes == null) {
-                  attributes = new HashMap<>();
-                  attributesMap.put(attributeName, attributes);
-              }
-              attributes.put(propertyName, attributeValue);
+              attributesMap
+                .computeIfAbsent(attributeName, k -> new HashMap<>())
+                .put(propertyName, attributeValue);
             }
           }
         }
@@ -425,7 +347,12 @@ public class Stack {
     return attributesMap;
   }
 
-  //todo:
+  private Map<String, Map<String, ConfigProperty>> getServiceConfigurations(String service) {
+    Map<String, Map<String, ConfigProperty>> map = serviceConfigurations.get(service);
+    return map != null ? ImmutableMap.copyOf(map) : ImmutableMap.of();
+  }
+
+  @Override
   public Map<String, Map<String, String>> getStackConfigurationAttributes(String type) {
     Map<String, Map<String, String>> attributesMap = new HashMap<>();
     Map<String, ConfigProperty> configProperties = stackConfigurations.get(type);
@@ -437,12 +364,9 @@ public class Stack {
           for (Map.Entry<String, String> propertyAttribute : propertyAttributes.entrySet()) {
             String attributeName = propertyAttribute.getKey();
             String attributeValue = propertyAttribute.getValue();
-            Map<String, String> attributes = attributesMap.get(attributeName);
-            if (attributes == null) {
-              attributes = new HashMap<>();
-              attributesMap.put(attributeName, attributes);
-            }
-            attributes.put(propertyName, attributeValue);
+            attributesMap
+              .computeIfAbsent(attributeName, k -> new HashMap<>())
+              .put(propertyName, attributeValue);
           }
         }
       }
@@ -450,24 +374,12 @@ public class Stack {
     return attributesMap;
   }
 
-  /**
-   * Get the service for the specified component.
-   *
-   * @param component  component name
-   *
-   * @return service name that contains tha specified component
-   */
+  @Override
   public String getServiceForComponent(String component) {
     return componentService.get(component);
   }
 
-  /**
-   * Get the names of the services which contains the specified components.
-   *
-   * @param components collection of components
-   *
-   * @return collection of services which contain the specified components
-   */
+  @Override
   public Collection<String> getServicesForComponents(Collection<String> components) {
     Set<String> services = new HashSet<>();
     for (String component : components) {
@@ -477,14 +389,11 @@ public class Stack {
     return services;
   }
 
-  /**
-   * Obtain the service name which corresponds to the specified configuration.
-   *
-   * @param config  configuration type
-   *
-   * @return name of service which corresponds to the specified configuration type
-   */
+  @Override
   public String getServiceForConfigType(String config) {
+    if (ConfigHelper.CLUSTER_ENV.equals(config)) { // for backwards compatibility
+      return null;
+    }
     for (Map.Entry<String, Map<String, Map<String, ConfigProperty>>> entry : serviceConfigurations.entrySet()) {
       Map<String, Map<String, ConfigProperty>> typeMap = entry.getValue();
       String serviceName = entry.getKey();
@@ -492,69 +401,53 @@ public class Stack {
         return serviceName;
       }
     }
-    throw new IllegalArgumentException(
-        "Specified configuration type is not associated with any service: " + config);
+    throw new IllegalArgumentException(formatMissingServiceForConfigType(config, getStackId().toString()));
   }
 
-  public List<String> getServicesForConfigType(String config) {
-    List<String> serviceNames = new ArrayList<>();
-    for (Map.Entry<String, Map<String, Map<String, ConfigProperty>>> entry : serviceConfigurations.entrySet()) {
-      Map<String, Map<String, ConfigProperty>> typeMap = entry.getValue();
-      String serviceName = entry.getKey();
-      if (typeMap.containsKey(config) && !getExcludedConfigurationTypes(serviceName).contains(config)) {
-        serviceNames.add(serviceName);
-      }
-    }
-    return serviceNames;
+  static String formatMissingServiceForConfigType(String config, String stackId) {
+    return String.format("Specified configuration type %s is not associated with any service in %s stack.", config, stackId);
   }
 
-  /**
-   * Return the dependencies specified for the given component.
-   *
-   * @param component  component to get dependency information for
-   *
-   * @return collection of dependency information for the specified component
-   */
-  //todo: full dependency graph
+  @Override
+  public Stream<String> getServicesForConfigType(String config) {
+    return serviceConfigurations.entrySet().stream()
+      .filter(e -> e.getValue().containsKey(config))
+      .filter(e -> !getExcludedConfigurationTypes(e.getKey()).contains(config))
+      .map(Map.Entry::getKey);
+  }
+
+  @Override
   public Collection<DependencyInfo> getDependenciesForComponent(String component) {
     return dependencies.containsKey(component) ? dependencies.get(component) :
         Collections.emptySet();
   }
 
-  /**
-   * Get the service, if any, that a component dependency is conditional on.
-   *
-   * @param dependency  dependency to get conditional service for
-   *
-   * @return conditional service for provided component or null if dependency
-   *         is not conditional on a service
-   */
+  @Override
   public String getConditionalServiceForDependency(DependencyInfo dependency) {
     return dependencyConditionalServiceMap.get(dependency);
   }
 
+  @Override
   public String getExternalComponentConfig(String component) {
     return dbDependencyInfo.get(component);
   }
 
-  /**
-   * Obtain the required cardinality for the specified component.
-   */
+  @Override
   public Cardinality getCardinality(String component) {
     return new Cardinality(cardinalityRequirements.get(component));
   }
 
-  /**
-   * Obtain auto-deploy information for the specified component.
-   */
+  @Override
   public AutoDeployInfo getAutoDeployInfo(String component) {
     return componentAutoDeployInfo.get(component);
   }
 
+  @Override
   public boolean isMasterComponent(String component) {
     return masterComponents.contains(component);
   }
 
+  @Override
   public Configuration getConfiguration(Collection<String> services) {
     Map<String, Map<String, Map<String, String>>> attributes = new HashMap<>();
     Map<String, Map<String, String>> properties = new HashMap<>();
@@ -562,12 +455,9 @@ public class Stack {
     for (String service : services) {
       Collection<String> serviceConfigTypes = getConfigurationTypes(service);
       for (String type : serviceConfigTypes) {
-        Map<String, String> typeProps = properties.get(type);
-        if (typeProps == null) {
-          typeProps = new HashMap<>();
-          properties.put(type, typeProps);
-        }
-        typeProps.putAll(getConfigurationProperties(service, type));
+        properties
+          .computeIfAbsent(type, k -> new HashMap<>())
+          .putAll(getConfigurationProperties(service, type));
 
         Map<String, Map<String, String>> stackTypeAttributes = getConfigurationAttributes(service, type);
         if (!stackTypeAttributes.isEmpty()) {
@@ -577,12 +467,9 @@ public class Stack {
           Map<String, Map<String, String>> typeAttributes = attributes.get(type);
           for (Map.Entry<String, Map<String, String>> attribute : stackTypeAttributes.entrySet()) {
             String attributeName = attribute.getKey();
-            Map<String, String> attributeProps = typeAttributes.get(attributeName);
-            if (attributeProps == null) {
-              attributeProps = new HashMap<>();
-              typeAttributes.put(attributeName, attributeProps);
-            }
-            attributeProps.putAll(attribute.getValue());
+            typeAttributes
+              .computeIfAbsent(attributeName, k -> new HashMap<>())
+              .putAll(attribute.getValue());
           }
         }
       }
@@ -590,18 +477,16 @@ public class Stack {
     return new Configuration(properties, attributes);
   }
 
-  public Configuration getConfiguration() {
+  @Override
+  public Configuration getConfiguration() { // TODO get rid of duplication between this and #getConfiguration(Collection<String>)
     Map<String, Map<String, Map<String, String>>> stackAttributes = new HashMap<>();
     Map<String, Map<String, String>> stackConfigs = new HashMap<>();
 
     for (String service : getServices()) {
       for (String type : getAllConfigurationTypes(service)) {
-        Map<String, String> typeProps = stackConfigs.get(type);
-        if (typeProps == null) {
-          typeProps = new HashMap<>();
-          stackConfigs.put(type, typeProps);
-        }
-        typeProps.putAll(getConfigurationProperties(service, type));
+        stackConfigs
+          .computeIfAbsent(type, k -> new HashMap<>())
+          .putAll(getConfigurationProperties(service, type));
 
         Map<String, Map<String, String>> stackTypeAttributes = getConfigurationAttributes(service, type);
         if (!stackTypeAttributes.isEmpty()) {
@@ -611,12 +496,9 @@ public class Stack {
           Map<String, Map<String, String>> typeAttrs = stackAttributes.get(type);
           for (Map.Entry<String, Map<String, String>> attribute : stackTypeAttributes.entrySet()) {
             String attributeName = attribute.getKey();
-            Map<String, String> attributes = typeAttrs.get(attributeName);
-            if (attributes == null) {
-              attributes = new HashMap<>();
-              typeAttrs.put(attributeName, attributes);
-            }
-            attributes.putAll(attribute.getValue());
+            typeAttrs
+              .computeIfAbsent(attributeName, k -> new HashMap<>())
+              .putAll(attribute.getValue());
           }
         }
       }
@@ -626,20 +508,16 @@ public class Stack {
 
   /**
    * Parse components for the specified service from the stack definition.
-   *
-   * @param service  service name
-   *
-   * @throws AmbariException an exception occurred getting components from the stack definition
    */
-  private void parseComponents(String service) throws AmbariException{
+  private void parseComponents(ServiceInfo serviceInfo) {
     Collection<String> componentSet = new HashSet<>();
 
-    Set<StackServiceComponentResponse> components = controller.getStackComponents(
-        Collections.singleton(new StackServiceComponentRequest(name, version, service, null)));
+    String service = serviceInfo.getName();
+    Collection<ComponentInfo> components = serviceInfo.getComponents();
 
     // stack service components
-    for (StackServiceComponentResponse component : components) {
-      String componentName = component.getComponentName();
+    for (ComponentInfo component : components) {
+      String componentName = component.getName();
       componentSet.add(componentName);
       componentService.put(componentName, service);
       String cardinality = component.getCardinality();
@@ -652,10 +530,7 @@ public class Stack {
       }
 
       // populate component dependencies
-      //todo: remove usage of AmbariMetaInfo
-      Collection<DependencyInfo> componentDependencies = controller.getAmbariMetaInfo().getComponentDependencies(
-          name, version, service, componentName);
-
+      Collection<DependencyInfo> componentDependencies = component.getDependencies();
       if (componentDependencies != null && ! componentDependencies.isEmpty()) {
         dependencies.put(componentName, componentDependencies);
       }
@@ -671,50 +546,40 @@ public class Stack {
    * Parse configurations for the specified service from the stack definition.
    *
    * @param stackService  service to parse the stack configuration for
-   *
-   * @throws AmbariException an exception occurred getting configurations from the stack definition
    */
-  private void parseConfigurations(StackServiceResponse stackService) throws AmbariException {
-    String service = stackService.getServiceName();
+  private void parseConfigurations(ServiceInfo stackService) {
+    String service = stackService.getName();
     Map<String, Map<String, ConfigProperty>> mapServiceConfig = new HashMap<>();
     Map<String, Map<String, ConfigProperty>> mapRequiredServiceConfig = new HashMap<>();
 
-
     serviceConfigurations.put(service, mapServiceConfig);
     requiredServiceConfigurations.put(service, mapRequiredServiceConfig);
 
-    Set<ReadOnlyConfigurationResponse> serviceConfigs = controller.getStackConfigurations(
-        Collections.singleton(new StackConfigurationRequest(name, version, service, null)));
-    Set<ReadOnlyConfigurationResponse> stackLevelConfigs = controller.getStackLevelConfigurations(
-        Collections.singleton(new StackLevelConfigurationRequest(name, version, null)));
+    Collection<PropertyInfo> serviceConfigs = stackService.getProperties();
+    Collection<PropertyInfo> stackLevelConfigs = stackInfo.getProperties();
     serviceConfigs.addAll(stackLevelConfigs);
 
     // shouldn't have any required properties in stack level configuration
-    for (ReadOnlyConfigurationResponse config : serviceConfigs) {
+    for (PropertyInfo pi : serviceConfigs) {
+      ReadOnlyConfigurationResponse config = pi.convertToResponse(); // TODO get rid of intermediate object
       ConfigProperty configProperty = new ConfigProperty(config);
       String type = configProperty.getType();
 
-      Map<String, ConfigProperty> mapTypeConfig = mapServiceConfig.get(type);
-      if (mapTypeConfig == null) {
-        mapTypeConfig = new HashMap<>();
-        mapServiceConfig.put(type, mapTypeConfig);
-      }
+      Map<String, ConfigProperty> mapTypeConfig = mapServiceConfig.computeIfAbsent(type, __ -> new HashMap<>());
 
-      mapTypeConfig.put(config.getPropertyName(), configProperty);
+      String name = config.getPropertyName();
+      mapTypeConfig.put(name, configProperty);
       if (config.isRequired()) {
-        Map<String, ConfigProperty> requiredTypeConfig = mapRequiredServiceConfig.get(type);
-        if (requiredTypeConfig == null) {
-          requiredTypeConfig = new HashMap<>();
-          mapRequiredServiceConfig.put(type, requiredTypeConfig);
-        }
-        requiredTypeConfig.put(config.getPropertyName(), configProperty);
+        mapRequiredServiceConfig
+          .computeIfAbsent(type, __ -> new HashMap<>())
+          .put(name, configProperty);
       }
     }
 
     // So far we added only config types that have properties defined
     // in stack service definition. Since there might be config types
     // with no properties defined we need to add those separately
-    Set<String> configTypes = stackService.getConfigTypes().keySet();
+    Set<String> configTypes = stackService.getConfigTypeAttributes().keySet();
     for (String configType: configTypes) {
       if (!mapServiceConfig.containsKey(configType)) {
         mapServiceConfig.put(configType, Collections.emptyMap());
@@ -722,23 +587,17 @@ public class Stack {
     }
   }
 
-  private void parseStackConfigurations () throws AmbariException {
-
-    Set<ReadOnlyConfigurationResponse> stackLevelConfigs = controller.getStackLevelConfigurations(
-        Collections.singleton(new StackLevelConfigurationRequest(name, version, null)));
+  private void parseStackConfigurations() {
+    Collection<PropertyInfo> stackLevelConfigs = stackInfo.getProperties();
 
-    for (ReadOnlyConfigurationResponse config : stackLevelConfigs) {
+    for (PropertyInfo pi : stackLevelConfigs) {
+      ReadOnlyConfigurationResponse config = pi.convertToResponse(); // TODO get rid of intermediate object
       ConfigProperty configProperty = new ConfigProperty(config);
       String type = configProperty.getType();
 
-      Map<String, ConfigProperty> mapTypeConfig = stackConfigurations.get(type);
-      if (mapTypeConfig == null) {
-        mapTypeConfig = new HashMap<>();
-        stackConfigurations.put(type, mapTypeConfig);
-      }
-
-      mapTypeConfig.put(config.getPropertyName(),
-          configProperty);
+      stackConfigurations
+        .computeIfAbsent(type, __ -> new HashMap<>())
+        .put(config.getPropertyName(), configProperty);
     }
   }
 
@@ -747,8 +606,8 @@ public class Stack {
    *
    * @param stackServiceResponse the response object associated with this stack service
    */
-  private void parseExcludedConfigurations(StackServiceResponse stackServiceResponse) {
-    excludedConfigurationTypes.put(stackServiceResponse.getServiceName(), stackServiceResponse.getExcludedConfigTypes());
+  private void parseExcludedConfigurations(ServiceInfo stackServiceResponse) {
+    excludedConfigurationTypes.put(stackServiceResponse.getName(), stackServiceResponse.getExcludedConfigTypes());
   }
 
   /**
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDefinition.java
new file mode 100644
index 0000000..25c4ede
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDefinition.java
@@ -0,0 +1,278 @@
+package org.apache.ambari.server.controller.internal;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.ambari.server.state.AutoDeployInfo;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.DependencyInfo;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.Cardinality;
+import org.apache.ambari.server.topology.Configuration;
+
+/**
+ * Encapsulates stack information.
+ */
+// TODO move to topology package
+public interface StackDefinition {
+
+  /**
+   * @return the IDs for the set of stacks that this stacks is (possibly) composed of.
+   */
+  Set<StackId> getStackIds();
+
+  /**
+   * @return the IDs of the set of stacks that the given service is defined in
+   */
+  Set<StackId> getStacksForService(String serviceName);
+
+  /**
+   * @return the IDs of the set of stacks that the given component is defined in
+   */
+  Set<StackId> getStacksForComponent(String componentName);
+
+  /**
+   * @return the names of services defined the given stack
+   */
+  Set<String> getServices(StackId stackId);
+
+  /**
+   * Get services contained in the stack.
+   *
+   * @return collection of all services for the stack
+   */
+  Collection<String> getServices();
+
+  /**
+   * Get components contained in the stack for the specified service.
+   *
+   * @param service  service name
+   *
+   * @return collection of component names for the specified service
+   */
+  Collection<String> getComponents(String service);
+
+  /**
+   * Get all service components
+   *
+   * @return collection of all components for the stack
+   */
+  Collection<String> getComponents();
+
+  /**
+   * Get info for the specified component.
+   *
+   * @param component  component name
+   *
+   * @return component information for the requested component
+   *         or null if the component doesn't exist in the stack
+   */
+  ComponentInfo getComponentInfo(String component);
+
+  /**
+   * Get all configuration types, including excluded types for the specified service.
+   *
+   * @param service  service name
+   *
+   * @return collection of all configuration types for the specified service
+   */
+  Collection<String> getAllConfigurationTypes(String service);
+
+  /**
+   * Get configuration types for the specified service.
+   * This doesn't include any service excluded types.
+   *
+   * @param service  service name
+   * @return collection of all configuration types for the specified service
+   */
+  Collection<String> getConfigurationTypes(String service);
+
+  /**
+   * Get the set of excluded configuration types for this service.
+   *
+   * @param service service name
+   * @return Set of names of excluded config types. Will not return null.
+   */
+  Set<String> getExcludedConfigurationTypes(String service);
+
+  /**
+   * Get config properties for the specified service and configuration type.
+   *
+   * @param service  service name
+   * @param type     configuration type
+   * @return map of property names to values for the specified service and configuration type
+   */
+  Map<String, String> getConfigurationProperties(String service, String type);
+
+  /**
+   * Get config properties with metadata attributes for the specified service and configuration type.
+   *
+   * @param service  service name
+   * @param type     configuration type
+   * @return map of property names to properties for the specified service and configuration type
+   */
+  Map<String, Stack.ConfigProperty> getConfigurationPropertiesWithMetadata(String service, String type);
+
+  /**
+   * Get all required config properties for the specified service.
+   *
+   * @param service  service name
+   * @return collection of all required properties for the given service
+   */
+  Collection<Stack.ConfigProperty> getRequiredConfigurationProperties(String service);
+
+  /**
+   * Get required config properties for the specified service which belong to the specified property type.
+   *
+   * @param service       service name
+   * @param propertyType  property type
+   *
+   * @return collection of required properties for the given service and property type
+   */
+  Collection<Stack.ConfigProperty> getRequiredConfigurationProperties(String service, PropertyInfo.PropertyType propertyType);
+
+  /**
+   * @return true if the given property for the specified service and config type is a password-type property
+   * @see org.apache.ambari.server.state.PropertyInfo.PropertyType#PASSWORD
+   */
+  boolean isPasswordProperty(String service, String type, String propertyName);
+
+  /**
+   * @return map of stack-level property names to properties for the specified configuration type
+   */
+  Map<String, String> getStackConfigurationProperties(String type);
+
+  /**
+   * @return true if the given property for the specified service and config type is a Kerberos principal-type property
+   * @see org.apache.ambari.server.state.PropertyInfo.PropertyType#KERBEROS_PRINCIPAL
+   */
+  boolean isKerberosPrincipalNameProperty(String service, String type, String propertyName);
+
+  /**
+   * Get config attributes for the specified service and configuration type.
+   *
+   * @param service  service name
+   * @param type     configuration type
+   *
+   * @return  map of attribute names to map of property names to attribute values
+   *          for the specified service and configuration type
+   */
+  Map<String, Map<String, String>> getConfigurationAttributes(String service, String type);
+
+  /**
+   * Get stack-level config attributes for the specified configuration type.
+   *
+   * @param type     configuration type
+   *
+   * @return  map of attribute names to map of property names to attribute values
+   *          for the specified configuration type
+   */
+  Map<String, Map<String, String>> getStackConfigurationAttributes(String type);
+
+  /**
+   * Get the service for the specified component.
+   *
+   * @param component  component name
+   *
+   * @return service name that contains tha specified component
+   */
+  String getServiceForComponent(String component);
+
+  /**
+   * Get the names of the services which contains the specified components.
+   *
+   * @param components collection of components
+   *
+   * @return collection of services which contain the specified components
+   */
+  Collection<String> getServicesForComponents(Collection<String> components);
+
+  /**
+   * Obtain the service name which corresponds to the specified configuration.
+   *
+   * @param config  configuration type
+   *
+   * @return name of service which corresponds to the specified configuration type
+   */
+  String getServiceForConfigType(String config);
+
+  /**
+   * @return stream of service names which correspond to the specified configuration type name
+   */
+  Stream<String> getServicesForConfigType(String config);
+
+  /**
+   * Return the dependencies specified for the given component.
+   *
+   * @param component  component to get dependency information for
+   *
+   * @return collection of dependency information for the specified component
+   */
+  //todo: full dependency graph
+  Collection<DependencyInfo> getDependenciesForComponent(String component);
+
+  /**
+   * Get the service, if any, that a component dependency is conditional on.
+   *
+   * @param dependency  dependency to get conditional service for
+   *
+   * @return conditional service for provided component or null if dependency
+   *         is not conditional on a service
+   */
+  String getConditionalServiceForDependency(DependencyInfo dependency);
+
+  /**
+   * Get the custom "descriptor" that is used to decide whether component
+   * is a managed or non-managed dependency.  The descriptor is formatted as:
+   * "config_type/property_name".  Currently it is only used for Hive Metastore's
+   * database.
+   *
+   * @param component component to get dependency information for
+   * @return the descriptor of form "config_type/property_name"
+   * @see org.apache.ambari.server.topology.BlueprintValidatorImpl#isDependencyManaged
+   */
+  String getExternalComponentConfig(String component);
+
+  /**
+   * Obtain the required cardinality for the specified component.
+   */
+  Cardinality getCardinality(String component);
+
+  /**
+   * Obtain auto-deploy information for the specified component.
+   */
+  AutoDeployInfo getAutoDeployInfo(String component);
+
+  /**
+   * @return true if the given component is a master component
+   */
+  boolean isMasterComponent(String component);
+
+  /**
+   * @return subset of the stack's configuration for the given services
+   */
+  Configuration getConfiguration(Collection<String> services);
+
+  /**
+   * @return the stack's configuration
+   */
+  Configuration getConfiguration();
+
+  /**
+   * Create a stack definition for one or more stacks.
+   * When given multiple stacks, it returns a composite stack,
+   * while a single stack is returned as is.
+   *
+   * @param stacks the stack(s) to combine
+   * @return composite or single stack
+   */
+  static StackDefinition of(Set<Stack> stacks) {
+    return stacks.size() > 1
+      ? new CompositeStack(stacks)
+      : stacks.stream().findAny().orElse(null);
+  }
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
index 8b7cb67..bb02b44 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UnitUpdater.java
@@ -69,18 +69,18 @@ public class UnitUpdater implements BlueprintConfigurationProcessor.PropertyUpda
     private static final String DEFAULT_UNIT = "m";
     private final String unit;
 
-    public static PropertyUnit of(Stack stack, UnitValidatedProperty property) {
+    public static PropertyUnit of(StackDefinition stack, UnitValidatedProperty property) {
       return PropertyUnit.of(stack, property.getServiceName(), property.getConfigType(), property.getPropertyName());
     }
 
-    public static PropertyUnit of(Stack stack, String serviceName, String configType, String propertyName) {
+    public static PropertyUnit of(StackDefinition stack, String serviceName, String configType, String propertyName) {
       return new PropertyUnit(
         stackUnit(stack, serviceName, configType, propertyName)
           .map(PropertyUnit::toJvmUnit)
           .orElse(DEFAULT_UNIT));
     }
 
-    private static Optional<String> stackUnit(Stack stack, String serviceName, String configType, String propertyName) {
+    private static Optional<String> stackUnit(StackDefinition stack, String serviceName, String configType, String propertyName) {
       try {
         return Optional.ofNullable(
           stack.getConfigurationPropertiesWithMetadata(serviceName, configType)
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/metadata/RoleCommandOrder.java b/ambari-server/src/main/java/org/apache/ambari/server/metadata/RoleCommandOrder.java
index 20edcfd..afa5a80 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/metadata/RoleCommandOrder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/metadata/RoleCommandOrder.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.metadata;
 
+import static java.util.stream.Collectors.toSet;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -30,8 +32,10 @@ import org.apache.ambari.server.RoleCommand;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.stageplanner.RoleGraphNode;
 import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.Service;
 import org.apache.ambari.server.state.ServiceComponent;
+import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.StackInfo;
 import org.slf4j.Logger;
@@ -137,6 +141,16 @@ public class RoleCommandOrder implements Cloneable {
     for (Service service : cluster.getServices().values()) {
       stackIds.add(service.getDesiredStackId());
     }
+    Set<String> components = cluster.getServices().values().stream()
+      .flatMap(s -> s.getServiceComponents().values().stream())
+      .map(ServiceComponent::getName)
+      .collect(toSet());
+    // FIXME ugly workaround
+    StackInfo hdp = ambariMetaInfo.getStacks().stream()
+      .filter(s -> "HDP".equals(s.getName()))
+      .filter(s -> s.getVersion().startsWith("3.0.0"))
+      .findAny()
+      .orElse(null);
 
     for (StackId stackId : stackIds) {
       StackInfo stack = null;
@@ -158,6 +172,26 @@ public class RoleCommandOrder implements Cloneable {
 
         addDependencies(section);
       }
+
+      // FIXME ugly workaround to make sure hdp-select can be installed in before-INSTALL hook
+      if (hdp != null && !"HDP".equals(stackId.getStackName())) {
+        for (ServiceInfo blockedService : stack.getServices()) {
+          for (ComponentInfo blockedComponent : blockedService.getComponents()) {
+            if (components.contains(blockedComponent.getName())) {
+              Role blockedRole = Role.valueOf(blockedComponent.getName());
+              for (ServiceInfo blockingService : hdp.getServices()) {
+                for (ComponentInfo blockingComponent : blockingService.getComponents()) {
+                  if (components.contains(blockedComponent.getName())) {
+                    Role blockerRole = Role.valueOf(blockingComponent.getName());
+                    addDependency(blockedRole, RoleCommand.INSTALL, blockerRole, RoleCommand.INSTALL, false);
+                    LOG.info("Added FIXME dependency {} -> {}", blockedRole, blockerRole);
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
     }
 
     extendTransitiveDependency();
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/BlueprintMpackInstanceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/BlueprintMpackInstanceEntity.java
index fb9ed7c..1d61523 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/BlueprintMpackInstanceEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/BlueprintMpackInstanceEntity.java
@@ -52,10 +52,10 @@ public class BlueprintMpackInstanceEntity {
   @Column(name = "id", nullable = false, updatable = false)
   private Long id;
 
-  @Column(name = "mpack_name")
+  @Column(name = "mpack_name", nullable = false)
   private String mpackName;
 
-  @Column(name = "mpack_version")
+  @Column(name = "mpack_version", nullable = false)
   private String mpackVersion;
 
   @Column(name = "mpack_uri")
@@ -68,7 +68,7 @@ public class BlueprintMpackInstanceEntity {
   private Collection<BlueprintMpackConfigEntity> configurations = new ArrayList<>();
 
   @ManyToOne
-  @JoinColumn(name = "mpack_id", referencedColumnName = "id", nullable = true)
+  @JoinColumn(name = "mpack_id", referencedColumnName = "id")
   private MpackEntity mpackEntity;
 
   @ManyToOne
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/NoSuchStackException.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/NoSuchStackException.java
index 47000db..18f4e5c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/NoSuchStackException.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/NoSuchStackException.java
@@ -18,10 +18,15 @@
 
 package org.apache.ambari.server.stack;
 
+import org.apache.ambari.server.state.StackId;
+
 /**
- * Indicates that the requested Stack doesn't esist.
+ * Indicates that the requested Stack doesn't exist.
  */
-public class NoSuchStackException extends Exception {
+public class NoSuchStackException extends IllegalArgumentException {
+  public NoSuchStackException(StackId stackId) {
+    this(stackId.getStackName(), stackId.getStackVersion());
+  }
   public NoSuchStackException(String stackName, String stackVersion) {
     super(String.format("The requested stack doesn't exist. Name='%s' Version='%s'", stackName, stackVersion));
   }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/AmbariContext.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/AmbariContext.java
index 37840f6..f4058a1 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/AmbariContext.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/AmbariContext.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.topology;
 
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toSet;
 
 import java.util.Collection;
@@ -67,7 +68,7 @@ import org.apache.ambari.server.controller.internal.ServiceDependencyResourcePro
 import org.apache.ambari.server.controller.internal.ServiceGroupDependencyResourceProvider;
 import org.apache.ambari.server.controller.internal.ServiceGroupResourceProvider;
 import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.controller.internal.VersionDefinitionResourceProvider;
 import org.apache.ambari.server.controller.predicate.EqualsPredicate;
 import org.apache.ambari.server.controller.spi.ClusterController;
@@ -91,12 +92,11 @@ import org.apache.ambari.server.state.SecurityType;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.configgroup.ConfigGroup;
 import org.apache.ambari.server.utils.RetryHelper;
-import org.apache.commons.lang.StringUtils;
+import org.apache.directory.api.util.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Striped;
@@ -213,97 +213,114 @@ public class AmbariContext {
 
   public void createAmbariResources(ClusterTopology topology, String clusterName, SecurityType securityType,
                                     String repoVersionString, Long repoVersionId) {
-    Stack stack = topology.getBlueprint().getStack();
-    StackId stackId = new StackId(stack.getName(), stack.getVersion());
+    Map<StackId, Long> repoVersionByStack = new HashMap<>();
 
-    RepositoryVersionEntity repoVersion = null;
-    if (StringUtils.isEmpty(repoVersionString) && null == repoVersionId) {
-      List<RepositoryVersionEntity> stackRepoVersions = repositoryVersionDAO.findByStack(stackId);
+    Set<StackId> stackIds = topology.getBlueprint().getStackIds();
+    for (StackId stackId : stackIds) {
+      RepositoryVersionEntity repoVersion = null;
+      if (stackIds.size() == 1) {
+        repoVersion = findSpecifiedRepo(repoVersionString, repoVersionId, stackId);
+      }
+      if (null == repoVersion) {
+        repoVersion = findRepoForStack(stackId);
+      }
+      Preconditions.checkNotNull(repoVersion);
+      // only use a STANDARD repo when creating a new cluster
+      if (repoVersion.getType() != RepositoryType.STANDARD) {
+        throw new IllegalArgumentException(String.format(
+          "Unable to create a cluster using the following repository since it is not a STANDARD type: %s",
+          repoVersion
+        ));
+      }
+    }
 
-      if (stackRepoVersions.isEmpty()) {
-        // !!! no repos, try to get the version for the stack
-        VersionDefinitionResourceProvider vdfProvider = getVersionDefinitionResourceProvider();
+    StackId stackId = Iterables.getFirst(topology.getBlueprint().getStackIds(), null);
+    createAmbariClusterResource(clusterName, stackId, securityType);
+    createAmbariServiceAndComponentResources(topology, clusterName, repoVersionByStack);
+  }
 
-        Map<String, Object> properties = new HashMap<>();
-        properties.put(VersionDefinitionResourceProvider.VERSION_DEF_AVAILABLE_DEFINITION, stackId.toString());
+  private RepositoryVersionEntity findRepoForStack(StackId stackId) {
+    RepositoryVersionEntity repoVersion;
+    List<RepositoryVersionEntity> stackRepoVersions = repositoryVersionDAO.findByStack(stackId);
+    if (stackRepoVersions.isEmpty()) {
+      // !!! no repos, try to get the version for the stack
+      VersionDefinitionResourceProvider vdfProvider = getVersionDefinitionResourceProvider();
 
-        Request request = new RequestImpl(Collections.<String>emptySet(),
-            Collections.singleton(properties), Collections.<String, String>emptyMap(), null);
+      Map<String, Object> properties = new HashMap<>();
+      properties.put(VersionDefinitionResourceProvider.VERSION_DEF_AVAILABLE_DEFINITION, stackId.toString());
 
-        Long defaultRepoVersionId = null;
+      Request request = new RequestImpl(Collections.emptySet(),
+        Collections.singleton(properties), Collections.emptyMap(), null
+      );
 
-        try {
-          RequestStatus requestStatus = vdfProvider.createResources(request);
-          if (!requestStatus.getAssociatedResources().isEmpty()) {
-            Resource resource = requestStatus.getAssociatedResources().iterator().next();
-            defaultRepoVersionId = (Long) resource.getPropertyValue(VersionDefinitionResourceProvider.VERSION_DEF_ID);
-          }
-        } catch (Exception e) {
-          throw new IllegalArgumentException(String.format(
-              "Failed to create a default repository version definition for stack %s. "
-              + "This typically is a result of not loading the stack correctly or being able "
-              + "to load information about released versions.  Create a repository version "
-              + " and try again.", stackId), e);
-        }
+      Long defaultRepoVersionId = null;
 
-        repoVersion = repositoryVersionDAO.findByPK(defaultRepoVersionId);
-        // !!! better not!
-        if (null == repoVersion) {
-          throw new IllegalArgumentException(String.format(
-              "Failed to load the default repository version definition for stack %s. "
-              + "Check for a valid repository version and try again.", stackId));
+      try {
+        RequestStatus requestStatus = vdfProvider.createResources(request);
+        if (!requestStatus.getAssociatedResources().isEmpty()) {
+          Resource resource = requestStatus.getAssociatedResources().iterator().next();
+          defaultRepoVersionId = (Long) resource.getPropertyValue(VersionDefinitionResourceProvider.VERSION_DEF_ID);
         }
+      } catch (Exception e) {
+        throw new IllegalArgumentException(String.format(
+          "Failed to create a default repository version definition for stack %s. "
+            + "This typically is a result of not loading the stack correctly or being able "
+            + "to load information about released versions.  Create a repository version "
+            + " and try again.", stackId), e);
+      }
 
-      } else if (stackRepoVersions.size() > 1) {
+      repoVersion = repositoryVersionDAO.findByPK(defaultRepoVersionId);
+      // !!! better not!
+      if (null == repoVersion) {
+        throw new IllegalArgumentException(String.format(
+          "Failed to load the default repository version definition for stack %s. "
+            + "Check for a valid repository version and try again.", stackId));
+      }
 
-        Function<RepositoryVersionEntity, String> function = new Function<RepositoryVersionEntity, String>() {
-          @Override
-          public String apply(RepositoryVersionEntity input) {
-            return input.getVersion();
-          }
-        };
+    } else if (stackRepoVersions.size() > 1) {
+      String versions = stackRepoVersions.stream()
+        .map(RepositoryVersionEntity::getVersion)
+        .collect(joining(", "));
 
-        Collection<String> versions = Collections2.transform(stackRepoVersions, function);
+      throw new IllegalArgumentException(String.format(
+        "Several repositories were found for %s:  %s.  Specify the version with '%s'",
+        stackId, versions, ProvisionClusterRequest.REPO_VERSION_PROPERTY
+      ));
+    } else {
+      repoVersion = stackRepoVersions.get(0);
+      LOG.info("Found single matching repository version {} for stack {}", repoVersion.getVersion(), stackId);
+    }
+    return repoVersion;
+  }
 
-        throw new IllegalArgumentException(String.format("Several repositories were found for %s:  %s.  Specify the version"
-            + " with '%s'", stackId, StringUtils.join(versions, ", "), ProvisionClusterRequest.REPO_VERSION_PROPERTY));
-      } else {
-        repoVersion = stackRepoVersions.get(0);
-        LOG.warn("Cluster is being provisioned using the single matching repository version {}", repoVersion.getVersion());
-      }
-    } else if (null != repoVersionId){
+  private RepositoryVersionEntity findSpecifiedRepo(String repoVersionString, Long repoVersionId, StackId stackId) {
+    RepositoryVersionEntity repoVersion = null;
+    if (null != repoVersionId) {
       repoVersion = repositoryVersionDAO.findByPK(repoVersionId);
 
       if (null == repoVersion) {
         throw new IllegalArgumentException(String.format(
           "Could not identify repository version with repository version id %s for installing services. "
             + "Specify a valid repository version id with '%s'",
-          repoVersionId, ProvisionClusterRequest.REPO_VERSION_ID_PROPERTY));
+          repoVersionId, ProvisionClusterRequest.REPO_VERSION_ID_PROPERTY
+        ));
       }
-    } else {
+    } else if (Strings.isNotEmpty(repoVersionString)) {
       repoVersion = repositoryVersionDAO.findByStackAndVersion(stackId, repoVersionString);
 
       if (null == repoVersion) {
         throw new IllegalArgumentException(String.format(
           "Could not identify repository version with stack %s and version %s for installing services. "
             + "Specify a valid version with '%s'",
-          stackId, repoVersionString, ProvisionClusterRequest.REPO_VERSION_PROPERTY));
+          stackId, repoVersionString, ProvisionClusterRequest.REPO_VERSION_PROPERTY
+        ));
       }
     }
-
-    // only use a STANDARD repo when creating a new cluster
-    if (repoVersion.getType() != RepositoryType.STANDARD) {
-      throw new IllegalArgumentException(String.format(
-          "Unable to create a cluster using the following repository since it is not a STANDARD type: %s",
-          repoVersion));
-    }
-
-    createAmbariClusterResource(clusterName, stack.getName(), stack.getVersion(), securityType);
-    createAmbariServiceAndComponentResources(topology, clusterName, stackId, repoVersion.getId());
+    return repoVersion;
   }
 
-  public void createAmbariClusterResource(String clusterName, String stackName, String stackVersion, SecurityType securityType) {
-    String stackInfo = String.format("%s-%s", stackName, stackVersion);
+  public void createAmbariClusterResource(String clusterName, StackId stackId, SecurityType securityType) {
+    String stackInfo = stackId.toString();
     final ClusterRequest clusterRequest = new ClusterRequest(null, clusterName, null, securityType, stackInfo, null);
 
     try {
@@ -325,9 +342,7 @@ public class AmbariContext {
     }
   }
 
-  public void createAmbariServiceAndComponentResources(ClusterTopology topology, String clusterName,
-      StackId stackId, Long repositoryVersionId) {
-
+  public void createAmbariServiceAndComponentResources(ClusterTopology topology, String clusterName, Map<StackId, Long> repoVersionByStack) {
     Set<String> serviceGroups = Sets.newHashSet(DEFAULT_SERVICE_GROUP_NAME);
     Collection<String> services = topology.getBlueprint().getServices();
 
@@ -347,8 +362,11 @@ public class AmbariContext {
     Set<ServiceComponentRequest> componentRequests = new HashSet<>();
     for (String service : services) {
       String credentialStoreEnabled = topology.getBlueprint().getCredentialStoreEnabled(service);
+      StackId stackId = Iterables.getOnlyElement(topology.getBlueprint().getStackIdsForService(service)); // FIXME temporarily assume each service is defined in only one mpack
+      Long repositoryVersionId = repoVersionByStack.get(stackId);
       serviceRequests.add(new ServiceRequest(clusterName, DEFAULT_SERVICE_GROUP_NAME, service, service,
-              repositoryVersionId, null, credentialStoreEnabled, null));
+        repositoryVersionId, null, credentialStoreEnabled, stackId
+      ));
 
       for (String component : topology.getBlueprint().getComponentNames(service)) {
         String recoveryEnabled = topology.getBlueprint().getRecoveryEnabled(service, component);
@@ -522,11 +540,10 @@ public class AmbariContext {
    * installed and started and that the monitoring screen for the cluster should be displayed to the user.
    *
    * @param clusterName  cluster name
-   * @param stackName    stack name
-   * @param stackVersion stack version
+   * @param stackId    stack ID
    */
-  public void persistInstallStateForUI(String clusterName, String stackName, String stackVersion) {
-    String stackInfo = String.format("%s-%s", stackName, stackVersion);
+  public void persistInstallStateForUI(String clusterName, StackId stackId) {
+    String stackInfo = stackId.toString();
     final ClusterRequest clusterRequest = new ClusterRequest(null, clusterName, "INSTALLED", null, stackInfo, null);
 
     try {
@@ -743,7 +760,7 @@ public class AmbariContext {
    */
   private void createConfigGroupsAndRegisterHost(ClusterTopology topology, String groupName) throws AmbariException {
     Map<String, Map<String, Config>> groupConfigs = new HashMap<>();
-    Stack stack = topology.getBlueprint().getStack();
+    StackDefinition stack = topology.getBlueprint().getStack();
 
     // get the host-group config with cluster creation template overrides
     Configuration topologyHostGroupConfig = topology.
@@ -756,11 +773,12 @@ public class AmbariContext {
     // iterate over topo host group configs which were defined in
     for (Map.Entry<String, Map<String, String>> entry : userProvidedGroupProperties.entrySet()) {
       String type = entry.getKey();
-      List<String> services = stack.getServicesForConfigType(type);
-      String service = services.stream()
+      String service = stack.getServicesForConfigType(type)
         .filter(each -> topology.getBlueprint().getServices().contains(each))
         .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException("Specified configuration type is not associated with any service: " + type));
+        // TODO check if this is required at all (might be handled by the "orphan" removal)
+        // TODO move this validation earlier
+        .orElseThrow(() -> new IllegalArgumentException("Specified configuration type is not associated with any service in the blueprint: " + type));
 
       Config config = configFactory.createReadOnly(type, groupName, entry.getValue(), null);
       //todo: attributes
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/Blueprint.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/Blueprint.java
index 64d2ae3..04e004e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/Blueprint.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/Blueprint.java
@@ -21,9 +21,11 @@ package org.apache.ambari.server.topology;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
+import org.apache.ambari.server.state.StackId;
 
 /**
  * Blueprint representation.
@@ -39,15 +41,15 @@ public interface Blueprint {
 
   /**
    * Get the hot groups contained in the blueprint.
+   *
    * @return map of host group name to host group
    */
   Map<String, HostGroup> getHostGroups();
 
   /**
-   * Get a hostgroup specified by name.
+   * Get a host group specified by name.
    *
    * @param name  name of the host group to get
-   *
    * @return the host group with the given name or null
    */
   HostGroup getHostGroup(String name);
@@ -124,19 +126,26 @@ public interface Blueprint {
 
   /**
    * Get the stack associated with the blueprint.
+   * For mpack-based installation this is a composite stack
+   * that provides a single unified view of all underlying mpacks,
+   * but does not have any identifier.
    *
    * @return associated stack
    */
-  @Deprecated
-  Stack getStack();
+  StackDefinition getStack();
 
   /**
-   * Get the stacks associated with the blueprint.
-   *
-   * @return associated stacks
+   * @return the set of stack (mpack) IDs associated with the blueprint
    */
-  Collection<Stack> getStacks();
+  Set<StackId> getStackIds();
 
+  /**
+   * Look up the stacks that define <code>service</code>.
+   * To be used only after checking that services map to
+   * @param service the name of the service as defined in the stack (mpack), eg. ZOOKEEPER
+   * @return the ID of the stack that defines the given service
+   */
+  Set<StackId> getStackIdsForService(String service);
 
   /**
    * Get the mpacks associated with the blueprint.
@@ -167,24 +176,7 @@ public interface Blueprint {
   SecurityConfiguration getSecurity();
 
   /**
-   * Validate the blueprint topology.
-   *
-   * @throws InvalidTopologyException if the topology is invalid
-   */
-  void validateTopology() throws InvalidTopologyException;
-
-  /**
-   * Validate that the blueprint contains all of the required properties.
-   *
-   * @throws InvalidTopologyException if the blueprint doesn't contain all required properties
-   */
-  void validateRequiredProperties() throws InvalidTopologyException, GPLLicenseNotAcceptedException;
-
-  /**
-   *
    * A config type is valid if there are services related to except cluster-env and global.
-   * @param configType
-   * @return
    */
   boolean isValidConfigType(String configType);
 
@@ -197,14 +189,4 @@ public interface Blueprint {
 
   List<RepositorySetting> getRepositorySettings();
 
-  /**
-   * @return a boolean indicating if all mpack referenced by the blueprints are resolved (installed on the system)
-   */
-  boolean isAllMpacksResolved();
-
-  /**
-   * @return a collection of the names unresolved mpacks in {name}-{version} format.
-   */
-  Collection<String> getUnresolvedMpackNames();
-
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintFactory.java
index 48f41a6..4386ec6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintFactory.java
@@ -10,8 +10,7 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distribut
- * ed on an "AS IS" BASIS,
+ * 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.
@@ -19,6 +18,24 @@
 
 package org.apache.ambari.server.topology;
 
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.BLUEPRINT_NAME_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.COMPONENT_MPACK_INSTANCE_PROPERTY;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.COMPONENT_NAME_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.COMPONENT_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.COMPONENT_PROVISION_ACTION_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.COMPONENT_SERVICE_INSTANCE_PROPERTY;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.CONFIGURATION_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.HOST_GROUP_CARDINALITY_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.HOST_GROUP_NAME_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.HOST_GROUP_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.MPACK_INSTANCES_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.SETTING_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.STACK_NAME_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.STACK_VERSION_PROPERTY_ID;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -27,6 +44,9 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ObjectNotFoundException;
@@ -35,16 +55,19 @@ import org.apache.ambari.server.controller.AmbariServer;
 import org.apache.ambari.server.controller.RootComponent;
 import org.apache.ambari.server.controller.internal.ProvisionAction;
 import org.apache.ambari.server.controller.internal.Stack;
-import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.orm.dao.BlueprintDAO;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
 import org.apache.ambari.server.stack.NoSuchStackException;
+import org.apache.ambari.server.state.StackId;
+import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Joiner;
 import com.google.inject.Inject;
 
 /**
@@ -54,38 +77,9 @@ public class BlueprintFactory {
 
   private static final Logger LOG = LoggerFactory.getLogger(BlueprintFactory.class);
 
-  // Blueprints
-  protected static final String BLUEPRINT_NAME_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "blueprint_name");
-  protected static final String STACK_NAME_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "stack_name");
-  protected static final String STACK_VERSION_PROPERTY_ID =
-      PropertyHelper.getPropertyId("Blueprints", "stack_version");
-
-  // Host Groups
-  protected static final String HOST_GROUP_PROPERTY_ID = "host_groups";
-  protected static final String HOST_GROUP_NAME_PROPERTY_ID = "name";
-  protected static final String HOST_GROUP_CARDINALITY_PROPERTY_ID = "cardinality";
-
-  // Host Group Components
-  protected static final String COMPONENT_PROPERTY_ID ="components";
-  protected static final String COMPONENT_NAME_PROPERTY_ID ="name";
-  protected static final String COMPONENT_PROVISION_ACTION_PROPERTY_ID = "provision_action";
-
-  // Configurations
-  protected static final String CONFIGURATION_PROPERTY_ID = "configurations";
-  protected static final String PROPERTIES_PROPERTY_ID = "properties";
-  protected static final String PROPERTIES_ATTRIBUTES_PROPERTY_ID = "properties_attributes";
-
-  protected static final String SETTINGS_PROPERTY_ID = "settings";
-
-  protected static final String MPACK_INSTANCES_PROPERTY = "mpack_instances";
-  protected static final String MPACK_INSTANCE_PROPERTY = "mpack_instance";
-  protected static final String SERVICE_INSTANCE_PROPERTY = "service_instance";
-
   private static BlueprintDAO blueprintDAO;
-  private ConfigurationFactory configFactory = new ConfigurationFactory();
 
+  private final ConfigurationFactory configFactory = new ConfigurationFactory();
   private final StackFactory stackFactory;
 
   public BlueprintFactory() {
@@ -98,8 +92,14 @@ public class BlueprintFactory {
 
   public Blueprint getBlueprint(String blueprintName) throws NoSuchStackException {
     BlueprintEntity entity = blueprintDAO.findByName(blueprintName);
-    //todo: just return null?
-    return entity == null ? null : new BlueprintImpl(entity);
+    if (entity != null) {
+      Set<StackId> stackIds = entity.getMpackInstances().stream()
+        .map(m -> new StackId(m.getMpackName(), m.getMpackVersion()))
+        .collect(toSet());
+      StackDefinition stack = composeStacks(stackIds);
+      return new BlueprintImpl(entity, stack, stackIds);
+    }
+    return null;
   }
 
   /**
@@ -118,42 +118,31 @@ public class BlueprintFactory {
       throw new IllegalArgumentException("Blueprint name must be provided");
     }
 
-    Stack stack;
     Collection<MpackInstance> mpackInstances = createMpackInstances(properties);
     if (mpackInstances.isEmpty()) {
-      stack = createStack(properties);
+      StackId stackId = getStackId(properties);
+      mpackInstances = Collections.singleton(new MpackInstance(stackId.getStackName(), stackId.getStackVersion(), null, null, Configuration.createEmpty()));
     }
-    else {
-      stack = mpackInstances.iterator().next().getStack();
-    }
-
+    Set<StackId> stackIds = mpackInstances.stream()
+        .map(m -> new StackId(m.getMpackName(), m.getMpackVersion()))
+        .collect(toSet());
+    StackDefinition stack = composeStacks(stackIds);
     Collection<HostGroup> hostGroups = processHostGroups(name, stack, properties);
     Configuration configuration = configFactory.getConfiguration((Collection<Map<String, String>>)
             properties.get(CONFIGURATION_PROPERTY_ID));
-    Setting setting = SettingFactory.getSetting((Collection<Map<String, Object>>) properties.get(SETTINGS_PROPERTY_ID));
+    Setting setting = SettingFactory.getSetting((Collection<Map<String, Object>>) properties.get(SETTING_PROPERTY_ID));
 
-    if (!mpackInstances.isEmpty()) {
-      return new BlueprintImpl(name, hostGroups, mpackInstances, configuration, securityConfiguration, setting);
-    }
-    else {
-      // Legacy constructor for old blueprints without mpacks
-      return new BlueprintImpl(name, hostGroups, stack, configuration, securityConfiguration, setting);
-    }
+    return new BlueprintImpl(name, hostGroups, stack, stackIds, mpackInstances, configuration, securityConfiguration, setting);
   }
 
   private Collection<MpackInstance> createMpackInstances(Map<String, Object> properties) throws NoSuchStackException {
-    if (properties.containsKey(MPACK_INSTANCES_PROPERTY)) {
+    if (properties.containsKey(MPACK_INSTANCES_PROPERTY_ID)) {
       ObjectMapper mapper = new ObjectMapper();
       mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
       try {
-        String mpackInstancesJson = mapper.writeValueAsString(properties.get(MPACK_INSTANCES_PROPERTY));
-          Collection<MpackInstance> mpacks = mapper.readValue(mpackInstancesJson, new TypeReference<Collection<MpackInstance>>(){});
-          for (MpackInstance mpack: mpacks) {
-            tryResolveStack(mpack);
-          }
-          return mpacks;
-      }
-      catch (IOException ex) {
+        String mpackInstancesJson = mapper.writeValueAsString(properties.get(MPACK_INSTANCES_PROPERTY_ID));
+        return mapper.readValue(mpackInstancesJson, new TypeReference<Collection<MpackInstance>>(){});
+      } catch (IOException ex) {
         throw new RuntimeException("Unable to parse mpack instances for blueprint: " +
           String.valueOf(properties.get(BLUEPRINT_NAME_PROPERTY_ID)), ex);
       }
@@ -162,39 +151,64 @@ public class BlueprintFactory {
     }
   }
 
-  protected void tryResolveStack(MpackInstance mpack) {
-    try {
-      Stack stack = loadStack(mpack.getMpackName(), mpack.getMpackVersion());
-      mpack.setStack(stack);
-    }
-    catch (NoSuchStackException ex) {
-      // This case can be normal if a blueprint had been sent in before the referenced mpack was installed
-      LOG.warn("Cannot resolve stack for mpack {}-{}. Is mpack installed?", mpack.getMpackName(), mpack.getMpackVersion());
-    }
-  }
-
-  protected Stack createStack(Map<String, Object> properties) throws NoSuchStackException {
+  private static StackId getStackId(Map<String, Object> properties) throws NoSuchStackException {
     String stackName = String.valueOf(properties.get(STACK_NAME_PROPERTY_ID));
     String stackVersion = String.valueOf(properties.get(STACK_VERSION_PROPERTY_ID));
-    return loadStack(stackName, stackVersion);
+    return new StackId(stackName, stackVersion);
+  }
+
+  private StackDefinition composeStacks(Set<StackId> stackIds) {
+    Set<Stack> stacks = stackIds.stream()
+      .map(this::createStack)
+      .collect(toSet());
+    StackDefinition composite = StackDefinition.of(stacks);
+
+    // temporary check
+    verifyStackDefinitionsAreDisjoint(composite.getServices().stream(), "Service", composite::getStacksForService);
+    verifyStackDefinitionsAreDisjoint(composite.getComponents().stream(), "Component", composite::getStacksForComponent);
+
+    return composite;
   }
 
-  protected Stack loadStack(String stackName, String stackVersion) throws NoSuchStackException {
+  /**
+   * Verify that each item in <code>items</code> is defined by only one stack.
+   *
+   * @param items the items to check
+   * @param type string description of the type of items (eg. "Service", or "Component")
+   * @param lookup a function to find the set of stacks that an item belongs to
+   * @throws IllegalArgumentException if some items are defined in multiple stacks
+   */
+  static void verifyStackDefinitionsAreDisjoint(Stream<String> items, String type, Function<String, Set<StackId>> lookup) {
+    Set<Pair<String, Set<StackId>>> definedInMultipleStacks = items
+      .map(s -> Pair.of(s, lookup.apply(s)))
+      .filter(p -> p.getRight().size() > 1)
+      .collect(toCollection(TreeSet::new));
+
+    if (!definedInMultipleStacks.isEmpty()) {
+      String msg = definedInMultipleStacks.stream()
+        .map(p -> String.format("%s %s is defined in multiple stacks: %s", type, p.getLeft(), Joiner.on(", ").join(p.getRight())))
+        .collect(joining("\n"));
+      LOG.error(msg);
+      throw new IllegalArgumentException(msg);
+    }
+  }
+
+  protected Stack createStack(StackId stackId) {
     try {
       //todo: don't pass in controller
-      return stackFactory.createStack(stackName, stackVersion, AmbariServer.getController());
+      return stackFactory.createStack(stackId, AmbariServer.getController());
     } catch (ObjectNotFoundException e) {
-      throw new NoSuchStackException(stackName, stackVersion);
+      throw new NoSuchStackException(stackId);
     } catch (AmbariException e) {
       // todo
       throw new RuntimeException(
-        String.format("An error occurred parsing the stack information for %s-%s", stackName, stackVersion) , e);
+        String.format("An error occurred parsing the stack information for %s", stackId) , e);
     }
   }
 
   //todo: Move logic to HostGroupImpl
   @SuppressWarnings("unchecked")
-  private Collection<HostGroup> processHostGroups(String bpName, Stack stack, Map<String, Object> properties) {
+  private Collection<HostGroup> processHostGroups(String bpName, StackDefinition stack, Map<String, Object> properties) {
     Set<HashMap<String, Object>> hostGroupProps = (HashSet<HashMap<String, Object>>)
         properties.get(HOST_GROUP_PROPERTY_ID);
 
@@ -219,14 +233,14 @@ public class BlueprintFactory {
       Configuration configuration = configFactory.getConfiguration(configProps);
       String cardinality = String.valueOf(hostGroupProperties.get(HOST_GROUP_CARDINALITY_PROPERTY_ID));
 
-      HostGroup group = new HostGroupImpl(hostGroupName, bpName, Collections.singleton(stack), components, configuration, cardinality);
+      HostGroup group = new HostGroupImpl(hostGroupName, bpName, stack, components, configuration, cardinality);
 
       hostGroups.add(group);
     }
     return hostGroups;
   }
 
-  private Collection<Component> processHostGroupComponents(Stack stack, String groupName, HashSet<HashMap<String, String>>  componentProps) {
+  private Collection<Component> processHostGroupComponents(StackDefinition stack, String groupName, HashSet<HashMap<String, String>>  componentProps) {
     if (componentProps == null || componentProps.isEmpty()) {
       throw new IllegalArgumentException("Host group '" + groupName + "' must contain at least one component");
     }
@@ -246,8 +260,8 @@ public class BlueprintFactory {
             groupName + "' is not valid for the specified stack");
       }
 
-      String mpackInstance = componentProperties.get(MPACK_INSTANCE_PROPERTY);
-      String serviceInstance = componentProperties.get(SERVICE_INSTANCE_PROPERTY);
+      String mpackInstance = componentProperties.get(COMPONENT_MPACK_INSTANCE_PROPERTY);
+      String serviceInstance = componentProperties.get(COMPONENT_SERVICE_INSTANCE_PROPERTY);
       //TODO, might want to add some validation here, to only accept value enum types, rwn
       ProvisionAction provisionAction = componentProperties.containsKey(COMPONENT_PROVISION_ACTION_PROPERTY_ID) ?
         ProvisionAction.valueOf(componentProperties.get(COMPONENT_PROVISION_ACTION_PROPERTY_ID)) : null;
@@ -263,12 +277,10 @@ public class BlueprintFactory {
    * @return collection of component names for the specified stack
    * @throws IllegalArgumentException if the specified stack doesn't exist
    */
-  private Collection<String> getAllStackComponents(Stack stack) {
-    Collection<String> allComponents = new HashSet<>();
-    for (Collection<String> components: stack.getComponents().values()) {
-      allComponents.addAll(components);
-    }
-    // currently ambari server is no a recognized component
+  private Collection<String> getAllStackComponents(StackDefinition stack) {
+    Collection<String> allComponents = new HashSet<>(stack.getComponents());
+
+    // currently ambari server is not a recognized component
     allComponents.add(RootComponent.AMBARI_SERVER.name());
 
     return allComponents;
@@ -292,7 +304,7 @@ public class BlueprintFactory {
    * simulate various Stack or error conditions.
    */
   interface StackFactory {
-    Stack createStack(String stackName, String stackVersion, AmbariManagementController managementController) throws AmbariException;
+    Stack createStack(StackId stackId, AmbariManagementController managementController) throws AmbariException;
   }
 
   /**
@@ -303,8 +315,8 @@ public class BlueprintFactory {
    */
   private static class DefaultStackFactory implements StackFactory {
     @Override
-    public Stack createStack(String stackName, String stackVersion, AmbariManagementController managementController) throws AmbariException {
-      return new Stack(stackName, stackVersion, managementController);
+    public Stack createStack(StackId stackId, AmbariManagementController managementController) throws AmbariException {
+      return new Stack(stackId.getStackName(), stackId.getStackVersion(), managementController);
     }
   }
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintImpl.java
index 24e5d36..04616a7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintImpl.java
@@ -29,13 +29,10 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 
-import org.apache.ambari.server.AmbariException;
-import org.apache.ambari.server.StackAccessException;
-import org.apache.ambari.server.controller.AmbariServer;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.orm.entities.BlueprintConfigEntity;
 import org.apache.ambari.server.orm.entities.BlueprintConfiguration;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
@@ -47,13 +44,13 @@ import org.apache.ambari.server.orm.entities.HostGroupConfigEntity;
 import org.apache.ambari.server.orm.entities.HostGroupEntity;
 import org.apache.ambari.server.stack.NoSuchStackException;
 import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.utils.JsonUtils;
 import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
-import com.google.common.base.Supplier;
 import com.google.gson.Gson;
 
 /**
@@ -61,81 +58,71 @@ import com.google.gson.Gson;
  */
 public class BlueprintImpl implements Blueprint {
 
-  private static final Logger LOG = LoggerFactory.getLogger(BlueprintImpl.class);
-
-  private String name;
-  private Map<String, HostGroup> hostGroups = new HashMap<>();
-  private Collection<MpackInstance> mpacks = new ArrayList<>();
-  private Configuration configuration;
-  private BlueprintValidator validator;
-  private SecurityConfiguration security;
-  private Setting setting;
-  private List<RepositorySetting> repoSettings = new ArrayList<>();
-  private boolean allMpacksResolved = false;
-
-  public BlueprintImpl(BlueprintEntity entity) throws NoSuchStackException {
-    this.name = entity.getBlueprintName();
-    if (entity.getSecurityType() != null) {
-      this.security = new SecurityConfiguration(entity.getSecurityType(),
-        entity.getSecurityDescriptorReference(),
-        null);
-    }
-    mpacks.addAll(parseMpacks(entity));
+  private final String name;
+  private final Map<String, HostGroup> hostGroups;
+  private final Collection<MpackInstance> mpacks;
+  private final StackDefinition stack;
+  private final Set<StackId> stackIds;
+  private final Configuration configuration;
+  private final SecurityConfiguration security;
+  private final Setting setting;
+  private final List<RepositorySetting> repoSettings;
+
+  public BlueprintImpl(BlueprintEntity entity, StackDefinition stack, Set<StackId> stackIds) throws NoSuchStackException {
+    name = entity.getBlueprintName();
+    security = entity.getSecurityType() != null
+      ? new SecurityConfiguration(entity.getSecurityType(), entity.getSecurityDescriptorReference(), null)
+      : SecurityConfiguration.NONE;
+    mpacks = parseMpacks(entity);
+
+    this.stack = stack;
+    this.stackIds = stackIds;
 
     // create config first because it is set as a parent on all host-group configs
     configuration = processConfiguration(entity.getConfigurations());
-    parseBlueprintHostGroups(entity);
+    hostGroups = parseBlueprintHostGroups(entity);
     // TODO: how to handle multiple stacks correctly?
-//    configuration.setParentConfiguration(stack.getConfiguration(getServices()));
-    validator = new BlueprintValidatorImpl(this);
-    processSetting(entity.getSettings());
-    processRepoSettings();
-  }
-
-  /**
-   * Legacy constructor for pre-multi-mpack code.
-   * @param name blueprint name
-   * @param groups host groups
-   * @param stack stack
-   * @param configuration configuration
-   * @param security security config
-   * @param setting setting
-   */
-  @Deprecated
-  public BlueprintImpl(String name, Collection<HostGroup> groups, Stack stack, Configuration configuration,
-                       SecurityConfiguration security, Setting setting) {
-    this(name, groups, stackToMpacks(stack), configuration, security, setting);
-  }
-
-  private static Collection<MpackInstance> stackToMpacks(Stack stack) {
-    MpackInstance mpack = new MpackInstance(stack.getName(), stack.getVersion(), null, stack, new Configuration());
-    return Collections.singleton(mpack);
+    configuration.setParentConfiguration(stack.getConfiguration(getServices()));
+    setting = processSetting(entity.getSettings());
+    repoSettings = processRepoSettings();
   }
 
-  public BlueprintImpl(String name, Collection<HostGroup> groups, Collection<MpackInstance> mpacks,
-                       Configuration configuration, SecurityConfiguration security, Setting setting) {
+  public BlueprintImpl(String name, Collection<HostGroup> groups, StackDefinition stack, Set<StackId> stackIds, Collection<MpackInstance> mpacks,
+      Configuration configuration, SecurityConfiguration security, Setting setting) {
     this.name = name;
     this.mpacks = mpacks;
-    this.security = security;
+    this.stack = stack;
+    this.stackIds = stackIds;
+    this.security = security != null ? security : SecurityConfiguration.NONE;
 
     // caller should set host group configs
+    hostGroups = new HashMap<>();
     for (HostGroup hostGroup : groups) {
       hostGroups.put(hostGroup.getName(), hostGroup);
     }
     // TODO: handle configuration from multiple stacks properly
     // if the parent isn't set, the stack configuration is set as the parent
     this.configuration = configuration;
-//    if (configuration.getParentConfiguration() == null) {
-//      configuration.setParentConfiguration(stack.getConfiguration(getServices()));
-//    }
-    validator = new BlueprintValidatorImpl(this);
+    if (configuration.getParentConfiguration() == null) {
+      configuration.setParentConfiguration(stack.getConfiguration(getServices()));
+    }
     this.setting = setting;
+    repoSettings = processRepoSettings();
   }
 
   public String getName() {
     return name;
   }
 
+  public Set<StackId> getStackIds() {
+    return stackIds;
+  }
+
+  @Override
+  public Set<StackId> getStackIdsForService(String service) {
+    return stack.getStacksForService(service);
+  }
+
   public SecurityConfiguration getSecurity() {
     return security;
   }
@@ -307,14 +294,8 @@ public class BlueprintImpl implements Blueprint {
     return mpacks;
   }
 
-  @Override
-  public Collection<Stack> getStacks() {
-    return mpacks.stream().map(MpackInstance::getStack).filter(s -> null != s).collect(toList());
-  }
-
-  @Override
-  public Stack getStack() {
-    return getStacks().iterator().next();
+  public StackDefinition getStack() {
+    return stack;
   }
 
   /**
@@ -353,25 +334,18 @@ public class BlueprintImpl implements Blueprint {
     return resultGroups;
   }
 
-  @Override
-  public void validateTopology() throws InvalidTopologyException {
-    validator.validateTopology();
-  }
-
   public BlueprintEntity toEntity() {
     BlueprintEntity entity = new BlueprintEntity();
     entity.setBlueprintName(name);
-    if (security != null) {
-      if (security.getType() != null) {
-        entity.setSecurityType(security.getType());
-      }
-      if (security.getDescriptorReference() != null) {
-        entity.setSecurityDescriptorReference(security.getDescriptorReference());
-      }
+    if (security.getType() != null) {
+      entity.setSecurityType(security.getType());
+    }
+    if (security.getDescriptorReference() != null) {
+      entity.setSecurityDescriptorReference(security.getDescriptorReference());
     }
 
     createHostGroupEntities(entity);
-    Collection<BlueprintConfigEntity> configEntities = toConfigEntities(getConfiguration(), () -> new BlueprintConfigEntity());
+    Collection<BlueprintConfigEntity> configEntities = toConfigEntities(getConfiguration(), BlueprintConfigEntity::new);
     configEntities.forEach(configEntity -> {
       configEntity.setBlueprintEntity(entity);
       configEntity.setBlueprintName(getName());
@@ -390,17 +364,6 @@ public class BlueprintImpl implements Blueprint {
     });
   }
 
-  /**
-   * Validate blueprint configuration.
-   *
-   * @throws InvalidTopologyException if the blueprint configuration is invalid
-   * @throws GPLLicenseNotAcceptedException ambari was configured to use gpl software, but gpl license is not accepted
-   */
-  @Override
-  public void validateRequiredProperties() throws InvalidTopologyException, GPLLicenseNotAcceptedException {
-    validator.validateRequiredProperties();
-  }
-
   private Collection<MpackInstance> parseMpacks(BlueprintEntity blueprintEntity) throws NoSuchStackException {
     Collection<MpackInstance> mpackInstances = new ArrayList<>();
     for (BlueprintMpackInstanceEntity mpack: blueprintEntity.getMpackInstances()) {
@@ -410,7 +373,6 @@ public class BlueprintImpl implements Blueprint {
       mpackInstance.setUrl(mpack.getMpackUri());
       mpackInstance.setConfiguration(processConfiguration(mpack.getConfigurations()));
       // TODO: come up with proper mpack -> stack resolution
-      tryParseStack(mpack.getMpackName(), mpack.getMpackVersion()).ifPresent( stack ->  mpackInstance.setStack(stack) );
       for(BlueprintServiceEntity serviceEntity: mpack.getServiceInstances()) {
         ServiceInstance serviceInstance = new ServiceInstance(
           serviceEntity.getName(),
@@ -423,32 +385,10 @@ public class BlueprintImpl implements Blueprint {
     return mpackInstances;
   }
 
-  private Stack parseStack(String stackName, String stackVersion) throws NoSuchStackException {
-    try {
-      //todo: don't pass in controller
-      return new Stack(stackName, stackVersion, AmbariServer.getController());
-    } catch (StackAccessException e) {
-      throw new NoSuchStackException(stackName, stackVersion, e);
-    } catch (AmbariException e) {
-      //todo:
-      throw new RuntimeException("An error occurred parsing the stack information.", e);
-    }
-  }
-
-  private Optional<Stack> tryParseStack(String stackName, String stackVersion) {
-    try {
-      return Optional.of(parseStack(stackName, stackVersion));
-    }
-    catch (Exception ex) {
-      LOG.warn("Cannot parse stack {}-{}. Exception: {}/{}", stackName, stackVersion, ex.getClass().getName(),
-        ex.getMessage());
-      return Optional.empty();
-    }
-  }
-
   private Map<String, HostGroup> parseBlueprintHostGroups(BlueprintEntity entity) {
+    Map<String, HostGroup> hostGroups = new HashMap<>();
     for (HostGroupEntity hostGroupEntity : entity.getHostGroups()) {
-      HostGroupImpl hostGroup = new HostGroupImpl(hostGroupEntity, getName(), getStacks());
+      HostGroupImpl hostGroup = new HostGroupImpl(hostGroupEntity, getName(), getStack());
       // set the bp configuration as the host group config parent
       hostGroup.getConfiguration().setParentConfiguration(configuration);
       hostGroups.put(hostGroupEntity.getName(), hostGroup);
@@ -467,13 +407,11 @@ public class BlueprintImpl implements Blueprint {
 
   /**
    * Process blueprint setting.
-   *
-   * @param blueprintSetting
    */
-  private void processSetting(Collection<BlueprintSettingEntity> blueprintSetting) {
-    if (blueprintSetting != null) {
-      setting = new Setting(parseSetting(blueprintSetting));
-    }
+  private Setting processSetting(Collection<BlueprintSettingEntity> blueprintSetting) {
+    return blueprintSetting != null
+      ? new Setting(parseSetting(blueprintSetting))
+      : null;
   }
 
   /**
@@ -549,7 +487,7 @@ public class BlueprintImpl implements Blueprint {
       hostGroupEntity.setBlueprintName(getName());
       hostGroupEntity.setCardinality(group.getCardinality());
 
-      Collection<HostGroupConfigEntity> configEntities = toConfigEntities(group.getConfiguration(), () -> new HostGroupConfigEntity());
+      Collection<HostGroupConfigEntity> configEntities = toConfigEntities(group.getConfiguration(), HostGroupConfigEntity::new);
       configEntities.forEach(configEntity -> {
         configEntity.setBlueprintName(getName());
         configEntity.setHostGroupEntity(hostGroupEntity);
@@ -641,13 +579,14 @@ public class BlueprintImpl implements Blueprint {
    * @return the configuration
    */
   static Configuration fromConfigEntities(Collection<? extends BlueprintConfiguration> configEntities) {
-    Gson jsonSerializer = new Gson();
     Configuration configuration = new Configuration();
 
     for (BlueprintConfiguration configEntity: configEntities) {
       String type = configEntity.getType();
-      Map<String, String> configData = jsonSerializer.fromJson(configEntity.getConfigData(), Map.class);
-      Map<String, Map<String, String>> configAttributes = jsonSerializer.fromJson(configEntity.getConfigAttributes(), Map.class);
+      Map<String, String> configData = JsonUtils.fromJson(configEntity.getConfigData(),
+        new TypeReference<Map<String, String>>(){});
+      Map<String, Map<String, String>> configAttributes = JsonUtils.fromJson(configEntity.getConfigAttributes(),
+        new TypeReference<Map<String, Map<String, String>>>(){});
       if (null != configData) {
         configuration.getProperties().put(type, configData);
       }
@@ -689,29 +628,26 @@ public class BlueprintImpl implements Blueprint {
     if (ConfigHelper.CLUSTER_ENV.equals(configType) || "global".equals(configType)) {
       return true;
     }
-
-    Collection<String> services = getStacks().stream().map(stack -> stack.getServiceForConfigType(configType)).collect(toList());
-    for (String service: services) {
-      if (getServices().contains(service)) {
-        return true;
-      }
-    }
-    return false;
+    String service = getStack().getServiceForConfigType(configType);
+    return getServices().contains(service);
   }
 
   /**
    * Parse stack repo info stored in the blueprint_settings table
-   * @return set of repositories
-   * */
-  private void processRepoSettings(){
-    repoSettings = new ArrayList<>();
-    if (setting != null){
-      Set<HashMap<String, String>> settingValue = setting.getSettingValue(Setting.SETTING_NAME_REPOSITORY_SETTINGS);
-      for (Map<String, String> setting : settingValue) {
-        RepositorySetting rs = parseRepositorySetting(setting);
-        repoSettings.add(rs);
-      }
+   */
+  private List<RepositorySetting> processRepoSettings() {
+    if (setting == null) {
+      return Collections.emptyList();
+    }
+
+    Set<? extends Map<String, String>> repositorySettingsValue = setting.getSettingValue(Setting.SETTING_NAME_REPOSITORY_SETTINGS);
+    if (repositorySettingsValue == null) {
+      return Collections.emptyList();
     }
+
+    return repositorySettingsValue.stream()
+      .map(this::parseRepositorySetting)
+      .collect(toList());
   }
 
   private RepositorySetting parseRepositorySetting(Map<String, String> setting){
@@ -727,19 +663,4 @@ public class BlueprintImpl implements Blueprint {
     return repoSettings;
   }
 
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public boolean isAllMpacksResolved() {
-    return !mpacks.stream().filter(mpack -> mpack.getStack() == null).findAny().isPresent();
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  public Collection<String> getUnresolvedMpackNames() {
-    return mpacks.stream().filter(mpack -> mpack.getStack() == null).map(MpackInstance::getMpackNameAndVersion).collect(toList());
-  }
-
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidator.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidator.java
index 0f83168..64e08ad 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidator.java
@@ -16,10 +16,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.ambari.server.topology;
 
-
 /**
  * Provides blueprint validation.
  */
@@ -27,16 +25,18 @@ public interface BlueprintValidator {
   /**
    * Validate blueprint topology.
    *
+   * @param blueprint the blueprint to validate
    * @throws InvalidTopologyException if the topology is invalid
    */
-  void validateTopology() throws InvalidTopologyException;
+  void validateTopology(Blueprint blueprint) throws InvalidTopologyException;
 
   /**
    * Validate that required properties are provided.
    * This doesn't include password properties.
    *
+   * @param blueprint the blueprint to validate
    * @throws InvalidTopologyException if required properties are not set in blueprint
-   * @throws GPLLicenseNotAcceptedException ambari was configured to use gpl software, but gpl license is not accepted
+   * @throws GPLLicenseNotAcceptedException if the blueprint requires use of GPL software, but GPL license was not accepted
    */
-  void validateRequiredProperties() throws InvalidTopologyException, GPLLicenseNotAcceptedException;
+  void validateRequiredProperties(Blueprint blueprint) throws InvalidTopologyException, GPLLicenseNotAcceptedException;
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidatorImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidatorImpl.java
index 1e6213e..39cbbfa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidatorImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintValidatorImpl.java
@@ -26,56 +26,45 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ambari.server.StaticallyInject;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.state.AutoDeployInfo;
 import org.apache.ambari.server.state.DependencyConditionInfo;
 import org.apache.ambari.server.state.DependencyInfo;
 import org.apache.ambari.server.utils.SecretReference;
-import org.apache.ambari.server.utils.VersionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Joiner;
 import com.google.inject.Inject;
 
 /**
  * Default blueprint validator.
  */
-@StaticallyInject
 public class BlueprintValidatorImpl implements BlueprintValidator {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(BlueprintValidatorImpl.class);
-  private final Blueprint blueprint;
-  private final Stack stack;
 
   public static final String LZO_CODEC_CLASS_PROPERTY_NAME = "io.compression.codec.lzo.class";
   public static final String CODEC_CLASSES_PROPERTY_NAME = "io.compression.codecs";
   public static final String LZO_CODEC_CLASS = "com.hadoop.compression.lzo.LzoCodec";
 
-  @Inject
-  private static org.apache.ambari.server.configuration.Configuration configuration;
+  private final Configuration configuration;
 
-  public BlueprintValidatorImpl(Blueprint blueprint) {
-    this.blueprint = blueprint;
-    this.stack = blueprint.getStack();
+  @Inject
+  public BlueprintValidatorImpl(Configuration configuration) {
+    this.configuration = configuration;
   }
 
   @Override
-  public void validateTopology() throws InvalidTopologyException {
+  public void validateTopology(Blueprint blueprint) throws InvalidTopologyException {
     LOGGER.info("Validating topology for blueprint: [{}]", blueprint.getName());
 
-    if (!blueprint.isAllMpacksResolved()) {
-      LOGGER.warn("The following macks are not resolved: [{}] Skipping topology validation.",
-        Joiner.on(", ").join(blueprint.getUnresolvedMpackNames()));
-      return;
-    }
-
+    StackDefinition stack = blueprint.getStack();
     Collection<HostGroup> hostGroups = blueprint.getHostGroups().values();
     Map<String, Map<String, Collection<DependencyInfo>>> missingDependencies = new HashMap<>();
 
     for (HostGroup group : hostGroups) {
-      Map<String, Collection<DependencyInfo>> missingGroupDependencies = validateHostGroup(group);
+      Map<String, Collection<DependencyInfo>> missingGroupDependencies = validateHostGroup(blueprint, stack, group);
       if (!missingGroupDependencies.isEmpty()) {
         missingDependencies.put(group.getName(), missingGroupDependencies);
       }
@@ -89,10 +78,10 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
         Cardinality cardinality = stack.getCardinality(component);
         AutoDeployInfo autoDeploy = stack.getAutoDeployInfo(component);
         if (cardinality.isAll()) {
-          cardinalityFailures.addAll(verifyComponentInAllHostGroups(new Component(component), autoDeploy));
+          cardinalityFailures.addAll(verifyComponentInAllHostGroups(blueprint, new Component(component), autoDeploy));
         } else {
           cardinalityFailures.addAll(verifyComponentCardinalityCount(
-            new Component(component), cardinality, autoDeploy));
+            stack, blueprint, new Component(component), cardinality, autoDeploy));
         }
       }
     }
@@ -103,7 +92,7 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
   }
 
   @Override
-  public void validateRequiredProperties() throws InvalidTopologyException, GPLLicenseNotAcceptedException {
+  public void validateRequiredProperties(Blueprint blueprint) throws InvalidTopologyException, GPLLicenseNotAcceptedException {
 
     // we don't want to include default stack properties so we can't just use hostGroup full properties
     Map<String, Map<String, String>> clusterConfigurations = blueprint.getConfiguration().getProperties();
@@ -161,58 +150,31 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
           }
         }
         if (ClusterTopologyImpl.isNameNodeHAEnabled(clusterConfigurations) && component.equals("NAMENODE")) {
-            Map<String, String> hadoopEnvConfig = clusterConfigurations.get("hadoop-env");
-            if(hadoopEnvConfig != null && !hadoopEnvConfig.isEmpty() && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_active") && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_standby")) {
-              ArrayList<HostGroup> hostGroupsForComponent = new ArrayList<>(blueprint.getHostGroupsForComponent(component));
-              Set<String> givenHostGroups = new HashSet<>();
-              givenHostGroups.add(hadoopEnvConfig.get("dfs_ha_initial_namenode_active"));
-              givenHostGroups.add(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby"));
-              if(givenHostGroups.size() != hostGroupsForComponent.size()) {
-                 throw new IllegalArgumentException("NAMENODE HA host groups mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected Host groups are :" + hostGroupsForComponent);
-              }
-              if(HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")).matches() && HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")).matches()){
-                for (HostGroup hostGroupForComponent : hostGroupsForComponent) {
-                   Iterator<String> itr = givenHostGroups.iterator();
-                   while(itr.hasNext()){
-                      if(itr.next().contains(hostGroupForComponent.getName())){
-                         itr.remove();
-                      }
-                   }
-                 }
-                 if(!givenHostGroups.isEmpty()){
-                    throw new IllegalArgumentException("NAMENODE HA host groups mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected Host groups are :" + hostGroupsForComponent);
+          Map<String, String> hadoopEnvConfig = clusterConfigurations.get("hadoop-env");
+          if(hadoopEnvConfig != null && !hadoopEnvConfig.isEmpty() && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_active") && hadoopEnvConfig.containsKey("dfs_ha_initial_namenode_standby")) {
+            ArrayList<HostGroup> hostGroupsForComponent = new ArrayList<>(blueprint.getHostGroupsForComponent(component));
+            Set<String> givenHostGroups = new HashSet<>();
+            givenHostGroups.add(hadoopEnvConfig.get("dfs_ha_initial_namenode_active"));
+            givenHostGroups.add(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby"));
+            if(givenHostGroups.size() != hostGroupsForComponent.size()) {
+               throw new IllegalArgumentException("NAMENODE HA host groups mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected Host groups are :" + hostGroupsForComponent);
+            }
+            if(HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_active")).matches() && HostGroup.HOSTGROUP_REGEX.matcher(hadoopEnvConfig.get("dfs_ha_initial_namenode_standby")).matches()){
+              for (HostGroup hostGroupForComponent : hostGroupsForComponent) {
+                 Iterator<String> itr = givenHostGroups.iterator();
+                 while(itr.hasNext()){
+                    if(itr.next().contains(hostGroupForComponent.getName())){
+                       itr.remove();
+                    }
                  }
-                }
               }
-          }
-
-        if (blueprint.isAllMpacksResolved()) {
-          if (component.equals("HIVE_METASTORE")) {
-            Map<String, String> hiveEnvConfig = clusterConfigurations.get("hive-env");
-            if (hiveEnvConfig != null && !hiveEnvConfig.isEmpty() && hiveEnvConfig.get("hive_database") != null
-              && hiveEnvConfig.get("hive_database").equals("Existing SQL Anywhere Database")
-              && VersionUtils.compareVersions(stack.getVersion(), "2.3.0.0") < 0
-              && stack.getName().equalsIgnoreCase("HDP")) {
-              throw new InvalidTopologyException("Incorrect configuration: SQL Anywhere db is available only for stack HDP-2.3+ " +
-                "and repo version 2.3.2+!");
             }
-          }
 
-          if (component.equals("OOZIE_SERVER")) {
-            Map<String, String> oozieEnvConfig = clusterConfigurations.get("oozie-env");
-            if (oozieEnvConfig != null && !oozieEnvConfig.isEmpty() && oozieEnvConfig.get("oozie_database") != null
-              && oozieEnvConfig.get("oozie_database").equals("Existing SQL Anywhere Database")
-              && VersionUtils.compareVersions(stack.getVersion(), "2.3.0.0") < 0
-              && stack.getName().equalsIgnoreCase("HDP")) {
-              throw new InvalidTopologyException("Incorrect configuration: SQL Anywhere db is available only for stack HDP-2.3+ " +
-                "and repo version 2.3.2+!");
+            if(!givenHostGroups.isEmpty()){
+              throw new IllegalArgumentException("NAMENODE HA host groups mapped incorrectly for properties 'dfs_ha_initial_namenode_active' and 'dfs_ha_initial_namenode_standby'. Expected Host groups are :" + hostGroupsForComponent);
             }
           }
         }
-        else {
-          LOGGER.warn("The following macks are not resolved: [{}] Skipping validation of HIVE_METASTORE and OOZIE_SERVER properties.",
-            Joiner.on(", ").join(blueprint.getUnresolvedMpackNames()));
-        }
       }
     }
   }
@@ -221,13 +183,14 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
    * Verify that a component is included in all host groups.
    * For components that are auto-install enabled, will add component to topology if needed.
    *
+   *
+   * @param blueprint   blueprint to validate
    * @param component   component to validate
    * @param autoDeploy  auto-deploy information for component
    *
    * @return collection of missing component information
    */
-  private Collection<String> verifyComponentInAllHostGroups(Component component, AutoDeployInfo autoDeploy) {
-
+  private Collection<String> verifyComponentInAllHostGroups(Blueprint blueprint, Component component, AutoDeployInfo autoDeploy) {
     Collection<String> cardinalityFailures = new HashSet<>();
     int actualCount = blueprint.getHostGroupsForComponent(component.getName()).size();
     Map<String, HostGroup> hostGroups = blueprint.getHostGroups();
@@ -243,7 +206,7 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
     return cardinalityFailures;
   }
 
-  private Map<String, Collection<DependencyInfo>> validateHostGroup(HostGroup group) {
+  private Map<String, Collection<DependencyInfo>> validateHostGroup(Blueprint blueprint, StackDefinition stack, HostGroup group) {
     LOGGER.info("Validating hostgroup: {}", group.getName());
     Map<String, Collection<DependencyInfo>> missingDependencies = new HashMap<>();
 
@@ -288,7 +251,7 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
         }
         if (dependencyScope.equals("cluster")) {
           Collection<String> missingDependencyInfo = verifyComponentCardinalityCount(
-              new Component(componentName), new Cardinality("1+"), autoDeployInfo);
+            stack, blueprint, new Component(componentName), new Cardinality("1+"), autoDeployInfo);
 
           resolved = missingDependencyInfo.isEmpty();
         } else if (dependencyScope.equals("host")) {
@@ -315,16 +278,22 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
    * Verify that a component meets cardinality requirements.  For components that are
    * auto-install enabled, will add component to topology if needed.
    *
+   *
+   * @param stack        stack definition
+   * @param blueprint    blueprint to validate
    * @param component    component to validate
    * @param cardinality  required cardinality
    * @param autoDeploy   auto-deploy information for component
    *
    * @return collection of missing component information
    */
-  public Collection<String> verifyComponentCardinalityCount(Component component,
-                                                            Cardinality cardinality,
-                                                            AutoDeployInfo autoDeploy) {
-
+  private Collection<String> verifyComponentCardinalityCount(
+    StackDefinition stack,
+    Blueprint blueprint,
+    Component component,
+    Cardinality cardinality,
+    AutoDeployInfo autoDeploy
+  ) {
     Map<String, Map<String, String>> configProperties = blueprint.getConfiguration().getProperties();
     Collection<String> cardinalityFailures = new HashSet<>();
     //todo: don't hard code this HA logic here
@@ -362,13 +331,13 @@ public class BlueprintValidatorImpl implements BlueprintValidator {
    * Determine if a component is managed, meaning that it is running inside of the cluster
    * topology.  Generally, non-managed dependencies will be database components.
    *
-   * @param stack          stack instance
+   * @param stack          stack definition
    * @param component      component to determine if it is managed
    * @param clusterConfig  cluster configuration
    *
    * @return true if the specified component managed by the cluster; false otherwise
    */
-  protected boolean isDependencyManaged(Stack stack, String component, Map<String, Map<String, String>> clusterConfig) {
+  protected boolean isDependencyManaged(StackDefinition stack, String component, Map<String, Map<String, String>> clusterConfig) {
     boolean isManaged = true;
     String externalComponentConfig = stack.getExternalComponentConfig(component);
     if (externalComponentConfig != null) {
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 dfe5dd0..ee626bc 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
@@ -36,7 +36,7 @@ import org.apache.ambari.server.controller.ConfigurationRequest;
 import org.apache.ambari.server.controller.internal.BlueprintConfigurationProcessor;
 import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
 import org.apache.ambari.server.controller.internal.ConfigurationTopologyException;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.SecurityType;
@@ -63,7 +63,7 @@ public class ClusterConfigurationRequest {
   private ClusterTopology clusterTopology;
   private BlueprintConfigurationProcessor configurationProcessor;
   private StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor;
-  private Stack stack;
+  private StackDefinition stack;
   private boolean configureSecurity = false;
 
   public ClusterConfigurationRequest(AmbariContext ambariContext, ClusterTopology topology, boolean setInitial, StackAdvisorBlueprintProcessor stackAdvisorBlueprintProcessor, boolean configureSecurity) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
index b71b12c..c20d10a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
@@ -18,8 +18,8 @@
 
 package org.apache.ambari.server.topology;
 
-import static org.apache.ambari.server.topology.BlueprintFactory.PROPERTIES_ATTRIBUTES_PROPERTY_ID;
-import static org.apache.ambari.server.topology.BlueprintFactory.PROPERTIES_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_ATTRIBUTES_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_PROPERTY_ID;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -42,7 +42,7 @@ public interface Configurable {
   Configuration getConfiguration();
 
   @JsonProperty("configurations")
-  default void setConfigs(Collection<? extends Map<String, ? extends Object>> configs) {
+  default void setConfigs(Collection<? extends Map<String, ?>> configs) {
     Configuration configuration;
     if (!configs.isEmpty() && configs.iterator().next().keySet().iterator().next().contains("/")) {
       // Configuration has keys with slashes like "zk.cfg/properties/dataDir" means it is coming through
@@ -73,9 +73,9 @@ public interface Configurable {
   }
 
   @JsonProperty("configurations")
-  default Collection<Map<String, Map<String, ? extends Object>>> getConfigs() {
+  default Collection<Map<String, Map<String, ?>>> getConfigs() {
     Configuration configuration = getConfiguration();
-    Collection<Map<String, Map<String, ? extends Object>>> configurations = new ArrayList<>();
+    Collection<Map<String, Map<String, ?>>> configurations = new ArrayList<>();
     Set<String> allConfigTypes = Sets.union(configuration.getProperties().keySet(), configuration.getAttributes().keySet());
     for (String configType: allConfigTypes) {
       Map<String, Map<String, ? extends Object>> configData = new HashMap<>();
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
index 8ffbdae..c32b775 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configuration.java
@@ -43,12 +43,15 @@ public class Configuration {
    */
   private Configuration parentConfiguration;
 
-
   public Configuration() {
     properties = new HashMap<>();
     attributes = new HashMap<>();
   }
 
+  public static Configuration createEmpty() {
+    return new Configuration(new HashMap<>(), new HashMap<>());
+  }
+
   /**
    * Constructor.
    *
@@ -365,4 +368,19 @@ public class Configuration {
       parentConfiguration.removeConfigType(configType);
     }
   }
+
+  public static Configuration combine(Configuration c1, Configuration c2) {
+    if (c1 == null || (c1.getProperties().isEmpty() && c1.getAttributes().isEmpty())) {
+      return c2;
+    }
+    if (c2 == null || (c2.getProperties().isEmpty() && c2.getAttributes().isEmpty())) {
+      return c1;
+    }
+    Configuration combined = new Configuration(new HashMap<>(), new HashMap<>());
+    combined.getProperties().putAll(c1.getProperties());
+    combined.getProperties().putAll(c2.getProperties());
+    combined.getAttributes().putAll(c1.getAttributes());
+    combined.getAttributes().putAll(c2.getAttributes());
+    return combined;
+  }
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurationFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurationFactory.java
index 7f9a06f..7249e3c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurationFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ConfigurationFactory.java
@@ -10,8 +10,7 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distribut
- * ed on an "AS IS" BASIS,
+ * 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.
@@ -19,6 +18,9 @@
 
 package org.apache.ambari.server.topology;
 
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_ATTRIBUTES_PROPERTY_ID;
+import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_PROPERTY_ID;
+
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -59,8 +61,8 @@ public class ConfigurationFactory {
       String propertiesType = keyNameTokens[1];
       if (levels == 2) {
         return new ConfigurationStrategyV1();
-      } else if ((levels == 3 && BlueprintFactory.PROPERTIES_PROPERTY_ID.equals(propertiesType))
-          || (levels == 4 && BlueprintFactory.PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertiesType))) {
+      } else if ((levels == 3 && PROPERTIES_PROPERTY_ID.equals(propertiesType))
+          || (levels == 4 && PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertiesType))) {
         return new ConfigurationStrategyV2();
       } else {
         throw new IllegalArgumentException(SCHEMA_IS_NOT_SUPPORTED_MESSAGE);
@@ -111,9 +113,9 @@ public class ConfigurationFactory {
     @Override
     protected void setConfiguration(Configuration configuration, String[] propertyNameTokens, String propertyValue) {
       String type = propertyNameTokens[0];
-      if (BlueprintFactory.PROPERTIES_PROPERTY_ID.equals(propertyNameTokens[1])) {
+      if (PROPERTIES_PROPERTY_ID.equals(propertyNameTokens[1])) {
         configuration.setProperty(type, propertyNameTokens[2], propertyValue);
-      } else if (BlueprintFactory.PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertyNameTokens[1])) {
+      } else if (PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertyNameTokens[1])) {
         configuration.setAttribute(type, propertyNameTokens[3], propertyNameTokens[2], propertyValue);
       }
     }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroup.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroup.java
index 43ce5b9..a329f42 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroup.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroup.java
@@ -22,7 +22,7 @@ import java.util.Collection;
 import java.util.regex.Pattern;
 
 import org.apache.ambari.server.controller.internal.ProvisionAction;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 
 /**
  * Host Group representation.
@@ -109,8 +109,6 @@ public interface HostGroup {
 
   /**
    * Add a component to the host group
-   * @param component
-   * @return
    */
   boolean addComponent(Component component);
 
@@ -142,15 +140,7 @@ public interface HostGroup {
    *
    * @return associated stack
    */
-  @Deprecated
-  Stack getStack();
-
-  /**
-   * Get the stack associated with the host group.
-   *
-   * @return associated stacks
-   */
-  Collection<Stack> getStacks();
+  StackDefinition getStack();
 
   /**
    * Get the cardinality value that was specified for the host group.
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroupImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroupImpl.java
index 88ad162..0899d0b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroupImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostGroupImpl.java
@@ -22,27 +22,22 @@ package org.apache.ambari.server.topology;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
+import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import org.apache.ambari.server.controller.internal.ProvisionAction;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.orm.entities.HostGroupComponentEntity;
 import org.apache.ambari.server.orm.entities.HostGroupConfigEntity;
 import org.apache.ambari.server.orm.entities.HostGroupEntity;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Sets;
 import com.google.gson.Gson;
 
 /**
@@ -65,7 +60,7 @@ public class HostGroupImpl implements HostGroup {
   /**
    * components contained in the host group
    */
-  private final List<Component> components = new ArrayList<>();
+  private final Set<Component> components = new LinkedHashSet<>();
 
   /**
    * map of service to components for the host group
@@ -78,31 +73,29 @@ public class HostGroupImpl implements HostGroup {
    */
   private Configuration configuration = null;
 
-  private boolean containsMasterComponent = false;
+  private boolean containsMasterComponent = false; // FIXME never set
 
-  private Map<String, Stack> stackMap;
+  private StackDefinition stack;
 
   private String cardinality = "NOT SPECIFIED";
 
-  public HostGroupImpl(HostGroupEntity entity, String blueprintName, Collection<Stack> stacks) {
+  public HostGroupImpl(HostGroupEntity entity, String blueprintName, StackDefinition stack) {
     this.name = entity.getName();
     this.cardinality = entity.getCardinality();
     this.blueprintName = blueprintName;
-    this.stackMap = stacks.stream().collect(Collectors.toMap(stack -> stack.getName() + "-" + stack.getVersion(), stack -> stack));
+    this.stack = stack;
     parseComponents(entity);
     parseConfigurations(entity);
   }
 
-  public HostGroupImpl(String name, String bpName, Collection<Stack> stacks, Collection<Component> components, Configuration configuration, String cardinality) {
+  public HostGroupImpl(String name, String bpName, StackDefinition stack, Collection<Component> components, Configuration configuration, String cardinality) {
     this.name = name;
     this.blueprintName = bpName;
-    this.stackMap = stacks.stream().collect(Collectors.toMap(stack -> stack.getName() + "-" + stack.getVersion(), stack -> stack));
+    this.stack = stack;
 
     // process each component
     for (Component component: components) {
-      if (!addComponent(component)) {
-        throw new IllegalArgumentException("Ambiguous component or can't determine stack for: " + component);
-      }
+      addComponent(component);
     }
 
     this.configuration = configuration;
@@ -162,82 +155,25 @@ public class HostGroupImpl implements HostGroup {
   }
 
   /**
-   * Adds a component to the host group. The component is successfully added if it is not a duplicate or ambiguous (as of
-   * Ambari 3.1 multiple components of the same type can exist in a hostgroup. However, they have to come from different
-   * management packs or belong to different service instances)
+   * Adds a component to the host group.
    * @param component the component to add
-   * @return a boolean to indicate if addition was successful
    */
+  @Override
   public boolean addComponent(Component component) {
-    // Exclude ambiguous component definitions
-    boolean ambigous = components.stream().filter(c -> {
-      if (c.getName().equals(component.getName())) { // found another component with the same name
-        if (c.getMpackInstance() == null || component.getMpackInstance() == null) {
-          return true; // if either of them has no mpack instance defined it is ambiguous
-        }
-        if (c.getMpackInstance().equals(component.getMpackInstance())) {
-          // both components are in the same mpack, and one of them does not declare a service instance or
-          // both declare the same service instance --> ambiguous
-          return  c.getServiceInstance() != null && component.getServiceInstance() != null &&
-            c.getServiceInstance().equals(component.getServiceInstance());
-        }
-        else {
-          return false; // different mpacks --> no ambiguity
-        }
-      }
-      else {
-        return false; // different name --> no ambiguity
+    if (components.add(component)) {
+      containsMasterComponent |= stack.isMasterComponent(component.getName());
+
+      String service = stack.getServiceForComponent(component.getName());
+      if (service != null) {
+        componentsForService
+          .computeIfAbsent(service, __ -> new HashSet<>())
+          .add(component);
       }
-    }).findAny().isPresent();
-    if (ambigous) {
-      return false;
-    }
-    addComponent(component, getStackForComponent(component));
-    return true;
-  }
 
-  private Optional<Stack> getStackForComponent(Component component) {
-    // Look for the stack of this component
-    if (component.getMpackInstance() == null) {
-      // Component does not declare its stack. Let's find it.
-      Collection<Stack> candidateStacks =
-        stackMap.values().stream().filter(stack -> stack.getServiceForComponent(component.getName()) != null).collect(toList());
-      switch (candidateStacks.size()) {
-        case 0:
-          // no stack (no service) for this component
-          LOG.warn("No stack/service found for component: {}", component);
-          return Optional.empty();
-        case 1:
-          return Optional.of(candidateStacks.iterator().next());
-        default:
-          LOG.warn("Ambiguous stack resolution for component: {}, stacks: {}", component, candidateStacks);
-          return Optional.empty();
-      }
-    }
-    else {
-      // TODO: refine this logic
-      Stack stack = stackMap.get(component.getMpackInstance());
-      if (null == stack) {
-        LOG.warn("Component declared an invalid stack: {}", component);
-      }
-      return Optional.ofNullable(stack);
+      return true;
     }
-  }
 
-  private void addComponent(Component component, Optional<Stack> stack) {
-    if (stack.isPresent()) {
-      String serviceName = stack.get().getServiceForComponent(component.getName());
-      if (!componentsForService.containsKey(serviceName)) {
-        componentsForService.put(serviceName, Sets.newHashSet(component));
-      }
-      else {
-        componentsForService.get(serviceName).add(component);
-      }
-      if (stack.get().isMasterComponent(component.getName())) {
-        containsMasterComponent = true;
-      }
-    }
-    components.add(component);
+    return false;
   }
 
   /**
@@ -255,7 +191,7 @@ public class HostGroupImpl implements HostGroup {
   }
 
   /**
-   * Get the names components for the specified service which are associated with the host group.
+   * Get the names of components for the specified service which are associated with the host group.
    *
    * @param service  service name
    *
@@ -296,14 +232,8 @@ public class HostGroupImpl implements HostGroup {
   }
 
   @Override
-  public Collection<Stack> getStacks() {
-    return stackMap.values();
-  }
-
-  @Override
-  @Deprecated
-  public Stack getStack() {
-    return getStacks().iterator().next();
+  public StackDefinition getStack() {
+    return stack;
   }
 
   @Override
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostRequest.java
index 13f89b5..3a8acc8 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/HostRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/HostRequest.java
@@ -34,7 +34,7 @@ import org.apache.ambari.server.api.predicate.InvalidQueryException;
 import org.apache.ambari.server.api.predicate.PredicateCompiler;
 import org.apache.ambari.server.controller.internal.HostResourceProvider;
 import org.apache.ambari.server.controller.internal.ResourceImpl;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
@@ -257,7 +257,7 @@ public class HostRequest implements Comparable<HostRequest> {
           "PENDING HOST ASSIGNMENT : HOSTGROUP=" + getHostgroupName();
 
       AmbariContext context = topology.getAmbariContext();
-      Stack stack = hostGroup.getStack();
+      StackDefinition stack = hostGroup.getStack();
 
       // Skip INSTALL task in case server component is marked as START_ONLY, or the cluster provision_action is
       // START_ONLY, unless component is marked with INSTALL_ONLY or INSTALL_AND_START.
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 cde01e0..6b57b00 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
@@ -122,7 +122,7 @@ public class MpackInstance implements Configurable {
     mpackEntity.setMpackName(mpackName);
     mpackEntity.setMpackVersion(mpackVersion);
     Collection<BlueprintMpackConfigEntity> mpackConfigEntities =
-      BlueprintImpl.toConfigEntities(configuration, () -> new BlueprintMpackConfigEntity());
+      BlueprintImpl.toConfigEntities(configuration, BlueprintMpackConfigEntity::new);
     mpackConfigEntities.forEach( configEntity -> configEntity.setMpackInstance(mpackEntity) );
     mpackEntity.setConfigurations(mpackConfigEntities);
 
@@ -131,7 +131,7 @@ public class MpackInstance implements Configurable {
       serviceEntity.setName(serviceInstance.getName());
       serviceEntity.setType(serviceInstance.getType());
       Collection<BlueprintServiceConfigEntity> serviceConfigEntities =
-        BlueprintImpl.toConfigEntities(serviceInstance.getConfiguration(), () -> new BlueprintServiceConfigEntity());
+        BlueprintImpl.toConfigEntities(serviceInstance.getConfiguration(), BlueprintServiceConfigEntity::new);
       serviceConfigEntities.forEach( configEntity -> configEntity.setService(serviceEntity) );
       serviceEntity.setConfigurations(serviceConfigEntities);
       mpackEntity.getServiceInstances().add(serviceEntity);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/SecurityConfiguration.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/SecurityConfiguration.java
index 4ff5504..d7418ce 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/SecurityConfiguration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/SecurityConfiguration.java
@@ -28,6 +28,8 @@ import org.apache.ambari.server.state.SecurityType;
  */
 public class SecurityConfiguration {
 
+  public static final SecurityConfiguration NONE = new SecurityConfiguration(SecurityType.NONE);
+
   /**
    * Security Type
    */
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 35add3c..51eedd3 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
@@ -51,7 +51,7 @@ import org.apache.ambari.server.controller.internal.CredentialResourceProvider;
 import org.apache.ambari.server.controller.internal.ProvisionClusterRequest;
 import org.apache.ambari.server.controller.internal.RequestImpl;
 import org.apache.ambari.server.controller.internal.ScaleClusterRequest;
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
@@ -71,6 +71,7 @@ import org.apache.ambari.server.orm.entities.StageEntity;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.host.HostImpl;
 import org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTask;
@@ -81,6 +82,7 @@ import org.apache.ambari.server.utils.RetryHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Iterables;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
@@ -275,7 +277,7 @@ public class TopologyManager {
 
     final ClusterTopology topology = new ClusterTopologyImpl(ambariContext, request);
     final String clusterName = request.getClusterName();
-    final Stack stack = topology.getBlueprint().getStack(); // TODO: implement multi-stack
+    final StackDefinition stack = topology.getBlueprint().getStack();
     final String repoVersion = request.getRepositoryVersion();
     final Long repoVersionID = request.getRepositoryVersionId();
 
@@ -347,7 +349,8 @@ public class TopologyManager {
     //todo: this should be invoked as part of a generic lifecycle event which could possibly
     //todo: be tied to cluster state
 
-    ambariContext.persistInstallStateForUI(clusterName, stack.getName(), stack.getVersion());
+    StackId stackId = Iterables.getFirst(topology.getBlueprint().getStackIds(), null); // FIXME need for stackId in ClusterRequest will be removed
+    ambariContext.persistInstallStateForUI(clusterName, stackId);
     clusterProvisionWithBlueprintCreateRequests.put(clusterId, logicalRequest);
     return getRequestStatus(logicalRequest.getRequestId());
   }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
index 38a5153..5c9e0eb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
@@ -21,6 +21,7 @@ import java.util.Map;
 
 import org.apache.ambari.server.controller.RootComponent;
 import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.state.PropertyInfo;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.ClusterTopology;
@@ -82,7 +83,7 @@ public class RequiredPasswordValidator implements TopologyValidator {
 
       Collection<String> processedServices = new HashSet<>();
       Blueprint blueprint = topology.getBlueprint();
-      Stack stack = blueprint.getStack();
+      StackDefinition stack = blueprint.getStack();
 
       HostGroup hostGroup = blueprint.getHostGroup(hostGroupName);
       for (String component : hostGroup.getComponentNames()) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/UnitValidator.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/UnitValidator.java
index e75ffa4..31d5275 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/UnitValidator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/UnitValidator.java
@@ -22,7 +22,7 @@ import static org.apache.ambari.server.controller.internal.UnitUpdater.PropertyV
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.controller.internal.StackDefinition;
 import org.apache.ambari.server.controller.internal.UnitUpdater.PropertyUnit;
 import org.apache.ambari.server.topology.ClusterTopology;
 import org.apache.ambari.server.topology.HostGroupInfo;
@@ -42,33 +42,33 @@ public class UnitValidator implements TopologyValidator {
 
   @Override
   public void validate(ClusterTopology topology) throws InvalidTopologyException {
-    Stack stack = topology.getBlueprint().getStack();
+    StackDefinition stack = topology.getBlueprint().getStack();
     validateConfig(topology.getConfiguration().getFullProperties(), stack);
     for (HostGroupInfo hostGroup : topology.getHostGroupInfo().values()) {
       validateConfig(hostGroup.getConfiguration().getFullProperties(), stack);
     }
   }
 
-  private void validateConfig(Map<String, Map<String, String>> configuration, Stack stack) {
+  private void validateConfig(Map<String, Map<String, String>> configuration, StackDefinition stack) {
     for (Map.Entry<String, Map<String, String>> each : configuration.entrySet()) {
       validateConfigType(each.getKey(), each.getValue(), stack);
     }
   }
 
-  private void validateConfigType(String configType, Map<String, String> config, Stack stack) {
+  private void validateConfigType(String configType, Map<String, String> config, StackDefinition stack) {
     for (String propertyName : config.keySet()) {
       validateProperty(configType, config, propertyName, stack);
     }
   }
 
-  private void validateProperty(String configType, Map<String, String> config, String propertyName, Stack stack) {
+  private void validateProperty(String configType, Map<String, String> config, String propertyName, StackDefinition stack) {
     relevantProps.stream()
       .filter(each -> each.hasTypeAndName(configType, propertyName))
       .findFirst()
       .ifPresent(relevantProperty -> checkUnit(config, stack, relevantProperty));
   }
 
-  private void checkUnit(Map<String, String> configToBeValidated, Stack stack, UnitValidatedProperty prop) {
+  private void checkUnit(Map<String, String> configToBeValidated, StackDefinition stack, UnitValidatedProperty prop) {
     PropertyUnit stackUnit = PropertyUnit.of(stack, prop);
     PropertyValue value = PropertyValue.of(prop.getPropertyName(), configToBeValidated.get(prop.getPropertyName()));
     if (value.hasAnyUnit() && !value.hasUnit(stackUnit)) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/utils/JsonUtils.java b/ambari-server/src/main/java/org/apache/ambari/server/utils/JsonUtils.java
index 8d5e307..8c6df29 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/utils/JsonUtils.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/utils/JsonUtils.java
@@ -17,8 +17,14 @@
  */
 package org.apache.ambari.server.utils;
 
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
 import org.apache.commons.lang.StringUtils;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonSyntaxException;
 
@@ -27,8 +33,14 @@ import com.google.gson.JsonSyntaxException;
  */
 public class JsonUtils {
 
+  /**
+   * Used to serialize to/from json.
+   */
   public static JsonParser jsonParser = new JsonParser();
 
+  private static final ObjectMapper JSON_SERIALIZER = new ObjectMapper();
+  private static final ObjectWriter JSON_WRITER = JSON_SERIALIZER.writer();
+
   /**
    * Checks if an input string is in valid JSON format
    * @param jsonString input json string to validate
@@ -46,4 +58,23 @@ public class JsonUtils {
       return false;
     }
   }
+
+  public static <T> T fromJson(String json, TypeReference<? extends T> valueType) {
+    if (null == json) {
+      return  null;
+    }
+    try {
+      return JSON_SERIALIZER.reader(valueType).readValue(json);
+    } catch (IOException ex) {
+      throw new UncheckedIOException(ex);
+    }
+  }
+
+  public static String toJson(Object object) {
+    try {
+      return JSON_WRITER.writeValueAsString(object);
+    } catch (IOException ex) {
+      throw new UncheckedIOException(ex);
+    }
+  }
 }
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
index d0692d0..2e0c0fd 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -594,7 +594,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR(255) NOT NULL,
   mpack_name VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
-  mpack_uri VARCHAR(255) NOT NULL,
+  mpack_uri VARCHAR(255),
   mpack_id BIGINT,
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index c45da45..a177a91 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -612,7 +612,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR(255) NOT NULL,
   mpack_name VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
-  mpack_uri VARCHAR(255) NOT NULL,
+  mpack_uri VARCHAR(255),
   mpack_id BIGINT,
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index 04f24ad..fe0c66b 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -592,7 +592,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR2(255) NOT NULL,
   mpack_name VARCHAR2(255) NOT NULL,
   mpack_version VARCHAR2(255) NOT NULL,
-  mpack_uri VARCHAR2(255) NOT NULL,
+  mpack_uri VARCHAR2(255),
   mpack_id NUMBER(19),
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 5ba57fb..1848346 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -593,7 +593,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR(255) NOT NULL,
   mpack_name VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
-  mpack_uri VARCHAR(255) NOT NULL,
+  mpack_uri VARCHAR(255),
   mpack_id BIGINT,
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
index 790926f..128ff4a 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -589,7 +589,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR(255) NOT NULL,
   mpack_name VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
-  mpack_uri VARCHAR(255) NOT NULL,
+  mpack_uri VARCHAR(255),
   mpack_id NUMERIC(19),
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
index eead2a0..6ba6a41 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -598,7 +598,7 @@ CREATE TABLE blueprint_mpack_instance(
   blueprint_name VARCHAR(255) NOT NULL,
   mpack_name VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
-  mpack_uri VARCHAR(255) NOT NULL,
+  mpack_uri VARCHAR(255),
   mpack_id BIGINT,
   CONSTRAINT PK_blueprint_mpack_inst PRIMARY KEY (id),
   CONSTRAINT FK_mpi_blueprint_name FOREIGN KEY (blueprint_name) REFERENCES blueprint(blueprint_name),
diff --git a/ambari-server/src/main/resources/key_properties.json b/ambari-server/src/main/resources/key_properties.json
index 73eb039..6e2463b 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -123,9 +123,6 @@
     "Cluster": "RequestSchedule/cluster_name",
     "RequestSchedule": "RequestSchedule/id"
   },
-  "Blueprint": {
-    "Blueprint": "Blueprints/blueprint_name"
-  },
   "Recommendation": {
     "Recommendation": "Recommendation/id",
     "Stack": "Versions/stack_name",
diff --git a/ambari-server/src/main/resources/properties.json b/ambari-server/src/main/resources/properties.json
index aac2140..4deb33a 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -369,19 +369,6 @@
         "RootServiceHostComponents/component_version",
         "RootServiceHostComponents/properties"
      ],
-    "Blueprint":[
-        "Blueprints/blueprint_name",
-        "Blueprints/stack_name",
-        "Blueprints/stack_version",
-        "Blueprints/security",
-        "host_groups",
-        "host_groups/components",
-        "host_groups/cardinality",
-        "configurations",
-        "validate_topology",
-        "settings",
-        "mpack_instances"
-    ],
     "Recommendation":[
         "Recommendation/id",
         "Versions/stack_name",
diff --git a/ambari-server/src/main/resources/stack-hooks/before-ANY/scripts/params.py b/ambari-server/src/main/resources/stack-hooks/before-ANY/scripts/params.py
index 20992e2..e050db5 100644
--- a/ambari-server/src/main/resources/stack-hooks/before-ANY/scripts/params.py
+++ b/ambari-server/src/main/resources/stack-hooks/before-ANY/scripts/params.py
@@ -243,7 +243,7 @@ except ValueError:
 user_to_gid_dict = collections.defaultdict(lambda:user_group)
 
 user_list = json.loads(config['hostLevelParams']['user_list'])
-group_list = json.loads(config['hostLevelParams']['group_list'])
+group_list = set(json.loads(config['hostLevelParams']['group_list']) + [user_group])
 host_sys_prepped = default("/hostLevelParams/host_sys_prepped", False)
 
 tez_am_view_acls = config['configurations']['tez-site']["tez.am.view-acls"]
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
index cedf3a2..5c83c82 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelperTest.java
@@ -42,6 +42,8 @@ import org.apache.ambari.server.state.ServiceInfo;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * StackAdvisorHelper unit tests.
  */
@@ -112,8 +114,11 @@ public class StackAdvisorHelperTest {
     StackAdvisorCommand<RecommendationResponse> command = mock(StackAdvisorCommand.class);
     RecommendationResponse expected = mock(RecommendationResponse.class);
     StackAdvisorRequestType requestType = StackAdvisorRequestType.HOST_GROUPS;
-    StackAdvisorRequest request = StackAdvisorRequestBuilder.forStack("stackName", "stackVersion")
-        .ofType(requestType).build();
+    StackAdvisorRequest request = StackAdvisorRequestBuilder
+      .forStack("stackName", "stackVersion")
+      .forServices(ImmutableSet.of("ZOOKEEPER"))
+      .ofType(requestType)
+      .build();
 
     when(command.invoke(request, ServiceInfo.ServiceAdvisorType.PYTHON)).thenReturn(expected);
     doReturn(command).when(helper).createRecommendationCommand("ZOOKEEPER", request);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessorTest.java
index dae6fdf..341e889 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessorTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessorTest.java
@@ -1,8 +1,5 @@
 package org.apache.ambari.server.controller.internal;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.isA;
 import static org.junit.Assert.assertEquals;
 
 import java.util.Collection;
@@ -11,11 +8,8 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ambari.server.controller.AmbariManagementController;
-import org.apache.ambari.server.controller.StackLevelConfigurationRequest;
-import org.apache.ambari.server.controller.StackServiceResponse;
 import org.apache.ambari.server.state.DependencyInfo;
-import org.easymock.EasyMockSupport;
+import org.apache.ambari.server.state.StackInfo;
 import org.junit.Test;
 
 /**
@@ -42,15 +36,7 @@ public class BaseBlueprintProcessorTest {
   //todo: BaseBluprintProcess no longer exists.
   @Test
   public void testStackRegisterConditionalDependencies() throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
+    StackInfo stackInfo = new StackInfo();
 
         // test dependencies
     final DependencyInfo hCatDependency = new TestDependencyInfo("HIVE/HCAT");
@@ -63,10 +49,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo oozieClientDependency = new TestDependencyInfo(
         "OOZIE/OOZIE_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(stackInfo) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -138,22 +122,10 @@ public class BaseBlueprintProcessorTest {
         "OOZIE",
         testStack.getDependencyConditionalServiceMap().get(
             oozieClientDependency));
-
-    mockSupport.verifyAll();
   }
 
   @Test
   public void testStackRegisterConditionalDependenciesNoHCAT() throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-
     // test dependencies
     final DependencyInfo yarnClientDependency = new TestDependencyInfo(
         "YARN/YARN_CLIENT");
@@ -164,10 +136,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo oozieClientDependency = new TestDependencyInfo(
         "OOZIE/OOZIE_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(new StackInfo()) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -236,23 +206,11 @@ public class BaseBlueprintProcessorTest {
         "OOZIE",
         testStack.getDependencyConditionalServiceMap().get(
             oozieClientDependency));
-
-    mockSupport.verifyAll();
   }
 
   @Test
   public void testStackRegisterConditionalDependenciesNoYarnClient()
       throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-
     // test dependencies
     final DependencyInfo hCatDependency = new TestDependencyInfo("HIVE/HCAT");
     final DependencyInfo tezClientDependency = new TestDependencyInfo(
@@ -262,10 +220,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo oozieClientDependency = new TestDependencyInfo(
         "OOZIE/OOZIE_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(new StackInfo()) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -332,23 +288,11 @@ public class BaseBlueprintProcessorTest {
         "OOZIE",
         testStack.getDependencyConditionalServiceMap().get(
             oozieClientDependency));
-
-    mockSupport.verifyAll();
   }
 
   @Test
   public void testStackRegisterConditionalDependenciesNoTezClient()
       throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-
     // test dependencies
     final DependencyInfo hCatDependency = new TestDependencyInfo("HIVE/HCAT");
     final DependencyInfo yarnClientDependency = new TestDependencyInfo(
@@ -358,10 +302,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo oozieClientDependency = new TestDependencyInfo(
         "OOZIE/OOZIE_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(new StackInfo()) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -430,23 +372,11 @@ public class BaseBlueprintProcessorTest {
         "OOZIE",
         testStack.getDependencyConditionalServiceMap().get(
             oozieClientDependency));
-
-    mockSupport.verifyAll();
   }
 
   @Test
   public void testStackRegisterConditionalDependenciesNoMapReduceClient()
       throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-
     // test dependencies
     final DependencyInfo hCatDependency = new TestDependencyInfo("HIVE/HCAT");
     final DependencyInfo yarnClientDependency = new TestDependencyInfo(
@@ -456,10 +386,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo oozieClientDependency = new TestDependencyInfo(
         "OOZIE/OOZIE_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(new StackInfo()) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -526,23 +454,11 @@ public class BaseBlueprintProcessorTest {
         "OOZIE",
         testStack.getDependencyConditionalServiceMap().get(
             oozieClientDependency));
-
-    mockSupport.verifyAll();
   }
 
   @Test
   public void testStackRegisterConditionalDependenciesNoOozieClient()
       throws Exception {
-    EasyMockSupport mockSupport = new EasyMockSupport();
-    AmbariManagementController mockMgmtController = mockSupport.createMock(AmbariManagementController.class);
-
-    // setup mock expectations
-    expect(mockMgmtController.getStackServices(isA(Set.class))).andReturn(
-        Collections.<StackServiceResponse> emptySet());
-
-    expect(mockMgmtController.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-
     // test dependencies
     final DependencyInfo hCatDependency = new TestDependencyInfo("HIVE/HCAT");
     final DependencyInfo yarnClientDependency = new TestDependencyInfo(
@@ -552,10 +468,8 @@ public class BaseBlueprintProcessorTest {
     final DependencyInfo mapReduceTwoClientDependency = new TestDependencyInfo(
         "YARN/MAPREDUCE2_CLIENT");
 
-    mockSupport.replayAll();
-
     // create stack for testing
-    Stack testStack = new Stack("HDP", "2.1", mockMgmtController) {
+    Stack testStack = new Stack(new StackInfo()) {
       @Override
       public Collection<DependencyInfo> getDependenciesForComponent(
           String component) {
@@ -622,231 +536,8 @@ public class BaseBlueprintProcessorTest {
         "MAPREDUCE2",
         testStack.getDependencyConditionalServiceMap().get(
             mapReduceTwoClientDependency));
-
-    mockSupport.verifyAll();
   }
 
-
- //todo: validate method moved
-//  @Test
-//  public void testValidationOverrideForSecondaryNameNodeWithHA() throws Exception {
-//    EasyMockSupport mockSupport = new EasyMockSupport();
-//
-//    AmbariManagementController mockController =
-//      mockSupport.createMock(AmbariManagementController.class);
-//
-//    AmbariMetaInfo mockMetaInfo =
-//      mockSupport.createMock(AmbariMetaInfo.class);
-//
-//    BaseBlueprintProcessor.stackInfo = mockMetaInfo;
-//
-//    ServiceInfo serviceInfo = new ServiceInfo();
-//    serviceInfo.setName("HDFS");
-//
-//    StackServiceResponse stackServiceResponse =
-//      new StackServiceResponse(serviceInfo);
-//
-//    ComponentInfo componentInfo = new ComponentInfo();
-//    componentInfo.setName("SECONDARY_NAMENODE");
-//    // simulate the stack requirements that there
-//    // always be one SECONDARY_NAMENODE per cluster
-//    componentInfo.setCardinality("1");
-//
-//    StackServiceComponentResponse stackComponentResponse =
-//      new StackServiceComponentResponse(componentInfo);
-//
-//    ComponentInfo componentInfoNameNode = new ComponentInfo();
-//    componentInfoNameNode.setName("NAMENODE");
-//    componentInfo.setCardinality("1-2");
-//    StackServiceComponentResponse stackServiceComponentResponseTwo =
-//      new StackServiceComponentResponse(componentInfoNameNode);
-//
-//    Set<StackServiceComponentResponse> responses =
-//      new HashSet<StackServiceComponentResponse>();
-//    responses.add(stackComponentResponse);
-//    responses.add(stackServiceComponentResponseTwo);
-//
-//    expect(mockController.getStackServices(isA(Set.class))).andReturn(
-//      Collections.singleton(stackServiceResponse));
-//    expect(mockController.getStackComponents(isA(Set.class))).andReturn(
-//      responses);
-//    expect(mockController.getStackConfigurations(isA(Set.class))).andReturn(Collections.<StackConfigurationResponse>emptySet());
-//    expect(mockController.getStackLevelConfigurations(isA(Set.class))).andReturn(Collections.<StackConfigurationResponse>emptySet());
-//
-//    expect(mockMetaInfo.getComponentDependencies("HDP", "2.0.6", "HDFS", "SECONDARY_NAMENODE")).andReturn(Collections.<DependencyInfo>emptyList());
-//    expect(mockMetaInfo.getComponentDependencies("HDP", "2.0.6", "HDFS", "NAMENODE")).andReturn(Collections.<DependencyInfo>emptyList());
-//
-//
-//    mockSupport.replayAll();
-//
-//    BaseBlueprintProcessor baseBlueprintProcessor =
-//      new BaseBlueprintProcessor(Collections.<String>emptySet(), Collections.<Resource.Type, String>emptyMap(), mockController) {
-//        @Override
-//        protected Set<String> getPKPropertyIds() {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus createResources(Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus updateResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus deleteResources(Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//      };
-//
-//    HostGroupComponentEntity hostGroupComponentEntity =
-//      new HostGroupComponentEntity();
-//    // don't include the SECONDARY_NAMENODE in this entity
-//    hostGroupComponentEntity.setName("NAMENODE");
-//
-//    HostGroupEntity hostGroupEntity =
-//      new HostGroupEntity();
-//    hostGroupEntity.setName("host-group-one");
-//    hostGroupEntity.setComponents(Collections.singleton(hostGroupComponentEntity));
-//    hostGroupEntity.setConfigurations(Collections.<HostGroupConfigEntity>emptyList());
-//
-//    // setup config entity to simulate the case of NameNode HA being enabled
-//    BlueprintConfigEntity configEntity =
-//      new BlueprintConfigEntity();
-//    configEntity.setConfigData("{\"dfs.nameservices\":\"mycluster\",\"key4\":\"value4\"}");
-//    configEntity.setType("hdfs-site");
-//
-//    BlueprintEntity testEntity =
-//      new BlueprintEntity();
-//    testEntity.setBlueprintName("test-blueprint");
-//    testEntity.setStackName("HDP");
-//    testEntity.setStackVersion("2.0.6");
-//    testEntity.setHostGroups(Collections.singleton(hostGroupEntity));
-//    testEntity.setConfigurations(Collections.singleton(configEntity));
-//
-//    baseBlueprintProcessor.validateTopology(testEntity);
-//
-//    mockSupport.verifyAll();
-//  }
-
-//  @Test
-//  public void testValidationOverrideForSecondaryNameNodeWithoutHA() throws Exception {
-//    EasyMockSupport mockSupport = new EasyMockSupport();
-//
-//    AmbariManagementController mockController =
-//      mockSupport.createMock(AmbariManagementController.class);
-//
-//    AmbariMetaInfo mockMetaInfo =
-//      mockSupport.createMock(AmbariMetaInfo.class);
-//
-//    BaseBlueprintProcessor.stackInfo = mockMetaInfo;
-//
-//    ServiceInfo serviceInfo = new ServiceInfo();
-//    serviceInfo.setName("HDFS");
-//
-//    StackServiceResponse stackServiceResponse =
-//      new StackServiceResponse(serviceInfo);
-//
-//    ComponentInfo componentInfo = new ComponentInfo();
-//    componentInfo.setName("SECONDARY_NAMENODE");
-//    // simulate the stack requirements that there
-//    // always be one SECONDARY_NAMENODE per cluster
-//    componentInfo.setCardinality("1");
-//
-//    StackServiceComponentResponse stackComponentResponse =
-//      new StackServiceComponentResponse(componentInfo);
-//
-//    ComponentInfo componentInfoNameNode = new ComponentInfo();
-//    componentInfoNameNode.setName("NAMENODE");
-//    componentInfo.setCardinality("1-2");
-//    StackServiceComponentResponse stackServiceComponentResponseTwo =
-//      new StackServiceComponentResponse(componentInfoNameNode);
-//
-//    Set<StackServiceComponentResponse> responses =
-//      new HashSet<StackServiceComponentResponse>();
-//    responses.add(stackComponentResponse);
-//    responses.add(stackServiceComponentResponseTwo);
-//
-//    expect(mockController.getStackServices(isA(Set.class))).andReturn(
-//      Collections.singleton(stackServiceResponse));
-//    expect(mockController.getStackComponents(isA(Set.class))).andReturn(
-//      responses);
-//    expect(mockController.getStackConfigurations(isA(Set.class))).andReturn(Collections.<StackConfigurationResponse>emptySet());
-//    expect(mockController.getStackLevelConfigurations(isA(Set.class))).andReturn(Collections.<StackConfigurationResponse>emptySet());
-//
-//    expect(mockMetaInfo.getComponentDependencies("HDP", "2.0.6", "HDFS", "SECONDARY_NAMENODE")).andReturn(Collections.<DependencyInfo>emptyList());
-//    expect(mockMetaInfo.getComponentDependencies("HDP", "2.0.6", "HDFS", "NAMENODE")).andReturn(Collections.<DependencyInfo>emptyList());
-//
-//
-//    mockSupport.replayAll();
-//
-//    BaseBlueprintProcessor baseBlueprintProcessor =
-//      new BaseBlueprintProcessor(Collections.<String>emptySet(), Collections.<Resource.Type, String>emptyMap(), mockController) {
-//        @Override
-//        protected Set<String> getPKPropertyIds() {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus createResources(Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus updateResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//
-//        @Override
-//        public RequestStatus deleteResources(Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-//          return null;
-//        }
-//      };
-//
-//    HostGroupComponentEntity hostGroupComponentEntity =
-//      new HostGroupComponentEntity();
-//    // don't include the SECONDARY_NAMENODE in this entity
-//    hostGroupComponentEntity.setName("NAMENODE");
-//
-//    HostGroupEntity hostGroupEntity =
-//      new HostGroupEntity();
-//    hostGroupEntity.setName("host-group-one");
-//    hostGroupEntity.setComponents(Collections.singleton(hostGroupComponentEntity));
-//    hostGroupEntity.setConfigurations(Collections.<HostGroupConfigEntity>emptyList());
-//
-//
-//
-//    BlueprintEntity testEntity =
-//      new BlueprintEntity();
-//    testEntity.setBlueprintName("test-blueprint");
-//    testEntity.setStackName("HDP");
-//    testEntity.setStackVersion("2.0.6");
-//    testEntity.setHostGroups(Collections.singleton(hostGroupEntity));
-//    testEntity.setConfigurations(Collections.<BlueprintConfigEntity>emptyList());
-//
-//    try {
-//      baseBlueprintProcessor.validateTopology(testEntity);
-//      fail("IllegalArgumentException should have been thrown");
-//    } catch (IllegalArgumentException expectedException) {
-//      // expected exception
-//    }
-//
-//    mockSupport.verifyAll();
-//  }
-
   /**
    * Convenience class for easier setup/initialization of dependencies for unit
    * testing.
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
index d9d674d..2328370 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintConfigurationProcessorTest.java
@@ -104,6 +104,7 @@ public class BlueprintConfigurationProcessorTest extends EasyMockSupport {
 
   private final String STACK_NAME = "testStack";
   private final String STACK_VERSION = "1";
+  private final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
 
   @Rule
   public EasyMockRule mocks = new EasyMockRule(this);
@@ -144,6 +145,7 @@ public class BlueprintConfigurationProcessorTest extends EasyMockSupport {
   @Before
   public void init() throws Exception {
     expect(bp.getStack()).andReturn(stack).anyTimes();
+    expect(bp.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(bp.getName()).andReturn("test-bp").anyTimes();
 
     expect(stack.getName()).andReturn(STACK_NAME).atLeastOnce();
@@ -8232,7 +8234,7 @@ public class BlueprintConfigurationProcessorTest extends EasyMockSupport {
       }
 
       //create host group which is set on topology
-      allHostGroups.put(hostGroup.name, new HostGroupImpl(hostGroup.name, "test-bp", Collections.singleton(stack),
+      allHostGroups.put(hostGroup.name, new HostGroupImpl(hostGroup.name, "test-bp", stack,
         componentList, EMPTY_CONFIG, "1"));
 
       hostGroupInfo.put(hostGroup.name, groupInfo);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintResourceProviderTest.java
index e303731..dece002 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/BlueprintResourceProviderTest.java
@@ -19,6 +19,7 @@
 package org.apache.ambari.server.controller.internal;
 
 import static org.easymock.EasyMock.anyBoolean;
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.createStrictMock;
@@ -47,6 +48,7 @@ import java.util.Set;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.ResourceProviderFactory;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider.BlueprintConfigPopulationStrategy;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider.BlueprintConfigPopulationStrategyV1;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider.BlueprintConfigPopulationStrategyV2;
@@ -73,20 +75,18 @@ import org.apache.ambari.server.state.SecurityType;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.BlueprintFactory;
+import org.apache.ambari.server.topology.BlueprintValidator;
 import org.apache.ambari.server.topology.InvalidTopologyException;
 import org.apache.ambari.server.topology.SecurityConfiguration;
 import org.apache.ambari.server.topology.SecurityConfigurationFactory;
 import org.apache.ambari.server.topology.Setting;
 import org.apache.ambari.server.utils.StageUtils;
-import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-import com.google.gson.Gson;
-
 /**
  * BlueprintResourceProvider unit tests.
  */
@@ -98,18 +98,21 @@ public class BlueprintResourceProviderTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
+  private static final ResourceProviderFactory resourceProviderFactory = createNiceMock(ResourceProviderFactory.class);
   private final static BlueprintDAO dao = createStrictMock(BlueprintDAO.class);
   private final static BlueprintEntity entity = createStrictMock(BlueprintEntity.class);
   private final static Blueprint blueprint = createMock(Blueprint.class);
+  private final static BlueprintValidator blueprintValidator = createMock(BlueprintValidator.class);
   private final static AmbariMetaInfo metaInfo = createMock(AmbariMetaInfo.class);
   private final static BlueprintFactory blueprintFactory = createMock(BlueprintFactory.class);
   private final static SecurityConfigurationFactory securityFactory = createMock(SecurityConfigurationFactory.class);
   private final static BlueprintResourceProvider provider = createProvider();
-  private final static Gson gson = new Gson();
 
   @BeforeClass
   public static void initClass() {
-    BlueprintResourceProvider.init(blueprintFactory, dao, securityFactory, metaInfo);
+    AbstractControllerResourceProvider.init(resourceProviderFactory);
+    expect(resourceProviderFactory.getBlueprintResourceProvider(anyObject())).andReturn(provider).anyTimes();
+    replay(resourceProviderFactory);
   }
 
   private Map<String, Set<HashMap<String, String>>> getSettingProperties() {
@@ -118,7 +121,7 @@ public class BlueprintResourceProviderTest {
 
   @Before
   public void resetGlobalMocks() {
-    reset(dao, metaInfo, blueprintFactory, securityFactory, blueprint, entity);
+    reset(dao, metaInfo, blueprintFactory, securityFactory, blueprint, blueprintValidator, entity);
   }
 
   @Test
@@ -135,8 +138,8 @@ public class BlueprintResourceProviderTest {
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
     expect(securityFactory.createSecurityConfigurationFromRequest(null, true)).andReturn(null).anyTimes();
-    blueprint.validateRequiredProperties();
-    blueprint.validateTopology();
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -146,7 +149,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -165,7 +168,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, securityFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, securityFactory, metaInfo, request, managementController);
   }
 
   @Test()
@@ -212,7 +215,7 @@ public class BlueprintResourceProviderTest {
 
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
+    blueprintValidator.validateRequiredProperties(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -222,7 +225,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -241,7 +244,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request, managementController);
   }
 
   @Test
@@ -253,16 +256,16 @@ public class BlueprintResourceProviderTest {
 
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
     expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).atLeastOnce();
-    blueprint.validateTopology();
-    expectLastCall().andThrow(new InvalidTopologyException("test"));
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
+    expectLastCall().andThrow(new InvalidTopologyException("test")).once();
 
     expect(request.getProperties()).andReturn(setProperties);
     expect(request.getRequestInfoProperties()).andReturn(requestInfoProperties);
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
 
-    replay(dao, entity, metaInfo, blueprintFactory, blueprint, request);
+    replay(dao, entity, metaInfo, blueprintFactory, blueprint, blueprintValidator, request);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -279,7 +282,7 @@ public class BlueprintResourceProviderTest {
       // expected
     }
 
-    verify(dao, entity, blueprintFactory, metaInfo, request);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request);
   }
 
 
@@ -296,8 +299,8 @@ public class BlueprintResourceProviderTest {
 
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
-    blueprint.validateTopology();
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -307,7 +310,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -326,7 +329,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request, managementController);
   }
 
   @Test
@@ -346,7 +349,7 @@ public class BlueprintResourceProviderTest {
     expect(request.getProperties()).andReturn(setProperties);
     expect(request.getRequestInfoProperties()).andReturn(requestInfoProperties);
 
-    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, request);
+    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, blueprintValidator, request);
     // end expectations
 
     try {
@@ -355,7 +358,7 @@ public class BlueprintResourceProviderTest {
     } catch (IllegalArgumentException e) {
       // expected
     }
-    verify(dao, entity, blueprintFactory, metaInfo, request);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request);
   }
 
   @Test
@@ -370,11 +373,11 @@ public class BlueprintResourceProviderTest {
     SecurityConfiguration securityConfiguration = new SecurityConfiguration(SecurityType.KERBEROS, "testRef", null);
 
     // set expectations
-    expect(securityFactory.createSecurityConfigurationFromRequest(EasyMock.anyObject(), anyBoolean())).andReturn
+    expect(securityFactory.createSecurityConfigurationFromRequest(anyObject(), anyBoolean())).andReturn
       (securityConfiguration).once();
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), securityConfiguration)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
-    blueprint.validateTopology();
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -384,7 +387,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, securityFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -403,7 +406,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request, managementController);
   }
 
   @Test
@@ -499,8 +502,8 @@ public class BlueprintResourceProviderTest {
 
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
-    blueprint.validateTopology();
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -510,7 +513,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -529,7 +532,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request, managementController);
   }
 
   @Test
@@ -546,8 +549,8 @@ public class BlueprintResourceProviderTest {
 
     // set expectations
     expect(blueprintFactory.createBlueprint(setProperties.iterator().next(), null)).andReturn(blueprint).once();
-    blueprint.validateRequiredProperties();
-    blueprint.validateTopology();
+    blueprintValidator.validateRequiredProperties(blueprint);
+    blueprintValidator.validateTopology(blueprint);
     expect(blueprint.getSetting()).andReturn(setting).anyTimes();
     expect(setting.getProperties()).andReturn(settingProperties).anyTimes();
     expect(blueprint.toEntity()).andReturn(entity);
@@ -557,7 +560,7 @@ public class BlueprintResourceProviderTest {
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(null);
     dao.create(entity);
 
-    replay(dao, entity, metaInfo, blueprintFactory, blueprint, setting, request, managementController);
+    replay(dao, entity, metaInfo, blueprintFactory, blueprint, blueprintValidator, setting, request, managementController);
     // end expectations
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
@@ -576,7 +579,7 @@ public class BlueprintResourceProviderTest {
     assertEquals(request, lastEvent.getRequest());
     assertNull(lastEvent.getPredicate());
 
-    verify(dao, entity, blueprintFactory, metaInfo, request, managementController);
+    verify(dao, entity, blueprintFactory, blueprint, blueprintValidator, metaInfo, request, managementController);
   }
 
   @Test
@@ -795,10 +798,6 @@ public class BlueprintResourceProviderTest {
 
   }
 
-  private static BlueprintResourceProvider createProvider() {
-    return new BlueprintResourceProvider(null);
-  }
-
   private BlueprintEntity createEntity(Map<String, Object> properties) {
     BlueprintEntity entity = new BlueprintEntity();
     entity.setBlueprintName((String) properties.get(BlueprintResourceProvider.BLUEPRINT_NAME_PROPERTY_ID));
@@ -841,7 +840,7 @@ public class BlueprintResourceProviderTest {
 
     Collection<Map<String, String>> configProperties = (Collection<Map<String, String>>) properties.get(
         BlueprintResourceProvider.CONFIGURATION_PROPERTY_ID);
-    createProvider().createBlueprintConfigEntities(configProperties, entity);
+    provider.createBlueprintConfigEntities(configProperties, entity);
     return entity;
   }
 
@@ -1208,5 +1207,9 @@ public class BlueprintResourceProviderTest {
     assertTrue(setting3value.get(1).containsKey("recovery_enabled"));
     assertEquals(setting3value.get(1).get("recovery_enabled"), "false");
   }
+
+  private static BlueprintResourceProvider createProvider() {
+    return new BlueprintResourceProvider(blueprintValidator, blueprintFactory, dao, securityFactory, metaInfo, null);
+  }
 }
 
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CompositeStackTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CompositeStackTest.java
new file mode 100644
index 0000000..0561bb4
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CompositeStackTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.controller.internal;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toSet;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.ambari.server.stack.StackManager;
+import org.apache.ambari.server.stack.StackManagerTest;
+import org.apache.ambari.server.state.StackId;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class CompositeStackTest {
+
+  private static Set<Stack> elements;
+  private static StackDefinition composite;
+
+  @BeforeClass
+  public static void initStack() throws Exception{
+    StackManager stackManager = StackManagerTest.createTestStackManager();
+    elements = stackManager.getStacksByName().values().stream()
+      .flatMap(stacks -> stacks.stream().limit(1))
+      .filter(Objects::nonNull)
+      .map(Stack::new)
+      .collect(toSet());
+    composite = StackDefinition.of(elements);
+  }
+
+  @Test
+  public void getStackIds() {
+    assertEquals(elements.size(), composite.getStackIds().size());
+  }
+
+  @Test
+  public void getServices() {
+    Set<String> services = new HashSet<>(composite.getServices());
+    for (Stack stack : elements) {
+      assertTrue(services.containsAll(stack.getServices()));
+    }
+    for (Stack stack : elements) {
+      services.removeAll(stack.getServices());
+    }
+    assertEquals(emptySet(), services);
+  }
+
+  @Test
+  public void getComponents() {
+    Set<String> components = new HashSet<>(composite.getComponents());
+    for (Stack stack : elements) {
+      assertTrue(components.containsAll(stack.getComponents()));
+    }
+    for (Stack stack : elements) {
+      components.removeAll(stack.getComponents());
+    }
+    assertEquals(emptySet(), components);
+  }
+
+  @Test
+  public void getStacksForService() {
+    Set<String> services = new HashSet<>(composite.getServices());
+    for (String service : services) {
+      Set<StackId> stackIds = new HashSet<>();
+      for (Stack stack : elements) {
+        if (stack.getServices().contains(service)) {
+          stackIds.add(stack.getStackId());
+        }
+      }
+      assertEquals(service, stackIds, composite.getStacksForService(service));
+    }
+  }
+
+  @Test
+  public void getStacksForComponent() {
+    Set<String> components = new HashSet<>(composite.getComponents());
+    for (String component : components) {
+      Set<StackId> stackIds = new HashSet<>();
+      for (Stack stack : elements) {
+        if (stack.getComponents().contains(component)) {
+          stackIds.add(stack.getStackId());
+        }
+      }
+      assertEquals(component, stackIds, composite.getStacksForComponent(component));
+    }
+  }
+
+  // TODO add more tests after StackDefinition interface is finalized
+
+}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequestTest.java
index e70901a..65d89c1 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequestTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExportBlueprintRequestTest.java
@@ -19,7 +19,6 @@
 
 package org.apache.ambari.server.controller.internal;
 
-import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
@@ -39,14 +38,13 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.api.util.TreeNodeImpl;
 import org.apache.ambari.server.controller.AmbariManagementController;
-import org.apache.ambari.server.controller.StackLevelConfigurationRequest;
-import org.apache.ambari.server.controller.StackServiceRequest;
 import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.HostGroup;
 import org.apache.ambari.server.topology.HostGroupInfo;
@@ -76,11 +74,14 @@ public class ExportBlueprintRequestTest {
     f.setAccessible(true);
     f.set(null, controller);
 
-    expect(controller.getStackServices((Set<StackServiceRequest>)  anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-    expect(controller.getStackLevelConfigurations((Set<StackLevelConfigurationRequest>) anyObject())).andReturn(
-        Collections.emptySet()).anyTimes();
-    replay(controller);
+    AmbariMetaInfo metainfo = createNiceMock(AmbariMetaInfo.class);
+    expect(controller.getAmbariMetaInfo()).andReturn(metainfo).anyTimes();
+    StackInfo stackInfo = createNiceMock(StackInfo.class);
+    expect(metainfo.getStack("TEST", "1.0")).andReturn(stackInfo);
+    expect(stackInfo.getServices()).andReturn(Collections.emptySet()).anyTimes();
+    expect(stackInfo.getProperties()).andReturn(Collections.emptyList()).anyTimes();
+
+    replay(controller, metainfo, stackInfo);
 
     // This can save precious time
     mockStatic(InetAddress.class);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackTest.java
index 9e61a70..e6794b8 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackTest.java
@@ -18,119 +18,226 @@
 
 package org.apache.ambari.server.controller.internal;
 
-import static org.easymock.EasyMock.capture;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
 import static org.easymock.EasyMock.expect;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.powermock.api.easymock.PowerMock.createNiceMock;
-import static org.powermock.api.easymock.PowerMock.replay;
-import static org.powermock.api.easymock.PowerMock.verifyAll;
 
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Stream;
 
-import org.apache.ambari.server.api.services.AmbariMetaInfo;
-import org.apache.ambari.server.controller.AmbariManagementController;
-import org.apache.ambari.server.controller.StackConfigurationRequest;
-//import org.apache.ambari.server.controller.StackConfigurationResponse;
-import org.apache.ambari.server.controller.StackLevelConfigurationRequest;
 import org.apache.ambari.server.controller.StackLevelConfigurationResponse;
-import org.apache.ambari.server.controller.StackServiceComponentRequest;
-import org.apache.ambari.server.controller.StackServiceComponentResponse;
-import org.apache.ambari.server.controller.StackServiceRequest;
-import org.apache.ambari.server.controller.StackServiceResponse;
+import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.PropertyDependencyInfo;
 import org.apache.ambari.server.state.PropertyInfo;
+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.Configuration;
-import org.easymock.Capture;
-import org.easymock.EasyMock;
+import org.apache.commons.lang3.tuple.Pair;
 import org.easymock.EasyMockSupport;
+import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 
 /**
  * Stack unit tests.
  */
 public class StackTest {
 
+  private static final StackId STACK_ID = new StackId("stack name", "1.0");
+  private static final String STACK_CONFIG_TYPE = "cluster-env";
+  private static final String STACK_CONFIG_FILE = STACK_CONFIG_TYPE + ".xml";
+  private static final String SERVICE_CONFIG_TYPE = "test-site";
+  private static final String SERVICE_CONFIG_FILE = SERVICE_CONFIG_TYPE + ".xml";
+
+  private StackInfo stackInfo;
+  private ServiceInfo serviceInfo;
+  private ComponentInfo componentInfo;
+  private PropertyInfo serviceLevelProperty;
+  private PropertyInfo stackLevelProperty;
+  private PropertyInfo optionalServiceLevelProperty;
+  private PropertyInfo passwordProperty;
+
+  @Before
+  public void setUp() {
+    stackInfo = new StackInfo();
+    stackInfo.setName(STACK_ID.getStackName());
+    stackInfo.setVersion(STACK_ID.getStackVersion());
+
+    serviceInfo = new ServiceInfo();
+    serviceInfo.setName("some service");
+    stackInfo.getServices().add(serviceInfo);
+
+    componentInfo = new ComponentInfo();
+    componentInfo.setName("some component");
+    serviceInfo.getComponents().add(componentInfo);
+
+    serviceLevelProperty = new PropertyInfo();
+    serviceLevelProperty.setName("service_level");
+    serviceLevelProperty.setValue("service-level value");
+    serviceLevelProperty.setFilename(SERVICE_CONFIG_FILE);
+    serviceLevelProperty.setRequireInput(true);
+    serviceLevelProperty.getPropertyTypes().add(PropertyInfo.PropertyType.TEXT);
+    serviceInfo.getProperties().add(serviceLevelProperty);
+
+    passwordProperty = new PropertyInfo();
+    passwordProperty.setName("a_password");
+    passwordProperty.setValue("secret");
+    passwordProperty.setFilename(SERVICE_CONFIG_FILE);
+    passwordProperty.setRequireInput(true);
+    passwordProperty.getPropertyTypes().add(PropertyInfo.PropertyType.PASSWORD);
+    serviceInfo.getProperties().add(passwordProperty);
+
+    optionalServiceLevelProperty = new PropertyInfo();
+    optionalServiceLevelProperty.setName("optional_service_level");
+    optionalServiceLevelProperty.setValue("service-level value (optional)");
+    optionalServiceLevelProperty.setFilename(SERVICE_CONFIG_FILE);
+    optionalServiceLevelProperty.setRequireInput(false);
+    optionalServiceLevelProperty.getPropertyTypes().add(PropertyInfo.PropertyType.USER);
+    serviceInfo.getProperties().add(optionalServiceLevelProperty);
+
+    stackLevelProperty = new PropertyInfo();
+    stackLevelProperty.setName("stack_level");
+    stackLevelProperty.setValue("stack-level value");
+    stackLevelProperty.setFilename(STACK_CONFIG_FILE);
+    stackLevelProperty.setRequireInput(true);
+    stackLevelProperty.getPropertyTypes().add(PropertyInfo.PropertyType.TEXT);
+    stackInfo.getProperties().add(stackLevelProperty);
+  }
+
+  @Test
+  public void stackHasCorrectNameAndVersion() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
+
+    // THEN
+    assertEquals(stackInfo.getName(), stack.getName());
+    assertEquals(stackInfo.getVersion(), stack.getVersion());
+  }
+
   @Test
-  public void testTestXmlExtensionStrippedOff() throws Exception {
-    AmbariManagementController controller = createNiceMock(AmbariManagementController.class);
-    AmbariMetaInfo metaInfo = createNiceMock(AmbariMetaInfo.class);
-    Capture<Set<StackServiceRequest>> stackServiceRequestCapture = EasyMock.newCapture();
-    StackServiceResponse stackServiceResponse = createNiceMock(StackServiceResponse.class);
-    Capture<Set<StackServiceComponentRequest>> stackComponentRequestCapture = EasyMock.newCapture();
-    StackServiceComponentResponse stackComponentResponse = createNiceMock(StackServiceComponentResponse.class);
-    Capture<Set<StackConfigurationRequest>> stackConfigurationRequestCapture = EasyMock.newCapture();
-    Capture<Set<StackLevelConfigurationRequest>> stackLevelConfigurationRequestCapture = EasyMock.newCapture();
-    StackLevelConfigurationResponse stackConfigurationResponse = EasyMock.createNiceMock(StackLevelConfigurationResponse.class);
+  public void getServices() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    expect(controller.getStackServices(capture(stackServiceRequestCapture))).
-        andReturn(Collections.singleton(stackServiceResponse)).anyTimes();
+    // WHEN
+    Collection<String> services = stack.getServices();
 
-    expect(controller.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes();
+    // THEN
+    assertEquals(ImmutableSet.of(serviceInfo.getName()), ImmutableSet.copyOf(services));
+  }
 
-    expect(stackServiceResponse.getServiceName()).andReturn("service1").anyTimes();
-    expect(stackServiceResponse.getExcludedConfigTypes()).andReturn(Collections.emptySet());
-    expect(stackServiceResponse.getConfigTypes()).andReturn(Collections.emptyMap());
+  @Test
+  public void getServicesForOwnStackId() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    expect(controller.getStackComponents(capture(stackComponentRequestCapture))).
-        andReturn(Collections.singleton(stackComponentResponse)).anyTimes();
+    // WHEN
+    Collection<String> services = stack.getServices(stack.getStackId());
+
+    // THEN
+    assertEquals(ImmutableSet.of(serviceInfo.getName()), ImmutableSet.copyOf(services));
+  }
 
-    expect(stackComponentResponse.getComponentName()).andReturn("component1").anyTimes();
-    expect(stackComponentResponse.getComponentCategory()).andReturn("test-site.xml").anyTimes();
+  @Test
+  public void getServicesForOtherStackId() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    expect(controller.getStackConfigurations(capture(stackConfigurationRequestCapture))).
-        andReturn(Collections.singleton(stackConfigurationResponse)).anyTimes();
+    // WHEN
+    Collection<String> services = stack.getServices(new StackId("other stack", "1.0"));
 
-    // no stack level configs for this test
-    expect(controller.getStackLevelConfigurations(capture(stackLevelConfigurationRequestCapture))).
-        andReturn(Collections.emptySet()).anyTimes();
+    // THEN
+    assertEquals(ImmutableSet.of(), ImmutableSet.copyOf(services));
+  }
 
-    expect(stackConfigurationResponse.getPropertyName()).andReturn("prop1").anyTimes();
-    expect(stackConfigurationResponse.getPropertyValue()).andReturn("prop1Val").anyTimes();
-    expect(stackConfigurationResponse.getType()).andReturn("test-site.xml").anyTimes();
-    expect(stackConfigurationResponse.getPropertyType()).andReturn(
-        Collections.emptySet()).anyTimes();
-    expect(stackConfigurationResponse.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse.isRequired()).andReturn(true).anyTimes();
+  @Test
+  public void getStacksForServicesInStack() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
+
+    for (String service : stack.getServices()) {
+      // WHEN
+      Set<StackId> stacksForService = stack.getStacksForService(service);
+      // THEN
+      assertEquals("Stack for service " + service, ImmutableSet.of(STACK_ID), stacksForService);
+    }
+  }
 
-    expect(metaInfo.getComponentDependencies("test", "1.0", "service1", "component1")).
-        andReturn(Collections.emptyList()).anyTimes();
+  @Test
+  public void getStacksForUnknownService() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
+    // WHEN
+    Set<StackId> stacksForService = stack.getStacksForService("unknown service");
 
-    replay(controller, stackServiceResponse, stackComponentResponse, stackConfigurationResponse, metaInfo);
+    // THEN
+    assertEquals(ImmutableSet.of(), stacksForService);
+  }
 
+  @Test
+  public void configTypeOmitsFileExtension() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    Stack stack = new Stack("test", "1.0", controller);
-    Configuration configuration = stack.getConfiguration(Collections.singleton("service1"));
-    assertEquals("prop1Val", configuration.getProperties().get("test-site").get("prop1"));
+    // WHEN
+    Configuration configuration = stack.getConfiguration(singleton(serviceInfo.getName()));
 
-    assertEquals("test-site", stack.getRequiredConfigurationProperties("service1").iterator().next().getType());
+    // THEN
+    assertEquals(serviceLevelProperty.getValue(), configuration.getProperties().get(SERVICE_CONFIG_TYPE).get(serviceLevelProperty.getName()));
+  }
 
-    // assertions
-    StackServiceRequest stackServiceRequest = stackServiceRequestCapture.getValue().iterator().next();
-    assertEquals("test", stackServiceRequest.getStackName());
-    assertEquals("1.0", stackServiceRequest.getStackVersion());
+  @Test
+  public void getRequiredPropertiesForService() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
+
+    // WHEN
+    Collection<Stack.ConfigProperty> requiredConfigurationProperties = stack.getRequiredConfigurationProperties(serviceInfo.getName());
+
+    // THEN
+    // should include stack-level property
+    // should exclude optional property
+    Set<Pair<String, String>> actualRequiredProperties = convertToPropertySet(requiredConfigurationProperties);
+    Set<Pair<String, String>> expected = ImmutableSet.of(
+      Pair.of(STACK_CONFIG_TYPE, stackLevelProperty.getName()),
+      Pair.of(SERVICE_CONFIG_TYPE, passwordProperty.getName()),
+      Pair.of(SERVICE_CONFIG_TYPE, serviceLevelProperty.getName())
+    );
+    assertEquals(expected, actualRequiredProperties);
+    assertEquals(expected.size(), requiredConfigurationProperties.size());
+  }
 
-    StackServiceComponentRequest stackComponentRequest = stackComponentRequestCapture.getValue().iterator().next();
-    assertEquals("service1", stackComponentRequest.getServiceName());
-    assertEquals("test", stackComponentRequest.getStackName());
-    assertEquals("1.0", stackComponentRequest.getStackVersion());
-    assertNull(stackComponentRequest.getComponentName());
+  @Test
+  public void getRequiredPropertiesForServiceAndType() throws Exception {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
+
+    // WHEN
+    Collection<Stack.ConfigProperty> requiredConfigurationProperties = stack.getRequiredConfigurationProperties(serviceInfo.getName(), PropertyInfo.PropertyType.TEXT);
+
+    // THEN
+    Set<Pair<String, String>> actualRequiredProperties = convertToPropertySet(requiredConfigurationProperties);
+    Set<Pair<String, String>> expected = ImmutableSet.of(
+      Pair.of(STACK_CONFIG_TYPE, stackLevelProperty.getName()),
+      Pair.of(SERVICE_CONFIG_TYPE, serviceLevelProperty.getName())
+    );
+    assertEquals(expected, actualRequiredProperties);
+    assertEquals(expected.size(), requiredConfigurationProperties.size());
   }
 
   @Test
   public void testConfigPropertyReadsInDependencies() throws Exception {
+    // FIXME get rid of mock
     EasyMockSupport mockSupport = new EasyMockSupport();
 
     Set<PropertyDependencyInfo> setOfDependencyInfo = new HashSet<>();
@@ -158,221 +265,106 @@ public class StackTest {
   }
 
   @Test
-  public void testGetRequiredProperties_serviceAndPropertyType() throws Exception {
-    AmbariManagementController controller = createNiceMock(AmbariManagementController.class);
-    AmbariMetaInfo metaInfo = createNiceMock(AmbariMetaInfo.class);
-    Capture<Set<StackServiceRequest>> stackServiceRequestCapture = EasyMock.newCapture();
-    StackServiceResponse stackServiceResponse = createNiceMock(StackServiceResponse.class);
-    Capture<Set<StackServiceComponentRequest>> stackComponentRequestCapture = EasyMock.newCapture();
-    StackServiceComponentResponse stackComponentResponse = createNiceMock(StackServiceComponentResponse.class);
-    Capture<Set<StackConfigurationRequest>> stackConfigurationRequestCapture = EasyMock.newCapture();
-    Capture<Set<StackLevelConfigurationRequest>> stackLevelConfigurationRequestCapture = EasyMock.newCapture();
-    StackLevelConfigurationResponse stackConfigurationResponse = EasyMock.createNiceMock(StackLevelConfigurationResponse.class);
-    StackLevelConfigurationResponse stackConfigurationResponse2 = EasyMock.createNiceMock(StackLevelConfigurationResponse.class);
-
-    expect(controller.getStackServices(capture(stackServiceRequestCapture))).
-        andReturn(Collections.singleton(stackServiceResponse)).anyTimes();
-
-    expect(controller.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes();
-
-    expect(stackServiceResponse.getServiceName()).andReturn("service1").anyTimes();
-    expect(stackServiceResponse.getExcludedConfigTypes()).andReturn(Collections.emptySet());
-    expect(stackServiceResponse.getConfigTypes()).andReturn(Collections.emptyMap());
-
-    expect(controller.getStackComponents(capture(stackComponentRequestCapture))).
-        andReturn(Collections.singleton(stackComponentResponse)).anyTimes();
-
-    expect(stackComponentResponse.getComponentName()).andReturn("component1").anyTimes();
-    expect(stackComponentResponse.getComponentCategory()).andReturn("test-site.xml").anyTimes();
-
-    expect(controller.getStackConfigurations(capture(stackConfigurationRequestCapture))).
-        andReturn(new HashSet<>(Arrays.asList(
-          stackConfigurationResponse, stackConfigurationResponse2))).anyTimes();
-
-    // no stack level configs for this test
-    expect(controller.getStackLevelConfigurations(capture(stackLevelConfigurationRequestCapture))).
-        andReturn(Collections.emptySet()).anyTimes();
-
-    expect(stackConfigurationResponse.getPropertyName()).andReturn("prop1").anyTimes();
-    expect(stackConfigurationResponse.getPropertyValue()).andReturn(null).anyTimes();
-    expect(stackConfigurationResponse.getType()).andReturn("test-site.xml").anyTimes();
-    expect(stackConfigurationResponse.getPropertyType()).andReturn(
-        Collections.singleton(PropertyInfo.PropertyType.PASSWORD)).anyTimes();
-    expect(stackConfigurationResponse.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse.isRequired()).andReturn(true).anyTimes();
-
-    // not a PASSWORD property type so shouldn't be returned
-    expect(stackConfigurationResponse2.getPropertyName()).andReturn("prop2").anyTimes();
-    expect(stackConfigurationResponse2.getPropertyValue()).andReturn(null).anyTimes();
-    expect(stackConfigurationResponse2.getType()).andReturn("test-site.xml").anyTimes();
-    expect(stackConfigurationResponse2.getPropertyType()).andReturn(
-        Collections.singleton(PropertyInfo.PropertyType.USER)).anyTimes();
-    expect(stackConfigurationResponse2.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse2.isRequired()).andReturn(true).anyTimes();
-
-    expect(metaInfo.getComponentDependencies("test", "1.0", "service1", "component1")).
-        andReturn(Collections.emptyList()).anyTimes();
-
-    replay(controller, stackServiceResponse, stackComponentResponse, stackConfigurationResponse,
-        stackConfigurationResponse2, metaInfo);
-
-    // test
-    Stack stack = new Stack("test", "1.0", controller);
-    // get required password properties
-    Collection<Stack.ConfigProperty> requiredPasswordProperties = stack.getRequiredConfigurationProperties(
-        "service1", PropertyInfo.PropertyType.PASSWORD);
-
-    // assertions
-    assertEquals(1, requiredPasswordProperties.size());
-    Stack.ConfigProperty requiredPasswordConfigProperty = requiredPasswordProperties.iterator().next();
-    assertEquals("test-site", requiredPasswordConfigProperty.getType());
-    assertEquals("prop1", requiredPasswordConfigProperty.getName());
-    assertTrue(requiredPasswordConfigProperty.getPropertyTypes().contains(PropertyInfo.PropertyType.PASSWORD));
-
-    StackServiceRequest stackServiceRequest = stackServiceRequestCapture.getValue().iterator().next();
-    assertEquals("test", stackServiceRequest.getStackName());
-    assertEquals("1.0", stackServiceRequest.getStackVersion());
-
-    StackServiceComponentRequest stackComponentRequest = stackComponentRequestCapture.getValue().iterator().next();
-    assertEquals("service1", stackComponentRequest.getServiceName());
-    assertEquals("test", stackComponentRequest.getStackName());
-    assertEquals("1.0", stackComponentRequest.getStackVersion());
-    assertNull(stackComponentRequest.getComponentName());
-  }
-
-  // Test that getAllConfigurationTypes returns beside the configuration types that have
-  // service config properties defined also the empty ones that doesn't have any config
-  // property defined.
-  @Test
-  public void testGetAllConfigurationTypesWithEmptyStackServiceConfigType() throws Exception {
-    // Given
-    AmbariManagementController controller = createNiceMock(AmbariManagementController.class);
-    AmbariMetaInfo metaInfo = createNiceMock(AmbariMetaInfo.class);
-    StackServiceResponse stackServiceResponse = createNiceMock(StackServiceResponse.class);
-    StackServiceComponentResponse stackComponentResponse = createNiceMock(StackServiceComponentResponse.class);
-    StackLevelConfigurationResponse stackConfigurationResponse1 = createNiceMock(StackLevelConfigurationResponse.class);
-    StackLevelConfigurationResponse stackConfigurationResponse2 = createNiceMock(StackLevelConfigurationResponse.class);
+  public void getAllConfigurationTypesReturnsExcludedOnesToo() throws Exception {
+    // GIVEN
+    serviceInfo.setExcludedConfigTypes(ImmutableSet.of(SERVICE_CONFIG_TYPE));
+    Stack stack = new Stack(stackInfo);
 
-    String testServiceName = "service1";
-    String testEmptyConfigType = "test-empty-config-type";
-    String testSiteConfigFile = "test-site.xml";
-    String testSiteConfigType = "test-site";
+    // WHEN
+    Collection<String> allConfigurationTypes = stack.getAllConfigurationTypes(serviceInfo.getName());
 
+    // THEN
+    Set<String> expected = ImmutableSet.of(SERVICE_CONFIG_TYPE, STACK_CONFIG_TYPE);
+    assertEquals(expected, allConfigurationTypes);
+  }
 
-    expect(controller.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes();
-
-    expect(controller.getStackServices(EasyMock.anyObject())).andReturn(Collections.singleton(stackServiceResponse)).anyTimes();
-    expect(stackServiceResponse.getServiceName()).andReturn(testServiceName).anyTimes();
-    expect(stackServiceResponse.getExcludedConfigTypes()).andReturn(Collections.emptySet());
-
-    // stack components
-    expect(stackComponentResponse.getComponentName()).andReturn("component1").anyTimes();
-    expect(stackComponentResponse.getComponentCategory()).andReturn(testSiteConfigFile).anyTimes();
-    expect(controller.getStackComponents(EasyMock.anyObject())).andReturn(Collections.singleton(stackComponentResponse)).anyTimes();
-
-    // stack configurations
+  @Test
+  public void getConfigurationTypesOmitsExcludedOnes() throws Exception {
+    // GIVEN
+    serviceInfo.setExcludedConfigTypes(ImmutableSet.of(SERVICE_CONFIG_TYPE));
+    Stack stack = new Stack(stackInfo);
 
-    // two properties with config type 'test-site'
-    expect(stackConfigurationResponse1.getPropertyName()).andReturn("prop1").anyTimes();
-    expect(stackConfigurationResponse1.getPropertyValue()).andReturn(null).anyTimes();
-    expect(stackConfigurationResponse1.getType()).andReturn(testSiteConfigFile).anyTimes();
-    expect(stackConfigurationResponse1.getPropertyType()).andReturn(Collections.singleton(PropertyInfo.PropertyType.TEXT)).anyTimes();
-    expect(stackConfigurationResponse1.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse1.isRequired()).andReturn(true).anyTimes();
+    // WHEN
+    Collection<String> allConfigurationTypes = stack.getConfigurationTypes(serviceInfo.getName());
 
-    expect(stackConfigurationResponse2.getPropertyName()).andReturn("prop2").anyTimes();
-    expect(stackConfigurationResponse2.getPropertyValue()).andReturn(null).anyTimes();
-    expect(stackConfigurationResponse2.getType()).andReturn(testSiteConfigFile).anyTimes();
-    expect(stackConfigurationResponse2.getPropertyType()).andReturn(Collections.singleton(PropertyInfo.PropertyType.USER)).anyTimes();
-    expect(stackConfigurationResponse2.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse2.isRequired()).andReturn(true).anyTimes();
+    // THEN
+    Set<String> expected = ImmutableSet.of(STACK_CONFIG_TYPE);
+    assertEquals(expected, allConfigurationTypes);
+  }
 
-    expect(controller.getStackConfigurations(EasyMock.anyObject())).andReturn(Sets.newHashSet(stackConfigurationResponse1, stackConfigurationResponse2)).anyTimes();
+  @Test
+  public void findsServiceForValidConfigType() {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    // empty stack service config type
-    expect(stackServiceResponse.getConfigTypes()).andReturn(Collections.singletonMap(testEmptyConfigType, Collections.emptyMap()));
+    // WHEN
+    String service = stack.getServiceForConfigType(SERVICE_CONFIG_TYPE);
 
-    // no stack level configs for this test
-    expect(controller.getStackLevelConfigurations(EasyMock.anyObject())).andReturn(Collections.emptySet()).anyTimes();
+    // THEN
+    assertEquals(serviceInfo.getName(), service);
+  }
 
-    expect(metaInfo.getComponentDependencies("test", "1.0", "service1", "component1")).andReturn(Collections.emptyList()).anyTimes();
+  @Test(expected = IllegalArgumentException.class) // THEN
+  public void serviceIsNotFoundForExcludedConfigType() {
+    // GIVEN
+    serviceInfo.setExcludedConfigTypes(ImmutableSet.of(SERVICE_CONFIG_TYPE));
+    Stack stack = new Stack(stackInfo);
 
-    replay(controller, stackServiceResponse, stackComponentResponse, stackConfigurationResponse1, stackConfigurationResponse2, metaInfo);
+    // WHEN
+    stack.getServiceForConfigType(SERVICE_CONFIG_TYPE);
+  }
 
+  @Test(expected = IllegalArgumentException.class) // THEN
+  public void serviceIsNotFoundForUnknownConfigType() {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    Stack stack = new Stack("test", "1.0", controller);
+    // WHEN
+    stack.getServiceForConfigType("no_such_config_type");
+  }
 
-    // When
-    Collection<String> allServiceConfigTypes = stack.getAllConfigurationTypes(testServiceName);
+  @Test
+  public void findsAllServicesForValidConfigType() {
+    // GIVEN
+    ServiceInfo otherMatchingService = new ServiceInfo();
+    otherMatchingService.setName("matches");
+    Stack stack = new Stack(stackInfo);
+
+    // WHEN
+    Stream<String> services = stack.getServicesForConfigType(SERVICE_CONFIG_TYPE);
+
+    // THEN
+    Set<String> expected = ImmutableSet.of(serviceInfo.getName());
+    assertEquals(expected, services.collect(toSet()));
+  }
 
-    // Then
+  @Test
+  public void noServiceFoundForExcludedConfigType() {
+    // GIVEN
+    serviceInfo.setExcludedConfigTypes(ImmutableSet.of(SERVICE_CONFIG_TYPE));
+    Stack stack = new Stack(stackInfo);
 
-    assertTrue(allServiceConfigTypes.containsAll(ImmutableSet.of(testSiteConfigType, testEmptyConfigType)));
-    assertEquals(2, allServiceConfigTypes.size());
+    // WHEN
+    Stream<String> services = stack.getServicesForConfigType(SERVICE_CONFIG_TYPE);
 
-    verifyAll();
+    // THEN
+    assertEquals(emptySet(), services.collect(toSet()));
   }
 
-  // Test that getServiceForConfigType skips excluded config types.
   @Test
-  public void testGetServiceForConfigTypeWithExcludedConfigs() throws Exception {
-    // Given
-    AmbariManagementController controller = createNiceMock(AmbariManagementController.class);
-    AmbariMetaInfo metaInfo = createNiceMock(AmbariMetaInfo.class);
-    StackServiceResponse stackServiceResponse = createNiceMock(StackServiceResponse.class);
-    StackServiceComponentResponse stackComponentResponse = createNiceMock(StackServiceComponentResponse.class);
-    StackLevelConfigurationResponse stackConfigurationResponse1 = createNiceMock(StackLevelConfigurationResponse.class);
-
-    String testServiceName = "service1";
-    String testEmptyConfigType = "test-empty-config-type";
-    String testSiteConfigFile = "test-site.xml";
-    String testSiteConfigType = "test-site";
-
-    expect(controller.getAmbariMetaInfo()).andReturn(metaInfo).anyTimes();
-
-    expect(controller.getStackServices(EasyMock.anyObject())).andReturn(Collections.singleton(stackServiceResponse)).anyTimes();
-    expect(stackServiceResponse.getServiceName()).andReturn(testServiceName).anyTimes();
-
-    // Config type test-site is excluded for the service service1
-    expect(stackServiceResponse.getExcludedConfigTypes()).andReturn(Collections.singleton(testSiteConfigType));
-
-    // stack components
-    expect(stackComponentResponse.getComponentName()).andReturn("component1").anyTimes();
-    expect(stackComponentResponse.getComponentCategory()).andReturn(testSiteConfigFile).anyTimes();
-    expect(controller.getStackComponents(EasyMock.anyObject())).andReturn(Collections.singleton(stackComponentResponse)).anyTimes();
-
-    expect(stackConfigurationResponse1.getPropertyName()).andReturn("prop1").anyTimes();
-    expect(stackConfigurationResponse1.getPropertyValue()).andReturn(null).anyTimes();
-    expect(stackConfigurationResponse1.getType()).andReturn(testSiteConfigFile).anyTimes();
-    expect(stackConfigurationResponse1.getPropertyType()).andReturn(Collections.singleton(PropertyInfo.PropertyType.TEXT)).anyTimes();
-    expect(stackConfigurationResponse1.getPropertyAttributes()).andReturn(Collections.emptyMap()).anyTimes();
-    expect(stackConfigurationResponse1.isRequired()).andReturn(true).anyTimes();
-
-    expect(controller.getStackConfigurations(EasyMock.anyObject())).andReturn(Collections.singleton(stackConfigurationResponse1)).anyTimes();
-
-    // empty stack service config type
-    expect(stackServiceResponse.getConfigTypes()).andReturn(Collections.singletonMap(testEmptyConfigType, Collections.emptyMap()));
-
-    // no stack level configs for this test
-    expect(controller.getStackLevelConfigurations(EasyMock.anyObject())).andReturn(Collections.emptySet()).anyTimes();
-    expect(metaInfo.getComponentDependencies("test", "1.0", "service1", "component1")).andReturn(Collections.emptyList()).anyTimes();
-
-    replay(controller, stackServiceResponse, stackComponentResponse, stackConfigurationResponse1, metaInfo);
-
-    Stack stack = new Stack("test", "1.0", controller);
-
-    // When
-    try {
-      stack.getServiceForConfigType(testSiteConfigType);
-      fail("Exception not thrown");
-    } catch (IllegalArgumentException e) {
-      // Expected
-    }
+  public void noServiceFoundForUnknownConfigType() {
+    // GIVEN
+    Stack stack = new Stack(stackInfo);
 
-    // Not excluded config type
-    assertEquals(testServiceName, stack.getServiceForConfigType(testEmptyConfigType));
+    // WHEN
+    Stream<String> services = stack.getServicesForConfigType("no_such_config_type");
 
-    verifyAll();
+    // THEN
+    assertEquals(emptySet(), services.collect(toSet()));
   }
 
+  private static Set<Pair<String, String>> convertToPropertySet(Collection<Stack.ConfigProperty> requiredConfigurationProperties) {
+    return requiredConfigurationProperties
+      .stream().map(p -> Pair.of(p.getType(), p.getName())).collect(toSet());
+  }
 }
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/metadata/RoleCommandOrderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/metadata/RoleCommandOrderTest.java
index 5b647e7..a540464 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/metadata/RoleCommandOrderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/metadata/RoleCommandOrderTest.java
@@ -97,6 +97,7 @@ public class RoleCommandOrderTest {
     ClusterImpl cluster = createMock(ClusterImpl.class);
     Service service = createMock(Service.class);
     expect(service.getDesiredStackId()).andReturn(stackId);
+    expect(service.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(cluster.getClusterId()).andReturn(1L);
     expect(cluster.getService("GLUSTERFS")).andReturn(service);
     expect(cluster.getService("HDFS")).andReturn(null);
@@ -149,6 +150,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("HDFS")).andReturn(hdfsService).atLeastOnce();
     expect(cluster.getService("YARN")).andReturn(null).atLeastOnce();
+    expect(hdfsService.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(hdfsService.getServiceComponent("JOURNALNODE")).andReturn(null);
     expect(hdfsService.getDesiredStackId()).andReturn(new StackId("HDP", "2.0.6"));
 //    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("HDP", "2.0.6"));
@@ -196,6 +198,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("HDFS")).andReturn(hdfsService).atLeastOnce();
     expect(cluster.getService("YARN")).andReturn(null);
+    expect(hdfsService.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(hdfsService.getServiceComponent("JOURNALNODE")).andReturn(journalnodeSC);
     expect(hdfsService.getDesiredStackId()).andReturn(new StackId("HDP", "2.0.6"));
 //    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("HDP", "2.0.6"));
@@ -245,6 +248,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("YARN")).andReturn(yarnService).atLeastOnce();
     expect(cluster.getService("HDFS")).andReturn(null);
+    expect(yarnService.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(yarnService.getServiceComponent("RESOURCEMANAGER")).andReturn(resourcemanagerSC).anyTimes();
     expect(resourcemanagerSC.getServiceComponentHosts()).andReturn(hostComponents).anyTimes();
 //    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("HDP", "2.0.6"));
@@ -301,6 +305,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("YARN")).andReturn(yarnService).atLeastOnce();
     expect(cluster.getService("HDFS")).andReturn(null);
+    expect(yarnService.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(yarnService.getServiceComponent("RESOURCEMANAGER")).andReturn(resourcemanagerSC).anyTimes();
     expect(yarnService.getDesiredStackId()).andReturn(new StackId("HDP", "2.0.6")).anyTimes();
     expect(resourcemanagerSC.getServiceComponentHosts()).andReturn(hostComponents).anyTimes();
@@ -398,6 +403,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("HDFS")).andReturn(hdfsService).atLeastOnce();
     expect(cluster.getService("YARN")).andReturn(null);
+    expect(hdfsService.getServiceComponents()).andReturn(Collections.emptyMap());
     expect(hdfsService.getServiceComponent("JOURNALNODE")).andReturn(null);
     //There is no rco file in this stack, should use default
 //    expect(cluster.getCurrentStackVersion()).andReturn(new StackId("HDP", "2.0.5"));
@@ -490,6 +496,7 @@ public class RoleCommandOrderTest {
 
     expect(cluster.getService("HDFS")).andReturn(hdfsService).atLeastOnce();
     expect(cluster.getService("YARN")).andReturn(null).atLeastOnce();
+    expect(hdfsService.getServiceComponents()).andReturn(Collections.emptyMap()).anyTimes();
     expect(hdfsService.getServiceComponent("JOURNALNODE")).andReturn(null);
     expect(hdfsService.getDesiredStackId()).andReturn(new StackId("HDP", "2.2.0")).anyTimes();
     expect(cluster.getServices()).andReturn(ImmutableMap.<String, Service>builder()
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/AmbariContextTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/AmbariContextTest.java
index 1bd79e1..b568354 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/AmbariContextTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/AmbariContextTest.java
@@ -43,6 +43,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.ClusterRequest;
@@ -99,11 +100,11 @@ public class AmbariContextTest {
   private static final long CLUSTER_ID = 1L;
   private static final String STACK_NAME = "testStack";
   private static final String STACK_VERSION = "testVersion";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
   private static final String HOST_GROUP_1 = "group1";
   private static final String HOST_GROUP_2 = "group2";
   private static final String HOST1 = "host1";
   private static final String HOST2 = "host2";
-  StackId stackId = new StackId(STACK_NAME, STACK_VERSION);
 
   private static final AmbariContext context = new AmbariContext();
   private static final AmbariManagementController controller = createNiceMock(AmbariManagementController.class);
@@ -242,9 +243,12 @@ public class AmbariContextTest {
 
     expect(blueprint.getName()).andReturn(BP_NAME).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(Collections.singleton(STACK_ID)).anyTimes();
     expect(blueprint.getServices()).andReturn(blueprintServices).anyTimes();
     expect(blueprint.getComponentNames("service1")).andReturn(Arrays.asList("s1Component1", "s1Component2")).anyTimes();
     expect(blueprint.getComponentNames("service2")).andReturn(Collections.singleton("s2Component1")).anyTimes();
+    expect(blueprint.getStackIdsForService("service1")).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
+    expect(blueprint.getStackIdsForService("service2")).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(blueprint.getConfiguration()).andReturn(bpConfiguration).anyTimes();
     expect(blueprint.getCredentialStoreEnabled("service1")).andReturn("true").anyTimes();
 
@@ -252,7 +256,7 @@ public class AmbariContextTest {
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
 
     for (Map.Entry<String, String> entry : configTypeServiceMapping.entrySet()) {
-      expect(stack.getServicesForConfigType(entry.getKey())).andReturn(singletonList(entry.getValue())).anyTimes();
+      expect(stack.getServicesForConfigType(entry.getKey())).andReturn(Stream.of(entry.getValue())).anyTimes();
     }
 
     expect(controller.getClusters()).andReturn(clusters).anyTimes();
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintFactoryTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintFactoryTest.java
index c2bf712..722345f 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintFactoryTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintFactoryTest.java
@@ -26,7 +26,6 @@ import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.powermock.api.easymock.PowerMock.createStrictMock;
 import static org.powermock.api.easymock.PowerMock.expectNew;
@@ -36,11 +35,10 @@ import static org.powermock.api.easymock.PowerMock.verify;
 
 import java.lang.reflect.Field;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.ambari.server.ObjectNotFoundException;
 import org.apache.ambari.server.controller.AmbariManagementController;
@@ -51,6 +49,7 @@ import org.apache.ambari.server.orm.dao.BlueprintDAO;
 import org.apache.ambari.server.orm.entities.BlueprintConfigEntity;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
 import org.apache.ambari.server.stack.NoSuchStackException;
+import org.apache.ambari.server.state.StackId;
 import org.easymock.EasyMockSupport;
 import org.junit.After;
 import org.junit.Before;
@@ -59,7 +58,6 @@ import org.junit.runner.RunWith;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
@@ -85,19 +83,18 @@ public class BlueprintFactoryTest {
   public void init() throws Exception {
     setPrivateField(factory, "blueprintDAO", dao);
 
-    Map<String, Collection<String>> componentMap = new HashMap<>();
-    Collection<String> components1 = new HashSet<>();
-    componentMap.put("test-service1", components1);
-    components1.add("component1");
-    Collection<String> components2 = new HashSet<>();
-    componentMap.put("test-service2", components2);
-    components2.add("component2");
+    Set<StackId> stackIds = ImmutableSet.of(new StackId("stack", "0.1"));
+    Collection<String> services = ImmutableSet.of("test-service1", "test-service2");
+    Collection<String> components = ImmutableSet.of("component1", "component2");
 
-    expect(stack.getComponents()).andReturn(componentMap).anyTimes();
+    expect(stack.getServices()).andReturn(services).anyTimes();
+    expect(stack.getComponents()).andReturn(components).anyTimes();
     expect(stack.isMasterComponent("component1")).andReturn(true).anyTimes();
     expect(stack.isMasterComponent("component2")).andReturn(false).anyTimes();
     expect(stack.getServiceForComponent("component1")).andReturn("test-service1").anyTimes();
     expect(stack.getServiceForComponent("component2")).andReturn("test-service2").anyTimes();
+    expect(stack.getStacksForService(anyString())).andReturn(stackIds).anyTimes();
+    expect(stack.getStacksForComponent(anyString())).andReturn(stackIds).anyTimes();
   }
 
   @After
@@ -131,13 +128,15 @@ public class BlueprintFactoryTest {
     reset(dao);
     expect(dao.findByName(BLUEPRINT_NAME)).andReturn(expectedBlueprint.toEntity());
     Stack hdpStack = createNiceMock(Stack.class);
-    expect(hdpStack.getName()).andReturn("HDPCORE").anyTimes();
-    expect(hdpStack.getVersion()).andReturn("3.0.0.0").anyTimes();
+    StackId hdp = new StackId("HDPCORE-3.0", "3.0.0.0");
+    StackId edw = new StackId("EDW-3.1", "3.1.0.0");
+    expect(hdpStack.getName()).andReturn(hdp.getStackName()).anyTimes();
+    expect(hdpStack.getVersion()).andReturn(hdp.getStackVersion()).anyTimes();
     Stack edwStack = createNiceMock(Stack.class);
-    expect(edwStack.getName()).andReturn("EDW").anyTimes();
-    expect(edwStack.getVersion()).andReturn("3.1.0.0").anyTimes();
-    expectNew(Stack.class, eq("HDPCORE-3.0"), anyString(), anyObject(AmbariManagementController.class)).andReturn(hdpStack).anyTimes();
-    expectNew(Stack.class, eq("EDW-3.1"), anyString(), anyObject(AmbariManagementController.class)).andReturn(edwStack).anyTimes();
+    expect(edwStack.getName()).andReturn(edw.getStackName()).anyTimes();
+    expect(edwStack.getVersion()).andReturn(edw.getStackVersion()).anyTimes();
+    expectNew(Stack.class, eq(hdp.getStackName()), eq(hdp.getStackVersion()), anyObject(AmbariManagementController.class)).andReturn(hdpStack).anyTimes();
+    expectNew(Stack.class, eq(edw.getStackVersion()), eq(edw.getStackVersion()), anyObject(AmbariManagementController.class)).andReturn(edwStack).anyTimes();
     replay(Stack.class, hdpStack, edwStack, dao);
 
     // test
@@ -153,9 +152,8 @@ public class BlueprintFactoryTest {
     Set<String> serviceInstanceTypes =
       hdpCore.getServiceInstances().stream().map(ServiceInstance::getType).collect(toSet());
     assertEquals(ImmutableSet.of("ZOOKEEPER"), serviceInstanceTypes);
-    Set<String> stackNames =
-      blueprint.getStacks().stream().map(Stack::getName).collect(Collectors.toSet());
-    assertEquals(ImmutableSet.of("HDPCORE", "EDW"), stackNames);
+    Set<StackId> stackIds = blueprint.getStackIds();
+    assertEquals(ImmutableSet.of(hdp, edw), stackIds);
     assertEquals(1, blueprint.getHostGroups().size());
   }
 
@@ -175,7 +173,6 @@ public class BlueprintFactoryTest {
     Blueprint blueprint = testFactory.createBlueprint(props, null);
 
     assertEquals(BLUEPRINT_NAME, blueprint.getName());
-    assertSame(stack, blueprint.getStack());
     assertEquals(2, blueprint.getHostGroups().size());
 
     Map<String, HostGroup> hostGroups = blueprint.getHostGroups();
@@ -221,11 +218,11 @@ public class BlueprintFactoryTest {
   }
 
   public Blueprint createMultiInstanceBlueprint() throws Exception {
-    Map<String, Collection<String>> allComponents = ImmutableMap.of(
-      "HDFS", ImmutableSet.of("NAMENODE", "SECONDARY_NAMENODE"),
-      "ZOOKEEPER", ImmutableSet.of("ZOOKEEPER_SERVER")
-    );
+    Collection<String> allComponents = ImmutableSet.of("NAMENODE", "SECONDARY_NAMENODE", "ZOOKEEPER_SERVER");
+    Collection<String> services = ImmutableSet.of("HDFS", "ZOOKEEPER");
+    Set<StackId> stackIds = ImmutableSet.of(new StackId("HDPCORE-3.0", "3.0.0.0"));
     reset(stack);
+    expect(stack.getServices()).andReturn(services).anyTimes();
     expect(stack.getComponents()).andReturn(allComponents).anyTimes();
     expect(stack.isMasterComponent("NAMENODE")).andReturn(true).anyTimes();
     expect(stack.isMasterComponent("ZOOKEEPER_SERVER")).andReturn(true).anyTimes();
@@ -233,6 +230,8 @@ public class BlueprintFactoryTest {
     expect(stack.getServiceForComponent("NAMENODE")).andReturn("HDFS").anyTimes();
     expect(stack.getServiceForComponent("SECONDARY_NAMENODE")).andReturn("HDFS").anyTimes();
     expect(stack.getServiceForComponent("ZOOKEEPER_SERVER")).andReturn("ZOOKEEPER").anyTimes();
+    expect(stack.getStacksForService(anyString())).andReturn(stackIds).anyTimes();
+    expect(stack.getStacksForComponent(anyString())).andReturn(stackIds).anyTimes();
 
     replay(stack, dao, entity, configEntity);
 
@@ -259,13 +258,13 @@ public class BlueprintFactoryTest {
       mockSupport.createMock(BlueprintFactory.StackFactory.class);
 
     // setup mock to throw exception, to simulate invalid stack request
-    expect(mockStackFactory.createStack("null", "null", null)).andThrow(new ObjectNotFoundException("Invalid Stack"));
+    expect(mockStackFactory.createStack(new StackId(), null)).andThrow(new ObjectNotFoundException("Invalid Stack"));
 
     mockSupport.replayAll();
 
     BlueprintFactory factoryUnderTest =
       new BlueprintFactory(mockStackFactory);
-    factoryUnderTest.createStack(new HashMap<>());
+    factoryUnderTest.createStack(new StackId());
 
     mockSupport.verifyAll();
   }
@@ -321,6 +320,47 @@ public class BlueprintFactoryTest {
     testFactory.createBlueprint(props, null);
   }
 
+  @Test(expected = IllegalArgumentException.class) // THEN
+  public void verifyDefinitionsDisjointShouldRejectDuplication() {
+    // GIVEN
+    final String service1 = "unique service";
+    final String service2 = "duplicated service";
+    StackId stack1 = new StackId("a_stack", "1.0");
+    StackId stack2 = new StackId("another_stack", "0.9");
+    Stream<String> stream = ImmutableSet.of(service1, service2).stream();
+
+    // WHEN
+    BlueprintFactory.verifyStackDefinitionsAreDisjoint(stream, "Services", service -> {
+      switch (service) {
+        case service1: return ImmutableSet.of(stack1);
+        case service2: return ImmutableSet.of(stack1, stack2);
+        default: return null;
+      }
+    });
+  }
+
+  @Test
+  public void verifyStackDefinitionsAreDisjointShouldAllowDisjointStacks() {
+    // GIVEN
+    final String service1 = "unique service";
+    final String service2 = "another service";
+    StackId stack1 = new StackId("a_stack", "1.0");
+    StackId stack2 = new StackId("another_stack", "0.9");
+    Stream<String> stream = ImmutableSet.of(service1, service2).stream();
+
+    // WHEN
+    BlueprintFactory.verifyStackDefinitionsAreDisjoint(stream, "Services", service -> {
+      switch (service) {
+        case service1: return ImmutableSet.of(stack1);
+        case service2: return ImmutableSet.of(stack2);
+        default: return null;
+      }
+    });
+
+    // THEN
+    // no exception expected
+  }
+
   private class TestBlueprintFactory extends BlueprintFactory {
     private Stack stack;
 
@@ -329,7 +369,7 @@ public class BlueprintFactoryTest {
     }
 
     @Override
-    protected Stack loadStack(String stackName, String stackVersion) throws NoSuchStackException {
+    protected Stack createStack(StackId stackId) throws NoSuchStackException {
       return stack;
     }
   }
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintImplTest.java
index 057332d..362f4e4 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintImplTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintImplTest.java
@@ -18,16 +18,16 @@
 
 package org.apache.ambari.server.topology;
 
+import static java.util.Collections.emptySet;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.mock;
 import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -61,7 +61,8 @@ public class BlueprintImplTest {
   Map<String, Map<String, String>> properties = new HashMap<>();
   Map<String, String> hdfsProps = new HashMap<>();
   Configuration configuration = new Configuration(properties, EMPTY_ATTRIBUTES, EMPTY_CONFIGURATION);
-  org.apache.ambari.server.configuration.Configuration serverConfig;
+  private final org.apache.ambari.server.configuration.Configuration serverConfig = createNiceMock(org.apache.ambari.server.configuration.Configuration.class);
+  private final BlueprintValidator blueprintValidator = new BlueprintValidatorImpl(serverConfig);
 
   @Before
   public void setup() throws NoSuchFieldException, IllegalAccessException {
@@ -106,7 +107,7 @@ public class BlueprintImplTest {
     expect(stack.getRequiredConfigurationProperties("HDFS")).andReturn(requiredHDFSProperties).anyTimes();
     expect(stack.getRequiredConfigurationProperties("SERVICE2")).andReturn(requiredService2Properties).anyTimes();
 
-    serverConfig = setupConfigurationWithGPLLicense(true);
+    setupConfigurationWithGPLLicense(true);
   }
 
   @Test
@@ -124,8 +125,8 @@ public class BlueprintImplTest {
     category2Props.put("prop2", "val");
 
     SecurityConfiguration securityConfiguration = new SecurityConfiguration(SecurityType.KERBEROS, "testRef", null);
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, securityConfiguration, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, securityConfiguration, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     BlueprintEntity entity = blueprint.toEntity();
 
     verify(stack, group1, group2, serverConfig);
@@ -162,8 +163,8 @@ public class BlueprintImplTest {
     hadoopProps.put("dfs_ha_initial_namenode_active", "%HOSTGROUP:group1%");
     hadoopProps.put("dfs_ha_initial_namenode_standby", "%HOSTGROUP:group2%");
     replay(stack, group1, group2, serverConfig);
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     BlueprintEntity entity = blueprint.toEntity();
     verify(stack, group1, group2, serverConfig);
     assertTrue(entity.getSecurityType() == SecurityType.NONE);
@@ -202,8 +203,8 @@ public class BlueprintImplTest {
     hadoopProps.put("dfs_ha_initial_namenode_standby", "%HOSTGROUP::group2%");
     replay(stack, group1, group2, serverConfig);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     BlueprintEntity entity = blueprint.toEntity();
 
     verify(stack, group1, group2, serverConfig);
@@ -243,8 +244,8 @@ public class BlueprintImplTest {
     hadoopProps.put("dfs_ha_initial_namenode_active", "%HOSTGROUP::group2%");
     hadoopProps.put("dfs_ha_initial_namenode_standby", "%HOSTGROUP::group3%");
     replay(stack, group1, group2, serverConfig);
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
   @Test(expected= IllegalArgumentException.class)
@@ -279,8 +280,8 @@ public class BlueprintImplTest {
     hadoopProps.put("dfs_ha_initial_namenode_active", "%HOSTGROUP::group2%");
     hadoopProps.put("dfs_ha_initial_namenode_standby", "%HOSTGROUP::group2%");
     replay(stack, group1, group2, serverConfig);
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
   @Test(expected = InvalidTopologyException.class)
@@ -294,8 +295,8 @@ public class BlueprintImplTest {
     hdfsProps.put("secret", "SECRET:hdfs-site:1:test");
     replay(stack, group1, group2, serverConfig);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
 
@@ -308,11 +309,11 @@ public class BlueprintImplTest {
     }});
     Configuration lzoUsageConfiguration = new Configuration(lzoProperties, EMPTY_ATTRIBUTES, EMPTY_CONFIGURATION);
 
-    serverConfig = setupConfigurationWithGPLLicense(false);
+    setupConfigurationWithGPLLicense(false);
     replay(stack, group1, group2, serverConfig);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, lzoUsageConfiguration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), lzoUsageConfiguration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
 
@@ -325,11 +326,11 @@ public class BlueprintImplTest {
     }});
     Configuration lzoUsageConfiguration = new Configuration(lzoProperties, EMPTY_ATTRIBUTES, EMPTY_CONFIGURATION);
 
-    serverConfig = setupConfigurationWithGPLLicense(false);
+    setupConfigurationWithGPLLicense(false);
     replay(stack, group1, group2, serverConfig);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, lzoUsageConfiguration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), lzoUsageConfiguration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
 
@@ -346,8 +347,8 @@ public class BlueprintImplTest {
     expect(group2.getConfiguration()).andReturn(EMPTY_CONFIGURATION).atLeastOnce();
     replay(stack, group1, group2, serverConfig);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, lzoUsageConfiguration, null, null);
-    blueprint.validateRequiredProperties();
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), lzoUsageConfiguration, null, null);
+    blueprintValidator.validateRequiredProperties(blueprint);
     verify(stack, group1, group2, serverConfig);
   }
 
@@ -360,7 +361,7 @@ public class BlueprintImplTest {
     expect(setting.getSettingValue(Setting.SETTING_NAME_DEPLOYMENT_SETTINGS)).andReturn(Collections.singleton(skipFailureSetting));
     replay(stack, setting);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, setting);
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, setting);
     assertTrue(blueprint.shouldSkipFailure());
 
     verify(stack, setting);
@@ -373,21 +374,15 @@ public class BlueprintImplTest {
     expect(setting.getSettingValue(Setting.SETTING_NAME_DEPLOYMENT_SETTINGS)).andReturn(Collections.singleton(skipFailureSetting));
     replay(stack, setting);
 
-    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, configuration, null, setting);
+    Blueprint blueprint = new BlueprintImpl("test", hostGroups, stack, emptySet(), emptySet(), configuration, null, setting);
     assertFalse(blueprint.shouldSkipFailure());
 
     verify(stack, setting);
   }
 
-  public static org.apache.ambari.server.configuration.Configuration setupConfigurationWithGPLLicense(boolean isGPLAllowed)
-      throws NoSuchFieldException, IllegalAccessException {
-    org.apache.ambari.server.configuration.Configuration serverConfig =
-        mock(org.apache.ambari.server.configuration.Configuration.class);
+  private org.apache.ambari.server.configuration.Configuration setupConfigurationWithGPLLicense(boolean isGPLAllowed) {
+    reset(serverConfig);
     expect(serverConfig.getGplLicenseAccepted()).andReturn(isGPLAllowed).atLeastOnce();
-
-    Field field = BlueprintValidatorImpl.class.getDeclaredField("configuration");
-    field.setAccessible(true);
-    field.set(null, serverConfig);
     return serverConfig;
   }
 
@@ -501,7 +496,7 @@ public class BlueprintImplTest {
 //
 //    // set expectations
 //    expect(blueprintFactory.createBlueprint(setProperties.iterator().next())).andReturn(blueprint).once();
-//    expect(blueprint.validateRequiredProperties()).andReturn(Collections.<String, Map<String, Collection<String>>>emptyMap()).once();
+//    expect(blueprintValidator.validateRequiredProperties()).andReturn(Collections.<String, Map<String, Collection<String>>>emptyMap()).once(blueprint);
 //    expect(blueprint.toEntity()).andReturn(entity);
 //    expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).atLeastOnce();
 //    expect(managementController.getStackServices(capture(stackServiceRequestCapture))).andReturn(
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintValidatorImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintValidatorImplTest.java
index e88bef3..4639687 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintValidatorImplTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/BlueprintValidatorImplTest.java
@@ -38,6 +38,7 @@ import org.apache.ambari.server.state.AutoDeployInfo;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.DependencyConditionInfo;
 import org.apache.ambari.server.state.DependencyInfo;
+import org.apache.ambari.server.state.StackId;
 import org.easymock.EasyMock;
 import org.easymock.EasyMockRule;
 import org.easymock.Mock;
@@ -47,6 +48,8 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * BlueprintValidatorImpl unit tests.
  */
@@ -99,6 +102,7 @@ public class BlueprintValidatorImplTest {
     autoDeploy.setEnabled(true);
     autoDeploy.setCoLocate("service1/component2");
 
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(new StackId("HDP", "2.2"))).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
     expect(blueprint.getHostGroups()).andReturn(hostGroups).anyTimes();
     expect(blueprint.getServices()).andReturn(services).anyTimes();
@@ -124,7 +128,6 @@ public class BlueprintValidatorImplTest {
     dependenciesConditionInfos1.add(dependencyConditionInfo2);
 
     expect(blueprint.getConfiguration()).andReturn(configuration).anyTimes();
-    expect(blueprint.isAllMpacksResolved()).andReturn(true);
   }
 
   @After
@@ -146,8 +149,8 @@ public class BlueprintValidatorImplTest {
     expect(blueprint.getHostGroupsForComponent("component2")).andReturn(Arrays.asList(group1, group2)).anyTimes();
 
     replay(blueprint, stack, group1, group2, dependency1);
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
   }
 
   @Test(expected = InvalidTopologyException.class)
@@ -162,8 +165,8 @@ public class BlueprintValidatorImplTest {
     expect(blueprint.getHostGroupsForComponent("component2")).andReturn(Arrays.asList(group1, group2)).anyTimes();
 
     replay(blueprint, stack, group1, group2, dependency1);
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
   }
 
   @Test
@@ -180,8 +183,8 @@ public class BlueprintValidatorImplTest {
     expect(group1.addComponent(new Component("component1"))).andReturn(true).once();
 
     replay(blueprint, stack, group1, group2, dependency1);
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
 
     verify(group1);
   }
@@ -218,66 +221,12 @@ public class BlueprintValidatorImplTest {
 
     replay(blueprint, stack, group1, group2, dependency1, dependencyComponentInfo);
 
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
 
     verify(group1);
   }
 
-  @Test(expected=InvalidTopologyException.class)
-  public void testValidateRequiredProperties_SqlaInHiveStackHdp22() throws Exception {
-    Map<String, String> hiveEnvConfig = new HashMap<>();
-    hiveEnvConfig.put("hive_database","Existing SQL Anywhere Database");
-    configProperties.put("hive-env", hiveEnvConfig);
-
-    group1Components.add("HIVE_METASTORE");
-
-    services.addAll(Arrays.asList("HIVE"));
-
-    org.apache.ambari.server.configuration.Configuration serverConfig =
-        BlueprintImplTest.setupConfigurationWithGPLLicense(true);
-
-    Configuration config = new Configuration(new HashMap<>(), new HashMap<>());
-    expect(group1.getConfiguration()).andReturn(config).anyTimes();
-
-    expect(stack.getComponents("HIVE")).andReturn(Collections.singleton("HIVE_METASTORE")).anyTimes();
-    expect(stack.getVersion()).andReturn("2.2").once();
-    expect(stack.getName()).andReturn("HDP").once();
-
-    expect(blueprint.getHostGroupsForComponent("HIVE_METASTORE")).andReturn(Collections.singleton(group1)).anyTimes();
-
-    replay(blueprint, stack, group1, group2, dependency1, serverConfig);
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateRequiredProperties();
-  }
-
-  @Test(expected=InvalidTopologyException.class)
-  public void testValidateRequiredProperties_SqlaInOozieStackHdp22() throws Exception {
-    Map<String, String> hiveEnvConfig = new HashMap<>();
-    hiveEnvConfig.put("oozie_database","Existing SQL Anywhere Database");
-    configProperties.put("oozie-env", hiveEnvConfig);
-
-    group1Components.add("OOZIE_SERVER");
-
-    services.addAll(Arrays.asList("OOZIE"));
-
-    org.apache.ambari.server.configuration.Configuration serverConfig =
-        BlueprintImplTest.setupConfigurationWithGPLLicense(true);
-
-    Configuration config = new Configuration(new HashMap<>(), new HashMap<>());
-    expect(group1.getConfiguration()).andReturn(config).anyTimes();
-
-    expect(stack.getComponents("OOZIE")).andReturn(Collections.singleton("OOZIE_SERVER")).anyTimes();
-    expect(stack.getVersion()).andReturn("2.2").once();
-    expect(stack.getName()).andReturn("HDP").once();
-
-    expect(blueprint.getHostGroupsForComponent("OOZIE_SERVER")).andReturn(Collections.singleton(group1)).anyTimes();
-
-    replay(blueprint, stack, group1, group2, dependency1, serverConfig);
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateRequiredProperties();
-  }
-
   @Test
   public void testShouldDependencyBeExcludedWenRelatedServiceIsNotInBlueprint() throws Exception {
     // GIVEN
@@ -317,8 +266,8 @@ public class BlueprintValidatorImplTest {
     replay(blueprint, stack, group1, group2, dependency1, dependencyComponentInfo);
 
     // WHEN
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
 
     // THEN
     verify(group1);
@@ -360,8 +309,8 @@ public class BlueprintValidatorImplTest {
     replay(blueprint, stack, group1, group2, dependency1, dependencyComponentInfo);
 
     // WHEN
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
 
     // THEN
     verify(group1);
@@ -422,8 +371,8 @@ public class BlueprintValidatorImplTest {
     replay(blueprint, stack, group1, group2, dependency1, dependency2, dependencyComponentInfo,dependencyConditionInfo1,dependencyConditionInfo2);
 
     // WHEN
-    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
-    validator.validateTopology();
+    BlueprintValidator validator = new BlueprintValidatorImpl(null);
+    validator.validateTopology(blueprint);
 
     // THEN
     verify(group1);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterConfigurationRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterConfigurationRequestTest.java
index 28ce72a..c4ba8d8 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterConfigurationRequestTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterConfigurationRequestTest.java
@@ -64,6 +64,7 @@ import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 
 /**
@@ -107,8 +108,9 @@ public class ClusterConfigurationRequestTest {
   @Mock(type = MockType.NICE)
   private ConfigHelper configHelper;
 
-  private final String STACK_NAME = "testStack";
-  private final String STACK_VERSION = "1";
+  private static final String STACK_NAME = "testStack";
+  private static final String STACK_VERSION = "1";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
   private final Map<String, Map<String, String>> stackProperties = new HashMap<>();
   private final Map<String, String> defaultClusterEnvProperties = new HashMap<>();
 
@@ -235,6 +237,7 @@ public class ClusterConfigurationRequestTest {
     expect(clusters.getCluster("testCluster")).andReturn(cluster).anyTimes();
 
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(stack.getName()).andReturn(STACK_NAME).anyTimes();
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
     expect(stack.getServiceForConfigType("testConfigType")).andReturn("KERBEROS").anyTimes();
@@ -278,7 +281,7 @@ public class ClusterConfigurationRequestTest {
       .emptyList()).anyTimes();
 
     expect(configHelper.getDefaultStackProperties(
-        EasyMock.eq(new StackId(STACK_NAME, STACK_VERSION)))).andReturn(stackProperties).anyTimes();
+        EasyMock.eq(STACK_ID))).andReturn(stackProperties).anyTimes();
 
     if (kerberosConfig == null) {
       kerberosConfig = new HashMap<>();
@@ -327,6 +330,7 @@ public class ClusterConfigurationRequestTest {
     expect(clusters.getCluster("testCluster")).andReturn(cluster).anyTimes();
 
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(stack.getName()).andReturn(STACK_NAME).anyTimes();
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
     expect(stack.getAllConfigurationTypes(anyString())).andReturn(Collections.singletonList("testConfigType")).anyTimes();
@@ -363,7 +367,7 @@ public class ClusterConfigurationRequestTest {
       .emptyList()).anyTimes();
 
     expect(configHelper.getDefaultStackProperties(
-        EasyMock.eq(new StackId(STACK_NAME, STACK_VERSION)))).andReturn(stackProperties).anyTimes();
+        EasyMock.eq(STACK_ID))).andReturn(stackProperties).anyTimes();
 
     PowerMock.replay(stack, blueprint, topology, controller, clusters, ambariContext,
         AmbariContext.class, configHelper);
@@ -404,7 +408,7 @@ public class ClusterConfigurationRequestTest {
     expect(ambariContext.getConfigHelper()).andReturn(configHelper).anyTimes();
 
     expect(configHelper.getDefaultStackProperties(
-        EasyMock.eq(new StackId(STACK_NAME, STACK_VERSION)))).andReturn(stackProperties).anyTimes();
+        EasyMock.eq(STACK_ID))).andReturn(stackProperties).anyTimes();
 
     EasyMock.replay(stack, blueprint, topology, ambariContext, configHelper);
     // WHEN
@@ -452,7 +456,7 @@ public class ClusterConfigurationRequestTest {
     expect(ambariContext.getConfigHelper()).andReturn(configHelper).anyTimes();
 
     expect(configHelper.getDefaultStackProperties(
-        EasyMock.eq(new StackId(STACK_NAME, STACK_VERSION)))).andReturn(stackProperties).anyTimes();
+        EasyMock.eq(STACK_ID))).andReturn(stackProperties).anyTimes();
 
     EasyMock.replay(stack, blueprint, topology, ambariContext, configHelper);
 
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
index d0e82f2..55db768 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
@@ -61,6 +61,7 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTask;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
 import org.apache.ambari.server.topology.validators.TopologyValidatorService;
@@ -79,6 +80,8 @@ import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.google.common.collect.ImmutableSet;
+
 @RunWith(PowerMockRunner.class)
 @PrepareForTest(AmbariServer.class)
 public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
@@ -87,6 +90,7 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
   private static final String BLUEPRINT_NAME = "test-bp";
   private static final String STACK_NAME = "test-stack";
   private static final String STACK_VERSION = "test-stack-version";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
 
   @Rule
   public EasyMockRule mocks = new EasyMockRule(this);
@@ -189,8 +193,6 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
   private Map<String, Collection<String>> group1ServiceComponents = new HashMap<>();
   private Map<String, Collection<String>> group2ServiceComponents = new HashMap<>();
 
-  private Map<String, Collection<String>> serviceComponents = new HashMap<>();
-
   private String predicate = "Hosts/host_name=foo";
 
   private List<TopologyValidator> topologyValidators = new ArrayList<>();
@@ -231,8 +233,9 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
     groupMap.put("group1", group1);
     groupMap.put("group2", group2);
 
-    serviceComponents.put("service1", Arrays.asList("component1", "component3"));
-    serviceComponents.put("service2", Arrays.asList("component2", "component4"));
+    Collection<String> components1 = ImmutableSet.of("component1", "component3");
+    Collection<String> components2 = ImmutableSet.of("component2", "component4");
+    Collection<String> components = ImmutableSet.<String>builder().addAll(components1).addAll(components2).build();
 
     group1ServiceComponents.put("service1", Arrays.asList("component1", "component3"));
     group1ServiceComponents.put("service2", Collections.singleton("component2"));
@@ -254,6 +257,7 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
     expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).anyTimes();
     expect(blueprint.getServices()).andReturn(Arrays.asList("service1", "service2")).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(blueprint.isValidConfigType(anyString())).andReturn(true).anyTimes();
     expect(blueprint.getRepositorySettings()).andReturn(new ArrayList<>()).anyTimes();
     // don't expect toEntity()
@@ -277,9 +281,9 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
     expect(stack.getCardinality("component2")).andReturn(new Cardinality("1")).anyTimes();
     expect(stack.getCardinality("component3")).andReturn(new Cardinality("1+")).anyTimes();
     expect(stack.getCardinality("component4")).andReturn(new Cardinality("1+")).anyTimes();
-    expect(stack.getComponents()).andReturn(serviceComponents).anyTimes();
-    expect(stack.getComponents("service1")).andReturn(serviceComponents.get("service1")).anyTimes();
-    expect(stack.getComponents("service2")).andReturn(serviceComponents.get("service2")).anyTimes();
+    expect(stack.getComponents()).andReturn(components).anyTimes();
+    expect(stack.getComponents("service1")).andReturn(components1).anyTimes();
+    expect(stack.getComponents("service2")).andReturn(components2).anyTimes();
     expect(stack.getConfiguration()).andReturn(stackConfig).anyTimes();
     expect(stack.getName()).andReturn(STACK_NAME).anyTimes();
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
@@ -391,7 +395,7 @@ public class ClusterDeployWithStartOnlyTest extends EasyMockSupport {
 
     ambariContext.setConfigurationOnCluster(capture(updateClusterConfigRequestCapture));
     expectLastCall().times(3);
-    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_NAME, STACK_VERSION);
+    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_ID);
     expectLastCall().once();
 
     expect(configureClusterTaskFactory.createConfigureClusterTask(anyObject(), anyObject(), anyObject())).andReturn(configureClusterTask);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
index c64e228..bac0e5b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
@@ -18,7 +18,6 @@
 package org.apache.ambari.server.topology;
 
 
-import static java.util.stream.Collectors.toList;
 import static org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_AND_START;
 import static org.easymock.EasyMock.anyBoolean;
 import static org.easymock.EasyMock.anyLong;
@@ -63,6 +62,7 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTask;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
 import org.apache.ambari.server.topology.validators.TopologyValidatorService;
@@ -81,6 +81,8 @@ import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.google.common.collect.ImmutableSet;
+
 @RunWith(PowerMockRunner.class)
 @PrepareForTest(AmbariServer.class)
 public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupport {
@@ -89,6 +91,7 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
   private static final String BLUEPRINT_NAME = "test-bp";
   private static final String STACK_NAME = "test-stack";
   private static final String STACK_VERSION = "test-stack-version";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
 
   @Rule
   public EasyMockRule mocks = new EasyMockRule(this);
@@ -186,9 +189,6 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
   private Map<String, Collection<Component>> group1ServiceComponents = new HashMap<>();
   private Map<String, Collection<Component>> group2ServiceComponents = new HashMap<>();
 
-  private Map<String, Collection<Component>> serviceComponents = new HashMap<>();
-  private Map<String, Collection<String>> serviceComponentNames = new HashMap<>();
-
   private String predicate = "Hosts/host_name=foo";
 
   private List<TopologyValidator> topologyValidators = new ArrayList<>();
@@ -230,12 +230,9 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
     groupMap.put("group1", group1);
     groupMap.put("group2", group2);
 
-    serviceComponents.put("service1", Arrays.asList(new Component("component1"), new Component("component3")));
-    serviceComponents.put("service2", Arrays.asList(new Component("component2"), new Component("component4")));
-
-    for(Map.Entry<String, Collection<Component>> entry: serviceComponents.entrySet()) {
-      serviceComponentNames.put(entry.getKey(), entry.getValue().stream().map(Component::getName).collect(toList()));
-    }
+    Collection<String> components1 = ImmutableSet.of("component1", "component3");
+    Collection<String> components2 = ImmutableSet.of("component2", "component4");
+    Collection<String> components = ImmutableSet.<String>builder().addAll(components1).addAll(components2).build();
 
     group1ServiceComponents.put("service1", Arrays.asList(new Component("component1"), new Component("component3")));
     group1ServiceComponents.put("service2", Collections.singleton(new Component("component2")));
@@ -257,6 +254,7 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
     expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).anyTimes();
     expect(blueprint.getServices()).andReturn(Arrays.asList("service1", "service2")).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(blueprint.isValidConfigType(anyString())).andReturn(true).anyTimes();
     expect(blueprint.getRepositorySettings()).andReturn(new ArrayList<>()).anyTimes();
     // don't expect toEntity()
@@ -280,9 +278,9 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
     expect(stack.getCardinality("component2")).andReturn(new Cardinality("1")).anyTimes();
     expect(stack.getCardinality("component3")).andReturn(new Cardinality("1+")).anyTimes();
     expect(stack.getCardinality("component4")).andReturn(new Cardinality("1+")).anyTimes();
-    expect(stack.getComponents()).andReturn(serviceComponentNames).anyTimes();
-    expect(stack.getComponents("service1")).andReturn(serviceComponentNames.get("service1")).anyTimes();
-    expect(stack.getComponents("service2")).andReturn(serviceComponentNames.get("service2")).anyTimes();
+    expect(stack.getComponents()).andReturn(components).anyTimes();
+    expect(stack.getComponents("service1")).andReturn(components1).anyTimes();
+    expect(stack.getComponents("service2")).andReturn(components2).anyTimes();
     expect(stack.getConfiguration()).andReturn(stackConfig).anyTimes();
     expect(stack.getName()).andReturn(STACK_NAME).anyTimes();
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
@@ -375,7 +373,7 @@ public class ClusterInstallWithoutStartOnComponentLevelTest extends EasyMockSupp
 
     ambariContext.setConfigurationOnCluster(capture(updateClusterConfigRequestCapture));
     expectLastCall().times(3);
-    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_NAME, STACK_VERSION);
+    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_ID);
     expectLastCall().once();
 
     expect(configureClusterTaskFactory.createConfigureClusterTask(anyObject(), anyObject(), anyObject())).andReturn(configureClusterTask);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
index 6fa70b4..3e11f5e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
@@ -62,6 +62,7 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTask;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
 import org.apache.ambari.server.topology.validators.TopologyValidatorService;
@@ -80,6 +81,8 @@ import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.google.common.collect.ImmutableSet;
+
 @RunWith(PowerMockRunner.class)
 @PrepareForTest(AmbariServer.class)
 public class ClusterInstallWithoutStartTest extends EasyMockSupport {
@@ -89,6 +92,7 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
   private static final String BLUEPRINT_NAME = "test-bp";
   private static final String STACK_NAME = "test-stack";
   private static final String STACK_VERSION = "test-stack-version";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
 
   @Rule
   public EasyMockRule mocks = new EasyMockRule(this);
@@ -187,8 +191,6 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
   private Map<String, Collection<String>> group1ServiceComponents = new HashMap<>();
   private Map<String, Collection<String>> group2ServiceComponents = new HashMap<>();
 
-  private Map<String, Collection<String>> serviceComponents = new HashMap<>();
-
   private String predicate = "Hosts/host_name=foo";
 
   private List<TopologyValidator> topologyValidators = new ArrayList<>();
@@ -229,8 +231,9 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
     groupMap.put("group1", group1);
     groupMap.put("group2", group2);
 
-    serviceComponents.put("service1", Arrays.asList("component1", "component3"));
-    serviceComponents.put("service2", Arrays.asList("component2", "component4"));
+    Collection<String> components1 = ImmutableSet.of("component1", "component3");
+    Collection<String> components2 = ImmutableSet.of("component2", "component4");
+    Collection<String> components = ImmutableSet.<String>builder().addAll(components1).addAll(components2).build();
 
     group1ServiceComponents.put("service1", Arrays.asList("component1", "component3"));
     group1ServiceComponents.put("service2", Collections.singleton("component2"));
@@ -252,6 +255,7 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
     expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).anyTimes();
     expect(blueprint.getServices()).andReturn(Arrays.asList("service1", "service2")).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(blueprint.isValidConfigType(anyString())).andReturn(true).anyTimes();
     expect(blueprint.getRepositorySettings()).andReturn(new ArrayList<>()).anyTimes();
     // don't expect toEntity()
@@ -275,9 +279,9 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
     expect(stack.getCardinality("component2")).andReturn(new Cardinality("1")).anyTimes();
     expect(stack.getCardinality("component3")).andReturn(new Cardinality("1+")).anyTimes();
     expect(stack.getCardinality("component4")).andReturn(new Cardinality("1+")).anyTimes();
-    expect(stack.getComponents()).andReturn(serviceComponents).anyTimes();
-    expect(stack.getComponents("service1")).andReturn(serviceComponents.get("service1")).anyTimes();
-    expect(stack.getComponents("service2")).andReturn(serviceComponents.get("service2")).anyTimes();
+    expect(stack.getComponents()).andReturn(components).anyTimes();
+    expect(stack.getComponents("service1")).andReturn(components1).anyTimes();
+    expect(stack.getComponents("service2")).andReturn(components2).anyTimes();
     expect(stack.getConfiguration()).andReturn(stackConfig).anyTimes();
     expect(stack.getName()).andReturn(STACK_NAME).anyTimes();
     expect(stack.getVersion()).andReturn(STACK_VERSION).anyTimes();
@@ -363,7 +367,7 @@ public class ClusterInstallWithoutStartTest extends EasyMockSupport {
 
     ambariContext.setConfigurationOnCluster(capture(updateClusterConfigRequestCapture));
     expectLastCall().times(3);
-    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_NAME, STACK_VERSION);
+    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_ID);
     expectLastCall().once();
 
     expect(configureClusterTaskFactory.createConfigureClusterTask(anyObject(), anyObject(), anyObject())).andReturn(configureClusterTask);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
index 0daf20f..7174a5e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
@@ -65,6 +65,7 @@ import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
 import org.apache.ambari.server.stack.NoSuchStackException;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTask;
 import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
@@ -86,6 +87,8 @@ import org.powermock.api.easymock.PowerMock;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * TopologyManager unit tests
  */
@@ -98,6 +101,7 @@ public class TopologyManagerTest {
   private static final String BLUEPRINT_NAME = "test-bp";
   private static final String STACK_NAME = "test-stack";
   private static final String STACK_VERSION = "test-stack-version";
+  private static final StackId STACK_ID = new StackId(STACK_NAME, STACK_VERSION);
   private static final String SAMPLE_QUICKLINKS_PROFILE_1 = "{\"filters\":[{\"visible\":true}],\"services\":[]}";
   private static final String SAMPLE_QUICKLINKS_PROFILE_2 =
       "{\"filters\":[],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}";
@@ -192,8 +196,6 @@ public class TopologyManagerTest {
   private Map<String, Collection<String>> group1ServiceComponents = new HashMap<>();
   private Map<String, Collection<String>> group2ServiceComponents = new HashMap<>();
 
-  private Map<String, Collection<String>> serviceComponents = new HashMap<>();
-
   private String predicate = "Hosts/host_name=foo";
 
   private List<TopologyValidator> topologyValidators = new ArrayList<>();
@@ -234,8 +236,9 @@ public class TopologyManagerTest {
     groupMap.put("group1", group1);
     groupMap.put("group2", group2);
 
-    serviceComponents.put("service1", Arrays.asList("component1", "component3"));
-    serviceComponents.put("service2", Arrays.asList("component2", "component4"));
+    Collection<String> components1 = ImmutableSet.of("component1", "component3");
+    Collection<String> components2 = ImmutableSet.of("component2", "component4");
+    Collection<String> components = ImmutableSet.<String>builder().addAll(components1).addAll(components2).build();
 
     group1ServiceComponents.put("service1", Arrays.asList("component1", "component3"));
     group1ServiceComponents.put("service2", Collections.singleton("component2"));
@@ -257,6 +260,7 @@ public class TopologyManagerTest {
     expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).anyTimes();
     expect(blueprint.getServices()).andReturn(Arrays.asList("service1", "service2")).anyTimes();
     expect(blueprint.getStack()).andReturn(stack).anyTimes();
+    expect(blueprint.getStackIds()).andReturn(ImmutableSet.of(STACK_ID)).anyTimes();
     expect(blueprint.getRepositorySettings()).andReturn(new ArrayList<>()).anyTimes();
     // don't expect toEntity()
 
@@ -270,9 +274,9 @@ public class TopologyManagerTest {
     expect(stack.getCardinality("component2")).andReturn(new Cardinality("1")).anyTimes();
     expect(stack.getCardinality("component3")).andReturn(new Cardinality("1+")).anyTimes();
     expect(stack.getCardinality("component4")).andReturn(new Cardinality("1+")).anyTimes();
-    expect(stack.getComponents()).andReturn(serviceComponents).anyTimes();
-    expect(stack.getComponents("service1")).andReturn(serviceComponents.get("service1")).anyTimes();
-    expect(stack.getComponents("service2")).andReturn(serviceComponents.get("service2")).anyTimes();
+    expect(stack.getComponents()).andReturn(components).anyTimes();
+    expect(stack.getComponents("service1")).andReturn(components1).anyTimes();
+    expect(stack.getComponents("service2")).andReturn(components2).anyTimes();
     expect(stack.getServiceForConfigType("service1-site")).andReturn("service1").anyTimes();
     expect(stack.getServiceForConfigType("service2-site")).andReturn("service2").anyTimes();
     expect(stack.getConfiguration()).andReturn(stackConfig).anyTimes();
@@ -340,7 +344,7 @@ public class TopologyManagerTest {
 
     ambariContext.setConfigurationOnCluster(capture(updateClusterConfigRequestCapture));
     expectLastCall().anyTimes();
-    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_NAME, STACK_VERSION);
+    ambariContext.persistInstallStateForUI(CLUSTER_NAME, STACK_ID);
     expectLastCall().anyTimes();
 
     expect(clusterController.ensureResourceProvider(anyObject(Resource.Type.class))).andReturn(resourceProvider);

-- 
To stop receiving notification emails like this one, please contact
adoroszlai@apache.org.