Repository: ambari
Updated Branches:
  refs/heads/branch-2.5 491854239 -> 99bb98840


AMBARI-19597. Blueprint installation should accept quick link profile. (Balazs 
Bence Sari via stoader


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

Branch: refs/heads/branch-2.5
Commit: 99bb988406184aa12c4d3d0b12e39b83025b7385
Parents: 4918542
Author: Balazs Bence Sari <bs...@hortonworks.com>
Authored: Thu Jan 19 22:11:14 2017 +0100
Committer: Toader, Sebastian <stoa...@hortonworks.com>
Committed: Thu Jan 19 22:54:39 2017 +0100

----------------------------------------------------------------------
 .../AmbariManagementControllerImpl.java         |  10 +-
 .../internal/ClusterResourceProvider.java       |  17 +-
 .../internal/ProvisionClusterRequest.java       |  36 +++
 .../quicklinksprofile/AcceptAllFilter.java      |   5 +
 .../state/quicklinksprofile/Component.java      |   3 +
 .../server/state/quicklinksprofile/Filter.java  |   7 +-
 .../quicklinksprofile/QuickLinksProfile.java    |   9 +
 .../QuickLinksProfileBuilder.java               | 128 ++++++++++
 .../QuickLinksProfileEvaluationException.java   |   4 +
 .../QuickLinksProfileParser.java                |   6 +-
 .../server/state/quicklinksprofile/Service.java |   3 +
 .../ambari/server/topology/TopologyManager.java |  38 +++
 .../internal/ProvisionClusterRequestTest.java   | 112 +++++++--
 .../QuickLinkArtifactResourceProviderTest.java  |   1 -
 .../QuickLinksProfileBuilderTest.java           | 234 +++++++++++++++++++
 .../server/topology/TopologyManagerTest.java    | 138 ++++++++---
 16 files changed, 681 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index fc241f5..6feccb4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -184,6 +184,7 @@ import org.apache.ambari.server.state.State;
 import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
 import 
org.apache.ambari.server.state.quicklinksprofile.QuickLinkVisibilityController;
 import 
org.apache.ambari.server.state.quicklinksprofile.QuickLinkVisibilityControllerFactory;
+import org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile;
 import org.apache.ambari.server.state.repository.VersionDefinitionXml;
 import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
 import org.apache.ambari.server.state.stack.RepositoryXml;
@@ -238,12 +239,6 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
   public static final String SKIP_INSTALL_FOR_COMPONENTS = 
"skipInstallForComponents";
   public static final String DONT_SKIP_INSTALL_FOR_COMPONENTS = 
"dontSkipInstallForComponents";
 
-  /**
-   * The name of the ambari setting that stores the quicklinks profile.
-   * See {@link 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile}
-   */
-  public static final String SETTING_QUICKLINKS_PROFILE = "QuickLinksProfile";
-
   private final Clusters clusters;
 
   private final ActionManager actionManager;
@@ -504,7 +499,6 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
         c.createClusterVersion(stackId, versionEntity.getVersion(), 
getAuthName(), RepositoryVersionState.INIT);
       }
     }
-
   }
 
   @Override
@@ -5517,7 +5511,7 @@ public class AmbariManagementControllerImpl implements 
AmbariManagementControlle
 
   @Override
   public QuickLinkVisibilityController getQuicklinkVisibilityController() {
-    SettingEntity entity = settingDAO.findByName(SETTING_QUICKLINKS_PROFILE);
+    SettingEntity entity = 
settingDAO.findByName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE);
     String quickLinkProfileJson = null != entity ? entity.getContent() : null;
     return QuickLinkVisibilityControllerFactory.get(quickLinkProfileJson);
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
----------------------------------------------------------------------
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 cb30f2d..613ab3f 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
@@ -82,6 +82,7 @@ public class ClusterResourceProvider extends 
AbstractControllerResourceProvider
   public static final String BLUEPRINT_PROPERTY_ID = 
PropertyHelper.getPropertyId(null, "blueprint");
   public static final String SECURITY_PROPERTY_ID = 
PropertyHelper.getPropertyId(null, "security");
   public static final String CREDENTIALS_PROPERTY_ID = 
PropertyHelper.getPropertyId(null, "credentials");
+  public static final String QUICKLINKS_PROFILE_PROPERTY_ID = 
PropertyHelper.getPropertyId(null, "quicklinks_profile");
   public static final String SESSION_ATTRIBUTES_PROPERTY_ID = 
"session_attributes";
 
   public static final String CLUSTER_REPO_VERSION = 
"Clusters/repository_version";
@@ -152,6 +153,7 @@ public class ClusterResourceProvider extends 
AbstractControllerResourceProvider
     propertyIds.add(SECURITY_PROPERTY_ID);
     propertyIds.add(CREDENTIALS_PROPERTY_ID);
     propertyIds.add(CLUSTER_REPO_VERSION);
+    propertyIds.add(QUICKLINKS_PROFILE_PROPERTY_ID);
   }
 
 
@@ -356,13 +358,13 @@ public class ClusterResourceProvider extends 
AbstractControllerResourceProvider
 
     for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
       final ClusterRequest clusterRequest = getRequest(propertyMap);
