Repository: ambari
Updated Branches:
  refs/heads/branch-2.5 19eaf22e5 -> 85af32f2d


AMBARI-20488 Config types are validated before the cluster resources are 
persisted


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/0fb115b1
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/0fb115b1
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/0fb115b1

Branch: refs/heads/branch-2.5
Commit: 0fb115b126b81a4174f744697e2429ad2891d515
Parents: 19eaf22
Author: lpuskas <lpus...@apache.org>
Authored: Fri Mar 17 17:53:58 2017 +0100
Committer: lpuskas <lpus...@apache.org>
Committed: Thu Mar 30 16:27:14 2017 +0200

----------------------------------------------------------------------
 .../internal/ProvisionClusterRequest.java       |  47 +++---
 .../topology/RequiredPasswordValidator.java     | 156 ------------------
 .../validators/ClusterConfigTypeValidator.java  |  76 +++++++++
 .../validators/RequiredPasswordValidator.java   | 161 +++++++++++++++++++
 .../internal/ProvisionClusterRequestTest.java   |  12 +-
 .../topology/RequiredPasswordValidatorTest.java |  23 +--
 .../ClusterConfigTypeValidatorTest.java         | 110 +++++++++++++
 7 files changed, 390 insertions(+), 195 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
index 2c8d09a..a63013a 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequest.java
@@ -18,7 +18,6 @@
 package org.apache.ambari.server.controller.internal;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -36,15 +35,17 @@ import org.apache.ambari.server.topology.Credential;
 import org.apache.ambari.server.topology.HostGroupInfo;
 import org.apache.ambari.server.topology.InvalidTopologyTemplateException;
 import org.apache.ambari.server.topology.NoSuchBlueprintException;
-import org.apache.ambari.server.topology.RequiredPasswordValidator;
 import org.apache.ambari.server.topology.SecurityConfiguration;
 import org.apache.ambari.server.topology.TopologyValidator;
+import org.apache.ambari.server.topology.validators.ClusterConfigTypeValidator;
+import org.apache.ambari.server.topology.validators.RequiredPasswordValidator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Enums;
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 
 /**
  * Request for provisioning a cluster.
@@ -118,7 +119,6 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
   public static final String QUICKLINKS_PROFILE_SERVICES_PROPERTY = 
"quicklinks_profile/services";
 
 
-
   /**
    * configuration factory
    */
@@ -145,6 +145,8 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
   private final String quickLinksProfileJson;
 
+  private final List<TopologyValidator> topologyValidators;
+
   private final static Logger LOG = 
LoggerFactory.getLogger(ProvisionClusterRequest.class);
 
   /**
@@ -177,7 +179,7 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
     this.securityConfiguration = securityConfiguration;
 
     Configuration configuration = configurationFactory.getConfiguration(
-        (Collection<Map<String, String>>) 
properties.get(CONFIGURATIONS_PROPERTY));
+      (Collection<Map<String, String>>) 
properties.get(CONFIGURATIONS_PROPERTY));
     configuration.setParentConfiguration(blueprint.getConfiguration());
     setConfiguration(configuration);
 
@@ -191,10 +193,11 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
     try {
       this.quickLinksProfileJson = processQuickLinksProfile(properties);
-    }
-    catch (QuickLinksProfileEvaluationException ex) {
+    } catch (QuickLinksProfileEvaluationException ex) {
       throw new InvalidTopologyTemplateException("Invalid quick links 
profile", ex);
     }
+
+    topologyValidators = ImmutableList.of(new 
RequiredPasswordValidator(defaultPassword), new ClusterConfigTypeValidator());
   }
 
   private String processQuickLinksProfile(Map<String, Object> properties) 
throws QuickLinksProfileEvaluationException {
@@ -269,7 +272,7 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
   @Override
   public List<TopologyValidator> getTopologyValidators() {
-    return Collections.<TopologyValidator>singletonList(new 
RequiredPasswordValidator(defaultPassword));
+    return topologyValidators;
   }
 
   @Override
@@ -304,7 +307,7 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
    */
   private void parseHostGroupInfo(Map<String, Object> properties) throws 
