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

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


The following commit(s) were added to refs/heads/trunk by this push:
     new 50ea420  [AMBARI-24914] Block Ozone datanode co-location with HDFS 
Datanode when added though the API (dsen) (#2623)
50ea420 is described below

commit 50ea4200d389a5ddb41f15484cdddf0ae072e3e3
Author: Dmitry Sen <d...@apache.org>
AuthorDate: Thu Nov 22 11:33:50 2018 +0200

    [AMBARI-24914] Block Ozone datanode co-location with HDFS Datanode when 
added though the API (dsen) (#2623)
    
    * [AMBARI-24914] Block Ozone datanode co-location with HDFS Datanode when 
added though the API - initial (dsen)
    
    * [AMBARI-24914] Block Ozone datanode co-location with HDFS Datanode when 
added though the API - changes according to review + few fixes (dsen)
    
    * [AMBARI-24914] Block Ozone datanode co-location with HDFS Datanode when 
added though the API - fixed stack advisor (dsen)
---
 .../stackadvisor/commands/StackAdvisorCommand.java |  1 +
 .../controller/AmbariManagementControllerImpl.java | 57 ++++++++++++++++++++
 .../internal/StackDependencyResourceProvider.java  |  4 ++
 .../apache/ambari/server/state/DependencyInfo.java | 20 ++++++-
 .../server/topology/BlueprintValidatorImpl.java    | 61 ++++++++++++++--------
 .../src/main/resources/stacks/stack_advisor.py     | 36 +++++++------
 .../controller/AmbariManagementControllerTest.java | 43 ++++++++++++++-
 .../topology/BlueprintValidatorImplTest.java       | 41 +++++++++++++++
 .../stacks/HDP/0.2/services/HDFS/metainfo.xml      | 18 +++++++
 9 files changed, 243 insertions(+), 38 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
index a7d45fa..0028872 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommand.java
@@ -86,6 +86,7 @@ public abstract class StackAdvisorCommand<T extends 
StackAdvisorResponse> extend
       + 
"?fields=Versions/stack_name,Versions/stack_version,Versions/parent_stack_version"
       + 
",services/StackServices/service_name,services/StackServices/service_version"
       + 
",services/components/StackServiceComponents,services/components/dependencies/Dependencies/scope"
+      + ",services/components/dependencies/Dependencies/type"
       + 
",services/components/dependencies/Dependencies/conditions,services/components/auto_deploy"
       + ",services/configurations/StackConfigurations/property_depends_on"
       + 
",services/configurations/dependencies/StackConfigurationDependency/dependency_name"
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index 9b5f74b..038c29c 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -196,6 +196,7 @@ import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.Config;
 import org.apache.ambari.server.state.ConfigFactory;
 import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.DependencyInfo;
 import org.apache.ambari.server.state.DesiredConfig;
 import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.Host;
@@ -752,6 +753,8 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
       throw new DuplicateResourceException(msg + names);
     }
 
+    validateExclusiveDependencies(hostComponentNames);
+
     // set restartRequired flag for  monitoring services
     setMonitoringServicesRestartRequired(requests);
     // now doing actual work
@@ -759,6 +762,60 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
     
m_topologyHolder.get().updateData(getAddedComponentsTopologyEvent(requests));
   }
 
