Repository: ambari
Updated Branches:
  refs/heads/trunk 9d224f73b -> 8b5c7db60


AMBARI-21343. Cleanup relevant Kerberos identities when a component 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/8b5c7db6
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8b5c7db6
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8b5c7db6

Branch: refs/heads/trunk
Commit: 8b5c7db602a0e1e2dfb214ec1d51884c16219467
Parents: 9d224f7
Author: Attila Magyar <amag...@hortonworks.com>
Authored: Thu Jun 29 11:05:25 2017 +0200
Committer: Attila Magyar <amag...@hortonworks.com>
Committed: Thu Jun 29 11:05:25 2017 +0200

----------------------------------------------------------------------
 .../ambari/server/controller/AmbariServer.java  |   4 +
 .../controller/DeleteIdentityHandler.java       | 283 +++++++++++++++++++
 .../server/controller/KerberosHelper.java       |   3 +
 .../server/controller/KerberosHelperImpl.java   |  31 +-
 .../OrderedRequestStageContainer.java           |  45 +++
 .../utilities/KerberosIdentityCleaner.java      | 135 +++++++++
 .../AbstractPrepareKerberosServerAction.java    |  19 +-
 .../server/serveraction/kerberos/Component.java |  74 +++++
 .../kerberos/FinalizeKerberosServerAction.java  |  27 +-
 .../kerberos/KerberosServerAction.java          |  27 ++
 .../kerberos/AbstractKerberosDescriptor.java    |  15 +
 .../kerberos/KerberosComponentDescriptor.java   |  15 +
 .../state/kerberos/KerberosDescriptor.java      |   8 -
 .../kerberos/KerberosIdentityDescriptor.java    |  30 ++
 .../kerberos/KerberosServiceDescriptor.java     |   6 +
 .../utilities/KerberosIdentityCleanerTest.java  | 204 +++++++++++++
 ambari-web/app/controllers/main/service/item.js |   6 +-
 17 files changed, 894 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
index aeba739..8988be0 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
@@ -76,6 +76,7 @@ import 
org.apache.ambari.server.controller.internal.UserPrivilegeResourceProvide
 import 
org.apache.ambari.server.controller.internal.ViewPermissionResourceProvider;
 import 
org.apache.ambari.server.controller.metrics.ThreadPoolEnabledPropertyProvider;
 import org.apache.ambari.server.controller.utilities.KerberosChecker;
+import org.apache.ambari.server.controller.utilities.KerberosIdentityCleaner;
 import org.apache.ambari.server.metrics.system.MetricsService;
 import org.apache.ambari.server.orm.GuiceJpaInitializer;
 import org.apache.ambari.server.orm.PersistenceType;
