AMBARI-21613. Dynamically determine what keytab files have been distributed 
(amagyar)


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

Branch: refs/heads/branch-feature-AMBARI-14714
Commit: a45e8f4f5e6a214cab98896092bc7d7d588a518b
Parents: b17225d
Author: Attila Magyar <amag...@hortonworks.com>
Authored: Thu Aug 3 12:01:04 2017 +0200
Committer: Attila Magyar <amag...@hortonworks.com>
Committed: Thu Aug 3 12:01:04 2017 +0200

----------------------------------------------------------------------
 .../resource_management/core/resources/klist.py | 45 ++++++++++++++++++
 .../ambari/server/agent/HeartBeatHandler.java   | 10 ++--
 .../ambari/server/agent/HeartbeatProcessor.java | 34 ++++++++++++-
 .../server/controller/KerberosHelperImpl.java   | 45 +++++++++++++++++-
 .../KERBEROS/1.10.3-10/metainfo.xml             |  8 ++++
 .../package/scripts/kerberos_client.py          |  3 ++
 .../package/scripts/kerberos_common.py          | 50 +++++++++++++++++++-
 .../PERF/1.0/services/KERBEROS/metainfo.xml     |  8 ++++
 .../KERBEROS/package/scripts/kerberos_client.py |  2 +
 .../KERBEROS/package/scripts/kerberos_common.py | 48 +++++++++++++++++++
 .../server/agent/TestHeartbeatHandler.java      |  5 +-
 .../server/controller/KerberosHelperTest.java   | 24 ++++++++--
 .../stacks/2.2/KERBEROS/test_kerberos_client.py | 23 +++++++++
 13 files changed, 290 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-common/src/main/python/resource_management/core/resources/klist.py
