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/05/30 11:36:32 UTC

[ambari] 05/07: AMBARI-22875. Service group name mismatch

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

commit 85c6c6e6cca7c052d51f2b2d50a4815720c6018f
Author: Doroszlai, Attila <ad...@apache.org>
AuthorDate: Wed May 30 00:28:58 2018 +0200

    AMBARI-22875. Service group name mismatch
---
 .../internal/ExportBlueprintRequest.java           |   3 +-
 .../orm/entities/HostGroupComponentEntity.java     |   2 +-
 .../server/orm/entities/MpackInstanceEntity.java   |  21 +-
 .../BlueprintBasedClusterProvisionRequest.java     |  11 -
 .../ambari/server/topology/BlueprintFactory.java   |  36 +--
 .../ambari/server/topology/BlueprintImpl.java      |  16 +-
 .../apache/ambari/server/topology/Component.java   |  47 +---
 .../ambari/server/topology/HostGroupImpl.java      |   2 +-
 .../ambari/server/topology/MpackInstance.java      |  48 +---
 .../ambari/server/topology/ResolvedComponent.java  |   9 +-
 .../server/topology/ResolvedComponent_Builder.java |   2 +-
 .../server/topology/StackComponentResolver.java    |  56 ++---
 .../src/main/resources/Ambari-DDL-Derby-CREATE.sql |   1 +
 .../src/main/resources/Ambari-DDL-MySQL-CREATE.sql |   1 +
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql    |   1 +
 .../main/resources/Ambari-DDL-Postgres-CREATE.sql  |   1 +
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql    |   1 +
 .../main/resources/Ambari-DDL-SQLServer-CREATE.sql |   1 +
 .../ambari/server/topology/AmbariContextTest.java  |   6 +-
 .../server/topology/BlueprintFactoryTest.java      |  42 ----
 .../server/topology/DownloadMpacksTaskTest.java    |   6 +-
 .../topology/StackComponentResolverTest.java       | 275 ++++++++++++++++++---
 .../server/topology/TopologyManagerTest.java       |   4 +-
 .../RejectUnknownComponentsTest.java}              |  10 +-
 24 files changed, 345 insertions(+), 257 deletions(-)

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 dc3f2d5..6e6fd69 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
@@ -272,8 +272,7 @@ public class ExportBlueprintRequest implements TopologyRequest {
           String.valueOf(resource.getPropertyValue(HostComponentResourceProvider.HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID));
         String serviceGroupName =
           String.valueOf(resource.getPropertyValue(HostComponentResourceProvider.HOST_COMPONENT_SERVICE_GROUP_NAME_PROPERTY_ID));
-        StackId stackId = serviceGroupToMpack.get(serviceGroupName);
-        getComponents().add(new Component(componentName, stackId, serviceName, null));
+        getComponents().add(new Component(componentName, serviceGroupName, serviceName, null));
       }
       addAmbariComponentIfLocalhost((String) host.getObject().getPropertyValue(
           PropertyHelper.getPropertyId("Hosts", "host_name")));
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostGroupComponentEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostGroupComponentEntity.java
index f16ef40..708fb3f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostGroupComponentEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostGroupComponentEntity.java
@@ -211,7 +211,7 @@ public class HostGroupComponentEntity {
 
   /**
    * @return the name of the service instance defining this component
-   *         (only needs to be set if component resolution would be ambigous otherwise)
+   *         (only needs to be set if component resolution would be ambiguous otherwise)
    */
   public String getServiceName() {
     return serviceName;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MpackInstanceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MpackInstanceEntity.java
index 1956108..9063ed0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MpackInstanceEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/MpackInstanceEntity.java
@@ -59,6 +59,9 @@ public abstract class MpackInstanceEntity {
   @Column(name = "mpack_name", nullable = false)
   private String mpackName;
 
+  @Column(name = "mpack_type", nullable = false)
+  private String mpackType;
+
   @Column(name = "mpack_version", nullable = false)
   private String mpackVersion;
 
@@ -90,20 +93,34 @@ public abstract class MpackInstanceEntity {
   }
 
   /**
-   * @return the name of the mpack
+   * @return the name of the mpack instance
    */
   public String getMpackName() {
     return mpackName;
   }
 
   /**
-   * @param mpackName the name of the mpack
+   * @param mpackName the name of the mpack instance
    */
   public void setMpackName(String mpackName) {
     this.mpackName = mpackName;
   }
 
   /**
+   * @return the name (type) of the mpack
+   */
+  public String getMpackType() {
+    return mpackType;
+  }
+
+  /**
+   * @param mpackType the name (type) of the mpack
+   */
+  public void setMpackType(String mpackType) {
+    this.mpackType = mpackType;
+  }
+
+  /**
    * @return the version of the mpack
    */
   public String getMpackVersion() {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintBasedClusterProvisionRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintBasedClusterProvisionRequest.java
index 22d4bab..58ce5d7 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintBasedClusterProvisionRequest.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/BlueprintBasedClusterProvisionRequest.java
@@ -20,7 +20,6 @@ package org.apache.ambari.server.topology;
 import static java.util.stream.Collectors.toMap;
 
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
@@ -162,16 +161,6 @@ public class BlueprintBasedClusterProvisionRequest implements Blueprint, Provisi
     return stack;
   }
 
-  public Map<String, Map<String, ServiceInstance>> getServicesByMpack() {
-    Map<String, Map<String, ServiceInstance>> result = new HashMap<>();
-    for (MpackInstance mpack : mpacks) {
-      Map<String, ServiceInstance> services = mpack.getServiceInstances().stream()
-        .collect(toMap(ServiceInstance::getName, Function.identity()));
-      result.put(mpack.getMpackName(), services);
-    }
-    return result;
-  }
-
   /**
    * @return service instances defined in the topology, mapped by service name,
    * whose name is unique across all mpacks.
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 cc29551..0f13539 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
@@ -18,8 +18,6 @@
 
 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;
@@ -44,9 +42,6 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.TreeSet;
-import java.util.function.Function;
-import java.util.stream.Stream;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -56,14 +51,12 @@ 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;
 
 /**
  * Create a Blueprint instance.
@@ -84,7 +77,7 @@ public class BlueprintFactory {
     BlueprintEntity entity = blueprintDAO.get().findByName(blueprintName);
     if (entity != null) {
       Set<StackId> stackIds = entity.getMpackInstances().stream()
-        .map(m -> new StackId(m.getMpackName(), m.getMpackVersion()))
+        .map(m -> new StackId(m.getMpackType(), m.getMpackVersion()))
         .collect(toSet());
       return new BlueprintImpl(entity, stackIds);
     }
@@ -113,7 +106,7 @@ public class BlueprintFactory {
       if (stackId.isPresent()) {
         String stackName = stackId.get().getStackName();
         String stackVersion = stackId.get().getStackVersion();
-        mpackInstances = Collections.singleton(new MpackInstance(stackName, stackVersion, null, null, Configuration.createEmpty()));
+        mpackInstances = Collections.singleton(new MpackInstance(stackName, stackName, stackVersion, null, Configuration.createEmpty()));
       }
     }
     Set<StackId> stackIds = mpackInstances.stream()
@@ -151,29 +144,6 @@ public class BlueprintFactory {
       : Optional.empty();
   }
 
-  /**
-   * 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);
-    }
-  }
-
   //todo: Move logic to HostGroupImpl
   @SuppressWarnings("unchecked")
   private Collection<HostGroup> processHostGroups(Map<String, Object> properties) {
@@ -227,7 +197,7 @@ public class BlueprintFactory {
       //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;
-      components.add(new Component(componentName, new StackId(mpackInstance), serviceInstance, provisionAction));
+      components.add(new Component(componentName, mpackInstance, serviceInstance, provisionAction));
     }
 
     return components;
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 633bcb0..7e60cb9 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,6 +29,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Supplier;
 
+import javax.annotation.Nonnull;
+
 import org.apache.ambari.server.orm.entities.BlueprintConfigEntity;
 import org.apache.ambari.server.orm.entities.BlueprintConfiguration;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
@@ -103,6 +105,7 @@ public class BlueprintImpl implements Blueprint {
     return stackIds;
   }
 
+  @Nonnull
   @Override
   public SecurityConfiguration getSecurity() {
     return security;
@@ -185,12 +188,8 @@ public class BlueprintImpl implements Blueprint {
 
   private Collection<MpackInstance> parseMpacks(BlueprintEntity blueprintEntity) throws NoSuchStackException {
     Collection<MpackInstance> mpackInstances = new ArrayList<>();
-    for (BlueprintMpackInstanceEntity mpack: blueprintEntity.getMpackInstances()) {
-      MpackInstance mpackInstance = new MpackInstance();
-      mpackInstance.setMpackName(mpack.getMpackName());
-      mpackInstance.setMpackVersion(mpack.getMpackVersion());
-      mpackInstance.setUrl(mpack.getMpackUri());
-      mpackInstance.setConfiguration(processConfiguration(mpack.getConfigurations()));
+    for (BlueprintMpackInstanceEntity mpack : blueprintEntity.getMpackInstances()) {
+      MpackInstance mpackInstance = new MpackInstance(mpack.getMpackName(), mpack.getMpackType(), mpack.getMpackVersion(), mpack.getMpackUri(), BlueprintImpl.fromConfigEntities(mpack.getConfigurations()));
       // TODO: come up with proper mpack -> stack resolution
       for(MpackInstanceServiceEntity serviceEntity: mpack.getServiceInstances()) {
         ServiceInstance serviceInstance = new ServiceInstance(
@@ -329,10 +328,7 @@ public class BlueprintImpl implements Blueprint {
       componentEntity.setHostGroupEntity(group);
       componentEntity.setHostGroupName(group.getName());
       componentEntity.setServiceName(component.getServiceInstance());
-      if (null != component.getStackId()) {
-        componentEntity.setMpackName(component.getStackId().getStackName());
-        componentEntity.setMpackVersion(component.getStackId().getStackVersion());
-      }
+      componentEntity.setMpackName(component.getMpackInstance());
 
       // add provision action (if specified) to entity type
       // otherwise, just leave this column null (provision_action)
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/Component.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/Component.java
index eac584f..99ab038 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/Component.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/Component.java
@@ -24,41 +24,25 @@ import java.util.Objects;
 import javax.annotation.Nullable;
 
 import org.apache.ambari.server.controller.internal.ProvisionAction;
-import org.apache.ambari.server.state.StackId;
+
+import com.google.common.base.MoreObjects;
 
 public class Component {
 
   private final String name;
-
-  @Nullable
   private final String mpackInstance;
-
-  @Nullable
-  private final StackId stackId;
-
-  @Nullable
   private final String serviceInstance;
   private final ProvisionAction provisionAction;
 
-  @Deprecated
   public Component(String name) {
     this(name, null, null, null);
   }
 
-  public Component(String name, @Nullable String mpackInstance, @Nullable String serviceInstance) {
+  public Component(String name, @Nullable String mpackInstance, @Nullable String serviceInstance, ProvisionAction provisionAction) {
     this.name = name;
-    this.stackId = null;
     this.mpackInstance = mpackInstance;
     this.serviceInstance = serviceInstance;
-    this.provisionAction = null;
-  }
-
-  public Component(String name, @Nullable StackId stackId, @Nullable String serviceInstance, ProvisionAction provisionAction) {
-    this.name = name;
-    this.stackId = stackId;
-    this.mpackInstance = null;
-    this.serviceInstance = serviceInstance;
-    this.provisionAction = provisionAction;
+    this.provisionAction = provisionAction != null ? provisionAction : ProvisionAction.INSTALL_AND_START;
   }
 
   /**
@@ -70,21 +54,7 @@ public class Component {
     return this.name;
   }
 
-  /**
-   * @return the mpack associated with this component as {@link String} (can be {@code null} if component -> mpack mapping is unambiguous)
-   */
-  public String getStackIdAsString() {
-    return stackId != null ? stackId.toString() : null;
-  }
-
-  /**
-   * @return the mpack associated with this component as {@link StackId} (can be {@code null} if component -> mpack mapping is unambiguous)
-   */
-  public StackId getStackId() {
-    return stackId;
-  }
-
-
+  @Nullable
   public String getMpackInstance() {
     return this.mpackInstance;
   }
@@ -93,6 +63,7 @@ public class Component {
    * @return the service instance this component belongs to. Can be {@code null} if component does not belong to a service
    * instance (there is a single service of the component's service type)
    */
+  @Nullable
   public String getServiceInstance() {
     return serviceInstance;
   }
@@ -109,9 +80,8 @@ public class Component {
 
   @Override
   public String toString() {
-    return com.google.common.base.Objects.toStringHelper(this)
+    return MoreObjects.toStringHelper(this)
       .add("name", name)
-      .add("stackId", stackId)
       .add("mpackInstance", mpackInstance)
       .add("serviceInstance", serviceInstance)
       .add("provisionAction", provisionAction)
@@ -125,7 +95,6 @@ public class Component {
     if (o == null || getClass() != o.getClass()) return false;
     Component component = (Component) o;
     return Objects.equals(name, component.name) &&
-      Objects.equals(stackId, component.stackId) &&
       Objects.equals(mpackInstance, component.mpackInstance) &&
       Objects.equals(serviceInstance, component.serviceInstance) &&
       provisionAction == component.provisionAction;
@@ -133,6 +102,6 @@ public class Component {
 
   @Override
   public int hashCode() {
-    return Objects.hash(name, stackId, mpackInstance, serviceInstance, provisionAction);
+    return Objects.hash(name, mpackInstance, serviceInstance, provisionAction);
   }
 }
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 83375d5..29e75a0 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
@@ -140,7 +140,7 @@ public class HostGroupImpl implements HostGroup {
 
       Component component = new Component(
         componentEntity.getName(),
-        stackId,
+        componentEntity.getMpackName(),
         componentEntity.getServiceName(),
         null == componentEntity.getProvisionAction() ? null : ProvisionAction.valueOf(componentEntity.getProvisionAction()));
 
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 882621b..5756941 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
@@ -21,7 +21,6 @@ package org.apache.ambari.server.topology;
 import java.util.ArrayList;
 import java.util.Collection;
 
-import org.apache.ambari.server.controller.internal.Stack;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
 import org.apache.ambari.server.orm.entities.BlueprintMpackInstanceEntity;
 import org.apache.ambari.server.orm.entities.MpackInstanceConfigEntity;
@@ -43,19 +42,23 @@ public class MpackInstance implements Configurable {
   @JsonProperty("url")
   private String url;
 
+  @JsonProperty("type")
   private String mpackType;
 
-  private Stack stack;
   private Configuration configuration = new Configuration();
 
   @JsonProperty("service_instances")
   private Collection<ServiceInstance> serviceInstances = new ArrayList<>();
 
-  public MpackInstance(String mpackName, String mpackVersion, String url, Stack stack, Configuration configuration) {
+  public MpackInstance(StackId stackId) {
+    this(null, stackId.getStackName(), stackId.getStackVersion(), null, Configuration.createEmpty());
+  }
+
+  public MpackInstance(String mpackName, String mpackType, String mpackVersion, String url, Configuration configuration) {
     this.mpackName = mpackName;
+    this.mpackType = mpackType;
     this.mpackVersion = mpackVersion;
     this.url = url;
-    this.stack = stack;
     this.configuration = configuration;
   }
 
@@ -69,42 +72,20 @@ public class MpackInstance implements Configurable {
   public MpackInstance() { }
 
   public String getMpackName() {
-    return mpackName;
-  }
-
-  public void setMpackName(String mpackName) {
-    this.mpackName = mpackName;
+    return mpackName != null ? mpackName : mpackType;
   }
 
   public String getMpackType() {
-    return mpackType;
-  }
-
-  public void setMpackType(String mpackType) {
-    this.mpackType = mpackType;
+    return mpackType != null ? mpackType : mpackName;
   }
 
-
   public String getMpackVersion() {
     return mpackVersion;
   }
 
-  public void setMpackVersion(String mpackVersion) {
-    this.mpackVersion = mpackVersion;
-  }
-
   @JsonIgnore
   public StackId getStackId() {
-    return new StackId(getMpackName(), getMpackVersion());
-  }
-
-  @JsonIgnore
-  public Stack getStack() {
-    return stack;
-  }
-
-  public void setStack(Stack stack) {
-    this.stack = stack;
+    return new StackId(getMpackType(), getMpackVersion());
   }
 
   @JsonIgnore
@@ -171,7 +152,8 @@ public class MpackInstance implements Configurable {
    */
   private void setCommonProperties(MpackInstanceEntity mpackInstanceEntity) {
     mpackInstanceEntity.setMpackUri(url);
-    mpackInstanceEntity.setMpackName(mpackName);
+    mpackInstanceEntity.setMpackName(getMpackName());
+    mpackInstanceEntity.setMpackType(getMpackType());
     mpackInstanceEntity.setMpackVersion(mpackVersion);
     Collection<MpackInstanceConfigEntity> mpackConfigEntities =
       BlueprintImpl.toConfigEntities(configuration, MpackInstanceConfigEntity::new);
@@ -192,11 +174,7 @@ public class MpackInstance implements Configurable {
   }
 
   public static MpackInstance fromEntity(MpackInstanceEntity entity) {
-    MpackInstance mpack = new MpackInstance();
-    mpack.setUrl(entity.getMpackUri());
-    mpack.setMpackName(entity.getMpackName());
-    mpack.setMpackVersion(entity.getMpackVersion());
-    mpack.setConfiguration(BlueprintImpl.fromConfigEntities(entity.getConfigurations()));
+    MpackInstance mpack = new MpackInstance(entity.getMpackName(), entity.getMpackType(), entity.getMpackVersion(), entity.getMpackUri(), BlueprintImpl.fromConfigEntities(entity.getConfigurations()));
     for (MpackInstanceServiceEntity serviceEntity: entity.getServiceInstances()) {
       ServiceInstance serviceInstance = new ServiceInstance();
       serviceInstance.setName(serviceEntity.getName());
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent.java
index 2b9d478..b3849b4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent.java
@@ -61,7 +61,14 @@ public interface ResolvedComponent {
       .component(component)
       .componentName(component.getName())
       .serviceName(Optional.ofNullable(component.getServiceInstance()))
-      .serviceGroupName(Optional.ofNullable(component.getStackIdAsString()));
+      .serviceGroupName(Optional.ofNullable(component.getMpackInstance()));
+  }
+
+  /**
+   * Starts building a {@code ResolvedComponent}.
+   */
+  static Builder builder(String name) {
+    return new Builder().componentName(name);
   }
 
   class Builder extends ResolvedComponent_Builder {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent_Builder.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent_Builder.java
index 7f3e805..082ed4b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent_Builder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/ResolvedComponent_Builder.java
@@ -34,7 +34,7 @@ import com.google.common.base.Preconditions;
  * Auto-generated superclass of {@link ResolvedComponent.Builder}, derived from the API of {@link
  * ResolvedComponent}.
  */
-abstract class ResolvedComponent_Builder {
+abstract class ResolvedComponent_Builder implements ResolvedComponent {
 
   /** Creates a new builder using {@code value} as a template. */
   public static ResolvedComponent.Builder from(ResolvedComponent value) {
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/StackComponentResolver.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/StackComponentResolver.java
index 783be40..4448feb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/topology/StackComponentResolver.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/StackComponentResolver.java
@@ -17,13 +17,16 @@
  */
 package org.apache.ambari.server.topology;
 
+import static java.util.stream.Collectors.toMap;
 import static java.util.stream.Collectors.toSet;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Stream;
 
@@ -34,7 +37,6 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 
 public class StackComponentResolver implements ComponentResolver {
@@ -43,20 +45,21 @@ public class StackComponentResolver implements ComponentResolver {
 
   @Override
   public Map<String, Set<ResolvedComponent>> resolveComponents(BlueprintBasedClusterProvisionRequest request) {
-    Map<String, ServiceInstance> uniqueServices = request.getUniqueServices();
-    Map<String, Map<String, ServiceInstance>> mpackServices = request.getServicesByMpack();
+    Collection<MpackInstance> mpacks = request.getMpacks();
+    Map<String, StackId> stackIdByMpackName = getMpackStackIds(mpacks);
 
     Map<String, Set<ResolvedComponent>> result = new HashMap<>();
     List<String> problems = new LinkedList<>();
 
     StackDefinition stack = request.getStack();
     for (HostGroup hg : request.getHostGroups().values()) {
-      result.put(hg.getName(), new HashSet<>());
+      Set<ResolvedComponent> hostGroupComponents = new HashSet<>();
+      result.put(hg.getName(), hostGroupComponents);
 
       for (Component comp : hg.getComponents()) {
+        String mpackInstanceName = comp.getMpackInstance();
         Stream<Pair<StackId, ServiceInfo>> servicesForComponent = stack.getServicesForComponent(comp.getName());
-        servicesForComponent = filterByMpackName(comp, servicesForComponent);
-        servicesForComponent = filterByServiceName(comp, servicesForComponent, mpackServices, uniqueServices);
+        servicesForComponent = filterByMpackName(mpackInstanceName, servicesForComponent, stackIdByMpackName);
 
         Set<Pair<StackId, ServiceInfo>> serviceMatches = servicesForComponent.collect(toSet());
 
@@ -75,13 +78,13 @@ public class StackComponentResolver implements ComponentResolver {
             .build();
 
           LOG.debug("Component resolved: " + resolved);
-          result.get(hg.getName()).add(resolved);
+          hostGroupComponents.add(resolved);
         }
       }
     }
 
     if (!problems.isEmpty()) {
-      throw new IllegalArgumentException("Component resolution failure:\n" + Joiner.on("\n").join(problems));
+      throw new IllegalArgumentException("Component resolution failure:\n" + String.join("\n", problems));
     }
 
     return result;
@@ -95,8 +98,8 @@ public class StackComponentResolver implements ComponentResolver {
       .append(" found for component ").append(comp.getName())
       .append(" in host group " ).append(hg.getName());
 
-    if (!Strings.isNullOrEmpty(comp.getStackIdAsString())) {
-      sb.append(" mpack: ").append(comp.getStackIdAsString());
+    if (!Strings.isNullOrEmpty(comp.getMpackInstance())) {
+      sb.append(" mpack: ").append(comp.getMpackInstance());
     }
     if (!Strings.isNullOrEmpty(comp.getServiceInstance())) {
       sb.append(" service: ").append(comp.getServiceInstance());
@@ -108,33 +111,22 @@ public class StackComponentResolver implements ComponentResolver {
     return sb.toString();
   }
 
-  // if component references a specific service instance, filter the stream by the type of that service
-  private static Stream<Pair<StackId, ServiceInfo>> filterByServiceName(Component comp, Stream<Pair<StackId, ServiceInfo>> stream,
-    Map<String, Map<String, ServiceInstance>> mpackServices, Map<String, ServiceInstance> uniqueServices
+  // if component references a specific mpack instance, filter the stream by the name of that mpack
+  private static Stream<Pair<StackId, ServiceInfo>> filterByMpackName(
+    String mpackInstanceName,
+    Stream<Pair<StackId, ServiceInfo>> stream,
+    Map<String, StackId> stackIdByMpackInstanceName
   ) {
-    if (!Strings.isNullOrEmpty(comp.getServiceInstance())) {
-      String mpackName = comp.getStackIdAsString();
-      Map<String, ServiceInstance> services = !Strings.isNullOrEmpty(mpackName)
-        ? mpackServices.get(mpackName)
-        : uniqueServices;
-
-      ServiceInstance service = services.get(comp.getServiceInstance());
-      if (service != null) {
-        String serviceType = service.getType();
-
-        return stream.filter(pair -> pair.getRight().getName().equals(serviceType));
-      }
+    if (mpackInstanceName != null) {
+      StackId mpackStackId = stackIdByMpackInstanceName.get(mpackInstanceName);
+      return stream.filter(pair -> Objects.equals(pair.getLeft(), mpackStackId));
     }
-
     return stream;
   }
 
-  // if component references a specific mpack instance, filter the stream by the name of that mpack
-  private static Stream<Pair<StackId, ServiceInfo>> filterByMpackName(Component comp, Stream<Pair<StackId, ServiceInfo>> stream) {
-    if (comp.getStackId() != null) {
-      return stream.filter(pair -> pair.getLeft().equals(comp.getStackId()));
-    }
-    return stream;
+  private static Map<String, StackId> getMpackStackIds(Collection<MpackInstance> mpacks) {
+    return mpacks.stream()
+      .collect(toMap(MpackInstance::getMpackName, MpackInstance::getStackId));
   }
 
 }
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 5c0e8a1..a4cfeb6 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -621,6 +621,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR(255),
   topology_request_id BIGINT,
   mpack_name VARCHAR(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
   mpack_uri VARCHAR(255),
   mpack_id BIGINT,
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 d9f1472..4dce027 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -639,6 +639,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR(255),
   topology_request_id BIGINT,
   mpack_name VARCHAR(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
   mpack_uri VARCHAR(255),
   mpack_id BIGINT,
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 a2b9dee..88bb0c3 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -619,6 +619,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR2(255),
   topology_request_id NUMBER(19),
   mpack_name VARCHAR2(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR2(255) NOT NULL,
   mpack_uri VARCHAR2(255),
   mpack_id NUMBER(19),
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 505e8db..dc33e43 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -622,6 +622,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR(255),
   topology_request_id BIGINT,
   mpack_name VARCHAR(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
   mpack_uri VARCHAR(255),
   mpack_id BIGINT,
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 0033a69..3e847c6 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -617,6 +617,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR(255),
   topology_request_id NUMERIC(19),
   mpack_name VARCHAR(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
   mpack_uri VARCHAR(255),
   mpack_id NUMERIC(19),
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 c6cca3c..1f1a24d 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -636,6 +636,7 @@ CREATE TABLE mpack_instance(
   blueprint_name VARCHAR(255),
   topology_request_id BIGINT,
   mpack_name VARCHAR(255) NOT NULL,
+  mpack_type VARCHAR(255) NOT NULL,
   mpack_version VARCHAR(255) NOT NULL,
   mpack_uri VARCHAR(255),
   mpack_id BIGINT,
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 b53bc5e..5354088 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
@@ -407,9 +407,9 @@ public class AmbariContextTest {
 
     // test
     Stream<ResolvedComponent> components = Stream.of(
-      ResolvedComponent.builder(new Component("component1", new StackId("mpack", "1.0"), "service1", null)).buildPartial(),
-      ResolvedComponent.builder(new Component("component2", new StackId("mpack", "1.0"), "service1", null)).buildPartial(),
-      ResolvedComponent.builder(new Component("component3", new StackId("mpack", "1.0"), "service2", null)).buildPartial()
+      ResolvedComponent.builder(new Component("component1", "mpack", "service1", null)).buildPartial(),
+      ResolvedComponent.builder(new Component("component2", "mpack", "service1", null)).buildPartial(),
+      ResolvedComponent.builder(new Component("component3", "mpack", "service2", null)).buildPartial()
     );
     context.createAmbariHostResources(CLUSTER_ID, "host1", components);
 
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 ff163d3..2b64390 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
@@ -35,7 +35,6 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.apache.ambari.server.controller.internal.BlueprintResourceProvider;
 import org.apache.ambari.server.controller.internal.BlueprintResourceProviderTest;
@@ -250,45 +249,4 @@ 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
-  }
-
 }
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/DownloadMpacksTaskTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/DownloadMpacksTaskTest.java
index 8a27be5..bce2a1e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/DownloadMpacksTaskTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/DownloadMpacksTaskTest.java
@@ -124,11 +124,7 @@ public class DownloadMpacksTaskTest {
   }
 
   private static MpackInstance mpack(String stackName, String stackVersion) {
-    MpackInstance mpack = new MpackInstance();
-    mpack.setMpackName(stackName);
-    mpack.setMpackVersion(stackVersion);
-    mpack.setUrl(createUri(stackName, stackVersion));
-    return mpack;
+    return new MpackInstance(stackName, stackName, stackVersion, createUri(stackName, stackVersion), Configuration.createEmpty());
   }
 
   private static String createUri(String stackName, String stackVersion) {
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java
index 6235aae..98179a6 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java
@@ -17,71 +17,280 @@
  */
 package org.apache.ambari.server.topology;
 
-import static org.easymock.EasyMock.createNiceMock;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
+import static org.junit.Assert.assertEquals;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Stream;
 
 import org.apache.ambari.server.controller.internal.StackDefinition;
-import org.apache.ambari.server.topology.validators.RejectUnknownComponents;
-import org.apache.ambari.server.topology.validators.TopologyValidator;
-import org.junit.After;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.commons.lang3.tuple.Pair;
+import org.easymock.EasyMockRule;
+import org.easymock.EasyMockSupport;
+import org.easymock.Mock;
+import org.easymock.MockType;
+import org.easymock.TestSubject;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Sets;
 
-public class StackComponentResolverTest {
+public class StackComponentResolverTest extends EasyMockSupport {
 
-  private final TopologyValidator validator = new RejectUnknownComponents();
-  private final ClusterTopology topology = createNiceMock(ClusterTopology.class);
-  private final StackDefinition stack = createNiceMock(StackDefinition.class);
+  private static final StackId STACK_ID1 = new StackId("HDPCORE", "1.0.0-b123");
+  private static final StackId STACK_ID2 = new StackId("ODS", "1.0.0-b1111");
+  private static final MpackInstance MPACK_INSTANCE1 = new MpackInstance(STACK_ID1);
+  private static final MpackInstance MPACK_INSTANCE2 = new MpackInstance(STACK_ID2);
+
+  private static final Comparator<ResolvedComponent> RESOLVED_COMPONENT_COMPARATOR = comparing((ResolvedComponent comp) -> comp.stackId().toString())
+    .thenComparing(ResolvedComponent::effectiveServiceGroupName)
+    .thenComparing(ResolvedComponent::effectiveServiceName)
+    .thenComparing(ResolvedComponent::componentName);
+
+  private static final Set<String> CLIENT_COMPONENTS = Sets.union(hdpCoreComponents(), odsComponents()).stream()
+    .map(ResolvedComponent::componentName)
+    .filter(each -> each.contains("_CLIENT"))
+    .collect(toSet());
+
+  @Rule
+  public EasyMockRule rule = new EasyMockRule(this);
+
+  @TestSubject
+  private final ComponentResolver subject = new StackComponentResolver();
+
+  @Mock(type = MockType.NICE)
+  private BlueprintBasedClusterProvisionRequest request;
+
+  @Mock(type = MockType.NICE)
+  private StackDefinition stack;
 
   @Before
-  public void setUp() {
-    expect(topology.getStack()).andReturn(stack).anyTimes();
+  public void setup() {
+    expect(request.getStack()).andReturn(stack).anyTimes();
   }
 
-  @After
-  public void tearDown() {
-    reset(topology, stack);
+  @Test(expected = IllegalArgumentException.class) // THEN
+  public void ambiguousComponentName() {
+    // GIVEN
+    Set<ResolvedComponent> components = build(Sets.union(hdpCoreComponents(), odsComponents()));
+    aStackWith(components);
+    defineMpacksAs(MPACK_INSTANCE1, MPACK_INSTANCE2);
+    aHostGroupWith(components);
+    replayAll();
+
+    // WHEN
+    Map<String, Set<ResolvedComponent>> resolved = subject.resolveComponents(request);
+  }
+
+  @Test(expected = IllegalArgumentException.class) // THEN
+  public void unknownComponent() {
+    // GIVEN
+    Set<ResolvedComponent> components = build(Sets.union(hdpCoreComponents(), odsComponents()));
+    aStackWith(components);
+    defineMpacksAs(MPACK_INSTANCE1, MPACK_INSTANCE2);
+    aHostGroupWith(new Component("UNKNOWN_COMPONENT"));
+    replayAll();
+
+    // WHEN
+    Map<String, Set<ResolvedComponent>> resolved = subject.resolveComponents(request);
   }
 
   @Test
-  public void acceptsKnownComponents() throws Exception {
+  public void withExplicitNamesOnlyForClients() {
     // GIVEN
-    componentsInTopologyAre("VALID_COMPONENT", "ANOTHER_COMPONENT");
-    validComponentsAre("VALID_COMPONENT", "ANOTHER_COMPONENT", "ONE_MORE_COMPONENT");
+    Set<ResolvedComponent.Builder> builders = Sets.union(hdpCoreComponents(), odsComponents());
+    builders.stream().filter(each -> CLIENT_COMPONENTS.contains(each.componentName())).forEach(each -> each.serviceGroupName(each.stackId().getStackName()));
+    Set<ResolvedComponent> components = build(builders);
+    aStackWith(components);
+    defineMpacksAs(MPACK_INSTANCE1, MPACK_INSTANCE2);
+    aHostGroupWith(components);
+    replayAll();
 
     // WHEN
-    validator.validate(topology);
+    Map<String, Set<ResolvedComponent>> resolved = subject.resolveComponents(request);
 
     // THEN
-    // no exception expected
+    assertHostGroupEquals(ImmutableMap.of("node", components), resolved);
   }
 
-  @Test(expected = InvalidTopologyException.class)
-  public void rejectsUnknownComponents() throws Exception {
+  @Test
+  public void withExplicitDefaultNames() {
     // GIVEN
-    componentsInTopologyAre("VALID_COMPONENT", "UNKNOWN_COMPONENT");
-    validComponentsAre("VALID_COMPONENT", "ANOTHER_COMPONENT");
+    String mpackInstanceName1 = STACK_ID1.getStackName(), mpackInstanceName2 = STACK_ID2.getStackName();
+    Set<ResolvedComponent> components = build(Sets.union(
+      inServiceGroup(mpackInstanceName1, hdpCoreComponents()),
+      inServiceGroup(mpackInstanceName2, odsComponents())
+    ));
+
+    aStackWith(components);
+
+    defineMpacksAs(
+      withCustomName(MPACK_INSTANCE1, mpackInstanceName1),
+      withCustomName(MPACK_INSTANCE2, mpackInstanceName2)
+    );
+
+    aHostGroupWith(components);
+    replayAll();
+
+    // WHEN
+    Map<String, Set<ResolvedComponent>> resolved = subject.resolveComponents(request);
+
+    // THEN
+    assertHostGroupEquals(ImmutableMap.of("node", components), resolved);
+  }
+
+  @Test
+  public void withCustomNames() {
+    // GIVEN
+    String mpackInstanceName1 = "hdp-core", mpackInstanceName2 = "ods!";
+    Set<ResolvedComponent> components = build(Sets.union(
+      inServiceGroup(mpackInstanceName1, hdpCoreComponents()),
+      inServiceGroup(mpackInstanceName2, odsComponents())
+    ));
+
+    aStackWith(components);
+
+    defineMpacksAs(
+      withCustomName(MPACK_INSTANCE1, mpackInstanceName1),
+      withCustomName(MPACK_INSTANCE2, mpackInstanceName2)
+    );
+
+    aHostGroupWith(components);
+    replayAll();
 
     // WHEN
-    validator.validate(topology);
+    Map<String, Set<ResolvedComponent>> resolved = subject.resolveComponents(request);
+
+    // THEN
+    assertHostGroupEquals(ImmutableMap.of("node", components), resolved);
+  }
+
+  private void defineMpacksAs(MpackInstance... mpacks) {
+    expect(request.getMpacks()).andReturn(ImmutableSet.copyOf(mpacks)).anyTimes();
+  }
+
+  private void aStackWith(Set<ResolvedComponent> components) {
+    Map<Pair<String, String>, List<ResolvedComponent>> byServiceName = components.stream()
+      .collect(groupingBy(each -> Pair.of(each.effectiveServiceGroupName(), each.effectiveServiceName())));
+
+    Map<ResolvedComponent, ServiceInfo> services = new HashMap<>();
+    for (Map.Entry<Pair<String, String>, List<ResolvedComponent>> entry : byServiceName.entrySet()) {
+      ServiceInfo service = createNiceMock(ServiceInfo.class);
+      expect(service.getName()).andReturn(entry.getValue().iterator().next().serviceType()).anyTimes();
+      for (ResolvedComponent component : entry.getValue()) {
+        services.put(component, service);
+      }
+    }
+
+    Map<String, List<Pair<ResolvedComponent, ServiceInfo>>> byComponentName = components.stream()
+      .map(each -> Pair.of(each, services.get(each)))
+      .collect(groupingBy(each -> each.getLeft().componentName()));
+
+    for (Map.Entry<String, List<Pair<ResolvedComponent, ServiceInfo>>> entry : byComponentName.entrySet()) {
+      expect(stack.getServicesForComponent(entry.getKey()))
+        .andAnswer(() -> entry.getValue().stream().map(each -> Pair.of(each.getLeft().stackId(), each.getRight()))).anyTimes();
+    }
+    expect(stack.getServicesForComponent(anyString())).andAnswer(Stream::empty).anyTimes();
+  }
+
+  private void mpacksWith(String serviceName, String componentName, MpackInstance... mpacks) {
+    defineMpacksAs(mpacks);
+
+    Collection<Pair<StackId, ServiceInfo>> services = Arrays.stream(mpacks)
+      .map(mpack -> {
+        ServiceInfo service = createNiceMock(ServiceInfo.class);
+        expect(service.getName()).andReturn(serviceName).anyTimes();
+        return Pair.of(mpack.getStackId(), service);
+      })
+      .collect(toList());
+    expect(stack.getServicesForComponent(componentName)).andAnswer(services::stream).anyTimes();
+  }
+
+  private void aHostGroupWith(Component component) {
+    hostGroupWith(ImmutableSet.of(component));
+  }
+
+  private void aHostGroupWith(Collection<ResolvedComponent> components) {
+    hostGroupWith(components.stream()
+      .map(each -> new Component(each.componentName(), each.serviceGroupName().orElse(null), each.effectiveServiceName(), null))
+      .collect(toSet()));
+  }
+
+  private void hostGroupWith(Set<Component> components) {
+    HostGroup hostGroup = new HostGroupImpl("node", components, Configuration.createEmpty(), "1+");
+    Map<String, HostGroup> hostGroups = ImmutableMap.of(hostGroup.getName(), hostGroup);
+    expect(request.getHostGroups()).andReturn(hostGroups).anyTimes();
+  }
+
+  private static Set<ResolvedComponent> build(ResolvedComponent.Builder builder) {
+    return build(ImmutableSet.of(builder));
+  }
+
+  private static Set<ResolvedComponent> build(Set<ResolvedComponent.Builder> builders) {
+    builders.forEach(each ->
+      each.component(new Component(each.componentName(), each.effectiveServiceGroupName(), each.effectiveServiceName(), null)));
+
+    return builders.stream()
+      .map(ResolvedComponent.Builder::build)
+      .collect(toSet());
+  }
+
+  private static MpackInstance withCustomName(MpackInstance mpack, String name) {
+    return new MpackInstance(name, mpack.getMpackType(), mpack.getMpackVersion(), mpack.getUrl(), mpack.getConfiguration());
+  }
+
+  private static Set<ResolvedComponent.Builder> inServiceGroup(String name, Collection<ResolvedComponent.Builder> components) {
+    return components.stream().map(each -> each.serviceGroupName(name)).collect(toSet());
+  }
+
+  private static void assertHostGroupEquals(Map<String, Set<ResolvedComponent>> expected, Map<String, Set<ResolvedComponent>> actual) {
+    assertEquals(expected.keySet(), actual.keySet());
+    for (String hostGroupName : expected.keySet()) {
+      Set<ResolvedComponent> expectedComponents = ImmutableSortedSet.copyOf(RESOLVED_COMPONENT_COMPARATOR, expected.get(hostGroupName));
+      Set<ResolvedComponent> actualComponents = ImmutableSortedSet.copyOf(RESOLVED_COMPONENT_COMPARATOR, actual.get(hostGroupName));
+      assertEquals(expectedComponents, actualComponents);
+    }
   }
 
-  private void componentsInTopologyAre(String... components) {
-    expect(topology.getComponents()).andReturn(Stream.of(components)
-      .map(name -> ResolvedComponent.builder(new Component(name)).buildPartial())
-    ).anyTimes();
-    replay(topology);
+  private static Set<ResolvedComponent.Builder> hdpCoreComponents() {
+    Set<ResolvedComponent.Builder> builders = ImmutableSet.of(
+      ResolvedComponent.builder("HADOOP_CLIENT").serviceType("HADOOP_CLIENTS"),
+      ResolvedComponent.builder("DATANODE").serviceType("HDFS"),
+      ResolvedComponent.builder("NAMENODE").serviceType("HDFS"),
+      ResolvedComponent.builder("ZOOKEEPER_CLIENT").serviceType("ZOOKEEPER_CLIENTS"),
+      ResolvedComponent.builder("ZOOKEEPER_SERVER").serviceType("ZOOKEEPER")
+    );
+    builders.forEach(each -> each.stackId(STACK_ID1));
+    return builders;
   }
 
-  private void validComponentsAre(String... components) {
-    expect(stack.getComponents()).andReturn(ImmutableSet.<String>builder().add(components).build()).anyTimes();
-    replay(stack);
+  private static Set<ResolvedComponent.Builder> odsComponents() {
+    Set<ResolvedComponent.Builder> builders = ImmutableSet.of(
+      ResolvedComponent.builder("HADOOP_CLIENT").serviceType("HADOOP_CLIENTS"),
+      ResolvedComponent.builder("HBASE_MASTER").serviceType("HBASE"),
+      ResolvedComponent.builder("HBASE_REGIONSERVER").serviceType("HBASE"),
+      ResolvedComponent.builder("HBASE_CLIENT").serviceType("HBASE_CLIENTS"),
+      ResolvedComponent.builder("ZOOKEEPER_CLIENT").serviceType("ZOOKEEPER_CLIENTS")
+    );
+    builders.forEach(each -> each.stackId(STACK_ID2));
+    return builders;
   }
 
 }
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 39799de..5f7c854 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
@@ -220,8 +220,8 @@ public class TopologyManagerTest {
   private final Collection<Component> group2Components = Arrays.asList(new Component("component3"), new Component("component4"));
   private final Collection<String> group2ComponentNames = group2Components.stream().map(Component::getName).collect(toSet());
 
-  private final MpackInstance mpack1 = new MpackInstance("HDPCORE", "1.0.0.0", "http://mpacks.org/hdpcore", null, new Configuration());
-  private final MpackInstance mpack2 = new MpackInstance("HDF", "3.3.0", "http://mpacks.org/hdf", null, new Configuration());
+  private final MpackInstance mpack1 = new MpackInstance("HDPCORE", "HDPCORE", "1.0.0.0", "http://mpacks.org/hdpcore", new Configuration());
+  private final MpackInstance mpack2 = new MpackInstance("HDF", "HDF", "3.3.0", "http://mpacks.org/hdf", new Configuration());
 
   private Map<String, Collection<String>> group1ServiceComponents = new HashMap<>();
   private Map<String, Collection<String>> group2ServiceComponents = new HashMap<>();
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/RejectUnknownComponentsTest.java
similarity index 89%
copy from ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java
copy to ambari-server/src/test/java/org/apache/ambari/server/topology/validators/RejectUnknownComponentsTest.java
index 6235aae..d71051b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/topology/StackComponentResolverTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/RejectUnknownComponentsTest.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ambari.server.topology;
+package org.apache.ambari.server.topology.validators;
 
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.expect;
@@ -25,15 +25,17 @@ import static org.easymock.EasyMock.reset;
 import java.util.stream.Stream;
 
 import org.apache.ambari.server.controller.internal.StackDefinition;
-import org.apache.ambari.server.topology.validators.RejectUnknownComponents;
-import org.apache.ambari.server.topology.validators.TopologyValidator;
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.Component;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.ResolvedComponent;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableSet;
 
-public class StackComponentResolverTest {
+public class RejectUnknownComponentsTest {
 
   private final TopologyValidator validator = new RejectUnknownComponents();
   private final ClusterTopology topology = createNiceMock(ClusterTopology.class);

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