AMBARI-21392. Cleanup relevant Kerberos identities when a service is removed 
(amagyar)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e767aa44
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e767aa44
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e767aa44

Branch: refs/heads/branch-feature-logsearch-ui
Commit: e767aa44d872bab9ac0c416684f80b2b662347e5
Parents: 0b397cd
Author: Attila Magyar <amag...@hortonworks.com>
Authored: Tue Jul 11 20:10:12 2017 +0200
Committer: Attila Magyar <amag...@hortonworks.com>
Committed: Tue Jul 11 20:10:12 2017 +0200

----------------------------------------------------------------------
 .../controller/DeleteIdentityHandler.java       |  77 ++++++++--
 .../server/controller/KerberosHelper.java       |   2 +-
 .../server/controller/KerberosHelperImpl.java   |   5 +-
 .../utilities/KerberosIdentityCleaner.java      |  88 +++--------
 .../utilities/RemovableIdentities.java          | 145 +++++++++++++++++++
 .../controller/utilities/UsedIdentities.java    | 101 +++++++++++++
 .../ServiceComponentUninstalledEvent.java       |   6 +
 .../server/events/ServiceRemovedEvent.java      |  29 ++--
 .../ambari/server/orm/dao/ClusterDAO.java       |  15 ++
 .../orm/entities/ClusterConfigEntity.java       |   3 +
 .../org/apache/ambari/server/state/Cluster.java |   7 +
 .../apache/ambari/server/state/ServiceImpl.java |  14 +-
 .../server/state/cluster/ClusterImpl.java       |   9 ++
 .../AbstractKerberosDescriptorContainer.java    |  12 ++
 .../kerberos/KerberosComponentDescriptor.java   |  15 --
 .../kerberos/KerberosIdentityDescriptor.java    |  14 +-
 .../utilities/KerberosIdentityCleanerTest.java  | 102 +++++++++++--
 .../server/orm/dao/ServiceConfigDAOTest.java    |  12 ++
 18 files changed, 520 insertions(+), 136 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
index aa098b6..3329e76 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
@@ -17,12 +17,13 @@
  */
 package org.apache.ambari.server.controller;
 
-import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
 import static 
org.apache.ambari.server.controller.KerberosHelperImpl.BASE_LOG_DIR;
 
 import java.io.File;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -45,10 +46,15 @@ import 
org.apache.ambari.server.serveraction.kerberos.KDCType;
 import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler;
 import org.apache.ambari.server.serveraction.kerberos.KerberosServerAction;
 import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Config;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
 import 
org.apache.ambari.server.state.svccomphost.ServiceComponentHostServerActionEvent;
 import org.apache.ambari.server.utils.StageUtils;
 
+import com.google.gson.reflect.TypeToken;
+
+
 /**
  * I delete kerberos identities (principals and keytabs) of a given component.
  */
@@ -78,7 +84,7 @@ class DeleteIdentityHandler {
     if (manageIdentities) {
       addPrepareDeleteIdentity(cluster, hostParamsJson, event, 
commandParameters, stageContainer);
       addDestroyPrincipals(cluster, hostParamsJson, event, commandParameters, 
stageContainer);
-      addDeleteKeytab(cluster, 
newHashSet(commandParameters.component.getHostName()), hostParamsJson, 
commandParameters, stageContainer);
+      addDeleteKeytab(cluster, commandParameters.getAffectedHostNames(), 
hostParamsJson, commandParameters, stageContainer);
     }
     addFinalize(cluster, hostParamsJson, event, stageContainer, 
commandParameters);
   }
