/** * 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 distribut * ed 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.topology; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.ObjectNotFoundException; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.AmbariServer; import org.apache.ambari.server.controller.internal.ProvisionAction; import org.apache.ambari.server.controller.internal.Stack; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.apache.ambari.server.orm.dao.BlueprintDAO; import org.apache.ambari.server.orm.entities.BlueprintEntity; import org.apache.ambari.server.stack.NoSuchStackException; import com.google.inject.Inject; /** * Create a Blueprint instance. */ public class BlueprintFactory { // Blueprints protected static final String BLUEPRINT_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Blueprints", "blueprint_name"); protected static final String STACK_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Blueprints", "stack_name"); protected static final String STACK_VERSION_PROPERTY_ID = PropertyHelper.getPropertyId("Blueprints", "stack_version"); // Host Groups protected static final String HOST_GROUP_PROPERTY_ID = "host_groups"; protected static final String HOST_GROUP_NAME_PROPERTY_ID = "name"; protected static final String HOST_GROUP_CARDINALITY_PROPERTY_ID = "cardinality"; // Host Group Components protected static final String COMPONENT_PROPERTY_ID ="components"; protected static final String COMPONENT_NAME_PROPERTY_ID ="name"; protected static final String COMPONENT_PROVISION_ACTION_PROPERTY_ID = "provision_action"; // Configurations protected static final String CONFIGURATION_PROPERTY_ID = "configurations"; protected static final String PROPERTIES_PROPERTY_ID = "properties"; protected static final String PROPERTIES_ATTRIBUTES_PROPERTY_ID = "properties_attributes"; protected static final String SETTINGS_PROPERTY_ID = "settings"; private static BlueprintDAO blueprintDAO; private ConfigurationFactory configFactory = new ConfigurationFactory(); private final StackFactory stackFactory; public BlueprintFactory() { this(new DefaultStackFactory()); } protected BlueprintFactory(StackFactory stackFactory) { this.stackFactory = stackFactory; } public Blueprint getBlueprint(String blueprintName) throws NoSuchStackException { BlueprintEntity entity = blueprintDAO.findByName(blueprintName); //todo: just return null? return entity == null ? null : new BlueprintImpl(entity); } /** * Convert a map of properties to a blueprint entity. * * @param properties property map * @param securityConfiguration security related properties * @return new blueprint entity */ @SuppressWarnings("unchecked") public Blueprint createBlueprint(Map<String, Object> properties, SecurityConfiguration securityConfiguration) throws NoSuchStackException { String name = String.valueOf(properties.get(BLUEPRINT_NAME_PROPERTY_ID)); // String.valueOf() will return "null" if value is null if (name.equals("null") || name.isEmpty()) { //todo: should throw a checked exception from here throw new IllegalArgumentException("Blueprint name must be provided"); } Stack stack = createStack(properties); Collection<HostGroup> hostGroups = processHostGroups(name, stack, properties); Configuration configuration = configFactory.getConfiguration((Collection<Map<String, String>>) properties.get(CONFIGURATION_PROPERTY_ID)); Setting setting = SettingFactory.getSetting((Collection<Map<String, Object>>) properties.get(SETTINGS_PROPERTY_ID)); return new BlueprintImpl(name, hostGroups, stack, configuration, securityConfiguration, setting); } protected Stack createStack(Map<String, Object> properties) throws NoSuchStackException { String stackName = String.valueOf(properties.get(STACK_NAME_PROPERTY_ID)); String stackVersion = String.valueOf(properties.get(STACK_VERSION_PROPERTY_ID)); try { //todo: don't pass in controller return stackFactory.createStack(stackName, stackVersion, AmbariServer.getController()); } catch (ObjectNotFoundException e) { throw new NoSuchStackException(stackName, stackVersion); } catch (AmbariException e) { //todo: throw new RuntimeException("An error occurred parsing the stack information.", e); } } //todo: Move logic to HostGroupImpl @SuppressWarnings("unchecked") private Collection<HostGroup> processHostGroups(String bpName, Stack stack, Map<String, Object> properties) { Set<HashMap<String, Object>> hostGroupProps = (HashSet<HashMap<String, Object>>) properties.get(HOST_GROUP_PROPERTY_ID); if (hostGroupProps == null || hostGroupProps.isEmpty()) { throw new IllegalArgumentException("At least one host group must be specified in a blueprint"); } Collection<HostGroup> hostGroups = new ArrayList<>(); for (HashMap<String, Object> hostGroupProperties : hostGroupProps) { String hostGroupName = (String) hostGroupProperties.get(HOST_GROUP_NAME_PROPERTY_ID); if (hostGroupName == null || hostGroupName.isEmpty()) { throw new IllegalArgumentException("Every host group must include a non-null 'name' property"); } HashSet<HashMap<String, String>> componentProps = (HashSet<HashMap<String, String>>) hostGroupProperties.get(COMPONENT_PROPERTY_ID); Collection<Map<String, String>> configProps = (Collection<Map<String, String>>) hostGroupProperties.get(CONFIGURATION_PROPERTY_ID); Collection<Component> components = processHostGroupComponents(stack, hostGroupName, componentProps); Configuration configuration = configFactory.getConfiguration(configProps); String cardinality = String.valueOf(hostGroupProperties.get(HOST_GROUP_CARDINALITY_PROPERTY_ID)); HostGroup group = new HostGroupImpl(hostGroupName, bpName, stack, components, configuration, cardinality); hostGroups.add(group); } return hostGroups; } private Collection<Component> processHostGroupComponents(Stack stack, String groupName, HashSet<HashMap<String, String>> componentProps) { if (componentProps == null || componentProps.isEmpty()) { throw new IllegalArgumentException("Host group '" + groupName + "' must contain at least one component"); } Collection<String> stackComponentNames = getAllStackComponents(stack); Collection<Component> components = new ArrayList<>(); for (HashMap<String, String> componentProperties : componentProps) { String componentName = componentProperties.get(COMPONENT_NAME_PROPERTY_ID); if (componentName == null || componentName.isEmpty()) { throw new IllegalArgumentException("Host group '" + groupName + "' contains a component with no 'name' property"); } if (! stackComponentNames.contains(componentName)) { throw new IllegalArgumentException("The component '" + componentName + "' in host group '" + groupName + "' is not valid for the specified stack"); } String componentProvisionAction = componentProperties.get(COMPONENT_PROVISION_ACTION_PROPERTY_ID); if (componentProvisionAction != null) { //TODO, might want to add some validation here, to only accept value enum types, rwn components.add(new Component(componentName, ProvisionAction.valueOf(componentProvisionAction))); } else { components.add(new Component(componentName)); } } return components; } /** * Obtain all component names for the specified stack. * * @return collection of component names for the specified stack * @throws IllegalArgumentException if the specified stack doesn't exist */ private Collection<String> getAllStackComponents(Stack stack) { Collection<String> allComponents = new HashSet<>(); for (Collection<String> components: stack.getComponents().values()) { allComponents.addAll(components); } // currently ambari server is no a recognized component allComponents.add("AMBARI_SERVER"); return allComponents; } /** * Static initialization. * * @param dao blueprint data access object */ @Inject public static void init(BlueprintDAO dao) { blueprintDAO = dao; } /** * Internal interface used to abstract out the process of creating the Stack object. * * This is used to simplify unit testing, since a new Factory can be provided to * simulate various Stack or error conditions. */ interface StackFactory { Stack createStack(String stackName, String stackVersion, AmbariManagementController managementController) throws AmbariException; } /** * Default implementation of StackFactory. * * Calls the Stack constructor to create the Stack instance. * */ private static class DefaultStackFactory implements StackFactory { @Override public Stack createStack(String stackName, String stackVersion, AmbariManagementController managementController) throws AmbariException { return new Stack(stackName, stackVersion, managementController); } } }