@@ -941,6 +942,9 @@ public class AmbariServer {
     BaseService.init(injector.getInstance(RequestAuditLogger.class));
 
     RetryHelper.init(injector.getInstance(Clusters.class), 
configs.getOperationsRetryAttempts());
+
+    KerberosIdentityCleaner identityCleaner = 
injector.getInstance(KerberosIdentityCleaner.class);
+    identityCleaner.register();
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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
new file mode 100644
index 0000000..aa098b6
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/DeleteIdentityHandler.java
@@ -0,0 +1,283 @@
+/*
+ * 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;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.BASE_LOG_DIR;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.Role;
+import org.apache.ambari.server.RoleCommand;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.actionmanager.Stage;
+import org.apache.ambari.server.actionmanager.StageFactory;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.ambari.server.controller.internal.RequestResourceFilter;
+import org.apache.ambari.server.serveraction.ServerAction;
+import 
org.apache.ambari.server.serveraction.kerberos.AbstractPrepareKerberosServerAction;
+import org.apache.ambari.server.serveraction.kerberos.Component;
+import 
org.apache.ambari.server.serveraction.kerberos.DestroyPrincipalsServerAction;
+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.kerberos.KerberosDescriptor;
+import 
org.apache.ambari.server.state.svccomphost.ServiceComponentHostServerActionEvent;
+import org.apache.ambari.server.utils.StageUtils;
+
+/**
+ * I delete kerberos identities (principals and keytabs) of a given component.
+ */
+class DeleteIdentityHandler {
+  private final AmbariCustomCommandExecutionHelper 
customCommandExecutionHelper;
+  private final Integer taskTimeout;
+  private final StageFactory stageFactory;
+  private final AmbariManagementController ambariManagementController;
+
+  public DeleteIdentityHandler(AmbariCustomCommandExecutionHelper 
customCommandExecutionHelper, Integer taskTimeout, StageFactory stageFactory, 
AmbariManagementController ambariManagementController) {
+    this.customCommandExecutionHelper = customCommandExecutionHelper;
+    this.taskTimeout = taskTimeout;
+    this.stageFactory = stageFactory;
+    this.ambariManagementController = ambariManagementController;
+  }
+
+  /**
+   * Creates and adds stages to the given stage container for deleting 
kerberos identities.
+   * The service component that belongs to the identity doesn't need to be 
installed.
+   */
+  public void addDeleteIdentityStages(Cluster cluster, 
OrderedRequestStageContainer stageContainer, CommandParams commandParameters, 
boolean manageIdentities)
+    throws AmbariException
+  {
+    ServiceComponentHostServerActionEvent event = new 
ServiceComponentHostServerActionEvent("AMBARI_SERVER", 
StageUtils.getHostName(), System.currentTimeMillis());
+    String hostParamsJson = 
StageUtils.getGson().toJson(customCommandExecutionHelper.createDefaultHostParams(cluster,
 cluster.getDesiredStackVersion()));
+    
stageContainer.setClusterHostInfo(StageUtils.getGson().toJson(StageUtils.getClusterHostInfo(cluster)));
+    if (manageIdentities) {
+      addPrepareDeleteIdentity(cluster, hostParamsJson, event, 
commandParameters, stageContainer);
+      addDestroyPrincipals(cluster, hostParamsJson, event, commandParameters, 
stageContainer);
+      addDeleteKeytab(cluster, 
newHashSet(commandParameters.component.getHostName()), hostParamsJson, 
commandParameters, stageContainer);
+    }
+    addFinalize(cluster, hostParamsJson, event, stageContainer, 
commandParameters);
+  }
+
+  private void addPrepareDeleteIdentity(Cluster cluster,
+                                        String hostParamsJson, 
ServiceComponentHostServerActionEvent event,
+                                        CommandParams commandParameters,
+                                        OrderedRequestStageContainer 
stageContainer)
+    throws AmbariException
+  {
+    Stage stage = createServerActionStage(stageContainer.getLastStageId(),
+      cluster,
+      stageContainer.getId(),
+      "Prepare delete identities",
+      "{}",
+      hostParamsJson,
+      PrepareDeleteIdentityServerAction.class,
+      event,
+      commandParameters.asMap(),
+      "Prepare delete identities",
+      taskTimeout);
+    stageContainer.addStage(stage);
+  }
+
+  private void addDestroyPrincipals(Cluster cluster,
+                                    String hostParamsJson, 
ServiceComponentHostServerActionEvent event,
+                                    CommandParams commandParameters,
+                                    OrderedRequestStageContainer 
stageContainer)
+    throws AmbariException
+  {
+    Stage stage = createServerActionStage(stageContainer.getLastStageId(),
+      cluster,
+      stageContainer.getId(),
+      "Destroy Principals",
+      "{}",
+      hostParamsJson,
+      DestroyPrincipalsServerAction.class,
+      event,
+      commandParameters.asMap(),
+      "Destroy Principals",
+      Math.max(ServerAction.DEFAULT_LONG_RUNNING_TASK_TIMEOUT_SECONDS, 
taskTimeout));
+    stageContainer.addStage(stage);
+  }
+
+  private void addDeleteKeytab(Cluster cluster,
+                               Set<String> hostFilter,
+                               String hostParamsJson,
+                               CommandParams commandParameters,
+                               OrderedRequestStageContainer stageContainer)
+    throws AmbariException
+  {
+    Stage stage = createNewStage(stageContainer.getLastStageId(),
+      cluster,
+      stageContainer.getId(),
+      "Delete Keytabs",
+      commandParameters.asJson(),
+      hostParamsJson);
+
+    Map<String, String> requestParams = new HashMap<>();
+    List<RequestResourceFilter> requestResourceFilters = new ArrayList<>();
+    RequestResourceFilter reqResFilter = new RequestResourceFilter("KERBEROS", 
"KERBEROS_CLIENT", new ArrayList<>(hostFilter));
+    requestResourceFilters.add(reqResFilter);
+
+    ActionExecutionContext actionExecContext = new ActionExecutionContext(
+      cluster.getClusterName(),
+      "REMOVE_KEYTAB",
+      requestResourceFilters,
+      requestParams);
+    
customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, 
stage, requestParams, null);
+    stageContainer.addStage(stage);
+  }
+
+  private void addFinalize(Cluster cluster,
+                           String hostParamsJson, 
ServiceComponentHostServerActionEvent event,
+                           OrderedRequestStageContainer requestStageContainer,
+                           CommandParams commandParameters)
+    throws AmbariException
+  {
+    Stage stage = 
createServerActionStage(requestStageContainer.getLastStageId(),
+      cluster,
+      requestStageContainer.getId(),
+      "Finalize Operations",
+      "{}",
+      hostParamsJson,
+      DeleteDataDirAction.class,
+      event,
+      commandParameters.asMap(),
+      "Finalize Operations", 300);
+    requestStageContainer.addStage(stage);
+  }
+
+
+  public static class CommandParams {
+    private final Component component;
+    private final List<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;
+      this.identities = identities;
+      this.authName = authName;
+      this.dataDirectory = dataDirectory;
+      this.defaultRealm = defaultRealm;
+      this.kdcType = kdcType;
+    }
+
+    public Map<String, String> asMap() {
+      Map<String, String> commandParameters = new HashMap<>();
+      commandParameters.put(KerberosServerAction.AUTHENTICATED_USER_NAME, 
authName);
+      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.DATA_DIRECTORY, 
dataDirectory.getAbsolutePath());
+      return commandParameters;
+    }
+
+    public String asJson() {
+      return StageUtils.getGson().toJson(asMap());
+    }
+  }
+
+  private static class PrepareDeleteIdentityServerAction extends 
AbstractPrepareKerberosServerAction {
+    @Override
+    public CommandReport execute(ConcurrentMap<String, Object> 
requestSharedDataContext) throws AmbariException, InterruptedException {
+      KerberosDescriptor kerberosDescriptor = getKerberosDescriptor();
+      processServiceComponents(
+        getCluster(),
+        kerberosDescriptor,
+        Collections.singletonList(getComponentFilter()),
+        getIdentityFilter(),
+        dataDirectory(),
+        calculateConfig(kerberosDescriptor),
+        new HashMap<String, Map<String, String>>(),
+        false,
+        new HashMap<String, Set<String>>());
+      return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", 
actionLog.getStdOut(), actionLog.getStdErr());
+    }
+
+    protected Component getComponentFilter() {
+      return 
StageUtils.getGson().fromJson(getCommandParameterValue(KerberosServerAction.COMPONENT_FILTER),
 Component.class);
+    }
+
+    private Map<String, Map<String, String>> 
calculateConfig(KerberosDescriptor kerberosDescriptor) throws AmbariException {
+      return getKerberosHelper().calculateConfigurations(getCluster(), null, 
kerberosDescriptor.getProperties());
+    }
+
+    private String dataDirectory() {
+      return getCommandParameterValue(getCommandParameters(), DATA_DIRECTORY);
+    }
+
+    private KerberosDescriptor getKerberosDescriptor() throws AmbariException {
+      return getKerberosHelper().getKerberosDescriptor(getCluster());
+    }
+  }
+
+  private Stage createNewStage(long id, Cluster cluster, long requestId, 
String requestContext, String commandParams, String hostParams) {
+    Stage stage = stageFactory.createNew(requestId,
+      BASE_LOG_DIR + File.pathSeparator + requestId,
+      cluster.getClusterName(),
+      cluster.getClusterId(),
+      requestContext,
+      commandParams,
+      hostParams);
+    stage.setStageId(id);
+    return stage;
+  }
+
+  private Stage createServerActionStage(long id, Cluster cluster, long 
requestId,
+                                       String requestContext,
+                                       String commandParams, String hostParams,
+                                       Class<? extends ServerAction> 
actionClass,
+                                       ServiceComponentHostServerActionEvent 
event,
+                                       Map<String, String> commandParameters, 
String commandDetail,
+                                       Integer timeout) throws AmbariException 
{
+
+    Stage stage = createNewStage(id, cluster, requestId, requestContext,  
commandParams, hostParams);
+    stage.addServerActionCommand(actionClass.getName(), null, 
Role.AMBARI_SERVER_ACTION,
+      RoleCommand.EXECUTE, cluster.getClusterName(), event, commandParameters, 
commandDetail,
+      ambariManagementController.findConfigurationTagsWithOverrides(cluster, 
null), timeout,
+      false, false);
+
+    return stage;
+  }
+
+  private static class DeleteDataDirAction extends KerberosServerAction {
+
+    @Override
+    public CommandReport execute(ConcurrentMap<String, Object> 
requestSharedDataContext) throws AmbariException, InterruptedException {
+      deleteDataDirectory(getCommandParameterValue(DATA_DIRECTORY));
+      return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", 
actionLog.getStdOut(), actionLog.getStdErr());
+    }
+
+    @Override
+    protected CommandReport processIdentity(Map<String, String> 
identityRecord, String evaluatedPrincipal, KerberosOperationHandler 
operationHandler, Map<String, String> kerberosConfiguration, Map<String, 
Object> requestSharedDataContext) throws AmbariException {
+      return null;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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 ca2dda5..cc0c048 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
@@ -27,6 +27,7 @@ import java.util.Set;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.internal.RequestStageContainer;
 import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
+import org.apache.ambari.server.serveraction.kerberos.Component;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosAdminAuthenticationException;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWriter;
 import 
org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
@@ -232,6 +233,8 @@ public interface KerberosHelper {
                                          RequestStageContainer 
requestStageContainer, Boolean manageIdentities)
       throws AmbariException, KerberosOperationException;
 
+  void deleteIdentity(Cluster cluster, Component component, List<String> 
identities) throws AmbariException, KerberosOperationException;
+
   /**
    * Updates the relevant configurations for the components specified in the 
service filter.
    * <p/>

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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 d57fcd2..b30f8f6 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
@@ -64,6 +64,7 @@ import 
org.apache.ambari.server.security.credential.PrincipalKeyCredential;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
 import org.apache.ambari.server.serveraction.ServerAction;
 import org.apache.ambari.server.serveraction.kerberos.CleanupServerAction;
+import org.apache.ambari.server.serveraction.kerberos.Component;
 import 
org.apache.ambari.server.serveraction.kerberos.ConfigureAmbariIdentitiesServerAction;
 import 
org.apache.ambari.server.serveraction.kerberos.CreateKeytabFilesServerAction;
 import 
org.apache.ambari.server.serveraction.kerberos.CreatePrincipalsServerAction;
@@ -130,7 +131,7 @@ import com.google.inject.persist.Transactional;
 @Singleton
 public class KerberosHelperImpl implements KerberosHelper {
 
-  private static final String BASE_LOG_DIR = "/tmp/ambari";
+  public static final String BASE_LOG_DIR = "/tmp/ambari";
 
   private static final Logger LOG = 
LoggerFactory.getLogger(KerberosHelperImpl.class);
 
@@ -296,6 +297,34 @@ public class KerberosHelperImpl implements KerberosHelper {
         requestStageContainer, new DeletePrincipalsAndKeytabsHandler());
   }
 
+  /**
+   * 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 {
+    if (identities.isEmpty()) {
+      return;
+    }
+    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,
+      identities,
+      ambariManagementController.getAuthName(),
+      dataDirectory,
+      kerberosDetails.getDefaultRealm(),
+      kerberosDetails.getKdcType());
+    OrderedRequestStageContainer stageContainer = new 
OrderedRequestStageContainer(
+      roleGraphFactory,
+      roleCommandOrder,
+      new RequestStageContainer(actionManager.getNextRequestId(), null, 
requestFactory, actionManager));
+    handler.addDeleteIdentityStages(cluster, stageContainer, 
commandParameters, kerberosDetails.manageIdentities());
+    stageContainer.getRequestStageContainer().persist();
+  }
+
   @Override
   public void configureServices(Cluster cluster, Map<String, 
Collection<String>> serviceFilter)
       throws AmbariException, KerberosInvalidConfigurationException {

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java
new file mode 100644
index 0000000..6d8b5a3
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/OrderedRequestStageContainer.java
@@ -0,0 +1,45 @@
+package org.apache.ambari.server.controller;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.Stage;
+import org.apache.ambari.server.controller.internal.RequestStageContainer;
+import org.apache.ambari.server.metadata.RoleCommandOrder;
+import org.apache.ambari.server.stageplanner.RoleGraph;
+import org.apache.ambari.server.stageplanner.RoleGraphFactory;
+
+/**
+ * An extension of RequestStageContainer that takes the role command order 
into consideration when adding stages
+ */
+public class OrderedRequestStageContainer {
+  private final RoleGraphFactory roleGraphFactory;
+  private final RoleCommandOrder roleCommandOrder;
+  private final RequestStageContainer requestStageContainer;
+
+  public OrderedRequestStageContainer(RoleGraphFactory roleGraphFactory, 
RoleCommandOrder roleCommandOrder, RequestStageContainer requestStageContainer) 
{
+    this.roleGraphFactory = roleGraphFactory;
+    this.roleCommandOrder = roleCommandOrder;
+    this.requestStageContainer = requestStageContainer;
+  }
+
+  public void addStage(Stage stage) throws AmbariException {
+    RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder);
+    roleGraph.build(stage);
+    requestStageContainer.addStages(roleGraph.getStages());
+  }
+
+  public long getLastStageId() {
+    return requestStageContainer.getLastStageId();
+  }
+
+  public long getId() {
+    return requestStageContainer.getId();
+  }
+
+  public RequestStageContainer getRequestStageContainer() {
+    return requestStageContainer;
+  }
+
+  public void setClusterHostInfo(String clusterHostInfo) {
+    this.requestStageContainer.setClusterHostInfo(clusterHostInfo);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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
new file mode 100644
index 0000000..0a8462f
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleaner.java
@@ -0,0 +1,135 @@
+/*
+ * 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 
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.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;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class KerberosIdentityCleaner {
+  private final static Logger LOG = 
LoggerFactory.getLogger(KerberosIdentityCleaner.class);
+  private final AmbariEventPublisher eventPublisher;
+  private final KerberosHelper kerberosHelper;
+  private final Clusters clusters;
+
+  @Inject
+  public KerberosIdentityCleaner(AmbariEventPublisher eventPublisher, 
KerberosHelper kerberosHelper, Clusters clusters) {
+    this.eventPublisher = eventPublisher;
+    this.kerberosHelper = kerberosHelper;
+    this.clusters = clusters;
+  }
+
+  public void register() {
+    this.eventPublisher.register(this);
+  }
+
+  /**
+   * Removes kerberos identities (principals and keytabs) after a component 
was uninstalled.
+   * Keeps the identity if either the principal or the keytab is used by an 
other service
+   */
+  @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);
+    } 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));
+        }
+      }
+    }
+    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/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java
index 7aac346..dd2b223 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/AbstractPrepareKerberosServerAction.java
@@ -21,6 +21,7 @@ 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.Collections;
 import java.util.HashMap;
