blob: 4d8e56ffd223d6c635ef2edad0200dc993a5c8cd [file] [log] [blame]
/**
* 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 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.apache.ambari.server.topology.TopologyValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
/**
* 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<String, HostGroupInfo>();
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 List<TopologyValidator> getTopologyValidators() {
return Collections.emptyList();
}
@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<HostGroup>();
for (ExportedHostGroup exportedHostGroup : exportedHostGroups) {
// create Component using component name
List<Component> componentList = new ArrayList<Component>();
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<String, Map<String, String>>();
Map<String, Map<String, Map<String, String>>> attributes = new HashMap<String, Map<String, Map<String, String>>>();
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<ExportedHostGroup, ExportedHostGroup>();
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<Map<String, String>>();
for (String component : group.getComponents()) {
Map<String, String> mapComponentProperties = new HashMap<String, String>();
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<String>();
/**
* Host group scoped configurations.
*/
private Collection<ExportedConfiguration> configurations = new HashSet<ExportedConfiguration>();
/**
* Number of instances.
*/
private int m_cardinality = 1;
/**
* Collection of associated hosts.
*/
private Collection<String> hosts = new HashSet<String>();
/**
* 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<String, Map<String, String>>();
Map<String, Map<String, Map<String, String>>> configAttributes = new HashMap<String, Map<String, Map<String, String>>>();
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<String, String>();
/**
* Attributes for the properties in the cluster configuration.
*/
private Map<String, Map<String, String>> propertyAttributes = new HashMap<String, Map<String, String>>();
/**
* 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;
}
}
}