package org.apache.ambari.server.topology.validators; /* * Licensed 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. */ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.apache.ambari.server.controller.internal.Stack; import org.apache.ambari.server.state.PropertyInfo; import org.apache.ambari.server.topology.Blueprint; import org.apache.ambari.server.topology.ClusterTopology; import org.apache.ambari.server.topology.HostGroup; import org.apache.ambari.server.topology.HostGroupInfo; import org.apache.ambari.server.topology.InvalidTopologyException; import org.apache.ambari.server.topology.TopologyValidator; /** * Validates that all required passwords are provided. */ public class RequiredPasswordValidator implements TopologyValidator { // todo remove the field as all the information is available in the topology being validated private String defaultPassword; public RequiredPasswordValidator() { } /** * Validate that all required password properties have been set or that 'default_password' is specified. * * @throws InvalidTopologyException if required password properties are missing and no * default is specified via 'default_password' */ public void validate(ClusterTopology topology) throws InvalidTopologyException { defaultPassword = topology.getDefaultPassword(); Map<String, Map<String, Collection<String>>> missingPasswords = validateRequiredPasswords(topology); if (! missingPasswords.isEmpty()) { throw new InvalidTopologyException("Missing required password properties. Specify a value for these " + "properties in the cluster or host group configurations or include 'default_password' field in request. " + missingPasswords); } } /** * Validate all configurations. Validation is done on the operational configuration of each * host group. An operational configuration is achieved by overlaying host group configuration * on top of cluster configuration which overlays the default stack configurations. * * @return map of required properties which are missing. Empty map if none are missing. * * @throws IllegalArgumentException if blueprint contains invalid information */ //todo: this is copied/pasted from Blueprint and is currently only used by validatePasswordProperties() //todo: seems that we should have some common place for this code so it can be used by BP and here? private Map<String, Map<String, Collection<String>>> validateRequiredPasswords(ClusterTopology topology) { Map<String, Map<String, Collection<String>>> missingProperties = new HashMap<>(); for (Map.Entry<String, HostGroupInfo> groupEntry: topology.getHostGroupInfo().entrySet()) { String hostGroupName = groupEntry.getKey(); Map<String, Map<String, String>> groupProperties = groupEntry.getValue().getConfiguration().getFullProperties(3); Collection<String> processedServices = new HashSet<>(); Blueprint blueprint = topology.getBlueprint(); Stack stack = blueprint.getStack(); HostGroup hostGroup = blueprint.getHostGroup(hostGroupName); for (String component : hostGroup.getComponentNames()) { //for now, AMBARI is not recognized as a service in Stacks if (component.equals("AMBARI_SERVER")) { continue; } String serviceName = stack.getServiceForComponent(component); if (processedServices.add(serviceName)) { //todo: do I need to subtract excluded configs? Collection<Stack.ConfigProperty> requiredProperties = stack.getRequiredConfigurationProperties(serviceName, PropertyInfo.PropertyType.PASSWORD); for (Stack.ConfigProperty property : requiredProperties) { String category = property.getType(); String name = property.getName(); if (! propertyExists(topology, groupProperties, category, name)) { Map<String, Collection<String>> missingHostGroupPropsMap = missingProperties.get(hostGroupName); if (missingHostGroupPropsMap == null) { missingHostGroupPropsMap = new HashMap<>(); missingProperties.put(hostGroupName, missingHostGroupPropsMap); } Collection<String> missingHostGroupTypeProps = missingHostGroupPropsMap.get(category); if (missingHostGroupTypeProps == null) { missingHostGroupTypeProps = new HashSet<>(); missingHostGroupPropsMap.put(category, missingHostGroupTypeProps); } missingHostGroupTypeProps.add(name); } } } } } return missingProperties; } private boolean propertyExists(ClusterTopology topology, Map<String, Map<String, String>> props, String type, String property) { Map<String, String> typeProps = props.get(type); return (typeProps != null && typeProps.containsKey(property)) || setDefaultPassword(topology, type, property); } /** * Attempt to set the default password in cluster configuration for missing password property. * * @param configType configuration type * @param property password property name * * @return true if password was set, otherwise false. Currently the password will always be set * unless it is null */ private boolean setDefaultPassword(ClusterTopology topology, String configType, String property) { boolean setDefaultPassword = false; if (defaultPassword != null && ! defaultPassword.trim().isEmpty()) { topology.getConfiguration().setProperty(configType, property, defaultPassword); setDefaultPassword = true; } return setDefaultPassword; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RequiredPasswordValidator that = (RequiredPasswordValidator) o; return defaultPassword == null ? that.defaultPassword == null : defaultPassword.equals(that.defaultPassword); } @Override public int hashCode() { return defaultPassword != null ? defaultPassword.hashCode() : 0; } }