InvalidTopologyTemplateException {
     Collection<Map<String, Object>> hostGroups =
-        (Collection<Map<String, Object>>) properties.get(HOSTGROUPS_PROPERTY);
+      (Collection<Map<String, Object>>) properties.get(HOSTGROUPS_PROPERTY);
 
     if (hostGroups == null || hostGroups.isEmpty()) {
       throw new InvalidTopologyTemplateException("'host_groups' element must 
be included in cluster create body");
@@ -334,11 +337,11 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
     processHostCountAndPredicate(hostGroupProperties, hostGroupInfo);
     processGroupHosts(name, (Collection<Map<String, String>>)
-        hostGroupProperties.get(HOSTGROUP_HOSTS_PROPERTY), hostGroupInfo);
+      hostGroupProperties.get(HOSTGROUP_HOSTS_PROPERTY), hostGroupInfo);
 
     // don't set the parent configuration
     hostGroupInfo.setConfiguration(configurationFactory.getConfiguration(
-        (Collection<Map<String, String>>) 
hostGroupProperties.get(CONFIGURATIONS_PROPERTY)));
+      (Collection<Map<String, String>>) 
hostGroupProperties.get(CONFIGURATIONS_PROPERTY)));
   }
 
   /**
@@ -350,20 +353,20 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
    * @throws InvalidTopologyTemplateException  specified host group properties 
fail validation
    */
   private void processHostCountAndPredicate(Map<String, Object> 
hostGroupProperties, HostGroupInfo hostGroupInfo)
-      throws InvalidTopologyTemplateException {
+    throws InvalidTopologyTemplateException {
 
     if (hostGroupProperties.containsKey(HOSTGROUP_HOST_COUNT_PROPERTY)) {
       hostGroupInfo.setRequestedCount(Integer.valueOf(String.valueOf(
-          hostGroupProperties.get(HOSTGROUP_HOST_COUNT_PROPERTY))));
+        hostGroupProperties.get(HOSTGROUP_HOST_COUNT_PROPERTY))));
       LOG.info("Stored expected hosts count {} for group {}",
-               hostGroupInfo.getRequestedHostCount(), 
hostGroupInfo.getHostGroupName());
+        hostGroupInfo.getRequestedHostCount(), 
hostGroupInfo.getHostGroupName());
     }
 
     if (hostGroupProperties.containsKey(HOSTGROUP_HOST_PREDICATE_PROPERTY)) {
       if (hostGroupInfo.getRequestedHostCount() == 0) {
         throw new InvalidTopologyTemplateException(String.format(
-            "Host group '%s' must not specify 'host_predicate' without 
'host_count'",
-            hostGroupInfo.getHostGroupName()));
+          "Host group '%s' must not specify 'host_predicate' without 
'host_count'",
+          hostGroupInfo.getHostGroupName()));
       }
 
       String hostPredicate = 
String.valueOf(hostGroupProperties.get(HOSTGROUP_HOST_PREDICATE_PROPERTY));
@@ -373,7 +376,7 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
         LOG.info("Compiled host predicate {} for group {}", hostPredicate, 
hostGroupInfo.getHostGroupName());
       } catch (InvalidQueryException e) {
         throw new InvalidTopologyTemplateException(
-            String.format("Unable to compile host predicate '%s': %s", 
hostPredicate, e), e);
+          String.format("Unable to compile host predicate '%s': %s", 
hostPredicate, e), e);
       }
     }
   }
@@ -388,17 +391,17 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
    * @throws InvalidTopologyTemplateException specified host group properties 
