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

benyoka pushed a commit to branch branch-feature-AMBARI-14714
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-feature-AMBARI-14714 by 
this push:
     new 0e70cf2  [AMBARI-23130] Retrieve cluster template as artifact with 
passwords filtered out. (#931)
0e70cf2 is described below

commit 0e70cf2ab030e5eaebad9fb0d0f184c988e1bed3
Author: benyoka <beny...@users.noreply.github.com>
AuthorDate: Wed Apr 11 13:44:07 2018 +0200

    [AMBARI-23130] Retrieve cluster template as artifact with passwords 
filtered out. (#931)
    
    * AMBARI-23130 persist raw cluster provision request and extract stack ids 
on server restart (benyoka)
    
    * AMBARI-23130 add columnt to other DDLs + fix DDLs (benyoka)
    
    * AMBARI-23130 fix review findings (benyoka)
    
    * AMBARI-23130 persist only mpack instances instead of the full request
    
    * AMBARI-23130 address review findings (benyoka)
    
    * AMBARI-23130 topology request mpack information normalized (benyoka)
    
    * AMBARI-23130 fix broken unit test (benyoka)
    
    * AMBARI-23130 fix import and review comments
    
    * AMBARI-23130 persist cluster template as artifact WIP (benyoka)
    
    * AMBARI-23130 cluster template passwords replaced - almost complete 
(benyoka)
    
    * AMBARI-23130 save/retrieve cluster template as artifact review candidate 
(benyoka)
    
    * AMBARI-23130 fix review comments (benyoka)
    
    * AMBARI-23130 fix review comments #2 (benyoka)
    
    * AMBARI-23130 fix import (benyoka)
---
 .../internal/ArtifactResourceProvider.java         |  11 +-
 .../internal/ClusterResourceProvider.java          |   2 +-
 .../ClusterTemplateArtifactPasswordReplacer.java   | 160 +++++++++++++++++++
 .../apache/ambari/server/state/ServiceInfo.java    |   2 +-
 .../ambari/server/topology/Configurable.java       |  42 ++++-
 .../ambari/server/topology/TopologyManager.java    |  44 ++++--
 .../ambari/server/utils/SecretReference.java       | 142 ++++++++++++++++-
 .../internal/ArtifactResourceProviderTest.java     |  96 +++++++++++-
 .../internal/ClusterResourceProviderTest.java      |   8 +-
 ...lusterTemplateArtifactPasswordReplacerTest.java | 170 +++++++++++++++++++++
 .../topology/ClusterDeployWithStartOnlyTest.java   |   2 +-
 ...terInstallWithoutStartOnComponentLevelTest.java |   2 +-
 .../topology/ClusterInstallWithoutStartTest.java   |   2 +-
 .../server/topology/TopologyManagerTest.java       |  16 +-
 .../ambari/server/utils/SecretReferenceTest.java   | 131 ++++++++++++++++
 15 files changed, 792 insertions(+), 38 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ArtifactResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ArtifactResourceProvider.java
index 567cfa9..4fda556 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ArtifactResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ArtifactResourceProvider.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.controller.internal;
 
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -75,6 +76,8 @@ public class ArtifactResourceProvider extends 
AbstractResourceProvider {
   public static final String CLUSTER_NAME_PROPERTY = RESPONSE_KEY + 
PropertyHelper.EXTERNAL_PATH_SEP + CLUSTER_NAME;
   public static final String SERVICE_NAME_PROPERTY = RESPONSE_KEY + 
PropertyHelper.EXTERNAL_PATH_SEP + SERVICE_NAME;
 
+  public static final String PROVISION_REQUEST_ARTIFACT_NAME = 
"provision_cluster_request";
+
   /**
    * primary key fields
    */
@@ -525,7 +528,13 @@ public class ArtifactResourceProvider extends 
AbstractResourceProvider {
   private Resource toResource(ArtifactEntity entity, Set<String> requestedIds) 
throws AmbariException {
     Resource resource = new ResourceImpl(Resource.Type.Artifact);
     setResourceProperty(resource, ARTIFACT_NAME_PROPERTY, 
entity.getArtifactName(), requestedIds);
-    setResourceProperty(resource, ARTIFACT_DATA_PROPERTY, 
entity.getArtifactData(), requestedIds);
+    Map<String, Object> artifactData =
+      PROVISION_REQUEST_ARTIFACT_NAME.equals(entity.getArtifactName()) ?
+        // replace passwords for cluster template artifacts
+        new 
ClusterTemplateArtifactPasswordReplacer().replacePasswords(entity.getArtifactData())
 :
+        // return data as is for other artifacts
+        entity.getArtifactData();
+    setResourceProperty(resource, ARTIFACT_DATA_PROPERTY, artifactData, 
requestedIds);
 
     for (Map.Entry<String, String> entry : entity.getForeignKeys().entrySet()) 
{
       TypeRegistration typeRegistration = 
typeRegistrationsByShortFK.get(entry.getKey());
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
index f0dd517..4454195 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
@@ -540,7 +540,7 @@ public class ClusterResourceProvider extends 
AbstractControllerResourceProvider
     }
 
     try {
-      return topologyManager.provisionCluster(createClusterRequest);
+      return topologyManager.provisionCluster(createClusterRequest, 
rawRequestBody);
     } catch (InvalidTopologyException e) {
       throw new IllegalArgumentException("Topology validation failed: " + e, 
e);
     } catch (AmbariException e) {
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacer.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacer.java
new file mode 100644
index 0000000..3caeb0f
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacer.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.topology.Configurable;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.ambari.server.utils.SecretReference;
+
+import com.google.common.collect.Multimap;
+
+/**
+ * Helper class for replacing password properties in cluster template 
artifacts. {@see #replacePasswords}
+ */
+public class ClusterTemplateArtifactPasswordReplacer {
+
+  /**
+   * Replaces all passwords (service config passwords and default password) in 
a received cluster creation template
+   * artifact. Mpack (stack) information is used to identify password type 
properties. If the cluster template does not
+   * specify mpacks, all installed stacks are queried for password type 
properties.
+   *
+   * @param artifactData the raw cluster template artifact as parsed json
+   * @return the cluster template artifact with passwords replaced
+   */
+  public Map<String, Object> replacePasswords(Map<String, Object> 
artifactData) {
+    Collection<StackId> stackIds = 
extractStackIdsFromClusterRequest(artifactData);
+    // get all password properties from the specified stacks or
+    // all stacks if the cluster template doesn't specify mpacks
+    Multimap<String, String> passwordProperties = stackIds.isEmpty() ?
+      SecretReference.getAllPasswordProperties() :
+      SecretReference.getAllPasswordProperties(stackIds);
+    Map<String, Object> passwordsReplaced = 
replacePasswordsInConfigurations(artifactData, passwordProperties);
+    passwordsReplaced.replace("default_password", 
SecretReference.SECRET_PREFIX + ":default_password");
+    return passwordsReplaced;
+  }
+
+  /**
+   * Replaces passwords in the received cluster template artifact based on the 
received password information extracted
+   * from stacks.
+   * @param artifactData the cluster template artifact
+   * @param passwordProperties a multimap containing password type properties. 
The map has a structure of
+   *                           config type -> password properties.
+   * @return the cluster template artifact with passwords replaced
+   */
+  protected Map<String, Object> replacePasswordsInConfigurations(Map<String, 
Object> artifactData,
+                                                                 
Multimap<String, String> passwordProperties) {
+    return (Map<String, Object>)
+      applyToAllConfigurations(artifactData,
+        config -> {
+          Configuration configuration = Configurable.parseConfigs(config);
+          Configuration replacedConfiguration =
+            SecretReference.replacePasswordsInConfigurations(configuration, 
passwordProperties);
+          return Configurable.convertConfigToMap(replacedConfiguration);
+        }
+      );
+  }
+
+  /**
+   * <p> Recursively scans the received data structure consisting of maps, 
lists ans simple values (a parsed Json) and
+   * applies the @{code transform} function to each configurations found. </p>
+   * <p> A value counts as configuration if it has a type of @{link 
java.util.List} and is a value in a map with
+   * {@code "configurations"} key.
+   *
+   * </p>
+   * @param data the data to process recursively (is a structure of maps, 
lists and simple values)
+   * @param transform the transformation to apply to configuration values
+   * @return a replication of the input data with the transformation applied 
to all configuration values.
+   */
+  protected Object applyToAllConfigurations(Object data,
+                                            Function<List<Map<String, 
Object>>, Object> transform) {
+    if (data instanceof List<?>) {
+      return processList((List<Object>)data, transform);
+    }
+    else if (data instanceof Map<?, ?>) {
+      return processMap((Map<String, Object>)data, transform);
+    }
+    else {
+      return data;
+    }
+  }
+
+  /**
+   * Recursively call {@link #applyToAllConfigurations(Object, Function)} on 
all items in the list
+   * @param listItem the list to process
+   * @param transform the transformation to be passed to {@code 
applyToAllConfigurations}
+   * @return the transformed list
+   */
+  protected List<Object> processList(List<Object> listItem, 
Function<List<Map<String, Object>>, Object> transform) {
+    return listItem.stream().
+      map(item -> applyToAllConfigurations(item, transform)).
+      collect(toList());
+  }
+
+  /**
+   * Process map items in a cluster template artifact structure. For all 
configuration type map entries,
+   * {@code transform} will be called on the value. For other entries {@link 
#applyToAllConfigurations(Object, Function)}
+   * will be called recursively
+   * @param mapItem the map to process
+   * @param transform the transformation to apply on configuration type entries
+   * @return the transformed map
+   */
+  protected Map<String, Object> processMap(Map<String, Object> mapItem, 
Function<List<Map<String, Object>>, Object> transform) {
+    return mapItem.entrySet().stream().map(
+      entry -> {
+        // apply transformation for configuration entries
+        if ("configurations".equals(entry.getKey()) && entry.getValue() 
instanceof List<?>) {
+          return new AbstractMap.SimpleEntry<>(
+            entry.getKey(),
+            transform.apply((List<Map<String, Object>>)entry.getValue()));
+        }
+        // recursively call applyToAllConfigurations() for non-configuration 
entries
+        else {
+          return new AbstractMap.SimpleEntry<>(
+            entry.getKey(),
+            applyToAllConfigurations(entry.getValue(), transform));
+        }
+      }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
+  /**
+   * Extracts mpack / stack information from the cluster template artifacts
+   * @param artifactData the cluster template artifact
+   * @return a collection of stack id's (an empty collection if the artifact 
does not contain mpack references)
+   */
+  private Collection<StackId> extractStackIdsFromClusterRequest(Map<String, 
Object> artifactData) {
+    List<Map<String, Object>> mpackInstances =
+      (List<Map<String, Object>>)artifactData.getOrDefault("mpack_instances", 
emptyList());
+    return mpackInstances.stream().
+      map(mpackMap -> new StackId((String)mpackMap.get("name"), 
(String)mpackMap.get("version"))).
+      collect(toList());
+  }
+
+
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
index f939a53..4784c64 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
@@ -467,7 +467,7 @@ public class ServiceInfo implements Validable {
     return properties;
   }
 
-  public void setProperties(List properties) {
+  public void setProperties(List<PropertyInfo> properties) {
     this.properties = properties;
   }
 
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
index c20d10a..d42b966 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java
@@ -43,6 +43,32 @@ public interface Configurable {
 
   @JsonProperty("configurations")
   default void setConfigs(Collection<? extends Map<String, ?>> configs) {
+    setConfiguration(parseConfigs(configs));
+  }
+
+  /**
+   * Parses configuration maps The configs can be in fully structured JSON, 
e.g.
+   * <code>
+   * [{"hdfs-site":
+   *  "properties": {
+   *    ""dfs.replication": "3",
+   *    ...
+   *  },
+   *  properties_attributes: {}
+   * }]
+   * </code>
+   * or '/' separated like
+   * <code>
+   * [{
+   *  "hdfs-site/properties/dfs.replication": "3",
+   *  ...
+   * }]
+   * </code>
+   * In the latter case it calls {@link 
ConfigurationFactory#getConfiguration(Collection)}
+   * @param configs
+   * @return
+   */
+  static Configuration parseConfigs(Collection<? extends Map<String, ?>> 
configs) {
     Configuration configuration;
     if (!configs.isEmpty() && 
configs.iterator().next().keySet().iterator().next().contains("/")) {
       // Configuration has keys with slashes like "zk.cfg/properties/dataDir" 
means it is coming through
@@ -69,12 +95,15 @@ public interface Configurable {
       });
       configuration = new Configuration(allProperties, allAttributes);
     }
-    setConfiguration(configuration);
+    return configuration;
   }
 
-  @JsonProperty("configurations")
-  default Collection<Map<String, Map<String, ?>>> getConfigs() {
-    Configuration configuration = getConfiguration();
+  /**
+   * Converts {@link Configuration} objects to a collection easily 
serializable to Json
+   * @param configuration the configuration to convert
+   * @return the resulting collection
+   */
+  static Collection<Map<String, Map<String, ?>>> 
convertConfigToMap(Configuration configuration) {
     Collection<Map<String, Map<String, ?>>> configurations = new ArrayList<>();
     Set<String> allConfigTypes = 
Sets.union(configuration.getProperties().keySet(), 
configuration.getAttributes().keySet());
     for (String configType: allConfigTypes) {
@@ -90,4 +119,9 @@ public interface Configurable {
     return configurations;
   }
 
+  @JsonProperty("configurations")
+  default Collection<Map<String, Map<String, ?>>> getConfigs() {
+    return convertConfigToMap(getConfiguration());
+  }
+
 }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
index 81082aa..3fd37aa 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
@@ -101,6 +101,7 @@ public class TopologyManager {
   public static final String INITIAL_CONFIG_TAG = "INITIAL";
   public static final String TOPOLOGY_RESOLVED_TAG = "TOPOLOGY_RESOLVED";
   public static final String KDC_ADMIN_CREDENTIAL = "kdc.admin.credential";
+  public static final String RAW_REQUEST_BODY_ARTIFACT_NAME = 
"raw_provision_cluster_template";
 
   private PersistedState persistedState;
 
@@ -277,7 +278,8 @@ public class TopologyManager {
     return 
isLogicalRequestFinished(clusterProvisionWithBlueprintCreateRequests.get(clusterId));
   }
 
-  public RequestStatusResponse provisionCluster(final ProvisionClusterRequest 
request) throws InvalidTopologyException, AmbariException {
+  public RequestStatusResponse provisionCluster(final ProvisionClusterRequest 
request,
+                                               String rawRequestBody) throws 
InvalidTopologyException, AmbariException {
     ensureInitialized();
 
     BlueprintBasedClusterProvisionRequest provisionRequest = new 
BlueprintBasedClusterProvisionRequest(ambariContext, 
securityConfigurationFactory, request.getBlueprint(), request);
@@ -301,6 +303,8 @@ public class TopologyManager {
       submitKerberosDescriptorAsArtifact(clusterName, 
securityConfiguration.getDescriptor());
     }
 
+    submitRawRequestBodyAsArtifact(clusterName, rawRequestBody);
+
     if (securityConfiguration.getType() == SecurityType.KERBEROS) {
       Credential credential = 
request.getCredentialsMap().get(KDC_ADMIN_CREDENTIAL);
       submitCredential(clusterName, credential);
@@ -317,7 +321,8 @@ public class TopologyManager {
     LogicalRequest logicalRequest = RetryHelper.executeWithRetry(new 
Callable<LogicalRequest>() {
         @Override
         public LogicalRequest call() throws Exception {
-          LogicalRequest logicalRequest = 
processAndPersistProvisionClusterTopologyRequest(request, topology, 
provisionId);
+          LogicalRequest logicalRequest =
+            processAndPersistProvisionClusterTopologyRequest(request, 
topology, provisionId);
           return logicalRequest;
         }
       }
@@ -406,27 +411,41 @@ public class TopologyManager {
 
   }
 
+  private void submitRawRequestBodyAsArtifact(String clusterName, String 
rawRequestBody) {
+    submitArtifact(clusterName, 
ArtifactResourceProvider.PROVISION_REQUEST_ARTIFACT_NAME, rawRequestBody);
+  }
+
+
   private void submitKerberosDescriptorAsArtifact(String clusterName, String 
descriptor) {
+    submitArtifact(clusterName, "kerberos_descriptor", descriptor);
+  }
 
+  /**
+   * Submits an artifact to {@link ArtifactResourceProvider} for persistence
+   * @param clusterName the cluster name
+   * @param artifactName the artifact name (kerberos_descriptor or 
provision_cluster_request)
+   * @param artifactJson the artifact as json string
+   */
+  private void submitArtifact(String clusterName, String artifactName, String 
artifactJson) {
     ResourceProvider artifactProvider =
-        
AmbariContext.getClusterController().ensureResourceProvider(Resource.Type.Artifact);
+      
AmbariContext.getClusterController().ensureResourceProvider(Resource.Type.Artifact);
 
     Map<String, Object> properties = new HashMap<>();
-    properties.put(ArtifactResourceProvider.ARTIFACT_NAME_PROPERTY, 
"kerberos_descriptor");
+    properties.put(ArtifactResourceProvider.ARTIFACT_NAME_PROPERTY, 
artifactName);
     properties.put("Artifacts/cluster_name", clusterName);
 
     Map<String, String> requestInfoProps = new HashMap<>();
     
requestInfoProps.put(org.apache.ambari.server.controller.spi.Request.REQUEST_INFO_BODY_PROPERTY,
-            "{\"" + ArtifactResourceProvider.ARTIFACT_DATA_PROPERTY + "\": " + 
descriptor + "}");
+      "{\"" + ArtifactResourceProvider.ARTIFACT_DATA_PROPERTY + "\": " + 
artifactJson + "}");
 
     org.apache.ambari.server.controller.spi.Request request = new 
RequestImpl(Collections.emptySet(),
-        Collections.singleton(properties), requestInfoProps, null);
+      Collections.singleton(properties), requestInfoProps, null);
 
     try {
       RequestStatus status = artifactProvider.createResources(request);
       try {
         while (status.getStatus() != RequestStatus.Status.Complete) {
-          LOG.info("Waiting for kerberos_descriptor artifact creation.");
+          LOG.info("Waiting for {} artifact creation.", artifactName);
           Thread.sleep(100);
         }
       } catch (InterruptedException e) {
@@ -434,16 +453,17 @@ public class TopologyManager {
       }
 
       if (status.getStatus() != RequestStatus.Status.Complete) {
-        throw new RuntimeException("Failed to attach kerberos_descriptor 
artifact to cluster!");
+        throw new RuntimeException("Failed to attach " + artifactName + " 
artifact to cluster!");
       }
     } catch (SystemException | UnsupportedPropertyException | 
NoSuchParentResourceException e) {
-      throw new RuntimeException("Failed to attach kerberos_descriptor 
artifact to cluster: " + e);
+      throw new RuntimeException("Failed to attach " + artifactName + " 
artifact to cluster: " + e);
     } catch (ResourceAlreadyExistsException e) {
-      throw new RuntimeException("Failed to attach kerberos_descriptor 
artifact to cluster as resource already exists.");
+      throw new RuntimeException("Failed to attach " + artifactName + " 
artifact to cluster as resource already exists.");
     }
 
   }
 
+
   public RequestStatusResponse scaleHosts(final ScaleClusterRequest request)
       throws InvalidTopologyException, AmbariException {
 
@@ -550,7 +570,7 @@ public class TopologyManager {
 
   /**
    * Creates and persists a {@see PersistedTopologyRequest} and a {@see 
LogicalRequest} for the provided
-   * provision cluster request and topology.
+   * provision cluster request and topology. Also persists the quick links 
profile if present.
    * @param request Provision cluster request to create a logical request for.
    * @param topology Cluster topology
    * @param logicalRequestId The Id for the created logical request
@@ -560,10 +580,12 @@ public class TopologyManager {
   protected LogicalRequest 
processAndPersistProvisionClusterTopologyRequest(ProvisionClusterRequest 
request, ClusterTopology topology, Long logicalRequestId)
     throws InvalidTopologyException, AmbariException {
 
+    // persist quick links profile if present
     if (null != request.getQuickLinksProfileJson()) {
       saveOrUpdateQuickLinksProfile(request.getQuickLinksProfileJson());
     }
 
+    // create and persist topology request
     LogicalRequest logicalRequest = processAndPersistTopologyRequest(request, 
topology, logicalRequestId);
 
     return logicalRequest;
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/utils/SecretReference.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/utils/SecretReference.java
index dfd925d..2e4ee88 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/utils/SecretReference.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/utils/SecretReference.java
@@ -18,16 +18,33 @@
 
 package org.apache.ambari.server.utils;
 
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.StackAccessException;
 import org.apache.ambari.server.StaticallyInject;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AmbariServer;
+import org.apache.ambari.server.stack.StackDirectory;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Config;
 import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.topology.Configuration;
+import org.apache.commons.lang3.StringUtils;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
@@ -35,7 +52,7 @@ import com.google.inject.Inject;
 
 @StaticallyInject
 public class SecretReference {
-  private static final String secretPrefix = "SECRET";
+  public static final String SECRET_PREFIX = "SECRET";
   private String configType;
   private Long version;
   private String value;
@@ -81,11 +98,11 @@ public class SecretReference {
 
   public static boolean isSecret(String value) {
     String[] values = value.split(":");
-    return values.length == 4 && values[0].equals(secretPrefix);
+    return values.length == 4 && values[0].equals(SECRET_PREFIX);
   }
 
   public static String generateStub(String configType, Long configVersion, 
String propertyName) {
-    return secretPrefix + ":" + configType + ":" + configVersion + ":" + 
propertyName;
+    return SECRET_PREFIX + ":" + configType + ":" + configVersion + ":" + 
propertyName;
   }
 
   /**
@@ -100,7 +117,7 @@ public class SecretReference {
     for (Map.Entry<String, String> e : map.entrySet()) {
       String value = e.getValue();
       if (e.getKey().toLowerCase().contains(PASSWORD_TEXT) || 
e.getKey().toLowerCase().contains(PASSWD_TEXT)) {
-        value = secretPrefix;
+        value = SECRET_PREFIX;
       }
       maskedMap.put(e.getKey(), value);
     }
@@ -151,6 +168,51 @@ public class SecretReference {
   }
 
   /**
+   * Returns all password properties defined in the stacks specified by the 
given stack id's. Keys in the map are
+   * file names (e.g hadoop-env.xml) and values are property names.
+   * @param stackIds the stack ids to specify which stacks to look for
+   * @return A set multimap of password type properties.
+   * @throws IllegalArgumentException when a non-existing stack is specified
+   */
+  public static SetMultimap<String, String> 
getAllPasswordProperties(Collection<StackId> stackIds) throws 
IllegalArgumentException {
+    AmbariMetaInfo metaInfo = AmbariServer.getController().getAmbariMetaInfo();
+    Collection<StackInfo> stacks = stackIds.stream().map(
+      stackId -> {
+        try {
+          return metaInfo.getStack(stackId);
+        }
+        catch (StackAccessException ex) {
+          throw new IllegalArgumentException(ex);
+        }
+      }
+    ).collect(toList());
+    return getAllPasswordPropertiesInternal(stacks);
+  }
+
+  /**
+   * Returns all password properties defined in all stacks. Keys in the map are
+   * file names (e.g hadoop-env.xml) and values are property names.
+   * @return A set multimap of password type properties.
+   */
+  public static SetMultimap<String, String> getAllPasswordProperties() {
+    AmbariMetaInfo metaInfo = AmbariServer.getController().getAmbariMetaInfo();
+    return getAllPasswordPropertiesInternal(metaInfo.getStacks());
+  }
+
+  static SetMultimap<String, String> 
getAllPasswordPropertiesInternal(Collection<StackInfo> stacks) {
+    SetMultimap<String, String> passwordPropertyMap = HashMultimap.create();
+    stacks.stream().
+      flatMap(stack -> stack.getServices().stream()).
+      flatMap(serviceInfo -> serviceInfo.getProperties().stream()).
+      filter(propertyInfo -> 
propertyInfo.getPropertyTypes().contains(PropertyInfo.PropertyType.PASSWORD)).
+      forEach(propertyInfo -> passwordPropertyMap.put(
+        StringUtils.removeEnd(propertyInfo.getFilename(), 
StackDirectory.SERVICE_CONFIG_FILE_NAME_POSTFIX),
+        propertyInfo.getName())
+      );
+    return passwordPropertyMap;
+  }
+
+  /**
    * Replace real passwords with secret references
    * @param configAttributes map with config attributes containing properties 
types as part of their content
    * @param propertiesMap map with properties in which replacement will be 
performed
@@ -172,4 +234,76 @@ public class SecretReference {
       }
     }
   }
+
+  /**
+   * Replaces all password type properties in the given {@link Configuration} 
object. Creates a new Configuration
+   * object instead of mutating the input configuration.
+   * @param configuration the input configuration
+   * @param passwordProperties password type properties in a multimap.
+   *                           It has {@code config-type -> [password-prop-1, 
password-prop-2, ...]} structure.
+   * @return a new configuration with password properties replaced
+   */
+  public static Configuration replacePasswordsInConfigurations(Configuration 
configuration,
+                                                                     
Multimap<String, String> passwordProperties) {
+    // replace passwords in config properties
+    Map<String, Map<String, String>> replacedProperties = replaceInInnerMap(
+      configuration.getProperties(),
+      // in this case the map has {"configType": {"property": "value"}} 
structure
+      entry -> entry.getKey(),
+      passwordProperties);
+
+    // replace passwords in config attributes
+    Map<String, Map<String, Map<String, String>>> replacedAttributes = 
configuration.getAttributes().entrySet().stream().map(
+      configTypeEntry -> {
+        Map<String, Map<String, String>> replacedConfigProps = 
replaceInInnerMap(
+          configTypeEntry.getValue(),
+          // in this case the map has {"attributeType": {"property": "value"}} 
structure, the config type comes from the outer map
+          entry -> configTypeEntry.getKey(),
+          passwordProperties);
+        return new SimpleEntry<>(configTypeEntry.getKey(), 
replacedConfigProps);
+      }
+    ).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+    return new Configuration(replacedProperties, replacedAttributes);
+    }
+
+  /**
+   * @param input the map that contains a property map
+   * @param configType a function that calculates the config type based on the 
actual map entry processed
+   *                   during transformation
+   * @param passwordProperties password type properties in a multimap.
+   *                           It has {@code config-type -> [password-prop-1, 
password-prop-2, ...]} structure.
+   * @return a new map in which the inner property map has its password 
properties replaced
+   */
+    private static Map<String, Map<String, String>> 
replaceInInnerMap(Map<String, Map<String, String>> input,
+                                                               
Function<Map.Entry<String, Map<String, String>>, String> configType,
+                                                               
Multimap<String, String> passwordProperties) {
+      return input.entrySet().stream().collect(toMap(
+        Map.Entry::getKey,
+        entry -> replacePasswordsInPropertyMap(entry.getValue(), 
configType.apply(entry), passwordProperties)
+      ));
+    }
+
+  /**
+   * Replaces all password type properties in the given property map. Creates 
a new map instead of mutating
+   * the input configuration.
+   * @param propertyMap the input property map
+   * @param passwordProperties password type properties in a multimap.
+   *                           It has {@code config-type -> [password-prop-1, 
password-prop-2, ...]} structure.
+   * @return a new property map with password properties replaced
+   */
+  public static Map<String, String> replacePasswordsInPropertyMap(Map<String, 
String> propertyMap,
+                                                                  String 
configType,
+                                                                  
Multimap<String, String> passwordProperties) {
+    return propertyMap.entrySet().stream().map(
+      entry -> {
+        String propertyType = entry.getKey();
+        String newValue = 
passwordProperties.get(configType).contains(propertyType) ?
+          SECRET_PREFIX + ":" + configType + ":" + propertyType :
+          entry.getValue();
+        return new SimpleEntry<>(propertyType, newValue);
+      }
+    ).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
 }
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ArtifactResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ArtifactResourceProviderTest.java
index 2a0fdaa..afd8e4a 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ArtifactResourceProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ArtifactResourceProviderTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.anyString;
 import static org.easymock.EasyMock.capture;
 import static org.easymock.EasyMock.createMock;
@@ -56,17 +57,27 @@ import org.apache.ambari.server.orm.dao.ArtifactDAO;
 import org.apache.ambari.server.orm.entities.ArtifactEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.utils.SecretReference;
 import org.easymock.Capture;
 import org.easymock.IAnswer;
 import org.junit.Before;
 import org.junit.Test;
-
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSortedMap;
 import com.google.gson.Gson;
 
 /**
  * ArtifactResourceProvider unit tests.
  */
 @SuppressWarnings("unchecked")
+@RunWith(PowerMockRunner.class)
 public class ArtifactResourceProviderTest {
 
   private ArtifactDAO dao = createStrictMock(ArtifactDAO.class);
@@ -150,6 +161,89 @@ public class ArtifactResourceProviderTest {
     assertEquals("child4Value", 
resource.getPropertyValue("artifact_data/child/child2/child3/child4/child4Key"));
   }
 
+  /**
+   * Test to prove that passwords are replaced with references in case the 
retrieved artifact is of type
+   * {@link ArtifactResourceProvider#PROVISION_REQUEST_ARTIFACT_NAME}
+   */
+  @Test
+  @PrepareForTest(SecretReference.class)
+  public void testGetResources_clusterTemplatePasswordsAreReplaced() throws 
Exception {
+    // given
+    TreeMap<String, String> foreignKeys = new 
TreeMap<>(ImmutableSortedMap.of("cluster", "500"));
+    TreeMap<String, String> responseForeignKeys = new TreeMap<>(foreignKeys);
+
+    Map<String, Object> artifactData = ImmutableMap.of(
+      "configurations",
+        ImmutableList.of(
+          ImmutableMap.of("ranger-yarn-audit",
+            ImmutableMap.of("properties",
+              ImmutableMap.of(
+                "xasecure.policymgr.clientssl.keystore.password", 
"You'llNeverGuess",
+                "xasecure.policymgr.clientssl.keystore.credential.file", 
"jceks:/dev/null")))),
+      "default_password", "TopSecurePassword",
+      "mpack_instances",
+        ImmutableList.of(
+          ImmutableMap.of(
+            "name", "HDPCORE",
+            "version", "1.0.0")));
+
+    // expectations
+    expect(controller.getClusters()).andReturn(clusters).anyTimes();
+    expect(clusters.getCluster("test-cluster")).andReturn(cluster).anyTimes();
+    expect(clusters.getClusterById(500L)).andReturn(cluster).anyTimes();
+    expect(cluster.getClusterId()).andReturn(500L).anyTimes();
+    expect(cluster.getClusterName()).andReturn("test-cluster").anyTimes();
+
+    expect(request.getPropertyIds()).andReturn(new HashSet<>()).anyTimes();
+
+    
expect(dao.findByNameAndForeignKeys(eq(ArtifactResourceProvider.PROVISION_REQUEST_ARTIFACT_NAME),
+      eq(foreignKeys))).andReturn(entity).once();
+    
expect(entity.getArtifactName()).andReturn(ArtifactResourceProvider.PROVISION_REQUEST_ARTIFACT_NAME).anyTimes();
+    expect(entity.getForeignKeys()).andReturn(responseForeignKeys).anyTimes();
+    expect(entity.getArtifactData()).andReturn(artifactData).anyTimes();
+
+    PowerMock.mockStaticPartial(SecretReference.class, 
"getAllPasswordProperties");
+    
expect(SecretReference.getAllPasswordProperties(anyObject(Collection.class))).andReturn(
+      ImmutableSetMultimap.of("ranger-yarn-audit", 
"xasecure.policymgr.clientssl.keystore.password")
+    );
+
+    // end of expectation setting
+    replay(dao, em, controller, request, clusters, cluster, entity, entity2);
+    PowerMock.replay(SecretReference.class);
+
+    PredicateBuilder pb = new PredicateBuilder();
+    Predicate predicate = 
pb.begin().property("Artifacts/cluster_name").equals("test-cluster").and().
+      
property("Artifacts/artifact_name").equals(ArtifactResourceProvider.PROVISION_REQUEST_ARTIFACT_NAME).end().
+      toPredicate();
+
+    // when
+    Set<Resource> response = resourceProvider.getResources(request, predicate);
+
+    // then
+    assertEquals(1, response.size());
+    Resource resource = response.iterator().next();
+    Map<String, Map<String, Object>> responseProperties = 
resource.getPropertiesMap();
+    Map<String, Object> artifactDataMap = 
responseProperties.get("artifact_data");
+
+    Map<String, Object> expectedArtifactResultData = ImmutableMap.of(
+      "configurations",
+        ImmutableList.of(
+          ImmutableMap.of("ranger-yarn-audit",
+            ImmutableMap.of("properties",
+              ImmutableMap.of(
+                "xasecure.policymgr.clientssl.keystore.password",
+                  
"SECRET:ranger-yarn-audit:xasecure.policymgr.clientssl.keystore.password",
+                "xasecure.policymgr.clientssl.keystore.credential.file", 
"jceks:/dev/null")))),
+      "default_password", "SECRET:default_password",
+      "mpack_instances",
+      ImmutableList.of(
+        ImmutableMap.of(
+          "name", "HDPCORE",
+          "version", "1.0.0")));
+
+    assertEquals(expectedArtifactResultData, artifactDataMap);
+  }
+
   @Test
   public void testGetResources_collection() throws Exception {
     Set<String> propertyIds = new HashSet<>();
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java
index ba358f3..adc0503 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java
@@ -154,7 +154,7 @@ public class ClusterResourceProviderTest {
     
expect(securityFactory.createSecurityConfigurationFromRequest(EasyMock.anyObject(),
 anyBoolean())).andReturn(null)
       .once();
     expect(topologyFactory.createProvisionClusterRequest(properties, 
null)).andReturn(topologyRequest).once();
-    
expect(topologyManager.provisionCluster(topologyRequest)).andReturn(requestStatusResponse).once();
+    expect(topologyManager.provisionCluster(eq(topologyRequest), 
anyObject())).andReturn(requestStatusResponse).once();
     expect(requestStatusResponse.getRequestId()).andReturn(5150L).anyTimes();
 
     replayAll();
@@ -186,7 +186,7 @@ public class ClusterResourceProviderTest {
     expect(topologyFactory.createProvisionClusterRequest(properties, 
securityConfiguration)).andReturn(topologyRequest).once();
     
expect(securityFactory.createSecurityConfigurationFromRequest(EasyMock.anyObject(),
 anyBoolean())).andReturn
       (securityConfiguration).once();
-    
expect(topologyManager.provisionCluster(topologyRequest)).andReturn(requestStatusResponse).once();
+    expect(topologyManager.provisionCluster(eq(topologyRequest), 
anyObject())).andReturn(requestStatusResponse).once();
     expect(requestStatusResponse.getRequestId()).andReturn(5150L).anyTimes();
 
     replayAll();
@@ -472,7 +472,7 @@ public class ClusterResourceProviderTest {
     
expect(securityFactory.createSecurityConfigurationFromRequest(EasyMock.anyObject(),
 anyBoolean())).andReturn(null)
         .once();
     expect(topologyFactory.createProvisionClusterRequest(eq(properties), 
anyObject())).andReturn(topologyRequest).once();
-    
expect(topologyManager.provisionCluster(topologyRequest)).andReturn(requestStatusResponse).once();
+    expect(topologyManager.provisionCluster(eq(topologyRequest), 
anyObject())).andReturn(requestStatusResponse).once();
     expect(requestStatusResponse.getRequestId()).andReturn(5150L).anyTimes();
 
     replayAll();
@@ -803,7 +803,7 @@ public class ClusterResourceProviderTest {
     
expect(securityFactory.createSecurityConfigurationFromRequest(EasyMock.anyObject(),
 anyBoolean())).andReturn(null)
         .once();
     expect(topologyFactory.createProvisionClusterRequest(properties, 
null)).andReturn(topologyRequest).once();
-    
expect(topologyManager.provisionCluster(topologyRequest)).andReturn(requestStatusResponse).once();
+    expect(topologyManager.provisionCluster(eq(topologyRequest), 
anyObject())).andReturn(requestStatusResponse).once();
     expect(requestStatusResponse.getRequestId()).andReturn(5150L).anyTimes();
 
     replayAll();
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacerTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacerTest.java
new file mode 100644
index 0000000..1547f8f
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterTemplateArtifactPasswordReplacerTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class ClusterTemplateArtifactPasswordReplacerTest {
+
+  private static final List<Map<String, Object>> CONFIGURATION_1 =
+    createConfiguration("hdfs-site", map("property1", "value1"));
+
+  private static final List<Map<String, Object>> CONFIGURATION_2 =
+    createConfiguration("yarn-site", map("property2", "value2"));
+
+  private static final List<Map<String, Object>> CONFIGURATION_3 =
+    createConfiguration("hbase-site", map("property3", "value3"));
+
+  private static final List<Map<String, Object>> CONFIGURATION_1_REPLACED =
+    createConfiguration("hdfs-site", map("property1", "value1.replaced"));
+
+  private static final List<Map<String, Object>> CONFIGURATION_2_REPLACED =
+    createConfiguration("yarn-site", map("property2", "value2.replaced"));
+
+  private static final List<Map<String, Object>> CONFIGURATION_3_REPLACED =
+    createConfiguration("hbase-site", map("property3", "value3.replaced"));
+
+
+  private static final Map<String, Object> ORIGINAL_CLUSTER_TEMPLATE =
+    createClusterTemplateArtifact(CONFIGURATION_1, CONFIGURATION_2, 
CONFIGURATION_3);
+
+  private static final Map<String, Object> EXPECTED_PROCESSED_CLUSTER_TEMPLATE 
=
+    createClusterTemplateArtifact(CONFIGURATION_1_REPLACED, 
CONFIGURATION_2_REPLACED, CONFIGURATION_3_REPLACED);
+
+  /**
+   * Test to prove that all configurations are replaced within a cluster 
template structure no matter where
+   * they are defined.
+   * @throws Exception
+   */
+  @Test
+  public void testApplyToAllConfigurations() throws Exception {
+    // given
+    ClusterTemplateArtifactPasswordReplacer replacer = new 
ClusterTemplateArtifactPasswordReplacer();
+
+    // when
+    Object clusterTemplateWithReplacedConfigs =
+      replacer.applyToAllConfigurations(
+        ORIGINAL_CLUSTER_TEMPLATE,
+        config -> replaceValues(config));
+
+    // then
+    assertEquals(EXPECTED_PROCESSED_CLUSTER_TEMPLATE, 
clusterTemplateWithReplacedConfigs);
+  }
+
+  /**
+   * Creates a cluster template artifact with configuration objects at 
different levels
+   * @param configuration1 mpack instance level configuration
+   * @param configuration2 service instance level configuration
+   * @param configuration3 cluster template level configuration
+   * @return
+   */
+  private static Map<String, Object> createClusterTemplateArtifact(Object 
configuration1, Object configuration2, Object configuration3) {
+    return
+      map(
+        "mpack_instances",
+        list(
+          map(
+            "name", "HDPCORE",
+            "version", "1.0.0.0",
+            "configurations", configuration1,
+            "service_instances", list(
+              map(
+                "name", "ZK1",
+                "type", "ZOOKEEPER",
+                "configurations", configuration2
+              )
+            )
+          )
+        ),
+        "host_groups",
+        list(
+          map(
+            "name", "hostgroup1",
+            "host_count", "1"
+          )
+        ),
+        "configurations", configuration3
+      );
+  }
+
+  /**
+   *
+   * @param configType the config type, e.g. hdfs-site
+   * @param properties the properties
+   * @return a simple configuration in <code>[{"configType": {"properties": 
{"key1": "value1", ...}}}]</code> format.
+   */
+  private static List<Map<String, Object>> createConfiguration(String 
configType, Map<String, String> properties) {
+    return list(map(configType, map("properties", properties)));
+  }
+
+  /**
+   * Replaces property values by appending a ".replaced" suffix to each value. 
E.g:
+   * <code>[ {"hdfs-site": {"properties": {"property": "value", ...}}}</code>
+   * will become
+   * <code>[ {"hdfs-site": {"properties": {"property": "value.replaced", 
...}}}</code>
+   * @param configuration
+   * @return
+   */
+  private static List<Map<String, Object>> replaceValues(List<Map<String, 
Object>> configuration) {
+    return configuration.stream().map(
+      config -> {
+        String configType = config.keySet().iterator().next();
+        Map<String, String> properties =
+          ((Map<String, Map<String, 
String>>)config.values().iterator().next()).values().iterator().next();
+        ImmutableMap.Builder<String, Object> replacedProperties = 
ImmutableMap.builder();
+        properties.entrySet().stream().forEach(
+          e -> replacedProperties.put(e.getKey(), e.getValue() + ".replaced"));
+        return ImmutableMap.<String, Object>of(configType, map("properties", 
replacedProperties.build()));
+      }
+    ).collect(toList());
+  }
+
+  // ---- Convenience methods to create lists and maps
+
+  private static <T> List<T> list(T... elements) {
+    return ImmutableList.copyOf(elements);
+  }
+
+  private static <K, V> Map<K, V> map(K k1, V v1) {
+    return ImmutableMap.of(k1, v1);
+  }
+
+  private static <K, V> Map<K, V> map(K k1, V v1, K k2, V v2) {
+    return ImmutableMap.of(k1, v1, k2, v2);
+  }
+
+  private static <K, V> Map<K, V> map(K k1, V v1, K k2, V v2, K k3, V v3) {
+    return ImmutableMap.of(k1, v1, k2, v2, k3, v3);
+  }
+
+  private static <K, V> Map<K, V> map(K k1, V v1, K k2, V v2, K k3, V v3, K 
k4, V v4) {
+    return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4);
+  }
+
+}
\ No newline at end of file
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
index dab9062..8cce356 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterDeployWithStartOnlyTest.java
@@ -446,7 +446,7 @@ public class ClusterDeployWithStartOnlyTest extends 
EasyMockSupport {
 
   @Test
   public void testProvisionCluster() throws Exception {
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     LogicalRequest request = topologyManager.getRequest(1);
     assertEquals(request.getHostRequests().size(), 3);
   }
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
index 13d5b17..9c54ff5 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartOnComponentLevelTest.java
@@ -425,6 +425,6 @@ public class ClusterInstallWithoutStartOnComponentLevelTest 
extends EasyMockSupp
 
   @Test
   public void testProvisionCluster() throws Exception {
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
   }
 }
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
index d0841ab..52e83b4 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/ClusterInstallWithoutStartTest.java
@@ -425,6 +425,6 @@ public class ClusterInstallWithoutStartTest extends 
EasyMockSupport {
 
   @Test
   public void testProvisionCluster() throws Exception {
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
   }
 }
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
index 9f3a1f0..e60123e 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
@@ -415,7 +415,7 @@ public class TopologyManagerTest {
     
expect(persistedState.getAllRequests()).andReturn(Collections.emptyMap()).anyTimes();
     replayAll();
 
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     //todo: assertions
   }
 
@@ -439,7 +439,7 @@ public class TopologyManagerTest {
 
     replayAll();
 
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     //todo: assertions
   }
 
@@ -462,7 +462,7 @@ public class TopologyManagerTest {
     expect(logicalRequest.isFinished()).andReturn(true).anyTimes();
     expect(logicalRequest.isSuccessful()).andReturn(true).anyTimes();
     replayAll();
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     requestFinished();
     
Assert.assertTrue(topologyManager.isClusterProvisionWithBlueprintFinished(CLUSTER_ID));
   }
@@ -486,7 +486,7 @@ public class TopologyManagerTest {
     expect(logicalRequest.isFinished()).andReturn(true).anyTimes();
     expect(logicalRequest.isSuccessful()).andReturn(false).anyTimes();
     replayAll();
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     requestFinished();
     
Assert.assertTrue(topologyManager.isClusterProvisionWithBlueprintFinished(CLUSTER_ID));
   }
@@ -509,7 +509,7 @@ public class TopologyManagerTest {
     
expect(persistedState.getProvisionRequest(CLUSTER_ID)).andReturn(logicalRequest).anyTimes();
     expect(logicalRequest.isFinished()).andReturn(false).anyTimes();
     replayAll();
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     requestFinished();
     
Assert.assertFalse(topologyManager.isClusterProvisionWithBlueprintFinished(CLUSTER_ID));
   }
@@ -583,7 +583,7 @@ public class TopologyManagerTest {
     replay(bpfMock);
     
expect(persistedState.getAllRequests()).andReturn(Collections.emptyMap()).anyTimes();
     replayAll();
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
     topologyManager.scaleHosts(new ScaleClusterRequest(propertySet));
     Assert.fail("InvalidTopologyException should have been thrown");
   }
@@ -608,7 +608,7 @@ public class TopologyManagerTest {
 
     replayAll();
 
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
   }
 
   @Test
@@ -633,7 +633,7 @@ public class TopologyManagerTest {
 
     replayAll();
 
-    topologyManager.provisionCluster(request);
+    topologyManager.provisionCluster(request, "{}");
   }
 
   private SettingEntity createQuickLinksSettingEntity(String content, long 
timeStamp) {
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/utils/SecretReferenceTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/utils/SecretReferenceTest.java
new file mode 100644
index 0000000..004bf55
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/utils/SecretReferenceTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.utils;
+
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.topology.Configuration;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Multimap;
+
+public class SecretReferenceTest {
+
+  public static final String RANGER_HDFS_POLICYMGR_SSL = 
"ranger-hdfs-policymgr-ssl.xml";
+  public static final String RANGER_HDFS_POLICYMGR_SSL_XML = 
RANGER_HDFS_POLICYMGR_SSL + ".xml";
+
+  public static final Set<String> PASSWORD_PROPERTIES = ImmutableSet.of(
+    "xasecure.policymgr.clientssl.keystore.password",
+    "xasecure.policymgr.clientssl.truststore.password"
+  );
+
+  public static final List<String> PROPERTY_NAMES = ImmutableList.of(
+    "xasecure.policymgr.clientssl.keystore.password",
+    "xasecure.policymgr.clientssl.truststore.password",
+    "xasecure.policymgr.clientssl.keystore.credential.file",
+    "xasecure.policymgr.clientssl.truststore.credential.file"
+  );
+
+  public static final Multimap<String, String> EXPECTED_PASSWORD_PROPERTY_MAP =
+    ImmutableSetMultimap.<String, String>builder().
+    putAll(RANGER_HDFS_POLICYMGR_SSL, PASSWORD_PROPERTIES).
+    build();
+
+  private StackInfo hdpCore;
+
+  @Before
+  public void setup() throws Exception {
+    hdpCore = new StackInfo();
+    ServiceInfo hdfs = new ServiceInfo();
+    hdfs.setProperties(createProperties());
+    hdpCore.setServices(ImmutableList.of(hdfs));
+  }
+
+
+  private List<PropertyInfo> createProperties() {
+    return PROPERTY_NAMES.stream().map(propertyName -> {
+      PropertyInfo propertyInfo = new PropertyInfo();
+      propertyInfo.setFilename(RANGER_HDFS_POLICYMGR_SSL_XML);
+      propertyInfo.setName(propertyName);
+      if (PASSWORD_PROPERTIES.contains(propertyName)) {
+        
propertyInfo.setPropertyTypes(ImmutableSet.of(PropertyInfo.PropertyType.PASSWORD));
+      }
+      return propertyInfo;
+    }).collect(toList());
+  }
+
+  @Test
+  public void testGetAllPasswordPropertiesInternal() {
+    assertEquals(EXPECTED_PASSWORD_PROPERTY_MAP,
+      
SecretReference.getAllPasswordPropertiesInternal(ImmutableList.of(hdpCore)));
+  }
+
+
+  @Test
+  public void testReplacePasswordsInConfiguration() {
+    // given
+    Map<String, String> propertyMap =
+      PROPERTY_NAMES.stream().collect(toMap(Function.identity(), propertyName 
-> "someValue"));
+
+    Map<String, Map<String, String>> properties =
+      ImmutableMap.of(RANGER_HDFS_POLICYMGR_SSL, propertyMap);
+
+    Map<String, Map<String, Map<String, String>>> attributes =
+      ImmutableMap.of(RANGER_HDFS_POLICYMGR_SSL, ImmutableMap.of("final", 
propertyMap));
+
+    Configuration config = new Configuration(properties, attributes);
+
+    // when
+    Configuration replacedConfig =
+      SecretReference.replacePasswordsInConfigurations(config, 
EXPECTED_PASSWORD_PROPERTY_MAP);
+
+    // then
+    Map<String, String> replacedPropertyMap =
+      PROPERTY_NAMES.stream().collect(toMap(
+        Function.identity(),
+        propertyName -> PASSWORD_PROPERTIES.contains(propertyName) ?
+          secret(propertyName) :
+          "someValue")
+      );
+
+    assertEquals(replacedPropertyMap, 
replacedConfig.getProperties().entrySet().iterator().next().getValue());
+    assertEquals(replacedPropertyMap,
+      
replacedConfig.getAttributes().entrySet().iterator().next().getValue().entrySet().iterator().next().getValue());
+  }
+
+  private static String secret(String propertyName) {
+    return "SECRET:" + RANGER_HDFS_POLICYMGR_SSL + ":" + propertyName;
+  }
+
+}
\ No newline at end of file

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

Reply via email to