Repository: ambari Updated Branches: refs/heads/trunk 597951c1f -> 43dd0cddf
AMBARI-11093. Implement host predicate property validation and fix issue where host name is specified explicitly in scaling request Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/43dd0cdd Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/43dd0cdd Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/43dd0cdd Branch: refs/heads/trunk Commit: 43dd0cddf656bd54cda90e182786e304655b4765 Parents: 597951c Author: John Speidel <[email protected]> Authored: Tue May 12 22:17:01 2015 -0400 Committer: John Speidel <[email protected]> Committed: Wed May 13 12:37:50 2015 -0400 ---------------------------------------------------------------------- .../ambari/server/controller/AmbariServer.java | 6 +- .../controller/internal/BaseClusterRequest.java | 186 +++++++++ .../internal/HostResourceProvider.java | 11 +- .../internal/ProvisionClusterRequest.java | 98 ++--- .../internal/ScaleClusterRequest.java | 155 +++++--- .../internal/ProvisionClusterRequestTest.java | 263 ++++++++++++- .../internal/ScaleClusterRequestTest.java | 394 +++++++++++++++++++ 7 files changed, 987 insertions(+), 126 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java index 77f6d2c..4a30c0d 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java @@ -48,14 +48,13 @@ import org.apache.ambari.server.configuration.ComponentSSLConfiguration; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider; import org.apache.ambari.server.controller.internal.AmbariPrivilegeResourceProvider; +import org.apache.ambari.server.controller.internal.BaseClusterRequest; import org.apache.ambari.server.controller.internal.BlueprintResourceProvider; import org.apache.ambari.server.controller.internal.ClusterPrivilegeResourceProvider; import org.apache.ambari.server.controller.internal.ClusterResourceProvider; import org.apache.ambari.server.controller.internal.HostResourceProvider; import org.apache.ambari.server.controller.internal.PermissionResourceProvider; import org.apache.ambari.server.controller.internal.PrivilegeResourceProvider; -import org.apache.ambari.server.controller.internal.ProvisionClusterRequest; -import org.apache.ambari.server.controller.internal.ScaleClusterRequest; import org.apache.ambari.server.controller.internal.StackAdvisorResourceProvider; import org.apache.ambari.server.controller.internal.StackDefinedPropertyProvider; import org.apache.ambari.server.controller.internal.StackDependencyResourceProvider; @@ -616,8 +615,7 @@ public class AmbariServer { injector.getInstance(TopologyRequestFactoryImpl.class)); HostResourceProvider.setTopologyManager(injector.getInstance(TopologyManager.class)); BlueprintFactory.init(injector.getInstance(BlueprintDAO.class)); - ProvisionClusterRequest.init(injector.getInstance(BlueprintFactory.class)); - ScaleClusterRequest.init(injector.getInstance(BlueprintFactory.class)); + BaseClusterRequest.init(injector.getInstance(BlueprintFactory.class)); AmbariContext.init(injector.getInstance(HostRoleCommandFactory.class)); PermissionResourceProvider.init(injector.getInstance(PermissionDAO.class)); http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseClusterRequest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseClusterRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseClusterRequest.java new file mode 100644 index 0000000..7f6a634 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseClusterRequest.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.controller.internal; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.ambari.server.api.predicate.InvalidQueryException; +import org.apache.ambari.server.api.predicate.QueryLexer; +import org.apache.ambari.server.api.predicate.Token; +import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.controller.spi.ResourceProvider; +import org.apache.ambari.server.controller.utilities.ClusterControllerHelper; +import org.apache.ambari.server.topology.Blueprint; +import org.apache.ambari.server.topology.BlueprintFactory; +import org.apache.ambari.server.topology.Configuration; +import org.apache.ambari.server.topology.HostGroupInfo; +import org.apache.ambari.server.topology.InvalidTopologyTemplateException; +import org.apache.ambari.server.topology.TopologyRequest; + +/** + * Provides common cluster request functionality. + */ +public abstract class BaseClusterRequest implements TopologyRequest { + /** + * host group info map + */ + protected final Map<String, HostGroupInfo> hostGroupInfoMap = new HashMap<String, HostGroupInfo>(); + + /** + * cluster name + */ + protected String clusterName; + + /** + * blueprint + */ + //todo: change interface to only return blueprint name + protected Blueprint blueprint; + + /** + * configuration + */ + protected Configuration configuration; + + /** + * blueprint factory + */ + protected static BlueprintFactory blueprintFactory; + + /** + * Lexer used to obtain property names from a predicate string + */ + private static final QueryLexer queryLexer = new QueryLexer(); + + /** + * host resource provider used to validate predicate properties + */ + private static ResourceProvider hostResourceProvider; + + + /** + * inject blueprint factory + * @param factory blueprint factory + */ + public static void init(BlueprintFactory factory) { + blueprintFactory = factory; + } + + @Override + public String getClusterName() { + return clusterName; + } + + @Override + public Blueprint getBlueprint() { + return blueprint; + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + public Map<String, HostGroupInfo> getHostGroupInfo() { + return hostGroupInfoMap; + } + + /** + * Validate that all properties specified in the predicate are valid for the Host resource. + * + * @param predicate predicate to validate + * + * @throws InvalidTopologyTemplateException if any of the properties specified in the predicate are invalid + * for the Host resource type + */ + protected void validateHostPredicateProperties(String predicate) throws InvalidTopologyTemplateException { + Token[] tokens; + try { + tokens = queryLexer.tokens(predicate); + } catch (InvalidQueryException e) { + throw new InvalidTopologyTemplateException( + String.format("The specified host query is invalid: %s", e.getMessage())); + } + + Set<String> propertyIds = new HashSet<String>(); + for (Token token : tokens) { + if (token.getType() == Token.TYPE.PROPERTY_OPERAND) { + propertyIds.add(token.getValue()); + } + } + + Set<String> invalidProperties = ensureHostProvider().checkPropertyIds(propertyIds); + if (! invalidProperties.isEmpty()) { + throw new InvalidTopologyTemplateException(String.format( + "Invalid Host Predicate. The following properties are not valid for a host predicate: %s", + invalidProperties)); + } + } + + /** + * Set the request cluster name. + * + * @param clusterName cluster name + */ + protected void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + /** + * Set the request blueprint. + * + * @param blueprint blueprint + */ + protected void setBlueprint(Blueprint blueprint) { + this.blueprint = blueprint; + } + + /** + * Set the request configuration. + * + * @param configuration configuration + */ + protected void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Get the blueprint factory. + */ + protected BlueprintFactory getBlueprintFactory() { + return blueprintFactory; + } + + /** + * Get the host resource provider instance. + * + * @return host resourece provider instance + */ + private static synchronized ResourceProvider ensureHostProvider() { + if (hostResourceProvider == null) { + hostResourceProvider = ClusterControllerHelper.getClusterController(). + ensureResourceProvider(Resource.Type.Host); + } + return hostResourceProvider; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java index 47a4ce0..4c14426 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java @@ -132,6 +132,10 @@ public class HostResourceProvider extends AbstractControllerResourceProvider { PropertyHelper.getPropertyId(null, "host_group"); public static final String HOST_NAME_NO_CATEGORY_PROPERTY_ID = PropertyHelper.getPropertyId(null, "host_name"); + public static final String HOST_COUNT_PROPERTY_ID = + PropertyHelper.getPropertyId(null, "host_count"); + public static final String HOST_PREDICATE_PROPERTY_ID = + PropertyHelper.getPropertyId(null, "host_predicate"); private static Set<String> pkPropertyIds = new HashSet<String>(Arrays.asList(new String[]{ @@ -173,7 +177,6 @@ public class HostResourceProvider extends AbstractControllerResourceProvider { RequestStatusResponse createResponse = null; if (isHostGroupRequest(request)) { -// createResponse = addHostsUsingHostgroup(request); createResponse = submitHostRequests(request); } else { createResources(new Command<Void>() { @@ -334,8 +337,8 @@ public class HostResourceProvider extends AbstractControllerResourceProvider { baseUnsupported.remove(HOSTGROUP_PROPERTY_ID); baseUnsupported.remove(HOST_NAME_NO_CATEGORY_PROPERTY_ID); //todo: constants - baseUnsupported.remove("host_count"); - baseUnsupported.remove("host_predicate"); + baseUnsupported.remove(HOST_COUNT_PROPERTY_ID); + baseUnsupported.remove(HOST_PREDICATE_PROPERTY_ID); return checkConfigPropertyIds(baseUnsupported, "Hosts"); } @@ -830,7 +833,7 @@ public class HostResourceProvider extends AbstractControllerResourceProvider { private RequestStatusResponse submitHostRequests(Request request) throws SystemException { TopologyRequest requestRequest; try { - requestRequest = new ScaleClusterRequest(request); + requestRequest = new ScaleClusterRequest(request.getProperties()); } catch (InvalidTopologyTemplateException e) { throw new IllegalArgumentException("Invalid Add Hosts Template: " + e, e); } http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/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 a1a0ac6..1a4520e 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 @@ -22,41 +22,43 @@ package org.apache.ambari.server.controller.internal; import org.apache.ambari.server.api.predicate.InvalidQueryException; import org.apache.ambari.server.stack.NoSuchStackException; -import org.apache.ambari.server.topology.Blueprint; -import org.apache.ambari.server.topology.BlueprintFactory; import org.apache.ambari.server.topology.Configuration; import org.apache.ambari.server.topology.ConfigurationFactory; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.apache.ambari.server.topology.NoSuchBlueprintException; import org.apache.ambari.server.topology.RequiredPasswordValidator; -import org.apache.ambari.server.topology.TopologyRequest; import org.apache.ambari.server.topology.TopologyValidator; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; /** * Request for provisioning a cluster. */ -public class ProvisionClusterRequest implements TopologyRequest { +public class ProvisionClusterRequest extends BaseClusterRequest { - private static BlueprintFactory blueprintFactory; + /** + * configuration factory + */ private static ConfigurationFactory configurationFactory = new ConfigurationFactory(); - private String clusterName; + /** + * default password + */ private String defaultPassword; - private Blueprint blueprint; - private Configuration configuration; - private Map<String, HostGroupInfo> hostGroupInfoMap = new HashMap<String, HostGroupInfo>(); @SuppressWarnings("unchecked") + /** + * Constructor. + * + * @param properties request properties + */ public ProvisionClusterRequest(Map<String, Object> properties) throws InvalidTopologyTemplateException { - this.clusterName = String.valueOf(properties.get( - ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID)); + setClusterName(String.valueOf(properties.get( + ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID))); //todo: constant if (properties.containsKey("default_password")) { @@ -70,21 +72,13 @@ public class ProvisionClusterRequest implements TopologyRequest { } catch (NoSuchBlueprintException e) { throw new InvalidTopologyTemplateException("The specified blueprint doesn't exist: " + e, e); } - this.configuration = configurationFactory.getConfiguration( - (Collection<Map<String, String>>) properties.get("configurations")); - this.configuration.setParentConfiguration(blueprint.getConfiguration()); - //parseConfiguration(properties); - parseHostGroupInfo(properties); - } - //todo: - public static void init(BlueprintFactory factory) { - blueprintFactory = factory; - } + Configuration configuration = configurationFactory.getConfiguration( + (Collection<Map<String, String>>) properties.get("configurations")); + configuration.setParentConfiguration(blueprint.getConfiguration()); + setConfiguration(configuration); - @Override - public String getClusterName() { - return clusterName; + parseHostGroupInfo(properties); } @Override @@ -93,22 +87,6 @@ public class ProvisionClusterRequest implements TopologyRequest { } @Override - public Blueprint getBlueprint() { - return blueprint; - } - - @Override - public Configuration getConfiguration() { - return configuration; - } - - @Override - //todo: return copy? - public Map<String, HostGroupInfo> getHostGroupInfo() { - return hostGroupInfoMap; - } - - @Override public List<TopologyValidator> getTopologyValidators() { return Collections.<TopologyValidator>singletonList(new RequiredPasswordValidator(defaultPassword)); } @@ -118,15 +96,30 @@ public class ProvisionClusterRequest implements TopologyRequest { return String.format("Provision Cluster '%s'", clusterName); } + /** + * Parse blueprint. + * + * @param properties request properties + * + * @throws NoSuchStackException if specified stack doesn't exist + * @throws NoSuchBlueprintException if specified blueprint doesn't exist + */ private void parseBlueprint(Map<String, Object> properties) throws NoSuchStackException, NoSuchBlueprintException { String blueprintName = String.valueOf(properties.get(ClusterResourceProvider.BLUEPRINT_PROPERTY_ID)); - blueprint = blueprintFactory.getBlueprint(blueprintName); + // set blueprint field + setBlueprint(getBlueprintFactory().getBlueprint(blueprintName)); if (blueprint == null) { throw new NoSuchBlueprintException(blueprintName); } } + /** + * Parse host group information. + * + * @param properties request properties + * @throws InvalidTopologyTemplateException if any validation checks on properties fail + */ @SuppressWarnings("unchecked") private void parseHostGroupInfo(Map<String, Object> properties) throws InvalidTopologyTemplateException { Collection<Map<String, Object>> hostGroups = @@ -149,18 +142,27 @@ public class ProvisionClusterRequest implements TopologyRequest { throw new InvalidTopologyTemplateException("Host group '" + name + "' must contain a 'hosts' element"); } - // blueprint was parsed already HostGroupInfo hostGroupInfo = new HostGroupInfo(name); - hostGroupInfoMap.put(name, hostGroupInfo); + getHostGroupInfo().put(name, hostGroupInfo); for (Object oHost : hosts) { Map<String, String> hostProperties = (Map<String, String>) oHost; + + String hostName = hostProperties.get("fqdn"); + boolean containsHostCount = hostProperties.containsKey("host_count"); + boolean containsHostPredicate = hostProperties.containsKey("host_predicate"); + + if (hostName != null && (containsHostCount || containsHostPredicate)) { + throw new InvalidTopologyTemplateException( + "Can't specify host_count or host_predicate if host_name is specified in hostgroup: " + name); + } + //add host information to host group - String fqdn = hostProperties.get("fqdn"); - if (fqdn == null || fqdn.isEmpty()) { + if (hostName == null || hostName.isEmpty()) { //todo: validate the host_name and host_predicate are not both specified for same group String predicate = hostProperties.get("host_predicate"); if (predicate != null && ! predicate.isEmpty()) { + validateHostPredicateProperties(predicate); try { hostGroupInfo.setPredicate(predicate); } catch (InvalidQueryException e) { @@ -169,7 +171,7 @@ public class ProvisionClusterRequest implements TopologyRequest { } } - if (hostProperties.containsKey("host_count")) { + if (containsHostCount) { hostGroupInfo.setRequestedCount(Integer.valueOf(hostProperties.get("host_count"))); } else { throw new InvalidTopologyTemplateException( @@ -177,7 +179,7 @@ public class ProvisionClusterRequest implements TopologyRequest { " or a host_count must be specified"); } } else { - hostGroupInfo.addHost(fqdn); + hostGroupInfo.addHost(hostName); } } // don't set the parent configuration http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ScaleClusterRequest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ScaleClusterRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ScaleClusterRequest.java index 1530a3e..b9a4173 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ScaleClusterRequest.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ScaleClusterRequest.java @@ -20,51 +20,42 @@ package org.apache.ambari.server.controller.internal; import org.apache.ambari.server.api.predicate.InvalidQueryException; -import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.stack.NoSuchStackException; import org.apache.ambari.server.topology.Blueprint; -import org.apache.ambari.server.topology.BlueprintFactory; import org.apache.ambari.server.topology.Configuration; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; -import org.apache.ambari.server.topology.TopologyRequest; import org.apache.ambari.server.topology.TopologyValidator; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * A request for a scaling an existing cluster. */ -public class ScaleClusterRequest implements TopologyRequest { - - private static BlueprintFactory blueprintFactory; - - private String clusterName; - - private Blueprint blueprint; - - private Map<String, HostGroupInfo> hostGroupInfoMap = new HashMap<String, HostGroupInfo>(); - - public static void init(BlueprintFactory factory) { - blueprintFactory = factory; - } - - public ScaleClusterRequest(Request request) throws InvalidTopologyTemplateException { - for (Map<String, Object> properties : request.getProperties()) { +public class ScaleClusterRequest extends BaseClusterRequest { + + /** + * Constructor. + * + * @param propertySet set of request properties + * + * @throws InvalidTopologyTemplateException if any validation of properties fails + */ + public ScaleClusterRequest(Set<Map<String, Object>> propertySet) throws InvalidTopologyTemplateException { + for (Map<String, Object> properties : propertySet) { // can only operate on a single cluster per logical request - if (clusterName == null) { - clusterName = String.valueOf(properties.get(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID)); + if (getClusterName() == null) { + setClusterName(String.valueOf(properties.get(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID))); } - parseHostGroup(properties); - } - } + // currently don't allow cluster scoped configuration in scaling operation + setConfiguration(new Configuration(Collections.<String, Map<String, String>>emptyMap(), + Collections.<String, Map<String, Map<String, String>>>emptyMap())); - @Override - public String getClusterName() { - return clusterName; + parseHostGroups(properties); + } } @Override @@ -73,23 +64,6 @@ public class ScaleClusterRequest implements TopologyRequest { } @Override - public Blueprint getBlueprint() { - return blueprint; - } - - @Override - public Configuration getConfiguration() { - // currently don't allow cluster scoped configuration in scaling operation - return new Configuration(Collections.<String, Map<String, String>>emptyMap(), - Collections.<String, Map<String, Map<String, String>>>emptyMap()); - } - - @Override - public Map<String, HostGroupInfo> getHostGroupInfo() { - return hostGroupInfoMap; - } - - @Override public List<TopologyValidator> getTopologyValidators() { return Collections.emptyList(); } @@ -99,25 +73,54 @@ public class ScaleClusterRequest implements TopologyRequest { return String.format("Scale Cluster '%s' (+%s hosts)", clusterName, getTotalRequestedHostCount()); } - private void parseHostGroup(Map<String, Object> properties) throws InvalidTopologyTemplateException { + /** + * Parse and set host group information. + * + * @param properties request properties + * @throws InvalidTopologyTemplateException if any property validation fails + */ + //todo: need to use fully qualified host group name. For now, disregard name collisions across BP's + private void parseHostGroups(Map<String, Object> properties) throws InvalidTopologyTemplateException { String blueprintName = String.valueOf(properties.get(HostResourceProvider.BLUEPRINT_PROPERTY_ID)); - if (blueprint == null) { + if (blueprintName == null || blueprintName.equals("null")) { + throw new InvalidTopologyTemplateException("Blueprint name must be specified for all host groups"); + } + + String hgName = String.valueOf(properties.get(HostResourceProvider.HOSTGROUP_PROPERTY_ID)); + if (hgName == null || hgName.equals("null")) { + throw new InvalidTopologyTemplateException("A name must be specified for all host groups"); + } + + Blueprint blueprint = getBlueprint(); + if (getBlueprint() == null) { blueprint = parseBlueprint(blueprintName); + setBlueprint(blueprint); } else if (! blueprintName.equals(blueprint.getName())) { throw new InvalidTopologyTemplateException( "Currently, a scaling request may only refer to a single blueprint"); } - String hgName = String.valueOf(properties.get(HostResourceProvider.HOSTGROUP_PROPERTY_ID)); - //todo: need to use fully qualified host group name. For now, disregard name collisions across BP's - HostGroupInfo hostGroupInfo = hostGroupInfoMap.get(hgName); + String hostName = getHostNameFromProperties(properties); + boolean containsHostCount = properties.containsKey(HostResourceProvider.HOST_COUNT_PROPERTY_ID); + boolean containsHostPredicate = properties.containsKey(HostResourceProvider.HOST_PREDICATE_PROPERTY_ID); + if (hostName != null && (containsHostCount || containsHostPredicate)) { + throw new InvalidTopologyTemplateException( + "Can't specify host_count or host_predicate if host_name is specified in hostgroup: " + hgName); + } + + if (hostName == null && ! containsHostCount) { + throw new InvalidTopologyTemplateException( + "Must specify either host_name or host_count for hostgroup: " + hgName); + } + + HostGroupInfo hostGroupInfo = getHostGroupInfo().get(hgName); if (hostGroupInfo == null) { if (blueprint.getHostGroup(hgName) == null) { throw new InvalidTopologyTemplateException("Invalid host group specified in request: " + hgName); } hostGroupInfo = new HostGroupInfo(hgName); - hostGroupInfoMap.put(hgName, hostGroupInfo); + getHostGroupInfo().put(hgName, hostGroupInfo); } // specifying configuration is scaling request isn't permitted @@ -125,11 +128,11 @@ public class ScaleClusterRequest implements TopologyRequest { Collections.<String, Map<String, Map<String, String>>>emptyMap())); // process host_name and host_count - if (properties.containsKey("host_count")) { - //todo: validate the host_name and host_predicate are not both specified for same group - //todo: validate that when predicate is specified that only a single host group entry is specified - String predicate = String.valueOf(properties.get("host_predicate")); - if (predicate != null && ! predicate.isEmpty()) { + if (containsHostCount) { + //todo: host_count and host_predicate up one level + if (containsHostPredicate) { + String predicate = String.valueOf(properties.get(HostResourceProvider.HOST_PREDICATE_PROPERTY_ID)); + validateHostPredicateProperties(predicate); try { hostGroupInfo.setPredicate(predicate); } catch (InvalidQueryException e) { @@ -139,21 +142,32 @@ public class ScaleClusterRequest implements TopologyRequest { } if (! hostGroupInfo.getHostNames().isEmpty()) { - throw new InvalidTopologyTemplateException("Can't specify both host_name and host_count for the same hostgroup: " + hgName); + throw new InvalidTopologyTemplateException( + "Can't specify both host_name and host_count for the same hostgroup: " + hgName); } - hostGroupInfo.setRequestedCount(Integer.valueOf(String.valueOf(properties.get("host_count")))); + hostGroupInfo.setRequestedCount(Integer.valueOf(String.valueOf( + properties.get(HostResourceProvider.HOST_COUNT_PROPERTY_ID)))); } else { if (hostGroupInfo.getRequestedHostCount() != hostGroupInfo.getHostNames().size()) { - throw new InvalidTopologyTemplateException("Can't specify both host_name and host_count for the same hostgroup: " + hgName); + // host_name specified in one host block and host_count in another for the same group + throw new InvalidTopologyTemplateException("Invalid host group specified in request: " + hgName); } - hostGroupInfo.addHost(getHostNameFromProperties(properties)); + hostGroupInfo.addHost(hostName); } } + /** + * Parse blueprint. + * + * @param blueprintName blueprint name + * @return blueprint instance + * + * @throws InvalidTopologyTemplateException if specified blueprint or stack doesn't exist + */ private Blueprint parseBlueprint(String blueprintName) throws InvalidTopologyTemplateException { Blueprint blueprint; try { - blueprint = blueprintFactory.getBlueprint(blueprintName); + blueprint = getBlueprintFactory().getBlueprint(blueprintName); } catch (NoSuchStackException e) { throw new InvalidTopologyTemplateException("Invalid stack specified in the blueprint: " + blueprintName); } @@ -164,14 +178,25 @@ public class ScaleClusterRequest implements TopologyRequest { return blueprint; } + /** + * Get the host name from the request properties. + * + * @param properties request properties + * @return host name + */ //todo: this was copied exactly from HostResourceProvider private String getHostNameFromProperties(Map<String, Object> properties) { - String hostname = String.valueOf(properties.get(HostResourceProvider.HOST_NAME_PROPERTY_ID)); - - return hostname != null ? hostname : - String.valueOf(properties.get(HostResourceProvider.HOST_NAME_NO_CATEGORY_PROPERTY_ID)); + String hostName = (String) properties.get(HostResourceProvider.HOST_NAME_PROPERTY_ID); + if (hostName == null) { + hostName = (String) properties.get(HostResourceProvider.HOST_NAME_NO_CATEGORY_PROPERTY_ID); + } + return hostName; } + /** + * Get the total number of requested hosts for the request. + * @return total requested host count + */ private int getTotalRequestedHostCount() { int count = 0; for (HostGroupInfo groupInfo : getHostGroupInfo().values()) { http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/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 acfd426..82dd705 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,6 +18,7 @@ package org.apache.ambari.server.controller.internal; +import org.apache.ambari.server.controller.spi.ResourceProvider; import org.apache.ambari.server.topology.Blueprint; import org.apache.ambari.server.topology.BlueprintFactory; import org.apache.ambari.server.topology.Configuration; @@ -30,6 +31,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; @@ -38,6 +40,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.verify; @@ -61,6 +64,7 @@ public class ProvisionClusterRequestTest { private static final BlueprintFactory blueprintFactory = createStrictMock(BlueprintFactory.class); private static final Blueprint blueprint = createNiceMock(Blueprint.class); + private static final ResourceProvider hostResourceProvider = createMock(ResourceProvider.class); private static final Configuration blueprintConfig = new Configuration( Collections.<String, Map<String, String>>emptyMap(), Collections.<String, Map<String, Map<String, String>>>emptyMap()); @@ -68,27 +72,146 @@ public class ProvisionClusterRequestTest { @Before public void setUp() throws Exception { ProvisionClusterRequest.init(blueprintFactory); + // set host resource provider field + Class clazz = BaseClusterRequest.class; + Field f = clazz.getDeclaredField("hostResourceProvider"); + f.setAccessible(true); + f.set(null, hostResourceProvider); expect(blueprintFactory.getBlueprint(BLUEPRINT_NAME)).andReturn(blueprint).once(); expect(blueprint.getConfiguration()).andReturn(blueprintConfig).anyTimes(); + expect(hostResourceProvider.checkPropertyIds(Collections.singleton("Hosts/host_name"))). + andReturn(Collections.<String>emptySet()).once(); - replay(blueprintFactory, blueprint); + replay(blueprintFactory, blueprint, hostResourceProvider); } @After public void tearDown() { - reset(blueprintFactory, blueprint); + verify(blueprintFactory, blueprint, hostResourceProvider); + reset(blueprintFactory, blueprint, hostResourceProvider); } @Test - public void test_basic() throws Exception { + public void testHostNameSpecified() throws Exception { + // reset host resource provider expectations to none since we are not specifying a host predicate + reset(hostResourceProvider); + replay(hostResourceProvider); + + Map<String, Object> properties = createBlueprintRequestPropertiesNameOnly(CLUSTER_NAME, BLUEPRINT_NAME); + TopologyRequest provisionClusterRequest = new ProvisionClusterRequest(properties); + + assertEquals(CLUSTER_NAME, provisionClusterRequest.getClusterName()); + assertEquals(TopologyRequest.Type.PROVISION, provisionClusterRequest.getType()); + assertEquals(String.format("Provision Cluster '%s'", CLUSTER_NAME) , provisionClusterRequest.getCommandDescription()); + assertSame(blueprint, provisionClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = provisionClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(1, provisionClusterRequest.getTopologyValidators().size()); + + // group1 + // host info + HostGroupInfo group1Info = hostGroupInfo.get("group1"); + assertEquals("group1", group1Info.getHostGroupName()); + assertEquals(1, group1Info.getHostNames().size()); + assertTrue(group1Info.getHostNames().contains("host1.myDomain.com")); + assertEquals(1, group1Info.getRequestedHostCount()); + assertNull(group1Info.getPredicate()); + // configuration + Configuration group1Configuration = group1Info.getConfiguration(); + assertNull(group1Configuration.getParentConfiguration()); + assertEquals(1, group1Configuration.getProperties().size()); + Map<String, String> group1TypeProperties = group1Configuration.getProperties().get("foo-type"); + assertEquals(2, group1TypeProperties.size()); + assertEquals("prop1Value", group1TypeProperties.get("hostGroup1Prop1")); + assertEquals("prop2Value", group1TypeProperties.get("hostGroup1Prop2")); + assertTrue(group1Configuration.getAttributes().isEmpty()); + + // cluster scoped configuration + Configuration clusterScopeConfiguration = provisionClusterRequest.getConfiguration(); + assertSame(blueprintConfig, clusterScopeConfiguration.getParentConfiguration()); + assertEquals(1, clusterScopeConfiguration.getProperties().size()); + Map<String, String> clusterScopedProperties = clusterScopeConfiguration.getProperties().get("someType"); + assertEquals(1, clusterScopedProperties.size()); + assertEquals("someValue", clusterScopedProperties.get("property1")); + // attributes + Map<String, Map<String, Map<String, String>>> clusterScopedAttributes = clusterScopeConfiguration.getAttributes(); + assertEquals(1, clusterScopedAttributes.size()); + Map<String, Map<String, String>> clusterScopedTypeAttributes = clusterScopedAttributes.get("someType"); + assertEquals(1, clusterScopedTypeAttributes.size()); + Map<String, String> clusterScopedTypePropertyAttributes = clusterScopedTypeAttributes.get("property1"); + assertEquals(1, clusterScopedTypePropertyAttributes.size()); + assertEquals("someAttributePropValue", clusterScopedTypePropertyAttributes.get("attribute1")); + } + + @Test + public void testHostCountSpecified() throws Exception { + // reset host resource provider expectations to none since we are not specifying a host predicate + reset(hostResourceProvider); + replay(hostResourceProvider); + + Map<String, Object> properties = createBlueprintRequestPropertiesCountOnly(CLUSTER_NAME, BLUEPRINT_NAME); + TopologyRequest provisionClusterRequest = new ProvisionClusterRequest(properties); + + assertEquals(CLUSTER_NAME, provisionClusterRequest.getClusterName()); + assertEquals(TopologyRequest.Type.PROVISION, provisionClusterRequest.getType()); + assertEquals(String.format("Provision Cluster '%s'", CLUSTER_NAME) , provisionClusterRequest.getCommandDescription()); + assertSame(blueprint, provisionClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = provisionClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(1, provisionClusterRequest.getTopologyValidators().size()); + + // group2 + HostGroupInfo group2Info = hostGroupInfo.get("group2"); + assertEquals("group2", group2Info.getHostGroupName()); + assertTrue(group2Info.getHostNames().isEmpty()); + assertEquals(5, group2Info.getRequestedHostCount()); + assertNull(group2Info.getPredicate()); + // configuration + Configuration group2Configuration = group2Info.getConfiguration(); + assertNull(group2Configuration.getParentConfiguration()); + assertEquals(1, group2Configuration.getProperties().size()); + Map<String, String> group2TypeProperties = group2Configuration.getProperties().get("foo-type"); + assertEquals(1, group2TypeProperties.size()); + assertEquals("prop1Value", group2TypeProperties.get("hostGroup2Prop1")); + //attributes + Map<String, Map<String, Map<String, String>>> group2Attributes = group2Configuration.getAttributes(); + assertEquals(1, group2Attributes.size()); + Map<String, Map<String, String>> group2Type1Attributes = group2Attributes.get("foo-type"); + assertEquals(1, group2Type1Attributes.size()); + Map<String, String> group2Type1Prop1Attributes = group2Type1Attributes.get("hostGroup2Prop10"); + assertEquals(1, group2Type1Prop1Attributes.size()); + assertEquals("attribute1Prop10-value", group2Type1Prop1Attributes.get("attribute1")); + + // cluster scoped configuration + Configuration clusterScopeConfiguration = provisionClusterRequest.getConfiguration(); + assertSame(blueprintConfig, clusterScopeConfiguration.getParentConfiguration()); + assertEquals(1, clusterScopeConfiguration.getProperties().size()); + Map<String, String> clusterScopedProperties = clusterScopeConfiguration.getProperties().get("someType"); + assertEquals(1, clusterScopedProperties.size()); + assertEquals("someValue", clusterScopedProperties.get("property1")); + // attributes + Map<String, Map<String, Map<String, String>>> clusterScopedAttributes = clusterScopeConfiguration.getAttributes(); + assertEquals(1, clusterScopedAttributes.size()); + Map<String, Map<String, String>> clusterScopedTypeAttributes = clusterScopedAttributes.get("someType"); + assertEquals(1, clusterScopedTypeAttributes.size()); + Map<String, String> clusterScopedTypePropertyAttributes = clusterScopedTypeAttributes.get("property1"); + assertEquals(1, clusterScopedTypePropertyAttributes.size()); + assertEquals("someAttributePropValue", clusterScopedTypePropertyAttributes.get("attribute1")); + } + + @Test + public void testMultipleGroups() throws Exception { Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); TopologyRequest provisionClusterRequest = new ProvisionClusterRequest(properties); assertEquals(CLUSTER_NAME, provisionClusterRequest.getClusterName()); + assertEquals(TopologyRequest.Type.PROVISION, provisionClusterRequest.getType()); + assertEquals(String.format("Provision Cluster '%s'", CLUSTER_NAME) , provisionClusterRequest.getCommandDescription()); assertSame(blueprint, provisionClusterRequest.getBlueprint()); Map<String, HostGroupInfo> hostGroupInfo = provisionClusterRequest.getHostGroupInfo(); assertEquals(2, hostGroupInfo.size()); + assertEquals(1, provisionClusterRequest.getTopologyValidators().size()); // group1 // host info @@ -145,8 +268,6 @@ public class ProvisionClusterRequestTest { Map<String, String> clusterScopedTypePropertyAttributes = clusterScopedTypeAttributes.get("property1"); assertEquals(1, clusterScopedTypePropertyAttributes.size()); assertEquals("someAttributePropValue", clusterScopedTypePropertyAttributes.get("attribute1")); - - verify(blueprintFactory, blueprint); } @Test(expected= InvalidTopologyTemplateException.class) @@ -154,6 +275,9 @@ public class ProvisionClusterRequestTest { Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); ((Collection)properties.get("host_groups")).clear(); + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); // should result in an exception new ProvisionClusterRequest(properties); } @@ -163,6 +287,9 @@ public class ProvisionClusterRequestTest { Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); ((Collection<Map<String, Object>>)properties.get("host_groups")).iterator().next().remove("name"); + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); // should result in an exception new ProvisionClusterRequest(properties); } @@ -172,6 +299,9 @@ public class ProvisionClusterRequestTest { Map<String, Object> properties = createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME); ((Collection<Map<String, Object>>)properties.get("host_groups")).iterator().next().remove("hosts"); + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); // should result in an exception new ProvisionClusterRequest(properties); } @@ -189,6 +319,9 @@ public class ProvisionClusterRequestTest { } } + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); // should result in an exception new ProvisionClusterRequest(properties); } @@ -221,6 +354,47 @@ public class ProvisionClusterRequestTest { assertEquals(pwdValidator, defaultPwdValidator); } + @Test(expected = InvalidTopologyTemplateException.class) + public void testInvalidPredicateProperty() throws Exception { + reset(hostResourceProvider); + // checkPropertyIds() returns invalid property names + expect(hostResourceProvider.checkPropertyIds(Collections.singleton("Hosts/host_name"))). + andReturn(Collections.singleton("Hosts/host_name")); + replay(hostResourceProvider); + + // should result in an exception due to invalid property in host predicate + new ProvisionClusterRequest(createBlueprintRequestProperties(CLUSTER_NAME, BLUEPRINT_NAME)); + } + + @Test(expected = InvalidTopologyTemplateException.class) + public void testHostNameAndCountSpecified() throws Exception { + // reset host resource provider expectations to none since we are not specifying a host predicate + reset(hostResourceProvider); + replay(hostResourceProvider); + + Map<String, Object> properties = createBlueprintRequestPropertiesNameOnly(CLUSTER_NAME, BLUEPRINT_NAME); + List hostGroups = (List) properties.get("host_groups"); + Map hostGroup = (Map) hostGroups.iterator().next(); + List hostInfo = (List) hostGroup.get("hosts"); + ((Map) hostInfo.iterator().next()).put("host_count", "5"); + // should result in an exception due to both host name and host count being specified + TopologyRequest provisionClusterRequest = new ProvisionClusterRequest(properties); + } + + @Test(expected = InvalidTopologyTemplateException.class) + public void testNeitherHostNameOrCountSpecified() throws Exception { + // reset host resource provider expectations to none since we are not specifying a host predicate + reset(hostResourceProvider); + replay(hostResourceProvider); + + Map<String, Object> properties = createBlueprintRequestPropertiesNameOnly(CLUSTER_NAME, BLUEPRINT_NAME); + List hostGroups = (List) properties.get("host_groups"); + Map hostGroup = (Map) hostGroups.iterator().next(); + List hostInfo = (List) hostGroup.get("hosts"); + ((Map) hostInfo.iterator().next()).remove("fqdn"); + // should result in an exception due to both host name and host count being specified + TopologyRequest provisionClusterRequest = new ProvisionClusterRequest(properties); + } public static Map<String, Object> createBlueprintRequestProperties(String clusterName, String blueprintName) { Map<String, Object> properties = new LinkedHashMap<String, Object>(); @@ -279,4 +453,83 @@ public class ProvisionClusterRequestTest { return properties; } + + public static Map<String, Object> createBlueprintRequestPropertiesNameOnly(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(ClusterResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + + Collection<Map<String, Object>> hostGroups = new ArrayList<Map<String, Object>>(); + properties.put("host_groups", hostGroups); + + // host group 1 + Map<String, Object> hostGroup1Properties = new HashMap<String, Object>(); + hostGroups.add(hostGroup1Properties); + hostGroup1Properties.put("name", "group1"); + Collection<Map<String, String>> hostGroup1Hosts = new ArrayList<Map<String, String>>(); + hostGroup1Properties.put("hosts", hostGroup1Hosts); + Map<String, String> hostGroup1HostProperties = new HashMap<String, String>(); + hostGroup1HostProperties.put("fqdn", "host1.myDomain.com"); + hostGroup1Hosts.add(hostGroup1HostProperties); + // host group 1 scoped configuration + // version 1 configuration syntax + Collection<Map<String, String>> hostGroup1Configurations = new ArrayList<Map<String, String>>(); + hostGroup1Properties.put("configurations", hostGroup1Configurations); + Map<String, String> hostGroup1Configuration1 = new HashMap<String, String>(); + hostGroup1Configuration1.put("foo-type/hostGroup1Prop1", "prop1Value"); + hostGroup1Configuration1.put("foo-type/hostGroup1Prop2", "prop2Value"); + hostGroup1Configurations.add(hostGroup1Configuration1); + + // cluster scoped configuration + Collection<Map<String, String>> clusterConfigurations = new ArrayList<Map<String, String>>(); + properties.put("configurations", clusterConfigurations); + + Map<String, String> clusterConfigurationProperties = new HashMap<String, String>(); + clusterConfigurations.add(clusterConfigurationProperties); + clusterConfigurationProperties.put("someType/properties/property1", "someValue"); + clusterConfigurationProperties.put("someType/properties_attributes/attribute1/property1", "someAttributePropValue"); + + return properties; + } + + public static Map<String, Object> createBlueprintRequestPropertiesCountOnly(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(ClusterResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + + Collection<Map<String, Object>> hostGroups = new ArrayList<Map<String, Object>>(); + properties.put("host_groups", hostGroups); + + // host group 2 + Map<String, Object> hostGroup2Properties = new HashMap<String, Object>(); + hostGroups.add(hostGroup2Properties); + hostGroup2Properties.put("name", "group2"); + Collection<Map<String, String>> hostGroup2Hosts = new ArrayList<Map<String, String>>(); + hostGroup2Properties.put("hosts", hostGroup2Hosts); + Map<String, String> hostGroup2HostProperties = new HashMap<String, String>(); + // count with no predicate + hostGroup2HostProperties.put("host_count", "5"); + hostGroup2Hosts.add(hostGroup2HostProperties); + // host group 2 scoped configuration + // version 2 configuration syntax + Collection<Map<String, String>> hostGroup2Configurations = new ArrayList<Map<String, String>>(); + hostGroup2Properties.put("configurations", hostGroup2Configurations); + Map<String, String> hostGroup2Configuration1 = new HashMap<String, String>(); + hostGroup2Configuration1.put("foo-type/properties/hostGroup2Prop1", "prop1Value"); + hostGroup2Configuration1.put("foo-type/properties_attributes/attribute1/hostGroup2Prop10", "attribute1Prop10-value"); + hostGroup2Configurations.add(hostGroup2Configuration1); + + // cluster scoped configuration + Collection<Map<String, String>> clusterConfigurations = new ArrayList<Map<String, String>>(); + properties.put("configurations", clusterConfigurations); + + Map<String, String> clusterConfigurationProperties = new HashMap<String, String>(); + clusterConfigurations.add(clusterConfigurationProperties); + clusterConfigurationProperties.put("someType/properties/property1", "someValue"); + clusterConfigurationProperties.put("someType/properties_attributes/attribute1/property1", "someAttributePropValue"); + + return properties; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/43dd0cdd/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ScaleClusterRequestTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ScaleClusterRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ScaleClusterRequestTest.java new file mode 100644 index 0000000..91b6c6f --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ScaleClusterRequestTest.java @@ -0,0 +1,394 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.controller.internal; + +import org.apache.ambari.server.controller.spi.ResourceProvider; +import org.apache.ambari.server.topology.Blueprint; +import org.apache.ambari.server.topology.BlueprintFactory; +import org.apache.ambari.server.topology.Configuration; +import org.apache.ambari.server.topology.HostGroup; +import org.apache.ambari.server.topology.HostGroupInfo; +import org.apache.ambari.server.topology.InvalidTopologyTemplateException; +import org.apache.ambari.server.topology.TopologyRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +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.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; + +/** + * Unit tests for ScaleClusterRequest. + */ +@SuppressWarnings("unchecked") +public class ScaleClusterRequestTest { + + private static final String CLUSTER_NAME = "cluster_name"; + private static final String BLUEPRINT_NAME = "blueprint_name"; + private static final String HOST1_NAME = "host1.test.com"; + private static final String HOST2_NAME = "host2.test.com"; + private static final String GROUP1_NAME = "group1"; + private static final String GROUP2_NAME = "group2"; + private static final String GROUP3_NAME = "group3"; + private static final String PREDICATE = "test/prop=foo"; + + private static final BlueprintFactory blueprintFactory = createStrictMock(BlueprintFactory.class); + private static final Blueprint blueprint = createNiceMock(Blueprint.class); + private static final ResourceProvider hostResourceProvider = createMock(ResourceProvider.class); + private static final HostGroup hostGroup1 = createNiceMock(HostGroup.class); + private static final Configuration blueprintConfig = new Configuration( + Collections.<String, Map<String, String>>emptyMap(), + Collections.<String, Map<String, Map<String, String>>>emptyMap()); + + @Before + public void setUp() throws Exception { + ScaleClusterRequest.init(blueprintFactory); + // set host resource provider field + Class clazz = BaseClusterRequest.class; + Field f = clazz.getDeclaredField("hostResourceProvider"); + f.setAccessible(true); + f.set(null, hostResourceProvider); + + expect(blueprintFactory.getBlueprint(BLUEPRINT_NAME)).andReturn(blueprint).anyTimes(); + expect(blueprint.getConfiguration()).andReturn(blueprintConfig).anyTimes(); + expect(blueprint.getHostGroup(GROUP1_NAME)).andReturn(hostGroup1).anyTimes(); + expect(blueprint.getHostGroup(GROUP2_NAME)).andReturn(hostGroup1).anyTimes(); + expect(blueprint.getHostGroup(GROUP3_NAME)).andReturn(hostGroup1).anyTimes(); + expect(blueprint.getName()).andReturn(BLUEPRINT_NAME).anyTimes(); + expect(hostResourceProvider.checkPropertyIds(Collections.singleton("test/prop"))). + andReturn(Collections.<String>emptySet()).once(); + + replay(blueprintFactory, blueprint, hostResourceProvider, hostGroup1); + } + + @After + public void tearDown() { + verify(blueprintFactory, blueprint, hostResourceProvider, hostGroup1); + reset(blueprintFactory, blueprint, hostResourceProvider, hostGroup1); + } + + @Test + public void test_basic_hostName() throws Exception { + // reset default host resource provider expectations to none since no host predicate is used + reset(hostResourceProvider); + replay(hostResourceProvider); + + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(Collections.singleton( + createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME))); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "1"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group1 + // host info + HostGroupInfo group1Info = hostGroupInfo.get(GROUP1_NAME); + assertEquals(GROUP1_NAME, group1Info.getHostGroupName()); + assertEquals(1, group1Info.getHostNames().size()); + assertTrue(group1Info.getHostNames().contains(HOST1_NAME)); + assertEquals(1, group1Info.getRequestedHostCount()); + assertNull(group1Info.getPredicate()); + } + + @Test + public void testMultipleHostNames() throws Exception { + // reset default host resource provider expectations to none since no host predicate is used + reset(hostResourceProvider); + replay(hostResourceProvider); + + Set<Map<String, Object>> propertySet = new HashSet<Map<String, Object>>(); + propertySet.add(createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME)); + propertySet.add(createScaleClusterPropertiesGroup1_HostName2(CLUSTER_NAME, BLUEPRINT_NAME)); + + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(propertySet); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "2"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group1 + // host info + HostGroupInfo group1Info = hostGroupInfo.get(GROUP1_NAME); + assertEquals(GROUP1_NAME, group1Info.getHostGroupName()); + assertEquals(2, group1Info.getHostNames().size()); + assertTrue(group1Info.getHostNames().contains(HOST1_NAME)); + assertTrue(group1Info.getHostNames().contains(HOST2_NAME)); + assertEquals(2, group1Info.getRequestedHostCount()); + assertNull(group1Info.getPredicate()); + } + + @Test + public void test_basic_hostCount() throws Exception { + // reset default host resource provider expectations to none since no host predicate is used + reset(hostResourceProvider); + replay(hostResourceProvider); + + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(Collections.singleton( + createScaleClusterPropertiesGroup1_HostCount(CLUSTER_NAME, BLUEPRINT_NAME))); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "1"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group2 + // host info + HostGroupInfo group2Info = hostGroupInfo.get(GROUP2_NAME); + assertEquals(GROUP2_NAME, group2Info.getHostGroupName()); + assertEquals(0, group2Info.getHostNames().size()); + assertEquals(1, group2Info.getRequestedHostCount()); + assertNull(group2Info.getPredicate()); + } + + @Test + public void test_basic_hostCount2() throws Exception { + // reset default host resource provider expectations to none since no host predicate is used + reset(hostResourceProvider); + replay(hostResourceProvider); + + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(Collections.singleton( + createScaleClusterPropertiesGroup1_HostCount2(CLUSTER_NAME, BLUEPRINT_NAME))); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "2"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group2 + // host info + HostGroupInfo group2Info = hostGroupInfo.get(GROUP3_NAME); + assertEquals(GROUP3_NAME, group2Info.getHostGroupName()); + assertEquals(0, group2Info.getHostNames().size()); + assertEquals(2, group2Info.getRequestedHostCount()); + assertNull(group2Info.getPredicate()); + } + + @Test + public void test_basic_hostCountAndPredicate() throws Exception { + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(Collections.singleton( + createScaleClusterPropertiesGroup1_HostCountAndPredicate(CLUSTER_NAME, BLUEPRINT_NAME))); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "1"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(1, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group3 + // host info + HostGroupInfo group3Info = hostGroupInfo.get(GROUP3_NAME); + assertEquals(GROUP3_NAME, group3Info.getHostGroupName()); + assertEquals(0, group3Info.getHostNames().size()); + assertEquals(1, group3Info.getRequestedHostCount()); + assertEquals(PREDICATE, group3Info.getPredicateString()); + } + + @Test + public void testMultipleHostGroups() throws Exception { + Set<Map<String, Object>> propertySet = new HashSet<Map<String, Object>>(); + propertySet.add(createScaleClusterPropertiesGroup1_HostCountAndPredicate(CLUSTER_NAME, BLUEPRINT_NAME)); + propertySet.add(createScaleClusterPropertiesGroup1_HostCount(CLUSTER_NAME, BLUEPRINT_NAME)); + propertySet.add(createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME)); + + TopologyRequest scaleClusterRequest = new ScaleClusterRequest(propertySet); + + assertEquals(TopologyRequest.Type.SCALE, scaleClusterRequest.getType()); + assertEquals(String.format("Scale Cluster '%s' (+%s hosts)", CLUSTER_NAME, "3"), + scaleClusterRequest.getCommandDescription()); + assertEquals(CLUSTER_NAME, scaleClusterRequest.getClusterName()); + assertSame(blueprint, scaleClusterRequest.getBlueprint()); + Map<String, HostGroupInfo> hostGroupInfo = scaleClusterRequest.getHostGroupInfo(); + assertEquals(3, hostGroupInfo.size()); + assertEquals(0, scaleClusterRequest.getTopologyValidators().size()); + + // group + // host info + HostGroupInfo group1Info = hostGroupInfo.get(GROUP1_NAME); + assertEquals(GROUP1_NAME, group1Info.getHostGroupName()); + assertEquals(1, group1Info.getHostNames().size()); + assertTrue(group1Info.getHostNames().contains(HOST1_NAME)); + assertEquals(1, group1Info.getRequestedHostCount()); + assertNull(group1Info.getPredicate()); + + // group2 + // host info + HostGroupInfo group2Info = hostGroupInfo.get(GROUP2_NAME); + assertEquals(GROUP2_NAME, group2Info.getHostGroupName()); + assertEquals(0, group2Info.getHostNames().size()); + assertEquals(1, group2Info.getRequestedHostCount()); + assertNull(group2Info.getPredicate()); + + // group3 + // host info + HostGroupInfo group3Info = hostGroupInfo.get(GROUP3_NAME); + assertEquals(GROUP3_NAME, group3Info.getHostGroupName()); + assertEquals(0, group3Info.getHostNames().size()); + assertEquals(1, group3Info.getRequestedHostCount()); + assertEquals(PREDICATE, group3Info.getPredicateString()); + } + + + + @Test(expected = InvalidTopologyTemplateException.class) + public void test_GroupInfoMissingName() throws Exception { + Map<String, Object> properties = createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME); + // remove host group name + properties.remove("host_group"); + + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); + // should result in an exception + new ScaleClusterRequest(Collections.singleton(properties)); + } + + @Test(expected = InvalidTopologyTemplateException.class) + public void test_NoHostNameOrHostCount() throws Exception { + Map<String, Object> properties = createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME); + // remove host name + properties.remove("host_name"); + + // reset default host resource provider expectations to none + reset(hostResourceProvider); + replay(hostResourceProvider); + // should result in an exception because neither host name or host count are specified + new ScaleClusterRequest(Collections.singleton(properties)); + } + + + @Test(expected = InvalidTopologyTemplateException.class) + public void testInvalidPredicateProperty() throws Exception { + reset(hostResourceProvider); + // checkPropertyIds() returns invalid property names + expect(hostResourceProvider.checkPropertyIds(Collections.singleton("test/prop"))). + andReturn(Collections.singleton("test/prop")); + replay(hostResourceProvider); + + // should result in an exception due to invalid property in host predicate + new ScaleClusterRequest(Collections.singleton( + createScaleClusterPropertiesGroup1_HostCountAndPredicate(CLUSTER_NAME, BLUEPRINT_NAME))); + } + + @Test(expected = InvalidTopologyTemplateException.class) + public void testMultipleBlueprints() throws Exception { + reset(hostResourceProvider); + replay(hostResourceProvider); + + Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>(); + propertySet.add(createScaleClusterPropertiesGroup1_HostName(CLUSTER_NAME, BLUEPRINT_NAME)); + propertySet.add(createScaleClusterPropertiesGroup1_HostName2(CLUSTER_NAME, "OTHER_BLUEPRINT")); + + // should result in an exception due to different blueprints being specified + new ScaleClusterRequest(propertySet); + } + + public static Map<String, Object> createScaleClusterPropertiesGroup1_HostName(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(HostResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + properties.put(HostResourceProvider.HOSTGROUP_PROPERTY_ID, GROUP1_NAME); + properties.put(HostResourceProvider.HOST_NAME_NO_CATEGORY_PROPERTY_ID, HOST1_NAME); + + return properties; + } + + public static Map<String, Object> createScaleClusterPropertiesGroup1_HostCount(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(HostResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + properties.put(HostResourceProvider.HOSTGROUP_PROPERTY_ID, GROUP2_NAME); + properties.put(HostResourceProvider.HOST_COUNT_PROPERTY_ID, 1); + + return properties; + } + + public static Map<String, Object> createScaleClusterPropertiesGroup1_HostCountAndPredicate(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(HostResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + properties.put(HostResourceProvider.HOSTGROUP_PROPERTY_ID, GROUP3_NAME); + properties.put(HostResourceProvider.HOST_COUNT_PROPERTY_ID, 1); + properties.put(HostResourceProvider.HOST_PREDICATE_PROPERTY_ID, PREDICATE); + + return properties; + } + + public static Map<String, Object> createScaleClusterPropertiesGroup1_HostCount2(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(HostResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + properties.put(HostResourceProvider.HOSTGROUP_PROPERTY_ID, GROUP3_NAME); + properties.put(HostResourceProvider.HOST_COUNT_PROPERTY_ID, 2); + + return properties; + } + + public static Map<String, Object> createScaleClusterPropertiesGroup1_HostName2(String clusterName, String blueprintName) { + Map<String, Object> properties = new LinkedHashMap<String, Object>(); + + properties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, clusterName); + properties.put(HostResourceProvider.BLUEPRINT_PROPERTY_ID, blueprintName); + properties.put(HostResourceProvider.HOSTGROUP_PROPERTY_ID, GROUP1_NAME); + properties.put(HostResourceProvider.HOST_NAME_NO_CATEGORY_PROPERTY_ID, HOST2_NAME); + + return properties; + } +}