fail validation
    */
   private void processGroupHosts(String name, Collection<Map<String, String>> 
hosts, HostGroupInfo hostGroupInfo)
-      throws InvalidTopologyTemplateException {
+    throws InvalidTopologyTemplateException {
 
     if (hosts != null) {
       if (hostGroupInfo.getRequestedHostCount() != 0) {
         throw new InvalidTopologyTemplateException(String.format(
-            "Host group '%s' must not contain both a 'hosts' element and a 
'host_count' value", name));
+          "Host group '%s' must not contain both a 'hosts' element and a 
'host_count' value", name));
       }
 
       if (hostGroupInfo.getPredicate() != null) {
         throw new InvalidTopologyTemplateException(String.format(
-            "Host group '%s' must not contain both a 'hosts' element and a 
'host_predicate' value", name));
+          "Host group '%s' must not contain both a 'hosts' element and a 
'host_predicate' value", name));
       }
 
       for (Map<String, String> hostProperties : hosts) {
@@ -408,15 +411,15 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
         if (hostProperties.containsKey(HOSTGROUP_HOST_RACK_INFO_PROPERTY)) {
           hostGroupInfo.addHostRackInfo(
-              hostProperties.get(HOSTGROUP_HOST_FQDN_PROPERTY),
-              hostProperties.get(HOSTGROUP_HOST_RACK_INFO_PROPERTY));
+            hostProperties.get(HOSTGROUP_HOST_FQDN_PROPERTY),
+            hostProperties.get(HOSTGROUP_HOST_RACK_INFO_PROPERTY));
         }
       }
     }
 
     if (hostGroupInfo.getRequestedHostCount() == 0) {
       throw new InvalidTopologyTemplateException(String.format(
-          "Host group '%s' must contain at least one 'hosts/fqdn' or a 
'host_count' value", name));
+        "Host group '%s' must contain at least one 'hosts/fqdn' or a 
'host_count' value", name));
     }
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/main/java/org/apache/ambari/server/topology/RequiredPasswordValidator.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/RequiredPasswordValidator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/RequiredPasswordValidator.java
deleted file mode 100644
index 98eaa40..0000000
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/RequiredPasswordValidator.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distribut
- * ed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ambari.server.topology;
-
-import org.apache.ambari.server.controller.internal.Stack;
-import org.apache.ambari.server.state.PropertyInfo;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-
-/**
- * Validates that all required passwords are provided.
- */
-public class RequiredPasswordValidator implements TopologyValidator {
-
-  private String defaultPassword;
-
-  public RequiredPasswordValidator(String defaultPassword) {
-    this.defaultPassword = defaultPassword;
-  }
-
-  /**
-   * Validate that all required password properties have been set or that 
'default_password' is specified.
-   *
-   * @throws InvalidTopologyException if required password properties are 
missing and no
-   *                                  default is specified via 
'default_password'
-   */
-  public void validate(ClusterTopology topology) throws 
InvalidTopologyException {
-    Map<String, Map<String, Collection<String>>> missingPasswords = 
validateRequiredPasswords(topology);
-
-    if (! missingPasswords.isEmpty()) {
-      throw new InvalidTopologyException("Missing required password 
properties.  Specify a value for these " +
-          "properties in the cluster or host group configurations or include 
'default_password' field in request. " +
-          missingPasswords);
-    }
-  }
-
-  /**
-   * Validate all configurations.  Validation is done on the operational 
configuration of each
-   * host group.  An operational configuration is achieved by overlaying host 
group configuration
-   * on top of cluster configuration which overlays the default stack 
configurations.
-   *
-   * @return map of required properties which are missing.  Empty map if none 
are missing.
-   *
-   * @throws IllegalArgumentException if blueprint contains invalid information
-   */
-
-  //todo: this is copied/pasted from Blueprint and is currently only used by 
validatePasswordProperties()
-  //todo: seems that we should have some common place for this code so it can 
be used by BP and here?
-  private Map<String, Map<String, Collection<String>>> 
validateRequiredPasswords(ClusterTopology topology) {
-
-    Map<String, Map<String, Collection<String>>> missingProperties =
-        new HashMap<String, Map<String, Collection<String>>>();
-
-    for (Map.Entry<String, HostGroupInfo> groupEntry: 
topology.getHostGroupInfo().entrySet()) {
-      String hostGroupName = groupEntry.getKey();
-      Map<String, Map<String, String>> groupProperties =
-          groupEntry.getValue().getConfiguration().getFullProperties(3);
-
-      Collection<String> processedServices = new HashSet<String>();
-      Blueprint blueprint = topology.getBlueprint();
-      Stack stack = blueprint.getStack();
-
-      HostGroup hostGroup = blueprint.getHostGroup(hostGroupName);
-      for (String component : hostGroup.getComponentNames()) {
-        //for now, AMBARI is not recognized as a service in Stacks
-        if (component.equals("AMBARI_SERVER")) {
-          continue;
-        }
-
-        String serviceName = stack.getServiceForComponent(component);
-        if (processedServices.add(serviceName)) {
-          //todo: do I need to subtract excluded configs?
-          Collection<Stack.ConfigProperty> requiredProperties =
-              stack.getRequiredConfigurationProperties(serviceName, 
PropertyInfo.PropertyType.PASSWORD);
-
-          for (Stack.ConfigProperty property : requiredProperties) {
-            String category = property.getType();
-            String name = property.getName();
-            if (! propertyExists(topology, groupProperties, category, name)) {
-              Map<String, Collection<String>> missingHostGroupPropsMap = 
missingProperties.get(hostGroupName);
-              if (missingHostGroupPropsMap == null) {
-                missingHostGroupPropsMap = new HashMap<String, 
Collection<String>>();
-                missingProperties.put(hostGroupName, missingHostGroupPropsMap);
-              }
-              Collection<String> missingHostGroupTypeProps = 
missingHostGroupPropsMap.get(category);
-              if (missingHostGroupTypeProps == null) {
-                missingHostGroupTypeProps = new HashSet<String>();
-                missingHostGroupPropsMap.put(category, 
missingHostGroupTypeProps);
-              }
-              missingHostGroupTypeProps.add(name);
-            }
-          }
-        }
-      }
-    }
-    return missingProperties;
-  }
-
-  private boolean propertyExists(ClusterTopology topology, Map<String, 
Map<String, String>> props, String type, String property) {
-    Map<String, String> typeProps = props.get(type);
-    return (typeProps != null && typeProps.containsKey(property)) || 
setDefaultPassword(topology, type, property);
-  }
-
-  /**
-   * Attempt to set the default password in cluster configuration for missing 
password property.
-   *
-   * @param configType       configuration type
-   * @param property         password property name
-   *
-   * @return true if password was set, otherwise false.  Currently the 
password will always be set
-   *         unless it is null
-   */
-  private boolean setDefaultPassword(ClusterTopology topology, String 
configType, String property) {
-    boolean setDefaultPassword = false;
-    if (defaultPassword != null && ! defaultPassword.trim().isEmpty()) {
-      topology.getConfiguration().setProperty(configType, property, 
defaultPassword);
-      setDefaultPassword = true;
-    }
-    return setDefaultPassword;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    RequiredPasswordValidator that = (RequiredPasswordValidator) o;
-
-    return defaultPassword == null ? that.defaultPassword == null : 
defaultPassword.equals(that.defaultPassword);
-  }
-
-  @Override
-  public int hashCode() {
-    return defaultPassword != null ? defaultPassword.hashCode() : 0;
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidator.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidator.java
new file mode 100644
index 0000000..18d08b9
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidator.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.topology.validators;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.TopologyValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validates configuration types related to services specified in the 
blueprint.
+ * If the cluster creation template contains configuration types that are not 
related to services in the blueprint the
+ * validator fails interrupting the cluster provisioning.
+ */
+public class ClusterConfigTypeValidator implements TopologyValidator {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(ClusterConfigTypeValidator.class);
+
+  @Override
+  public void validate(ClusterTopology topology) throws 
InvalidTopologyException {
+
+    if (topology.getConfiguration() == null) {
+      LOGGER.debug("No configuration is set into the topology");
+      return;
+    }
+
+    if (topology.getConfiguration().getProperties() == null) {
+      LOGGER.debug("No properties is set into the topology configuration");
+      return;
+    }
+
+    // config types in from the request
+    Set<String> clusterConfigTypes = 
topology.getConfiguration().getProperties().keySet();
+    LOGGER.debug("Cluster config types: {}", clusterConfigTypes);
+
+    if (clusterConfigTypes == null || clusterConfigTypes.isEmpty()) {
+      LOGGER.debug("No config types to be checked.");
+      return;
+    }
+
+    // collecting all config types for services in the blueprint (from the 
related stack)
+    Set<String> serviceConfigTypes = new HashSet<>();
+    for (String serviceName : topology.getBlueprint().getServices()) {
+      
serviceConfigTypes.addAll(topology.getBlueprint().getStack().getConfigurationTypes(serviceName));
+    }
+
+    // identifying invalid config types
+    Set<String> configTypeIntersection = new 
HashSet<String>(serviceConfigTypes);
+
+    // if the intersection is changed, there's been some wrong config type 
provided in the cluster configuration
+    if (configTypeIntersection.retainAll(clusterConfigTypes)) {
+      LOGGER.debug("Valid config types: {}", configTypeIntersection);
+
+      // get the wrong  config types
+      Set<String> invalidConfigTypes = new HashSet<>(clusterConfigTypes);
+      invalidConfigTypes.removeAll(configTypeIntersection);
+
+      LOGGER.error("The following config typess are wrong: {}", 
invalidConfigTypes);
+      throw new InvalidTopologyException("The following configuration types 
are invalid: " + invalidConfigTypes.toString());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
new file mode 100644
index 0000000..f230d3d
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/validators/RequiredPasswordValidator.java
@@ -0,0 +1,161 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.topology.validators;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.topology.Blueprint;
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.HostGroup;
+import org.apache.ambari.server.topology.HostGroupInfo;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.apache.ambari.server.topology.TopologyValidator;
+
+/**
+ * Validates that all required passwords are provided.
+ */
+public class RequiredPasswordValidator implements TopologyValidator {
+
+  private String defaultPassword;
+
+  public RequiredPasswordValidator(String defaultPassword) {
+    this.defaultPassword = defaultPassword;
+  }
+
+  /**
+   * Validate that all required password properties have been set or that 
'default_password' is specified.
+   *
+   * @throws InvalidTopologyException if required password properties are 
missing and no
+   *                                  default is specified via 
'default_password'
+   */
+  public void validate(ClusterTopology topology) throws 
InvalidTopologyException {
+    Map<String, Map<String, Collection<String>>> missingPasswords = 
validateRequiredPasswords(topology);
+
+    if (! missingPasswords.isEmpty()) {
+      throw new InvalidTopologyException("Missing required password 
properties.  Specify a value for these " +
+          "properties in the cluster or host group configurations or include 
'default_password' field in request. " +
+          missingPasswords);
+    }
+  }
+
+  /**
+   * Validate all configurations.  Validation is done on the operational 
configuration of each
+   * host group.  An operational configuration is achieved by overlaying host 
group configuration
+   * on top of cluster configuration which overlays the default stack 
configurations.
+   *
+   * @return map of required properties which are missing.  Empty map if none 
are missing.
+   *
+   * @throws IllegalArgumentException if blueprint contains invalid information
+   */
+
+  //todo: this is copied/pasted from Blueprint and is currently only used by 
validatePasswordProperties()
+  //todo: seems that we should have some common place for this code so it can 
be used by BP and here?
+  private Map<String, Map<String, Collection<String>>> 
validateRequiredPasswords(ClusterTopology topology) {
+
+    Map<String, Map<String, Collection<String>>> missingProperties =
+        new HashMap<String, Map<String, Collection<String>>>();
+
+    for (Map.Entry<String, HostGroupInfo> groupEntry: 
topology.getHostGroupInfo().entrySet()) {
+      String hostGroupName = groupEntry.getKey();
+      Map<String, Map<String, String>> groupProperties =
+          groupEntry.getValue().getConfiguration().getFullProperties(3);
+
+      Collection<String> processedServices = new HashSet<String>();
+      Blueprint blueprint = topology.getBlueprint();
+      Stack stack = blueprint.getStack();
+
+      HostGroup hostGroup = blueprint.getHostGroup(hostGroupName);
+      for (String component : hostGroup.getComponentNames()) {
+        //for now, AMBARI is not recognized as a service in Stacks
+        if (component.equals("AMBARI_SERVER")) {
+          continue;
+        }
+
+        String serviceName = stack.getServiceForComponent(component);
+        if (processedServices.add(serviceName)) {
+          //todo: do I need to subtract excluded configs?
+          Collection<Stack.ConfigProperty> requiredProperties =
+              stack.getRequiredConfigurationProperties(serviceName, 
PropertyInfo.PropertyType.PASSWORD);
+
+          for (Stack.ConfigProperty property : requiredProperties) {
+            String category = property.getType();
+            String name = property.getName();
+            if (! propertyExists(topology, groupProperties, category, name)) {
+              Map<String, Collection<String>> missingHostGroupPropsMap = 
missingProperties.get(hostGroupName);
+              if (missingHostGroupPropsMap == null) {
+                missingHostGroupPropsMap = new HashMap<String, 
Collection<String>>();
+                missingProperties.put(hostGroupName, missingHostGroupPropsMap);
+              }
+              Collection<String> missingHostGroupTypeProps = 
missingHostGroupPropsMap.get(category);
+              if (missingHostGroupTypeProps == null) {
+                missingHostGroupTypeProps = new HashSet<String>();
+                missingHostGroupPropsMap.put(category, 
missingHostGroupTypeProps);
+              }
+              missingHostGroupTypeProps.add(name);
+            }
+          }
+        }
+      }
+    }
+    return missingProperties;
+  }
+
+  private boolean propertyExists(ClusterTopology topology, Map<String, 
Map<String, String>> props, String type, String property) {
+    Map<String, String> typeProps = props.get(type);
+    return (typeProps != null && typeProps.containsKey(property)) || 
setDefaultPassword(topology, type, property);
+  }
+
+  /**
+   * Attempt to set the default password in cluster configuration for missing 
password property.
+   *
+   * @param configType       configuration type
+   * @param property         password property name
+   *
+   * @return true if password was set, otherwise false.  Currently the 
password will always be set
+   *         unless it is null
+   */
+  private boolean setDefaultPassword(ClusterTopology topology, String 
configType, String property) {
+    boolean setDefaultPassword = false;
+    if (defaultPassword != null && ! defaultPassword.trim().isEmpty()) {
+      topology.getConfiguration().setProperty(configType, property, 
defaultPassword);
+      setDefaultPassword = true;
+    }
+    return setDefaultPassword;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    RequiredPasswordValidator that = (RequiredPasswordValidator) o;
+
+    return defaultPassword == null ? that.defaultPassword == null : 
defaultPassword.equals(that.defaultPassword);
+  }
+
+  @Override
+  public int hashCode() {
+    return defaultPassword != null ? defaultPassword.hashCode() : 0;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
index b114edc..c78910d 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ProvisionClusterRequestTest.java
@@ -49,9 +49,9 @@ import org.apache.ambari.server.topology.BlueprintFactory;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.HostGroupInfo;
 import org.apache.ambari.server.topology.InvalidTopologyTemplateException;
-import org.apache.ambari.server.topology.RequiredPasswordValidator;
 import org.apache.ambari.server.topology.TopologyRequest;
 import org.apache.ambari.server.topology.TopologyValidator;
+import org.apache.ambari.server.topology.validators.RequiredPasswordValidator;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -112,7 +112,7 @@ public class ProvisionClusterRequestTest {
     assertSame(blueprint, provisionClusterRequest.getBlueprint());
     Map<String, HostGroupInfo> hostGroupInfo = 
provisionClusterRequest.getHostGroupInfo();
     assertEquals(1, hostGroupInfo.size());
-    assertEquals(1, provisionClusterRequest.getTopologyValidators().size());
+    assertEquals(2, provisionClusterRequest.getTopologyValidators().size());
 
     // group1
     // host info
@@ -164,7 +164,7 @@ public class ProvisionClusterRequestTest {
     assertSame(blueprint, provisionClusterRequest.getBlueprint());
     Map<String, HostGroupInfo> hostGroupInfo = 
provisionClusterRequest.getHostGroupInfo();
     assertEquals(1, hostGroupInfo.size());
-    assertEquals(1, provisionClusterRequest.getTopologyValidators().size());
+    assertEquals(2, provisionClusterRequest.getTopologyValidators().size());
 
     // group2
     HostGroupInfo group2Info = hostGroupInfo.get("group2");
@@ -216,7 +216,7 @@ public class ProvisionClusterRequestTest {
     assertSame(blueprint, provisionClusterRequest.getBlueprint());
     Map<String, HostGroupInfo> hostGroupInfo = 
provisionClusterRequest.getHostGroupInfo();
     assertEquals(2, hostGroupInfo.size());
-    assertEquals(1, provisionClusterRequest.getTopologyValidators().size());
+    assertEquals(2, provisionClusterRequest.getTopologyValidators().size());
 
     // group1
     // host info
@@ -374,7 +374,7 @@ public class ProvisionClusterRequestTest {
     TopologyRequest request = new ProvisionClusterRequest(properties, null);
     List<TopologyValidator> validators = request.getTopologyValidators();
 
-    assertEquals(1, validators.size());
+    assertEquals(2, validators.size());
     TopologyValidator pwdValidator = validators.get(0);
 
     TopologyValidator noDefaultPwdValidator = new 
RequiredPasswordValidator(null);
@@ -388,7 +388,7 @@ public class ProvisionClusterRequestTest {
     TopologyRequest request = new ProvisionClusterRequest(properties, null);
     List<TopologyValidator> validators = request.getTopologyValidators();
 
-    assertEquals(1, validators.size());
+    assertEquals(2, validators.size());
     TopologyValidator pwdValidator = validators.get(0);
 
     TopologyValidator defaultPwdValidator = new 
RequiredPasswordValidator("pwd");

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/test/java/org/apache/ambari/server/topology/RequiredPasswordValidatorTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/RequiredPasswordValidatorTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/RequiredPasswordValidatorTest.java
index e8a2ff5..a077a7e 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/RequiredPasswordValidatorTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/RequiredPasswordValidatorTest.java
@@ -18,11 +18,12 @@
 
 package org.apache.ambari.server.topology;
 
-import org.apache.ambari.server.controller.internal.Stack;
-import org.apache.ambari.server.state.PropertyInfo;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+import static junit.framework.Assert.assertEquals;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.powermock.api.easymock.PowerMock.createNiceMock;
+import static org.powermock.api.easymock.PowerMock.verify;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -31,12 +32,12 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
-import static junit.framework.Assert.assertEquals;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.powermock.api.easymock.PowerMock.createNiceMock;
-import static org.powermock.api.easymock.PowerMock.verify;
+import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.topology.validators.RequiredPasswordValidator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Unit tests for RequiredPasswordValidator.

http://git-wip-us.apache.org/repos/asf/ambari/blob/0fb115b1/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidatorTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidatorTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidatorTest.java
new file mode 100644
index 0000000..24fa8b8
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/validators/ClusterConfigTypeValidatorTest.java
@@ -0,0 +1,110 @@
+package org.apache.ambari.server.topology.validators;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.topology.Blueprint;
+import org.apache.ambari.server.topology.ClusterTopology;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.ambari.server.topology.InvalidTopologyException;
+import org.easymock.EasyMock;
+import org.easymock.EasyMockRule;
+import org.easymock.EasyMockSupport;
+import org.easymock.Mock;
+import org.easymock.TestSubject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class ClusterConfigTypeValidatorTest extends EasyMockSupport {
+
+  @Rule
+  public EasyMockRule mocks = new EasyMockRule(this);
+
+
+  @Mock
+  private Configuration clusterConfigurationMock;
+
+  @Mock
+  private Map<String, Map<String, String>> clusterConfigurationMapMock;
+
+  @Mock
+  private Blueprint blueprintMock;
+
+  @Mock
+  private Stack stackMock;
+
+  @Mock
+  private ClusterTopology clusterTopologyMock;
+
+  @TestSubject
+  private ClusterConfigTypeValidator clusterConfigTypeValidator = new 
ClusterConfigTypeValidator();
+
+  @Before
+  public void before() {
+    
EasyMock.expect(clusterTopologyMock.getConfiguration()).andReturn(clusterConfigurationMock).anyTimes();
+    
EasyMock.expect(clusterConfigurationMock.getProperties()).andReturn(clusterConfigurationMapMock).anyTimes();
+
+    
EasyMock.expect(clusterTopologyMock.getBlueprint()).andReturn(blueprintMock).anyTimes();
+    EasyMock.expect(blueprintMock.getStack()).andReturn(stackMock).anyTimes();
+  }
+
+  @After
+  public void after() {
+    resetAll();
+  }
+
+  @Test(expected = InvalidTopologyException.class)
+  public void 
testShouldValidationFailWhenInvalidConfigGroupSpecifiedInCCTemplate() throws 
Exception {
+    // given
+    EasyMock.expect(clusterConfigurationMapMock.keySet()).andReturn(new 
HashSet<String>(Arrays.asList("oozie-site")));
+    EasyMock.expect(blueprintMock.getServices()).andReturn(new 
HashSet<String>(Arrays.asList("YARN", "HDFS")));
+
+    
EasyMock.expect(stackMock.getConfigurationTypes("HDFS")).andReturn(Arrays.asList("core-site"));
+    
EasyMock.expect(stackMock.getConfigurationTypes("YARN")).andReturn(Arrays.asList("yarn-site"));
+
+    replayAll();
+
+    //when
+    clusterConfigTypeValidator.validate(clusterTopologyMock);
+
+    // then
+    // Exception is thrown
+  }
+
+  @Test
+  public void testShouldValidationPassIfNoConfigTypesSpecifiedInCCTemplate() 
throws Exception {
+    //GIVEN
+    
EasyMock.expect(clusterConfigurationMapMock.keySet()).andReturn(Collections.<String>emptySet());
+    replayAll();
+
+    //WHEN
+    clusterConfigTypeValidator.validate(clusterTopologyMock);
+
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    
EqualsVerifier.forClass(ClusterConfigTypeValidator.class).usingGetClass().verify();
+  }
+}

Reply via email to