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 <[email protected]>
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>