AMBARI-21602. Pre-configure services when Kerberos is enabled to reduce number of core service restarts when services are added (rlevas)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e6641558 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e6641558 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e6641558 Branch: refs/heads/branch-2.6 Commit: e6641558cfff0b06a8ad0c0a41610433f96f1335 Parents: e27a214 Author: Robert Levas <[email protected]> Authored: Thu Aug 24 16:06:56 2017 -0400 Committer: Robert Levas <[email protected]> Committed: Thu Aug 24 16:06:56 2017 -0400 ---------------------------------------------------------------------- .../server/api/services/AmbariMetaInfo.java | 118 +-- .../stackadvisor/StackAdvisorRequest.java | 10 +- .../server/controller/KerberosHelper.java | 116 ++- .../server/controller/KerberosHelperImpl.java | 761 ++++++++++++++----- ...usterKerberosDescriptorResourceProvider.java | 3 +- .../internal/StackVersionResourceProvider.java | 67 +- .../customactions/ActionDefinitionManager.java | 4 +- .../AbstractPrepareKerberosServerAction.java | 2 +- .../kerberos/KerberosServerAction.java | 6 + .../kerberos/PreconfigureServiceType.java | 46 ++ .../PrepareDisableKerberosServerAction.java | 2 +- .../PrepareEnableKerberosServerAction.java | 40 +- .../PrepareKerberosIdentitiesServerAction.java | 52 +- .../upgrades/FixAuthToLocalMappingAction.java | 2 +- .../upgrades/UpgradeUserKerberosDescriptor.java | 4 +- .../server/stack/ConfigurationDirectory.java | 5 +- .../ambari/server/stack/ExtensionDirectory.java | 3 +- .../ambari/server/stack/ServiceDirectory.java | 16 +- .../ambari/server/stack/ServiceModule.java | 6 +- .../ambari/server/stack/StackDirectory.java | 75 +- .../ambari/server/stack/StackManager.java | 13 +- .../apache/ambari/server/stack/StackModule.java | 9 +- .../ambari/server/state/ConfigHelper.java | 3 +- .../apache/ambari/server/state/ServiceInfo.java | 13 +- .../apache/ambari/server/state/StackInfo.java | 22 +- .../kerberos/AbstractKerberosDescriptor.java | 34 + .../state/kerberos/KerberosDescriptor.java | 46 +- .../kerberos/KerberosServiceDescriptor.java | 86 ++- .../server/upgrade/AbstractUpgradeCatalog.java | 2 +- .../1.10.3-10/configuration/kerberos-env.xml | 27 + .../stacks/HDP/2.6/kerberos_preconfigure.json | 24 + .../server/api/services/AmbariMetaInfoTest.java | 55 +- .../server/controller/KerberosHelperTest.java | 717 ++++++++++++----- ...rKerberosDescriptorResourceProviderTest.java | 14 +- .../FixAuthToLocalMappingActionTest.java | 2 +- .../UpgradeUserKerberosDescriptorTest.java | 4 +- .../ambari/server/stack/ServiceModuleTest.java | 3 +- .../KerberosDescriptorUpdateHelperTest.java | 4 +- .../kerberos/KerberosServiceDescriptorTest.java | 2 + .../stacks/HDP/2.0.8/kerberos_preconfigure.json | 23 + 40 files changed, 1759 insertions(+), 682 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e6641558/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java index 9afba8c..d03081f 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java @@ -24,7 +24,6 @@ import static org.apache.ambari.server.controller.utilities.PropertyHelper.AGGRE import java.io.File; import java.io.FileReader; -import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; @@ -57,7 +56,6 @@ import org.apache.ambari.server.orm.dao.AlertDefinitionDAO; import org.apache.ambari.server.orm.dao.MetainfoDAO; import org.apache.ambari.server.orm.entities.AlertDefinitionEntity; import org.apache.ambari.server.orm.entities.MetainfoEntity; -import org.apache.ambari.server.stack.StackDirectory; import org.apache.ambari.server.stack.StackManager; import org.apache.ambari.server.stack.StackManagerFactory; import org.apache.ambari.server.state.Cluster; @@ -102,30 +100,6 @@ import com.google.inject.Singleton; */ @Singleton public class AmbariMetaInfo { - public static final String SERVICE_CONFIG_FOLDER_NAME = "configuration"; - public static final String SERVICE_PROPERTIES_FOLDER_NAME = "properties"; - public static final String SERVICE_THEMES_FOLDER_NAME = "themes"; - public static final String SERVICE_QUICKLINKS_CONFIGURATIONS_FOLDER_NAME = "quicklinks"; - public static final String SERVICE_CONFIG_FILE_NAME_POSTFIX = ".xml"; - public static final String RCO_FILE_NAME = "role_command_order.json"; - public static final String SERVICE_METRIC_FILE_NAME = "metrics.json"; - public static final String SERVICE_ALERT_FILE_NAME = "alerts.json"; - public static final String SERVICE_ADVISOR_FILE_NAME = "service_advisor.py"; - - /** - * The filename for a Kerberos descriptor file at either the stack or service level - */ - public static final String KERBEROS_DESCRIPTOR_FILE_NAME = "kerberos.json"; - - /** - * The filename for a Widgets descriptor file at either the stack or service level - */ - public static final String WIDGETS_DESCRIPTOR_FILE_NAME = "widgets.json"; - - /** - * Filename for theme file at service layer - */ - public static final String SERVICE_THEME_FILE_NAME = "theme.json"; /** * This string is used in placeholder in places that are common for @@ -136,13 +110,6 @@ public class AmbariMetaInfo { * Version of XML files with support of custom services and custom commands */ public static final String SCHEMA_VERSION_2 = "2.0"; - public static final FilenameFilter FILENAME_FILTER = new FilenameFilter() { - @Override - public boolean accept(File dir, String s) { - return !(s.equals(".svn") || s.equals(".git") || - s.equals(StackDirectory.HOOKS_FOLDER_NAME)); - } - }; private final static Logger LOG = LoggerFactory.getLogger(AmbariMetaInfo.class); /** @@ -1346,40 +1313,54 @@ public class AmbariMetaInfo { * All of the kerberos.json files from the specified stack (and version) are read, parsed and * complied into a complete Kerberos descriptor hierarchy. * - * @param stackName a String declaring the stack name - * @param stackVersion a String declaring the stack version + * @param stackName a String declaring the stack name + * @param stackVersion a String declaring the stack version + * @param includePreconfigureData a Boolean value indicating whether to include the pre-configuration + * data (<code>true</code>), or not (<code>false</code>) * @return a new complete KerberosDescriptor, or null if no Kerberos descriptor information is available * @throws AmbariException if an error occurs reading or parsing the stack's kerberos.json files */ - public KerberosDescriptor getKerberosDescriptor(String stackName, String stackVersion) throws AmbariException { + public KerberosDescriptor getKerberosDescriptor(String stackName, String stackVersion, boolean includePreconfigureData) throws AmbariException { StackInfo stackInfo = getStack(stackName, stackVersion); - String kerberosDescriptorFileLocation = stackInfo.getKerberosDescriptorFileLocation(); - KerberosDescriptor kerberosDescriptor = null; - // Read in the stack-level Kerberos descriptor - if (kerberosDescriptorFileLocation != null) { - File file = new File(kerberosDescriptorFileLocation); + // Read in the stack-level Kerberos descriptor pre-configuration data + if (includePreconfigureData) { + kerberosDescriptor = readKerberosDescriptorFromFile(stackInfo.getKerberosDescriptorPreConfigurationFileLocation()); - if (file.canRead()) { - try { - kerberosDescriptor = kerberosDescriptorFactory.createInstance(file); - } catch (IOException e) { - throw new AmbariException(String.format("Failed to parse kerberos descriptor file %s", - file.getAbsolutePath()), e); + if(kerberosDescriptor != null) { + // Ensure the all services to be pre-configured are flagged appropriately. + Map<String, KerberosServiceDescriptor> serviceDescriptors = kerberosDescriptor.getServices(); + if (serviceDescriptors != null) { + for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) { + serviceDescriptor.setPreconfigure(true); + } } - } else { - throw new AmbariException(String.format("Unable to read kerberos descriptor file %s", - file.getAbsolutePath())); } } - if (kerberosDescriptor == null) { - kerberosDescriptor = new KerberosDescriptor(); + // Read in the base stack-level Kerberos descriptor. + KerberosDescriptor stackKerberosDescriptor = readKerberosDescriptorFromFile(stackInfo.getKerberosDescriptorFileLocation()); + if(stackKerberosDescriptor == null) { + // If kerberosDescriptor is null and stackKerberosDescriptor is null, then ensure + // kerberosDescriptor is an empty KerberosDescriptor. + if(kerberosDescriptor == null) { + kerberosDescriptor = new KerberosDescriptor(); + } + } + else { + if(kerberosDescriptor == null) { + // If kerberosDescriptor is null; then set it to stackKerberosDescriptor. + kerberosDescriptor = stackKerberosDescriptor; + } + else { + // If kerberosDescriptor is not null; then update it using stackKerberosDescriptor. + kerberosDescriptor.update(stackKerberosDescriptor); + } } - // Read in the service-level Kerberos descriptors + // Read in the individual service-level Kerberos descriptors Map<String, ServiceInfo> services = getServices(stackName, stackVersion); if (services != null) { @@ -1388,6 +1369,9 @@ public class AmbariMetaInfo { if (serviceDescriptors != null) { for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors) { + // Add the service-level Kerberos descriptor to kerberosDescriptor. If the service was + // previously added (possible from the pre-configure data), update the existing service-level + // Kerberos descriptor with this one. kerberosDescriptor.putService(serviceDescriptor); } } @@ -1495,5 +1479,33 @@ public class AmbariMetaInfo { return versionDefinitions; } + /** + * Reads a Kerberos descriptor from the specified file path. + * + * @param fileLocation the path to the file + * @return a KerberosDescriptor or <code>null</code>, if no path is specified + * @throws AmbariException if an error occurs reading or parsing the Kerberos descriptor file + */ + KerberosDescriptor readKerberosDescriptorFromFile(String fileLocation) throws AmbariException { + if (!StringUtils.isEmpty(fileLocation)) { + File file = new File(fileLocation); + if (file.canRead()) { + try { + return kerberosDescriptorFactory.createInstance(file); + } catch (IOException e) { + throw new AmbariException(String.format("Failed to parse Kerberos descriptor file %s", + file.getAbsolutePath()), e); + } + } else { + throw new AmbariException(String.format("Unable to read Kerberos descriptor file %s", + file.getAbsolutePath())); + } + } + else { + LOG.debug("Missing path to Kerberos descriptor, returning null"); + } + + return null; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e6641558/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java index c655816..64180e5 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java @@ -1,4 +1,4 @@ -/** +/* * 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 @@ -20,6 +20,7 @@ package org.apache.ambari.server.api.services.stackadvisor; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -28,7 +29,6 @@ import java.util.Set; import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse; import org.apache.ambari.server.state.ChangedConfigInfo; -import org.apache.ambari.server.state.PropertyDependencyInfo; import org.apache.commons.lang.StringUtils; /** @@ -40,7 +40,7 @@ public class StackAdvisorRequest { private String stackVersion; private StackAdvisorRequestType requestType; private List<String> hosts = new ArrayList<String>(); - private List<String> services = new ArrayList<String>(); + private Collection<String> services = new ArrayList<String>(); private Map<String, Set<String>> componentHostsMap = new HashMap<String, Set<String>>(); private Map<String, Set<String>> hostComponents = new HashMap<String, Set<String>>(); private Map<String, Set<String>> hostGroupBindings = new HashMap<String, Set<String>>(); @@ -65,7 +65,7 @@ public class StackAdvisorRequest { return hosts; } - public List<String> getServices() { + public Collection<String> getServices() { return services; } @@ -143,7 +143,7 @@ public class StackAdvisorRequest { return this; } - public StackAdvisorRequestBuilder forServices(List<String> services) { + public StackAdvisorRequestBuilder forServices(Collection<String> services) { this.instance.services = services; return this; } http://git-wip-us.apache.org/repos/asf/ambari/blob/e6641558/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java index d000571..4066418 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java @@ -75,6 +75,10 @@ public interface KerberosHelper { */ String AMBARI_SERVER_KERBEROS_IDENTITY_NAME = "ambari-server"; /** + * The name of the Kerberos environment configuration type + */ + String KERBEROS_ENV = "kerberos-env"; + /** * The kerberos-env property name declaring whether Ambari should manage its own required * identities or not */ @@ -84,6 +88,32 @@ public interface KerberosHelper { * identities or not */ String MANAGE_IDENTITIES = "manage_identities"; + /** + * The kerberos-env property name declaring the default realm + */ + String DEFAULT_REALM = "realm"; + /** + * The kerberos-env property name declaring the kdc_type + * + * @see org.apache.ambari.server.serveraction.kerberos.KDCType + */ + String KDC_TYPE = "kdc_type"; + /** + * The kerberos-env property name declaring whether to manage auth-to-local rules or not + */ + String MANAGE_AUTH_TO_LOCAL_RULES = "manage_auth_to_local"; + /** + * The kerberos-env property name declaring whether auth-to-local rules should be case-insensitive or not + */ + String CASE_INSENSITIVE_USERNAME_RULES = "case_insensitive_username_rules"; + /** + * The kerberos-env property name declaring how to preprocess services. + * <p> + * Expected values are <code>"ALL"</code>, <code>"DEFAULT"</code>, <code>"NONE"</code> + * + * @see org.apache.ambari.server.serveraction.kerberos.PreconfigureServiceType + */ + String PRECONFIGURE_SERVICES = "preconfigure_services"; /** * Toggles Kerberos security to enable it or remove it depending on the state of the cluster. @@ -368,18 +398,23 @@ public interface KerberosHelper { * Sets the relevant auth-to-local rule configuration properties using the services installed on * the cluster and their relevant Kerberos descriptors to determine the rules to be created. * - * @param kerberosDescriptor the current Kerberos descriptor - * @param realm the default realm - * @param installedServices the map of services and relevant components to process - * @param existingConfigurations a map of the current configurations - * @param kerberosConfigurations a map of the configurations to update, this where the generated - * auth-to-local values will be stored + * @param cluster cluster instance + * @param kerberosDescriptor the current Kerberos descriptor + * @param realm the default realm + * @param installedServices the map of services and relevant components to process + * @param existingConfigurations a map of the current configurations + * @param kerberosConfigurations a map of the configurations to update, this where the generated + * auth-to-local values will be stored + * @param includePreconfigureData <code>true</code> to include the preconfigure data; <code>false</code> otherwise * @throws AmbariException */ - void setAuthToLocalRules(KerberosDescriptor kerberosDescriptor, String realm, + void setAuthToLocalRules(Cluster cluster, + KerberosDescriptor kerberosDescriptor, + String realm, Map<String, Set<String>> installedServices, Map<String, Map<String, String>> existingConfigurations, - Map<String, Map<String, String>> kerberosConfigurations) + Map<String, Map<String, String>> kerberosConfigurations, + boolean includePreconfigureData) throws AmbariException; /** @@ -426,12 +461,13 @@ public interface KerberosHelper { * descriptor and the composite is returned. If not, the default cluster descriptor is returned * as-is. * - * @param cluster cluster instance + * @param cluster cluster instance + * @param includePreconfigureData <code>true</code> to include the preconfigure data; <code>false</code> otherwise * @return the kerberos descriptor associated with the specified cluster * @throws AmbariException if unable to obtain the descriptor - * @see #getKerberosDescriptor(KerberosDescriptorType, Cluster, boolean, Collection) + * @see #getKerberosDescriptor(KerberosDescriptorType, Cluster, boolean, Collection, boolean) */ - KerberosDescriptor getKerberosDescriptor(Cluster cluster) throws AmbariException; + KerberosDescriptor getKerberosDescriptor(Cluster cluster, boolean includePreconfigureData) throws AmbariException; /** * Gets the requested Kerberos descriptor. @@ -445,39 +481,63 @@ public interface KerberosHelper { * <dd>A Kerberos descriptor built using user-specified data stored as an artifact of the cluster, only</dd> * <dt>{@link KerberosDescriptorType#COMPOSITE}</dt> * <dd>A Kerberos descriptor built using data from the current stack definition with user-specified data stored as an artifact of the cluster applied - * - see {@link #getKerberosDescriptor(Cluster)}</dd> + * - see {@link #getKerberosDescriptor(Cluster, boolean)}</dd> * </dl> * - * @param kerberosDescriptorType the type of Kerberos descriptor to retrieve - see {@link KerberosDescriptorType} - * @param cluster the relevant Cluster - * @param evaluateWhenClauses true to evaluate Kerberos identity <code>when</code> clauses and - * prune if necessary; false otherwise. - * @param additionalServices an optional collection of service names declaring additional - * services to add to the set of currently installed services to use - * while evaluating <code>when</code> clauses + * @param kerberosDescriptorType the type of Kerberos descriptor to retrieve - see {@link KerberosDescriptorType} + * @param cluster the relevant Cluster + * @param evaluateWhenClauses true to evaluate Kerberos identity <code>when</code> clauses and + * prune if necessary; false otherwise. + * @param additionalServices an optional collection of service names declaring additional + * services to add to the set of currently installed services to use + * while evaluating <code>when</code> clauses + * @param includePreconfigureData <code>true</code> to include the preconfigure data; <code>false</code> otherwise * @return a Kerberos descriptor * @throws AmbariException */ KerberosDescriptor getKerberosDescriptor(KerberosDescriptorType kerberosDescriptorType, Cluster cluster, - boolean evaluateWhenClauses, Collection<String> additionalServices) + boolean evaluateWhenClauses, Collection<String> additionalServices, + boolean includePreconfigureData) throws AmbariException; /** - * Merges configuration from a Map of configuration updates into a main configurations Map. Each - * property in the updates Map is processed to replace variables using the replacement Map. + * Merges configurations from a Map of configuration updates into a main configurations Map. + * <p> + * Each property in the updates Map is processed to replace variables using the replacement Map, + * unless it has been filtered out using an optionally supplied configuration type filter + * (configurationTypeFilter). * <p/> * See {@link org.apache.ambari.server.state.kerberos.VariableReplacementHelper#replaceVariables(String, java.util.Map)} * for information on variable replacement. * - * @param configurations a Map of configurations - * @param updates a Map of configuration updates - * @param replacements a Map of (grouped) replacement values - * @return the merged Map - * @throws AmbariException + * @param configurations a Map of existing configurations (updated in-place) + * @param updates a Map of configuration updates + * @param replacements a Map of (grouped) replacement values + * @param configurationTypeFilter a Set of config types to filter from the map of updates; + * <code>null</code> indicate no filter + * @return the updated configurations map + * @throws AmbariException if an issue occurs */ Map<String, Map<String, String>> mergeConfigurations(Map<String, Map<String, String>> configurations, Map<String, KerberosConfigurationDescriptor> updates, - Map<String, Map<String, String>> replacements) + Map<String, Map<String, String>> replacements, + Set<String> configurationTypeFilter) + throws AmbariException; + + /** + * Determines which services, not currently installed, should be preconfigured to aid in reducing + * the number of service restarts when new services are added to a cluster where Kerberos is enabled. + * + * @param configurations a Map of existing configurations (updated in-place) + * @param replacements a Map of (grouped) replacement values + * @param cluster the cluster + * @param kerberosDescriptor the Kerberos Descriptor + * @return the updated configurations map + * @throws AmbariException if an issue occurs + */ + Map<String, Map<String, String>> processPreconfiguredServiceConfigurations(Map<String, Map<String, String>> configurations, + Map<String, Map<String, String>> replacements, Cluster cluster, + KerberosDescriptor kerberosDescriptor) throws AmbariException; /** http://git-wip-us.apache.org/repos/asf/ambari/blob/e6641558/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java index 86ddc5f..73e9ec2 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -35,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import java.util.regex.Matcher; import org.apache.ambari.server.AmbariException; @@ -46,6 +48,7 @@ import org.apache.ambari.server.actionmanager.RequestFactory; import org.apache.ambari.server.actionmanager.Stage; import org.apache.ambari.server.actionmanager.StageFactory; import org.apache.ambari.server.api.services.AmbariMetaInfo; +import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException; import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorHelper; import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest; import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse; @@ -89,15 +92,18 @@ import org.apache.ambari.server.stageplanner.RoleGraph; import org.apache.ambari.server.stageplanner.RoleGraphFactory; import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.ComponentInfo; import org.apache.ambari.server.state.Config; import org.apache.ambari.server.state.ConfigHelper; import org.apache.ambari.server.state.Host; import org.apache.ambari.server.state.HostState; +import org.apache.ambari.server.state.PropertyInfo; import org.apache.ambari.server.state.SecurityState; import org.apache.ambari.server.state.SecurityType; import org.apache.ambari.server.state.Service; import org.apache.ambari.server.state.ServiceComponent; import org.apache.ambari.server.state.ServiceComponentHost; +import org.apache.ambari.server.state.ServiceInfo; import org.apache.ambari.server.state.StackId; import org.apache.ambari.server.state.State; import org.apache.ambari.server.state.ValueAttributesInfo; @@ -297,45 +303,43 @@ public class KerberosHelperImpl implements KerberosHelper { @Override public void configureServices(Cluster cluster, Map<String, Collection<String>> serviceFilter) throws AmbariException, KerberosInvalidConfigurationException { - Map<String, Map<String, String>> existingConfigurations = calculateExistingConfigurations(cluster, null); - Map<String, Set<String>> installedServices = new HashMap<>(); - Set<String> previouslyExistingServices = new HashSet<>(); - - // Calculate the map of installed services to installed components - Map<String, Service> clusterServices = cluster.getServices(); - if(clusterServices != null) { - for (Service clusterService : clusterServices.values()) { - Set<String> installedComponents = installedServices.get(clusterService.getName()); - if (installedComponents == null) { - installedComponents = new HashSet<>(); - installedServices.put(clusterService.getName(), installedComponents); - } + final Map<String, Set<String>> installedServices = new HashMap<>(); + final Set<String> previouslyExistingServices = new HashSet<>(); - Map<String, ServiceComponent> clusterServiceComponents = clusterService.getServiceComponents(); - if (clusterServiceComponents != null) { - for (ServiceComponent clusterServiceComponent : clusterServiceComponents.values()) { - installedComponents.add(clusterServiceComponent.getName()); - - // Determine if this component was PREVIOUSLY installed, which implies that its containing service was PREVIOUSLY installed - if (!previouslyExistingServices.contains(clusterService.getName())) { - Map<String, ServiceComponentHost> clusterServiceComponentHosts = clusterServiceComponent.getServiceComponentHosts(); - if (clusterServiceComponentHosts != null) { - for (ServiceComponentHost clusterServiceComponentHost : clusterServiceComponentHosts.values()) { - if (PREVIOUSLY_INSTALLED_STATES.contains(clusterServiceComponentHost.getState())) { - previouslyExistingServices.add(clusterService.getName()); - break; - } - } + // Calculate the map of installed services to installed components. + // We can create the map in the "shouldIncludeCommand" Command to avoid having to iterate + // over the returned ServiceComponentHost List. + getServiceComponentHosts(cluster, + new Command<Boolean, ServiceComponentHost>() { + @Override + public Boolean invoke(ServiceComponentHost sch) throws AmbariException { + if (sch != null) { + String serviceName = sch.getServiceName(); + + Set<String> installedComponents = installedServices.get(serviceName); + if (installedComponents == null) { + installedComponents = new HashSet<>(); + installedServices.put(serviceName, installedComponents); + } + installedComponents.add(sch.getServiceComponentName()); + + // Determine if this component was PREVIOUSLY installed, which implies that its containing service was PREVIOUSLY installed + if (!previouslyExistingServices.contains(serviceName) && PREVIOUSLY_INSTALLED_STATES.contains(sch.getState())) { + previouslyExistingServices.add(serviceName); } + + return true; } + + return false; } - } - } - } + }); + Map<String, Map<String, String>> existingConfigurations = calculateExistingConfigurations(cluster, null); Map<String, Map<String, String>> updates = getServiceConfigurationUpdates(cluster, existingConfigurations, installedServices, serviceFilter, previouslyExistingServices, true, true); + // Store the updates... for (Map.Entry<String, Map<String, String>> entry : updates.entrySet()) { configHelper.updateConfigType(cluster, cluster.getDesiredStackVersion(), ambariManagementController, entry.getKey(), entry.getValue(), null, @@ -355,7 +359,7 @@ public class KerberosHelperImpl implements KerberosHelper { Map<String, Map<String, String>> kerberosConfigurations = new HashMap<>(); KerberosDetails kerberosDetails = getKerberosDetails(cluster, null); - KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster); + KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); Map<String, String> kerberosDescriptorProperties = kerberosDescriptor.getProperties(); Map<String, Map<String, String>> configurations = addAdditionalConfigurations(cluster, @@ -363,10 +367,10 @@ public class KerberosHelperImpl implements KerberosHelper { Map<String, Set<String>> propertiesToIgnore = new HashMap<>(); - // If Ambari is managing it own identities then add AMBARI to the set of installed servcie so + // If Ambari is managing it own identities then add AMBARI to the set of installed service so // that its Kerberos descriptor entries will be included. - if (createAmbariIdentities(existingConfigurations.get("kerberos-env"))) { - installedServices = new HashMap<>(installedServices); + if (createAmbariIdentities(existingConfigurations.get(KERBEROS_ENV))) { + installedServices = new HashMap<String, Set<String>>(installedServices); installedServices.put("AMBARI", Collections.singleton("AMBARI_SERVER")); } @@ -403,7 +407,7 @@ public class KerberosHelperImpl implements KerberosHelper { processIdentityConfigurations(identityConfigurations, kerberosConfigurations, configurations, propertiesToIgnore); mergeConfigurations(kerberosConfigurations, - componentDescriptor.getConfigurations(!servicePreviouslyExisted), configurations); + componentDescriptor.getConfigurations(!servicePreviouslyExisted), configurations, null); } } } @@ -412,7 +416,7 @@ public class KerberosHelperImpl implements KerberosHelper { } } - setAuthToLocalRules(kerberosDescriptor, kerberosDetails.getDefaultRealm(), installedServices, configurations, kerberosConfigurations); + setAuthToLocalRules(cluster, kerberosDescriptor, kerberosDetails.getDefaultRealm(), installedServices, configurations, kerberosConfigurations, false); return (applyStackAdvisorUpdates) ? applyStackAdvisorUpdates(cluster, installedServices.keySet(), configurations, kerberosConfigurations, propertiesToIgnore, @@ -420,6 +424,126 @@ public class KerberosHelperImpl implements KerberosHelper { : kerberosConfigurations; } + /** + * Adds host assignments, recommended by the Stack Advisor, to the configuration map (clusterHostInfo) + * for the components specified in the component filter <code>componentFilter</code> (or all if the + * component filter is <code>null</code>). + * + * @param cluster the cluster + * @param services the relevant services to consider + * @param componentFilter the set of components to add to the clusterHostInfo structure + * @param configurations the configurations map to update + * @throws AmbariException if an error occurs + */ + private void applyStackAdvisorHostRecommendations(Cluster cluster, + Set<String> services, + Set<String> componentFilter, + Map<String, Map<String, String>> configurations) + throws AmbariException { + StackId stackVersion = cluster.getCurrentStackVersion(); + List<String> hostNames = new ArrayList<>(); + + Collection<Host> hosts = cluster.getHosts(); + if (hosts != null) { + for (Host host : hosts) { + hostNames.add(host.getHostName()); + } + } + + StackAdvisorRequest request = StackAdvisorRequest.StackAdvisorRequestBuilder + .forStack(stackVersion.getStackName(), stackVersion.getStackVersion()) + .forServices(services) + .forHosts(hostNames) + .withComponentHostsMap(cluster.getServiceComponentHostMap(null, services)) + .ofType(StackAdvisorRequest.StackAdvisorRequestType.HOST_GROUPS) + .build(); + + try { + RecommendationResponse response = stackAdvisorHelper.recommend(request); + + RecommendationResponse.Recommendation recommendation = (response == null) ? null : response.getRecommendations(); + RecommendationResponse.Blueprint blueprint = (recommendation == null) ? null : recommendation.getBlueprint(); + Set<RecommendationResponse.HostGroup> hostGroups = (blueprint == null) ? null : blueprint.getHostGroups(); + + + if (hostGroups != null) { + RecommendationResponse.BlueprintClusterBinding blueprintBinding = recommendation.getBlueprintClusterBinding(); + Map<String, RecommendationResponse.BindingHostGroup> bindingMap = new HashMap<>(); + if (blueprintBinding != null) { + Set<RecommendationResponse.BindingHostGroup> bindingHostGroups = blueprintBinding.getHostGroups(); + if (bindingHostGroups != null) { + for (RecommendationResponse.BindingHostGroup bindingHostGroup : bindingHostGroups) { + bindingMap.put(bindingHostGroup.getName(), bindingHostGroup); + } + } + } + + // Get (and created if needed) the clusterHostInfo map + Map<String, String> clusterHostInfoMap = configurations.get("clusterHostInfo"); + if (clusterHostInfoMap == null) { + clusterHostInfoMap = new HashMap<>(); + configurations.put("clusterHostInfo", clusterHostInfoMap); + } + + Map<String, String> componentToClusterInfoMap = StageUtils.getComponentToClusterInfoKeyMap(); + + // Iterate through the recommendations to find the recommended host assignments + for (RecommendationResponse.HostGroup hostGroup : hostGroups) { + Set<Map<String, String>> components = hostGroup.getComponents(); + + if (components != null) { + RecommendationResponse.BindingHostGroup binding = bindingMap.get(hostGroup.getName()); + + if (binding != null) { + Set<Map<String, String>> hostGroupHosts = binding.getHosts(); + + if (hostGroupHosts != null) { + for (Map<String, String> component : components) { + String componentName = component.get("name"); + + // If the component filter is null or the current component is found in the filter, + // include it in the map + if ((componentFilter == null) || componentFilter.contains(componentName)) { + String key = componentToClusterInfoMap.get(componentName); + + if(StringUtils.isEmpty(key)) { + // If not found in the componentToClusterInfoMap, then keys are assumed to be + // in the form of <component_name>_hosts (lowercase) + key = componentName.toLowerCase() + "_hosts"; + } + + Set<String> fqdns = new TreeSet<>(); + + // Values are a comma-delimited list of hosts. + // If a value exists, split it and add the tokens to the set + if (!StringUtils.isEmpty(clusterHostInfoMap.get(key))) { + fqdns.addAll(Arrays.asList(clusterHostInfoMap.get(key).split(","))); + } + + // Add the set of hosts for the current host group + for (Map<String, String> hostGroupHost : hostGroupHosts) { + String fqdn = hostGroupHost.get("fqdn"); + + if (!StringUtils.isEmpty(fqdn)) { + fqdns.add(fqdn); + } + } + + // create the comma-delimited list of hosts + clusterHostInfoMap.put(key, StringUtils.join(fqdns, ',')); + } + } + } + } + } + } + } + } catch (StackAdvisorException e) { + LOG.error("Failed to obtain the recommended host groups for the preconfigured components.", e); + throw new AmbariException(e.getMessage(), e); + } + } + @Override public Map<String, Map<String, String>> applyStackAdvisorUpdates(Cluster cluster, Set<String> services, Map<String, Map<String, String>> existingConfigurations, @@ -489,9 +613,16 @@ public class KerberosHelperImpl implements KerberosHelper { } Set<StackId> visitedStacks = new HashSet<>(); + Map<String, Service> installedServices = cluster.getServices(); for (String serviceName : services) { - Service service = cluster.getService(serviceName); + Service service = installedServices.get(serviceName); + + // Skip services that are not really installed + if(service == null) { + continue; + } + StackId stackId = service.getDesiredStackId(); if (visitedStacks.contains(stackId)) { @@ -500,7 +631,7 @@ public class KerberosHelperImpl implements KerberosHelper { StackAdvisorRequest request = StackAdvisorRequest.StackAdvisorRequestBuilder .forStack(stackId.getStackName(), stackId.getStackVersion()) - .forServices(new ArrayList<>(services)) + .forServices(services) .forHosts(hostNames) .withComponentHostsMap(cluster.getServiceComponentHostMap(null, services)) .withConfigurations(requestConfigurations) @@ -568,7 +699,7 @@ public class KerberosHelperImpl implements KerberosHelper { "\n\tConfigType: {}\n\tProperty: {}\n\tValue: {}", configType, propertyName, recommendedValue); - if(kerberosConfigProperties == null) { + if (kerberosConfigProperties == null) { kerberosConfigProperties = new HashMap<>(); kerberosConfigurations.put(configType, kerberosConfigProperties); } @@ -644,7 +775,7 @@ public class KerberosHelperImpl implements KerberosHelper { // Only perform this task if Ambari manages Kerberos identities if (kerberosDetails.manageIdentities()) { - KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster); + KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); Map<String, String> kerberosDescriptorProperties = kerberosDescriptor.getProperties(); Map<String, Map<String, String>> configurations = addAdditionalConfigurations(cluster, @@ -868,16 +999,19 @@ public class KerberosHelperImpl implements KerberosHelper { } @Override - public void setAuthToLocalRules(KerberosDescriptor kerberosDescriptor, String realm, + public void setAuthToLocalRules(Cluster cluster, + KerberosDescriptor kerberosDescriptor, + String realm, Map<String, Set<String>> installedServices, Map<String, Map<String, String>> existingConfigurations, - Map<String, Map<String, String>> kerberosConfigurations) + Map<String, Map<String, String>> kerberosConfigurations, + boolean includePreconfigureData) throws AmbariException { boolean processAuthToLocalRules = true; - Map<String, String> kerberosEnvProperties = existingConfigurations.get("kerberos-env"); - if (kerberosEnvProperties.containsKey("manage_auth_to_local")) { - processAuthToLocalRules = Boolean.valueOf(kerberosEnvProperties.get("manage_auth_to_local")); + Map<String, String> kerberosEnvProperties = existingConfigurations.get(KERBEROS_ENV); + if (kerberosEnvProperties.containsKey(MANAGE_AUTH_TO_LOCAL_RULES)) { + processAuthToLocalRules = Boolean.valueOf(kerberosEnvProperties.get(MANAGE_AUTH_TO_LOCAL_RULES)); } if (kerberosDescriptor != null && processAuthToLocalRules) { @@ -887,7 +1021,7 @@ public class KerberosHelperImpl implements KerberosHelper { // a flag to be used by the AuthToLocalBuilder marking whether the default realm rule should contain the //L option, indicating username case insensitive behaviour // the 'kerberos-env' structure is expected to be available here as it was previously validated - boolean caseInsensitiveUser = Boolean.valueOf(existingConfigurations.get("kerberos-env").get("case_insensitive_username_rules")); + boolean caseInsensitiveUser = Boolean.valueOf(existingConfigurations.get(KERBEROS_ENV).get(CASE_INSENSITIVE_USERNAME_RULES)); // Additional realms that need to be handled according to the Kerberos Descriptor String additionalRealms = kerberosDescriptor.getProperty("additional_realms"); @@ -897,43 +1031,69 @@ public class KerberosHelperImpl implements KerberosHelper { filterContext.put("configurations", existingConfigurations); filterContext.put("services", installedServices.keySet()); - // Determine which properties need to be set AuthToLocalBuilder authToLocalBuilder = new AuthToLocalBuilder(realm, additionalRealms, caseInsensitiveUser); - addIdentities(authToLocalBuilder, kerberosDescriptor.getIdentities(true, filterContext), null, existingConfigurations); + // Add in the default configurations for the services that need to be preconfigured. These + // configurations may be needed while calculating the auth-to-local rules. + Map<String, Map<String, String>> replacements = (includePreconfigureData) + ? addConfigurationsForPreProcessedServices(deepCopy(existingConfigurations), cluster, kerberosDescriptor, false) + : existingConfigurations; + + // Process top-level identities + addIdentities(authToLocalBuilder, kerberosDescriptor.getIdentities(true, filterContext), null, replacements); + + // Determine which properties need to be set authToLocalProperties = kerberosDescriptor.getAuthToLocalProperties(); if (authToLocalProperties != null) { authToLocalPropertiesToSet.addAll(authToLocalProperties); } - for(Map.Entry<String, Set<String>> installedService: installedServices.entrySet()) { - String serviceName = installedService.getKey(); + // Iterate through the services in the Kerberos descriptor. If a found service is installed + // or marked to be preconfigured, add the relevant data to the auth-to-local rules. + Map<String, KerberosServiceDescriptor> serviceDescriptors = kerberosDescriptor.getServices(); + if (serviceDescriptors != null) { + for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) { + String serviceName = serviceDescriptor.getName(); + boolean preconfigure = includePreconfigureData && serviceDescriptor.shouldPreconfigure(); + boolean explicitlyAdded = installedServices.containsKey(serviceName); + + // Add this service's identities if we are implicitly preconfigurring the service or if the + // service has been explicitly added to the cluster + if (preconfigure || explicitlyAdded) { + LOG.info("Adding identities for service {} to auth to local mapping [{}]", + serviceName, + (explicitlyAdded) ? "explicit" : "preconfigured"); - KerberosServiceDescriptor serviceDescriptor = kerberosDescriptor.getService(serviceName); - if(serviceDescriptor != null) { - LOG.info("Adding identities for service {} to auth to local mapping", installedService); + // Process the service-level Kerberos descriptor + addIdentities(authToLocalBuilder, serviceDescriptor.getIdentities(true, filterContext), null, replacements); - // Process the service-level Kerberos descriptor - addIdentities(authToLocalBuilder, serviceDescriptor.getIdentities(true, filterContext), null, existingConfigurations); + authToLocalProperties = serviceDescriptor.getAuthToLocalProperties(); + if (authToLocalProperties != null) { + authToLocalPropertiesToSet.addAll(authToLocalProperties); + } - authToLocalProperties = serviceDescriptor.getAuthToLocalProperties(); - if (authToLocalProperties != null) { - authToLocalPropertiesToSet.addAll(authToLocalProperties); - } + // Process the relevant component-level Kerberos descriptors + Map<String, KerberosComponentDescriptor> componentDescriptors = serviceDescriptor.getComponents(); + if (componentDescriptors != null) { + Set<String> installedServiceComponents = installedServices.get(serviceName); + // Ensure installedComponents is not null.... + if (installedServiceComponents == null) { + installedServiceComponents = Collections.emptySet(); + } - // Process the relevant component-level Kerberos descriptors - Set<String> installedComponents = installedService.getValue(); - if(installedComponents != null) { - for (String installedComponent : installedComponents) { - KerberosComponentDescriptor componentDescriptor = serviceDescriptor.getComponent(installedComponent); + for (KerberosComponentDescriptor componentDescriptor : componentDescriptors.values()) { + String componentName = componentDescriptor.getName(); - if (componentDescriptor != null) { - LOG.info("Adding identities for component {} to auth to local mapping", installedComponent); - addIdentities(authToLocalBuilder, componentDescriptor.getIdentities(true, filterContext), null, existingConfigurations); + // Add this component's identities if we are implicitly preconfiguring the parent + // service or if the component has been explicitly added to the cluster + if (preconfigure || (installedServiceComponents.contains(componentName))) { + LOG.info("Adding identities for component {} to auth to local mapping", componentName); + addIdentities(authToLocalBuilder, componentDescriptor.getIdentities(true, filterContext), null, replacements); - authToLocalProperties = componentDescriptor.getAuthToLocalProperties(); - if (authToLocalProperties != null) { - authToLocalPropertiesToSet.addAll(authToLocalProperties); + authToLocalProperties = componentDescriptor.getAuthToLocalProperties(); + if (authToLocalProperties != null) { + authToLocalPropertiesToSet.addAll(authToLocalProperties); + } } } } @@ -986,56 +1146,77 @@ public class KerberosHelperImpl implements KerberosHelper { @Override - public List<ServiceComponentHost> getServiceComponentHostsToProcess(Cluster cluster, - KerberosDescriptor kerberosDescriptor, - Map<String, ? extends Collection<String>> serviceComponentFilter, - Collection<String> hostFilter, Collection<String> identityFilter, - Command<Boolean, ServiceComponentHost> shouldProcessCommand) + public List<ServiceComponentHost> getServiceComponentHostsToProcess(final Cluster cluster, + final KerberosDescriptor kerberosDescriptor, + final Map<String, ? extends Collection<String>> serviceComponentFilter, + final Collection<String> hostFilter, Collection<String> identityFilter, + final Command<Boolean, ServiceComponentHost> shouldProcessCommand) throws AmbariException { - List<ServiceComponentHost> serviceComponentHostsToProcess = new ArrayList<>(); - Map<String, Service> services = cluster.getServices(); - - if ((services != null) && !services.isEmpty()) { - Collection<Host> hosts = cluster.getHosts(); - - if ((hosts != null) && !hosts.isEmpty()) { - // Iterate over the hosts in the cluster to find the components installed in each. For each - // component (aka service component host - sch) determine the configuration updates and - // and the principals an keytabs to create. - for (Host host : hosts) { - String hostname = host.getHostName(); - - // Filter hosts as needed.... - if ((hostFilter == null) || hostFilter.contains(hostname)) { - // Get a list of components on the current host - List<ServiceComponentHost> serviceComponentHosts = cluster.getServiceComponentHosts(hostname); - - if ((serviceComponentHosts != null) && !serviceComponentHosts.isEmpty()) { - - // Iterate over the components installed on the current host to get the service and - // component-level Kerberos descriptors in order to determine which principals, - // keytab files, and configurations need to be created or updated. - for (ServiceComponentHost sch : serviceComponentHosts) { - String serviceName = sch.getServiceName(); - String componentName = sch.getServiceComponentName(); - - // If there is no filter or the filter contains the current service name... - if ((serviceComponentFilter == null) || serviceComponentFilter.containsKey(serviceName)) { - Collection<String> componentFilter = (serviceComponentFilter == null) ? null : serviceComponentFilter.get(serviceName); - KerberosServiceDescriptor serviceDescriptor = kerberosDescriptor.getService(serviceName); - - if (serviceDescriptor != null) { - // If there is no filter or the filter contains the current component name, - // test to see if this component should be processed by querying the handler... - if (((componentFilter == null) || componentFilter.contains(componentName)) && shouldProcessCommand.invoke(sch)) { - serviceComponentHostsToProcess.add(sch); - } - } - } + + return getServiceComponentHosts(cluster, new Command<Boolean, ServiceComponentHost>() { + @Override + public Boolean invoke(ServiceComponentHost sch) throws AmbariException { + if (sch != null) { + // Check the host filter + if ((hostFilter == null) || hostFilter.contains(sch.getHostName())) { + String serviceName = sch.getServiceName(); + + // Check the service filter + if ((serviceComponentFilter == null) || serviceComponentFilter.containsKey(serviceName)) { + KerberosServiceDescriptor serviceDescriptor = kerberosDescriptor.getService(serviceName); + + if (serviceDescriptor != null) { + Collection<String> componentFilter = (serviceComponentFilter == null) ? null : serviceComponentFilter.get(serviceName); + + // Check the service/component filter and the shouldProcessCommand + return (((componentFilter == null) || componentFilter.contains(sch.getServiceComponentName())) && + ((shouldProcessCommand == null) || shouldProcessCommand.invoke(sch))); } } } } + + return false; + } + }); + } + + /** + * Find the {@link ServiceComponentHost}s for the cluster, filtering using the + * supplied "should include" command (<code>shouldIncludeCommand</code>). + * <p> + * If <code>shouldIncludeCommand</code> is <code>null/code>, no filtering will be performed causing + * all found {@link ServiceComponentHost}s to be returned. + * + * @param cluster the cluster + * @param shouldIncludeCommand the filtering logic + * @return a list of (filtered) {@link ServiceComponentHost}s + * @throws AmbariException if an error occurs + */ + private List<ServiceComponentHost> getServiceComponentHosts(Cluster cluster, + Command<Boolean, ServiceComponentHost> shouldIncludeCommand) + throws AmbariException { + List<ServiceComponentHost> serviceComponentHostsToProcess = new ArrayList<>(); + // Get the hosts in the cluster + Collection<Host> hosts = cluster.getHosts(); + + if ((hosts != null) && !hosts.isEmpty()) { + // Iterate over the hosts in the cluster to find the components installed in each. + for (Host host : hosts) { + String hostname = host.getHostName(); + + // Get a list of components on the current host + List<ServiceComponentHost> serviceComponentHosts = cluster.getServiceComponentHosts(hostname); + + if ((serviceComponentHosts != null) && !serviceComponentHosts.isEmpty()) { + // Iterate over the components installed on the current host and execute the shouldIncludeCommand + // Command (if supplied) to get the desired ServiceComponentHost instances. + for (ServiceComponentHost sch : serviceComponentHosts) { + if ((shouldIncludeCommand == null) || shouldIncludeCommand.invoke(sch)) { + serviceComponentHostsToProcess.add(sch); + } + } + } } } @@ -1061,41 +1242,30 @@ public class KerberosHelperImpl implements KerberosHelper { } @Override - public KerberosDescriptor getKerberosDescriptor(Cluster cluster) throws AmbariException { - return getKerberosDescriptor(KerberosDescriptorType.COMPOSITE, cluster, false, null); + public KerberosDescriptor getKerberosDescriptor(Cluster cluster, boolean includePreconfigureData) throws AmbariException { + return getKerberosDescriptor(KerberosDescriptorType.COMPOSITE, cluster, false, null, includePreconfigureData); } @Override public KerberosDescriptor getKerberosDescriptor(KerberosDescriptorType kerberosDescriptorType, Cluster cluster, - boolean evaluateWhenClauses, Collection<String> additionalServices) + boolean evaluateWhenClauses, Collection<String> additionalServices, + boolean includePreconfigureData) throws AmbariException { - KerberosDescriptor kerberosDescriptor; KerberosDescriptor stackDescriptor = (kerberosDescriptorType == KerberosDescriptorType.STACK || kerberosDescriptorType == KerberosDescriptorType.COMPOSITE) - ? getKerberosDescriptorFromStack(cluster) + ? getKerberosDescriptorFromStack(cluster, includePreconfigureData) : null; KerberosDescriptor userDescriptor = (kerberosDescriptorType == KerberosDescriptorType.USER || kerberosDescriptorType == KerberosDescriptorType.COMPOSITE) ? getKerberosDescriptorUpdates(cluster) : null; - if (stackDescriptor == null) { - if (userDescriptor == null) { - return new KerberosDescriptor(); // return an empty Kerberos descriptor since we have no data - } else { - kerberosDescriptor = userDescriptor; - } - } else { - if (userDescriptor != null) { - stackDescriptor.update(userDescriptor); - } - kerberosDescriptor = stackDescriptor; - } + KerberosDescriptor kerberosDescriptor = combineKerberosDescriptors(stackDescriptor, userDescriptor); if (evaluateWhenClauses) { Set<String> services = new HashSet<>(cluster.getServices().keySet()); - if(additionalServices != null) { + if (additionalServices != null) { services.addAll(additionalServices); } @@ -1141,7 +1311,8 @@ public class KerberosHelperImpl implements KerberosHelper { @Override public Map<String, Map<String, String>> mergeConfigurations(Map<String, Map<String, String>> configurations, Map<String, KerberosConfigurationDescriptor> updates, - Map<String, Map<String, String>> replacements) + Map<String, Map<String, String>> replacements, + Set<String> configurationTypeFilter) throws AmbariException { if ((updates != null) && !updates.isEmpty()) { @@ -1151,11 +1322,59 @@ public class KerberosHelperImpl implements KerberosHelper { for (Map.Entry<String, KerberosConfigurationDescriptor> entry : updates.entrySet()) { String type = entry.getKey(); - KerberosConfigurationDescriptor configurationDescriptor = entry.getValue(); - if (configurationDescriptor != null) { - Map<String, String> updatedProperties = configurationDescriptor.getProperties(); - mergeConfigurations(configurations, type, updatedProperties, replacements); + if ((configurationTypeFilter == null) || (configurationTypeFilter.contains(type))) { + KerberosConfigurationDescriptor configurationDescriptor = entry.getValue(); + + if (configurationDescriptor != null) { + Map<String, String> updatedProperties = configurationDescriptor.getProperties(); + mergeConfigurations(configurations, type, updatedProperties, replacements); + } + } + } + } + + return configurations; + } + + @Override + public Map<String, Map<String, String>> processPreconfiguredServiceConfigurations(Map<String, Map<String, String>> configurations, + Map<String, Map<String, String>> replacements, + Cluster cluster, + KerberosDescriptor kerberosDescriptor) + throws AmbariException { + + // Ensure the Kerberos descriptor exists.... + if (kerberosDescriptor == null) { + kerberosDescriptor = getKerberosDescriptor(cluster, true); + } + + Map<String, KerberosServiceDescriptor> serviceDescriptors = kerberosDescriptor.getServices(); + + if (serviceDescriptors != null) { + if (configurations == null) { + configurations = new HashMap<>(); + } + + // Add in the default configurations for the services that need to be preconfigured. These + // configurations may be needed while calculating the auth-to-local rules. + Map<String, Map<String, String>> replacementsWithDefaults = addConfigurationsForPreProcessedServices(deepCopy(replacements), cluster, kerberosDescriptor, true); + + Map<String, Service> existingServices = cluster.getServices(); + + for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptors.values()) { + String serviceName = serviceDescriptor.getName(); + boolean shouldPreconfigure = serviceDescriptor.shouldPreconfigure(); + + if (!existingServices.containsKey(serviceName) && shouldPreconfigure) { + configurations = mergeConfigurations(configurations, serviceDescriptor.getConfigurations(), replacementsWithDefaults, replacements.keySet()); + + Map<String, KerberosComponentDescriptor> componentDescriptors = serviceDescriptor.getComponents(); + if (componentDescriptors != null) { + for (KerberosComponentDescriptor componentDescriptor : componentDescriptors.values()) { + configurations = mergeConfigurations(configurations, componentDescriptor.getConfigurations(), replacementsWithDefaults, replacements.keySet()); + } + } } } } @@ -1239,16 +1458,15 @@ public class KerberosHelperImpl implements KerberosHelper { @Override public Map<String, Map<String, String>> calculateConfigurations(Cluster cluster, String hostname, Map<String, String> kerberosDescriptorProperties) - throws AmbariException - { + throws AmbariException { return addAdditionalConfigurations(cluster, - calculateExistingConfigurations(cluster, hostname), - hostname, kerberosDescriptorProperties); + calculateExistingConfigurations(cluster, hostname), + hostname, kerberosDescriptorProperties); } private Map<String, String> principalNames(Cluster cluster, Map<String, Map<String, String>> configuration) throws AmbariException { Map<String, String> result = new HashMap<>(); - for (Map.Entry<String, String> each : getKerberosDescriptor(cluster).principals().entrySet()) { + for (Map.Entry<String, String> each : getKerberosDescriptor(cluster, false).principals().entrySet()) { result.put(each.getKey(), variableReplacementHelper.replaceVariables(each.getValue(), configuration)); } return result; @@ -1276,12 +1494,11 @@ public class KerberosHelperImpl implements KerberosHelper { // Only calculate the active identities if the kerberos-env configurtaion is available. Else // important information like the realm will be missing (kerberos-env/realm) - Config kerberosEnvConfig = cluster.getDesiredConfigByType("kerberos-env"); - if(kerberosEnvConfig == null) { + Config kerberosEnvConfig = cluster.getDesiredConfigByType(KERBEROS_ENV); + if (kerberosEnvConfig == null) { LOG.debug("Calculating the active identities for {} is being skipped since the kerberos-env configuration is not available", clusterName, cluster.getSecurityType().name(), SecurityType.KERBEROS.name()); - } - else { + } else { Collection<String> hosts; String ambariServerHostname = StageUtils.getHostName(); @@ -1304,7 +1521,7 @@ public class KerberosHelperImpl implements KerberosHelper { } if (!hosts.isEmpty()) { - KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster); + KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); if (kerberosDescriptor != null) { Map<String, String> kerberosDescriptorProperties = kerberosDescriptor.getProperties(); @@ -1331,7 +1548,7 @@ public class KerberosHelperImpl implements KerberosHelper { if (hostname.equals(ambariServerHostname)) { // Determine if we should _calculate_ the Ambari service identities. // If kerberos-env/create_ambari_principal is not set to false the identity should be calculated. - if(createAmbariIdentities(kerberosEnvConfig.getProperties())) { + if (createAmbariIdentities(kerberosEnvConfig.getProperties())) { List<KerberosIdentityDescriptor> ambariIdentities = getAmbariServerIdentities(kerberosDescriptor); if (ambariIdentities != null) { identities.addAll(ambariIdentities); @@ -1664,7 +1881,7 @@ public class KerberosHelperImpl implements KerberosHelper { final Handler handler) throws AmbariException, KerberosOperationException { - final KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster); + final KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); final SecurityState desiredSecurityState = handler.getNewServiceSecurityState(); List<ServiceComponentHost> schToProcess = getServiceComponentHostsToProcess( @@ -1809,7 +2026,7 @@ public class KerberosHelperImpl implements KerberosHelper { } List<ServiceComponentHost> serviceComponentHostsToProcess = new ArrayList<>(); - KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster); + KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); KerberosIdentityDataFileWriter kerberosIdentityDataFileWriter = null; Map<String, String> kerberosDescriptorProperties = kerberosDescriptor.getProperties(); @@ -1990,7 +2207,7 @@ public class KerberosHelperImpl implements KerberosHelper { throw new AmbariException(message); } - Config configKerberosEnv = cluster.getDesiredConfigByType("kerberos-env"); + Config configKerberosEnv = cluster.getDesiredConfigByType(KERBEROS_ENV); if (configKerberosEnv == null) { String message = "The 'kerberos-env' configuration is not available"; LOG.error(message); @@ -2005,14 +2222,14 @@ public class KerberosHelperImpl implements KerberosHelper { } kerberosDetails.setSecurityType(cluster.getSecurityType()); - kerberosDetails.setDefaultRealm(kerberosEnvProperties.get("realm")); + kerberosDetails.setDefaultRealm(kerberosEnvProperties.get(DEFAULT_REALM)); kerberosDetails.setKerberosEnvProperties(kerberosEnvProperties); // If set, override the manage identities behavior kerberosDetails.setManageIdentities(manageIdentities); - String kdcTypeProperty = kerberosEnvProperties.get("kdc_type"); + String kdcTypeProperty = kerberosEnvProperties.get(KDC_TYPE); if ((kdcTypeProperty == null) && kerberosDetails.manageIdentities()) { String message = "The 'kerberos-env/kdc_type' value must be set to a valid KDC type"; LOG.error(message); @@ -2206,12 +2423,12 @@ public class KerberosHelperImpl implements KerberosHelper { /** * Creates a new stage * - * @param id the new stage's id - * @param cluster the relevant Cluster - * @param requestId the relevant request Id - * @param requestContext a String describing the stage - * @param commandParams JSON-encoded command parameters - * @param hostParams JSON-encoded host parameters + * @param id the new stage's id + * @param cluster the relevant Cluster + * @param requestId the relevant request Id + * @param requestContext a String describing the stage + * @param commandParams JSON-encoded command parameters + * @param hostParams JSON-encoded host parameters * @return a newly created Stage */ private Stage createNewStage(long id, Cluster cluster, long requestId, @@ -2255,7 +2472,7 @@ public class KerberosHelperImpl implements KerberosHelper { Map<String, String> commandParameters, String commandDetail, Integer timeout) throws AmbariException { - Stage stage = createNewStage(id, cluster, requestId, requestContext, commandParams, hostParams); + Stage stage = createNewStage(id, cluster, requestId, requestContext, commandParams, hostParams); stage.addServerActionCommand(actionClass.getName(), null, Role.AMBARI_SERVER_ACTION, RoleCommand.EXECUTE, cluster.getClusterName(), event, commandParameters, commandDetail, ambariManagementController.findConfigurationTagsWithOverrides(cluster, null), timeout, @@ -2582,10 +2799,11 @@ public class KerberosHelperImpl implements KerberosHelper { * <code>stacks/:stackName/versions/:version/artifacts/kerberos_descriptor</code> * * @param cluster the cluster + * @param includePreconfigureData <code>true</code> to include the preconfigure data; otherwise false * @return a Kerberos Descriptor * @throws AmbariException if an error occurs while retrieving the Kerberos descriptor */ - private KerberosDescriptor getKerberosDescriptorFromStack(Cluster cluster) throws AmbariException { + private KerberosDescriptor getKerberosDescriptorFromStack(Cluster cluster, boolean includePreconfigureData) throws AmbariException { // !!! FIXME in a per-service view, what does this become? Set<StackId> stackIds = new HashSet<>(); @@ -2602,14 +2820,14 @@ public class KerberosHelperImpl implements KerberosHelper { // ------------------------------- // Get the default Kerberos descriptor from the stack, which is the same as the value from // stacks/:stackName/versions/:version/artifacts/kerberos_descriptor - return ambariMetaInfo.getKerberosDescriptor(stackId.getStackName(), stackId.getStackVersion()); + return ambariMetaInfo.getKerberosDescriptor(stackId.getStackName(), stackId.getStackVersion(), includePreconfigureData); // ------------------------------- } /** * Recursively walk the Kerberos descriptor tree to find all Kerberos identity definitions and * determine which should be filtered out. - * + * <p> * No actual filtering is performed while processing since any referenced Kerberos identities need * to be accessible throughout the process. So a map of container path to a list of identities is * created an returned @@ -2621,14 +2839,14 @@ public class KerberosHelperImpl implements KerberosHelper { * @return * @throws AmbariException */ - private Map<String,Set<String>> processWhenClauses(String currentPath, AbstractKerberosDescriptorContainer container, Map<String, Object> context, Map<String,Set<String>> identitiesToRemove) throws AmbariException { + private Map<String, Set<String>> processWhenClauses(String currentPath, AbstractKerberosDescriptorContainer container, Map<String, Object> context, Map<String, Set<String>> identitiesToRemove) throws AmbariException { // Get the list of this container's identities. // Do not filter these identities using KerberosIdentityDescriptor#shouldInclude since we will do // that later. List<KerberosIdentityDescriptor> identities = container.getIdentities(true, null); - if((identities != null) && !identities.isEmpty()) { + if ((identities != null) && !identities.isEmpty()) { Set<String> set = null; for (KerberosIdentityDescriptor identity : identities) { @@ -2644,8 +2862,8 @@ public class KerberosHelperImpl implements KerberosHelper { } Collection<? extends AbstractKerberosDescriptorContainer> children = container.getChildContainers(); - if(children != null) { - for(AbstractKerberosDescriptorContainer child: children) { + if (children != null) { + for (AbstractKerberosDescriptorContainer child : children) { identitiesToRemove = processWhenClauses(currentPath + "/" + child.getName(), child, context, identitiesToRemove); } } @@ -2702,6 +2920,142 @@ public class KerberosHelperImpl implements KerberosHelper { } + /** + * Gathers the Kerberos-related configurations for services not yet installed, but flagged to be + * preconfigured. + * <p> + * Only existing configuration types will be updated, new types will not be added since they are + * expected only when the relevant service has been installed. This is to help reduce the number + * of service restarts when new services are added to clusters where Kerberos has been enabled. + * <p> + * If desired, the Stack Advisor will be invoked to request recommended hosts for the component. + * This is needed to fill out the clusterHostInfo structure in the configuration map. For example, + * <code>clusterHostInfo/knox_gateway_hosts</code> + * + * @param configurations the existing configurations (updated in-place) + * @param cluster the cluster + * @param kerberosDescriptor the kerberos descriptor + * @param calculateClusterHostInfo true, to query the Stack Advisor for recommended hosts for the + * preconfigured services and components; false, otherwise + * @return the updated configuration map + * @throws AmbariException if an error occurs + */ + private Map<String, Map<String, String>> addConfigurationsForPreProcessedServices(Map<String, Map<String, String>> configurations, + Cluster cluster, + KerberosDescriptor kerberosDescriptor, + boolean calculateClusterHostInfo) + throws AmbariException { + + Map<String, KerberosServiceDescriptor> serviceDescriptorMap = kerberosDescriptor.getServices(); + + if (serviceDescriptorMap != null) { + Map<String, Service> existingServices = cluster.getServices(); + Set<String> allServices = new HashSet<>(existingServices.keySet()); + Set<String> componentFilter = new HashSet<>(); + StackId stackVersion = cluster.getCurrentStackVersion(); + + for (KerberosServiceDescriptor serviceDescriptor : serviceDescriptorMap.values()) { + String serviceName = serviceDescriptor.getName(); + boolean shouldPreconfigure = serviceDescriptor.shouldPreconfigure(); + + if (shouldPreconfigure && !existingServices.containsKey(serviceName)) { + if (ambariMetaInfo.isValidService(stackVersion.getStackName(), stackVersion.getStackVersion(), serviceName)) { + ServiceInfo serviceInfo = ambariMetaInfo.getService(stackVersion.getStackName(), stackVersion.getStackVersion(), serviceName); + + Collection<PropertyInfo> servicePropertiesInfos = serviceInfo.getProperties(); + if (servicePropertiesInfos != null) { + Map<String, Map<String, String>> propertiesToAdd = new HashMap<>(); + + for (PropertyInfo propertyInfo : servicePropertiesInfos) { + String type = ConfigHelper.fileNameToConfigType(propertyInfo.getFilename()); + + Map<String, String> map = propertiesToAdd.get(type); + if (map == null) { + map = new HashMap<>(); + propertiesToAdd.put(type, map); + } + map.put(propertyInfo.getName(), propertyInfo.getValue()); + } + + for (Map.Entry<String, Map<String, String>> entry : propertiesToAdd.entrySet()) { + if (!configurations.containsKey(entry.getKey())) { + configurations.put(entry.getKey(), entry.getValue()); + } + } + } + + // This is only needed if the Stack Advisor is being called to get recommended host + // for components + if (calculateClusterHostInfo) { + // Add the service to preconfigure to the all services set for use later + allServices.add(serviceName); + + // Add the components for the service to preconfigure to the component filter + List<ComponentInfo> componentInfos = serviceInfo.getComponents(); + if (componentInfos != null) { + for (ComponentInfo componentInfo : componentInfos) { + componentFilter.add(componentInfo.getName()); + } + } + } + } + } + } + + if (calculateClusterHostInfo && (allServices.size() > existingServices.size())) { + applyStackAdvisorHostRecommendations(cluster, allServices, componentFilter, configurations); + } + } + + return configurations; + } + + /** + * Combines a stack-level Kerberos descriptor with a user-suppled Kerberos descriptor to creae a + * composite {@link KerberosDescriptor} using the following logic: + * <p> + * <ul> + * <li> + * If both the stack-level and the user-supplied Kerberos descriptors are <code>null</code>, + * return an empty {@link KerberosDescriptor}. + * </li> + * <li> + * If the stack-level Kerberos descriptor is <code>null</code> and the user-supplied Kerberos + * descriptor is <code>non-null</code>, return the user-supplied Kerberos descriptor. + * </li> + * <li> + * If the stack-level Kerberos descriptor is <code>non-null</code> and the user-supplied + * Kerberos descriptor is <code>null</code>, return the stack-level Kerberos descriptor. + * </li> + * <li> + * If neither the stack-level nor the user-supplied Kerberos descriptors are <code>null</code>, + * return the stack-level Kerberos descriptor that has been updated using data from the + * user-supplied Kerberos descriptor. + * </li> + * </ul> + * + * @param stackDescriptor the stack-level Keberos descriptor + * @param userDescriptor the user-supplied Kerberos descriptor + * @return a KerberosDescriptor + */ + private KerberosDescriptor combineKerberosDescriptors(KerberosDescriptor stackDescriptor, KerberosDescriptor userDescriptor) { + KerberosDescriptor kerberosDescriptor ; + if (stackDescriptor == null) { + if (userDescriptor == null) { + return new KerberosDescriptor(); // return an empty Kerberos descriptor since we have no data + } else { + kerberosDescriptor = userDescriptor; + } + } else { + if (userDescriptor != null) { + stackDescriptor.update(userDescriptor); + } + kerberosDescriptor = stackDescriptor; + } + + return kerberosDescriptor; + } + /* ******************************************************************************************** * Helper classes and enums * ******************************************************************************************** *\ @@ -3034,19 +3388,18 @@ public class KerberosHelperImpl implements KerberosHelper { } void addDisableSecurityHookStage(Cluster cluster, - String clusterHostInfoJson, - String hostParamsJson, - Map<String, String> commandParameters, - RoleCommandOrder roleCommandOrder, - RequestStageContainer requestStageContainer) - throws AmbariException - { + String clusterHostInfoJson, + String hostParamsJson, + Map<String, String> commandParameters, + RoleCommandOrder roleCommandOrder, + RequestStageContainer requestStageContainer) + throws AmbariException { Stage stage = createNewStage(requestStageContainer.getLastStageId(), - cluster, - requestStageContainer.getId(), - "Disable security", - StageUtils.getGson().toJson(commandParameters), - hostParamsJson); + cluster, + requestStageContainer.getId(), + "Disable security", + StageUtils.getGson().toJson(commandParameters), + hostParamsJson); addDisableSecurityCommandToAllServices(cluster, stage); RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder); roleGraph.build(stage); @@ -3058,27 +3411,26 @@ public class KerberosHelperImpl implements KerberosHelper { private void addDisableSecurityCommandToAllServices(Cluster cluster, Stage stage) throws AmbariException { for (Service service : cluster.getServices().values()) { for (ServiceComponent component : service.getServiceComponents().values()) { - if (!component.getServiceComponentHosts().isEmpty()) { - String firstHost = component.getServiceComponentHosts().keySet().iterator().next(); // it is only necessary to send it to one host - ActionExecutionContext exec = new ActionExecutionContext( + if (!component.getServiceComponentHosts().isEmpty()) { + String firstHost = component.getServiceComponentHosts().keySet().iterator().next(); // it is only necessary to send it to one host + ActionExecutionContext exec = new ActionExecutionContext( cluster.getClusterName(), "DISABLE_SECURITY", singletonList(new RequestResourceFilter(service.getName(), component.getName(), singletonList(firstHost))), Collections.<String, String>emptyMap()); - customCommandExecutionHelper.addExecutionCommandsToStage(exec, stage, Collections.<String, String>emptyMap(), null); + customCommandExecutionHelper.addExecutionCommandsToStage(exec, stage, Collections.<String, String>emptyMap(), null); } } } } void addStopZookeeperStage(Cluster cluster, - String clusterHostInfoJson, - String hostParamsJson, - Map<String, String> commandParameters, - RoleCommandOrder roleCommandOrder, - RequestStageContainer requestStageContainer) - throws AmbariException - { + String clusterHostInfoJson, + String hostParamsJson, + Map<String, String> commandParameters, + RoleCommandOrder roleCommandOrder, + RequestStageContainer requestStageContainer) + throws AmbariException { Service zookeeper; try { zookeeper = cluster.getService("ZOOKEEPER"); @@ -3086,19 +3438,19 @@ public class KerberosHelperImpl implements KerberosHelper { return; } Stage stage = createNewStage(requestStageContainer.getLastStageId(), - cluster, - requestStageContainer.getId(), - "Stopping ZooKeeper", - StageUtils.getGson().toJson(commandParameters), - hostParamsJson); + cluster, + requestStageContainer.getId(), + "Stopping ZooKeeper", + StageUtils.getGson().toJson(commandParameters), + hostParamsJson); for (ServiceComponent component : zookeeper.getServiceComponents().values()) { - Set<String> hosts = component.getServiceComponentHosts().keySet(); - ActionExecutionContext exec = new ActionExecutionContext( + Set<String> hosts = component.getServiceComponentHosts().keySet(); + ActionExecutionContext exec = new ActionExecutionContext( cluster.getClusterName(), "STOP", singletonList(new RequestResourceFilter(zookeeper.getName(), component.getName(), new ArrayList<>(hosts))), Collections.<String, String>emptyMap()); - customCommandExecutionHelper.addExecutionCommandsToStage(exec, stage, Collections.<String, String>emptyMap(), null); + customCommandExecutionHelper.addExecutionCommandsToStage(exec, stage, Collections.<String, String>emptyMap(), null); } RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder); roleGraph.build(stage); @@ -3303,6 +3655,7 @@ public class KerberosHelperImpl implements KerberosHelper { commandParameters.put(KerberosServerAction.UPDATE_CONFIGURATIONS, "true"); commandParameters.put(KerberosServerAction.DEFAULT_REALM, kerberosDetails.getDefaultRealm()); commandParameters.put(KerberosServerAction.INCLUDE_AMBARI_IDENTITY, (kerberosDetails.createAmbariPrincipal()) ? "true" : "false"); + commandParameters.put(KerberosServerAction.PRECONFIGURE_SERVICES, kerberosDetails.getPreconfigureServices()); if (dataDirectory != null) { commandParameters.put(KerberosServerAction.DATA_DIRECTORY, dataDirectory.getAbsolutePath()); @@ -3434,10 +3787,10 @@ public class KerberosHelperImpl implements KerberosHelper { } addDisableSecurityHookStage(cluster, clusterHostInfoJson, hostParamsJson, commandParameters, - roleCommandOrder, requestStageContainer); + roleCommandOrder, requestStageContainer); addStopZookeeperStage(cluster, clusterHostInfoJson, hostParamsJson, commandParameters, - roleCommandOrder, requestStageContainer); + roleCommandOrder, requestStageContainer); // ***************************************************************** // Create stage to prepare operations @@ -3790,5 +4143,9 @@ public class KerberosHelperImpl implements KerberosHelper { return (kerberosEnvProperties == null) || !"false".equalsIgnoreCase(kerberosEnvProperties.get(CREATE_AMBARI_PRINCIPAL)); } + + public String getPreconfigureServices() { + return (kerberosEnvProperties == null) ? "" : kerberosEnvProperties.get(PRECONFIGURE_SERVICES); + } } } \ No newline at end of file