@@ -172,15 +178,15 @@ class DeleteIdentityHandler {
 
 
   public static class CommandParams {
-    private final Component component;
-    private final List<String> identities;
+    private final List<Component> components;
+    private final Set<String> identities;
     private final String authName;
     private final File dataDirectory;
     private final String defaultRealm;
     private final KDCType kdcType;
 
-    public CommandParams(Component component, List<String> identities, String 
authName, File dataDirectory, String defaultRealm, KDCType kdcType) {
-      this.component = component;
+    public CommandParams(List<Component> components, Set<String> identities, 
String authName, File dataDirectory, String defaultRealm, KDCType kdcType) {
+      this.components = components;
       this.identities = identities;
       this.authName = authName;
       this.dataDirectory = dataDirectory;
@@ -194,11 +200,15 @@ class DeleteIdentityHandler {
       commandParameters.put(KerberosServerAction.DEFAULT_REALM, defaultRealm);
       commandParameters.put(KerberosServerAction.KDC_TYPE, kdcType.name());
       commandParameters.put(KerberosServerAction.IDENTITY_FILTER, 
StageUtils.getGson().toJson(identities));
-      commandParameters.put(KerberosServerAction.COMPONENT_FILTER, 
StageUtils.getGson().toJson(component));
+      commandParameters.put(KerberosServerAction.COMPONENT_FILTER, 
StageUtils.getGson().toJson(components));
       commandParameters.put(KerberosServerAction.DATA_DIRECTORY, 
dataDirectory.getAbsolutePath());
       return commandParameters;
     }
 
+    public Set<String> getAffectedHostNames() {
+      return components.stream().map(Component::getHostName).collect(toSet());
+    }
+
     public String asJson() {
       return StageUtils.getGson().toJson(asMap());
     }
@@ -211,22 +221,57 @@ class DeleteIdentityHandler {
       processServiceComponents(
         getCluster(),
         kerberosDescriptor,
-        Collections.singletonList(getComponentFilter()),
+        componentFilter(),
         getIdentityFilter(),
         dataDirectory(),
-        calculateConfig(kerberosDescriptor),
-        new HashMap<String, Map<String, String>>(),
+        calculateConfig(kerberosDescriptor, serviceNames()),
+        new HashMap<>(),
         false,
-        new HashMap<String, Set<String>>());
+        new HashMap<>());
       return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", 
actionLog.getStdOut(), actionLog.getStdErr());
     }
 
-    protected Component getComponentFilter() {
-      return 
StageUtils.getGson().fromJson(getCommandParameterValue(KerberosServerAction.COMPONENT_FILTER),
 Component.class);
+    private Set<String> serviceNames() {
+      return componentFilter().stream().map(component -> 
component.getServiceName()).collect(toSet());
+    }
+
+    private List<Component> componentFilter() {
+      Type jsonType = new TypeToken<List<Component>>() {}.getType();
+      return 
StageUtils.getGson().fromJson(getCommandParameterValue(KerberosServerAction.COMPONENT_FILTER),
 jsonType);
+    }
+
+    /**
+     * Cleaning identities is asynchronous, it can happen that the service and 
its configuration is already deleted at this point.
+     * We're extending the actual config with the properties of the latest 
deleted configuration of the service.
+     * The service configuration is needed because principal names may contain 
placeholder variables which are replaced based on the service configuration.
+     */
+    private Map<String, Map<String, String>> 
calculateConfig(KerberosDescriptor kerberosDescriptor, Set<String> 
serviceNames) throws AmbariException {
+      Map<String, Map<String, String>> actualConfig = 
getKerberosHelper().calculateConfigurations(getCluster(), null, 
kerberosDescriptor.getProperties());
+      extendWithDeletedConfigOfService(actualConfig, serviceNames);
+      return actualConfig;
+    }
+
+    private void extendWithDeletedConfigOfService(Map<String, Map<String, 
String>> configToBeExtended, Set<String> serviceNames) throws AmbariException {
+      Set<String> deletedConfigTypes = serviceNames.stream()
+        .flatMap(serviceName -> configTypesOfService(serviceName).stream())
+        .collect(toSet());
+      for (Config deletedConfig : 
getCluster().getLatestConfigsWithTypes(deletedConfigTypes)) {
+        configToBeExtended.put(deletedConfig.getType(), 
deletedConfig.getProperties());
+      }
     }
 
-    private Map<String, Map<String, String>> 
calculateConfig(KerberosDescriptor kerberosDescriptor) throws AmbariException {
-      return getKerberosHelper().calculateConfigurations(getCluster(), null, 
kerberosDescriptor.getProperties());
+    private Set<String> configTypesOfService(String serviceName) {
+      try {
+        StackId stackId = getCluster().getCurrentStackVersion();
+        StackServiceRequest stackServiceRequest = new 
StackServiceRequest(stackId.getStackName(), stackId.getStackVersion(), 
serviceName);
+        return 
AmbariServer.getController().getStackServices(singleton(stackServiceRequest)).stream()
+          .findFirst()
+          .orElseThrow(() -> new IllegalArgumentException("Could not find 
stack service " + serviceName))
+          .getConfigTypes()
+          .keySet();
+      } catch (AmbariException e) {
+        throw new RuntimeException(e);
+      }
     }
 
     private String dataDirectory() {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/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 cc0c048..3819863 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
@@ -233,7 +233,7 @@ public interface KerberosHelper {
                                          RequestStageContainer 
requestStageContainer, Boolean manageIdentities)
       throws AmbariException, KerberosOperationException;
 
-  void deleteIdentity(Cluster cluster, Component component, List<String> 
identities) throws AmbariException, KerberosOperationException;
+  void deleteIdentities(Cluster cluster, List<Component> components, 
Set<String> identities) throws AmbariException, KerberosOperationException;
 
   /**
    * Updates the relevant configurations for the components specified in the 
service filter.

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/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 b30f8f6..e5b7afd 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
@@ -301,17 +301,18 @@ public class KerberosHelperImpl implements KerberosHelper 
{
    * Deletes the kerberos identities of the given component, even if the 
component is already deleted.
    */
   @Override
-  public void deleteIdentity(Cluster cluster, Component component, 
List<String> identities) throws AmbariException, KerberosOperationException {
+  public void deleteIdentities(Cluster cluster, List<Component> components, 
Set<String> identities) throws AmbariException, KerberosOperationException {
     if (identities.isEmpty()) {
       return;
     }
+    LOG.info("Deleting identities: ", identities);
     KerberosDetails kerberosDetails = getKerberosDetails(cluster, null);
     validateKDCCredentials(kerberosDetails, cluster);
     File dataDirectory = createTemporaryDirectory();
     RoleCommandOrder roleCommandOrder = 
ambariManagementController.getRoleCommandOrder(cluster);
     DeleteIdentityHandler handler = new 
DeleteIdentityHandler(customCommandExecutionHelper, 
configuration.getDefaultServerTaskTimeout(), stageFactory, 
ambariManagementController);
     DeleteIdentityHandler.CommandParams commandParameters = new 
DeleteIdentityHandler.CommandParams(
-      component,
+      components,
       identities,
       ambariManagementController.getAuthName(),
       dataDirectory,

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
index 0a8462f..7ec4a6e 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
@@ -17,26 +17,12 @@
  */
 package org.apache.ambari.server.controller.utilities;
 
-import static 
org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor.nullToEmpty;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
+import org.apache.ambari.server.events.ServiceRemovedEvent;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
-import org.apache.ambari.server.serveraction.kerberos.Component;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException;
-import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
-import org.apache.ambari.server.state.SecurityType;
-import org.apache.ambari.server.state.Service;
-import org.apache.ambari.server.state.kerberos.KerberosComponentDescriptor;
-import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
-import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor;
-import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,67 +55,29 @@ public class KerberosIdentityCleaner {
   @Subscribe
   public void componentRemoved(ServiceComponentUninstalledEvent event) throws 
KerberosMissingAdminCredentialsException {
     try {
-      Cluster cluster = clusters.getCluster(event.getClusterId());
-      if (cluster.getSecurityType() != SecurityType.KERBEROS) {
-        return;
-      }
-      KerberosComponentDescriptor descriptor = componentDescriptor(cluster, 
event.getServiceName(), event.getComponentName());
-      if (descriptor == null) {
-        LOG.info("No kerberos descriptor for {}", event);
-        return;
-      }
-      List<String> identitiesToRemove = 
identityNames(skipSharedIdentities(descriptor.getIdentitiesSkipReferences(), 
cluster, event));
-      LOG.info("Deleting identities {} after an event {}",  
identitiesToRemove, event);
-      kerberosHelper.deleteIdentity(cluster, new 
Component(event.getHostName(), event.getServiceName(), 
event.getComponentName()), identitiesToRemove);
+      LOG.info("Removing identities after {}", event);
+      RemovableIdentities
+        .ofComponent(clusters.getCluster(event.getClusterId()), event, 
kerberosHelper)
+        .remove(kerberosHelper);
     } catch (Exception e) {
       LOG.error("Error while deleting kerberos identity after an event: " + 
event, e);
     }
   }
 
-  private KerberosComponentDescriptor componentDescriptor(Cluster cluster, 
String serviceName, String componentName) throws AmbariException {
-    KerberosServiceDescriptor serviceDescriptor = 
kerberosHelper.getKerberosDescriptor(cluster).getService(serviceName);
-    return serviceDescriptor == null ? null : 
serviceDescriptor.getComponent(componentName);
-  }
-
-  private List<String> identityNames(List<KerberosIdentityDescriptor> 
identities) {
-    List<String> result = new ArrayList<>();
-    for (KerberosIdentityDescriptor each : identities) { 
result.add(each.getName()); }
-    return result;
-  }
-
-  private List<KerberosIdentityDescriptor> 
skipSharedIdentities(List<KerberosIdentityDescriptor> candidates, Cluster 
cluster, ServiceComponentUninstalledEvent event) throws AmbariException {
-    List<KerberosIdentityDescriptor> activeIdentities = 
activeIdentities(cluster, kerberosHelper.getKerberosDescriptor(cluster), event);
-    List<KerberosIdentityDescriptor> result = new ArrayList<>();
-    for (KerberosIdentityDescriptor candidate : candidates) {
-      if (!candidate.isShared(activeIdentities)) {
-        result.add(candidate);
-      } else {
-        LOG.debug("Skip removing shared identity: {}", candidate.getName());
-      }
-    }
-    return result;
-  }
-
-  private List<KerberosIdentityDescriptor> activeIdentities(Cluster cluster, 
KerberosDescriptor root, ServiceComponentUninstalledEvent event) {
-    List<KerberosIdentityDescriptor> result = new ArrayList<>();
-    result.addAll(nullToEmpty(root.getIdentities()));
-    for (Map.Entry<String, Service> serviceEntry : 
cluster.getServices().entrySet()) {
-      KerberosServiceDescriptor serviceDescriptor = 
root.getService(serviceEntry.getKey());
-      if (serviceDescriptor == null) {
-        continue;
-      }
-      result.addAll(nullToEmpty(serviceDescriptor.getIdentities()));
-      for (String componentName : 
serviceEntry.getValue().getServiceComponents().keySet()) {
-        if (!sameComponent(event, componentName, serviceEntry.getKey())) {
-          
result.addAll(serviceDescriptor.getComponentIdentities(componentName));
-        }
-      }
+  /**
+   * Removes kerberos identities (principals and keytabs) after a service was 
uninstalled.
+   * Keeps the identity if either the principal or the keytab is used by an 
other service
+   */
+  @Subscribe
+  public void serviceRemoved(ServiceRemovedEvent event) {
+    try {
+      LOG.info("Removing identities after {}", event);
+      RemovableIdentities
+        .ofService(clusters.getCluster(event.getClusterId()), event, 
kerberosHelper)
+        .remove(kerberosHelper);
+    } catch (Exception e) {
+      LOG.error("Error while deleting kerberos identity after an event: " + 
event, e);
     }
-    return result;
-  }
-
-  private boolean sameComponent(ServiceComponentUninstalledEvent event, String 
componentName, String serviceName) {
-    return event.getServiceName().equals(serviceName) && 
event.getComponentName().equals(componentName);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/RemovableIdentities.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/RemovableIdentities.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/RemovableIdentities.java
new file mode 100644
index 0000000..d4bb501
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/RemovableIdentities.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.utilities;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.KerberosHelper;
+import 
org.apache.ambari.server.controller.utilities.UsedIdentities.ComponentExclude;
+import 
org.apache.ambari.server.controller.utilities.UsedIdentities.ServiceExclude;
+import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
+import org.apache.ambari.server.events.ServiceRemovedEvent;
+import org.apache.ambari.server.serveraction.kerberos.Component;
+import 
org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.SecurityType;
+import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor;
+import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
+
+/**
+ * I represent a group of kerberos identities which are to be deleted after a 
service or a component was removed.
+ * My instances provide methods for removing the candidates, excluding those 
that are still used by other components or services.
+ */
+public class RemovableIdentities {
+  private final List<KerberosIdentityDescriptor> candidateIdentities;
+  private final UsedIdentities usedIdentities;
+  private final Cluster cluster;
+  private final List<Component> components;
+
+  /**
+   * Populate the identities with the identities of the removed service and 
its components
+   */
+  public static RemovableIdentities ofService(Cluster cluster, 
ServiceRemovedEvent event, KerberosHelper kerberosHelper) throws 
AmbariException {
+    if (cluster.getSecurityType() != SecurityType.KERBEROS) {
+      return RemovableIdentities.none();
+    }
+    KerberosServiceDescriptor serviceDescriptor = 
kerberosHelper.getKerberosDescriptor(cluster).getService(event.getServiceName());
+    if (serviceDescriptor == null) {
+      return RemovableIdentities.none();
+    }
+    UsedIdentities usedIdentities = UsedIdentities.populate(cluster, 
excludeService(event.getServiceName()), ComponentExclude.NONE, kerberosHelper);
+    return new RemovableIdentities(
+      serviceDescriptor.getIdentitiesSkipReferences(),
+      usedIdentities,
+      cluster,
+      event.getComponents());
+  }
+
+  /**
+   * Populate the identities with the identities of the removed component
+   */
+  public static RemovableIdentities ofComponent(Cluster cluster, 
ServiceComponentUninstalledEvent event, KerberosHelper kerberosHelper) throws 
AmbariException {
+    if (cluster.getSecurityType() != SecurityType.KERBEROS) {
+      return RemovableIdentities.none();
+    }
+    KerberosServiceDescriptor serviceDescriptor = 
kerberosHelper.getKerberosDescriptor(cluster).getService(event.getServiceName());
+    if (serviceDescriptor == null) {
+      return RemovableIdentities.none();
+    }
+    UsedIdentities usedIdentities = UsedIdentities.populate(
+      cluster,
+      ServiceExclude.NONE,
+      excludeComponent(event.getServiceName(), event.getComponentName(), 
event.getHostName()),
+      kerberosHelper);
+    return new RemovableIdentities(
+      componentIdentities(singletonList(event.getComponentName()), 
serviceDescriptor),
+      usedIdentities,
+      cluster,
+      singletonList(event.getComponent()));
+  }
+
+  /**
+   * Populates the identities with an empty list
+   */
+  public static RemovableIdentities none() throws AmbariException {
+    return new RemovableIdentities(emptyList(), UsedIdentities.none(), null, 
null);
+  }
+
+  private static ServiceExclude excludeService(String excludedServiceName) {
+    return serviceName -> excludedServiceName.equals(serviceName);
+  }
+
+  private static ComponentExclude excludeComponent(String excludedServiceName, 
String excludedComponentName, String excludedHostName) {
+    return (serviceName, componentName, hosts) -> 
excludedServiceName.equals(serviceName)
+      && excludedComponentName.equals(componentName)
+      && hostNames(hosts).equals(singletonList(excludedHostName));
+  }
+
+  private static List<String> hostNames(Collection<ServiceComponentHost> 
hosts) {
+    return 
hosts.stream().map(ServiceComponentHost::getHostName).collect(toList());
+  }
+
+  private static List<KerberosIdentityDescriptor> 
componentIdentities(List<String> componentNames, KerberosServiceDescriptor 
serviceDescriptor) throws AmbariException {
+    return componentNames.stream()
+      .map(componentName -> serviceDescriptor.getComponent(componentName))
+      .filter(Objects::nonNull)
+      .flatMap(componentDescriptor -> 
componentDescriptor.getIdentitiesSkipReferences().stream())
+      .collect(toList());
+  }
+
+  private RemovableIdentities(List<KerberosIdentityDescriptor> 
candidateIdentities, UsedIdentities usedIdentities, Cluster cluster, 
List<Component> components) {
+    this.candidateIdentities = candidateIdentities;
+    this.usedIdentities = usedIdentities;
+    this.cluster = cluster;
+    this.components = components;
+  }
+
+  /**
+   * Remove all identities which are not used by other services or components
+   */
+  public void remove(KerberosHelper kerberosHelper) throws AmbariException, 
KerberosOperationException {
+    Set<String> identitiesToRemove = 
skipUsed().stream().map(KerberosIdentityDescriptor::getName).collect(toSet());
+    if (!identitiesToRemove.isEmpty()) {
+      kerberosHelper.deleteIdentities(cluster, components, identitiesToRemove);
+    }
+  }
+
+  private List<KerberosIdentityDescriptor> skipUsed() throws AmbariException {
+    return candidateIdentities.stream().filter(each -> 
!usedIdentities.contains(each)).collect(toList());
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/UsedIdentities.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/UsedIdentities.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/UsedIdentities.java
new file mode 100644
index 0000000..46f5642
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/UsedIdentities.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.utilities;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toList;
+import static 
org.apache.ambari.server.state.kerberos.AbstractKerberosDescriptor.nullToEmpty;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.state.Cluster;
+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.kerberos.KerberosDescriptor;
+import org.apache.ambari.server.state.kerberos.KerberosIdentityDescriptor;
+import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
+
+/**
+ * I represent a group of identities that are still used by any non-excluded 
component or service
+ */
+public class UsedIdentities {
+  private final List<KerberosIdentityDescriptor> used;
+
+  public static UsedIdentities none() throws AmbariException {
+    return new UsedIdentities(emptyList());
+  }
+
+  /**
+   * Get all identities of the installed services and components. Skip service 
or component that is excluded.
+   */
+  public static UsedIdentities populate(Cluster cluster, ServiceExclude 
serviceExclude, ComponentExclude componentExclude, KerberosHelper 
kerberosHelper) throws AmbariException {
+    List<KerberosIdentityDescriptor> result = new ArrayList<>();
+    KerberosDescriptor root = kerberosHelper.getKerberosDescriptor(cluster);
+    result.addAll(nullToEmpty(root.getIdentities()));
+    for (Service service : cluster.getServices().values()) {
+      if (serviceExclude.shouldExclude(service.getName())) {
+        continue;
+      }
+      KerberosServiceDescriptor serviceDescriptor = 
root.getService(service.getName());
+      if (serviceDescriptor != null) {
+        result.addAll(nullToEmpty(serviceDescriptor.getIdentities()));
+        result.addAll(nullToEmpty(componentIdentities(serviceDescriptor, 
service, componentExclude)));
+      }
+    }
+    return new UsedIdentities(result);
+  }
+
+  private static List<KerberosIdentityDescriptor> 
componentIdentities(KerberosServiceDescriptor serviceDescriptor, Service 
service, ComponentExclude componentExclude) {
+    return service.getServiceComponents().values()
+      .stream()
+      .filter(component -> !isComponentExcluded(service, componentExclude, 
component))
+      .flatMap(component -> 
serviceDescriptor.getComponentIdentities(component.getName()).stream())
+      .collect(toList());
+  }
+
+  private static boolean isComponentExcluded(Service service, ComponentExclude 
componentExclude, ServiceComponent component) {
+    return component.getServiceComponentHosts().isEmpty()
+      || componentExclude.shouldExclude(service.getName(), 
component.getName(), component.getServiceComponentHosts().values());
+  }
+
+  private UsedIdentities(List<KerberosIdentityDescriptor> used) {
+    this.used = used;
+  }
+
+  /**
+   * @return true if there is an identity in the used list with the same 
keytab or principal name than the given identity
+   */
+  public boolean contains(KerberosIdentityDescriptor identity) {
+    return used.stream().anyMatch(each -> identity.isShared(each));
+  }
+
+  public interface ServiceExclude {
+    boolean shouldExclude(String serviceName);
+    ServiceExclude NONE = serviceName -> false; // default implementation, 
exclude nothing
+  }
+
+  public interface ComponentExclude {
+    boolean shouldExclude(String serviceName, String componentName, 
Collection<ServiceComponentHost> hosts);
+    ComponentExclude NONE = (serviceName, componentName, hosts) -> false; // 
default implementation, exclude nothing
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
index 5b55339..8acc401 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceComponentUninstalledEvent.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.events;
 
+import org.apache.ambari.server.serveraction.kerberos.Component;
+
 /**
  * The {@link ServiceComponentUninstalledEvent} class is fired when a service
  * component is successfully uninstalled.
@@ -85,4 +87,8 @@ public class ServiceComponentUninstalledEvent extends 
ServiceEvent {
     buffer.append("}");
     return buffer.toString();
   }
+
+  public Component getComponent() {
+    return new Component(getHostName(), getServiceName(), getComponentName());
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceRemovedEvent.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceRemovedEvent.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceRemovedEvent.java
index aca00a8..de96342 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceRemovedEvent.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/events/ServiceRemovedEvent.java
@@ -17,23 +17,24 @@
  */
 package org.apache.ambari.server.events;
 
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
+
+import org.apache.ambari.server.serveraction.kerberos.Component;
+
 /**
  * The {@link ServiceRemovedEvent} class is fired when a service is 
successfully
  * removed.
  */
 public class ServiceRemovedEvent extends ServiceEvent {
-  /**
-   * Constructor.
-   *
-   * @param clusterId
-   * @param stackName
-   * @param stackVersion
-   * @param serviceName
-   */
+  private final List<Component> components;
+
   public ServiceRemovedEvent(long clusterId, String stackName,
-      String stackVersion, String serviceName) {
+                             String stackVersion, String serviceName, 
List<Component> components) {
     super(AmbariEventType.SERVICE_REMOVED_SUCCESS, clusterId, stackName,
-        stackVersion, serviceName);
+      stackVersion, serviceName);
+    this.components = components;
   }
 
   /**
@@ -49,4 +50,12 @@ public class ServiceRemovedEvent extends ServiceEvent {
     buffer.append("}");
     return buffer.toString();
   }
+
+  public List<Component> getComponents() {
+    return components;
+  }
+
+  public List<String> getComponentNames() {
+    return 
components.stream().map(Component::getServiceComponentName).collect(toList());
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
index a23b914..d0f8d0b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ClusterDAO.java
@@ -217,6 +217,21 @@ public class ClusterDAO {
   }
 
   /**
+   * Gets the latest configurations for a given stack with any of the given 
config types.
+   * This method does not take into account the configuration being enabled.
+   */
+  @RequiresSession
+  public List<ClusterConfigEntity> getLatestConfigurationsWithTypes(long 
clusterId, StackId stackId, Collection<String> configTypes) {
+    StackEntity stackEntity = stackDAO.find(stackId.getStackName(), 
stackId.getStackVersion());
+    return daoUtils.selectList(
+      entityManagerProvider.get()
+      
.createNamedQuery("ClusterConfigEntity.findLatestConfigsByStackWithTypes", 
ClusterConfigEntity.class)
+      .setParameter("clusterId", clusterId)
+      .setParameter("stack", stackEntity)
+      .setParameter("types", configTypes));
+  }
+
+  /**
    * Gets the latest configurations for a given stack for all of the
    * configurations of the specified cluster.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
index 34f3034..3a74367 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ClusterConfigEntity.java
@@ -62,6 +62,9 @@ import org.apache.commons.lang.builder.EqualsBuilder;
         name = "ClusterConfigEntity.findLatestConfigsByStack",
         query = "SELECT clusterConfig FROM ClusterConfigEntity clusterConfig 
WHERE clusterConfig.clusterId = :clusterId AND clusterConfig.stack = :stack AND 
clusterConfig.selectedTimestamp = (SELECT MAX(clusterConfig2.selectedTimestamp) 
FROM ClusterConfigEntity clusterConfig2 WHERE 
clusterConfig2.clusterId=:clusterId AND clusterConfig2.stack=:stack AND 
clusterConfig2.type = clusterConfig.type)"),
     @NamedQuery(
+        name = "ClusterConfigEntity.findLatestConfigsByStackWithTypes",
+        query = "SELECT clusterConfig FROM ClusterConfigEntity clusterConfig 
WHERE clusterConfig.type IN :types AND clusterConfig.clusterId = :clusterId AND 
clusterConfig.stack = :stack AND clusterConfig.selectedTimestamp = (SELECT 
MAX(clusterConfig2.selectedTimestamp) FROM ClusterConfigEntity clusterConfig2 
WHERE clusterConfig2.clusterId=:clusterId AND clusterConfig2.stack=:stack AND 
clusterConfig2.type = clusterConfig.type)"),
+    @NamedQuery(
         name = "ClusterConfigEntity.findNotMappedClusterConfigsToService",
         query = "SELECT clusterConfig FROM ClusterConfigEntity clusterConfig 
WHERE clusterConfig.serviceConfigEntities IS EMPTY AND clusterConfig.type != 
'cluster-env'"),
     @NamedQuery(

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java 
b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
index b4f7120..9597ba1 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
@@ -267,6 +267,13 @@ public interface Cluster {
   Config getConfig(String configType, String versionTag);
 
   /**
+   * Get latest (including inactive ones) configurations with any of the given 
types.
+   * This method does not take into account the configuration being enabled.
+   * @return the list of configurations with the given types
+   */
+  List<Config> getLatestConfigsWithTypes(Collection<String> types);
+
+  /**
    * Gets the specific config that matches the specified type and version.  
This not
    * necessarily a DESIRED configuration that applies to a cluster.
    * @param configType  the config type to find

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceImpl.java 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceImpl.java
index 5084703..74d79c8 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceImpl.java
@@ -51,6 +51,7 @@ import 
org.apache.ambari.server.orm.entities.ServiceConfigEntity;
 import org.apache.ambari.server.orm.entities.ServiceDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.ServiceDesiredStateEntityPK;
 import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.serveraction.kerberos.Component;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -588,6 +589,7 @@ public class ServiceImpl implements Service {
   @Override
   @Transactional
   public void delete() throws AmbariException {
+    List<Component> components = getComponents(); // XXX temporal coupling, 
need to call this BEFORE deletingAllComponents
     deleteAllComponents();
     deleteAllServiceConfigs();
 
@@ -601,11 +603,21 @@ public class ServiceImpl implements Service {
     }
 
     ServiceRemovedEvent event = new ServiceRemovedEvent(getClusterId(), 
stackId.getStackName(),
-        stackId.getStackVersion(), getName());
+        stackId.getStackVersion(), getName(), components);
 
     eventPublisher.publish(event);
   }
 
+  private List<Component> getComponents() {
+    List<Component> result = new ArrayList<>();
+    for (ServiceComponent component : getServiceComponents().values()) {
+      for (ServiceComponentHost host : 
component.getServiceComponentHosts().values()) {
+        result.add(new Component(host.getHostName(), getName(), 
component.getName()));
+      }
+    }
+    return result;
+  }
+
   @Transactional
   protected void removeEntities() throws AmbariException {
     serviceDesiredStateDAO.removeByPK(serviceDesiredStateEntityPK);

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
index 06b6217..c950d67 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
@@ -35,6 +35,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.ReadWriteLock;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
 import javax.persistence.EntityManager;
@@ -1125,6 +1126,14 @@ public class ClusterImpl implements Cluster {
   }
 
   @Override
+  public List<Config> getLatestConfigsWithTypes(Collection<String> types) {
+    return clusterDAO.getLatestConfigurationsWithTypes(clusterId, 
getDesiredStackVersion(), types)
+      .stream()
+      .map(clusterConfigEntity -> configFactory.createExisting(this, 
clusterConfigEntity))
+      .collect(Collectors.toList());
+  }
+
+  @Override
   public Config getConfigByVersion(String configType, Long configVersion) {
     clusterGlobalLock.readLock().lock();
     try {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
index 0a89c1d..5658133 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptorContainer.java
@@ -18,6 +18,8 @@
 
 package org.apache.ambari.server.state.kerberos;
 
+import static java.util.stream.Collectors.toList;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -777,6 +779,16 @@ public abstract class AbstractKerberosDescriptorContainer 
extends AbstractKerber
     return map;
   }
 
+  /**
+   * @return identities which are not references to other identities
+   */
+  public List<KerberosIdentityDescriptor> getIdentitiesSkipReferences() {
+    return nullToEmpty(getIdentities())
+      .stream()
+      .filter(identity -> !identity.getReferencedServiceName().isPresent() && 
identity.getName() != null && !identity.getName().startsWith("/"))
+      .collect(toList());
+  }
+
   @Override
   public int hashCode() {
     return super.hashCode() +

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
index 41d1f65..768a17e 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosComponentDescriptor.java
@@ -17,9 +17,7 @@
  */
 package org.apache.ambari.server.state.kerberos;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -113,19 +111,6 @@ public class KerberosComponentDescriptor extends 
AbstractKerberosDescriptorConta
     return null;
   }
 
-  /**
-   * @return identities which are not references to other identities
-   */
-  public List<KerberosIdentityDescriptor> getIdentitiesSkipReferences() {
-    List<KerberosIdentityDescriptor> result = new ArrayList<>();
-    for (KerberosIdentityDescriptor each : nullToEmpty(getIdentities())) {
-      if (!each.getReferencedServiceName().isPresent() && each.getName() != 
null && !each.getName().startsWith("/")) {
-        result.add(each);
-      }
-    }
-    return result;
-  }
-
   @Override
   public int hashCode() {
     return 35 * super.hashCode();

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
index 2023793..911723b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosIdentityDescriptor.java
@@ -17,10 +17,8 @@
  */
 package org.apache.ambari.server.state.kerberos;
 
-import java.util.List;
 import java.util.Map;
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.collections.Predicate;
 import org.apache.ambari.server.collections.PredicateUtils;
 
@@ -371,16 +369,12 @@ public class KerberosIdentityDescriptor extends 
AbstractKerberosDescriptor {
     }
   }
 
+
   /**
-   * @return true if this identity either has the same principal or keytab as 
any of the given identities.
+   * @return true if the given identity has the same principal or keytab as me
    */
-  public boolean isShared(List<KerberosIdentityDescriptor> identities) throws 
AmbariException {
-    for (KerberosIdentityDescriptor each : identities) {
-      if (hasSamePrincipal(each) || hasSameKeytab(each)) {
-        return true;
-      }
-    }
-    return false;
+  public boolean isShared(KerberosIdentityDescriptor that) {
+    return hasSamePrincipal(that) || hasSameKeytab(that);
   }
 
   private boolean hasSameKeytab(KerberosIdentityDescriptor that) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
index d22c92e..027f339 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
@@ -18,15 +18,20 @@
 package org.apache.ambari.server.controller.utilities;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.singletonList;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.reset;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
+import org.apache.ambari.server.events.ServiceRemovedEvent;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
 import org.apache.ambari.server.serveraction.kerberos.Component;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException;
@@ -35,6 +40,7 @@ import org.apache.ambari.server.state.Clusters;
 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.kerberos.KerberosDescriptor;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
 import org.easymock.EasyMockRule;
@@ -47,6 +53,7 @@ import org.junit.Test;
 public class KerberosIdentityCleanerTest extends EasyMockSupport {
   @Rule public EasyMockRule mocks = new EasyMockRule(this);
   private static final String HOST = "c6401";
+  private static final String HOST2 = "c6402";
   private static final String OOZIE = "OOZIE";
   private static final String OOZIE_SERVER = "OOZIE_SERVER";
   private static final String OOZIE_2 = "OOZIE2";
@@ -55,6 +62,9 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
   private static final String RESOURCE_MANAGER_2 = "RESOURCE_MANAGER2";
   private static final String YARN = "YARN";
   private static final String RESOURCE_MANAGER = "RESOURCE_MANAGER";
+  private static final String HDFS = "HDFS";
+  private static final String NAMENODE = "NAMENODE";
+  private static final String DATANODE = "DATANODE";
   private static final long CLUSTER_ID = 1;
   @Mock private KerberosHelper kerberosHelper;
   @Mock private Clusters clusters;
@@ -66,8 +76,8 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
 
   @Test
   public void 
removesAllKerberosIdentitesOfComponentAfterComponentWasUninstalled() throws 
Exception {
-    installComponent(OOZIE, OOZIE_SERVER);
-    kerberosHelper.deleteIdentity(cluster, new Component(HOST, OOZIE, 
OOZIE_SERVER), newArrayList("oozie_server1", "oozie_server2"));
+    installComponent(OOZIE, OOZIE_SERVER, HOST);
+    kerberosHelper.deleteIdentities(cluster, singletonList(new Component(HOST, 
OOZIE, OOZIE_SERVER)), newHashSet("oozie_server1", "oozie_server2"));
     expectLastCall().once();
     replayAll();
     uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
@@ -83,9 +93,9 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
 
   @Test
   public void skipsRemovingIdentityThatIsSharedByPrincipalName() throws 
Exception {
-    installComponent(OOZIE, OOZIE_SERVER);
-    installComponent(OOZIE_2, OOZIE_SERVER_2);
-    kerberosHelper.deleteIdentity(cluster, new Component(HOST, OOZIE, 
OOZIE_SERVER), newArrayList("oozie_server1"));
+    installComponent(OOZIE, OOZIE_SERVER, HOST);
+    installComponent(OOZIE_2, OOZIE_SERVER_2, HOST);
+    kerberosHelper.deleteIdentities(cluster, singletonList(new Component(HOST, 
OOZIE, OOZIE_SERVER)), newHashSet("oozie_server1"));
     expectLastCall().once();
     replayAll();
     uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
@@ -94,9 +104,9 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
 
   @Test
   public void skipsRemovingIdentityThatIsSharedByKeyTabFilePath() throws 
Exception {
-    installComponent(YARN, RESOURCE_MANAGER);
-    installComponent(YARN_2, RESOURCE_MANAGER_2);
-    kerberosHelper.deleteIdentity(cluster, new Component(HOST, YARN, 
RESOURCE_MANAGER), newArrayList("rm_unique"));
+    installComponent(YARN, RESOURCE_MANAGER, HOST);
+    installComponent(YARN_2, RESOURCE_MANAGER_2, HOST);
+    kerberosHelper.deleteIdentities(cluster, singletonList(new Component(HOST, 
YARN, RESOURCE_MANAGER)), newHashSet("rm_unique"));
     expectLastCall().once();
     replayAll();
     uninstallComponent(YARN, RESOURCE_MANAGER, HOST);
@@ -112,11 +122,43 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
     verifyAll();
   }
 
-  private void installComponent(String serviceName, final String 
componentName) {
+  @Test
+  public void 
skipsRemovingIdentityIfComponentIsStillInstalledOnADifferentHost() throws 
Exception {
+    installComponent(OOZIE, OOZIE_SERVER, HOST, HOST2);
+    replayAll();
+    uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
+    verifyAll();
+  }
+
+  @Test
+  public void 
removesServiceIdentitiesSkipComponentIdentitiesAfterServiceWasUninstalled() 
throws Exception {
+    installComponent(OOZIE, OOZIE_SERVER, HOST);
+    kerberosHelper.deleteIdentities(cluster, hdfsComponents(), 
newHashSet("hdfs-service"));
+    expectLastCall().once();
+    replayAll();
+    uninstallService(HDFS, hdfsComponents());
+    verifyAll();
+  }
+
+  private ArrayList<Component> hdfsComponents() {
+    return newArrayList(new Component(HOST, HDFS, NAMENODE), new 
Component(HOST, HDFS, DATANODE));
+  }
+
+  private void installComponent(String serviceName, String componentName, 
String... hostNames) {
     Service service = createMock(serviceName + "_" + componentName, 
Service.class);
+    ServiceComponent component = createMock(componentName, 
ServiceComponent.class);
+    expect(component.getName()).andReturn(componentName).anyTimes();
+    Map<String, ServiceComponentHost> hosts = new HashMap<>();
+    expect(component.getServiceComponentHosts()).andReturn(hosts).anyTimes();
+    for (String hostName : hostNames) {
+      ServiceComponentHost host = createMock(hostName, 
ServiceComponentHost.class);
+      expect(host.getHostName()).andReturn(hostName).anyTimes();
+      hosts.put(hostName, host);
+    }
     installedServices.put(serviceName, service);
+    expect(service.getName()).andReturn(serviceName).anyTimes();
     expect(service.getServiceComponents()).andReturn(new HashMap<String, 
ServiceComponent>() {{
-      put(componentName, null);
+      put(componentName, component);
     }}).anyTimes();
   }
 
@@ -124,6 +166,10 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
     kerberosIdentityCleaner.componentRemoved(new 
ServiceComponentUninstalledEvent(CLUSTER_ID, "any", "any", service, component, 
host, false));
   }
 
+  private void uninstallService(String service, List<Component> components) 
throws KerberosMissingAdminCredentialsException {
+    kerberosIdentityCleaner.serviceRemoved(new ServiceRemovedEvent(CLUSTER_ID, 
"any", "any", service, components));
+  }
+
   @Before
   public void setUp() throws Exception {
     kerberosIdentityCleaner = new KerberosIdentityCleaner(new 
AmbariEventPublisher(), kerberosHelper, clusters);
@@ -139,7 +185,8 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
       "              'name': '/HDFS/NAMENODE/hdfs'" +
       "            }," +
       "            {" +
-      "              'name': 'oozie_server1'" +
+      "              'name': 'oozie_server1'," +
+      "              'principal': { 'value': 'oozie1/_h...@example.com' }" +
       "            }," +"" +
       "            {" +
       "              'name': 'oozie_server2'," +
@@ -193,6 +240,39 @@ public class KerberosIdentityCleanerTest extends 
EasyMockSupport {
       "          ]" +
       "        }" +
       "      ]" +
+      "    }," +
+      "    {" +
+      "      'name': 'HDFS'," +
+      "      'identities': [" +
+      "            {" +
+      "              'name': 'hdfs-service'" +
+      "            }," +
+      "            {" +
+      "              'name': 'shared'," +
+      "              'principal': { 'value': 'oozie/_h...@example.com' }" +
+      "            }," +
+      "            {" +
+      "              'name': '/YARN/RESOURCE_MANAGER/rm'" +
+      "            }," +
+      "          ]," +
+      "      'components': [" +
+      "        {" +
+      "          'name': 'NAMENODE'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': 'namenode'" +
+      "            }" +
+      "          ]" +
+      "        }," +
+      "        {" +
+      "          'name': 'DATANODE'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': 'datanode'" +
+      "            }" +
+      "          ]" +
+      "        }" +
+      "      ]" +
       "    }" +
       "  ]" +
       "}");

http://git-wip-us.apache.org/repos/asf/ambari/blob/e767aa44/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
index 406349a..80cb4dc 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ServiceConfigDAOTest.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.orm.dao;
 
+import static java.util.Arrays.asList;
+
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -482,6 +484,16 @@ public class ServiceConfigDAOTest {
     Assert.assertTrue(entity.isSelected());
   }
 
+  @Test
+  public void testGetLatestClusterConfigsWithTypes() throws Exception {
+    initClusterEntities();
+    ClusterEntity clusterEntity = clusterDAO.findByName("c1");
+    List<ClusterConfigEntity> entities = 
clusterDAO.getLatestConfigurationsWithTypes(clusterEntity.getClusterId(), 
HDP_01, asList("oozie-site"));
+    Assert.assertEquals(1, entities.size());
+    entities = 
clusterDAO.getLatestConfigurationsWithTypes(clusterEntity.getClusterId(), 
HDP_01, asList("no-such-type"));
+    Assert.assertTrue(entities.isEmpty());
+  }
+
   /**
    * Tests getting latest and enabled configurations when there is a
    * configuration group. Configurations for configuration groups are not

Reply via email to