/**
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.DuplicateResourceException;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
import org.apache.ambari.server.controller.spi.NoSuchResourceException;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.RequestStatus;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.orm.dao.BlueprintDAO;
import org.apache.ambari.server.orm.entities.BlueprintConfigEntity;
import org.apache.ambari.server.orm.entities.BlueprintConfiguration;
import org.apache.ambari.server.orm.entities.BlueprintEntity;
import org.apache.ambari.server.orm.entities.BlueprintSettingEntity;
import org.apache.ambari.server.orm.entities.HostGroupComponentEntity;
import org.apache.ambari.server.orm.entities.HostGroupEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.stack.NoSuchStackException;
import org.apache.ambari.server.state.SecurityType;
import org.apache.ambari.server.state.StackInfo;
import org.apache.ambari.server.topology.Blueprint;
import org.apache.ambari.server.topology.BlueprintFactory;
import org.apache.ambari.server.topology.InvalidTopologyException;
import org.apache.ambari.server.topology.SecurityConfiguration;
import org.apache.ambari.server.topology.SecurityConfigurationFactory;
import org.apache.ambari.server.utils.SecretReference;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.gson.Gson;
/**
* Resource Provider for Blueprint resources.
*/
public class BlueprintResourceProvider extends AbstractControllerResourceProvider {
// ----- Property ID constants ---------------------------------------------
// Blueprints
public static final String BLUEPRINT_NAME_PROPERTY_ID =
PropertyHelper.getPropertyId("Blueprints", "blueprint_name");
public static final String STACK_NAME_PROPERTY_ID =
PropertyHelper.getPropertyId("Blueprints", "stack_name");
public static final String STACK_VERSION_PROPERTY_ID =
PropertyHelper.getPropertyId("Blueprints", "stack_version");
public static final String BLUEPRINT_SECURITY_PROPERTY_ID =
PropertyHelper.getPropertyId("Blueprints", "security");
public static final String BLUEPRINTS_PROPERTY_ID = "Blueprints";
// Host Groups
public static final String HOST_GROUP_PROPERTY_ID = "host_groups";
public static final String HOST_GROUP_NAME_PROPERTY_ID = "name";
public static final String HOST_GROUP_CARDINALITY_PROPERTY_ID = "cardinality";
// Host Group Components
public static final String COMPONENT_PROPERTY_ID ="components";
public static final String COMPONENT_NAME_PROPERTY_ID ="name";
public static final String COMPONENT_PROVISION_ACTION_PROPERTY_ID = "provision_action";
// Configurations
public static final String CONFIGURATION_PROPERTY_ID = "configurations";
// Setting
public static final String SETTING_PROPERTY_ID = "settings";
public static final String PROPERTIES_PROPERTY_ID = "properties";
public static final String PROPERTIES_ATTRIBUTES_PROPERTY_ID = "properties_attributes";
public static final String SCHEMA_IS_NOT_SUPPORTED_MESSAGE =
"Configuration format provided in Blueprint is not supported";
public static final String REQUEST_BODY_EMPTY_ERROR_MESSAGE =
"Request body for Blueprint create request is empty";
public static final String CONFIGURATION_LIST_CHECK_ERROR_MESSAGE =
"Configurations property must be a List of Maps";
public static final String CONFIGURATION_MAP_CHECK_ERROR_MESSAGE =
"Configuration elements must be Maps";
public static final String CONFIGURATION_MAP_SIZE_CHECK_ERROR_MESSAGE =
"Configuration Maps must hold a single configuration type each";
// Primary Key Fields
private static Set<String> pkPropertyIds =
new HashSet<>(Arrays.asList(new String[]{
BLUEPRINT_NAME_PROPERTY_ID}));
/**
* Used to create Blueprint instances
*/
private static BlueprintFactory blueprintFactory;
/**
* Used to create SecurityConfiguration instances
*/
private static SecurityConfigurationFactory securityConfigurationFactory;
/**
* Blueprint Data Access Object
*/
private static BlueprintDAO blueprintDAO;
/**
* Used to serialize to/from json.
*/
private static Gson jsonSerializer;
// ----- Constructors ----------------------------------------------------
/**
* Create a new resource provider for the given management controller.
*
* @param propertyIds the property ids
* @param keyPropertyIds the key property ids
* @param controller management controller
*/
BlueprintResourceProvider(Set<String> propertyIds,
Map<Resource.Type, String> keyPropertyIds,
AmbariManagementController controller) {
super(propertyIds, keyPropertyIds, controller);
}
/**
* Static initialization.
*
* @param factory blueprint factory
* @param dao blueprint data access object
* @param gson json serializer
*/
public static void init(BlueprintFactory factory, BlueprintDAO dao, SecurityConfigurationFactory
securityFactory, Gson gson, AmbariMetaInfo metaInfo) {
blueprintFactory = factory;
blueprintDAO = dao;
securityConfigurationFactory = securityFactory;
jsonSerializer = gson;
ambariMetaInfo = metaInfo;
}
// ----- ResourceProvider ------------------------------------------------
@Override
protected Set<String> getPKPropertyIds() {
return pkPropertyIds;
}
@Override
public RequestStatus createResources(Request request)
throws SystemException, UnsupportedPropertyException,
ResourceAlreadyExistsException, NoSuchParentResourceException {
for (Map<String, Object> properties : request.getProperties()) {
try {
createResources(getCreateCommand(properties, request.getRequestInfoProperties()));
}catch(IllegalArgumentException e) {
LOG.error("Exception while creating blueprint", e);
throw e;
}
}
notifyCreate(Resource.Type.Blueprint, request);
return getRequestStatus(null);
}
@Override
//todo: continue to use dao/entity directly or use blueprint factory?
public Set<Resource> getResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException,
NoSuchResourceException, NoSuchParentResourceException {
List<BlueprintEntity> results = null;
boolean applyPredicate = false;
if (predicate != null) {
Set<Map<String, Object>> requestProps = getPropertyMaps(predicate);
if (requestProps.size() == 1 ) {
String name = (String) requestProps.iterator().next().get(
BLUEPRINT_NAME_PROPERTY_ID);
if (name != null) {
BlueprintEntity entity = blueprintDAO.findByName(name);
results = entity == null ? Collections.<BlueprintEntity>emptyList() :
Collections.singletonList(entity);
}
}
}
if (results == null) {
applyPredicate = true;
results = blueprintDAO.findAll();
}
Set<Resource> resources = new HashSet<>();
for (BlueprintEntity entity : results) {
Resource resource = toResource(entity, getRequestPropertyIds(request, predicate));
if (predicate == null || ! applyPredicate || predicate.evaluate(resource)) {
resources.add(resource);
}
}
if (predicate != null && resources.isEmpty()) {
throw new NoSuchResourceException(
"The requested resource doesn't exist: Blueprint not found, " + predicate);
}
return resources;
}
@Override
public RequestStatus updateResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException,
NoSuchResourceException, NoSuchParentResourceException {
// no-op, blueprints are immutable. Service doesn't support PUT so should never get here.
return null;
}
@Override
public RequestStatus deleteResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException,
NoSuchResourceException, NoSuchParentResourceException {
//TODO (jspeidel): Revisit concurrency control
Set<Resource> setResources = getResources(
new RequestImpl(null, null, null, null), predicate);
for (final Resource resource : setResources) {
final String blueprintName =
(String) resource.getPropertyValue(BLUEPRINT_NAME_PROPERTY_ID);
LOG.info("Deleting Blueprint, name = " + blueprintName);
modifyResources(new Command<Void>() {
@Override
public Void invoke() throws AmbariException {
blueprintDAO.removeByName(blueprintName);
return null;
}
});
}
notifyDelete(Resource.Type.Blueprint, predicate);
return getRequestStatus(null);
}
/**
* Used to get stack metainfo.
*/
private static AmbariMetaInfo ambariMetaInfo;
// ----- Instance Methods ------------------------------------------------
/**
* Create a resource instance from a blueprint entity.
*
* @param entity blueprint entity
* @param requestedIds requested id's
*
* @return a new resource instance for the given blueprint entity
*/
protected Resource toResource(BlueprintEntity entity, Set<String> requestedIds) throws NoSuchResourceException {
StackEntity stackEntity = entity.getStack();
Resource resource = new ResourceImpl(Resource.Type.Blueprint);
setResourceProperty(resource, BLUEPRINT_NAME_PROPERTY_ID, entity.getBlueprintName(), requestedIds);
setResourceProperty(resource, STACK_NAME_PROPERTY_ID, stackEntity.getStackName(), requestedIds);
setResourceProperty(resource, STACK_VERSION_PROPERTY_ID, stackEntity.getStackVersion(), requestedIds);
List<Map<String, Object>> listGroupProps = new ArrayList<>();
Collection<HostGroupEntity> hostGroups = entity.getHostGroups();
for (HostGroupEntity hostGroup : hostGroups) {
Map<String, Object> mapGroupProps = new HashMap<>();
mapGroupProps.put(HOST_GROUP_NAME_PROPERTY_ID, hostGroup.getName());
listGroupProps.add(mapGroupProps);
mapGroupProps.put(HOST_GROUP_CARDINALITY_PROPERTY_ID, hostGroup.getCardinality());
List<Map<String, String>> listComponentProps = new ArrayList<>();
Collection<HostGroupComponentEntity> components = hostGroup.getComponents();
for (HostGroupComponentEntity component : components) {
Map<String, String> mapComponentProps = new HashMap<>();
mapComponentProps.put(COMPONENT_NAME_PROPERTY_ID, component.getName());
if (component.getProvisionAction() != null) {
mapComponentProps.put(COMPONENT_PROVISION_ACTION_PROPERTY_ID, component.getProvisionAction().toString());
}
listComponentProps.add(mapComponentProps);
}
mapGroupProps.put(COMPONENT_PROPERTY_ID, listComponentProps);
mapGroupProps.put(CONFIGURATION_PROPERTY_ID, populateConfigurationList(
hostGroup.getConfigurations()));
}
setResourceProperty(resource, HOST_GROUP_PROPERTY_ID, listGroupProps, requestedIds);
setResourceProperty(resource, CONFIGURATION_PROPERTY_ID,
populateConfigurationList(entity.getConfigurations()), requestedIds);
setResourceProperty(resource, SETTING_PROPERTY_ID,
populateSettingList(entity.getSettings()), requestedIds);
if (entity.getSecurityType() != null) {
Map<String, String> securityConfigMap = new LinkedHashMap<>();
securityConfigMap.put(SecurityConfigurationFactory.TYPE_PROPERTY_ID, entity.getSecurityType().name());
if(entity.getSecurityType() == SecurityType.KERBEROS) {
securityConfigMap.put(SecurityConfigurationFactory.KERBEROS_DESCRIPTOR_REFERENCE_PROPERTY_ID, entity.getSecurityDescriptorReference());
}
setResourceProperty(resource, BLUEPRINT_SECURITY_PROPERTY_ID, securityConfigMap, requestedIds);
}
return resource;
}
/**
* Populate a list of configuration property maps from a collection of configuration entities.
*
* @param configurations collection of configuration entities
*
* @return list of configuration property maps
*/
List<Map<String, Map<String, Object>>> populateConfigurationList(
Collection<? extends BlueprintConfiguration> configurations) throws NoSuchResourceException {
List<Map<String, Map<String, Object>>> listConfigurations = new ArrayList<>();
for (BlueprintConfiguration config : configurations) {
Map<String, Map<String, Object>> mapConfigurations = new HashMap<>();
Map<String, Object> configTypeDefinition = new HashMap<>();
String type = config.getType();
if(config instanceof BlueprintConfigEntity) {
Map<String, String> properties = jsonSerializer.<Map<String, String>>fromJson(
config.getConfigData(), Map.class);
StackEntity stack = ((BlueprintConfigEntity)config).getBlueprintEntity().getStack();
StackInfo metaInfoStack;
try {
metaInfoStack = ambariMetaInfo.getStack(stack.getStackName(), stack.getStackVersion());
} catch (AmbariException e) {
throw new NoSuchResourceException(e.getMessage());
}
Map<org.apache.ambari.server.state.PropertyInfo.PropertyType, Set<String>> propertiesTypes =
metaInfoStack.getConfigPropertiesTypes(type);
SecretReference.replacePasswordsWithReferences(propertiesTypes, properties, type, -1l);
configTypeDefinition.put(PROPERTIES_PROPERTY_ID, properties);
} else {
Map<String, Object> properties = jsonSerializer.<Map<String, Object>>fromJson(
config.getConfigData(), Map.class);
configTypeDefinition.put(PROPERTIES_PROPERTY_ID, properties);
}
Map<String, Map<String, String>> attributes = jsonSerializer.<Map<String, Map<String, String>>>fromJson(
config.getConfigAttributes(), Map.class);
if (attributes != null && !attributes.isEmpty()) {
configTypeDefinition.put(PROPERTIES_ATTRIBUTES_PROPERTY_ID, attributes);
}
mapConfigurations.put(type, configTypeDefinition);
listConfigurations.add(mapConfigurations);
}
return listConfigurations;
}
/**
* Populate a list of setting property maps from a collection of setting entities.
*
* @param settings collection of setting entities
*
* @return list of setting property maps
*/
public static List<Map<String, Object>> populateSettingList(
Collection<? extends BlueprintSettingEntity> settings) throws NoSuchResourceException {
List<Map<String, Object>> listSettings = new ArrayList<>();
if (settings != null) {
for (BlueprintSettingEntity setting : settings) {
List<Map<String, String>> propertiesList = jsonSerializer.<List<Map<String, String>>>fromJson(
setting.getSettingData(), List.class);
Map<String, Object> settingMap = new HashMap<>();
settingMap.put(setting.getSettingName(), propertiesList);
listSettings.add(settingMap);
}
}
return listSettings;
}
/**
* Populate blueprint configurations.
*
* @param propertyMaps collection of configuration property maps
* @param blueprint blueprint entity to set configurations on
*/
void createBlueprintConfigEntities(Collection<Map<String, String>> propertyMaps,
BlueprintEntity blueprint) {
Collection<BlueprintConfigEntity> configurations = new ArrayList<>();
if (propertyMaps != null) {
for (Map<String, String> configuration : propertyMaps) {
BlueprintConfigEntity configEntity = new BlueprintConfigEntity();
configEntity.setBlueprintEntity(blueprint);
configEntity.setBlueprintName(blueprint.getBlueprintName());
populateConfigurationEntity(configuration, configEntity);
configurations.add(configEntity);
}
}
blueprint.setConfigurations(configurations);
}
/**
* Populate a configuration entity from properties.
*
* @param configuration property map
* @param configEntity config entity to populate
*/
void populateConfigurationEntity(Map<String, String> configuration, BlueprintConfiguration configEntity) {
BlueprintConfigPopulationStrategy p = decidePopulationStrategy(configuration);
p.applyConfiguration(configuration, configEntity);
}
BlueprintConfigPopulationStrategy decidePopulationStrategy(Map<String, String> configuration) {
if (configuration != null && !configuration.isEmpty()) {
String keyEntry = configuration.keySet().iterator().next();
String[] keyNameTokens = keyEntry.split("/");
int levels = keyNameTokens.length;
String propertiesType = keyNameTokens[1];
if (levels == 2) {
return new BlueprintConfigPopulationStrategyV1();
} else if ((levels == 3 && PROPERTIES_PROPERTY_ID.equals(propertiesType))
|| (levels == 4 && PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertiesType))) {
return new BlueprintConfigPopulationStrategyV2();
} else {
throw new IllegalArgumentException(SCHEMA_IS_NOT_SUPPORTED_MESSAGE);
}
} else {
return new BlueprintConfigPopulationStrategyV2();
}
}
/**
* Create a create command with all properties set.
*
* @param properties properties to be applied to blueprint
* @param requestInfoProps request info properties
*
* @return a new create command
*/
private Command<Void> getCreateCommand(final Map<String, Object> properties, final Map<String, String> requestInfoProps) {
return new Command<Void>() {
@SuppressWarnings("rawtypes")
@Override
public Void invoke() throws AmbariException {
String rawRequestBody = requestInfoProps.get(Request.REQUEST_INFO_BODY_PROPERTY);
Preconditions.checkArgument(!Strings.isNullOrEmpty(rawRequestBody), REQUEST_BODY_EMPTY_ERROR_MESSAGE);
Map<String, Object> rawBodyMap = jsonSerializer.<Map<String, Object>>fromJson(rawRequestBody, Map.class);
Object configurationData = rawBodyMap.get(CONFIGURATION_PROPERTY_ID);
if (configurationData != null) {
Preconditions.checkArgument(configurationData instanceof List, CONFIGURATION_LIST_CHECK_ERROR_MESSAGE);
for (Object map : (List) configurationData) {
Preconditions.checkArgument(map instanceof Map, CONFIGURATION_MAP_CHECK_ERROR_MESSAGE);
Preconditions.checkArgument(((Map) map).size() <= 1, CONFIGURATION_MAP_SIZE_CHECK_ERROR_MESSAGE);
}
}
SecurityConfiguration securityConfiguration = securityConfigurationFactory
.createSecurityConfigurationFromRequest((Map<String, Object>) rawBodyMap.get(BLUEPRINTS_PROPERTY_ID), true);
Blueprint blueprint;
try {
blueprint = blueprintFactory.createBlueprint(properties, securityConfiguration);
} catch (NoSuchStackException e) {
throw new IllegalArgumentException("Specified stack doesn't exist: " + e, e);
}
if (blueprintDAO.findByName(blueprint.getName()) != null) {
throw new DuplicateResourceException(
"Attempted to create a Blueprint which already exists, blueprint_name=" +
blueprint.getName());
}
try {
blueprint.validateRequiredProperties();
} catch (InvalidTopologyException e) {
throw new IllegalArgumentException("Blueprint configuration validation failed: " + e.getMessage(), e);
}
String validateTopology = requestInfoProps.get("validate_topology");
if (validateTopology == null || ! validateTopology.equalsIgnoreCase("false")) {
try {
blueprint.validateTopology();
} catch (InvalidTopologyException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
LOG.info("Creating Blueprint, name=" + blueprint.getName());
String blueprintSetting = blueprint.getSetting() == null ? "(null)" :
jsonSerializer.toJson(blueprint.getSetting().getProperties());
LOG.info("Blueprint setting=" + blueprintSetting);
try {
blueprintDAO.create(blueprint.toEntity());
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
};
}
/**
* The structure of blueprints is evolving where multiple resource
* structures are to be supported. This class abstracts the population
* of configurations which have changed from a map of key-value strings,
* to an map containing 'properties' and 'properties_attributes' maps.
*
* Extending classes can determine how they want to populate the
* configuration maps depending on input.
*/
protected static abstract class BlueprintConfigPopulationStrategy {
public void applyConfiguration(Map<String, String> configuration, BlueprintConfiguration blueprintConfiguration) {
Map<String, String> configData = new HashMap<>();
Map<String, Map<String, String>> configAttributes = new HashMap<>();
if (configuration != null) {
for (Map.Entry<String, String> entry : configuration.entrySet()) {
String absolutePropName = entry.getKey();
String propertyValue = entry.getValue();
String[] propertyNameTokens = absolutePropName.split("/");
if (blueprintConfiguration.getType() == null) {
blueprintConfiguration.setType(propertyNameTokens[0]);
}
addProperty(configData, configAttributes, propertyNameTokens, propertyValue);
}
}
blueprintConfiguration.setConfigData(jsonSerializer.toJson(configData));
blueprintConfiguration.setConfigAttributes(jsonSerializer.toJson(configAttributes));
}
protected abstract void addProperty(Map<String, String> configData,
Map<String, Map<String, String>> configAttributes,
String[] propertyNameTokens, String propertyValue);
}
/**
* Original blueprint configuration format where configs were a map
* of strings.
*/
protected static class BlueprintConfigPopulationStrategyV1 extends BlueprintConfigPopulationStrategy {
@Override
protected void addProperty(Map<String, String> configData,
Map<String, Map<String, String>> configAttributes,
String[] propertyNameTokens, String propertyValue) {
configData.put(propertyNameTokens[1], propertyValue);
}
}
/**
* New blueprint configuration format where configs are a map from 'properties' and
* 'properties_attributes' to a map of strings.
*
* @since 1.7.0
*/
protected static class BlueprintConfigPopulationStrategyV2 extends BlueprintConfigPopulationStrategy {
@Override
protected void addProperty(Map<String, String> configData,
Map<String, Map<String, String>> configAttributes,
String[] propertyNameTokens, String propertyValue) {
if (PROPERTIES_PROPERTY_ID.equals(propertyNameTokens[1])) {
configData.put(propertyNameTokens[2], propertyValue);
} else if (PROPERTIES_ATTRIBUTES_PROPERTY_ID.equals(propertyNameTokens[1])) {
addConfigAttribute(configAttributes, propertyNameTokens, propertyValue);
}
}
private void addConfigAttribute(Map<String, Map<String, String>> configDependencyProperties,
String[] propertyNameTokens, String value) {
if (!configDependencyProperties.containsKey(propertyNameTokens[2])) {
configDependencyProperties.put(propertyNameTokens[2], new HashMap<String, String>());
}
Map<String, String> propertiesGroup = configDependencyProperties.get(propertyNameTokens[2]);
propertiesGroup.put(propertyNameTokens[3], value);
}
}
}