-        modifyResources(new Command<Void>() {
-          @Override
-          public Void invoke() throws AmbariException {
-            getManagementController().deleteCluster(clusterRequest);
-            return null;
-          }
-        });
+      modifyResources(new Command<Void>() {
+        @Override
+        public Void invoke() throws AmbariException {
+          getManagementController().deleteCluster(clusterRequest);
+          return null;
+        }
+      });
     }
     notifyDelete(Resource.Type.Cluster, predicate);
     return getRequestStatus(null);
@@ -436,6 +438,7 @@ public class ClusterResourceProvider extends 
AbstractControllerResourceProvider
     return cr;
   }
 
+
   /**
    * Get the map of session attributes from the given property map.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/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 a35da86..2c8d09a 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
@@ -27,6 +27,8 @@ import java.util.Set;
 import org.apache.ambari.server.api.predicate.InvalidQueryException;
 import org.apache.ambari.server.security.encryption.CredentialStoreType;
 import org.apache.ambari.server.stack.NoSuchStackException;
+import 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileBuilder;
+import 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileEvaluationException;
 import org.apache.ambari.server.topology.ConfigRecommendationStrategy;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.ConfigurationFactory;
@@ -105,6 +107,17 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
    */
   public static final String REPO_VERSION_PROPERTY = "repository_version";
 
