/**
* 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;
}
}