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