+  /**
+   * For all components that will be added validate the exclusive components 
dependencies using the services metainfo
+   * and respecting already installed components
+   *
+   * @throws AmbariException is thrown if the exclusive dependency is violated 
or if the data is invalid
+   */
+  private void validateExclusiveDependencies(Map<String, Map<String, 
Map<String, Set<String>>>> hostComponentNames) throws AmbariException {
+    List<String> validationIssues = new ArrayList<>();
+
+    for (Entry<String, Map<String, Map<String, Set<String>>>> clusterEntry : 
hostComponentNames.entrySet()) {
+      for (Entry<String, Map<String, Set<String>>> serviceEntry : 
clusterEntry.getValue().entrySet()) {
+        for (Entry<String, Set<String>> componentEntry : 
serviceEntry.getValue().entrySet()) {
+          Set<String> hostnames = componentEntry.getValue();
+          if (hostnames != null && !hostnames.isEmpty()) {
+            //get dependency info
+            ServiceComponent sc = 
clusters.getCluster(clusterEntry.getKey()).getService(serviceEntry.getKey()).getServiceComponent(componentEntry.getKey());
+            StackId stackId = sc.getDesiredStackId();
+            List<DependencyInfo> dependencyInfos = 
ambariMetaInfo.getComponentDependencies(stackId.getStackName(),
+              stackId.getStackVersion(), serviceEntry.getKey(), 
componentEntry.getKey());
+
+            for (DependencyInfo dependencyInfo : dependencyInfos) {
+              if ("host".equals(dependencyInfo.getScope()) && 
"exclusive".equals(dependencyInfo.getType())) {
+                Service depService = 
clusters.getCluster(clusterEntry.getKey()).getService(dependencyInfo.getServiceName());
+                if (depService != null && 
depService.getServiceComponents().containsKey(dependencyInfo.getComponentName()))
 {
+                  ServiceComponent dependentSC = 
depService.getServiceComponent(dependencyInfo.getComponentName());
+                  if (dependentSC != null) {
+                    //get cluster dependent component hosts
+                    Set<String> dependentComponentHosts = new 
HashSet<>(dependentSC.getServiceComponentHosts().keySet());
+                    //get request dependent component hosts
+                    if 
(clusterEntry.getValue().containsKey(dependentSC.getServiceName()) &&
+                        
clusterEntry.getValue().get(dependentSC.getServiceName()).containsKey(dependentSC.getName()))
 {
+                      dependentComponentHosts.addAll(clusterEntry.getValue().
+                          
get(dependentSC.getServiceName()).get(dependentSC.getName()));
+                    }
+                    //get the intersection
+                    dependentComponentHosts.retainAll(hostnames);
+                    if (!dependentComponentHosts.isEmpty()) {
+                      validationIssues.add("Component " + 
componentEntry.getKey() + " can't be co-hosted with component "
+                        + dependencyInfo.getComponentName() + " on hosts " + 
dependentComponentHosts + " due to exclusive dependency");
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (!validationIssues.isEmpty()) {
+      throw new AmbariException("The components exclusive dependencies are not 
respected: " + validationIssues);
+    }
+  }
+
   void persistServiceComponentHosts(Set<ServiceComponentHostRequest> requests, 
boolean isBlueprintProvisioned)
     throws AmbariException {
     Multimap<Cluster, ServiceComponentHost> schMap = 
ArrayListMultimap.create();
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDependencyResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDependencyResourceProvider.java
index f12a37c..d431970 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDependencyResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackDependencyResourceProvider.java
@@ -65,6 +65,8 @@ public class StackDependencyResourceProvider extends 
AbstractResourceProvider {
       PropertyHelper.getPropertyId("Dependencies", "component_name");
   protected static final String SCOPE_ID =
       PropertyHelper.getPropertyId("Dependencies", "scope");
+  protected static final String TYPE_ID =
+      PropertyHelper.getPropertyId("Dependencies", "type");
   protected static final String CONDITIONS_ID = PropertyHelper
     .getPropertyId("Dependencies","conditions");
   protected static final String AUTO_DEPLOY_ENABLED_ID = PropertyHelper
@@ -94,6 +96,7 @@ public class StackDependencyResourceProvider extends 
AbstractResourceProvider {
       SERVICE_NAME_ID,
       COMPONENT_NAME_ID,
       SCOPE_ID,
+      TYPE_ID,
       CONDITIONS_ID,
       AUTO_DEPLOY_ENABLED_ID,
       AUTO_DEPLOY_LOCATION_ID);
@@ -256,6 +259,7 @@ public class StackDependencyResourceProvider extends 
AbstractResourceProvider {
     setResourceProperty(resource, DEPENDENT_SERVICE_NAME_ID, dependentService, 
requestedIds);
     setResourceProperty(resource, DEPENDENT_COMPONENT_NAME_ID, 
dependentComponent, requestedIds);
     setResourceProperty(resource, SCOPE_ID, dependency.getScope(), 
requestedIds);
+    setResourceProperty(resource, TYPE_ID, dependency.getType(), requestedIds);
 
     AutoDeployInfo autoDeployInfo = dependency.getAutoDeploy();
     if (autoDeployInfo != null) {
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
index 2c4ddcc..f8408d9 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
@@ -44,6 +44,13 @@ public class DependencyInfo {
   private String scope;
 
   /**
+   * The type of the dependency.  Either "inclusive" or "exclusive".
+   * "inclusive" means the dependent component MUST be co-hosted or installed 
on the same cluster
+   * "exclusive" means the dependent component CAN'T be co-hosted or installed 
on the same cluster
+   */
+  private String type = "inclusive";
+
+  /**
    * Service name of the dependency.
    */
   private String serviceName;
@@ -173,11 +180,21 @@ public class DependencyInfo {
     return !CollectionUtils.isEmpty(dependencyConditions);
   }
 
+  public String getType() {
+    return type;
+  }
+
+  public void setType(String type) {
+    this.type = type;
+  }
+
   @Override
   public String toString() {
+    String autoDeployString = m_autoDeploy == null? "false" : 
String.valueOf(m_autoDeploy.isEnabled());
     return "DependencyInfo[name=" + getName() +
            ", scope=" + getScope() +
-           ", auto-deploy=" + m_autoDeploy.isEnabled() +
+           ", type=" + getType() +
+           ", auto-deploy=" + autoDeployString +
            "]";
   }
 
@@ -192,6 +209,7 @@ public class DependencyInfo {
     if (m_autoDeploy != null ? !m_autoDeploy.equals(that.m_autoDeploy) : 
that.m_autoDeploy != null) return false;
     if (name != null ? !name.equals(that.name) : that.name != null) return 
false;
     if (scope != null ? !scope.equals(that.scope) : that.scope != null) return 
false;
+    if (type != null ? !type.equals(that.type) : that.type != null) return 
false;
     if (serviceName != null ? !serviceName.equals(that.serviceName) : 
that.serviceName != null) return false;
 
     return true;
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 33c80e4..2253f61 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
@@ -65,12 +65,15 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
   public void validateTopology() throws InvalidTopologyException {
     LOGGER.info("Validating topology for blueprint: [{}]", 
blueprint.getName());
     Collection<HostGroup> hostGroups = blueprint.getHostGroups().values();
-    Map<String, Map<String, Collection<DependencyInfo>>> missingDependencies = 
new HashMap<>();
+    Map<String, Map<String, Collection<DependencyInfo>>> 
dependenciesValidationIssues = new HashMap<>();
 
     for (HostGroup group : hostGroups) {
-      Map<String, Collection<DependencyInfo>> missingGroupDependencies = 
validateHostGroup(group);
-      if (!missingGroupDependencies.isEmpty()) {
-        missingDependencies.put(group.getName(), missingGroupDependencies);
+      Map<String, Collection<DependencyInfo>> 
groupDependenciesValidationIssues =
+          validateHostGroup(group, "inclusive");
+
+      groupDependenciesValidationIssues.putAll(validateHostGroup(group, 
"exclusive"));
+      if (!groupDependenciesValidationIssues.isEmpty()) {
+        dependenciesValidationIssues.put(group.getName(), 
groupDependenciesValidationIssues);
       }
     }
 
@@ -90,8 +93,8 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
       }
     }
 
-    if (!missingDependencies.isEmpty() || !cardinalityFailures.isEmpty()) {
-      generateInvalidTopologyException(missingDependencies, 
cardinalityFailures);
+    if (!dependenciesValidationIssues.isEmpty() || 
!cardinalityFailures.isEmpty()) {
+      generateInvalidTopologyException(dependenciesValidationIssues, 
cardinalityFailures);
     }
   }
 
@@ -228,9 +231,9 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
     return cardinalityFailures;
   }
 
-  private Map<String, Collection<DependencyInfo>> validateHostGroup(HostGroup 
group) {
+  private Map<String, Collection<DependencyInfo>> validateHostGroup(HostGroup 
group, String dependencyValidationType) {
     LOGGER.info("Validating hostgroup: {}", group.getName());
-    Map<String, Collection<DependencyInfo>> missingDependencies = new 
HashMap<>();
+    Map<String, Collection<DependencyInfo>> dependenciesIssues = new 
HashMap<>();
 
     for (String component : new HashSet<>(group.getComponentNames())) {
       LOGGER.debug("Processing component: {}", component);
@@ -260,10 +263,15 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
         }
 
         String         dependencyScope = dependency.getScope();
+        String         dependencyType  = dependency.getType();
         String         componentName   = dependency.getComponentName();
         AutoDeployInfo autoDeployInfo  = dependency.getAutoDeploy();
         boolean        resolved        = false;
 
+        if (dependencyValidationType != null && 
!dependencyValidationType.equals(dependencyType)) {
+          continue;
+        }
+
         //check if conditions are met, if any
         if(dependency.hasDependencyConditions()) {
           boolean conditionsSatisfied = true;
@@ -282,24 +290,33 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
               componentName, new Cardinality("1+"), autoDeployInfo);
 
           resolved = missingDependencyInfo.isEmpty();
+          if (dependencyType.equals("exclusive")) {
+            resolved = !resolved;
+          }
         } else if (dependencyScope.equals("host")) {
-          if (group.getComponentNames().contains(componentName) || 
(autoDeployInfo != null && autoDeployInfo.isEnabled())) {
-            resolved = true;
-            group.addComponent(componentName);
+          if (dependencyType.equals("exclusive")) {
+            if (!group.getComponentNames().contains(componentName)) {
+              resolved = true;
+            }
+          } else {
+            if (group.getComponentNames().contains(componentName) || 
(autoDeployInfo != null && autoDeployInfo.isEnabled())) {
+              resolved = true;
+              group.addComponent(componentName);
+            }
           }
         }
 
         if (! resolved) {
-          Collection<DependencyInfo> missingCompDependencies = 
missingDependencies.get(component);
-          if (missingCompDependencies == null) {
-            missingCompDependencies = new HashSet<>();
-            missingDependencies.put(component, missingCompDependencies);
+          Collection<DependencyInfo> compDependenciesIssues = 
dependenciesIssues.get(component);
+          if (compDependenciesIssues == null) {
+            compDependenciesIssues = new HashSet<>();
+            dependenciesIssues.put(component, compDependenciesIssues);
           }
-          missingCompDependencies.add(dependency);
+          compDependenciesIssues.add(dependency);
         }
       }
     }
-    return missingDependencies;
+    return dependenciesIssues;
   }
 
   /**
@@ -379,13 +396,15 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
   /**
    * Generate an exception for topology validation failure.
    *
-   * @param missingDependencies  missing dependency information
+   * @param dependenciesIssues  dependency issues information,
+   *                            like component needs to be co-hosted,
+   *                            or components can't be installed on the same 
host
    * @param cardinalityFailures  missing service component information
    *
    * @throws IllegalArgumentException  Always thrown and contains information 
regarding the topology validation failure
    *                                   in the msg
    */
-  private void generateInvalidTopologyException(Map<String, Map<String, 
Collection<DependencyInfo>>> missingDependencies,
+  private void generateInvalidTopologyException(Map<String, Map<String, 
Collection<DependencyInfo>>> dependenciesIssues,
                                                 Collection<String> 
cardinalityFailures) throws InvalidTopologyException {
 
     //todo: encapsulate some of this in exception?
@@ -393,8 +412,8 @@ public class BlueprintValidatorImpl implements 
BlueprintValidator {
     if (! cardinalityFailures.isEmpty()) {
       msg += "  Invalid service component count: " + cardinalityFailures;
     }
-    if (! missingDependencies.isEmpty()) {
-      msg += "  Unresolved component dependencies: " + missingDependencies;
+    if (! dependenciesIssues.isEmpty()) {
+      msg += " Component dependencies issues: " + dependenciesIssues;
     }
     msg += ".  To disable topology validation and create the blueprint, " +
         "add the following to the end of the url: '?validate_topology=false'";
diff --git a/ambari-server/src/main/resources/stacks/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/stack_advisor.py
index 336ae75..9fa05ed 100644
--- a/ambari-server/src/main/resources/stacks/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/stack_advisor.py
@@ -1172,30 +1172,36 @@ class DefaultStackAdvisor(StackAdvisor):
             # account for only dependencies that are not conditional
             conditionsPresent =  "conditions" in dependency["Dependencies"] 
and dependency["Dependencies"]["conditions"]
             if not conditionsPresent:
-              requiredComponent = self.getRequiredComponent(services, 
dependency["Dependencies"]["component_name"])
+              dependentComponent = self.getRequiredComponent(services, 
dependency["Dependencies"]["component_name"])
               componentDisplayName = 
component["StackServiceComponents"]["display_name"]
-              requiredComponentDisplayName = requiredComponent["display_name"] 
\
-                                             if requiredComponent is not None 
else dependency["Dependencies"]["component_name"]
-              requiredComponentHosts = requiredComponent["hostnames"] if 
requiredComponent is not None else []
+              dependentComponentDisplayName = 
dependentComponent["display_name"] \
+                                             if dependentComponent is not None 
else dependency["Dependencies"]["component_name"]
+              dependentComponentHosts = dependentComponent["hostnames"] if 
dependentComponent is not None else []
 
               # Client dependencies are not included in validation
               # Client dependencies are auto-deployed in both UI deployements 
and blueprint deployments
-              if (requiredComponent is None) or \
-                (requiredComponent["component_category"] != "CLIENT"):
+              if (dependentComponent is None) or \
+                (dependentComponent["component_category"] != "CLIENT"):
                 scope = "cluster" if "scope" not in dependency["Dependencies"] 
else dependency["Dependencies"]["scope"]
+                type = "inclusive" if "type" not in dependency["Dependencies"] 
else dependency["Dependencies"]["type"]
                 if scope == "host":
                   componentHosts = 
component["StackServiceComponents"]["hostnames"]
-                  requiredComponentHostsAbsent = []
-                  for componentHost in componentHosts:
-                    if componentHost not in requiredComponentHosts:
-                      requiredComponentHostsAbsent.append(componentHost)
-                  if requiredComponentHostsAbsent:
-                    message = "{0} requires {1} to be co-hosted on following 
host(s): {2}.".format(componentDisplayName,
-                               requiredComponentDisplayName, ', 
'.join(requiredComponentHostsAbsent))
+                  hostsWithIssues = []
+                  notMessagePart = ""
+                  if type == "exclusive":
+                    hostsWithIssues = list(set(componentHosts) & 
set(dependentComponentHosts))
+                    notMessagePart = "not"
+                  else:
+                    for componentHost in componentHosts:
+                      if componentHost not in dependentComponentHosts:
+                        hostsWithIssues.append(componentHost)
+                  if hostsWithIssues:
+                    message = "{0} requires {1} {2} to be co-hosted on 
following host(s): {3}.".format(componentDisplayName,
+                               dependentComponentDisplayName, notMessagePart, 
', '.join(hostsWithIssues))
                     items.append({ "type": 'host-component', "level": 'ERROR', 
"message": message,
                                    "component-name": 
component["StackServiceComponents"]["component_name"]})
-                elif scope == "cluster" and not requiredComponentHosts:
-                  message = "{0} requires {1} to be present in the 
cluster.".format(componentDisplayName, requiredComponentDisplayName)
+                elif scope == "cluster" and not dependentComponentHosts:
+                  message = "{0} requires {1} to be present in the 
cluster.".format(componentDisplayName, dependentComponentDisplayName)
                   items.append({ "type": 'host-component', "level": 'ERROR', 
"message": message, "component-name": 
component["StackServiceComponents"]["component_name"]})
     return items
 
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
index c7c6360..4926e87 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
@@ -50,6 +50,7 @@ import java.util.UUID;
 
 import javax.persistence.EntityManager;
 
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.DuplicateResourceException;
 import org.apache.ambari.server.H2DatabaseCleaner;
@@ -194,7 +195,7 @@ public class AmbariManagementControllerTest {
   private static final int STACK_VERSIONS_CNT = 17;
   private static final int REPOS_CNT = 3;
   private static final int STACK_PROPERTIES_CNT = 103;
-  private static final int STACK_COMPONENTS_CNT = 4;
+  private static final int STACK_COMPONENTS_CNT = 5;
   private static final int OS_CNT = 2;
 
   private static final String NON_EXT_VALUE = "XXX";
@@ -1526,6 +1527,46 @@ public class AmbariManagementControllerTest {
         .getServiceComponentHost(host2));
   }
 
+  @Test(expected=AmbariException.class)
+  public void testCreateServiceComponentHostExclusiveAmbariException()
+      throws Exception {
+    String cluster1 = getUniqueName();
+    createCluster(cluster1);
+    String serviceName = "HDFS";
+    createService(cluster1, serviceName, null);
+    String componentName1 = "NAMENODE";
+    String componentName2 = "DATANODE";
+    String componentName3 = "EXCLUSIVE_DEPENDENCY_COMPONENT";
+    createServiceComponent(cluster1, serviceName, componentName1,
+        State.INIT);
+    createServiceComponent(cluster1, serviceName, componentName2,
+        State.INIT);
+    createServiceComponent(cluster1, serviceName, componentName3,
+        State.INIT);
+    String host1 = getUniqueName();
+    String host2 = getUniqueName();
+    addHostToCluster(host1, cluster1);
+    addHostToCluster(host2, cluster1);
+
+    Set<ServiceComponentHostRequest> set1 =
+      new HashSet<>();
+    ServiceComponentHostRequest r1 =
+        new ServiceComponentHostRequest(cluster1, serviceName,
+            componentName1, host1, State.INIT.toString());
+    ServiceComponentHostRequest r2 =
+        new ServiceComponentHostRequest(cluster1, serviceName,
+            componentName3, host1, State.INIT.toString());
+    ServiceComponentHostRequest r3 =
+        new ServiceComponentHostRequest(cluster1, serviceName,
+            componentName2, host1, State.INIT.toString());
+
+    set1.add(r1);
+    set1.add(r2);
+    set1.add(r3);
+
+    controller.createHostComponents(set1);
+  }
+
   @Test
   public void testCreateServiceComponentHostWithInvalidRequest()
       throws Exception, AuthorizationException {
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 75a9d6b..aec3903 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
@@ -180,6 +180,43 @@ public class BlueprintValidatorImplTest {
     verify(group1);
   }
 
+  @Test(expected=InvalidTopologyException.class)
+  public void testValidateTopology_exclusiveDependency() throws Exception {
+    group1Components.add("component2");
+    group1Components.add("component3");
+    dependencies1.add(dependency1);
+    services.addAll(Collections.singleton("service1"));
+
+    
expect(blueprint.getHostGroupsForComponent("component1")).andReturn(Arrays.asList(group1,
 group2)).anyTimes();
+    
expect(blueprint.getHostGroupsForComponent("component2")).andReturn(Arrays.asList(group1,
 group2)).anyTimes();
+    
expect(blueprint.getHostGroupsForComponent("component3")).andReturn(Arrays.asList(group1,
 group2)).anyTimes();
+
+    
expect(stack.getComponents("service1")).andReturn(Arrays.asList("component1", 
"component2")).anyTimes();
+    
expect(stack.getComponents("service2")).andReturn(Collections.singleton("component3")).anyTimes();
+    
expect(stack.getAutoDeployInfo("component1")).andReturn(autoDeploy).anyTimes();
+
+    AutoDeployInfo dependencyAutoDeploy = new AutoDeployInfo();
+    dependencyAutoDeploy.setEnabled(true);
+    dependencyAutoDeploy.setCoLocate("service1/component1");
+
+    expect(dependency1.getScope()).andReturn("host").anyTimes();
+    expect(dependency1.getType()).andReturn("exclusive").anyTimes();
+    
expect(dependency1.getAutoDeploy()).andReturn(dependencyAutoDeploy).anyTimes();
+    expect(dependency1.getComponentName()).andReturn("component3").anyTimes();
+    expect(dependency1.getServiceName()).andReturn("service1").anyTimes();
+    expect(dependency1.getName()).andReturn("dependency1").anyTimes();
+
+    expect(dependencyComponentInfo.isClient()).andReturn(true).anyTimes();
+    
expect(stack.getComponentInfo("component3")).andReturn(dependencyComponentInfo).anyTimes();
+
+    replay(blueprint, stack, group1, group2, dependency1, 
dependencyComponentInfo);
+
+    BlueprintValidator validator = new BlueprintValidatorImpl(blueprint);
+    validator.validateTopology();
+
+    verify(group1);
+  }
+
   @Test
   public void testValidateTopology_autoDeploy_hasDependency() throws Exception 
{
     group1Components.add("component2");
@@ -199,6 +236,7 @@ public class BlueprintValidatorImplTest {
     dependencyAutoDeploy.setCoLocate("service1/component1");
 
     expect(dependency1.getScope()).andReturn("host").anyTimes();
+    expect(dependency1.getType()).andReturn("inclusive").anyTimes();
     
expect(dependency1.getAutoDeploy()).andReturn(dependencyAutoDeploy).anyTimes();
     expect(dependency1.getComponentName()).andReturn("component3").anyTimes();
     expect(dependency1.getServiceName()).andReturn("service1").anyTimes();
@@ -343,6 +381,7 @@ public class BlueprintValidatorImplTest {
     AutoDeployInfo dependencyAutoDeploy = null;
 
     expect(dependency1.getScope()).andReturn("host").anyTimes();
+    expect(dependency1.getType()).andReturn("inclusive").anyTimes();
     
expect(dependency1.getAutoDeploy()).andReturn(dependencyAutoDeploy).anyTimes();
     expect(dependency1.getComponentName()).andReturn("component-d").anyTimes();
     expect(dependency1.getServiceName()).andReturn("service-d").anyTimes();
@@ -393,6 +432,7 @@ public class BlueprintValidatorImplTest {
     AutoDeployInfo dependencyAutoDeploy = null;
 
     expect(dependency1.getScope()).andReturn("host").anyTimes();
+    expect(dependency1.getType()).andReturn("inclusive").anyTimes();
     
expect(dependency1.getAutoDeploy()).andReturn(dependencyAutoDeploy).anyTimes();
     expect(dependency1.getComponentName()).andReturn("component-d").anyTimes();
     expect(dependency1.getServiceName()).andReturn("service-d").anyTimes();
@@ -400,6 +440,7 @@ public class BlueprintValidatorImplTest {
     expect(dependency1.hasDependencyConditions()).andReturn(true).anyTimes();
     
expect(dependency1.getDependencyConditions()).andReturn(dependenciesConditionInfos1).anyTimes();
     expect(dependency2.getScope()).andReturn("host").anyTimes();
+    expect(dependency2.getType()).andReturn("inclusive").anyTimes();
     
expect(dependency2.getAutoDeploy()).andReturn(dependencyAutoDeploy).anyTimes();
     expect(dependency2.getComponentName()).andReturn("component-d").anyTimes();
     expect(dependency2.getServiceName()).andReturn("service-d").anyTimes();
diff --git 
a/ambari-server/src/test/resources/stacks/HDP/0.2/services/HDFS/metainfo.xml 
b/ambari-server/src/test/resources/stacks/HDP/0.2/services/HDFS/metainfo.xml
index bfe941c..0f5d35c 100644
--- a/ambari-server/src/test/resources/stacks/HDP/0.2/services/HDFS/metainfo.xml
+++ b/ambari-server/src/test/resources/stacks/HDP/0.2/services/HDFS/metainfo.xml
@@ -52,6 +52,24 @@
             <scriptType>PYTHON</scriptType>
             <timeout>600</timeout>
           </commandScript>
+          <dependencies>
+            <dependency>
+              <name>HDFS/EXCLUSIVE_DEPENDENCY_COMPONENT</name>
+              <scope>host</scope>
+              <type>exclusive</type>
+            </dependency>
+          </dependencies>
+        </component>
+
+        <component>
+          <name>EXCLUSIVE_DEPENDENCY_COMPONENT</name>
+          <category>SLAVE</category>
+          <cardinality>1+</cardinality>
+          <commandScript>
+            <script>scripts/datanode.py</script>
+            <scriptType>PYTHON</scriptType>
+            <timeout>600</timeout>
+          </commandScript>
         </component>
 
         <component>

Reply via email to