/** * 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.controller.internal; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.api.util.TreeNode; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.AmbariServer; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.apache.ambari.server.state.DesiredConfig; import org.apache.ambari.server.state.HostConfig; import org.apache.ambari.server.topology.Blueprint; import org.apache.ambari.server.topology.BlueprintImpl; import org.apache.ambari.server.topology.Component; import org.apache.ambari.server.topology.Configuration; import org.apache.ambari.server.topology.HostGroup; import org.apache.ambari.server.topology.HostGroupImpl; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyTemplateException; import org.apache.ambari.server.topology.TopologyRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Request to export a blueprint from an existing cluster. */ public class ExportBlueprintRequest implements TopologyRequest { private final static Logger LOG = LoggerFactory.getLogger(ExportBlueprintRequest.class); private static AmbariManagementController controller = AmbariServer.getController(); private String clusterName; private Long clusterId; private Blueprint blueprint; private Configuration configuration; //todo: Should this map be represented by a new class? private Map<String, HostGroupInfo> hostGroupInfo = new HashMap<>(); public ExportBlueprintRequest(TreeNode<Resource> clusterNode) throws InvalidTopologyTemplateException { Resource clusterResource = clusterNode.getObject(); clusterName = String.valueOf(clusterResource.getPropertyValue( ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID)); clusterId = Long.valueOf(String.valueOf(clusterResource.getPropertyValue( ClusterResourceProvider.CLUSTER_ID_PROPERTY_ID))); createConfiguration(clusterNode); //todo: should be parsing Configuration from the beginning //createConfiguration(configurations); Collection<ExportedHostGroup> exportedHostGroups = processHostGroups(clusterNode.getChild("hosts")); createHostGroupInfo(exportedHostGroups); createBlueprint(exportedHostGroups, parseStack(clusterResource)); } public String getClusterName() { return clusterName; } @Override public Long getClusterId() { return clusterId; } @Override public Type getType() { return Type.EXPORT; } @Override public Blueprint getBlueprint() { return blueprint; } @Override public Configuration getConfiguration() { return configuration; } @Override public Map<String, HostGroupInfo> getHostGroupInfo() { return hostGroupInfo; } @Override public String getDescription() { return String.format("Export Command For Cluster '%s'", clusterName); } // ----- private instance methods ------------------------------------------ private void createBlueprint(Collection<ExportedHostGroup> exportedHostGroups, Stack stack) { String bpName = "exported-blueprint"; Collection<HostGroup> hostGroups = new ArrayList<>(); for (ExportedHostGroup exportedHostGroup : exportedHostGroups) { // create Component using component name List<Component> componentList = new ArrayList<>(); for (String component : exportedHostGroup.getComponents()) { componentList.add(new Component(component)); } hostGroups.add(new HostGroupImpl(exportedHostGroup.getName(), bpName, stack, componentList, exportedHostGroup.getConfiguration(), String.valueOf(exportedHostGroup.getCardinality()))); } blueprint = new BlueprintImpl(bpName, hostGroups, stack, configuration, null); } private void createHostGroupInfo(Collection<ExportedHostGroup> exportedHostGroups) { for (ExportedHostGroup exportedGroup : exportedHostGroups) { HostGroupInfo groupInfo = new HostGroupInfo(exportedGroup.getName()); groupInfo.addHosts(exportedGroup.getHostInfo()); //todo: should be parsing Configuration from the beginning groupInfo.setConfiguration(exportedGroup.getConfiguration()); hostGroupInfo.put(groupInfo.getHostGroupName(), groupInfo); } } private Stack parseStack(Resource clusterResource) throws InvalidTopologyTemplateException { String[] stackTokens = String.valueOf(clusterResource.getPropertyValue( ClusterResourceProvider.CLUSTER_VERSION_PROPERTY_ID)).split("-"); try { return new Stack(stackTokens[0], stackTokens[1], controller); } catch (AmbariException e) { throw new InvalidTopologyTemplateException(String.format( "The specified stack doesn't exist: name=%s version=%s", stackTokens[0], stackTokens[1])); } } /** * Process cluster scoped configurations. * * @param clusterNode cluster node * */ private void createConfiguration(TreeNode<Resource> clusterNode) { Map<String, Map<String, String>> properties = new HashMap<>(); Map<String, Map<String, Map<String, String>>> attributes = new HashMap<>(); Map<String, Object> desiredConfigMap = clusterNode.getObject().getPropertiesMap().get("Clusters/desired_configs"); TreeNode<Resource> configNode = clusterNode.getChild("configurations"); for (TreeNode<Resource> config : configNode.getChildren()) { ExportedConfiguration configuration = new ExportedConfiguration(config); DesiredConfig desiredConfig = (DesiredConfig) desiredConfigMap.get(configuration.getType()); if (desiredConfig != null && desiredConfig.getTag().equals(configuration.getTag())) { properties.put(configuration.getType(), configuration.getProperties()); attributes.put(configuration.getType(), configuration.getPropertyAttributes()); } } configuration = new Configuration(properties, attributes); // empty parent configuration when exporting as all properties are included in this configuration configuration.setParentConfiguration(new Configuration( Collections.<String, Map<String, String>>emptyMap(), Collections.<String, Map<String, Map<String, String>>>emptyMap())); } /** * Process cluster host groups. * * @param hostNode host node * * @return collection of host groups */ private Collection<ExportedHostGroup> processHostGroups(TreeNode<Resource> hostNode) { Map<ExportedHostGroup, ExportedHostGroup> mapHostGroups = new HashMap<>(); int count = 1; for (TreeNode<Resource> host : hostNode.getChildren()) { ExportedHostGroup group = new ExportedHostGroup(host); String hostName = (String) host.getObject().getPropertyValue( PropertyHelper.getPropertyId("Hosts", "host_name")); if (mapHostGroups.containsKey(group)) { ExportedHostGroup hostGroup = mapHostGroups.get(group); hostGroup.incrementCardinality(); hostGroup.addHost(hostName); } else { mapHostGroups.put(group, group); group.setName("host_group_" + count++); group.addHost(hostName); } processHostGroupComponents(group); } return mapHostGroups.values(); } /** * Process host group component information for a specific host. * * @param group host group instance * * @return list of component names for the host */ private List<Map<String, String>> processHostGroupComponents(ExportedHostGroup group) { List<Map<String, String>> listHostGroupComponents = new ArrayList<>(); for (String component : group.getComponents()) { Map<String, String> mapComponentProperties = new HashMap<>(); listHostGroupComponents.add(mapComponentProperties); mapComponentProperties.put("name", component); } return listHostGroupComponents; } // ----- Host Group inner class -------------------------------------------- /** * Host Group representation. */ public class ExportedHostGroup { /** * Host Group name. * */ private String name; /** * Associated components. */ private Set<String> components = new HashSet<>(); /** * Host group scoped configurations. */ private Collection<ExportedConfiguration> configurations = new HashSet<>(); /** * Number of instances. */ private int m_cardinality = 1; /** * Collection of associated hosts. */ private Collection<String> hosts = new HashSet<>(); /** * Constructor. * * @param host host node */ public ExportedHostGroup(TreeNode<Resource> host) { TreeNode<Resource> components = host.getChild("host_components"); for (TreeNode<Resource> component : components.getChildren()) { getComponents().add((String) component.getObject().getPropertyValue( "HostRoles/component_name")); } addAmbariComponentIfLocalhost((String) host.getObject().getPropertyValue( PropertyHelper.getPropertyId("Hosts", "host_name"))); processGroupConfiguration(host); } public Configuration getConfiguration() { Map<String, Map<String, String>> configProperties = new HashMap<>(); Map<String, Map<String, Map<String, String>>> configAttributes = new HashMap<>(); for (ExportedConfiguration config : configurations) { configProperties.put(config.getType(), config.getProperties()); configAttributes.put(config.getType(), config.getPropertyAttributes()); } return new Configuration(configProperties, configAttributes); } /** * Process host group configuration. * * @param host host node */ private void processGroupConfiguration(TreeNode<Resource> host) { Map<String, Object> desiredConfigMap = host.getObject().getPropertiesMap().get("Hosts/desired_configs"); if (desiredConfigMap != null) { for (Map.Entry<String, Object> entry : desiredConfigMap.entrySet()) { String type = entry.getKey(); HostConfig hostConfig = (HostConfig) entry.getValue(); Map<Long, String> overrides = hostConfig.getConfigGroupOverrides(); if (overrides != null && ! overrides.isEmpty()) { Long version = Collections.max(overrides.keySet()); String tag = overrides.get(version); TreeNode<Resource> clusterNode = host.getParent().getParent(); TreeNode<Resource> configNode = clusterNode.getChild("configurations"); for (TreeNode<Resource> config : configNode.getChildren()) { ExportedConfiguration configuration = new ExportedConfiguration(config); if (type.equals(configuration.getType()) && tag.equals(configuration.getTag())) { getConfigurations().add(configuration); break; } } } } } } public String getName() { return name; } public Set<String> getComponents() { return components; } public Collection<String> getHostInfo() { return hosts; } /** * Set the name. * * @param name name of host group */ public void setName(String name) { this.name = name; } /** * Add a host. * * @param host host to add */ public void addHost(String host) { hosts.add(host); } /** * Obtain associated host group scoped configurations. * * @return collection of host group scoped configurations */ public Collection<ExportedConfiguration> getConfigurations() { return configurations; } /** * Obtain the number of instances associated with this host group. * * @return number of hosts associated with this host group */ public int getCardinality() { return m_cardinality; } /** * Increment the cardinality count by one. */ public void incrementCardinality() { m_cardinality += 1; } /** * Add the AMBARI_SERVER component if the host is the local host. * * @param hostname host to check */ private void addAmbariComponentIfLocalhost(String hostname) { try { InetAddress hostAddress = InetAddress.getByName(hostname); try { if (hostAddress.equals(InetAddress.getLocalHost())) { getComponents().add("AMBARI_SERVER"); } } catch (UnknownHostException e) { //todo: SystemException? throw new RuntimeException("Unable to obtain local host name", e); } } catch (UnknownHostException e) { // ignore } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExportedHostGroup hostGroup = (ExportedHostGroup) o; return components.equals(hostGroup.components) && configurations.equals(hostGroup.configurations); } @Override public int hashCode() { int result = components.hashCode(); result = 31 * result + configurations.hashCode(); return result; } } /** * Encapsulates a configuration. */ private class ExportedConfiguration { /** * Configuration type such as hdfs-site. */ private String type; /** * Configuration tag. */ private String tag; /** * Properties of the configuration. */ private Map<String, String> properties = new HashMap<>(); /** * Attributes for the properties in the cluster configuration. */ private Map<String, Map<String, String>> propertyAttributes = new HashMap<>(); /** * Constructor. * * @param configNode configuration node */ @SuppressWarnings("unchecked") public ExportedConfiguration(TreeNode<Resource> configNode) { Resource configResource = configNode.getObject(); type = (String) configResource.getPropertyValue("type"); tag = (String) configResource.getPropertyValue("tag"); // property map type is currently <String, Object> Map<String, Map<String, Object>> propertiesMap = configNode.getObject().getPropertiesMap(); if (propertiesMap.containsKey("properties")) { properties = (Map) propertiesMap.get("properties"); } // get the property attributes set in this configuration if (propertiesMap.containsKey("properties_attributes")) { propertyAttributes = (Map) propertiesMap.get("properties_attributes"); } //todo: not processing config here, ensure that //todo: this logic regarding null/empty properties is properly handled // if (properties != null && !properties.isEmpty()) { // stripRequiredProperties(properties); // } else { // LOG.warn("Empty configuration found for configuration type = " + type + // " during Blueprint export. This may occur after an upgrade of Ambari, when" + // "attempting to export a Blueprint from a cluster started by an older version of " + // "Ambari."); // } } /** * Get configuration type. * * @return configuration type */ public String getType() { return type; } /** * Get configuration tag. * * @return configuration tag */ public String getTag() { return tag; } /** * Get configuration properties. * * @return map of properties and values */ public Map<String, String> getProperties() { return properties; } /** * Get property attributes. * * @return map of property attributes */ public Map<String, Map<String, String>> getPropertyAttributes() { return propertyAttributes; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExportedConfiguration that = (ExportedConfiguration) o; return tag.equals(that.tag) && type.equals(that.type) && properties.equals(that.properties) && propertyAttributes.equals(that.propertyAttributes); } @Override public int hashCode() { int result = type.hashCode(); result = 31 * result + tag.hashCode(); result = 31 * result + properties.hashCode(); result = 31 * result + propertyAttributes.hashCode(); return result; } } }