This is an automated email from the ASF dual-hosted git repository. hapylestat pushed a commit to branch branch-2.7 in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.7 by this push: new 17dc5fd AMBARI-25332. Kerberos keytab regeneration working slow (dgrinenko) (#3120) 17dc5fd is described below commit 17dc5fd5610e0037e197934ceb27b61e087c8d57 Author: Dmytro Grinenko <hapyles...@apache.org> AuthorDate: Fri Nov 8 12:54:42 2019 +0200 AMBARI-25332. Kerberos keytab regeneration working slow (dgrinenko) (#3120) --- .../ambari/server/configuration/Configuration.java | 19 ++- .../ambari/server/controller/KerberosHelper.java | 60 ++++--- .../server/controller/KerberosHelperImpl.java | 83 +++++++-- .../HostKerberosIdentityResourceProvider.java | 11 +- .../ambari/server/orm/dao/KerberosKeytabDAO.java | 14 +- .../server/orm/dao/KerberosKeytabPrincipalDAO.java | 22 ++- .../server/orm/dao/KerberosPrincipalDAO.java | 6 +- .../server/orm/entities/KerberosKeytabEntity.java | 7 +- .../entities/KerberosKeytabPrincipalEntity.java | 30 +++- .../ConfigureAmbariIdentitiesServerAction.java | 37 ++-- .../kerberos/CreateKeytabFilesServerAction.java | 190 +++++++++++---------- .../kerberos/CreatePrincipalsServerAction.java | 63 ++++--- .../kerberos/FinalizeKerberosServerAction.java | 17 +- .../kerberos/KerberosServerAction.java | 69 +++++++- .../stageutils/KerberosKeytabController.java | 40 ++++- .../server/controller/KerberosHelperTest.java | 18 +- .../HostKerberosIdentityResourceProviderTest.java | 18 +- .../ConfigureAmbariIdentitiesServerActionTest.java | 13 +- 18 files changed, 530 insertions(+), 187 deletions(-) diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java index 6b0e383..87549a8 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java @@ -2616,6 +2616,13 @@ public class Configuration { public static final ConfigurationProperty<Integer> KERBEROS_SERVER_ACTION_FINALIZE_SECONDS = new ConfigurationProperty<>( "server.kerberos.finalize.timeout", 600); + /** + * The number of threads to use when executing server-side Kerberos commands, such as generate keytabs. + */ + @Markdown(description = "The number of threads to use when executing server-side Kerberos commands, such as generate keytabs.") + public static final ConfigurationProperty<Integer> KERBEROS_SERVER_ACTION_THREADPOOL_SIZE = new ConfigurationProperty<>( + "server.kerberos.action.threadpool.size", 1); + private static final Logger LOG = LoggerFactory.getLogger( Configuration.class); @@ -3041,7 +3048,7 @@ public class Configuration { writeConfigFile(existingProperties, false); // reloading properties - this.properties = readConfigFile(); + properties = readConfigFile(); } /** @@ -5569,6 +5576,16 @@ public class Configuration { } /** + * Gets the number of threads to use when executing server-side Kerberos + * commands, such as generate keytabs. + * + * @return the threadpool size, defaulting to 1 + */ + public int getKerberosServerActionThreadpoolSize() { + return Integer.parseInt(getProperty(KERBEROS_SERVER_ACTION_THREADPOOL_SIZE)); + } + + /** * Get the timeout, in seconds, when finalizing Kerberos * enable/disable/regenerate commands. * 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 3c4d6b2..5f84968 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 @@ -704,32 +704,52 @@ public interface KerberosHelper { Map<String, Map<String, String>> getIdentityConfigurations(List<KerberosIdentityDescriptor> identityDescriptors); /** - * Returns the active identities for the named cluster. Results are filtered by host, service, - * and/or component; and grouped by host. + * Returns the active identities for the named cluster. Results are filtered + * by host, service, and/or component; and grouped by host. * <p/> - * The cluster name is mandatory; however the active identities may be filtered by one or more of - * host, service, or component. A <code>null</code> value for any of these filters indicates no - * filter for that parameter. + * The cluster name is mandatory; however the active identities may be + * filtered by one or more of host, service, or component. A <code>null</code> + * value for any of these filters indicates no filter for that parameter. * <p/> - * The return values are grouped by host and optionally <code>_HOST</code> in principals will be - * replaced with the relevant hostname if specified to do so. - * - * @param clusterName the name of the relevant cluster (mandatory) - * @param hostName the name of a host for which to find results, null indicates all hosts - * @param serviceName the name of a service for which to find results, null indicates all - * services - * @param componentName the name of a component for which to find results, null indicates all - * components - * @param replaceHostNames if true, _HOST in principals will be replace with the relevant host - * name + * The return values are grouped by host and optionally <code>_HOST</code> in + * principals will be replaced with the relevant hostname if specified to do + * so. + * + * @param clusterName + * the name of the relevant cluster (mandatory) + * @param hostName + * the name of a host for which to find results, null indicates all + * hosts + * @param serviceName + * the name of a service for which to find results, null indicates + * all services + * @param componentName + * the name of a component for which to find results, null indicates + * all components + * @param replaceHostNames + * if true, _HOST in principals will be replace with the relevant + * host name + * @param hostConfigurations + * a mapping of hostname to configurations for that host. Fetching + * this ahead of time for every host in the cluster will ensure that + * this method doesn't need to do it inside of a loop. If + * {@code null} or empty, then this method will do the lookup itself, + * at considerable cost. + * @param kerberosDescriptor + * the kerberos descriptor to use when looking up identities. If + * {@code null}, then this method will deserialize the descriptor + * inside of a loop at considerable cost. * @return a map of host names to kerberos identities - * @throws AmbariException if an error occurs processing the cluster's active identities + * @throws AmbariException + * if an error occurs processing the cluster's active identities */ Map<String, Collection<KerberosIdentityDescriptor>> getActiveIdentities(String clusterName, String hostName, String serviceName, String componentName, - boolean replaceHostNames) + boolean replaceHostNames, + Map<String, Map<String, Map<String, String>>> hostConfigurations, + KerberosDescriptor kerberosDescriptor) throws AmbariException; /** @@ -814,7 +834,7 @@ public interface KerberosHelper { * @return a map of configuration types to sets of property names */ Map<String, Set<String>> translateConfigurationSpecifications(Collection<String> configurationSpecifications); - + /** * Gathers the Kerberos-related data from configurations and stores it in a new KerberosDetails * instance. @@ -827,7 +847,7 @@ public interface KerberosHelper { * @throws AmbariException */ KerberosDetails getKerberosDetails(Cluster cluster, Boolean manageIdentities) - throws KerberosInvalidConfigurationException, AmbariException; + throws KerberosInvalidConfigurationException, AmbariException; /** * Types of Kerberos descriptors related to where the data is stored. 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 00b74e0..987b77d 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 @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import org.apache.ambari.annotations.Experimental; @@ -63,6 +64,7 @@ import org.apache.ambari.server.orm.dao.ArtifactDAO; import org.apache.ambari.server.orm.dao.HostDAO; import org.apache.ambari.server.orm.dao.KerberosKeytabDAO; import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO; +import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO.KeytabPrincipalFindOrCreateResult; import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO; import org.apache.ambari.server.orm.entities.ArtifactEntity; import org.apache.ambari.server.orm.entities.HostEntity; @@ -137,6 +139,7 @@ import org.apache.directory.server.kerberos.shared.keytab.Keytab; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -1745,7 +1748,9 @@ public class KerberosHelperImpl implements KerberosHelper { String hostName, String serviceName, String componentName, - boolean replaceHostNames) + boolean replaceHostNames, + Map<String, Map<String, Map<String, String>>> hostConfigurations, + KerberosDescriptor kerberosDescriptor) throws AmbariException { if ((clusterName == null) || clusterName.isEmpty()) { @@ -1790,8 +1795,15 @@ public class KerberosHelperImpl implements KerberosHelper { hosts = Collections.singleton(hostName); } + if (null == hostConfigurations) { + hostConfigurations = new HashMap<>(); + } + if (!hosts.isEmpty()) { - KerberosDescriptor kerberosDescriptor = getKerberosDescriptor(cluster, false); + + if (null == kerberosDescriptor) { + kerberosDescriptor = getKerberosDescriptor(cluster, false); + } if (kerberosDescriptor != null) { Set<String> existingServices = cluster.getServices().keySet(); @@ -1799,12 +1811,17 @@ public class KerberosHelperImpl implements KerberosHelper { for (String host : hosts) { // Calculate the current host-specific configurations. These will be used to replace // variables within the Kerberos descriptor data - Map<String, Map<String, String>> configurations = calculateConfigurations(cluster, + Map<String, Map<String, String>> configurations = hostConfigurations.get(host); + if (configurations == null) { + configurations = calculateConfigurations(cluster, (ambariServerHostnameIsForced && ambariServerHostname.equals(host)) ? null : host, kerberosDescriptor, false, false); + hostConfigurations.put(host, configurations); + } + // Create the context to use for filtering Kerberos Identities based on the state of the cluster Map<String, Object> filterContext = new HashMap<>(); filterContext.put("configurations", configurations); @@ -1951,15 +1968,27 @@ public class KerberosHelperImpl implements KerberosHelper { } /** - * Creates and saves underlying {@link org.apache.ambari.server.orm.entities.KerberosPrincipalEntity}, - * {@link org.apache.ambari.server.orm.entities.KerberosKeytabEntity} entities in JPA storage. + * Creates and saves underlying + * {@link org.apache.ambari.server.orm.entities.KerberosPrincipalEntity}, + * {@link org.apache.ambari.server.orm.entities.KerberosKeytabEntity} entities + * in JPA storage. + * <p> + * This method has to be very, very careful WRT how and when it merges + * bidirectional associations.For larger cluster, merging the + * {@link KerberosKeytabEntity} and {@link KerberosPrincipalEntity} even when + * a {@link KerberosKeytabPrincipalEntity} will result in major performance + * problems. * - * @param resolvedKerberosKeytab kerberos keytab to be persisted + * @param resolvedKerberosKeytab + * kerberos keytab to be persisted */ @Override public void createResolvedKeytab(ResolvedKerberosKeytab resolvedKerberosKeytab) { - if (kerberosKeytabDAO.find(resolvedKerberosKeytab.getFile()) == null) { - KerberosKeytabEntity kke = new KerberosKeytabEntity(resolvedKerberosKeytab.getFile()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + KerberosKeytabEntity kke = kerberosKeytabDAO.find(resolvedKerberosKeytab.getFile()); + if (null == kke) { + kke = new KerberosKeytabEntity(resolvedKerberosKeytab.getFile()); kke.setAmbariServerKeytab(resolvedKerberosKeytab.isAmbariServerKeytab()); kke.setWriteAmbariJaasFile(resolvedKerberosKeytab.isMustWriteAmbariJaasFile()); kke.setOwnerName(resolvedKerberosKeytab.getOwnerName()); @@ -1968,24 +1997,43 @@ public class KerberosHelperImpl implements KerberosHelper { kke.setGroupAccess(resolvedKerberosKeytab.getGroupAccess()); kerberosKeytabDAO.create(kke); } + for (ResolvedKerberosPrincipal principal : resolvedKerberosKeytab.getPrincipals()) { - if (!kerberosPrincipalDAO.exists(principal.getPrincipal())) { - kerberosPrincipalDAO.create(principal.getPrincipal(), principal.isService()); + KerberosPrincipalEntity kpe = kerberosPrincipalDAO.find(principal.getPrincipal()); + + if (null == kpe) { + kpe = kerberosPrincipalDAO.create(principal.getPrincipal(), principal.isService()); } + + // only need to merge the kke and kpe if a new kkp is created/added to their lists + boolean mergeBidirectionalAssociatedEntities = false; for (Map.Entry<String, String> mappingEntry : principal.getServiceMapping().entries()) { String serviceName = mappingEntry.getKey(); HostEntity hostEntity = principal.getHostId() != null ? hostDAO.findById(principal.getHostId()) : null; - KerberosKeytabEntity kke = kerberosKeytabDAO.find(resolvedKerberosKeytab.getFile()); - KerberosPrincipalEntity kpe = kerberosPrincipalDAO.find(principal.getPrincipal()); - KerberosKeytabPrincipalEntity kkp = kerberosKeytabPrincipalDAO.findOrCreate(kke, hostEntity, kpe); + KeytabPrincipalFindOrCreateResult result = kerberosKeytabPrincipalDAO.findOrCreate(kke, hostEntity, kpe); + KerberosKeytabPrincipalEntity kkp = result.kkp; + mergeBidirectionalAssociatedEntities = mergeBidirectionalAssociatedEntities || result.created; + + // updating the kkp service mappings does not affect kke/kpe bidirectional relationships if (kkp.putServiceMapping(serviceName, mappingEntry.getValue())) { kerberosKeytabPrincipalDAO.merge(kkp); } - kerberosKeytabDAO.merge(kke); - kerberosPrincipalDAO.merge(kpe); + } + + // merge the keytab and the principal IFF at least one keytabprincipal was + // created causing the bi-directional lists associations to need updating + if(mergeBidirectionalAssociatedEntities) { + Stopwatch mergeStockwatch = Stopwatch.createStarted(); + kke = kerberosKeytabDAO.merge(kke); + kpe = kerberosPrincipalDAO.merge(kpe); + LOG.info("Merging bidirectional associated entities for this keytab took {}ms" + + mergeStockwatch.elapsed(TimeUnit.MILLISECONDS)); } } + + LOG.info("Resolving this keytab and all associated principals took {}ms ", + stopwatch.elapsed(TimeUnit.MILLISECONDS)); } @Override @@ -2392,7 +2440,10 @@ public class KerberosHelperImpl implements KerberosHelper { kerberosPrincipalDAO.create(kpe); } - KerberosKeytabPrincipalEntity kkp = kerberosKeytabPrincipalDAO.findOrCreate(kke, hostDAO.findById(sch.getHost().getHostId()), kpe); + KeytabPrincipalFindOrCreateResult result = kerberosKeytabPrincipalDAO.findOrCreate( + kke, hostDAO.findById(sch.getHost().getHostId()), kpe); + + KerberosKeytabPrincipalEntity kkp = result.kkp; if (kkp.putServiceMapping(sch.getServiceName(), sch.getServiceComponentName())) { kerberosKeytabPrincipalDAO.merge(kkp); } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProvider.java index d90d5bf..3b82801 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProvider.java @@ -40,6 +40,9 @@ import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO; import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO; import org.apache.ambari.server.orm.entities.HostEntity; import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; import org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor; import org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor; @@ -158,9 +161,15 @@ public class HostKerberosIdentityResourceProvider extends ReadOnlyResourceProvid String clusterName = (String) propertyMap.get(KERBEROS_IDENTITY_CLUSTER_NAME_PROPERTY_ID); String hostName = (String) propertyMap.get(KERBEROS_IDENTITY_HOST_NAME_PROPERTY_ID); + Clusters clusters = getManagementController().getClusters(); + Cluster cluster = clusters.getCluster(clusterName); + + KerberosDescriptor kerberosDescriptor = kerberosHelper.getKerberosDescriptor(cluster, false); + // Retrieve the active identities for the cluster filtered and grouped by hostname Map<String, Collection<KerberosIdentityDescriptor>> hostDescriptors = - kerberosHelper.getActiveIdentities(clusterName, hostName, null, null, true); + kerberosHelper.getActiveIdentities(clusterName, hostName, null, null, true, null, + kerberosDescriptor); if (hostDescriptors != null) { for (Map.Entry<String, Collection<KerberosIdentityDescriptor>> entry : hostDescriptors.entrySet()) { diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabDAO.java index 60ec754..c999f9b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabDAO.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabDAO.java @@ -21,6 +21,7 @@ package org.apache.ambari.server.orm.dao; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -32,6 +33,7 @@ import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Stopwatch; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -88,9 +90,15 @@ public class KerberosKeytabDAO { @RequiresSession public List<KerberosKeytabEntity> findByPrincipalAndHost(String principalName, Long hostId) { + Stopwatch stopwatch = Stopwatch.createStarted(); if(hostId == null) { - return findByPrincipalAndNullHost(principalName); + List<KerberosKeytabEntity> result = findByPrincipalAndNullHost(principalName); + LOG.debug("Loading keytabs by principal name took {}ms", + stopwatch.elapsed(TimeUnit.MILLISECONDS)); + + return result; } + TypedQuery<KerberosKeytabEntity> query = entityManagerProvider.get(). createNamedQuery("KerberosKeytabEntity.findByPrincipalAndHost", KerberosKeytabEntity.class); query.setParameter("hostId", hostId); @@ -99,6 +107,10 @@ public class KerberosKeytabDAO { if(result == null) { return Collections.emptyList(); } + + LOG.debug("Loading keytabs by principal name and host took {}ms", + stopwatch.elapsed(TimeUnit.MILLISECONDS)); + return result; } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabPrincipalDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabPrincipalDAO.java index 7a44f2c..bb8ac6c 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabPrincipalDAO.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosKeytabPrincipalDAO.java @@ -74,20 +74,28 @@ public class KerberosKeytabPrincipalDAO { * @param kerberosPrincipalEntity {@link KerberosPrincipalEntity} which related to this principal * @return evaluated entity */ - public KerberosKeytabPrincipalEntity findOrCreate(KerberosKeytabEntity kerberosKeytabEntity, HostEntity hostEntity, KerberosPrincipalEntity kerberosPrincipalEntity) { + public KeytabPrincipalFindOrCreateResult findOrCreate(KerberosKeytabEntity kerberosKeytabEntity, HostEntity hostEntity, KerberosPrincipalEntity kerberosPrincipalEntity) { + KeytabPrincipalFindOrCreateResult result = new KeytabPrincipalFindOrCreateResult(); + result.created = false; + Long hostId = hostEntity == null ? null : hostEntity.getHostId(); KerberosKeytabPrincipalEntity kkp = findByNaturalKey(hostId, kerberosKeytabEntity.getKeytabPath(), kerberosPrincipalEntity.getPrincipalName()); if (kkp == null) { + result.created = true; + kkp = new KerberosKeytabPrincipalEntity( kerberosKeytabEntity, hostEntity, kerberosPrincipalEntity ); create(kkp); + kerberosKeytabEntity.addKerberosKeytabPrincipal(kkp); kerberosPrincipalEntity.addKerberosKeytabPrincipal(kkp); } - return kkp; + + result.kkp = kkp; + return result; } @Transactional @@ -345,4 +353,14 @@ public class KerberosKeytabPrincipalDAO { principalNames); } } + + /** + * Used to return a keytab principal and whether or not it was created. This + * is required so that callers know whether associated keytabs and principals + * need bi-direcitonal merges. + */ + public static class KeytabPrincipalFindOrCreateResult { + public KerberosKeytabPrincipalEntity kkp; + public boolean created; + } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosPrincipalDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosPrincipalDAO.java index 8774ca8..5db8590 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosPrincipalDAO.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/KerberosPrincipalDAO.java @@ -69,8 +69,10 @@ public class KerberosPrincipalDAO { * @param service a boolean value declaring whether the principal represents a service (true) or not )false). */ @Transactional - public void create(String principalName, boolean service) { - create(new KerberosPrincipalEntity(principalName, service, null)); + public KerberosPrincipalEntity create(String principalName, boolean service) { + KerberosPrincipalEntity kpe = new KerberosPrincipalEntity(principalName, service, null); + create(kpe); + return kpe; } /** diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabEntity.java index 4fb4f96..b93bfa1 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabEntity.java @@ -29,6 +29,7 @@ import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; +import javax.persistence.QueryHint; import javax.persistence.Table; @Entity @@ -37,8 +38,10 @@ import javax.persistence.Table; @NamedQuery(name = "KerberosKeytabEntity.findAll", query = "SELECT kk FROM KerberosKeytabEntity kk"), @NamedQuery( name = "KerberosKeytabEntity.findByPrincipalAndHost", - query = "SELECT kk FROM KerberosKeytabEntity kk JOIN kk.kerberosKeytabPrincipalEntities kkp WHERE kkp.hostId=:hostId AND kkp.principalName=:principalName" - ), + query = "SELECT kk FROM KerberosKeytabEntity kk, KerberosKeytabPrincipalEntity kkp WHERE kkp.hostId=:hostId AND kkp.principalName=:principalName AND kkp.keytabPath = kk.keytabPath", + hints = { + @QueryHint(name = "eclipselink.query-results-cache", value = "true"), + @QueryHint(name = "eclipselink.query-results-cache.size", value = "500") }), @NamedQuery( name = "KerberosKeytabEntity.findByPrincipalAndNullHost", query = "SELECT kk FROM KerberosKeytabEntity kk JOIN kk.kerberosKeytabPrincipalEntities kkp WHERE kkp.hostId IS NULL AND kkp.principalName=:principalName" diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabPrincipalEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabPrincipalEntity.java index 676b3e6..3f87186 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabPrincipalEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/KerberosKeytabPrincipalEntity.java @@ -24,6 +24,7 @@ import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -32,9 +33,12 @@ import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; +import javax.persistence.QueryHint; import javax.persistence.Table; import javax.persistence.TableGenerator; +import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosPrincipal; + import com.google.common.base.Objects; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @@ -72,6 +76,12 @@ import com.google.common.collect.Multimap; query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId" ), @NamedQuery( + name = "KerberosKeytabPrincipalEntity.findByPrincipalAndHost", + query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId AND kkpe.principalName=:principalName", + hints = { + @QueryHint(name = "eclipselink.query-results-cache", value = "true"), + @QueryHint(name = "eclipselink.query-results-cache.size", value = "500") }), + @NamedQuery( name = "KerberosKeytabPrincipalEntity.findByHostKeytabAndPrincipal", query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId AND kkpe.keytabPath=:keytabPath AND kkpe.principalName=:principalName" ), @@ -98,7 +108,12 @@ public class KerberosKeytabPrincipalEntity { @Column(name = "is_distributed", nullable = false) private Integer isDistributed = 0; - @ManyToOne + /** + * The assocaited keytab entity must be fetched {@link FetchType#EAGER} to + * ensure that it can be discovered when only the principal and host are + * known. + */ + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "keytab_path", referencedColumnName = "keytab_path", updatable = false, nullable = false, insertable = false) private KerberosKeytabEntity kerberosKeytabEntity; @@ -110,7 +125,16 @@ public class KerberosKeytabPrincipalEntity { @JoinColumn(name = "principal_name", referencedColumnName = "principal_name", updatable = false, nullable = false, insertable = false) private KerberosPrincipalEntity kerberosPrincipalEntity; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "kerberosKeytabPrincipalEntity", orphanRemoval = true) + /** + * Service mappings must be fetched {@link FetchType#EAGER} since they are + * required when processing {@link KerberosKeytabPrincipalEntity} instances + * and turning them into {@link ResolvedKerberosPrincipal}. + */ + @OneToMany( + cascade = CascadeType.ALL, + mappedBy = "kerberosKeytabPrincipalEntity", + orphanRemoval = true, + fetch = FetchType.EAGER) private List<KerberosKeytabServiceMappingEntity> serviceMapping = new ArrayList<>(); public KerberosKeytabPrincipalEntity() { @@ -144,7 +168,7 @@ public class KerberosKeytabPrincipalEntity { } public void setKerberosKeytabEntity(KerberosKeytabEntity kke) { - this.kerberosKeytabEntity = kke; + kerberosKeytabEntity = kke; if (kke != null) { keytabPath = kke.getKeytabPath(); } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerAction.java index a2ddf12..8e8eeb9 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerAction.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.agent.CommandReport; @@ -46,6 +47,7 @@ import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.util.concurrent.Striped; import com.google.inject.Inject; /** @@ -79,6 +81,11 @@ public class ConfigureAmbariIdentitiesServerAction extends KerberosServerAction private HostDAO hostDAO; /** + * Used to prevent multiple threads from working with the same keytab. + */ + private Striped<Lock> m_locksByKeytab = Striped.lazyWeakLock(25); + + /** * Called to execute this action. Upon invocation, calls * {@link KerberosServerAction#processIdentities(Map)} )} * to iterate through the Kerberos identity metadata and call @@ -136,18 +143,24 @@ public class ConfigureAmbariIdentitiesServerAction extends KerberosServerAction File hostDirectory = new File(dataDirectory, hostName); File srcKeytabFile = new File(hostDirectory, DigestUtils.sha256Hex(destKeytabFilePath)); - if (srcKeytabFile.exists()) { - String ownerAccess = keytab.getOwnerAccess(); - String groupAccess = keytab.getGroupAccess(); - - installAmbariServerIdentity(resolvedPrincipal, srcKeytabFile.getAbsolutePath(), destKeytabFilePath, - keytab.getOwnerName(), ownerAccess, - keytab.getGroupName(), groupAccess, actionLog); - - if (serviceMappingEntry.getValue().contains("AMBARI_SERVER_SELF")) { - // Create/update the JAASFile... - configureJAAS(resolvedPrincipal.getPrincipal(), destKeytabFilePath, actionLog); + Lock lock = m_locksByKeytab.get(destKeytabFilePath); + lock.lock(); + try { + if (srcKeytabFile.exists()) { + String ownerAccess = keytab.getOwnerAccess(); + String groupAccess = keytab.getGroupAccess(); + + installAmbariServerIdentity(resolvedPrincipal, srcKeytabFile.getAbsolutePath(), destKeytabFilePath, + keytab.getOwnerName(), ownerAccess, + keytab.getGroupName(), groupAccess, actionLog); + + if (serviceMappingEntry.getValue().contains("AMBARI_SERVER_SELF")) { + // Create/update the JAASFile... + configureJAAS(resolvedPrincipal.getPrincipal(), destKeytabFilePath, actionLog); + } } + } finally { + lock.unlock(); } } } @@ -215,7 +228,7 @@ public class ConfigureAmbariIdentitiesServerAction extends KerberosServerAction for(Map.Entry<String, String> mapping : principal.getServiceMapping().entries()) { String serviceName = mapping.getKey(); String componentName = mapping.getValue(); - KerberosKeytabPrincipalEntity entity = kerberosKeytabPrincipalDAO.findOrCreate(kke, hostEntity, kpe); + KerberosKeytabPrincipalEntity entity = kerberosKeytabPrincipalDAO.findOrCreate(kke, hostEntity, kpe).kkp; entity.setDistributed(true); entity.putServiceMapping(serviceName, componentName); kerberosKeytabPrincipalDAO.merge(entity); diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java index 351e861..6af7c56 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java @@ -20,11 +20,12 @@ package org.apache.ambari.server.serveraction.kerberos; import java.io.File; import java.io.IOException; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.actionmanager.HostRoleStatus; @@ -32,7 +33,6 @@ import org.apache.ambari.server.agent.CommandReport; import org.apache.ambari.server.audit.event.kerberos.CreateKeyTabKerberosAuditEvent; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.controller.KerberosHelper; -import org.apache.ambari.server.orm.dao.HostDAO; import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO; import org.apache.ambari.server.orm.entities.KerberosPrincipalEntity; import org.apache.ambari.server.serveraction.ActionLog; @@ -44,6 +44,7 @@ import org.apache.directory.server.kerberos.shared.keytab.Keytab; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.util.concurrent.Striped; import com.google.inject.Inject; /** @@ -71,20 +72,19 @@ public class CreateKeytabFilesServerAction extends KerberosServerAction { @Inject private Configuration configuration; - /** - * HostDAO used to retrieveHost Entity object - */ - @Inject - private HostDAO hostDAO; - @Inject private KerberosKeytabController kerberosKeytabController; /** + * Used to prevent multiple threads from working with the same keytab. + */ + private Striped<Lock> m_locksByKeytab = Striped.lazyWeakLock(25); + + /** * A map of data used to track what has been processed in order to optimize the creation of keytabs * such as knowing when to create a cached keytab file or use a cached keytab file. */ - Map<String, Set<String>> visitedIdentities = new HashMap<>(); + Map<String, Set<String>> visitedIdentities = new ConcurrentHashMap<>(); /** * Called to execute this action. Upon invocation, calls @@ -175,114 +175,122 @@ public class CreateKeytabFilesServerAction extends KerberosServerAction { } else { Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext); Map<String, Integer> principalKeyNumberMap = getPrincipalKeyNumberMap(requestSharedDataContext); + for (ResolvedKerberosKeytab rkk : keytabsToCreate) { String hostName = resolvedPrincipal.getHostName(); String keytabFilePath = rkk.getFile(); if ((hostName != null) && !hostName.isEmpty() && (keytabFilePath != null) && !keytabFilePath.isEmpty()) { - Set<String> visitedPrincipalKeys = visitedIdentities.get(resolvedPrincipal.getPrincipal()); - String visitationKey = String.format("%s|%s", hostName, keytabFilePath); - - if ((visitedPrincipalKeys == null) || !visitedPrincipalKeys.contains(visitationKey)) { - // Look up the current evaluatedPrincipal's password. - // If found create the keytab file, else try to find it in the cache. - String password = principalPasswordMap.get(resolvedPrincipal.getPrincipal()); - Integer keyNumber = principalKeyNumberMap.get(resolvedPrincipal.getPrincipal()); - - message = String.format("Creating keytab file for %s on host %s", resolvedPrincipal.getPrincipal(), hostName); - LOG.info(message); - actionLog.writeStdOut(message); - auditEventBuilder.withPrincipal(resolvedPrincipal.getPrincipal()).withHostName(hostName).withKeyTabFilePath(keytabFilePath); - - // Determine where to store the keytab file. It should go into a host-specific - // directory under the previously determined data directory. - File hostDirectory = new File(dataDirectory, hostName); - - // Ensure the host directory exists... - if (!hostDirectory.exists() && hostDirectory.mkdirs()) { - // Make sure only Ambari has access to this directory. - ensureAmbariOnlyAccess(hostDirectory); - } + Lock lock = m_locksByKeytab.get(keytabFilePath); + lock.lock(); + + try { + Set<String> visitedPrincipalKeys = visitedIdentities.get(resolvedPrincipal.getPrincipal()); + String visitationKey = String.format("%s|%s", hostName, keytabFilePath); + + if ((visitedPrincipalKeys == null) || !visitedPrincipalKeys.contains(visitationKey)) { + // Look up the current evaluatedPrincipal's password. + // If found create the keytab file, else try to find it in the cache. + String password = principalPasswordMap.get(resolvedPrincipal.getPrincipal()); + Integer keyNumber = principalKeyNumberMap.get(resolvedPrincipal.getPrincipal()); + + message = String.format("Creating keytab file for %s on host %s", resolvedPrincipal.getPrincipal(), hostName); + LOG.info(message); + actionLog.writeStdOut(message); + auditEventBuilder.withPrincipal(resolvedPrincipal.getPrincipal()).withHostName(hostName).withKeyTabFilePath(keytabFilePath); + + // Determine where to store the keytab file. It should go into a host-specific + // directory under the previously determined data directory. + File hostDirectory = new File(dataDirectory, hostName); + + // Ensure the host directory exists... + if (!hostDirectory.exists() && hostDirectory.mkdirs()) { + // Make sure only Ambari has access to this directory. + ensureAmbariOnlyAccess(hostDirectory); + } - if (hostDirectory.exists()) { - File destinationKeytabFile = new File(hostDirectory, DigestUtils.sha256Hex(keytabFilePath)); + if (hostDirectory.exists()) { + File destinationKeytabFile = new File(hostDirectory, DigestUtils.sha256Hex(keytabFilePath)); - boolean regenerateKeytabs = getOperationType(getCommandParameters()) == OperationType.RECREATE_ALL; + boolean regenerateKeytabs = getOperationType(getCommandParameters()) == OperationType.RECREATE_ALL; - if(!includedInFilter) { - // If this principal is to be filtered out, skip... unless is has not yet been created... - regenerateKeytabs = false; - } + if(!includedInFilter) { + // If this principal is to be filtered out, skip... unless is has not yet been created... + regenerateKeytabs = false; + } - KerberosPrincipalEntity principalEntity = kerberosPrincipalDAO.find(resolvedPrincipal.getPrincipal()); - String cachedKeytabPath = (principalEntity == null) ? null : principalEntity.getCachedKeytabPath(); + KerberosPrincipalEntity principalEntity = kerberosPrincipalDAO.find(resolvedPrincipal.getPrincipal()); + String cachedKeytabPath = (principalEntity == null) ? null : principalEntity.getCachedKeytabPath(); - if (password == null) { - if (!regenerateKeytabs && hostName.equalsIgnoreCase(KerberosHelper.AMBARI_SERVER_HOST_NAME)) { - // There is nothing to do for this since it must already exist and we don't want to - // regenerate the keytab - message = String.format("Skipping keytab file for %s, missing password indicates nothing to do", resolvedPrincipal.getPrincipal()); - LOG.info(message); - } else { - if (cachedKeytabPath == null) { - message = String.format("Failed to create keytab for %s, missing cached file", resolvedPrincipal.getPrincipal()); - actionLog.writeStdErr(message); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + if (password == null) { + if (!regenerateKeytabs && hostName.equalsIgnoreCase(KerberosHelper.AMBARI_SERVER_HOST_NAME)) { + // There is nothing to do for this since it must already exist and we don't want to + // regenerate the keytab + message = String.format("Skipping keytab file for %s, missing password indicates nothing to do", resolvedPrincipal.getPrincipal()); + LOG.info(message); } else { - try { - operationHandler.createKeytabFile(new File(cachedKeytabPath), destinationKeytabFile); - } catch (KerberosOperationException e) { - message = String.format("Failed to create keytab file for %s - %s", resolvedPrincipal.getPrincipal(), e.getMessage()); + if (cachedKeytabPath == null) { + message = String.format("Failed to create keytab for %s, missing cached file", resolvedPrincipal.getPrincipal()); actionLog.writeStdErr(message); - LOG.error(message, e); + LOG.error(message); commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } else { + try { + operationHandler.createKeytabFile(new File(cachedKeytabPath), destinationKeytabFile); + } catch (KerberosOperationException e) { + message = String.format("Failed to create keytab file for %s - %s", resolvedPrincipal.getPrincipal(), e.getMessage()); + actionLog.writeStdErr(message); + LOG.error(message, e); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } } } - } - } else { - Keytab keytab = createKeytab(resolvedPrincipal.getPrincipal(), password, keyNumber, operationHandler, visitedPrincipalKeys != null, true, actionLog); - - if (keytab != null) { - try { - if (operationHandler.createKeytabFile(keytab, destinationKeytabFile)) { - ensureAmbariOnlyAccess(destinationKeytabFile); + } else { + Keytab keytab = createKeytab(resolvedPrincipal.getPrincipal(), password, keyNumber, operationHandler, visitedPrincipalKeys != null, true, actionLog); - message = String.format("Successfully created keytab file for %s at %s", resolvedPrincipal.getPrincipal(), destinationKeytabFile.getAbsolutePath()); - LOG.info(message); - auditEventBuilder.withPrincipal(resolvedPrincipal.getPrincipal()).withHostName(hostName).withKeyTabFilePath(destinationKeytabFile.getAbsolutePath()); - } else { - message = String.format("Failed to create keytab file for %s at %s", resolvedPrincipal.getPrincipal(), destinationKeytabFile.getAbsolutePath()); + if (keytab != null) { + try { + if (operationHandler.createKeytabFile(keytab, destinationKeytabFile)) { + ensureAmbariOnlyAccess(destinationKeytabFile); + + message = String.format("Successfully created keytab file for %s at %s", resolvedPrincipal.getPrincipal(), destinationKeytabFile.getAbsolutePath()); + LOG.info(message); + auditEventBuilder.withPrincipal(resolvedPrincipal.getPrincipal()).withHostName(hostName).withKeyTabFilePath(destinationKeytabFile.getAbsolutePath()); + } else { + message = String.format("Failed to create keytab file for %s at %s", resolvedPrincipal.getPrincipal(), destinationKeytabFile.getAbsolutePath()); + actionLog.writeStdErr(message); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } + } catch (KerberosOperationException e) { + message = String.format("Failed to create keytab file for %s - %s", resolvedPrincipal.getPrincipal(), e.getMessage()); actionLog.writeStdErr(message); - LOG.error(message); + LOG.error(message, e); commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); } - } catch (KerberosOperationException e) { - message = String.format("Failed to create keytab file for %s - %s", resolvedPrincipal.getPrincipal(), e.getMessage()); - actionLog.writeStdErr(message); - LOG.error(message, e); + } else { commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); } - } else { - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); - } - if (visitedPrincipalKeys == null) { - visitedPrincipalKeys = new HashSet<>(); - visitedIdentities.put(resolvedPrincipal.getPrincipal(), visitedPrincipalKeys); - } + if (visitedPrincipalKeys == null) { + visitedPrincipalKeys = new HashSet<>(); + visitedIdentities.put(resolvedPrincipal.getPrincipal(), visitedPrincipalKeys); + } - visitedPrincipalKeys.add(visitationKey); + visitedPrincipalKeys.add(visitationKey); + } + } else { + message = String.format("Failed to create keytab file for %s, the container directory does not exist: %s", + resolvedPrincipal.getPrincipal(), hostDirectory.getAbsolutePath()); + actionLog.writeStdErr(message); + LOG.error(message); + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); } } else { - message = String.format("Failed to create keytab file for %s, the container directory does not exist: %s", - resolvedPrincipal.getPrincipal(), hostDirectory.getAbsolutePath()); - actionLog.writeStdErr(message); - LOG.error(message); - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + LOG.debug("Skipping previously processed keytab for {} on host {}", resolvedPrincipal.getPrincipal(), hostName); } - } else { - LOG.debug("Skipping previously processed keytab for {} on host {}", resolvedPrincipal.getPrincipal(), hostName); + } finally { + lock.unlock(); } } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java index 4e710d5..7415d29 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.actionmanager.HostRoleStatus; @@ -39,6 +40,7 @@ import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.util.concurrent.Striped; import com.google.inject.Inject; /** @@ -68,6 +70,11 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { private KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO; /** + * Used to prevent multiple threads from working with the same principal. + */ + private Striped<Lock> locksByPrincipal = Striped.lazyWeakLock(25); + + /** * A set of visited principal names used to prevent unnecessary processing on already processed * principal names */ @@ -161,32 +168,40 @@ public class CreatePrincipalsServerAction extends KerberosServerAction { if (processPrincipal) { Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext); - String password = principalPasswordMap.get(resolvedPrincipal.getPrincipal()); - - if (password == null) { - CreatePrincipalResult result = createPrincipal(resolvedPrincipal.getPrincipal(), servicePrincipal, kerberosConfiguration, operationHandler, regenerateKeytabs, actionLog); - if (result == null) { - commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); - } else { - Map<String, Integer> principalKeyNumberMap = getPrincipalKeyNumberMap(requestSharedDataContext); - - principalPasswordMap.put(resolvedPrincipal.getPrincipal(), result.getPassword()); - principalKeyNumberMap.put(resolvedPrincipal.getPrincipal(), result.getKeyNumber()); - // invalidate given principal for all keytabs to make them redistributed again - for (KerberosKeytabPrincipalEntity kkpe: kerberosKeytabPrincipalDAO.findByPrincipal(resolvedPrincipal.getPrincipal())) { - kkpe.setDistributed(false); - kerberosKeytabPrincipalDAO.merge(kkpe); - } - // invalidate principal cache - KerberosPrincipalEntity principalEntity = kerberosPrincipalDAO.find(resolvedPrincipal.getPrincipal()); - try { - new File(principalEntity.getCachedKeytabPath()).delete(); - } catch (Exception e) { - LOG.debug("Failed to delete cache file '{}'", principalEntity.getCachedKeytabPath()); + String principal = resolvedPrincipal.getPrincipal(); + Lock lock = locksByPrincipal.get(principal); + lock.lock(); + + String password = principalPasswordMap.get(principal); + + try { + if (password == null) { + CreatePrincipalResult result = createPrincipal(resolvedPrincipal.getPrincipal(), servicePrincipal, kerberosConfiguration, operationHandler, regenerateKeytabs, actionLog); + if (result == null) { + commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(), actionLog.getStdErr()); + } else { + Map<String, Integer> principalKeyNumberMap = getPrincipalKeyNumberMap(requestSharedDataContext); + + principalPasswordMap.put(resolvedPrincipal.getPrincipal(), result.getPassword()); + principalKeyNumberMap.put(resolvedPrincipal.getPrincipal(), result.getKeyNumber()); + // invalidate given principal for all keytabs to make them redistributed again + for (KerberosKeytabPrincipalEntity kkpe: kerberosKeytabPrincipalDAO.findByPrincipal(resolvedPrincipal.getPrincipal())) { + kkpe.setDistributed(false); + kerberosKeytabPrincipalDAO.merge(kkpe); + } + // invalidate principal cache + KerberosPrincipalEntity principalEntity = kerberosPrincipalDAO.find(resolvedPrincipal.getPrincipal()); + try { + new File(principalEntity.getCachedKeytabPath()).delete(); + } catch (Exception e) { + LOG.debug("Failed to delete cache file '{}'", principalEntity.getCachedKeytabPath()); + } + principalEntity.setCachedKeytabPath(null); + kerberosPrincipalDAO.merge(principalEntity); } - principalEntity.setCachedKeytabPath(null); - kerberosPrincipalDAO.merge(principalEntity); } + } finally { + lock.unlock(); } } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java index b3b3082..12369c6 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.actionmanager.HostRoleStatus; @@ -35,6 +36,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.util.concurrent.Striped; import com.google.inject.Inject; public class FinalizeKerberosServerAction extends KerberosServerAction { @@ -42,6 +44,11 @@ public class FinalizeKerberosServerAction extends KerberosServerAction { private final TopologyHolder topologyHolder; + /** + * Used to prevent multiple threads from working with the same keytab. + */ + private Striped<Lock> m_locksByKeytab = Striped.lazyWeakLock(25); + @Inject public FinalizeKerberosServerAction(TopologyHolder topologyHolder) { this.topologyHolder = topologyHolder; @@ -91,7 +98,13 @@ public class FinalizeKerberosServerAction extends KerberosServerAction { String keytabFilePath = resolvedPrincipal.getKeytabPath(); - if (!StringUtils.isEmpty(keytabFilePath)) { + if (StringUtils.isEmpty(keytabFilePath)) { + return null; + } + + Lock lock = m_locksByKeytab.get(keytabFilePath); + lock.lock(); + try { Set<String> visited = (Set<String>) requestSharedDataContext.get(this.getClass().getName() + "_visited"); if (!visited.contains(keytabFilePath)) { @@ -154,6 +167,8 @@ public class FinalizeKerberosServerAction extends KerberosServerAction { visited.add(keytabFilePath); } + } finally { + lock.unlock(); } } } diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java index 55e90ca..468121e 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java @@ -21,15 +21,26 @@ package org.apache.ambari.server.serveraction.kerberos; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.actionmanager.HostRoleStatus; import org.apache.ambari.server.agent.CommandReport; import org.apache.ambari.server.agent.ExecutionCommand; +import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.controller.KerberosHelper; import org.apache.ambari.server.controller.UpdateConfigurationPolicy; import org.apache.ambari.server.orm.dao.HostDAO; @@ -42,6 +53,8 @@ import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerbero import org.apache.ambari.server.state.Cluster; import org.apache.ambari.server.state.Clusters; import org.apache.ambari.server.state.Config; +import org.apache.ambari.server.state.Host; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; import org.apache.ambari.server.utils.StageUtils; import org.apache.commons.io.FileUtils; @@ -50,6 +63,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; /** @@ -189,6 +203,9 @@ public abstract class KerberosServerAction extends AbstractServerAction { @Inject private KerberosKeytabController kerberosKeytabController; + @Inject + private Configuration configuration; + /** * Given a (command parameter) Map and a property name, attempts to safely retrieve the requested * data. @@ -403,6 +420,9 @@ public abstract class KerberosServerAction extends AbstractServerAction { * Using {@link #getHostFilter()}, {@link #getIdentityFilter()} and {@link #getServiceComponentFilter()} it retrieve * list of filtered keytabs and their principals and process each principal using * {@link #processIdentity(ResolvedKerberosPrincipal, KerberosOperationHandler, Map, boolean, Map)}. + * The configuration option {@link Configuration#getKerberosServerActionThreadpoolSize()} defines + * how many threads will handle {@link #processIdentity(ResolvedKerberosPrincipal, KerberosOperationHandler, Map, boolean, Map)}. + * The default is {@code 1}, but this method must be thread-safe in the event that concurrent threads are used. * * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related * to a given request @@ -437,20 +457,67 @@ public abstract class KerberosServerAction extends AbstractServerAction { } try { + // create the thread factory, executor, and completion service for + // running the identity processing in parallel + String factoryName = "process-identity-%d"; + ExecutionCommand executionCommand = getExecutionCommand(); + if( null != executionCommand ) { + factoryName = "process-identity-task-" + executionCommand.getTaskId() + "-thread-%d"; + } + + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(factoryName).build(); + + int threadCount = configuration.getKerberosServerActionThreadpoolSize(); + ExecutorService executorService = Executors.newFixedThreadPool(threadCount, threadFactory); + CompletionService<CommandReport> completionService = new ExecutorCompletionService<>(executorService); + Map<String, Collection<String>> serviceComponentFilter = getServiceComponentFilter(); if (serviceComponentFilter != null && pruneServiceFilter()) { kerberosKeytabController.adjustServiceComponentFilter(clusters.getCluster(getClusterName()), true, serviceComponentFilter); } final Collection<KerberosIdentityDescriptor> serviceIdentities = serviceComponentFilter == null ? null : kerberosKeytabController.getServiceIdentities(getClusterName(), serviceComponentFilter.keySet()); + List<Future<CommandReport>> futures = new ArrayList<>(); for (ResolvedKerberosKeytab rkk : kerberosKeytabController.getFilteredKeytabs(serviceIdentities, getHostFilter(),getIdentityFilter())) { for (ResolvedKerberosPrincipal principal : rkk.getPrincipals()) { - commandReport = processIdentity(principal, handler, kerberosConfiguration, isRelevantIdentity(serviceIdentities, principal), requestSharedDataContext); + // submit this method to the service to be processed concurrently + Future<CommandReport> future = completionService.submit(() -> { + try { + return processIdentity(principal, handler, kerberosConfiguration, + isRelevantIdentity(serviceIdentities, principal), requestSharedDataContext); + } catch (AmbariException ambariException) { + throw new RuntimeException(ambariException); + } + }); + + // keep track of futures for total count and ability to cancel later + futures.add(future); + } + } + + LOG.info("Processing {} identities concurrently...", futures.size()); + + // get each future as it completes (out of order is OK), cancelling if + // an error is found + try { + for( int i = 0; i < futures.size(); i++ ) { + Future<CommandReport> future = completionService.take(); + commandReport = future.get(); + // If the principal processor returns a CommandReport, than it is time to stop // since an error condition has probably occurred, else all is assumed to be well. if (commandReport != null) { break; } } + } catch (Exception exception) { + LOG.error("Unable to process identities asynchronously", exception); + return createCommandReport(0, HostRoleStatus.FAILED, "{}", actionLog.getStdOut(),actionLog.getStdErr()); + } finally { + futures.stream() + .filter(x -> !x.isCancelled() && !x.isDone()) + .forEach(x -> x.cancel(true)); + + executorService.shutdown(); } } finally { // The KerberosOperationHandler needs to be closed, if it fails to close ignore the diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/stageutils/KerberosKeytabController.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/stageutils/KerberosKeytabController.java index ec01310..cac811e 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/stageutils/KerberosKeytabController.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/stageutils/KerberosKeytabController.java @@ -35,8 +35,12 @@ import org.apache.ambari.server.orm.entities.KerberosKeytabEntity; import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity; import org.apache.ambari.server.orm.entities.KerberosPrincipalEntity; import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.Host; import org.apache.ambari.server.state.Service; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; +import org.apache.ambari.server.utils.StageUtils; import org.apache.commons.collections.MapUtils; import com.google.common.collect.ImmutableSet; @@ -56,6 +60,9 @@ public class KerberosKeytabController { @Inject private KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO; + @Inject + private Clusters clusters; + //TODO: due to circular dependencies in Guice this field cannot be injected with Guice's @Inject annotation; for now we should statically inject in AmbariServer private static KerberosHelper kerberosHelper; @@ -100,7 +107,10 @@ public class KerberosKeytabController { * @return set of keytabs found */ public Set<ResolvedKerberosKeytab> getFromPrincipal(ResolvedKerberosPrincipal rkp) { - return fromKeytabEntities(kerberosKeytabDAO.findByPrincipalAndHost(rkp.getPrincipal(), rkp.getHostId())); + List<KerberosKeytabEntity> keytabs = kerberosKeytabDAO.findByPrincipalAndHost( + rkp.getPrincipal(), rkp.getHostId()); + + return fromKeytabEntities(keytabs); } /** @@ -224,6 +234,7 @@ public class KerberosKeytabController { ImmutableSet.Builder<ResolvedKerberosPrincipal> builder = ImmutableSet.builder(); for (KerberosKeytabPrincipalEntity kkpe : principalEntities) { KerberosPrincipalEntity kpe = kkpe.getKerberosPrincipalEntity(); + if(kpe != null) { ResolvedKerberosPrincipal rkp = new ResolvedKerberosPrincipal( kkpe.getHostId(), @@ -281,8 +292,33 @@ public class KerberosKeytabController { public Collection<KerberosIdentityDescriptor> getServiceIdentities(String clusterName, Collection<String> services) throws AmbariException { final Collection<KerberosIdentityDescriptor> serviceIdentities = new ArrayList<>(); + Cluster cluster = clusters.getCluster(clusterName); + KerberosDescriptor kerberosDescriptor = kerberosHelper.getKerberosDescriptor(cluster, false); + Map<String, Map<String, Map<String, String>>> hostConfigurations = new HashMap<>(); + Map<String, Host> hostMap = clusters.getHostsForCluster(clusterName); + Set<String> hosts = new HashSet<>(hostMap.keySet()); + + String ambariServerHostname = StageUtils.getHostName(); + if (!hosts.contains(ambariServerHostname)) { + hosts.add(ambariServerHostname); + } + for( String hostName : hosts ) { + Map<String, Map<String, String>> configurations = kerberosHelper.calculateConfigurations( + cluster, hostName, kerberosDescriptor, false, false); + hostConfigurations.put(hostName, configurations); + } for (String service : services) { - for (Collection<KerberosIdentityDescriptor> activeIdentities : kerberosHelper.getActiveIdentities(clusterName, null, service, null, true).values()) { + Collection<Collection<KerberosIdentityDescriptor>> identities = kerberosHelper.getActiveIdentities( + clusterName, + null, + service, + null, + true, + hostConfigurations, + kerberosDescriptor + ).values(); + + for (Collection<KerberosIdentityDescriptor> activeIdentities : identities) { serviceIdentities.addAll(activeIdentities); } } diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java index c2e43e9..a31445b 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java @@ -84,6 +84,7 @@ import org.apache.ambari.server.orm.DBAccessor; import org.apache.ambari.server.orm.dao.ArtifactDAO; import org.apache.ambari.server.orm.dao.HostRoleCommandDAO; import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO; +import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO.KeytabPrincipalFindOrCreateResult; import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO; import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity; import org.apache.ambari.server.scheduler.ExecutionScheduler; @@ -3429,7 +3430,12 @@ public class KerberosHelperTest extends EasyMockSupport { private void testCreateTestIdentity(final PrincipalKeyCredential PrincipalKeyCredential, Boolean manageIdentities) throws Exception { KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class); KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO = injector.getInstance(KerberosKeytabPrincipalDAO.class); - expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), anyObject(), anyObject())).andReturn(createNiceMock(KerberosKeytabPrincipalEntity.class)).anyTimes(); + KerberosKeytabPrincipalEntity kkp = createNiceMock(KerberosKeytabPrincipalEntity.class); + KeytabPrincipalFindOrCreateResult result = new KeytabPrincipalFindOrCreateResult(); + result.created = true; + result.kkp = kkp; + + expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), anyObject(), anyObject())).andReturn(result).anyTimes(); boolean managingIdentities = !Boolean.FALSE.equals(manageIdentities); final Map<String, String> kerberosEnvProperties = new HashMap<>(); @@ -3620,7 +3626,12 @@ public class KerberosHelperTest extends EasyMockSupport { private void testDeleteTestIdentity(final PrincipalKeyCredential PrincipalKeyCredential) throws Exception { KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class); KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO = injector.getInstance(KerberosKeytabPrincipalDAO.class); - expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), anyObject(), anyObject())).andReturn(createNiceMock(KerberosKeytabPrincipalEntity.class)).anyTimes(); + KerberosKeytabPrincipalEntity kkp = createNiceMock(KerberosKeytabPrincipalEntity.class); + KeytabPrincipalFindOrCreateResult result = new KeytabPrincipalFindOrCreateResult(); + result.created = true; + result.kkp = kkp; + + expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), anyObject(), anyObject())).andReturn(result).anyTimes(); Host host1 = createMock(Host.class); expect(host1.getHostId()).andReturn(1l).anyTimes(); @@ -4051,7 +4062,8 @@ public class KerberosHelperTest extends EasyMockSupport { injector.getInstance(AmbariMetaInfo.class).init(); Map<String, Collection<KerberosIdentityDescriptor>> identities; - identities = kerberosHelper.getActiveIdentities(clusterName, hostName, serviceName, componentName, replaceHostNames); + identities = kerberosHelper.getActiveIdentities(clusterName, hostName, serviceName, + componentName, replaceHostNames, null, null); verifyAll(); diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProviderTest.java index e271932..c9143a9 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProviderTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostKerberosIdentityResourceProviderTest.java @@ -43,10 +43,14 @@ import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO; import org.apache.ambari.server.orm.dao.KerberosPrincipalDAO; import org.apache.ambari.server.orm.entities.HostEntity; import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity; +import org.apache.ambari.server.state.Cluster; +import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.kerberos.KerberosDescriptor; import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor; import org.apache.ambari.server.state.kerberos.KerberosKeytabDescriptor; import org.apache.ambari.server.state.kerberos.KerberosPrincipalDescriptor; import org.apache.ambari.server.state.kerberos.KerberosPrincipalType; +import org.easymock.EasyMock; import org.easymock.EasyMockSupport; import org.junit.Assert; import org.junit.Test; @@ -131,8 +135,12 @@ public class HostKerberosIdentityResourceProviderTest extends EasyMockSupport { @Test public void testGetResources() throws Exception { + Clusters clusters = createNiceMock(Clusters.class); + expect(clusters.getCluster(EasyMock.anyString())).andReturn( + createNiceMock(Cluster.class)).once(); AmbariManagementController managementController = createMock(AmbariManagementController.class); + expect(managementController.getClusters()).andReturn(clusters).atLeastOnce(); KerberosPrincipalDescriptor principalDescriptor1 = createStrictMock(KerberosPrincipalDescriptor.class); expect(principalDescriptor1.getValue()).andReturn("princip...@example.com"); @@ -214,9 +222,15 @@ public class HostKerberosIdentityResourceProviderTest extends EasyMockSupport { activeIdentities.put("Host100", identities); KerberosHelper kerberosHelper = createStrictMock(KerberosHelper.class); - expect(kerberosHelper.getActiveIdentities("Cluster100", "Host100", null, null, true)) + KerberosDescriptor kerberosDescriptor = createNiceMock(KerberosDescriptor.class); + expect(kerberosHelper.getKerberosDescriptor( + EasyMock.anyObject(Cluster.class), + EasyMock.eq(false))).andReturn(kerberosDescriptor).atLeastOnce(); + + expect(kerberosHelper.getActiveIdentities("Cluster100", "Host100", null, null, true, null, + kerberosDescriptor)) .andReturn(activeIdentities) - .times(1); + .once(); // replay replayAll(); diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerActionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerActionTest.java index beeac82..191b6cb 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerActionTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ConfigureAmbariIdentitiesServerActionTest.java @@ -36,11 +36,13 @@ import org.apache.ambari.server.controller.RootService; import org.apache.ambari.server.orm.DBAccessor; import org.apache.ambari.server.orm.dao.HostDAO; import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO; +import org.apache.ambari.server.orm.dao.KerberosKeytabPrincipalDAO.KeytabPrincipalFindOrCreateResult; import org.apache.ambari.server.orm.entities.HostEntity; import org.apache.ambari.server.orm.entities.KerberosKeytabPrincipalEntity; import org.apache.ambari.server.serveraction.ActionLog; import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosPrincipal; import org.apache.ambari.server.state.Clusters; +import org.apache.ambari.server.state.stack.OsFamily; import org.apache.ambari.server.utils.StageUtils; import org.apache.commons.io.FileUtils; import org.easymock.EasyMockSupport; @@ -94,9 +96,13 @@ public class ConfigureAmbariIdentitiesServerActionTest extends EasyMockSupport { expect(hostDAO.findByName(StageUtils.getHostName())).andReturn(hostEntity).once(); KerberosKeytabPrincipalDAO kerberosKeytabPrincipalDAO = injector.getInstance(KerberosKeytabPrincipalDAO.class); - KerberosKeytabPrincipalEntity kke = createNiceMock(KerberosKeytabPrincipalEntity.class); - expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), eq(hostEntity), anyObject())).andReturn(kke).once(); - expect(kerberosKeytabPrincipalDAO.merge(kke)).andReturn(createNiceMock(KerberosKeytabPrincipalEntity.class)).once(); + KerberosKeytabPrincipalEntity kkp = createNiceMock(KerberosKeytabPrincipalEntity.class); + KeytabPrincipalFindOrCreateResult result = new KeytabPrincipalFindOrCreateResult(); + result.created = true; + result.kkp = kkp; + + expect(kerberosKeytabPrincipalDAO.findOrCreate(anyObject(), eq(hostEntity), anyObject())).andReturn(result).once(); + expect(kerberosKeytabPrincipalDAO.merge(kkp)).andReturn(createNiceMock(KerberosKeytabPrincipalEntity.class)).once(); // Mock the methods that do the actual file manipulation to avoid having to deal with ambari-sudo.sh used in // ShellCommandUtil#mkdir, ShellCommandUtil#copyFile, etc.. @@ -215,6 +221,7 @@ public class ConfigureAmbariIdentitiesServerActionTest extends EasyMockSupport { bind(AuditLogger.class).toInstance(createNiceMock(AuditLogger.class)); bind(Clusters.class).toInstance(createNiceMock(Clusters.class)); bind(KerberosHelper.class).toInstance(createNiceMock(KerberosHelper.class)); + bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class)); bind(HostDAO.class).toInstance(createMock(HostDAO.class)); bind(KerberosKeytabPrincipalDAO.class).toInstance(createMock(KerberosKeytabPrincipalDAO.class));