/** * 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.Collection; import java.util.HashMap; import java.util.Map; 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; import org.apache.ambari.server.topology.Credential; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.apache.ambari.server.topology.NoSuchBlueprintException; import org.apache.ambari.server.topology.SecurityConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Enums; import com.google.common.base.Optional; import com.google.common.base.Strings; /** * Request for provisioning a cluster. */ @SuppressWarnings("unchecked") public class ProvisionClusterRequest extends BaseClusterRequest { /** * host groups property name */ public static final String HOSTGROUPS_PROPERTY = "host_groups"; /** * host group name property name */ public static final String HOSTGROUP_NAME_PROPERTY = "name"; /** * host group host count property name */ public static final String HOSTGROUP_HOST_COUNT_PROPERTY = "host_count"; /** * host group host predicate property name */ public static final String HOSTGROUP_HOST_PREDICATE_PROPERTY = "host_predicate"; /** * host group host fqdn property name */ public static final String HOSTGROUP_HOST_FQDN_PROPERTY = "fqdn"; /** * rack info property name */ public static final String HOSTGROUP_HOST_RACK_INFO_PROPERTY = "rack_info"; /** * host group hosts property name */ public static final String HOSTGROUP_HOSTS_PROPERTY = "hosts"; /** * configurations property name */ public static final String CONFIGURATIONS_PROPERTY = "configurations"; /** * default password property name */ public static final String DEFAULT_PASSWORD_PROPERTY = "default_password"; /** * configuration recommendation strategy property name */ public static final String CONFIG_RECOMMENDATION_STRATEGY = "config_recommendation_strategy"; /** * The repo version to use */ 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 */ private static ConfigurationFactory configurationFactory = new ConfigurationFactory(); /** * cluster name */ private String clusterName; /** * default password */ private String defaultPassword; private Map<String, Credential> credentialsMap; /** * configuration recommendation strategy */ private final ConfigRecommendationStrategy configRecommendationStrategy; private String repoVersion; private final String quickLinksProfileJson; private final static Logger LOG = LoggerFactory.getLogger(ProvisionClusterRequest.class); /** * Constructor. * * @param properties request properties * @param securityConfiguration security config related properties */ public ProvisionClusterRequest(Map<String, Object> properties, SecurityConfiguration securityConfiguration) throws InvalidTopologyTemplateException { setClusterName(String.valueOf(properties.get( ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID))); if (properties.containsKey(REPO_VERSION_PROPERTY)) { repoVersion = properties.get(REPO_VERSION_PROPERTY).toString(); } if (properties.containsKey(DEFAULT_PASSWORD_PROPERTY)) { defaultPassword = String.valueOf(properties.get(DEFAULT_PASSWORD_PROPERTY)); } try { parseBlueprint(properties); } catch (NoSuchStackException e) { throw new InvalidTopologyTemplateException("The specified stack doesn't exist: " + e, e); } catch (NoSuchBlueprintException e) { throw new InvalidTopologyTemplateException("The specified blueprint doesn't exist: " + e, e); } this.securityConfiguration = securityConfiguration; Configuration configuration = configurationFactory.getConfiguration( (Collection<Map<String, String>>) properties.get(CONFIGURATIONS_PROPERTY)); configuration.setParentConfiguration(blueprint.getConfiguration()); setConfiguration(configuration); parseHostGroupInfo(properties); this.credentialsMap = parseCredentials(properties); this.configRecommendationStrategy = parseConfigRecommendationStrategy(properties); setProvisionAction(parseProvisionAction(properties)); try { this.quickLinksProfileJson = processQuickLinksProfile(properties); } catch (QuickLinksProfileEvaluationException ex) { throw new InvalidTopologyTemplateException("Invalid quick links profile", ex); } } private String processQuickLinksProfile(Map<String, Object> properties) throws QuickLinksProfileEvaluationException { Object globalFilters = properties.get(QUICKLINKS_PROFILE_FILTERS_PROPERTY); Object serviceFilters = properties.get(QUICKLINKS_PROFILE_SERVICES_PROPERTY); return (null != globalFilters || null != serviceFilters) ? new QuickLinksProfileBuilder().buildQuickLinksProfile(globalFilters, serviceFilters) : null; } private Map<String, Credential> parseCredentials(Map<String, Object> properties) throws InvalidTopologyTemplateException { HashMap<String, Credential> credentialHashMap = new HashMap<>(); Set<Map<String, String>> credentialsSet = (Set<Map<String, String>>) properties.get(ClusterResourceProvider.CREDENTIALS_PROPERTY_ID); if (credentialsSet != null) { for (Map<String, String> credentialMap : credentialsSet) { String alias = Strings.emptyToNull(credentialMap.get("alias")); if (alias == null) { throw new InvalidTopologyTemplateException("credential.alias property is missing."); } String principal = Strings.emptyToNull(credentialMap.get("principal")); if (principal == null) { throw new InvalidTopologyTemplateException("credential.principal property is missing."); } String key = Strings.emptyToNull(credentialMap.get("key")); if (key == null) { throw new InvalidTopologyTemplateException("credential.key is missing."); } String typeString = Strings.emptyToNull(credentialMap.get("type")); if (typeString == null) { throw new InvalidTopologyTemplateException("credential.type is missing."); } CredentialStoreType type = Enums.getIfPresent(CredentialStoreType.class, typeString.toUpperCase()).orNull(); if (type == null) { throw new InvalidTopologyTemplateException("credential.type is invalid."); } credentialHashMap.put(alias, new Credential(alias, principal, key, type)); } } return credentialHashMap; } public Map<String, Credential> getCredentialsMap() { return credentialsMap; } public String getClusterName() { return clusterName; } public void setClusterName(String clusterName) { this.clusterName = clusterName; } public ConfigRecommendationStrategy getConfigRecommendationStrategy() { return configRecommendationStrategy; } @Override public Long getClusterId() { return clusterId; } public void setClusterId(Long clusterId) { this.clusterId = clusterId; } @Override public Type getType() { return Type.PROVISION; } @Override public String getDescription() { 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)); // 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 */ private void parseHostGroupInfo(Map<String, Object> properties) throws InvalidTopologyTemplateException { Collection<Map<String, Object>> hostGroups = (Collection<Map<String, Object>>) properties.get(HOSTGROUPS_PROPERTY); if (hostGroups == null || hostGroups.isEmpty()) { throw new InvalidTopologyTemplateException("'host_groups' element must be included in cluster create body"); } for (Map<String, Object> hostGroupProperties : hostGroups) { processHostGroup(hostGroupProperties); } } /** * Process host group properties. * * @param hostGroupProperties host group properties * * @throws InvalidTopologyTemplateException if any validation checks on properties fail */ private void processHostGroup(Map<String, Object> hostGroupProperties) throws InvalidTopologyTemplateException { String name = String.valueOf(hostGroupProperties.get(HOSTGROUP_NAME_PROPERTY)); // String.valueOf() converts null to "null" if (name == null || name.equals("null") || name.isEmpty()) { throw new InvalidTopologyTemplateException("All host groups must contain a 'name' element"); } HostGroupInfo hostGroupInfo = new HostGroupInfo(name); getHostGroupInfo().put(name, hostGroupInfo); processHostCountAndPredicate(hostGroupProperties, hostGroupInfo); processGroupHosts(name, (Collection<Map<String, String>>) hostGroupProperties.get(HOSTGROUP_HOSTS_PROPERTY), hostGroupInfo); // don't set the parent configuration hostGroupInfo.setConfiguration(configurationFactory.getConfiguration( (Collection<Map<String, String>>) hostGroupProperties.get(CONFIGURATIONS_PROPERTY))); } /** * Process host count and host predicate for a host group. * * @param hostGroupProperties host group properties * @param hostGroupInfo associated host group info instance * * @throws InvalidTopologyTemplateException specified host group properties fail validation */ private void processHostCountAndPredicate(Map<String, Object> hostGroupProperties, HostGroupInfo hostGroupInfo) throws InvalidTopologyTemplateException { if (hostGroupProperties.containsKey(HOSTGROUP_HOST_COUNT_PROPERTY)) { hostGroupInfo.setRequestedCount(Integer.valueOf(String.valueOf( hostGroupProperties.get(HOSTGROUP_HOST_COUNT_PROPERTY)))); LOG.info("Stored expected hosts count {} for group {}", hostGroupInfo.getRequestedHostCount(), hostGroupInfo.getHostGroupName()); } if (hostGroupProperties.containsKey(HOSTGROUP_HOST_PREDICATE_PROPERTY)) { if (hostGroupInfo.getRequestedHostCount() == 0) { throw new InvalidTopologyTemplateException(String.format( "Host group '%s' must not specify 'host_predicate' without 'host_count'", hostGroupInfo.getHostGroupName())); } String hostPredicate = String.valueOf(hostGroupProperties.get(HOSTGROUP_HOST_PREDICATE_PROPERTY)); validateHostPredicateProperties(hostPredicate); try { hostGroupInfo.setPredicate(hostPredicate); LOG.info("Compiled host predicate {} for group {}", hostPredicate, hostGroupInfo.getHostGroupName()); } catch (InvalidQueryException e) { throw new InvalidTopologyTemplateException( String.format("Unable to compile host predicate '%s': %s", hostPredicate, e), e); } } } /** * Process host group hosts. * * @param name host group name * @param hosts collection of host group host properties * @param hostGroupInfo associated host group info instance * * @throws InvalidTopologyTemplateException specified host group properties fail validation */ private void processGroupHosts(String name, Collection<Map<String, String>> hosts, HostGroupInfo hostGroupInfo) throws InvalidTopologyTemplateException { if (hosts != null) { if (hostGroupInfo.getRequestedHostCount() != 0) { throw new InvalidTopologyTemplateException(String.format( "Host group '%s' must not contain both a 'hosts' element and a 'host_count' value", name)); } if (hostGroupInfo.getPredicate() != null) { throw new InvalidTopologyTemplateException(String.format( "Host group '%s' must not contain both a 'hosts' element and a 'host_predicate' value", name)); } for (Map<String, String> hostProperties : hosts) { if (hostProperties.containsKey(HOSTGROUP_HOST_FQDN_PROPERTY)) { hostGroupInfo.addHost(hostProperties.get(HOSTGROUP_HOST_FQDN_PROPERTY)); } if (hostProperties.containsKey(HOSTGROUP_HOST_RACK_INFO_PROPERTY)) { hostGroupInfo.addHostRackInfo( hostProperties.get(HOSTGROUP_HOST_FQDN_PROPERTY), hostProperties.get(HOSTGROUP_HOST_RACK_INFO_PROPERTY)); } } } if (hostGroupInfo.getRequestedHostCount() == 0) { throw new InvalidTopologyTemplateException(String.format( "Host group '%s' must contain at least one 'hosts/fqdn' or a 'host_count' value", name)); } } /** * Parse config recommendation strategy. Throws exception in case of the value is not correct. * The default value is {@link ConfigRecommendationStrategy#NEVER_APPLY} * @param properties request properties * @throws InvalidTopologyTemplateException specified config recommendation strategy property fail validation */ private ConfigRecommendationStrategy parseConfigRecommendationStrategy(Map<String, Object> properties) throws InvalidTopologyTemplateException { if (properties.containsKey(CONFIG_RECOMMENDATION_STRATEGY)) { String configRecommendationStrategy = String.valueOf(properties.get(CONFIG_RECOMMENDATION_STRATEGY)); Optional<ConfigRecommendationStrategy> configRecommendationStrategyOpt = Enums.getIfPresent(ConfigRecommendationStrategy.class, configRecommendationStrategy); if (!configRecommendationStrategyOpt.isPresent()) { throw new InvalidTopologyTemplateException(String.format( "Config recommendation strategy is not supported: %s", configRecommendationStrategy)); } return configRecommendationStrategyOpt.get(); } else { // default return ConfigRecommendationStrategy.NEVER_APPLY; } } /** * Parse Provision Action specified in RequestInfo properties. */ private ProvisionAction parseProvisionAction(Map<String, Object> properties) throws InvalidTopologyTemplateException { if (properties.containsKey(PROVISION_ACTION_PROPERTY)) { String provisionActionStr = String.valueOf(properties.get(PROVISION_ACTION_PROPERTY)); Optional<ProvisionAction> provisionActionOptional = Enums.getIfPresent(ProvisionAction.class, provisionActionStr); if (!provisionActionOptional.isPresent()) { throw new InvalidTopologyTemplateException(String.format( "Invalid provision_action specified in the template: %s", provisionActionStr)); } return provisionActionOptional.get(); } else { return ProvisionAction.INSTALL_AND_START; } } /** * @return the repository version, if any */ public String getRepositoryVersion() { return repoVersion; } /** * @return the quick links profile in Json string format */ public String getQuickLinksProfileJson() { return quickLinksProfileJson; } public String getDefaultPassword() { return defaultPassword; } }