Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 85D3B200C01 for ; Thu, 19 Jan 2017 21:34:55 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 84486160B54; Thu, 19 Jan 2017 20:34:55 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id AC654160B3A for ; Thu, 19 Jan 2017 21:34:53 +0100 (CET) Received: (qmail 17114 invoked by uid 500); 19 Jan 2017 20:34:52 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 17105 invoked by uid 99); 19 Jan 2017 20:34:52 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 19 Jan 2017 20:34:52 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id ADF2BDFB86; Thu, 19 Jan 2017 20:34:52 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: stoader@apache.org To: commits@ambari.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: ambari git commit: AMBARI-19597. Blueprint installation should accept quick link profile. (Balazs Bence Sari via stoader) Date: Thu, 19 Jan 2017 20:34:52 +0000 (UTC) archived-at: Thu, 19 Jan 2017 20:34:55 -0000 Repository: ambari Updated Branches: refs/heads/trunk 504b45528 -> de8bf6019 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/de8bf601 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/de8bf601 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/de8bf601 Branch: refs/heads/trunk Commit: de8bf601918efbeb66ef68d25364aaed4b672172 Parents: 504b455 Author: Balazs Bence Sari Authored: Thu Jan 19 20:56:02 2017 +0100 Committer: Toader, Sebastian Committed: Thu Jan 19 20:56:02 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 | 66 ++++++ .../QuickLinkArtifactResourceProviderTest.java | 1 - .../QuickLinksProfileBuilderTest.java | 234 +++++++++++++++++++ .../server/topology/TopologyManagerTest.java | 88 ++++++- 16 files changed, 632 insertions(+), 23 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/de8bf601/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 5e8c803..9afe598 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 @@ -185,6 +185,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; @@ -239,12 +240,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; @@ -511,7 +506,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle c.createClusterVersion(stackId, versionEntity.getVersion(), getAuthName(), RepositoryVersionState.INIT); } } - } @Override @@ -5527,7 +5521,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/de8bf601/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 propertyMap : getPropertyMaps(predicate)) { final ClusterRequest clusterRequest = getRequest(propertyMap); - modifyResources(new Command() { - @Override - public Void invoke() throws AmbariException { - getManagementController().deleteCluster(clusterRequest); - return null; - } - }); + modifyResources(new Command() { + @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/de8bf601/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 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 parseCredentials(Map properties) throws InvalidTopologyTemplateException { HashMap 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/de8bf601/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/de8bf601/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 filters; static Component create(String name, List 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/de8bf601/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; * */ 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/de8bf601/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 filters; http://git-wip-us.apache.org/repos/asf/ambari/blob/de8bf601/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 globalFilters = buildQuickLinkFilters(globalFiltersRaw); + List 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 buildServices(@Nullable Object servicesRaw) { + if (null == servicesRaw) { + return ImmutableList.of(); + } + List services = new ArrayList<>(); + for (Map serviceAsMap: (Collection>)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 buildComponents(@Nullable Object componentsRaw) { + if (null == componentsRaw) { + return ImmutableList.of(); + } + List components = new ArrayList<>(); + for (Map componentAsMap: (Collection>)componentsRaw) { + String componentName = (String)componentAsMap.get(NAME); + Object filtersRaw = componentAsMap.get(FILTERS); + components.add(Component.create(componentName, + buildQuickLinkFilters(filtersRaw))); + } + return components; } + + + List buildQuickLinkFilters(@Nullable Object filtersRaw) throws ClassCastException, IllegalArgumentException { + if (null == filtersRaw) { + return ImmutableList.of(); + } + List filters = new ArrayList<>(); + for (Map filterAsMap: (Collection>)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/de8bf601/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/de8bf601/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 { - 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/de8bf601/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 filters; static Service create(String name, List filters, List 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/de8bf601/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/de8bf601/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 2cf478a..c64da62 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 @@ -43,6 +43,7 @@ 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; @@ -55,6 +56,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +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 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 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 properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); + + Map filter = QuickLinksProfileBuilderTest.filter(null, null, true); + Map hdfs = QuickLinksProfileBuilderTest.service("HDFS", null, Sets.newHashSet(filter)); + Set> 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 properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); + + properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_FILTERS_PROPERTY, + Sets.newHashSet(QuickLinksProfileBuilderTest.filter(null, null, true))); + + Map filter = QuickLinksProfileBuilderTest.filter(null, null, true); + Map hdfs = QuickLinksProfileBuilderTest.service("HDFS", null, Sets.newHashSet(filter)); + Set> 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 properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); + + properties.put(ProvisionClusterRequest.QUICKLINKS_PROFILE_SERVICES_PROPERTY, "Hello World!"); + + ProvisionClusterRequest request = new ProvisionClusterRequest(properties, null); + } + public static Map createBlueprintRequestProperties(String clusterName, String blueprintName) { Map properties = new LinkedHashMap(); http://git-wip-us.apache.org/repos/asf/ambari/blob/de8bf601/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/de8bf601/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> 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 nameNode = component("NAMENODE", + newHashSet(filter("namenode_ui", null, false))); + + Map hdfs = service("HDFS", + newHashSet(nameNode), + newHashSet(filter(null, "sso", true))); + + Set> 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> globalFilters = newHashSet( filter(null, null, false) ); + + Map nameNode = component("NAMENODE", + newHashSet(filter("namenode_ui", null, false))); + + Map hdfs = service("HDFS", + newHashSet(nameNode), + newHashSet(filter(null, "sso", true))); + + Set> 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 nameNode = component("NAMENODE", + newHashSet(filter("namenode_ui", null, false))); + + Map hdfs = service(null, // intentionally omitting service name + newHashSet(nameNode), + newHashSet(filter(null, "sso", true))); + + Set> services = Sets.newHashSet(hdfs); + + new QuickLinksProfileBuilder().buildQuickLinksProfile(null, services); + } + + @Test(expected = QuickLinksProfileEvaluationException.class) + public void testBuildProfileMissingDataComponentName() throws Exception { + Map nameNode = component(null, // intentionally omitting component name + newHashSet(filter("namenode_ui", null, false))); + + Map hdfs = service("HDFS", + newHashSet(nameNode), + newHashSet(filter(null, "sso", true))); + + Set> services = Sets.newHashSet(hdfs); + + new QuickLinksProfileBuilder().buildQuickLinksProfile(null, services); + } + + @Test(expected = QuickLinksProfileEvaluationException.class) + public void testBuildProfileInvalidProfileDefiniton() throws Exception { + // Contradicting rules in the profile + Set> 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 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 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 filter(@Nullable String linkName, @Nullable String attributeName, boolean visible) { + Map 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 component(String componentName, Set> filters) { + Map map = new HashMap<>(); + map.put(NAME, componentName); + map.put(FILTERS, filters); + return map; + } + + public static Map service(String serviceName, Set> components, + Set> filters) { + Map 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/de8bf601/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 7e6e5a3..07060b7 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 @@ -29,6 +29,7 @@ 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; @@ -57,9 +58,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; @@ -68,15 +73,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 junit.framework.Assert; +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"; @@ -84,6 +94,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); @@ -133,6 +147,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; @@ -345,13 +361,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 @@ -481,7 +499,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) @@ -504,4 +522,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.>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.>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; + } }