----------------------------------------------------------------------
diff --git 
a/ambari-common/src/main/python/resource_management/core/resources/klist.py 
b/ambari-common/src/main/python/resource_management/core/resources/klist.py
new file mode 100644
index 0000000..ec6fca8
--- /dev/null
+++ b/ambari-common/src/main/python/resource_management/core/resources/klist.py
@@ -0,0 +1,45 @@
+#!/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.
+
+Ambari Agent
+
+"""
+
+from resource_management.core import shell
+from resource_management import functions
+from resource_management.libraries.functions.default import default
+from subprocess import PIPE
+
+class Klist:
+  @staticmethod
+  def find_in_search_path():
+    return 
Klist(functions.get_klist_path(default('/configurations/kerberos-env/executable_search_paths',
 None)))
+
+  def __init__(self, klist_cmd="klist"):
+    self.klist_cmd = klist_cmd
+
+  def list_principals(self, keytab_path):
+    code, out, err = shell.call(self._command('-k', keytab_path), stdout = 
PIPE, stderr = PIPE, timeout = 5, sudo=True)
+    if code != 0 or (not out.startswith("Keytab name")):
+      raise Exception('Cannot run klist: %s. Exit code: %d. Error: %s' % 
(self.klist_cmd, code, err))
+    return set(line.split()[1] for line in out.split("\n")[3:])
+
+  def _command(self, *args):
+    cmd = [self.klist_cmd]
+    cmd.extend(args)
+    return cmd
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
index 1bc4c36..92ab3de 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartBeatHandler.java
@@ -17,6 +17,10 @@
  */
 package org.apache.ambari.server.agent;
 
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.CHECK_KEYTABS;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.REMOVE_KEYTAB;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.SET_KEYTAB;
+
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -294,7 +298,7 @@ public class HeartBeatHandler {
             Map<String, String> hlp = ec.getHostLevelParams();
             if (hlp != null) {
               String customCommand = hlp.get("custom_command");
-              if ("SET_KEYTAB".equalsIgnoreCase(customCommand) || 
"REMOVE_KEYTAB".equalsIgnoreCase(customCommand)) {
+              if (SET_KEYTAB.equalsIgnoreCase(customCommand) || 
REMOVE_KEYTAB.equalsIgnoreCase(customCommand) || 
CHECK_KEYTABS.equalsIgnoreCase(customCommand)) {
                 LOG.info(String.format("%s called", customCommand));
                 try {
                   injectKeytab(ec, customCommand, hostname);
@@ -593,7 +597,7 @@ public class HeartBeatHandler {
 
           if (targetHost.equalsIgnoreCase(hostName)) {
 
-            if ("SET_KEYTAB".equalsIgnoreCase(command)) {
+            if (SET_KEYTAB.equalsIgnoreCase(command)) {
               String keytabFilePath = 
record.get(KerberosIdentityDataFileReader.KEYTAB_FILE_PATH);
 
               if (keytabFilePath != null) {
@@ -629,7 +633,7 @@ public class HeartBeatHandler {
                   kcp.add(keytabMap);
                 }
               }
-            } else if ("REMOVE_KEYTAB".equalsIgnoreCase(command)) {
+            } else if (REMOVE_KEYTAB.equalsIgnoreCase(command) || 
CHECK_KEYTABS.equalsIgnoreCase(command)) {
               Map<String, String> keytabMap = new HashMap<>();
 
               keytabMap.put(KerberosIdentityDataFileReader.HOSTNAME, hostName);

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
index a08abab..2690008 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/agent/HeartbeatProcessor.java
@@ -18,6 +18,10 @@
 package org.apache.ambari.server.agent;
 
 
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.CHECK_KEYTABS;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.REMOVE_KEYTAB;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.SET_KEYTAB;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -420,8 +424,8 @@ public class HeartbeatProcessor extends AbstractService{
 
         String customCommand = report.getCustomCommand();
 
-        boolean adding = "SET_KEYTAB".equalsIgnoreCase(customCommand);
-        if (adding || "REMOVE_KEYTAB".equalsIgnoreCase(customCommand)) {
+        boolean adding = SET_KEYTAB.equalsIgnoreCase(customCommand);
+        if (adding || REMOVE_KEYTAB.equalsIgnoreCase(customCommand)) {
           WriteKeytabsStructuredOut writeKeytabsStructuredOut;
           try {
             writeKeytabsStructuredOut = 
gson.fromJson(report.getStructuredOut(), WriteKeytabsStructuredOut.class);
@@ -445,6 +449,12 @@ public class HeartbeatProcessor extends AbstractService{
               }
             }
           }
+        } else if (CHECK_KEYTABS.equalsIgnoreCase(customCommand)) {
+          ListKeytabsStructuredOut structuredOut = 
gson.fromJson(report.getStructuredOut(), ListKeytabsStructuredOut.class);
+          for (MissingKeytab each : structuredOut.missingKeytabs){
+            LOG.info("Missing keytab: {} on host: {} principal: {}", 
each.keytabFilePath, hostname, each.principal);
+            kerberosPrincipalHostDAO.remove(each.principal, host.getHostId());
+          }
         }
       }
 
@@ -702,6 +712,26 @@ public class HeartbeatProcessor extends AbstractService{
     }
   }
 
+  private static class ListKeytabsStructuredOut {
+    @SerializedName("missing_keytabs")
+    private final List<MissingKeytab> missingKeytabs;
+
+    public ListKeytabsStructuredOut(List<MissingKeytab> missingKeytabs) {
+      this.missingKeytabs = missingKeytabs;
+    }
+  }
+
+  private static class MissingKeytab {
+    @SerializedName("principal")
+    private final String principal;
+    @SerializedName("keytab_file_path")
+    private final String keytabFilePath;
+
+    public MissingKeytab(String principal, String keytabFilePath) {
+      this.principal = principal;
+      this.keytabFilePath = keytabFilePath;
+    }
+  }
 
   /**
    * This class is used for mapping json of structured output for component 
START action.

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/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 e5b7afd..6b50ea4 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
@@ -142,6 +142,9 @@ public class KerberosHelperImpl implements KerberosHelper {
    * These values are important when trying to determine the state of the 
cluster when adding new components
    */
   private static final Set<State> PREVIOUSLY_INSTALLED_STATES = 
EnumSet.of(State.INSTALLED, State.STARTED, State.DISABLED);
+  public static final String CHECK_KEYTABS = "CHECK_KEYTABS";
+  public static final String SET_KEYTAB = "SET_KEYTAB";
+  public static final String REMOVE_KEYTAB = "REMOVE_KEYTAB";
 
   @Inject
   private AmbariCustomCommandExecutionHelper customCommandExecutionHelper;
@@ -3027,7 +3030,7 @@ public class KerberosHelperImpl implements KerberosHelper 
{
 
         ActionExecutionContext actionExecContext = new ActionExecutionContext(
             cluster.getClusterName(),
-            "SET_KEYTAB",
+          SET_KEYTAB,
             requestResourceFilters,
             requestParams);
         
customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, 
stage,
@@ -3042,6 +3045,39 @@ public class KerberosHelperImpl implements 
KerberosHelper {
     }
 
     /**
+     * Send a custom command to the KERBEROS_CLIENT to check if there are 
missing keytabs on each hosts.
+     */
+    public void addCheckMissingKeytabsStage(Cluster cluster, String 
clusterHostInfoJson, String hostParamsJson, 
ServiceComponentHostServerActionEvent event, Map<String, String> 
commandParameters, RoleCommandOrder roleCommandOrder, RequestStageContainer 
requestStageContainer, List<ServiceComponentHost> serviceComponentHosts) throws 
AmbariException {
+      Stage stage = createNewStage(requestStageContainer.getLastStageId(),
+        cluster,
+        requestStageContainer.getId(),
+        "Checking keytabs",
+        StageUtils.getGson().toJson(commandParameters),
+        hostParamsJson);
+
+      Collection<ServiceComponentHost> filteredComponents = 
filterServiceComponentHostsForHosts(
+        new ArrayList<>(serviceComponentHosts), 
getHostsWithValidKerberosClient(cluster));
+
+      List<String> hostsToUpdate = createUniqueHostList(filteredComponents, 
Collections.singleton(HostState.HEALTHY));
+      Map<String, String> requestParams = new HashMap<>();
+      List<RequestResourceFilter> requestResourceFilters = new ArrayList<>();
+      RequestResourceFilter reqResFilter = new 
RequestResourceFilter(Service.Type.KERBEROS.name(), 
Role.KERBEROS_CLIENT.name(), hostsToUpdate);
+      requestResourceFilters.add(reqResFilter);
+
+      ActionExecutionContext actionExecContext = new ActionExecutionContext(
+        cluster.getClusterName(),
+        CHECK_KEYTABS,
+        requestResourceFilters,
+        requestParams);
+      
customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, 
stage, requestParams, null);
+      RoleGraph roleGraph = roleGraphFactory.createNew(roleCommandOrder);
+      roleGraph.build(stage);
+
+      requestStageContainer.setClusterHostInfo(clusterHostInfoJson);
+      requestStageContainer.addStages(roleGraph.getStages());
+    }
+
+    /**
      * Filter out ServiceComponentHosts that are on on hosts in the specified 
set of host names.
      * <p/>
      * It is expected that the supplied collection is modifiable. It will be 
modified inplace.
@@ -3170,7 +3206,7 @@ public class KerberosHelperImpl implements KerberosHelper 
{
 
           ActionExecutionContext actionExecContext = new 
ActionExecutionContext(
               cluster.getClusterName(),
-              "REMOVE_KEYTAB",
+            REMOVE_KEYTAB,
               requestResourceFilters,
               requestParams);
           
customCommandExecutionHelper.addExecutionCommandsToStage(actionExecContext, 
stage,
@@ -3635,6 +3671,11 @@ public class KerberosHelperImpl implements 
KerberosHelper {
       if (kerberosDetails.manageIdentities()) {
         commandParameters.put(KerberosServerAction.KDC_TYPE, 
kerberosDetails.getKdcType().name());
 
+        if (!regenerateAllKeytabs) {
+          addCheckMissingKeytabsStage(cluster, clusterHostInfoJson, 
hostParamsJson, event,
+            commandParameters, roleCommandOrder, requestStageContainer, 
serviceComponentHosts);
+        }
+
         // *****************************************************************
         // Create stage to create principals
         addCreatePrincipalsStage(cluster, clusterHostInfoJson, hostParamsJson, 
event,

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/metainfo.xml
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/metainfo.xml
 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/metainfo.xml
index 6a2dd09..191028a 100644
--- 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/metainfo.xml
+++ 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/metainfo.xml
@@ -60,6 +60,14 @@
                 <timeout>1000</timeout>
               </commandScript>
             </customCommand>
+            <customCommand>
+              <name>CHECK_KEYTABS</name>
+              <commandScript>
+                <script>scripts/kerberos_client.py</script>
+                <scriptType>PYTHON</scriptType>
+                <timeout>1000</timeout>
+              </commandScript>
+            </customCommand>
           </customCommands>
           <configFiles>
             <configFile>

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_client.py
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_client.py
 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_client.py
index 39fdcf5..691c4b8 100644
--- 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_client.py
+++ 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_client.py
@@ -49,5 +49,8 @@ class KerberosClient(KerberosScript):
   def remove_keytab(self, env):
     self.delete_keytab_file()
 
+  def check_keytabs(self, env):
+    self.find_missing_keytabs()
+
 if __name__ == "__main__":
   KerberosClient().execute()

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_common.py
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_common.py
 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_common.py
index fcd57af..21accdd 100644
--- 
a/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_common.py
+++ 
b/ambari-server/src/main/resources/common-services/KERBEROS/1.10.3-10/package/scripts/kerberos_common.py
@@ -36,6 +36,9 @@ from resource_management.core.source import InlineTemplate, 
Template, DownloadSo
 from utils import get_property_value
 from ambari_commons.os_utils import remove_file
 from ambari_agent import Constants
+from collections import namedtuple
+from resource_management.core import sudo
+from resource_management.core.resources.klist import Klist
 
 class KerberosScript(Script):
   KRB5_REALM_PROPERTIES = [
@@ -443,4 +446,49 @@ class KerberosScript(Script):
 
             curr_content['keytabs'][principal.replace("_HOST", 
params.hostname)] = '_REMOVED_'
 
-            self.put_structured_out(curr_content)
\ No newline at end of file
+            self.put_structured_out(curr_content)
+
+  def find_missing_keytabs(self):
+    import params
+    missing_keytabs = 
MissingKeytabs.fromKerberosRecords(params.kerberos_command_params, 
params.hostname)
+    Logger.info(str(missing_keytabs))
+    curr_content = Script.structuredOut
+    curr_content['missing_keytabs'] = missing_keytabs.as_dict()
+    self.put_structured_out(curr_content)
+
+class MissingKeytabs:
+  class Identity(namedtuple('Identity', ['principal', 'keytab_file_path'])):
+    @staticmethod
+    def fromKerberosRecord(item, hostname):
+      return MissingKeytabs.Identity(
+        get_property_value(item, 'principal').replace("_HOST", hostname),
+        get_property_value(item, 'keytab_file_path'))
+
+    def __str__(self):
+      return "Keytab: %s Principal: %s" % (self.keytab_file_path, 
self.principal)
+
+  @classmethod
+  def fromKerberosRecords(self, kerberos_record, hostname):
+    with_missing_keytab = (each for each in kerberos_record \
+                           if not self.keytab_exists(each) or not 
self.keytab_has_principal(each, hostname))
+    return MissingKeytabs(set(MissingKeytabs.Identity.fromKerberosRecord(each, 
hostname) for each in with_missing_keytab))
+
+  @staticmethod
+  def keytab_exists(kerberos_record):
+    return sudo.path_exists(get_property_value(kerberos_record, 
'keytab_file_path'))
+
+  @staticmethod
+  def keytab_has_principal(kerberos_record, hostname):
+    principal = get_property_value(kerberos_record, 
'principal').replace("_HOST", hostname)
+    keytab = get_property_value(kerberos_record, 'keytab_file_path')
+    klist = Klist.find_in_search_path()
+    return principal in klist.list_principals(keytab)
+
+  def __init__(self, items):
+    self.items = items
+
+  def as_dict(self):
+    return [each._asdict() for each in self.items]
+
+  def __str__(self):
+    return "Missing keytabs:\n%s" % ("\n".join(map(str, self.items))) if 
self.items else 'No missing keytabs'

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/metainfo.xml
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/metainfo.xml
 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/metainfo.xml
index 0e42bda..6f833c7 100644
--- 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/metainfo.xml
+++ 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/metainfo.xml
@@ -60,6 +60,14 @@
                                 <timeout>1000</timeout>
                             </commandScript>
                         </customCommand>
+                        <customCommand>
+                            <name>CHECK_KEYTABS</name>
+                            <commandScript>
+                                <script>scripts/kerberos_client.py</script>
+                                <scriptType>PYTHON</scriptType>
+                                <timeout>1000</timeout>
+                            </commandScript>
+                        </customCommand>
                     </customCommands>
                     <configFiles>
                         <configFile>

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_client.py
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_client.py
 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_client.py
index b2cdaa6..691c4b8 100644
--- 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_client.py
+++ 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_client.py
@@ -49,6 +49,8 @@ class KerberosClient(KerberosScript):
   def remove_keytab(self, env):
     self.delete_keytab_file()
 
+  def check_keytabs(self, env):
+    self.find_missing_keytabs()
 
 if __name__ == "__main__":
   KerberosClient().execute()

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_common.py
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_common.py
 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_common.py
index abf58ee..d959f11 100644
--- 
a/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_common.py
+++ 
b/ambari-server/src/main/resources/stacks/PERF/1.0/services/KERBEROS/package/scripts/kerberos_common.py
@@ -30,6 +30,9 @@ from resource_management import *
 from utils import get_property_value
 from ambari_commons.os_utils import remove_file
 from ambari_agent import Constants
+from collections import namedtuple
+from resource_management.core import sudo
+from resource_management.core.resources.klist import Klist
 
 class KerberosScript(Script):
   KRB5_REALM_PROPERTIES = [
@@ -430,3 +433,48 @@ class KerberosScript(Script):
             curr_content['keytabs'][principal.replace("_HOST", 
params.hostname)] = '_REMOVED_'
 
             self.put_structured_out(curr_content)
+
+  def find_missing_keytabs(self):
+    import params
+    missing_keytabs = 
MissingKeytabs.fromKerberosRecords(params.kerberos_command_params, 
params.hostname)
+    Logger.info(str(missing_keytabs))
+    curr_content = Script.structuredOut
+    curr_content['missing_keytabs'] = missing_keytabs.as_dict()
+    self.put_structured_out(curr_content)
+
+class MissingKeytabs:
+  class Identity(namedtuple('Identity', ['principal', 'keytab_file_path'])):
+    @staticmethod
+    def fromKerberosRecord(item, hostname):
+      return MissingKeytabs.Identity(
+        get_property_value(item, 'principal').replace("_HOST", hostname),
+        get_property_value(item, 'keytab_file_path'))
+
+    def __str__(self):
+      return "Keytab: %s Principal: %s" % (self.keytab_file_path, 
self.principal)
+
+  @classmethod
+  def fromKerberosRecords(self, kerberos_record, hostname):
+    with_missing_keytab = (each for each in kerberos_record \
+                           if not self.keytab_exists(each) or not 
self.keytab_has_principal(each, hostname))
+    return MissingKeytabs(set(MissingKeytabs.Identity.fromKerberosRecord(each, 
hostname) for each in with_missing_keytab))
+
+  @staticmethod
+  def keytab_exists(kerberos_record):
+    return sudo.path_exists(get_property_value(kerberos_record, 
'keytab_file_path'))
+
+  @staticmethod
+  def keytab_has_principal(kerberos_record, hostname):
+    principal = get_property_value(kerberos_record, 
'principal').replace("_HOST", hostname)
+    keytab = get_property_value(kerberos_record, 'keytab_file_path')
+    klist = Klist.find_in_search_path()
+    return principal in klist.list_principals(keytab)
+
+  def __init__(self, items):
+    self.items = items
+
+  def as_dict(self):
+    return [each._asdict() for each in self.items]
+
+  def __str__(self):
+    return "Missing keytabs:\n%s" % ("\n".join(map(str, self.items))) if 
self.items else 'No missing keytabs'

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
index baa9bae..4235b11 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
@@ -30,6 +30,7 @@ import static 
org.apache.ambari.server.agent.DummyHeartbeatConstants.HDFS;
 import static 
org.apache.ambari.server.agent.DummyHeartbeatConstants.HDFS_CLIENT;
 import static org.apache.ambari.server.agent.DummyHeartbeatConstants.NAMENODE;
 import static 
org.apache.ambari.server.agent.DummyHeartbeatConstants.SECONDARY_NAMENODE;
+import static 
org.apache.ambari.server.controller.KerberosHelperImpl.SET_KEYTAB;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.reset;
@@ -1476,7 +1477,7 @@ public class TestHeartbeatHandler {
     ExecutionCommand executionCommand = new ExecutionCommand();
 
     Map<String, String> hlp = new HashMap<>();
-    hlp.put("custom_command", "SET_KEYTAB");
+    hlp.put("custom_command", SET_KEYTAB);
     executionCommand.setHostLevelParams(hlp);
 
     Map<String, String> commandparams = new HashMap<>();
@@ -1496,7 +1497,7 @@ public class TestHeartbeatHandler {
         }});
     replay(am);
 
-    heartbeatTestHelper.getHeartBeatHandler(am, 
aq).injectKeytab(executionCommand, "SET_KEYTAB", targetHost);
+    heartbeatTestHelper.getHeartBeatHandler(am, 
aq).injectKeytab(executionCommand, SET_KEYTAB, targetHost);
 
     return executionCommand.getKerberosCommandParams();
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java
----------------------------------------------------------------------
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 4508527..0196069 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
@@ -2824,23 +2824,23 @@ public class KerberosHelperTest extends EasyMockSupport 
{
             add(schKerberosClientC);
           }
         })
-        .once();
+        .anyTimes();
 
     final Clusters clusters = injector.getInstance(Clusters.class);
     if ((filteredHosts == null) || filteredHosts.contains("hostA")) {
       expect(clusters.getHost("hostA"))
           .andReturn(hostA)
-          .once();
+          .anyTimes();
     }
     if ((filteredHosts == null) || filteredHosts.contains("hostB")) {
       expect(clusters.getHost("hostB"))
           .andReturn(hostB)
-          .once();
+          .anyTimes();
     }
     if ((filteredHosts == null) || filteredHosts.contains("hostC")) {
       expect(clusters.getHost("hostC"))
           .andReturn(hostC)
-          .once();
+          .anyTimes();
     }
 
     final AmbariManagementController ambariManagementController = 
injector.getInstance(AmbariManagementController.class);
@@ -2917,6 +2917,13 @@ public class KerberosHelperTest extends EasyMockSupport {
     expectLastCall().once();
     requestStageContainer.addStages(EasyMock.<List<Stage>>anyObject());
     expectLastCall().once();
+    // Getting missing keytabs
+    expect(requestStageContainer.getLastStageId()).andReturn(-1L).anyTimes();
+    expect(requestStageContainer.getId()).andReturn(1L).once();
+    requestStageContainer.setClusterHostInfo(anyString());
+    expectLastCall().once();
+    requestStageContainer.addStages(EasyMock.<List<Stage>>anyObject());
+    expectLastCall().once();
     // Create Principals Stage
     expect(requestStageContainer.getLastStageId()).andReturn(-1L).anyTimes();
     expect(requestStageContainer.getId()).andReturn(1L).once();
@@ -3268,7 +3275,7 @@ public class KerberosHelperTest extends EasyMockSupport {
       final Clusters clusters = injector.getInstance(Clusters.class);
       expect(clusters.getHost("host1"))
           .andReturn(host)
-          .once();
+          .anyTimes();
 
       final AmbariManagementController ambariManagementController = 
injector.getInstance(AmbariManagementController.class);
       
expect(ambariManagementController.findConfigurationTagsWithOverrides(cluster, 
null))
@@ -3302,6 +3309,13 @@ public class KerberosHelperTest extends EasyMockSupport {
       expectLastCall().once();
       requestStageContainer.addStages(EasyMock.<List<Stage>>anyObject());
       expectLastCall().once();
+      // Getting missing keytabs
+      expect(requestStageContainer.getLastStageId()).andReturn(-1L).anyTimes();
+      expect(requestStageContainer.getId()).andReturn(1L).once();
+      requestStageContainer.setClusterHostInfo(anyString());
+      expectLastCall().once();
+      requestStageContainer.addStages(EasyMock.<List<Stage>>anyObject());
+      expectLastCall().once();
       // Create Principals Stage
       expect(requestStageContainer.getLastStageId()).andReturn(-1L).anyTimes();
       expect(requestStageContainer.getId()).andReturn(1L).once();

http://git-wip-us.apache.org/repos/asf/ambari/blob/a45e8f4f/ambari-server/src/test/python/stacks/2.2/KERBEROS/test_kerberos_client.py
----------------------------------------------------------------------
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 f638845..cb2db3a 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
@@ -23,6 +23,8 @@ import os
 import sys
 import use_cases
 from stacks.utils.RMFTestCase import *
+from resource_management import Script
+from collections import OrderedDict
 
 from only_for_platform import not_for_platform, PLATFORM_WINDOWS
 
@@ -353,3 +355,24 @@ class TestKerberosClient(RMFTestCase):
     # The kdc_host is expected to generated using kdc_hosts, but only the 
first host is used since
     # previous versions only knew how to handle a single KDC host
     self.assertEquals('c6401.ambari.apache.org', 
sys.modules['params'].kdc_host)
+
+  @patch("resource_management.core.sudo.path_exists")
+  def test_find_missing_keytabs(self, path_exists):
+    path_exists.side_effect = [False]
+    json_data = use_cases.get_managed_kdc_use_case()
+    json_data['kerberosCommandParams'] = [
+      {
+        "keytab_file_path": '/deleted_keytab',
+        "principal": "HTTP/_h...@example.com"
+      }
+    ]
+    self.executeScript(self.COMMON_SERVICES_PACKAGE_DIR + 
"/scripts/kerberos_client.py",
+                       classname="KerberosClient",
+                       command="check_keytabs",
+                       config_dict=json_data,
+                       stack_version=self.STACK_VERSION,
+                       target=RMFTestCase.TARGET_COMMON_SERVICES)
+    self.assertEquals(Script.structuredOut['missing_keytabs'], [OrderedDict({
+      'keytab_file_path' : '/deleted_keytab',
+      'principal' : 'HTTP/c6401.ambari.apache....@example.com'
+    })])
\ No newline at end of file

Reply via email to