+  /**
+   * The global quick link filters property
+   */
+  public static final String QUICKLINKS_PROFILE_FILTERS_PROPERTY = 
"quicklinks_profile/filters";
+
+  /**
+   * The service and component level quick link filters property
+   */
+  public static final String QUICKLINKS_PROFILE_SERVICES_PROPERTY = 
"quicklinks_profile/services";
+
+
 
   /**
    * configuration factory
@@ -130,6 +143,8 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
 
   private String repoVersion;
 
+  private final String quickLinksProfileJson;
+
   private final static Logger LOG = 
LoggerFactory.getLogger(ProvisionClusterRequest.class);
 
   /**
@@ -173,8 +188,23 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
     this.configRecommendationStrategy = 
parseConfigRecommendationStrategy(properties);
 
     setProvisionAction(parseProvisionAction(properties));
+
+    try {
+      this.quickLinksProfileJson = processQuickLinksProfile(properties);
+    }
+    catch (QuickLinksProfileEvaluationException ex) {
+      throw new InvalidTopologyTemplateException("Invalid quick links 
profile", ex);
+    }
+  }
+
+  private String processQuickLinksProfile(Map<String, Object> properties) 
throws QuickLinksProfileEvaluationException {
+    Object globalFilters = properties.get(QUICKLINKS_PROFILE_FILTERS_PROPERTY);
+    Object serviceFilters = 
properties.get(QUICKLINKS_PROFILE_SERVICES_PROPERTY);
+    return (null != globalFilters || null != serviceFilters) ?
+      new QuickLinksProfileBuilder().buildQuickLinksProfile(globalFilters, 
serviceFilters) : null;
   }
 
+
   private Map<String, Credential> parseCredentials(Map<String, Object> 
properties) throws
     InvalidTopologyTemplateException {
     HashMap<String, Credential> credentialHashMap = new HashMap<>();
@@ -439,4 +469,10 @@ public class ProvisionClusterRequest extends 
BaseClusterRequest {
     return repoVersion;
   }
 
+  /**
+   * @return the quick links profile in Json string format
+   */
+  public String getQuickLinksProfileJson() {
+    return quickLinksProfileJson;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
index d784a22..069ae3f 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/AcceptAllFilter.java
@@ -43,4 +43,9 @@ public class AcceptAllFilter extends Filter {
   public int hashCode() {
     return java.util.Objects.hash(isVisible());
   }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + " (visible=" + isVisible() + ")";
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
index a1267df..729e5d4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Component.java
@@ -24,6 +24,8 @@ import org.codehaus.jackson.annotate.JsonIgnoreProperties;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Class to represent component-level filter definitions
  */
@@ -37,6 +39,7 @@ public class Component {
   private List<Filter> filters;
 
   static Component create(String name, List<Filter> filters) {
+    Preconditions.checkNotNull(name, "Component name must not be null");
     Component component = new Component();
     component.setName(name);
     component.setFilters(filters);

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
index c551830..a62fee0 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Filter.java
@@ -23,6 +23,8 @@ import org.codehaus.jackson.annotate.JsonIgnoreProperties;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
+import com.google.common.base.Preconditions;
+
 @JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
 /**
@@ -33,8 +35,9 @@ import org.codehaus.jackson.map.annotate.JsonSerialize;
  * </ul>
  */
 public abstract class Filter {
+  static final String VISIBLE = "visible";
 
-  @JsonProperty("visible")
+  @JsonProperty(VISIBLE)
   private boolean visible;
 
   /**
@@ -63,6 +66,7 @@ public abstract class Filter {
   }
 
   static LinkNameFilter linkNameFilter(String linkName, boolean visible) {
+    Preconditions.checkNotNull(linkName, "Link name must not be null");
     LinkNameFilter linkNameFilter = new LinkNameFilter();
     linkNameFilter.setLinkName(linkName);
     linkNameFilter.setVisible(visible);
@@ -70,6 +74,7 @@ public abstract class Filter {
   }
 
   static LinkAttributeFilter linkAttributeFilter(String linkAttribute, boolean 
visible) {
+    Preconditions.checkNotNull(linkAttribute, "Attribute name must not be 
null");
     LinkAttributeFilter linkAttributeFilter = new LinkAttributeFilter();
     linkAttributeFilter.setLinkAttribute(linkAttribute);
     linkAttributeFilter.setVisible(visible);

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
index c9ac6b4..7d480e9 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfile.java
@@ -41,6 +41,15 @@ import org.codehaus.jackson.map.annotate.JsonSerialize;
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class QuickLinksProfile {
 
+  /**
+   * The name of the Ambari setting that stores the quick links profile
+   */
+  public static final String SETTING_NAME_QUICKLINKS_PROFILE = 
"QuickLinksProfile";
+  /**
+   * The type of the Ambari setting that stores the quick links profile
+   */
+  public static final String SETTING_TYPE_AMBARI_SERVER = "ambari-server";
+
   @JsonProperty("filters")
   private List<Filter> filters;
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java
new file mode 100644
index 0000000..fca1155
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilder.java
@@ -0,0 +1,128 @@
+/*
+ * 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.state.quicklinksprofile;
+
+import static org.apache.ambari.server.state.quicklinksprofile.Filter.VISIBLE;
+import static 
org.apache.ambari.server.state.quicklinksprofile.LinkAttributeFilter.LINK_ATTRIBUTE;
+import static 
org.apache.ambari.server.state.quicklinksprofile.LinkNameFilter.LINK_NAME;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Class to create a {@link QuickLinksProfile} based on data received in a 
request
+ */
+public class QuickLinksProfileBuilder {
+
+  public static final String NAME = "name";
+  public static final String COMPONENTS = "components";
+  public static final String FILTERS = "filters";
+
+  /**
+   *
+   * @param globalFiltersRaw The data in the request belonging to the 
"quicklinks_profile/filters" key.
+   * @param serviceFiltersRaw The data in the request belonging to 
"quicklinks_profile/services"
+   * @return The quicklinks profile as Json encoded string.
+   * @throws QuickLinksProfileEvaluationException when the received data 
defines an invalid profile. This can be of various
+   *    reasons: the received data is has invalid structure, critical data is 
missing or there are contradicting filter
+   *    rules in the profile
+   */
+  public String buildQuickLinksProfile(@Nullable Object globalFiltersRaw, 
@Nullable Object serviceFiltersRaw) throws QuickLinksProfileEvaluationException 
{
+    try {
+      List<Filter> globalFilters = buildQuickLinkFilters(globalFiltersRaw);
+      List<Service> services = buildServices(serviceFiltersRaw);
+      QuickLinksProfile profile = QuickLinksProfile.create(globalFilters, 
services);
+      // sanity check: this should throw QuickLinksProfileEvaluationException 
if the profile is invalid
+      new DefaultQuickLinkVisibilityController(profile);
+      return new QuickLinksProfileParser().encode(profile);
+    }
+    catch (QuickLinksProfileEvaluationException ex) {
+      throw ex;
+    }
+    catch (Exception ex) {
+      throw new QuickLinksProfileEvaluationException("Error interpreting 
quicklinks profile data", ex);
+    }
+  }
+
+  List<Service> buildServices(@Nullable Object servicesRaw) {
+    if (null == servicesRaw) {
+      return ImmutableList.of();
+    }
+    List<Service> services = new ArrayList<>();
+    for (Map<String, Object> serviceAsMap: (Collection<Map<String, 
Object>>)servicesRaw) {
+      String serviceName = (String)serviceAsMap.get(NAME);
+      Object componentsRaw = serviceAsMap.get(COMPONENTS);
+      Object filtersRaw = serviceAsMap.get(FILTERS);
+      services.add(Service.create(serviceName,
+          buildQuickLinkFilters(filtersRaw),
+          buildComponents(componentsRaw)));
+    }
+    return services;
+  }
+
+  List<Component> buildComponents(@Nullable Object componentsRaw) {
+    if (null == componentsRaw) {
+      return ImmutableList.of();
+    }
+    List<Component> components = new ArrayList<>();
+    for (Map<String, Object> componentAsMap: (Collection<Map<String, 
Object>>)componentsRaw) {
+      String componentName = (String)componentAsMap.get(NAME);
+      Object filtersRaw = componentAsMap.get(FILTERS);
+      components.add(Component.create(componentName,
+          buildQuickLinkFilters(filtersRaw)));
+    }
+    return components;  }
+
+
+  List<Filter> buildQuickLinkFilters(@Nullable Object filtersRaw) throws 
ClassCastException, IllegalArgumentException {
+    if (null == filtersRaw) {
+      return ImmutableList.of();
+    }
+    List<Filter> filters  = new ArrayList<>();
+    for (Map<String, String> filterAsMap: (Collection<Map<String, 
String>>)filtersRaw) {
+      String linkName = filterAsMap.get(LINK_NAME);
+      String attributeName = filterAsMap.get(LINK_ATTRIBUTE);
+      boolean visible = Boolean.parseBoolean(filterAsMap.get(VISIBLE));
+
+      if (null != linkName && null != attributeName) {
+        throw new IllegalArgumentException(
+            String.format("%s link_name: %s, link_attribute: %s",
+                QuickLinksFilterDeserializer.PARSE_ERROR_MESSAGE, linkName, 
attributeName));
+      }
+      else if (null != linkName) {
+        filters.add(Filter.linkNameFilter(linkName, visible));
+      }
+      else if (null != attributeName) {
+        filters.add(Filter.linkAttributeFilter(attributeName, visible));
+      }
+      else {
+        filters.add(Filter.acceptAllFilter(visible));
+      }
+    }
+    return filters;
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluationException.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluationException.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluationException.java
index 26819e1..1ef8a59 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluationException.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileEvaluationException.java
@@ -28,4 +28,8 @@ public class QuickLinksProfileEvaluationException extends 
Exception {
     super(message);
   }
 
+  public QuickLinksProfileEvaluationException(String message, Throwable cause) 
{
+    super(message, cause);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
index a3ae677..150b7d4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileParser.java
@@ -55,13 +55,17 @@ public class QuickLinksProfileParser {
   public QuickLinksProfile parse(URL url) throws IOException {
     return parse(Resources.toByteArray(url));
   }
+
+  public String encode(QuickLinksProfile profile) throws IOException {
+    return mapper.writeValueAsString(profile);
+  }
 }
 
 /**
  * Custom deserializer is needed to handle filter polymorphism.
  */
 class QuickLinksFilterDeserializer extends StdDeserializer<Filter> {
-  private static final String PARSE_ERROR_MESSAGE =
+  static final String PARSE_ERROR_MESSAGE =
       "A filter is not allowed to declare both link_name and link_attribute at 
the same time.";
 
   QuickLinksFilterDeserializer() {

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
index 7724852..07cce29 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/quicklinksprofile/Service.java
@@ -24,6 +24,8 @@ import org.codehaus.jackson.annotate.JsonIgnoreProperties;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Class to represent component-level filter definitions
  */
@@ -40,6 +42,7 @@ public class Service {
   private List<Filter> filters;
 
   static Service create(String name, List<Filter> filters, List<Component> 
components) {
+    Preconditions.checkNotNull(name, "Service name must not be null");
     Service service = new Service();
     service.setName(name);
     service.setFilters(filters);

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/main/java/org/apache/ambari/server/topology/TopologyManager.java
----------------------------------------------------------------------
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 7db07a0..3103c34 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
@@ -63,10 +63,14 @@ import org.apache.ambari.server.events.HostsRemovedEvent;
 import org.apache.ambari.server.events.RequestFinishedEvent;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
 import org.apache.ambari.server.orm.dao.HostRoleCommandStatusSummaryDTO;
+import org.apache.ambari.server.orm.dao.SettingDAO;
+import org.apache.ambari.server.orm.entities.SettingEntity;
 import org.apache.ambari.server.orm.entities.StageEntity;
+import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.SecurityType;
 import org.apache.ambari.server.state.host.HostImpl;
+import org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile;
 import org.apache.ambari.server.utils.RetryHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -118,6 +122,9 @@ public class TopologyManager {
   @Inject
   private AmbariEventPublisher ambariEventPublisher;
 
+  @Inject
+  private SettingDAO settingDAO;
+
   /**
    * A boolean not cached thread-local (volatile) to prevent double-checked
    * locking on the synchronized keyword.
@@ -232,6 +239,11 @@ public class TopologyManager {
 
   public RequestStatusResponse provisionCluster(final ProvisionClusterRequest 
request) throws InvalidTopologyException, AmbariException {
     ensureInitialized();
+
+    if (null != request.getQuickLinksProfileJson()) {
+      saveOrUpdateQuickLinksProfile(request.getQuickLinksProfileJson());
+    }
+
     ClusterTopology topology = new ClusterTopologyImpl(ambariContext, request);
     final String clusterName = request.getClusterName();
     final String repoVersion = request.getRepositoryVersion();
@@ -302,6 +314,32 @@ public class TopologyManager {
     return getRequestStatus(logicalRequest.getRequestId());
   }
 
+  /**
+   * Saves the quick links profile to the DB as an Ambari setting. Creates a 
new setting entity or updates the existing
+   * one.
+   * @param quickLinksProfileJson the quicklinks profile in Json format
+   */
+  void saveOrUpdateQuickLinksProfile(String quickLinksProfileJson) {
+    SettingEntity settingEntity = 
settingDAO.findByName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE);
+    // create new
+    if (null == settingEntity) {
+      settingEntity = new SettingEntity();
+      settingEntity.setName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE);
+      
settingEntity.setSettingType(QuickLinksProfile.SETTING_TYPE_AMBARI_SERVER);
+      settingEntity.setContent(quickLinksProfileJson);
+      settingEntity.setUpdatedBy(AuthorizationHelper.getAuthenticatedName());
+      settingEntity.setUpdateTimestamp(System.currentTimeMillis());
+      settingDAO.create(settingEntity);
+    }
+    // update existing
+    else {
+      settingEntity.setContent(quickLinksProfileJson);
+      settingEntity.setUpdatedBy(AuthorizationHelper.getAuthenticatedName());
+      settingEntity.setUpdateTimestamp(System.currentTimeMillis());
+      settingDAO.merge(settingEntity);
+    }
+  }
+
   private void submitCredential(String clusterName, Credential credential) {
 
     ResourceProvider provider =

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/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 0610d10..b114edc 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
@@ -18,7 +18,32 @@
 
 package org.apache.ambari.server.controller.internal;
 
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.powermock.api.easymock.PowerMock.createStrictMock;
+import static org.powermock.api.easymock.PowerMock.replay;
+import static org.powermock.api.easymock.PowerMock.reset;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.ambari.server.controller.spi.ResourceProvider;
+import 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileBuilderTest;
 import org.apache.ambari.server.topology.Blueprint;
 import org.apache.ambari.server.topology.BlueprintFactory;
 import org.apache.ambari.server.topology.Configuration;
@@ -31,29 +56,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.powermock.api.easymock.PowerMock.createStrictMock;
-import static org.powermock.api.easymock.PowerMock.replay;
-import static org.powermock.api.easymock.PowerMock.reset;
+import com.google.common.collect.Sets;
 
 /**
  * Unit tests for ProvisionClusterRequest.
@@ -428,6 +431,69 @@ public class ProvisionClusterRequestTest {
     new ProvisionClusterRequest(properties, null);
   }
 
+  @Test
+  public void testQuickLinksProfile_NoDataInRequest() throws Exception {
+    Map<String, Object> properties = 
createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+    ProvisionClusterRequest request = new ProvisionClusterRequest(properties, 
null);
+    assertNull("No quick links profile is expected", 
request.getQuickLinksProfileJson());
+  }
+
+  @Test
+  public void testQuickLinksProfile_OnlyGlobalFilterDataInRequest() throws 
Exception {
+    Map<String, Object> properties = 
createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+
+    properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_FILTERS_PROPERTY,
+        Sets.newHashSet(QuickLinksProfileBuilderTest.filter(null, null, 
true)));
+
+    ProvisionClusterRequest request = new ProvisionClusterRequest(properties, 
null);
+    assertEquals("Quick links profile doesn't match expected",
+        "{\"filters\":[{\"visible\":true}],\"services\":[]}",
+        request.getQuickLinksProfileJson());
+  }
+
+  @Test
+  public void testQuickLinksProfile_OnlyServiceFilterDataInRequest() throws 
Exception {
+    Map<String, Object> properties = 
createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+
+    Map<String, String> filter = QuickLinksProfileBuilderTest.filter(null, 
null, true);
+    Map<String, Object> hdfs = QuickLinksProfileBuilderTest.service("HDFS", 
null, Sets.newHashSet(filter));
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+    
properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_SERVICES_PROPERTY, 
services);
+
+    ProvisionClusterRequest request = new ProvisionClusterRequest(properties, 
null);
+    assertEquals("Quick links profile doesn't match expected",
+        
"{\"filters\":[],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}",
+        request.getQuickLinksProfileJson());
+  }
+
+  @Test
+  public void testQuickLinksProfile_BothGlobalAndServiceLevelFilters() throws 
Exception {
+    Map<String, Object> properties = 
createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+
+    properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_FILTERS_PROPERTY,
+        Sets.newHashSet(QuickLinksProfileBuilderTest.filter(null, null, 
true)));
+
+    Map<String, String> filter = QuickLinksProfileBuilderTest.filter(null, 
null, true);
+    Map<String, Object> hdfs = QuickLinksProfileBuilderTest.service("HDFS", 
null, Sets.newHashSet(filter));
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+    
properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_SERVICES_PROPERTY, 
services);
+
+    ProvisionClusterRequest request = new ProvisionClusterRequest(properties, 
null);
+    System.out.println(request.getQuickLinksProfileJson());
+    assertEquals("Quick links profile doesn't match expected",
+        
"{\"filters\":[{\"visible\":true}],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}",
+        request.getQuickLinksProfileJson());
+  }
+
+  @Test(expected = InvalidTopologyTemplateException.class)
+  public void testQuickLinksProfile_InvalidRequestData() throws Exception {
+    Map<String, Object> properties = 
createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME);
+
+    
properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_SERVICES_PROPERTY, 
"Hello World!");
+
+    ProvisionClusterRequest request = new ProvisionClusterRequest(properties, 
null);
+  }
+
   public static Map<String, Object> createBlueprintRequestProperties(String 
clusterName, String blueprintName) {
     Map<String, Object> properties = new LinkedHashMap<String, Object>();
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java
index 8c723c9..689fbe4 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/QuickLinkArtifactResourceProviderTest.java
@@ -192,7 +192,6 @@ public class QuickLinkArtifactResourceProviderTest {
       expect(service.getName()).andReturn("YARN").anyTimes();
       binder.bind(AmbariManagementController.class).toInstance(amc);
       replay(amc, metaInfo, stack, service);
-
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java
new file mode 100644
index 0000000..1cc3fd3
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/state/quicklinksprofile/QuickLinksProfileBuilderTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.state.quicklinksprofile;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileBuilder.COMPONENTS;
+import static 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileBuilder.FILTERS;
+import static 
org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfileBuilder.NAME;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.junit.Test;
+
+import com.google.common.collect.Sets;
+
+public class QuickLinksProfileBuilderTest {
+
+
+  @Test
+  public void testBuildProfileOnlyGlobalFilters() throws Exception {
+    Set<Map<String, String>> filters = newHashSet(
+      filter("namenode_ui", null, true),
+      filter(null, "sso", true),
+      filter(null, null, false)
+    );
+
+    String profileJson = new 
QuickLinksProfileBuilder().buildQuickLinksProfile(filters, null);
+
+    //verify
+    QuickLinksProfile profile = new 
QuickLinksProfileParser().parse(profileJson.getBytes());
+    assertFilterExists(profile, null, null, 
Filter.linkNameFilter("namenode_ui", true));
+    assertFilterExists(profile, null, null, Filter.linkAttributeFilter("sso", 
true));
+    assertFilterExists(profile, null, null, Filter.acceptAllFilter(false));
+  }
+
+  @Test
+  public void testBuildProfileOnlyServiceFilters() throws Exception {
+    Map<String, Object> nameNode = component("NAMENODE",
+        newHashSet(filter("namenode_ui", null, false)));
+
+    Map<String, Object> hdfs = service("HDFS",
+        newHashSet(nameNode),
+        newHashSet(filter(null, "sso", true)));
+
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+
+    String profileJson = new 
QuickLinksProfileBuilder().buildQuickLinksProfile(null, services);
+
+    //verify
+    QuickLinksProfile profile = new 
QuickLinksProfileParser().parse(profileJson.getBytes());
+    assertFilterExists(profile, "HDFS", "NAMENODE", 
Filter.linkNameFilter("namenode_ui", false));
+    assertFilterExists(profile, "HDFS", null, 
Filter.linkAttributeFilter("sso", true));
+  }
+
+  @Test
+  public void testBuildProfileBothGlobalAndServiceFilters() throws Exception {
+    Set<Map<String, String>> globalFilters = newHashSet( filter(null, null, 
false) );
+
+    Map<String, Object> nameNode = component("NAMENODE",
+        newHashSet(filter("namenode_ui", null, false)));
+
+    Map<String, Object> hdfs = service("HDFS",
+        newHashSet(nameNode),
+        newHashSet(filter(null, "sso", true)));
+
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+
+    String profileJson = new 
QuickLinksProfileBuilder().buildQuickLinksProfile(globalFilters, services);
+
+    // verify
+    QuickLinksProfile profile = new 
QuickLinksProfileParser().parse(profileJson.getBytes());
+    assertFilterExists(profile, null, null, Filter.acceptAllFilter(false));
+    assertFilterExists(profile, "HDFS", "NAMENODE", 
Filter.linkNameFilter("namenode_ui", false));
+    assertFilterExists(profile, "HDFS", null, 
Filter.linkAttributeFilter("sso", true));
+  }
+
+  @Test(expected = QuickLinksProfileEvaluationException.class)
+  public void testBuildProfileBadInputStructure() throws Exception {
+    new QuickLinksProfileBuilder().buildQuickLinksProfile("Hello", "World");
+  }
+
+  @Test(expected = QuickLinksProfileEvaluationException.class)
+  public void testBuildProfileMissingDataServiceName() throws Exception {
+    Map<String, Object> nameNode = component("NAMENODE",
+        newHashSet(filter("namenode_ui", null, false)));
+
+    Map<String, Object> hdfs = service(null, // intentionally omitting service 
name
+        newHashSet(nameNode),
+        newHashSet(filter(null, "sso", true)));
+
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+
+    new QuickLinksProfileBuilder().buildQuickLinksProfile(null, services);
+  }
+
+  @Test(expected = QuickLinksProfileEvaluationException.class)
+  public void testBuildProfileMissingDataComponentName() throws Exception {
+    Map<String, Object> nameNode = component(null, // intentionally omitting 
component name
+        newHashSet(filter("namenode_ui", null, false)));
+
+    Map<String, Object> hdfs = service("HDFS",
+        newHashSet(nameNode),
+        newHashSet(filter(null, "sso", true)));
+
+    Set<Map<String, Object>> services = Sets.newHashSet(hdfs);
+
+    new QuickLinksProfileBuilder().buildQuickLinksProfile(null, services);
+  }
+
+  @Test(expected = QuickLinksProfileEvaluationException.class)
+  public void testBuildProfileInvalidProfileDefiniton() throws Exception {
+    // Contradicting rules in the profile
+    Set<Map<String, String>> filters = newHashSet(
+        filter(null, "sso", true),
+        filter(null, "sso", false)
+    );
+
+    String profileJson = new 
QuickLinksProfileBuilder().buildQuickLinksProfile(filters, null);
+  }
+
+  /**
+   * Verifies that the filter specified by the arguments exists in the 
received {@link QuickLinksProfile}
+   * @param profile the {@link QuickLinksProfile} to examine
+   * @param serviceName the service name where the filter is defined. {@code 
null} means the searched filter is a global
+   *                    filter
+   * @param componentName the component name where the filter is defined. Only 
makes sense when serviceName is defined
+   *                    too. {@code null} means the searched filter is a 
service level filter, not component level one.
+   *                    filter
+   * @param filter the {@link Filter} to look for.
+   */
+  private static void assertFilterExists(@Nonnull QuickLinksProfile profile,
+                                         @Nullable String serviceName,
+                                         @Nullable String componentName,
+                                         @Nonnull Filter filter) {
+    // looking for a global filter
+    if (null == serviceName) {
+      if (!profile.getFilters().contains(filter)) {
+        throw new AssertionError("Expected global filter not found: " + 
filter);
+      }
+    }
+    // looking for a filter defined on service or component level
+    else {
+      Service service = findService(profile.getServices(), serviceName);
+      // looking for a filter defined on service level
+      if (null == componentName) {
+        if (!service.getFilters().contains(filter)) {
+          throw new AssertionError(String.format("Expected filter not found. 
Service: %s, Filter: %s",
+              serviceName, filter));
+        }
+      }
+      // looking for a filter defined on component level
+      else {
+        Component component = findComponent(service.getComponents(), 
componentName);
+        if (!component.getFilters().contains(filter)) {
+          throw new AssertionError(String.format("Expected filter not found. 
Service: %s, Component: %s, Filter: %s",
+              serviceName, componentName, filter));
+        }
+      }
+    }
+  }
+
+  private static Component findComponent(List<Component> components, String 
componentName) {
+    for (Component component: components) {
+      if (component.getName().equals(componentName)) {
+        return component;
+      }
+    }
+    throw new AssertionError("Expected component not found: " + componentName);
+  }
+
+  private static Service findService(List<Service> services, String 
serviceName) {
+    for (Service service: services) {
+      if (service.getName().equals(serviceName)) {
+        return service;
+      }
+    }
+    throw new AssertionError("Expected service not found: " + serviceName);
+  }
+
+  public static Map<String, String> filter(@Nullable String linkName, 
@Nullable String attributeName, boolean visible) {
+    Map<String, String> map = new HashMap<>(3);
+    if (null != linkName) {
+      map.put(LinkNameFilter.LINK_NAME, linkName);
+    }
+    if (null != attributeName) {
+      map.put(LinkAttributeFilter.LINK_ATTRIBUTE, attributeName);
+    }
+    map.put(Filter.VISIBLE, Boolean.toString(visible));
+    return map;
+  }
+
+  public static Map<String, Object> component(String componentName, 
Set<Map<String, String>> filters) {
+    Map<String, Object> map = new HashMap<>();
+    map.put(NAME, componentName);
+    map.put(FILTERS, filters);
+    return map;
+  }
+
+  public static Map<String, Object> service(String serviceName, 
Set<Map<String, Object>> components,
+                                             Set<Map<String, String>> filters) 
{
+    Map<String, Object> map = new HashMap<>();
+    map.put(NAME, serviceName);
+    if (null != components) {
+      map.put(COMPONENTS, components);
+    }
+    if (null != filters) {
+      map.put(FILTERS, filters);
+    }
+    return map;
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/99bb9884/ambari-server/src/test/java/org/apache/ambari/server/topology/TopologyManagerTest.java
----------------------------------------------------------------------
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 b45c585..cf92911 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
@@ -18,7 +18,33 @@
 
 package org.apache.ambari.server.topology;
 
-import junit.framework.Assert;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.isNull;
+import static org.easymock.EasyMock.newCapture;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.powermock.api.easymock.PowerMock.mockStatic;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.ClusterRequest;
@@ -33,9 +59,13 @@ import 
org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.events.RequestFinishedEvent;
+import org.apache.ambari.server.orm.dao.SettingDAO;
+import org.apache.ambari.server.orm.entities.SettingEntity;
+import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
 import org.apache.ambari.server.stack.NoSuchStackException;
 import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.quicklinksprofile.QuickLinksProfile;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.easymock.EasyMockRule;
@@ -44,38 +74,20 @@ import org.easymock.Mock;
 import org.easymock.MockType;
 import org.easymock.TestSubject;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.isNull;
-import static org.easymock.EasyMock.newCapture;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
 
 /**
  * TopologyManager unit tests
  */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest( { TopologyManager.class })
 public class TopologyManagerTest {
 
   private static final String CLUSTER_NAME = "test-cluster";
@@ -83,6 +95,10 @@ public class TopologyManagerTest {
   private static final String BLUEPRINT_NAME = "test-bp";
   private static final String STACK_NAME = "test-stack";
   private static final String STACK_VERSION = "test-stack-version";
+  private static final String SAMPLE_QUICKLINKS_PROFILE_1 = 
"{\"filters\":[{\"visible\":true}],\"services\":[]}";
+  private static final String SAMPLE_QUICKLINKS_PROFILE_2 =
+      
"{\"filters\":[],\"services\":[{\"name\":\"HDFS\",\"components\":[],\"filters\":[{\"visible\":true}]}]}";
+
 
   @Rule
   public EasyMockRule mocks = new EasyMockRule(this);
@@ -132,6 +148,8 @@ public class TopologyManagerTest {
   private ClusterController clusterController;
   @Mock(type = MockType.STRICT)
   private ResourceProvider resourceProvider;
+  @Mock(type = MockType.STRICT)
+  private SettingDAO settingDAO;
 
   @Mock(type = MockType.STRICT)
   private Future mockFuture;
@@ -344,13 +362,15 @@ public class TopologyManagerTest {
 
   @After
   public void tearDown() {
+    PowerMock.verify(System.class);
     verify(blueprint, stack, request, group1, group2, ambariContext, 
logicalRequestFactory,
         logicalRequest, configurationRequest, configurationRequest2, 
configurationRequest3,
-        requestStatusResponse, executor, persistedState, mockFuture);
+        requestStatusResponse, executor, persistedState, mockFuture, 
settingDAO);
 
+    PowerMock.reset(System.class);
     reset(blueprint, stack, request, group1, group2, ambariContext, 
logicalRequestFactory,
         logicalRequest, configurationRequest, configurationRequest2, 
configurationRequest3,
-        requestStatusResponse, executor, persistedState, mockFuture);
+        requestStatusResponse, executor, persistedState, mockFuture, 
settingDAO);
   }
 
   @Test
@@ -480,7 +500,7 @@ public class TopologyManagerTest {
     replay(blueprint, stack, request, group1, group2, ambariContext, 
logicalRequestFactory,
             configurationRequest, configurationRequest2, 
configurationRequest3, executor,
             persistedState, securityConfigurationFactory, 
credentialStoreService, clusterController, resourceProvider,
-            mockFuture, requestStatusResponse, logicalRequest);
+            mockFuture, requestStatusResponse, logicalRequest, settingDAO);
   }
 
   @Test(expected = InvalidTopologyException.class)
@@ -503,4 +523,64 @@ public class TopologyManagerTest {
     topologyManager.scaleHosts(new ScaleClusterRequest(propertySet));
     Assert.fail("InvalidTopologyException should have been thrown");
   }
+
+  @Test
+  public void testProvisionCluster_QuickLinkProfileIsSavedTheFirstTime() 
throws Exception {
+    
expect(persistedState.getAllRequests()).andReturn(Collections.<ClusterTopology,
+        List<LogicalRequest>>emptyMap()).anyTimes();
+
+    // request has a quicklinks profile
+    
expect(request.getQuickLinksProfileJson()).andReturn(SAMPLE_QUICKLINKS_PROFILE_1).anyTimes();
+
+    // this means no quicklinks profile exists before calling 
provisionCluster()
+    
expect(settingDAO.findByName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE)).andReturn(null);
+
+    // expect that settingsDao saves the quick links profile with the right 
content
+    final long timeStamp = System.currentTimeMillis();
+    mockStatic(System.class);
+    expect(System.currentTimeMillis()).andReturn(timeStamp);
+    PowerMock.replay(System.class);
+    final SettingEntity quickLinksProfile = 
createQuickLinksSettingEntity(SAMPLE_QUICKLINKS_PROFILE_1, timeStamp);
+    settingDAO.create(eq(quickLinksProfile));
+
+    replayAll();
+
+    topologyManager.provisionCluster(request);
+  }
+
+  @Test
+  public void testProvisionCluster_ExistingQuickLinkProfileIsOverwritten() 
throws Exception {
+    
expect(persistedState.getAllRequests()).andReturn(Collections.<ClusterTopology,
+        List<LogicalRequest>>emptyMap()).anyTimes();
+
+    // request has a quicklinks profile
+    
expect(request.getQuickLinksProfileJson()).andReturn(SAMPLE_QUICKLINKS_PROFILE_2).anyTimes();
+
+    // existing quick links profile returned by dao
+    final long timeStamp1 = System.currentTimeMillis();
+    SettingEntity originalProfile = 
createQuickLinksSettingEntity(SAMPLE_QUICKLINKS_PROFILE_1, timeStamp1);
+    
expect(settingDAO.findByName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE)).andReturn(originalProfile);
+
+    // expect that settingsDao overwrites the quick links profile with the new 
content
+    mockStatic(System.class);
+    final long timeStamp2 = timeStamp1 + 100;
+    expect(System.currentTimeMillis()).andReturn(timeStamp2);
+    PowerMock.replay(System.class);
+    final SettingEntity newProfile = 
createQuickLinksSettingEntity(SAMPLE_QUICKLINKS_PROFILE_2, timeStamp2);
+    expect(settingDAO.merge(newProfile)).andReturn(newProfile);
+
+    replayAll();
+
+    topologyManager.provisionCluster(request);
+  }
+
+  private SettingEntity createQuickLinksSettingEntity(String content, long 
timeStamp) {
+    SettingEntity settingEntity = new SettingEntity();
+    settingEntity.setName(QuickLinksProfile.SETTING_NAME_QUICKLINKS_PROFILE);
+    settingEntity.setSettingType(QuickLinksProfile.SETTING_TYPE_AMBARI_SERVER);
+    settingEntity.setContent(content);
+    settingEntity.setUpdatedBy(AuthorizationHelper.getAuthenticatedName());
+    settingEntity.setUpdateTimestamp(timeStamp);
+    return settingEntity;
+  }
 }

Reply via email to