@@ -65,7 +66,7 @@ public abstract class AbstractPrepareKerberosServerAction 
extends KerberosServer
     throw new UnsupportedOperationException();
   }
 
-  KerberosHelper getKerberosHelper() {
+  protected KerberosHelper getKerberosHelper() {
     return kerberosHelper;
   }
 
@@ -76,6 +77,20 @@ public abstract class AbstractPrepareKerberosServerAction 
extends KerberosServer
                                     Map<String, Map<String, String>> 
kerberosConfigurations,
                                     boolean includeAmbariIdentity,
                                     Map<String, Set<String>> 
propertiesToBeIgnored) throws AmbariException {
+    List<Component> components = new ArrayList<>();
+    for (ServiceComponentHost each : schToProcess) {
+      components.add(Component.fromServiceComponentHost(each));
+    }
+    processServiceComponents(cluster, kerberosDescriptor, components, 
identityFilter, dataDirectory, currentConfigurations, kerberosConfigurations, 
includeAmbariIdentity, propertiesToBeIgnored);
+  }
+
+  protected void processServiceComponents(Cluster cluster, KerberosDescriptor 
kerberosDescriptor,
+                                          List<Component> schToProcess,
+                                          Collection<String> identityFilter, 
String dataDirectory,
+                                          Map<String, Map<String, String>> 
currentConfigurations,
+                                          Map<String, Map<String, String>> 
kerberosConfigurations,
+                                          boolean includeAmbariIdentity,
+                                          Map<String, Set<String>> 
propertiesToBeIgnored) throws AmbariException {
 
     actionLog.writeStdOut("Processing Kerberos identities and configurations");
 
@@ -113,7 +128,7 @@ public abstract class AbstractPrepareKerberosServerAction 
extends KerberosServer
         // Iterate over the components installed on the current host to get 
the service and
         // component-level Kerberos descriptors in order to determine which 
principals,
         // keytab files, and configurations need to be created or updated.
-        for (ServiceComponentHost sch : schToProcess) {
+        for (Component sch : schToProcess) {
           String hostName = sch.getHostName();
 
           String serviceName = sch.getServiceName();

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java
new file mode 100644
index 0000000..4f1ee52
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/Component.java
@@ -0,0 +1,74 @@
+/*
+ * 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.serveraction.kerberos;
+
+import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+public class Component {
+  private final String hostName;
+  private final String serviceName;
+  private final String serviceComponentName;
+
+  public static Component fromServiceComponentHost(ServiceComponentHost 
serviceComponentHost) {
+    return new Component(
+      serviceComponentHost.getHostName(),
+      serviceComponentHost.getServiceName(),
+      serviceComponentHost.getServiceComponentName());
+  }
+
+  public Component(String hostName, String serviceName, String 
serviceComponentName) {
+    this.hostName = hostName;
+    this.serviceName = serviceName;
+    this.serviceComponentName = serviceComponentName;
+  }
+
+  public String getHostName() {
+    return hostName;
+  }
+
+  public String getServiceName() {
+    return serviceName;
+  }
+
+  public String getServiceComponentName() {
+    return serviceComponentName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    Component component = (Component) o;
+    return new EqualsBuilder()
+      .append(hostName, component.hostName)
+      .append(serviceName, component.serviceName)
+      .append(serviceComponentName, component.serviceComponentName)
+      .isEquals();
+  }
+
+  @Override
+  public int hashCode() {
+    return new HashCodeBuilder(17, 37)
+      .append(hostName)
+      .append(serviceName)
+      .append(serviceComponentName)
+      .toHashCode();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/FinalizeKerberosServerAction.java
----------------------------------------------------------------------
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 2742390..10ad48b 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
@@ -18,8 +18,6 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
-import java.io.File;
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -36,7 +34,6 @@ import org.apache.ambari.server.state.SecurityState;
 import org.apache.ambari.server.state.ServiceComponentHost;
 import org.apache.ambari.server.utils.ShellCommandUtil;
 import org.apache.ambari.server.utils.StageUtils;
-import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -208,29 +205,9 @@ public class FinalizeKerberosServerAction extends 
KerberosServerAction {
       processIdentities(requestSharedDataContext);
       requestSharedDataContext.remove(this.getClass().getName() + "_visited");
     }
-
-    // Make sure this is a relevant directory. We don't want to accidentally 
allow _ANY_ directory
-    // to be deleted.
-    if ((dataDirectoryPath != null) && dataDirectoryPath.contains("/" + 
DATA_DIRECTORY_PREFIX)) {
-      File dataDirectory = new File(dataDirectoryPath);
-      File dataDirectoryParent = dataDirectory.getParentFile();
-
-      // Make sure this directory has a parent and it is writeable, else we 
wont be able to
-      // delete the directory
-      if ((dataDirectoryParent != null) && dataDirectory.isDirectory() &&
-          dataDirectoryParent.isDirectory() && dataDirectoryParent.canWrite()) 
{
-        try {
-          FileUtils.deleteDirectory(dataDirectory);
-        } catch (IOException e) {
-          // We should log this exception, but don't let it fail the process 
since if we got to this
-          // KerberosServerAction it is expected that the the overall process 
was a success.
-          String message = String.format("The data directory (%s) was not 
deleted due to an error condition - {%s}",
-              dataDirectory.getAbsolutePath(), e.getMessage());
-          LOG.warn(message, e);
-        }
-      }
-    }
+    deleteDataDirectory(dataDirectoryPath);
 
     return createCommandReport(0, HostRoleStatus.COMPLETED, "{}", 
actionLog.getStdOut(), actionLog.getStdErr());
   }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
----------------------------------------------------------------------
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 d404133..2e331bb 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
@@ -35,6 +35,7 @@ import 
org.apache.ambari.server.serveraction.AbstractServerAction;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.utils.StageUtils;
+import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -82,6 +83,8 @@ public abstract class KerberosServerAction extends 
AbstractServerAction {
    */
   public static final String IDENTITY_FILTER = "identity_filter";
 
+  public static final String COMPONENT_FILTER = "component_filter";
+
   /**
    * A (command parameter) property name used to hold the relevant KDC type 
value.  See
    * {@link org.apache.ambari.server.serveraction.kerberos.KDCType} for valid 
values
@@ -536,4 +539,28 @@ public abstract class KerberosServerAction extends 
AbstractServerAction {
 
     return commandReport;
   }
+
+  protected void deleteDataDirectory(String dataDirectoryPath) {
+    // Make sure this is a relevant directory. We don't want to accidentally 
allow _ANY_ directory
+    // to be deleted.
+    if ((dataDirectoryPath != null) && dataDirectoryPath.contains("/" + 
DATA_DIRECTORY_PREFIX)) {
+      File dataDirectory = new File(dataDirectoryPath);
+      File dataDirectoryParent = dataDirectory.getParentFile();
+
+      // Make sure this directory has a parent and it is writeable, else we 
wont be able to
+      // delete the directory
+      if ((dataDirectoryParent != null) && dataDirectory.isDirectory() &&
+          dataDirectoryParent.isDirectory() && dataDirectoryParent.canWrite()) 
{
+        try {
+          FileUtils.deleteDirectory(dataDirectory);
+        } catch (IOException e) {
+          // We should log this exception, but don't let it fail the process 
since if we got to this
+          // KerberosServerAction it is expected that the the overall process 
was a success.
+          String message = String.format("The data directory (%s) was not 
deleted due to an error condition - {%s}",
+              dataDirectory.getAbsolutePath(), e.getMessage());
+          LOG.warn(message, e);
+        }
+      }
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
index 397f384..38100ac 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/AbstractKerberosDescriptor.java
@@ -18,6 +18,9 @@
 
 package org.apache.ambari.server.state.kerberos;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -181,6 +184,18 @@ public abstract class AbstractKerberosDescriptor {
     return root;
   }
 
+  public static <T> Collection<T> nullToEmpty(Collection<T> collection) {
+    return collection == null ? Collections.<T>emptyList() : collection;
+  }
+
+  public static <T> List<T> nullToEmpty(List<T> list) {
+    return list == null ? Collections.<T>emptyList() : list;
+  }
+
+  public static <K,V> Map<K,V> nullToEmpty(Map<K,V> collection) {
+    return collection == null ? Collections.<K,V>emptyMap() : collection;
+  }
+
   @Override
   public int hashCode() {
     return 37 *

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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 768a17e..41d1f65 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,7 +17,9 @@
  */
 package org.apache.ambari.server.state.kerberos;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -111,6 +113,19 @@ 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/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
index f9dfa4a..eba1b3a 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosDescriptor.java
@@ -461,12 +461,4 @@ public class KerberosDescriptor extends 
AbstractKerberosDescriptorContainer {
       }
     }
   }
-
-  private static <T> Collection<T> nullToEmpty(Collection<T> collection) {
-    return collection == null ? Collections.<T>emptyList() : collection;
-  }
-
-  private static <K,V> Map<K,V> nullToEmpty(Map<K,V> collection) {
-    return collection == null ? Collections.<K,V>emptyMap() : collection;
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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 e180f7a..2023793 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,8 +17,10 @@
  */
 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;
 
@@ -369,6 +371,34 @@ public class KerberosIdentityDescriptor extends 
AbstractKerberosDescriptor {
     }
   }
 
+  /**
+   * @return true if this identity either has the same principal or keytab as 
any of the given identities.
+   */
+  public boolean isShared(List<KerberosIdentityDescriptor> identities) throws 
AmbariException {
+    for (KerberosIdentityDescriptor each : identities) {
+      if (hasSamePrincipal(each) || hasSameKeytab(each)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasSameKeytab(KerberosIdentityDescriptor that) {
+    try {
+      return 
this.getKeytabDescriptor().getFile().equals(that.getKeytabDescriptor().getFile());
+    } catch (NullPointerException e) {
+      return false;
+    }
+  }
+
+  private boolean hasSamePrincipal(KerberosIdentityDescriptor that) {
+    try {
+      return 
this.getPrincipalDescriptor().getValue().equals(that.getPrincipalDescriptor().getValue());
+    } catch (NullPointerException e) {
+      return false;
+    }
+  }
+
   @Override
   public int hashCode() {
     return super.hashCode() +

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
index 8507bfa..0777327 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/kerberos/KerberosServiceDescriptor.java
@@ -272,6 +272,12 @@ public class KerberosServiceDescriptor extends 
AbstractKerberosDescriptorContain
     return map;
   }
 
+  public List<KerberosIdentityDescriptor> getComponentIdentities(String 
componentName) {
+    return getComponent(componentName) != null
+      ? nullToEmpty(getComponent(componentName).getIdentities())
+      : Collections.<KerberosIdentityDescriptor>emptyList();
+  }
+
   @Override
   public int hashCode() {
     return super.hashCode() +

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/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
new file mode 100644
index 0000000..d22c92e
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/utilities/KerberosIdentityCleanerTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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 com.google.common.collect.Lists.newArrayList;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.reset;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
+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.ServiceComponent;
+import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
+import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
+import org.easymock.EasyMockRule;
+import org.easymock.EasyMockSupport;
+import org.easymock.Mock;
+import org.junit.Before;
+import org.junit.Rule;
+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 OOZIE = "OOZIE";
+  private static final String OOZIE_SERVER = "OOZIE_SERVER";
+  private static final String OOZIE_2 = "OOZIE2";
+  private static final String OOZIE_SERVER_2 = "OOZIE_SERVER2";
+  private static final String YARN_2 = "YARN2";
+  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 long CLUSTER_ID = 1;
+  @Mock private KerberosHelper kerberosHelper;
+  @Mock private Clusters clusters;
+  @Mock private Cluster cluster;
+  private Map<String, Service> installedServices = new HashMap<>();
+  private KerberosDescriptorFactory kerberosDescriptorFactory = new 
KerberosDescriptorFactory();
+  private KerberosIdentityCleaner kerberosIdentityCleaner;
+  private KerberosDescriptor kerberosDescriptor;
+
+  @Test
+  public void 
removesAllKerberosIdentitesOfComponentAfterComponentWasUninstalled() throws 
Exception {
+    installComponent(OOZIE, OOZIE_SERVER);
+    kerberosHelper.deleteIdentity(cluster, new Component(HOST, OOZIE, 
OOZIE_SERVER), newArrayList("oozie_server1", "oozie_server2"));
+    expectLastCall().once();
+    replayAll();
+    uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
+    verifyAll();
+  }
+
+  @Test
+  public void skipsRemovingIdentityWhenServiceDoesNotExist() throws Exception {
+    replayAll();
+    uninstallComponent("NO_SUCH_SERVICE", OOZIE_SERVER, HOST);
+    verifyAll();
+  }
+
+  @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"));
+    expectLastCall().once();
+    replayAll();
+    uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
+    verifyAll();
+  }
+
+  @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"));
+    expectLastCall().once();
+    replayAll();
+    uninstallComponent(YARN, RESOURCE_MANAGER, HOST);
+    verifyAll();
+  }
+
+  @Test
+  public void skipsRemovingIdentityWhenClusterIsNotKerberized() throws 
Exception {
+    reset(cluster);
+    expect(cluster.getSecurityType()).andReturn(SecurityType.NONE).anyTimes();
+    replayAll();
+    uninstallComponent(OOZIE, OOZIE_SERVER, HOST);
+    verifyAll();
+  }
+
+  private void installComponent(String serviceName, final String 
componentName) {
+    Service service = createMock(serviceName + "_" + componentName, 
Service.class);
+    installedServices.put(serviceName, service);
+    expect(service.getServiceComponents()).andReturn(new HashMap<String, 
ServiceComponent>() {{
+      put(componentName, null);
+    }}).anyTimes();
+  }
+
+  private void uninstallComponent(String service, String component, String 
host) throws KerberosMissingAdminCredentialsException {
+    kerberosIdentityCleaner.componentRemoved(new 
ServiceComponentUninstalledEvent(CLUSTER_ID, "any", "any", service, component, 
host, false));
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    kerberosIdentityCleaner = new KerberosIdentityCleaner(new 
AmbariEventPublisher(), kerberosHelper, clusters);
+    kerberosDescriptor = kerberosDescriptorFactory.createInstance("{" +
+      "  'services': [" +
+      "    {" +
+      "      'name': 'OOZIE'," +
+      "      'components': [" +
+      "        {" +
+      "          'name': 'OOZIE_SERVER'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': '/HDFS/NAMENODE/hdfs'" +
+      "            }," +
+      "            {" +
+      "              'name': 'oozie_server1'" +
+      "            }," +"" +
+      "            {" +
+      "              'name': 'oozie_server2'," +
+      "              'principal': { 'value': 'oozie/_h...@example.com' }" +
+      "            }" +
+      "          ]" +
+      "        }" +
+      "      ]" +
+      "    }," +
+      "    {" +
+      "      'name': 'OOZIE2'," +
+      "      'components': [" +
+      "        {" +
+      "          'name': 'OOZIE_SERVER2'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': 'oozie_server3'," +
+      "              'principal': { 'value': 'oozie/_h...@example.com' }" +
+      "            }" +"" +
+      "          ]" +
+      "        }" +
+      "      ]" +
+      "    }," +
+      "    {" +
+      "      'name': 'YARN'," +
+      "      'components': [" +
+      "        {" +
+      "          'name': 'RESOURCE_MANAGER'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': 'rm_unique'" +
+      "            }," +
+      "            {" +
+      "              'name': 'rm1-shared'," +
+      "              'keytab' : { 'file' : 'shared' }" +
+      "            }" +
+      "          ]" +
+      "        }" +
+      "      ]" +
+      "    }," +
+      "    {" +
+      "      'name': 'YARN2'," +
+      "      'components': [" +
+      "        {" +
+      "          'name': 'RESOURCE_MANAGER2'," +
+      "          'identities': [" +
+      "            {" +
+      "              'name': 'rm2-shared'," +
+      "              'keytab' : { 'file' : 'shared' }" +
+      "            }" +
+      "          ]" +
+      "        }" +
+      "      ]" +
+      "    }" +
+      "  ]" +
+      "}");
+    expect(clusters.getCluster(CLUSTER_ID)).andReturn(cluster).anyTimes();
+    
expect(cluster.getSecurityType()).andReturn(SecurityType.KERBEROS).anyTimes();
+    
expect(kerberosHelper.getKerberosDescriptor(cluster)).andReturn(kerberosDescriptor).anyTimes();
+    expect(cluster.getServices()).andReturn(installedServices).anyTimes();
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b5c7db6/ambari-web/app/controllers/main/service/item.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/service/item.js 
b/ambari-web/app/controllers/main/service/item.js
index 37713dc..197eb8e 100644
--- a/ambari-web/app/controllers/main/service/item.js
+++ b/ambari-web/app/controllers/main/service/item.js
@@ -1388,8 +1388,10 @@ App.MainServiceItemController = 
Em.Controller.extend(App.SupportClientConfigsDow
               this._super();
             }
           });
-        self.set('deleteServiceProgressPopup', progressPopup);
-        self.deleteServiceCall(serviceNames);
+        
App.get('router.mainAdminKerberosController').getKDCSessionState(function() {
+          self.set('deleteServiceProgressPopup', progressPopup);
+          self.deleteServiceCall(serviceNames);
+        });
         this._super();
       },
 

Reply via email to