You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2018/05/23 19:45:20 UTC

[ambari] branch trunk updated: [AMBARI-23930]q - Provide a Framework For Regenerating Keytabs During Upgrade (#1355)

This is an automated email from the ASF dual-hosted git repository.

jonathanhurley pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 7b3aca8  [AMBARI-23930]q - Provide a Framework For Regenerating Keytabs During Upgrade (#1355)
7b3aca8 is described below

commit 7b3aca83483cbc3cad777ba2eadf7015c1234d0d
Author: Jonathan Hurley <jo...@apache.org>
AuthorDate: Wed May 23 15:45:14 2018 -0400

    [AMBARI-23930]q - Provide a Framework For Regenerating Keytabs During Upgrade (#1355)
---
 .../ambari/server/controller/KerberosHelper.java   |   7 +
 .../server/controller/KerberosHelperImpl.java      | 144 ++++++++++++++-------
 .../internal/UpgradeResourceProvider.java          | 110 ++++++++++++----
 .../serveraction/upgrades/AddComponentAction.java  |   2 +-
 .../org/apache/ambari/server/stack/HostsType.java  |  20 ++-
 .../ambari/server/stack/MasterHostResolver.java    |  14 +-
 .../state/stack/upgrade/ClusterGrouping.java       |  34 ++++-
 .../server/state/stack/upgrade/Grouping.java       |   3 +
 .../state/stack/upgrade/RegenerateKeytabsTask.java |  81 ++++++++++++
 .../server/state/stack/upgrade/StageWrapper.java   |   3 +-
 .../ambari/server/state/stack/upgrade/Task.java    |  12 +-
 .../server/state/stack/upgrade/TaskWrapper.java    |   4 +-
 .../stack-hooks/before-SET_KEYTAB/scripts/hook.py  |  38 ++++++
 ambari-server/src/main/resources/upgrade-pack.xsd  |   9 +-
 .../server/controller/KerberosHelperTest.java      |  45 ++++++-
 .../internal/UpgradeResourceProviderTest.java      |  48 ++++++-
 .../before-SET_KEYTAB/test_before_set_keytab.py    |  41 ++++++
 .../stacks/2.2/KERBEROS/test_kerberos_client.py    |   2 +-
 .../upgrades/upgrade_test_regenerate_keytabs.xml   |  41 ++++++
 19 files changed, 558 insertions(+), 100 deletions(-)

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 297fc3c..38701e2 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
@@ -132,6 +132,13 @@ public interface KerberosHelper {
   String PRECONFIGURE_SERVICES = "preconfigure_services";
 
   /**
+   * If {@code true}, then this will create the stages and tasks as being
+   * retry-able. A failure during Kerberos operations will not cause the entire
+   * request to be aborted.
+   */
+  String ALLOW_RETRY = "allow_retry_on_failure";
+
+  /**
    * Toggles Kerberos security to enable it or remove it depending on the state of the cluster.
    * <p/>
    * The cluster "security_type" property is used to determine the security state of the cluster.
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 c492173..0fc8165 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
@@ -279,6 +279,12 @@ public class KerberosHelperImpl implements KerberosHelper {
                 throw new AmbariException(String.format("Custom operation %s can only be requested with the security type cluster property: %s", operation.name(), SecurityType.KERBEROS.name()));
               }
 
+              boolean retryAllowed = false;
+              if (requestProperties.containsKey(ALLOW_RETRY)) {
+                String allowRetryString = requestProperties.get(ALLOW_RETRY);
+                retryAllowed = Boolean.parseBoolean(allowRetryString);
+              }
+
               CreatePrincipalsAndKeytabsHandler handler = null;
 
               Set<String> hostFilter = parseHostFilter(requestProperties);
@@ -296,6 +302,8 @@ public class KerberosHelperImpl implements KerberosHelper {
               }
 
               if (handler != null) {
+                handler.setRetryAllowed(retryAllowed);
+
                 requestStageContainer = handle(cluster, getKerberosDetails(cluster, manageIdentities),
                   serviceComponentFilter, hostFilter, null, null, requestStageContainer, handler);
               } else {
@@ -473,7 +481,7 @@ public class KerberosHelperImpl implements KerberosHelper {
     // If Ambari is managing it own identities then add AMBARI to the set of installed service so
     // that its Kerberos descriptor entries will be included.
     if (createAmbariIdentities(existingConfigurations.get(KERBEROS_ENV))) {
-      installedServices = new HashMap<String, Set<String>>(installedServices);
+      installedServices = new HashMap<>(installedServices);
       installedServices.put(RootService.AMBARI.name(), Collections.singleton(RootComponent.AMBARI_SERVER.name()));
     }
 
@@ -2533,6 +2541,7 @@ public class KerberosHelperImpl implements KerberosHelper {
    * @return a File pointing to the new temporary directory, or null if one was not created
    * @throws AmbariException if a new temporary directory cannot be created
    */
+  @Override
   public File createTemporaryDirectory() throws AmbariException {
     try {
       File temporaryDirectory = getConfiguredTemporaryDirectory();
@@ -2721,41 +2730,6 @@ public class KerberosHelperImpl implements KerberosHelper {
   }
 
   /**
-   * Creates a new stage with a single task describing the ServerAction class to invoke and the other
-   * task-related information.
-   *
-   * @param id                the new stage's id
-   * @param cluster           the relevant Cluster
-   * @param requestId         the relevant request Id
-   * @param requestContext    a String describing the stage
-   * @param commandParams     JSON-encoded command parameters
-   * @param hostParams        JSON-encoded host parameters
-   * @param actionClass       The ServeAction class that implements the action to invoke
-   * @param event             The relevant ServiceComponentHostServerActionEvent
-   * @param commandParameters a Map of command parameters to attach to the task added to the new
-   *                          stage
-   * @param commandDetail     a String declaring a descriptive name to pass to the action - null or an
-   *                          empty string indicates no value is to be set
-   * @param timeout           the timeout for the task/action  @return a newly created 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;
-  }
-
-  /**
    * Given a Collection of ServiceComponentHosts generates a unique list of hosts.
    *
    * @param serviceComponentHosts a Collection of ServiceComponentHosts from which to to retrieve host names
@@ -3330,6 +3304,23 @@ public class KerberosHelperImpl implements KerberosHelper {
    */
   private abstract class Handler {
 
+    /**
+     * If (@code true}, allows stages and tasks created with the handler to be
+     * retried instead of outright failing a task.
+     *
+     * @see KerberosHelper#ALLOW_RETRY
+     */
+    protected boolean retryAllowed = false;
+
+    /**
+     * Sets whether tasks created as part of this handler can be retry if they fail. If a task
+     * cannot be retried it will fail the entire request.
+     *
+     * @param retryAllowed
+     */
+    void setRetryAllowed(boolean retryAllowed) {
+      this.retryAllowed = retryAllowed;
+    }
 
     /**
      * Creates the necessary stages to complete the relevant task and stores them in the supplied
@@ -3551,12 +3542,14 @@ public class KerberosHelperImpl implements KerberosHelper {
 
       if (!hosts.isEmpty()) {
         Map<String, String> requestParams = new HashMap<>();
+       
+        ActionExecutionContext actionExecContext = createActionExecutionContext(
+            cluster.getClusterName(), 
+            SET_KEYTAB, 
+            createRequestResourceFilters(hosts),
+            requestParams, 
+            retryAllowed);        
 
-        ActionExecutionContext actionExecContext = new ActionExecutionContext(
-          cluster.getClusterName(),
-          SET_KEYTAB,
-          createRequestResourceFilters(hosts),
-          requestParams);
         customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, stage,
           requestParams, null);
       }
@@ -3586,11 +3579,13 @@ public class KerberosHelperImpl implements KerberosHelper {
       if (!hostsToInclude.isEmpty()) {
         Map<String, String> requestParams = new HashMap<>();
 
-        ActionExecutionContext actionExecContext = new ActionExecutionContext(
-          cluster.getClusterName(),
-          CHECK_KEYTABS,
-          createRequestResourceFilters(hostsToInclude),
-          requestParams);
+        ActionExecutionContext actionExecContext = createActionExecutionContext(
+            cluster.getClusterName(), 
+            CHECK_KEYTABS, 
+            createRequestResourceFilters(hostsToInclude),
+            requestParams, 
+            retryAllowed);        
+
         customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, stage, requestParams, null);
       }
       RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder);
@@ -3797,6 +3792,63 @@ public class KerberosHelperImpl implements KerberosHelper {
       requestResourceFilters.add(reqResFilter);
       return requestResourceFilters;
     }
+
+    /**
+     * Creates a new stage with a single task describing the ServerAction class to invoke and the other
+     * task-related information.
+     *
+     * @param id                the new stage's id
+     * @param cluster           the relevant Cluster
+     * @param requestId         the relevant request Id
+     * @param requestContext    a String describing the stage
+     * @param commandParams     JSON-encoded command parameters
+     * @param hostParams        JSON-encoded host parameters
+     * @param actionClass       The ServeAction class that implements the action to invoke
+     * @param event             The relevant ServiceComponentHostServerActionEvent
+     * @param commandParameters a Map of command parameters to attach to the task added to the new
+     *                          stage
+     * @param commandDetail     a String declaring a descriptive name to pass to the action - null or an
+     *                          empty string indicates no value is to be set
+     * @param timeout           the timeout for the task/action  @return a newly created 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,
+          retryAllowed, false);
+
+      return stage;
+    }
+
+    /**
+     * Creates an {@link ActionExecutionContext} where some of the common values are pre-initialized.
+     * 
+     * @param clusterName
+     * @param commandName
+     * @param resourceFilters
+     * @param parameters
+     * @param retryAllowed
+     * @return
+     */
+    private ActionExecutionContext createActionExecutionContext(String clusterName,
+        String commandName, List<RequestResourceFilter> resourceFilters,
+        Map<String, String> parameters, boolean retryAllowed) {
+
+      ActionExecutionContext actionExecContext = new ActionExecutionContext(clusterName, SET_KEYTAB,
+          resourceFilters, parameters);
+
+      actionExecContext.setRetryAllowed(retryAllowed);
+
+      return actionExecContext;
+    }
   }
 
   /**
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
index 52425b4..c00121b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradeResourceProvider.java
@@ -50,6 +50,8 @@ import org.apache.ambari.server.controller.AmbariActionExecutionHelper;
 import org.apache.ambari.server.controller.AmbariCustomCommandExecutionHelper;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.ExecuteCommandJson;
+import org.apache.ambari.server.controller.KerberosHelper;
+import org.apache.ambari.server.controller.KerberosHelperImpl.SupportedCustomOperation;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -78,6 +80,7 @@ import org.apache.ambari.server.security.authorization.AuthorizationException;
 import org.apache.ambari.server.security.authorization.AuthorizationHelper;
 import org.apache.ambari.server.security.authorization.ResourceType;
 import org.apache.ambari.server.security.authorization.RoleAuthorization;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ConfigHelper;
@@ -261,6 +264,12 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
   @Inject
   private RequestDAO requestDAO;
 
+  /**
+   * Used for creating keytab regeneration stage inside of an upgrade request.
+   */
+  @Inject
+  private static Provider<KerberosHelper> s_kerberosHelper;
+
   static {
     // properties
     PROPERTY_IDS.add(UPGRADE_CLUSTER_NAME);
@@ -798,41 +807,86 @@ public class UpgradeResourceProvider extends AbstractControllerResourceProvider
       for (StageWrapper wrapper : group.items) {
         RepositoryVersionEntity effectiveRepositoryVersion = upgradeContext.getRepositoryVersion();
 
-        if (wrapper.getType() == StageWrapper.Type.SERVER_SIDE_ACTION) {
-          // !!! each stage is guaranteed to be of one type. but because there
-          // is a bug that prevents one stage with multiple tasks assigned for
-          // the same host, break them out into individual stages.
-          for (TaskWrapper taskWrapper : wrapper.getTasks()) {
-            for (Task task : taskWrapper.getTasks()) {
-              if (upgradeContext.isManualVerificationAutoSkipped()
-                  && task.getType() == Task.Type.MANUAL) {
-                continue;
+        switch(wrapper.getType()) {
+          case SERVER_SIDE_ACTION:{
+            // !!! each stage is guaranteed to be of one type. but because there
+            // is a bug that prevents one stage with multiple tasks assigned for
+            // the same host, break them out into individual stages.
+            for (TaskWrapper taskWrapper : wrapper.getTasks()) {
+              for (Task task : taskWrapper.getTasks()) {
+                if (upgradeContext.isManualVerificationAutoSkipped()
+                    && task.getType() == Task.Type.MANUAL) {
+                  continue;
+                }
+
+                UpgradeItemEntity itemEntity = new UpgradeItemEntity();
+
+                itemEntity.setText(wrapper.getText());
+                itemEntity.setTasks(wrapper.getTasksJson());
+                itemEntity.setHosts(wrapper.getHostsJson());
+
+                injectVariables(configHelper, cluster, itemEntity);
+                if (makeServerSideStage(group, upgradeContext, effectiveRepositoryVersion, req,
+                    itemEntity, (ServerSideActionTask) task, configUpgradePack)) {
+                  itemEntities.add(itemEntity);
+                }
               }
-
-              UpgradeItemEntity itemEntity = new UpgradeItemEntity();
-
-              itemEntity.setText(wrapper.getText());
-              itemEntity.setTasks(wrapper.getTasksJson());
-              itemEntity.setHosts(wrapper.getHostsJson());
-
-              injectVariables(configHelper, cluster, itemEntity);
-              if (makeServerSideStage(group, upgradeContext, effectiveRepositoryVersion, req,
-                  itemEntity, (ServerSideActionTask) task, configUpgradePack)) {
+            }
+            break;
+          }
+          case REGENERATE_KEYTABS: {
+            try {
+              // remmeber how many stages we had before adding keytab stuff
+              int stageCount = req.getStages().size();
+
+              // build a map of request properties which say to
+              //   - only regenerate missing tabs
+              //   - allow all tasks which fail to be retried (so the upgrade doesn't abort)
+              Map<String, String> requestProperties = new HashMap<>();
+              requestProperties.put(SupportedCustomOperation.REGENERATE_KEYTABS.name().toLowerCase(), "missing");
+              requestProperties.put(KerberosHelper.ALLOW_RETRY, Boolean.TRUE.toString().toLowerCase());
+
+              // add stages to the upgrade which will regenerate missing keytabs only
+              req = s_kerberosHelper.get().executeCustomOperations(cluster, requestProperties, req, null);
+
+              // for every stage which was added for kerberos stuff create an
+              // associated upgrade item for it
+              List<Stage> stages = req.getStages();
+              int newStageCount = stages.size();
+              for (int i = stageCount; i < newStageCount; i++) {
+                Stage stage = stages.get(i);
+                stage.setSkippable(group.skippable);
+                stage.setAutoSkipFailureSupported(group.supportsAutoSkipOnFailure);
+
+                UpgradeItemEntity itemEntity = new UpgradeItemEntity();
+                itemEntity.setStageId(stage.getStageId());
+                itemEntity.setText(stage.getRequestContext());
+                itemEntity.setTasks(wrapper.getTasksJson());
+                itemEntity.setHosts(wrapper.getHostsJson());
                 itemEntities.add(itemEntity);
+                injectVariables(configHelper, cluster, itemEntity);
               }
+            } catch (KerberosOperationException kerberosOperationException) {
+              throw new AmbariException("Unable to build keytab regeneration stage",
+                  kerberosOperationException);
             }
+
+            break;
           }
-        } else {
-          UpgradeItemEntity itemEntity = new UpgradeItemEntity();
-          itemEntity.setText(wrapper.getText());
-          itemEntity.setTasks(wrapper.getTasksJson());
-          itemEntity.setHosts(wrapper.getHostsJson());
-          itemEntities.add(itemEntity);
+          default: {
+            UpgradeItemEntity itemEntity = new UpgradeItemEntity();
+            itemEntity.setText(wrapper.getText());
+            itemEntity.setTasks(wrapper.getTasksJson());
+            itemEntity.setHosts(wrapper.getHostsJson());
+            itemEntities.add(itemEntity);
+
+            injectVariables(configHelper, cluster, itemEntity);
 
-          injectVariables(configHelper, cluster, itemEntity);
+            // upgrade items match a stage
+            createStage(group, upgradeContext, effectiveRepositoryVersion, req, itemEntity, wrapper);
 
-          // upgrade items match a stage
-          createStage(group, upgradeContext, effectiveRepositoryVersion, req, itemEntity, wrapper);
+            break;
+          }
         }
       }
 
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AddComponentAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AddComponentAction.java
index ee3150e..9a339d2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AddComponentAction.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/upgrades/AddComponentAction.java
@@ -81,7 +81,7 @@ public class AddComponentAction extends AbstractUpgradeServerAction {
     if (candidates.isEmpty()) {
       return createCommandReport(0, HostRoleStatus.FAILED, "{}", "", String.format(
           "Unable to add a new component to the cluster as there are no hosts which contain %s's %s",
-          task.service, task.component));
+          task.hostService, task.hostComponent));
     }
 
     Service service = cluster.getService(task.service);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/HostsType.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/HostsType.java
index 530c9ee..7dbfff8 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/HostsType.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/HostsType.java
@@ -30,6 +30,9 @@ import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
 
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Host;
+import org.apache.ambari.server.state.MaintenanceState;
 import org.apache.ambari.server.state.ServiceComponentHost;
 
 /**
@@ -76,7 +79,7 @@ public class HostsType {
    * For example: [sec1, sec2, master1, sec3, sec4, master2]
    */
   public void arrangeHostSecondariesFirst() {
-    this.hosts = getHighAvailabilityHosts().stream()
+    hosts = getHighAvailabilityHosts().stream()
       .flatMap(each -> Stream.concat(each.getSecondaries().stream(), Stream.of(each.getMaster())))
       .collect(toCollection(LinkedHashSet::new));
   }
@@ -170,6 +173,21 @@ public class HostsType {
     return HostsType.normal(host);
   }
 
+  /**
+   * Create an instance with all healthy hosts.
+   */
+  public static HostsType healthy(Cluster cluster) {
+    LinkedHashSet<String> hostNames = new LinkedHashSet<>();
+    for (Host host : cluster.getHosts()) {
+      MaintenanceState maintenanceState = host.getMaintenanceState(cluster.getClusterId());
+      if (maintenanceState == MaintenanceState.OFF) {
+        hostNames.add(host.getHostName());
+      }
+    }
+
+    return normal(hostNames);
+  }
+
   private HostsType(List<HighAvailabilityHosts> highAvailabilityHosts, LinkedHashSet<String> hosts) {
     this.highAvailabilityHosts = highAvailabilityHosts;
     this.hosts = hosts;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java
index 9042d2e..b018277 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/MasterHostResolver.java
@@ -176,7 +176,7 @@ public class MasterHostResolver {
       String serviceName, String componentName) {
     Collection<Host> candidates = cluster.getHosts();
     if (StringUtils.isNotBlank(serviceName) && StringUtils.isNotBlank(componentName)) {
-      List<ServiceComponentHost> schs = cluster.getServiceComponentHosts(serviceName,componentName);      
+      List<ServiceComponentHost> schs = cluster.getServiceComponentHosts(serviceName,componentName);
       candidates = schs.stream().map(sch -> sch.getHost()).collect(Collectors.toList());
     }
 
@@ -185,23 +185,23 @@ public class MasterHostResolver {
     }
 
     // figure out where to add the new component
-    List<Host> hosts = Lists.newArrayList();
+    List<Host> winners = Lists.newArrayList();
     switch (executeHostType) {
       case ALL:
-        hosts.addAll(candidates);
+        winners.addAll(candidates);
         break;
       case FIRST:
-        hosts.add(candidates.iterator().next());
+        winners.add(candidates.iterator().next());
         break;
       case MASTER:
-        hosts.add(candidates.iterator().next());
+        winners.add(candidates.iterator().next());
         break;
       case ANY:
-        hosts.add(candidates.iterator().next());
+        winners.add(candidates.iterator().next());
         break;
     }
 
-    return candidates;
+    return winners;
   }
 
   /**
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
index e013d18..fcc7e57 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/ClusterGrouping.java
@@ -222,6 +222,10 @@ public class ClusterGrouping extends Grouping {
               wrapper = getExecuteStageWrapper(upgradeContext, execution);
               break;
 
+            case REGENERATE_KEYTABS:
+              wrapper = getRegenerateKeytabsWrapper(upgradeContext, execution);
+              break;
+
             default:
               break;
           }
@@ -274,10 +278,32 @@ public class ClusterGrouping extends Grouping {
   }
 
   /**
-   * Return a Stage Wrapper for a task meant to execute code, typically on Ambari Server.
-   * @param ctx Upgrade Context
-   * @param execution Execution Stage
-   * @return Returns a Stage Wrapper, or null if a valid one could not be created.
+   * Return a {@link StageWrapper} for regeneration of keytabs.
+   *
+   * @param ctx
+   *          Upgrade Context
+   * @param execution
+   *          Execution Stage
+   * @return a {@link StageWrapper} which will regenerate keytabs on all hosts.
+   */
+  private StageWrapper getRegenerateKeytabsWrapper(UpgradeContext ctx, ExecuteStage execution) {
+    Task task = execution.task;
+    HostsType hosts = HostsType.healthy(ctx.getCluster());
+
+    return new StageWrapper(StageWrapper.Type.REGENERATE_KEYTABS, execution.title,
+        new TaskWrapper(null, null, hosts.getHosts(), task));
+  }
+
+  /**
+   * Return a Stage Wrapper for a task meant to execute code, typically on
+   * Ambari Server.
+   *
+   * @param ctx
+   *          Upgrade Context
+   * @param execution
+   *          Execution Stage
+   * @return Returns a Stage Wrapper, or null if a valid one could not be
+   *         created.
    */
   private StageWrapper getExecuteStageWrapper(UpgradeContext ctx, ExecuteStage execution) {
     String service   = execution.service;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
index cc42d65..150c9fa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Grouping.java
@@ -349,6 +349,9 @@ public class Grouping {
         case SERVICE_CHECK:
           type = StageWrapper.Type.SERVICE_CHECK;
           break;
+        case REGENERATE_KEYTABS:
+          type = StageWrapper.Type.REGENERATE_KEYTABS;
+          break;
       }
       tasks.add(initial);
     }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RegenerateKeytabsTask.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RegenerateKeytabsTask.java
new file mode 100644
index 0000000..704235f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RegenerateKeytabsTask.java
@@ -0,0 +1,81 @@
+/*
+ * 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.state.stack.upgrade;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+import com.google.gson.annotations.Expose;
+
+/**
+ * The {@link RegenerateKeytabsTask} is used for injection Kerberos tasks into
+ * an upgrade which will regenerate keytabs. The regeneration is always partial,
+ * opting to only regenerate missing keytabs.
+ */
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "regenerate_keytabs")
+public class RegenerateKeytabsTask extends Task {
+
+  @Expose
+  @XmlTransient
+  private Task.Type type = Type.REGENERATE_KEYTABS;
+
+  /**
+   * Constructor.
+   *
+   */
+  public RegenerateKeytabsTask() {
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Task.Type getType() {
+    return type;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public StageWrapper.Type getStageWrapperType() {
+    return StageWrapper.Type.REGENERATE_KEYTABS;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getActionVerb() {
+    return "Regenerating Keytabs";
+  }
+
+  /**
+   * Gets a JSON representation of this task.
+   *
+   * @return a JSON representation of this task.
+   */
+  public String toJson() {
+    return GSON.toJson(this);
+  }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapper.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapper.java
index db8f849..81c7ade 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/StageWrapper.java
@@ -159,7 +159,8 @@ public class StageWrapper {
     SERVICE_CHECK,
     STOP,
     START,
-    CONFIGURE
+    CONFIGURE, 
+    REGENERATE_KEYTABS;
   }
 
   /**
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Task.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Task.java
index 8b141a8..a77fceb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Task.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/Task.java
@@ -34,7 +34,8 @@ import com.google.gson.annotations.Expose;
 @XmlSeeAlso(
     value = { ExecuteTask.class, CreateAndConfigureTask.class, ConfigureTask.class,
         ManualTask.class, RestartTask.class, StartTask.class, StopTask.class,
-        ServerActionTask.class, ConfigureFunction.class, AddComponentTask.class })
+        ServerActionTask.class, ConfigureFunction.class, AddComponentTask.class,
+        RegenerateKeytabsTask.class })
 public abstract class Task {
 
   /**
@@ -161,7 +162,12 @@ public abstract class Task {
     /**
      * A task which adds new components to the cluster during the upgrade.
      */
-    ADD_COMPONENT;
+    ADD_COMPONENT,
+
+    /**
+     * Create keytab regeneration steps as part of the upgrade.
+     */
+    REGENERATE_KEYTABS;
 
     /**
      * Commands which run on the server.
@@ -173,7 +179,7 @@ public abstract class Task {
      * Commands which are run on agents.
      */
     public static final EnumSet<Type> COMMANDS = EnumSet.of(RESTART, START, CONFIGURE_FUNCTION,
-        STOP, SERVICE_CHECK);
+        STOP, SERVICE_CHECK, REGENERATE_KEYTABS);
 
     /**
      * @return {@code true} if the task is manual or automated.
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/TaskWrapper.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/TaskWrapper.java
index 56047d7..b13c9978 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/TaskWrapper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/TaskWrapper.java
@@ -26,7 +26,7 @@ import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 
-import com.google.common.base.Objects;
+import com.google.common.base.MoreObjects;
 
 /**
  * Aggregates all upgrade tasks for a HostComponent into one wrapper.
@@ -112,7 +112,7 @@ public class TaskWrapper {
    */
   @Override
   public String toString() {
-    return Objects.toStringHelper(this).add("service", service)
+    return MoreObjects.toStringHelper(this).add("service", service)
         .add("component", component)
         .add("tasks", tasks)
         .add("hosts", hosts)
diff --git a/ambari-server/src/main/resources/stack-hooks/before-SET_KEYTAB/scripts/hook.py b/ambari-server/src/main/resources/stack-hooks/before-SET_KEYTAB/scripts/hook.py
new file mode 100644
index 0000000..4d028fc
--- /dev/null
+++ b/ambari-server/src/main/resources/stack-hooks/before-SET_KEYTAB/scripts/hook.py
@@ -0,0 +1,38 @@
+"""
+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.
+
+"""
+
+from resource_management import *
+
+class BeforeSetKeytabHook(Hook):
+
+  def hook(self, env):
+    """
+    This will invoke the before-ANY hook which contains all of the user and group creation logic.
+    Keytab regeneration requires all users are already created, which is usually done by the
+    before-INSTALL hook. However, if the keytab regeneration is executed as part of an upgrade,
+    then the before-INSTALL hook never ran.
+
+    :param env:
+    :return:
+    """
+    self.run_custom_hook('before-ANY')
+
+if __name__ == "__main__":
+  BeforeSetKeytabHook().execute()
+
diff --git a/ambari-server/src/main/resources/upgrade-pack.xsd b/ambari-server/src/main/resources/upgrade-pack.xsd
index c77e29b..f9e6b37 100644
--- a/ambari-server/src/main/resources/upgrade-pack.xsd
+++ b/ambari-server/src/main/resources/upgrade-pack.xsd
@@ -374,7 +374,14 @@
       </xs:extension>
     </xs:complexContent>
   </xs:complexType>
-  
+
+  <xs:complexType name="regenerate_keytabs">
+    <xs:complexContent>
+      <xs:extension base="abstract-server-task-type">
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
   <xs:complexType name="order-type">
     <xs:sequence>
       <xs:element name="group" minOccurs="1" maxOccurs="unbounded" />
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java
index 5c6f693..09d5d4c 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java
@@ -167,6 +167,7 @@ public class KerberosHelperTest extends EasyMockSupport {
   private final AmbariMetaInfo metaInfo = createMock(AmbariMetaInfo.class);
   private final TopologyManager topologyManager = createMock(TopologyManager.class);
   private final Configuration configuration = createMock(Configuration.class);
+  private final AmbariCustomCommandExecutionHelper customCommandExecutionHelperMock = createNiceMock(AmbariCustomCommandExecutionHelper.class);
 
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -249,7 +250,7 @@ public class KerberosHelperTest extends EasyMockSupport {
         bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
         bind(SecurityHelper.class).toInstance(createNiceMock(SecurityHelper.class));
         bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
-        bind(AmbariCustomCommandExecutionHelper.class).toInstance(createNiceMock(AmbariCustomCommandExecutionHelper.class));
+        bind(AmbariCustomCommandExecutionHelper.class).toInstance(customCommandExecutionHelperMock);
         bind(AmbariManagementController.class).toInstance(createNiceMock(AmbariManagementController.class));
         bind(AmbariMetaInfo.class).toInstance(metaInfo);
         bind(ActionManager.class).toInstance(createNiceMock(ActionManager.class));
@@ -498,6 +499,33 @@ public class KerberosHelperTest extends EasyMockSupport {
     testRegenerateKeytabs(new PrincipalKeyCredential("principal", "password"), false, false);
   }
 
+  /**
+   * Tests that when regenerating keytabs for an upgrade, that the retry allowed
+   * boolean is set on the tasks created.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testRegenerateKeytabsWithRetryAllowed() throws Exception {
+    Capture<ActionExecutionContext> captureContext = Capture.newInstance();
+    customCommandExecutionHelperMock.addExecutionCommandsToStage(capture(captureContext),
+        anyObject(Stage.class), anyObject(), eq(null));
+
+    expectLastCall().atLeastOnce();
+
+    Map<String, String> requestMap = new HashMap<>();
+    requestMap.put(KerberosHelper.DIRECTIVE_REGENERATE_KEYTABS, "true");
+    requestMap.put(KerberosHelper.ALLOW_RETRY, "true");
+
+    RequestStageContainer requestStageContainer = testRegenerateKeytabs(
+        new PrincipalKeyCredential("principal", "password"), requestMap, false, false);
+
+    assertNotNull(requestStageContainer);
+
+    ActionExecutionContext capturedContext = captureContext.getValue();
+    assertTrue(capturedContext.isRetryAllowed());
+  }
+
   @Test
   public void testDisableKerberos() throws Exception {
     testDisableKerberos(new PrincipalKeyCredential("principal", "password"));
@@ -1309,7 +1337,11 @@ public class KerberosHelperTest extends EasyMockSupport {
     verifyAll();
   }
 
-  private void testRegenerateKeytabs(final PrincipalKeyCredential PrincipalKeyCredential, boolean mockRequestStageContainer, final boolean testInvalidHost) throws Exception {
+  private RequestStageContainer testRegenerateKeytabs(final PrincipalKeyCredential principalKeyCredential, boolean mockRequestStageContainer, final boolean testInvalidHost) throws Exception {
+    return testRegenerateKeytabs(principalKeyCredential, Collections.singletonMap(KerberosHelper.DIRECTIVE_REGENERATE_KEYTABS, "true"), mockRequestStageContainer, testInvalidHost);
+  }
+
+  private RequestStageContainer testRegenerateKeytabs(final PrincipalKeyCredential PrincipalKeyCredential, Map<String,String> requestMap, boolean mockRequestStageContainer, final boolean testInvalidHost) throws Exception {
 
     KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class);
 
@@ -1492,9 +1524,14 @@ public class KerberosHelperTest extends EasyMockSupport {
     credentialStoreService.setCredential(cluster.getClusterName(), KerberosHelper.KDC_ADMINISTRATOR_CREDENTIAL_ALIAS,
         PrincipalKeyCredential, CredentialStoreType.TEMPORARY);
 
-    Assert.assertNotNull(kerberosHelper.executeCustomOperations(cluster, Collections.singletonMap(KerberosHelper.DIRECTIVE_REGENERATE_KEYTABS, "true"), requestStageContainer, true));
+    RequestStageContainer returnValue = kerberosHelper.executeCustomOperations(cluster,
+        requestMap, requestStageContainer, true);
+
+    Assert.assertNotNull(returnValue);
 
     verifyAll();
+
+    return returnValue;
   }
 
   @Test
@@ -2860,7 +2897,7 @@ public class KerberosHelperTest extends EasyMockSupport {
       put(DIRECTIVE_COMPONENTS, "SERVICE1:COMPONENT1;COMPONENT2,SERVICE2:COMPONENT1;COMPONENT2;COMPONENT3");
     }};
 
-    Set<String> expectedHosts = new HashSet<String>(Arrays.asList("host1", "host2", "host3"));
+    Set<String> expectedHosts = new HashSet<>(Arrays.asList("host1", "host2", "host3"));
     Set<String> hosts = KerberosHelperImpl.parseHostFilter(requestProperties);
 
     assertEquals(expectedHosts, hosts);
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
index 72d364b..f577264 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradeResourceProviderTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ambari.server.controller.internal;
 
+import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.junit.Assert.assertEquals;
@@ -56,6 +57,7 @@ import org.apache.ambari.server.audit.AuditLogger;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.AmbariServer;
+import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.controller.ResourceProviderFactory;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Request;
@@ -100,6 +102,7 @@ import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostState;
 import org.apache.ambari.server.state.RepositoryType;
+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;
@@ -109,6 +112,7 @@ import org.apache.ambari.server.state.UpgradeHelper;
 import org.apache.ambari.server.state.UpgradeState;
 import org.apache.ambari.server.state.stack.upgrade.ConfigureTask;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
+import org.apache.ambari.server.state.stack.upgrade.RegenerateKeytabsTask;
 import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
 import org.apache.ambari.server.topology.TopologyManager;
 import org.apache.ambari.server.utils.StageUtils;
@@ -147,6 +151,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
   private RepositoryVersionDAO repoVersionDao = null;
   private Injector injector;
   private Clusters clusters;
+  private Cluster cluster;
   private AmbariManagementController amc;
   private ConfigHelper configHelper;
   private AgentConfigsHolder agentConfigsHolder = createNiceMock(AgentConfigsHolder.class);
@@ -154,6 +159,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
   private TopologyManager topologyManager;
   private ConfigFactory configFactory;
   private HostRoleCommandDAO hrcDAO;
+  private KerberosHelper kerberosHelperMock = createNiceMock(KerberosHelper.class);
 
   RepositoryVersionEntity repoVersionEntity2110;
   RepositoryVersionEntity repoVersionEntity2111;
@@ -256,7 +262,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
     clusters = injector.getInstance(Clusters.class);
 
     clusters.addCluster("c1", stack211);
-    Cluster cluster = clusters.getCluster("c1");
+    cluster = clusters.getCluster("c1");
 
     clusters.addHost("h1");
     Host host = clusters.getHost("h1");
@@ -2081,6 +2087,45 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
   }
 
   /**
+   * Tests that a {@link RegenerateKeytabsTask} causes the upgrade to inject the
+   * correct stages.
+   *
+   * @throws Exception
+   */
+  @Test
+  public void testCreateRegenerateKeytabStages() throws Exception {
+    Map<String, Object> requestProps = new HashMap<>();
+    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2200.getId()));
+    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test_regenerate_keytabs");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, "true");
+    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());
+
+    cluster.setSecurityType(SecurityType.KERBEROS);
+
+    RequestStageContainer requestStageContainer = createNiceMock(RequestStageContainer.class);
+    expect(requestStageContainer.getStages()).andReturn(Lists.newArrayList()).once();
+
+    expect(kerberosHelperMock.executeCustomOperations(eq(cluster), EasyMock.anyObject(),
+        EasyMock.anyObject(RequestStageContainer.class), eq(null))).andReturn(
+            requestStageContainer).once();
+
+    replayAll();
+
+    ResourceProvider upgradeResourceProvider = createProvider(amc);
+    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
+
+    try {
+      upgradeResourceProvider.createResources(request);
+      Assert.fail("The mock request stage container should have caused a problem in JPA");
+    } catch (IllegalArgumentException illegalArgumentException) {
+      // ignore
+    }
+
+    verifyAll();
+  }
+
+  /**
    *
    */
   private class MockModule implements Module {
@@ -2091,6 +2136,7 @@ public class UpgradeResourceProviderTest extends EasyMockSupport {
     public void configure(Binder binder) {
       binder.bind(ConfigHelper.class).toInstance(configHelper);
       binder.bind(AgentConfigsHolder.class).toInstance(agentConfigsHolder);
+      binder.bind(KerberosHelper.class).toInstance(kerberosHelperMock);
     }
   }
 }
diff --git a/ambari-server/src/test/python/stacks/2.0.6/hooks/before-SET_KEYTAB/test_before_set_keytab.py b/ambari-server/src/test/python/stacks/2.0.6/hooks/before-SET_KEYTAB/test_before_set_keytab.py
new file mode 100644
index 0000000..a706d9f
--- /dev/null
+++ b/ambari-server/src/test/python/stacks/2.0.6/hooks/before-SET_KEYTAB/test_before_set_keytab.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+'''
+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.
+'''
+
+from stacks.utils.RMFTestCase import *
+from mock.mock import MagicMock, patch
+from resource_management import Hook
+import itertools
+
+@patch("platform.linux_distribution", new = MagicMock(return_value="Linux"))
+@patch("os.path.exists", new = MagicMock(return_value=True))
+@patch.object(Hook, "run_custom_hook")
+class TestHookBeforeSetKeytab(RMFTestCase):
+  STACK_VERSION = '2.0.6'
+  def test_hook_default(self, run_custom_hook_mock):
+    self.executeScript("before-SET_KEYTAB/scripts/hook.py",
+                       classname="BeforeSetKeytabHook",
+                       command="hook",
+                       stack_version = self.STACK_VERSION,
+                       target=RMFTestCase.TARGET_STACK_HOOKS,
+                       config_file="default.json",
+                       call_mocks=itertools.cycle([(0, "1000")])
+    )
+
+    run_custom_hook_mock.assert_called_with('before-ANY')
\ No newline at end of file
diff --git a/ambari-server/src/test/python/stacks/2.2/KERBEROS/test_kerberos_client.py b/ambari-server/src/test/python/stacks/2.2/KERBEROS/test_kerberos_client.py
index 8d96707..8464526 100644
--- a/ambari-server/src/test/python/stacks/2.2/KERBEROS/test_kerberos_client.py
+++ b/ambari-server/src/test/python/stacks/2.2/KERBEROS/test_kerberos_client.py
@@ -18,7 +18,7 @@ limitations under the License.
 """
 
 import json
-from mock.mock import MagicMock, patch
+from mock.mock import patch
 import os
 import sys
 import use_cases
diff --git a/ambari-server/src/test/resources/stacks/HDP/2.1.1/upgrades/upgrade_test_regenerate_keytabs.xml b/ambari-server/src/test/resources/stacks/HDP/2.1.1/upgrades/upgrade_test_regenerate_keytabs.xml
new file mode 100644
index 0000000..df23ffd
--- /dev/null
+++ b/ambari-server/src/test/resources/stacks/HDP/2.1.1/upgrades/upgrade_test_regenerate_keytabs.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!--
+   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.
+-->
+<upgrade xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="upgrade-pack.xsd">
+  <target>2.2.*.*</target>
+  <target-stack>HDP-2.2.0</target-stack>
+  <type>ROLLING</type>
+  <prerequisite-checks/>
+
+  <order>
+    <group xsi:type="cluster" name="REGENERATE_KEYTABS" title="Regenerate Missing Keytabs">
+      <condition xsi:type="security" type="kerberos"/>
+      <direction>UPGRADE</direction>
+      <execute-stage title="Regenerate Missing Keytabs">
+        <task xsi:type="regenerate_keytabs"/>
+      </execute-stage>
+    </group>
+  </order>
+  
+  <processing>
+    <service name="ZOOKEEPER">
+      <component name="ZOOKEEPER_SERVER">
+        <upgrade />
+      </component>
+    </service>
+  </processing>
+</upgrade>

-- 
To stop receiving notification emails like this one, please contact
jonathanhurley@apache.org.