Browse Source

AMBARI-9014. Design admin principal session expiration handling API call (rlevas)

Robert Levas 10 năm trước cách đây
mục cha
commit
efe79f015a
15 tập tin đã thay đổi với 1377 bổ sung778 xóa
  1. 3 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
  2. 74 7
      ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java
  3. 153 139
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java
  4. 13 7
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java
  5. 33 27
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java
  6. 91 59
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
  7. 4 2
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java
  8. 12 5
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
  9. 205 227
      ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
  10. 75 3
      ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java
  11. 215 26
      ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java
  12. 0 245
      ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java
  13. 2 2
      ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactoryTest.java
  14. 194 12
      ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java
  15. 303 17
      ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java

+ 3 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java

@@ -68,6 +68,7 @@ import org.apache.ambari.server.scheduler.ExecutionScheduler;
 import org.apache.ambari.server.scheduler.ExecutionSchedulerImpl;
 import org.apache.ambari.server.security.SecurityHelper;
 import org.apache.ambari.server.security.SecurityHelperImpl;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Config;
@@ -201,6 +202,8 @@ public class ControllerModule extends AbstractModule {
     bind(SessionManager.class).toInstance(sessionManager);
     bind(SessionIdManager.class).toInstance(sessionIdManager);
 
+    bind(KerberosOperationHandlerFactory.class);
+
     bind(Configuration.class).toInstance(configuration);
     bind(OsFamily.class).toInstance(os_family);
     bind(HostsMap.class).toInstance(hostsMap);

+ 74 - 7
ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java

@@ -102,6 +102,9 @@ public class KerberosHelper {
   @Inject
   private ConfigHelper configHelper;
 
+  @Inject
+  private KerberosOperationHandlerFactory kerberosOperationHandlerFactory;
+
   /**
    * The Handler implementation that provides the logic to enable Kerberos
    */
@@ -384,22 +387,86 @@ public class KerberosHelper {
         // If there are ServiceComponentHosts to process, make sure the administrator credentials
         // are available
         if (!serviceComponentHostsToProcess.isEmpty()) {
-          if (getEncryptedAdministratorCredentials(cluster) == null) {
+          try {
+            String credentials = getEncryptedAdministratorCredentials(cluster);
+            if (credentials == null) {
+              throw new IllegalArgumentException(
+                  "Missing KDC administrator credentials.\n" +
+                      "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." +
+                      "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" +
+                      "{\n" +
+                      "  \"session_attributes\" : {\n" +
+                      "    \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" +
+                      "  }\n" +
+                      "}"
+              );
+            } else {
+              KerberosOperationHandler operationHandler = kerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType);
+
+              if (operationHandler == null) {
+                throw new AmbariException("Failed to get an appropriate Kerberos operation handler.");
+              } else {
+                byte[] key = Integer.toHexString(cluster.hashCode()).getBytes();
+                KerberosCredential kerberosCredentials = KerberosCredential.decrypt(credentials, key);
+
+                try {
+                  operationHandler.open(kerberosCredentials, realm);
+                  if (!operationHandler.testAdministratorCredentials()) {
+                    throw new IllegalArgumentException(
+                        "Invalid KDC administrator credentials.\n" +
+                            "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." +
+                            "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" +
+                            "{\n" +
+                            "  \"session_attributes\" : {\n" +
+                            "    \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" +
+                            "  }\n" +
+                            "}"
+                    );
+                  }
+                } catch (KerberosAdminAuthenticationException e) {
+                  throw new IllegalArgumentException(
+                      "Invalid KDC administrator credentials.\n" +
+                          "The KDC administrator credentials must be set in session by updating the relevant Cluster resource." +
+                          "This may be done by issuing a PUT to the api/v1/clusters/(cluster name) API entry point with the following payload:\n" +
+                          "{\n" +
+                          "  \"session_attributes\" : {\n" +
+                          "    \"kerberos_admin\" : {\"principal\" : \"(PRINCIPAL)\", \"password\" : \"(PASSWORD)\"}\n" +
+                          "  }\n" +
+                          "}",
+                      e
+                  );
+                } catch (KerberosKDCConnectionException e) {
+                  throw new AmbariException("Failed to connect to KDC - " + e.getMessage() + "\n" +
+                      "Update the KDC settings in krb5-conf and kerberos-env configurations to correct this issue.",
+                      e);
+                } catch (KerberosOperationException e) {
+                  throw new AmbariException(e.getMessage(), e);
+                } finally {
+                  try {
+                    operationHandler.close();
+                  } catch (KerberosOperationException e) {
+                    // Ignore this...
+                  }
+                }
+              }
+            }
+          } catch (IllegalArgumentException e) {
             try {
               FileUtils.deleteDirectory(dataDirectory);
-            } catch (IOException e) {
+            } catch (Throwable t) {
               LOG.warn(String.format("The data directory (%s) was not deleted due to an error condition - {%s}",
-                  dataDirectory.getAbsolutePath(), e.getMessage()), e);
+                  dataDirectory.getAbsolutePath(), t.getMessage()), t);
             }
-            throw new AmbariException("Missing KDC administrator credentials");
+
+            throw e;
           }
 
           // Determine if the any auth_to_local configurations need to be set dynamically
           // Lazily create the auth_to_local rules
           String authToLocal = null;
-          for(Map<String, String> configuration: kerberosConfigurations.values()) {
-            for(Map.Entry<String,String> entry: configuration.entrySet()) {
-              if("_AUTH_TO_LOCAL_RULES".equals(entry.getValue())) {
+          for (Map<String, String> configuration : kerberosConfigurations.values()) {
+            for (Map.Entry<String, String> entry : configuration.entrySet()) {
+              if ("_AUTH_TO_LOCAL_RULES".equals(entry.getValue())) {
                 if (authToLocal == null) {
                   authToLocal = authToLocalBuilder.generate(realm);
                 }

+ 153 - 139
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandler.java

@@ -19,18 +19,16 @@
 package org.apache.ambari.server.serveraction.kerberos;
 
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import javax.naming.*;
 import javax.naming.directory.*;
+import javax.naming.ldap.Control;
 import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapContext;
 import java.io.UnsupportedEncodingException;
-import java.util.HashSet;
 import java.util.Properties;
-import java.util.Set;
 
 /**
  * Implementation of <code>KerberosOperationHandler</code> to created principal in Active Directory
@@ -41,28 +39,22 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
 
   private static final String LDAP_CONTEXT_FACTORY_CLASS = "com.sun.jndi.ldap.LdapCtxFactory";
 
-  private String adminPrincipal;
-  private String adminPassword;
-  private String realm;
-
   private String ldapUrl;
   private String principalContainerDn;
 
-  private static final int ONELEVEL_SCOPE = SearchControls.ONELEVEL_SCOPE;
+  private static final int ONE_LEVEL_SCOPE = SearchControls.ONELEVEL_SCOPE;
   private static final String LDAP_ATUH_MECH_SIMPLE = "simple";
 
   private LdapContext ldapContext;
-
   private SearchControls searchControls;
 
   /**
    * Prepares and creates resources to be used by this KerberosOperationHandler.
-   * This method in this class would always throw <code>AmabriException</code> reporting
+   * This method in this class would always throw <code>KerberosOperationException</code> reporting
    * ldapUrl is not provided.
-   * Please use <code>open(KerberosCredential administratorCredentials, String defaultRealm,
+   * Use <code>open(KerberosCredential administratorCredentials, String defaultRealm,
    * String ldapUrl, String principalContainerDn)</code> for successful operation.
    * <p/>
-   * <p/>
    * It is expected that this KerberosOperationHandler will not be used before this call.
    *
    * @param administratorCredentials a KerberosCredential containing the administrative credentials
@@ -71,7 +63,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    */
   @Override
   public void open(KerberosCredential administratorCredentials, String realm)
-    throws AmbariException {
+      throws KerberosOperationException {
     open(administratorCredentials, realm, null, null);
   }
 
@@ -85,56 +77,42 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    * @param realm                    a String declaring the default Kerberos realm (or domain)
    * @param ldapUrl                  ldapUrl of ldap back end where principals would be created
    * @param principalContainerDn     DN of the container in ldap back end where principals would be created
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   @Override
   public void open(KerberosCredential administratorCredentials, String realm,
                    String ldapUrl, String principalContainerDn)
-    throws AmbariException {
+      throws KerberosOperationException {
+
+    if (isOpen()) {
+      close();
+    }
+
     if (administratorCredentials == null) {
-      throw new AmbariException("admininstratorCredential not provided");
+      throw new KerberosAdminAuthenticationException("administrator Credential not provided");
     }
     if (realm == null) {
-      throw new AmbariException("realm not provided");
+      throw new KerberosRealmException("realm not provided");
     }
     if (ldapUrl == null) {
-      throw new AmbariException("ldapUrl not provided");
+      throw new KerberosKDCConnectionException("ldapUrl not provided");
     }
     if (principalContainerDn == null) {
-      throw new AmbariException("principalContainerDn not provided");
+      throw new KerberosLDAPContainerException("principalContainerDn not provided");
     }
-    this.adminPrincipal = administratorCredentials.getPrincipal();
-    this.adminPassword = administratorCredentials.getPassword();
-    this.realm = realm;
-    this.ldapUrl = ldapUrl;
-    this.principalContainerDn = principalContainerDn;
-    createLdapContext();
-  }
-
-  private void createLdapContext() throws AmbariException {
-    LOG.info("Creating ldap context");
-
-    Properties env = new Properties();
-    env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY_CLASS);
-    env.put(Context.PROVIDER_URL, ldapUrl);
-    env.put(Context.SECURITY_PRINCIPAL, adminPrincipal);
-    env.put(Context.SECURITY_CREDENTIALS, adminPassword);
-    env.put(Context.SECURITY_AUTHENTICATION, LDAP_ATUH_MECH_SIMPLE);
-    env.put(Context.REFERRAL, "follow");
 
-    try {
-      ldapContext = new InitialLdapContext(env, null);
-    } catch (NamingException ne) {
-      LOG.error("Can not created ldapContext", ne);
-      throw new AmbariException("Can not created ldapContext", ne);
-    }
+    setAdministratorCredentials(administratorCredentials);
+    setDefaultRealm(realm);
 
-    searchControls = new SearchControls();
-    searchControls.setSearchScope(ONELEVEL_SCOPE);
+    this.ldapUrl = ldapUrl;
+    this.principalContainerDn = principalContainerDn;
+    this.ldapContext = createLdapContext();
+    this.searchControls = createSearchControls();
 
-    Set<String> userSearchAttributes = new HashSet<String>();
-    userSearchAttributes.add("cn");
-    searchControls.setReturningAttributes(userSearchAttributes.toArray(
-      new String[userSearchAttributes.size()]));
+    setOpen(true);
   }
 
   /**
@@ -143,37 +121,16 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    * It is expected that this KerberosOperationHandler will not be used after this call.
    */
   @Override
-  public void close() {
-    try {
-      if (ldapContext != null) {
+  public void close() throws KerberosOperationException {
+    if (ldapContext != null) {
+      try {
         ldapContext.close();
+      } catch (NamingException e) {
+        throw new KerberosOperationException("Unexpected error", e);
       }
-    } catch (NamingException ne) {
-      // ignored, nothing we could do about it
     }
 
-  }
-
-  /**
-   * Maps Keberos realm name to AD dc tree syntaz
-   *
-   * @param realm kerberos realm name
-   * @return mapped dc tree string
-   */
-  private static String realmToDcs(String realm) {
-    if (realm == null || realm.isEmpty()) {
-      return realm;
-    }
-    String[] tokens = realm.split("\\.");
-    StringBuilder sb = new StringBuilder();
-    int len = tokens.length;
-    if (len > 0) {
-      sb.append("dc=").append(tokens[0]);
-    }
-    for (int i = 1; i < len; i++)   {
-      sb.append(",").append("dc=").append(tokens[i]);
-    }
-    return sb.toString();
+    setOpen(false);
   }
 
   /**
@@ -183,24 +140,27 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param principal a String containing the principal to test
    * @return true if the principal exists; false otherwise
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   @Override
-  public boolean principalExists(String principal) throws AmbariException {
+  public boolean principalExists(String principal) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
     if (principal == null) {
-      throw new AmbariException("principal is null");
+      throw new KerberosOperationException("principal is null");
     }
     NamingEnumeration<SearchResult> searchResultEnum = null;
     try {
       searchResultEnum = ldapContext.search(
-        principalContainerDn,
-        "(cn=" + principal + ")",
-        searchControls);
+          principalContainerDn,
+          "(cn=" + principal + ")",
+          searchControls);
       if (searchResultEnum.hasMore()) {
         return true;
       }
     } catch (NamingException ne) {
-      throw new AmbariException("can not check if principal exists: " + principal, ne);
+      throw new KerberosOperationException("can not check if principal exists: " + principal, ne);
     } finally {
       try {
         if (searchResultEnum != null) {
@@ -221,16 +181,19 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    * @param principal a String containing the principal to add
    * @param password  a String containing the password to use when creating the principal
    * @return an Integer declaring the generated key number
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   @Override
   public Integer createServicePrincipal(String principal, String password)
-    throws AmbariException {
+      throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
     if (principal == null) {
-      throw new AmbariException("principal is null");
+      throw new KerberosOperationException("principal is null");
     }
     if (password == null) {
-      throw new AmbariException("principal password is null");
+      throw new KerberosOperationException("principal password is null");
     }
     Attributes attributes = new BasicAttributes();
 
@@ -243,7 +206,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     attributes.put(cn);
 
     Attribute upn = new BasicAttribute("userPrincipalName");
-    upn.add(principal + "@" + realm.toLowerCase());
+    upn.add(String.format("%s@%s", principal, getDefaultRealm().toLowerCase()));
     attributes.put(upn);
 
     Attribute spn = new BasicAttribute("servicePrincipalName");
@@ -259,7 +222,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
     try {
       passwordAttr.add(quotedPasswordVal.getBytes("UTF-16LE"));
     } catch (UnsupportedEncodingException ue) {
-      throw new AmbariException("Can not encode password with UTF-16LE", ue);
+      throw new KerberosOperationException("Can not encode password with UTF-16LE", ue);
     }
     attributes.put(passwordAttr);
 
@@ -267,7 +230,7 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
       Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn);
       ldapContext.createSubcontext(name, attributes);
     } catch (NamingException ne) {
-      throw new AmbariException("Can not created principal : " + principal, ne);
+      throw new KerberosOperationException("Can not create principal : " + principal, ne);
     }
     return 0;
   }
@@ -280,35 +243,38 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    * @param principal a String containing the principal to update
    * @param password  a String containing the password to set
    * @return an Integer declaring the new key number
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   @Override
-  public Integer setPrincipalPassword(String principal, String password) throws AmbariException {
+  public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
     if (principal == null) {
-      throw new AmbariException("principal is null");
+      throw new KerberosOperationException("principal is null");
     }
     if (password == null) {
-      throw new AmbariException("principal password is null");
+      throw new KerberosOperationException("principal password is null");
     }
-    if (!principalExists(principal)) {
-      if (password == null) {
-        throw new AmbariException("principal not found : " + principal);
+    try {
+      if (!principalExists(principal)) {
+        throw new KerberosOperationException("principal not found : " + principal);
       }
+    } catch (KerberosOperationException e) {
+      e.printStackTrace();
     }
     try {
-      createLdapContext();
-
       ModificationItem[] mods = new ModificationItem[1];
       String quotedPasswordVal = "\"" + password + "\"";
       mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
-        new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE")));
+          new BasicAttribute("UnicodePwd", quotedPasswordVal.getBytes("UTF-16LE")));
       ldapContext.modifyAttributes(
-        new CompositeName().add("cn=" + principal + "," + principalContainerDn),
-        mods);
+          new CompositeName().add("cn=" + principal + "," + principalContainerDn),
+          mods);
     } catch (NamingException ne) {
-      throw new AmbariException("Can not set password for principal : " + principal, ne);
+      throw new KerberosOperationException("Can not set password for principal : " + principal, ne);
     } catch (UnsupportedEncodingException ue) {
-      throw new AmbariException("Unsupported encoding UTF-16LE", ue);
+      throw new KerberosOperationException("Unsupported encoding UTF-16LE", ue);
     }
     return 0;
   }
@@ -320,61 +286,109 @@ public class ADKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param principal a String containing the principal to remove
    * @return true if the principal was successfully removed; otherwise false
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   @Override
-  public boolean removeServicePrincipal(String principal) throws AmbariException {
+  public boolean removeServicePrincipal(String principal) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
     if (principal == null) {
-      throw new AmbariException("principal is null");
+      throw new KerberosOperationException("principal is null");
     }
-    if (!principalExists(principal)) {
-      return false;
+    try {
+      if (!principalExists(principal)) {
+        return false;
+      }
+    } catch (KerberosOperationException e) {
+      e.printStackTrace();
     }
     try {
       Name name = new CompositeName().add("cn=" + principal + "," + principalContainerDn);
       ldapContext.destroySubcontext(name);
     } catch (NamingException ne) {
-      throw new AmbariException("Can not remove principal: " + principal);
+      throw new KerberosOperationException("Can not remove principal: " + principal);
     }
+
     return true;
   }
 
   /**
-   * Implementation of main method to illustrate the use of operations on this class
+   * Helper method to create the LDAP context needed to interact with the Active Directory.
    *
-   * @param args not used here
-   * @throws Throwable
+   * @return the relevant LdapContext
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
-  public static void main(String[] args) throws Throwable {
-
-    // SSL Certificate of AD should have been imported into truststore when that certificate
-    // is not issued by trusted authority. This is typical with self signed certificated in
-    // development environment
-    System.setProperty("javax.net.ssl.trustStore",
-      "/tmp/workspace/ambari/apache-ambari-rd/cacerts");
-
-    ADKerberosOperationHandler handler = new ADKerberosOperationHandler();
-
-    KerberosCredential kc = new KerberosCredential(
-      "Administrator@knox.com", "hadoop", null);  // null keytab
-
-    handler.open(kc, "KNOX.COM",
-      "ldaps://dillwin12.knox.com:636", "ou=service accounts,dc=knox,dc=com");
-
-    // does the princial already exist?
-    System.out.println("Principal exists: " + handler.principalExists("nn/c1508.ambari.apache.org"));
-
-    //create principal
-    handler.createServicePrincipal("nn/c1508.ambari.apache.org", "welcome");
+  protected LdapContext createLdapContext() throws KerberosOperationException {
+    KerberosCredential administratorCredentials = getAdministratorCredentials();
+
+    Properties properties = new Properties();
+    properties.put(Context.INITIAL_CONTEXT_FACTORY, LDAP_CONTEXT_FACTORY_CLASS);
+    properties.put(Context.PROVIDER_URL, ldapUrl);
+    properties.put(Context.SECURITY_PRINCIPAL, administratorCredentials.getPrincipal());
+    properties.put(Context.SECURITY_CREDENTIALS, administratorCredentials.getPassword());
+    properties.put(Context.SECURITY_AUTHENTICATION, LDAP_ATUH_MECH_SIMPLE);
+    properties.put(Context.REFERRAL, "follow");
+    properties.put("java.naming.ldap.factory.socket", TrustingSSLSocketFactory.class.getName());
 
-    //update the password
-    handler.setPrincipalPassword("nn/c1508.ambari.apache.org", "welcome10");
-
-    // remove the principal
-    // handler.removeServicePrincipal("nn/c1508.ambari.apache.org");
+    try {
+      return createInitialLdapContext(properties, null);
+    } catch (CommunicationException e) {
+      String message = String.format("Failed to communicate with the Active Directory at %s: %s", ldapUrl, e.getMessage());
+      LOG.warn(message, e);
+      throw new KerberosKDCConnectionException(message, e);
+    } catch (AuthenticationException e) {
+      String message = String.format("Failed to authenticate with the Active Directory at %s: %s", ldapUrl, e.getMessage());
+      LOG.warn(message, e);
+      throw new KerberosAdminAuthenticationException(message, e);
+    } catch (NamingException e) {
+      String error = e.getMessage();
+
+      if ((error != null) && !error.isEmpty()) {
+        String message = String.format("Failed to communicate with the Active Directory at %s: %s", ldapUrl, e.getMessage());
+        LOG.warn(message, e);
+
+        if (error.startsWith("Cannot parse url:")) {
+          throw new KerberosKDCConnectionException(message, e);
+        } else {
+          throw new KerberosOperationException(message, e);
+        }
+      } else {
+        throw new KerberosOperationException("Unexpected error condition", e);
+      }
+    }
+  }
 
-    handler.close();
+  /**
+   * Helper method to create the LDAP context needed to interact with the Active Directory.
+   * <p/>
+   * This is mainly used to help with building mocks for test cases.
+   *
+   * @param properties environment used to create the initial DirContext.
+   *                   Null indicates an empty environment.
+   * @param controls   connection request controls for the initial context.
+   *                   If null, no connection request controls are used.
+   * @return the relevant LdapContext
+   * @throws NamingException if a naming exception is encountered
+   */
+  protected LdapContext createInitialLdapContext(Properties properties, Control[] controls)
+      throws NamingException {
+    return new InitialLdapContext(properties, controls);
+  }
 
+  /**
+   * Helper method to create the SearchControls instance
+   *
+   * @return the relevant SearchControls
+   */
+  protected SearchControls createSearchControls() {
+    SearchControls searchControls = new SearchControls();
+    searchControls.setSearchScope(ONE_LEVEL_SCOPE);
+    searchControls.setReturningAttributes(new String[]{"cn"});
+    return searchControls;
   }
 
 }

+ 13 - 7
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java

@@ -139,13 +139,19 @@ public class CreateKeytabFilesServerAction extends KerberosServerAction {
               File keytabFile = new File(hostDirectory, DigestUtils.sha1Hex(keytabFilePath));
               Integer keyNumber = principalKeyNumberMap.get(evaluatedPrincipal);
 
-              if (operationHandler.createKeytabFile(evaluatedPrincipal, password, keyNumber, keytabFile)) {
-                LOG.debug("Successfully created keytab file for {} at {}",
-                    evaluatedPrincipal, keytabFile.getAbsolutePath());
-              } else {
-                String message = String.format("Failed to create keytab file for %s at %s",
-                    evaluatedPrincipal, keytabFile.getAbsolutePath());
-                LOG.error(message);
+              try {
+                if (operationHandler.createKeytabFile(evaluatedPrincipal, password, keyNumber, keytabFile)) {
+                  LOG.debug("Successfully created keytab file for {} at {}",
+                      evaluatedPrincipal, keytabFile.getAbsolutePath());
+                } else {
+                  String message = String.format("Failed to create keytab file for %s at %s",
+                      evaluatedPrincipal, keytabFile.getAbsolutePath());
+                  LOG.error(message);
+                  commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+                }
+              } catch (KerberosOperationException e) {
+                String message = String.format("Failed to create keytab file for %s - %s", evaluatedPrincipal, e.getMessage());
+                LOG.error(message, e);
                 commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
               }
             } else {

+ 33 - 27
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java

@@ -95,35 +95,41 @@ public class CreatePrincipalsServerAction extends KerberosServerAction {
     if (password == null) {
       password = operationHandler.createSecurePassword();
 
-      if (operationHandler.principalExists(evaluatedPrincipal)) {
-        // Create a new password since we need to know what it is.
-        // A new password/key would have been generated after exporting the keytab anyways.
-        LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal);
-
-        Integer keyNumber = operationHandler.setPrincipalPassword(evaluatedPrincipal, password);
-
-        if (keyNumber != null) {
-          principalPasswordMap.put(evaluatedPrincipal, password);
-          principalKeyNumberMap.put(evaluatedPrincipal, keyNumber);
-          LOG.debug("Successfully set password for principal {}", evaluatedPrincipal);
-        } else {
-          String message = String.format("Failed to set password for principal %s, unknown reason", evaluatedPrincipal);
-          LOG.error(message);
-          commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
-        }
-      } else {
-        LOG.debug("Creating new principal - {}", evaluatedPrincipal);
-        Integer keyNumber = operationHandler.createServicePrincipal(evaluatedPrincipal, password);
-
-        if (keyNumber != null) {
-          principalPasswordMap.put(evaluatedPrincipal, password);
-          principalKeyNumberMap.put(evaluatedPrincipal, keyNumber);
-          LOG.debug("Successfully created new principal {}", evaluatedPrincipal);
+      try {
+        if (operationHandler.principalExists(evaluatedPrincipal)) {
+          // Create a new password since we need to know what it is.
+          // A new password/key would have been generated after exporting the keytab anyways.
+          LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal);
+
+          Integer keyNumber = operationHandler.setPrincipalPassword(evaluatedPrincipal, password);
+
+          if (keyNumber != null) {
+            principalPasswordMap.put(evaluatedPrincipal, password);
+            principalKeyNumberMap.put(evaluatedPrincipal, keyNumber);
+            LOG.debug("Successfully set password for principal {}", evaluatedPrincipal);
+          } else {
+            String message = String.format("Failed to set password for principal %s - unknown reason", evaluatedPrincipal);
+            LOG.error(message);
+            commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+          }
         } else {
-          String message = String.format("Failed to create principal %s, unknown reason", evaluatedPrincipal);
-          LOG.error(message);
-          commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+          LOG.debug("Creating new principal - {}", evaluatedPrincipal);
+          Integer keyNumber = operationHandler.createServicePrincipal(evaluatedPrincipal, password);
+
+          if (keyNumber != null) {
+            principalPasswordMap.put(evaluatedPrincipal, password);
+            principalKeyNumberMap.put(evaluatedPrincipal, keyNumber);
+            LOG.debug("Successfully created new principal {}", evaluatedPrincipal);
+          } else {
+            String message = String.format("Failed to create principal %s - unknown reason", evaluatedPrincipal);
+            LOG.error(message);
+            commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+          }
         }
+      } catch (KerberosOperationException e) {
+        String message = String.format("Failed to create principal %s - %s", evaluatedPrincipal, e.getMessage());
+        LOG.error(message, e);
+        commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
       }
     }
 

+ 91 - 59
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.utils.ShellCommandUtil;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
@@ -77,9 +76,9 @@ public abstract class KerberosOperationHandler {
         add(EncryptionType.AES256_CTS_HMAC_SHA1_96);
       }});
 
-  private KerberosCredential administratorCredentials;
-  private String defaultRealm;
-
+  private KerberosCredential administratorCredentials = null;
+  private String defaultRealm = null;
+  private boolean open = false;
 
   /**
    * Create a secure (random) password using a secure random number generator and a set of (reasonable)
@@ -121,38 +120,36 @@ public abstract class KerberosOperationHandler {
   }
 
   /**
-     * Prepares and creates resources to be used by this KerberosOperationHandler
-     * <p/>
-     * It is expected that this KerberosOperationHandler will not be used before this call.
-     *
-     * @param administratorCredentials a KerberosCredential containing the administrative credentials
-     *                                 for the relevant KDC
-     * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
-     */
-    public abstract void open(KerberosCredential administratorCredentials, String defaultRealm)
-            throws AmbariException;
-
-    /**
-     * Prepares and creates resources to be used by this KerberosOperationHandler.
-     * Implementation in this class is ignoring parameters ldapUrl and principalContainerDn and delegate to
-     * <code>open(KerberosCredential administratorCredentials, String defaultRealm)</code>
-     * Subclasses that want to use these parameters need to override this method.
-     *
-     * <p/>
-     * It is expected that this KerberosOperationHandler will not be used before this call.
-     *
-     * @param administratorCredentials a KerberosCredential containing the administrative credentials
-     *                                 for the relevant KDC
-     * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
-     * @param ldapUrl  ldapUrl of ldap back end where principals would be created
-     * @param principalContainerDn DN of the container in ldap back end where principals would be created
-     *
-     */
-    public void open(KerberosCredential administratorCredentials, String defaultRealm,
-                              String ldapUrl, String principalContainerDn)
-            throws AmbariException {
-       open(administratorCredentials, defaultRealm);
-    }
+   * Prepares and creates resources to be used by this KerberosOperationHandler
+   * <p/>
+   * It is expected that this KerberosOperationHandler will not be used before this call.
+   *
+   * @param administratorCredentials a KerberosCredential containing the administrative credentials
+   *                                 for the relevant KDC
+   * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
+   */
+  public abstract void open(KerberosCredential administratorCredentials, String defaultRealm)
+      throws KerberosOperationException;
+
+  /**
+   * Prepares and creates resources to be used by this KerberosOperationHandler.
+   * Implementation in this class is ignoring parameters ldapUrl and principalContainerDn and delegate to
+   * <code>open(KerberosCredential administratorCredentials, String defaultRealm)</code>
+   * Subclasses that want to use these parameters need to override this method.
+   * <p/>
+   * It is expected that this KerberosOperationHandler will not be used before this call.
+   *
+   * @param administratorCredentials a KerberosCredential containing the administrative credentials
+   *                                 for the relevant KDC
+   * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
+   * @param ldapUrl                  ldapUrl of ldap back end where principals would be created
+   * @param principalContainerDn     DN of the container in ldap back end where principals would be created
+   */
+  public void open(KerberosCredential administratorCredentials, String defaultRealm,
+                   String ldapUrl, String principalContainerDn)
+      throws KerberosOperationException {
+    open(administratorCredentials, defaultRealm);
+  }
 
   /**
    * Closes and cleans up any resources used by this KerberosOperationHandler
@@ -160,7 +157,7 @@ public abstract class KerberosOperationHandler {
    * It is expected that this KerberosOperationHandler will not be used after this call.
    */
   public abstract void close()
-      throws AmbariException;
+      throws KerberosOperationException;
 
   /**
    * Test to see if the specified principal exists in a previously configured KDC
@@ -169,10 +166,10 @@ public abstract class KerberosOperationHandler {
    *
    * @param principal a String containing the principal to test
    * @return true if the principal exists; false otherwise
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   public abstract boolean principalExists(String principal)
-      throws AmbariException;
+      throws KerberosOperationException;
 
   /**
    * Creates a new principal in a previously configured KDC
@@ -182,10 +179,10 @@ public abstract class KerberosOperationHandler {
    * @param principal a String containing the principal to add
    * @param password  a String containing the password to use when creating the principal
    * @return an Integer declaring the generated key number
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   public abstract Integer createServicePrincipal(String principal, String password)
-      throws AmbariException;
+      throws KerberosOperationException;
 
   /**
    * Updates the password for an existing principal in a previously configured KDC
@@ -195,10 +192,10 @@ public abstract class KerberosOperationHandler {
    * @param principal a String containing the principal to update
    * @param password  a String containing the password to set
    * @return an Integer declaring the new key number
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   public abstract Integer setPrincipalPassword(String principal, String password)
-      throws AmbariException;
+      throws KerberosOperationException;
 
   /**
    * Removes an existing principal in a previously configured KDC
@@ -207,10 +204,27 @@ public abstract class KerberosOperationHandler {
    *
    * @param principal a String containing the principal to remove
    * @return true if the principal was successfully removed; otherwise false
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   public abstract boolean removeServicePrincipal(String principal)
-      throws AmbariException;
+      throws KerberosOperationException;
+
+  /**
+   * Tests to ensure the connection information and credentials allow for administrative
+   * connectivity to the KDC
+   *
+   * @return true of successful; otherwise false
+   * @throws KerberosOperationException if a failure occurs while testing the
+   *                                    administrator credentials
+   */
+  public boolean testAdministratorCredentials() throws KerberosOperationException {
+    KerberosCredential credentials = getAdministratorCredentials();
+    if (credentials == null) {
+      throw new KerberosOperationException("Missing KDC administrator credentials");
+    } else {
+      return principalExists(credentials.getPrincipal());
+    }
+  }
 
   /**
    * Create or append to a keytab file using the specified principal and password.
@@ -219,18 +233,18 @@ public abstract class KerberosOperationHandler {
    * @param password   a String containing the password to use when creating the principal
    * @param keytabFile a File containing the absolute path to the keytab file
    * @return true if the keytab file was successfully created; false otherwise
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   public boolean createKeytabFile(String principal, String password, Integer keyNumber, File keytabFile)
-      throws AmbariException {
+      throws KerberosOperationException {
     boolean success = false;
 
     if ((principal == null) || principal.isEmpty()) {
-      throw new AmbariException("Failed to create keytab file, missing principal");
+      throw new KerberosOperationException("Failed to create keytab file, missing principal");
     } else if (password == null) {
-      throw new AmbariException(String.format("Failed to create keytab file for %s, missing password", principal));
+      throw new KerberosOperationException(String.format("Failed to create keytab file for %s, missing password", principal));
     } else if (keytabFile == null) {
-      throw new AmbariException(String.format("Failed to create keytab file for %s, missing file path", principal));
+      throw new KerberosOperationException(String.format("Failed to create keytab file for %s, missing file path", principal));
     } else {
       Keytab keytab;
       Set<EncryptionType> ciphers = new HashSet<EncryptionType>(DEFAULT_CIPHERS);
@@ -294,7 +308,7 @@ public abstract class KerberosOperationHandler {
               keytabFile.deleteOnExit();
             }
 
-            throw new AmbariException(message, e);
+            throw new KerberosOperationException(message, e);
           }
         }
       }
@@ -319,6 +333,24 @@ public abstract class KerberosOperationHandler {
     this.defaultRealm = defaultRealm;
   }
 
+  /**
+   * Test this KerberosOperationHandler to see whether is was previously open or not
+   *
+   * @return a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false)
+   */
+  public boolean isOpen() {
+    return open;
+  }
+
+  /**
+   * Sets whether this KerberosOperationHandler is open or not.
+   *
+   * @param open a boolean value indicating whether this KerberosOperationHandler was open (true) or not (false)
+   */
+  public void setOpen(boolean open) {
+    this.open = open;
+  }
+
   /**
    * Given base64-encoded keytab data, decode the String to binary data and write it to a (temporary)
    * file.
@@ -328,10 +360,10 @@ public abstract class KerberosOperationHandler {
    *
    * @param keytabData a String containing base64-encoded keytab data
    * @return a File pointing to the decoded keytab file or null if not successful
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   protected File createKeytabFile(String keytabData)
-      throws AmbariException {
+      throws KerberosOperationException {
     boolean success = false;
     File tempFile = null;
 
@@ -354,12 +386,12 @@ public abstract class KerberosOperationHandler {
         String message = String.format("Failed to write to temporary keytab file %s: %s",
             tempFile.getAbsolutePath(), e.getLocalizedMessage());
         LOG.error(message, e);
-        throw new AmbariException(message, e);
+        throw new KerberosOperationException(message, e);
       } catch (IOException e) {
         String message = String.format("Failed to write to temporary keytab file %s: %s",
             tempFile.getAbsolutePath(), e.getLocalizedMessage());
         LOG.error(message, e);
-        throw new AmbariException(message, e);
+        throw new KerberosOperationException(message, e);
       } finally {
         if (fos != null) {
           try {
@@ -390,10 +422,10 @@ public abstract class KerberosOperationHandler {
    *
    * @param command an array of String value representing the command and its arguments
    * @return a ShellCommandUtil.Result declaring the result of the operation
-   * @throws AmbariException
+   * @throws KerberosOperationException
    */
   protected ShellCommandUtil.Result executeCommand(String[] command)
-      throws AmbariException {
+      throws KerberosOperationException {
 
     if ((command == null) || (command.length == 0)) {
       return null;
@@ -403,11 +435,11 @@ public abstract class KerberosOperationHandler {
       } catch (IOException e) {
         String message = String.format("Failed to execute the command: %s", e.getLocalizedMessage());
         LOG.error(message, e);
-        throw new AmbariException(message, e);
+        throw new KerberosOperationException(message, e);
       } catch (InterruptedException e) {
         String message = String.format("Failed to wait for the command to complete: %s", e.getLocalizedMessage());
         LOG.error(message, e);
-        throw new AmbariException(message, e);
+        throw new KerberosOperationException(message, e);
       }
     }
   }

+ 4 - 2
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java

@@ -18,9 +18,12 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
+import com.google.inject.Singleton;
+
 /**
  * KerberosOperationHandlerFactory gets relevant KerberosOperationHandlers given a KDCType.
  */
+@Singleton
 public class KerberosOperationHandlerFactory {
 
   /**
@@ -32,7 +35,7 @@ public class KerberosOperationHandlerFactory {
    * @param kdcType the relevant KDCType
    * @return a KerberosOperationHandler
    */
-  public static KerberosOperationHandler getKerberosOperationHandler(KDCType kdcType) {
+  public KerberosOperationHandler getKerberosOperationHandler(KDCType kdcType) {
     KerberosOperationHandler handler = null;
 
     // If not specified, use KDCType.MIT_KDC as a default
@@ -48,7 +51,6 @@ public class KerberosOperationHandlerFactory {
       case ACTIVE_DIRECTORY:
         handler = new ADKerberosOperationHandler();
         break;
-
     }
 
     return handler;

+ 12 - 5
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java

@@ -96,6 +96,15 @@ public abstract class KerberosServerAction extends AbstractServerAction {
   @Inject
   private Clusters clusters = null;
 
+  /**
+   * The KerberosOperationHandlerFactory to use to obtain KerberosOperationHandler instances
+   * <p/>
+   * This is needed to help with test cases to mock a KerberosOperationHandler
+   */
+  @Inject
+  private KerberosOperationHandlerFactory kerberosOperationHandlerFactory;
+
+
   /**
    * Given a (command parameter) Map and a property name, attempts to safely retrieve the requested
    * data.
@@ -301,7 +310,7 @@ public abstract class KerberosServerAction extends AbstractServerAction {
               throw new AmbariException(message);
             }
 
-            KerberosOperationHandler handler = KerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType);
+            KerberosOperationHandler handler = kerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType);
             if (handler == null) {
               String message = String.format("Failed to process the identities, a KDC operation handler was not found for the KDC type of : %s",
                   kdcType.toString());
@@ -325,9 +334,7 @@ public abstract class KerberosServerAction extends AbstractServerAction {
                   break;
                 }
               }
-            } catch (AmbariException e) {
-              // Catch this separately from IOException since the reason it was thrown was not the same
-              // Note: AmbariException is an IOException, so there may be some confusion
+            } catch (KerberosOperationException e) {
               throw new AmbariException(e.getMessage(), e);
             } catch (IOException e) {
               String message = String.format("Failed to process the identities, cannot read the index file: %s",
@@ -349,7 +356,7 @@ public abstract class KerberosServerAction extends AbstractServerAction {
               // exception since there is little we can or care to do about it now.
               try {
                 handler.close();
-              } catch (AmbariException e) {
+              } catch (KerberosOperationException e) {
                 // Ignore this...
               }
             }

+ 205 - 227
ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.utils.ShellCommandUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,14 +49,16 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
 
 
   @Override
-  public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
+  public void open(KerberosCredential administratorCredentials, String defaultRealm) throws KerberosOperationException {
     setAdministratorCredentials(administratorCredentials);
     setDefaultRealm(defaultRealm);
+    setOpen(true);
   }
 
   @Override
-  public void close() throws AmbariException {
+  public void close() throws KerberosOperationException {
     // There is nothing to do here.
+    setOpen(false);
   }
 
   /**
@@ -68,11 +69,18 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param principal a String containing the principal to test
    * @return true if the principal exists; false otherwise
-   * @throws AmbariException
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   @Override
   public boolean principalExists(String principal)
-      throws AmbariException {
+      throws KerberosOperationException {
+
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
 
     if (principal == null) {
       return false;
@@ -80,30 +88,18 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
       // Create the KAdmin query to execute:
       String query = String.format("get_principal %s", principal);
 
+      ShellCommandUtil.Result result;
       try {
-        ShellCommandUtil.Result result = invokeKAdmin(query);
-
-        if (result != null) {
-          if (result.isSuccessful()) {
-            String stdOut = result.getStdout();
-
-            // If there is data from STDOUT, see if the following string exists:
-            //    Principal: <principal>
-            return (stdOut != null) && stdOut.contains(String.format("Principal: %s", principal));
-          } else {
-            LOG.warn("Failed to query for principal {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
-                principal, result.getExitCode(), result.getStdout(), result.getStderr());
-            throw new AmbariException(String.format("Failed to query for principal %s", principal));
-          }
-        } else {
-          String message = String.format("Failed to query for principal %s - Unknown reason", principal);
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } catch (AmbariException e) {
+        result = invokeKAdmin(query);
+      } catch (KerberosOperationException e) {
         LOG.error(String.format("Failed to query for principal %s", principal), e);
         throw e;
       }
+
+      // If there is data from STDOUT, see if the following string exists:
+      //    Principal: <principal>
+      String stdOut = result.getStdout();
+      return (stdOut != null) && stdOut.contains(String.format("Principal: %s", principal));
     }
   }
 
@@ -117,60 +113,41 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    * @param principal a String containing the principal add
    * @param password  a String containing the password to use when creating the principal
    * @return an Integer declaring the generated key number
-   * @throws AmbariException
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   @Override
   public Integer createServicePrincipal(String principal, String password)
-      throws AmbariException {
+      throws KerberosOperationException {
+
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
 
     if ((principal == null) || principal.isEmpty()) {
-      throw new AmbariException("Failed to create new principal - no principal specified");
+      throw new KerberosOperationException("Failed to create new principal - no principal specified");
+    } else if ((password == null) || password.isEmpty()) {
+      throw new KerberosOperationException("Failed to create new principal - no password specified");
     } else {
       // Create the kdamin query:  add_principal <-randkey|-pw <password>> <principal>
-      StringBuilder queryBuilder = new StringBuilder();
-
-      queryBuilder.append("add_principal");
-
-      // If a password was not supplied, have the KDC generate a random key, else use the supplied
-      // password
-      if ((password == null) || password.isEmpty()) {
-        queryBuilder.append(" -randkey");
-      } else {
-        queryBuilder.append(" -pw ");
-        queryBuilder.append(password);
-      }
-
-      queryBuilder.append(" ");
-      queryBuilder.append(principal);
-
+      ShellCommandUtil.Result result;
       try {
-        ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString());
-
-        if (result != null) {
-          if (result.isSuccessful()) {
-            String stdOut = result.getStdout();
-
-            // If there is data from STDOUT, see if the following string exists:
-            //    Principal "<principal>" created
-            if ((stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal))) {
-              return getKeyNumber(principal);
-            } else {
-              throw new AmbariException(String.format("Failed to create service principal for %s", principal));
-            }
-          } else {
-            LOG.warn("Failed to create service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
-                principal, result.getExitCode(), result.getStdout(), result.getStderr());
-            throw new AmbariException(String.format("Failed to create service principal for %s", principal));
-          }
-        } else {
-          String message = String.format("Failed to create service principal for %s - Unknown reason", principal);
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } catch (AmbariException e) {
+        result = invokeKAdmin(String.format("add_principal -pw %s %s", password, principal));
+      } catch (KerberosOperationException e) {
         LOG.error(String.format("Failed to create new principal for %s", principal), e);
         throw e;
       }
+
+      // If there is data from STDOUT, see if the following string exists:
+      //    Principal "<principal>" created
+      String stdOut = result.getStdout();
+      if ((stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal))) {
+        return getKeyNumber(principal);
+      } else {
+        throw new KerberosOperationException(String.format("Failed to create service principal for %s", principal));
+      }
     }
   }
 
@@ -183,50 +160,31 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    * @param principal a String containing the principal to update
    * @param password  a String containing the password to set
    * @return an Integer declaring the new key number
-   * @throws AmbariException
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   @Override
-  public Integer setPrincipalPassword(String principal, String password) throws AmbariException {
+  public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
+
     if ((principal == null) || principal.isEmpty()) {
-      throw new AmbariException("Failed to set password - no principal specified");
+      throw new KerberosOperationException("Failed to set password - no principal specified");
+    } else if ((password == null) || password.isEmpty()) {
+      throw new KerberosOperationException("Failed to set password - no password specified");
     } else {
       // Create the kdamin query:  change_password <-randkey|-pw <password>> <principal>
-      StringBuilder queryBuilder = new StringBuilder();
-
-      queryBuilder.append("change_password");
-
-      // If a password was not supplied, have the KDC generate a random key, else use the supplied
-      // password
-      if ((password == null) || password.isEmpty()) {
-        queryBuilder.append(" -randkey");
-      } else {
-        queryBuilder.append(" -pw ");
-        queryBuilder.append(password);
-      }
-
-      queryBuilder.append(" ");
-      queryBuilder.append(principal);
-
       try {
-        ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString());
-
-        if (result != null) {
-          if (result.isSuccessful()) {
-            return getKeyNumber(principal);
-          } else {
-            LOG.warn("Failed to set password for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
-                principal, result.getExitCode(), result.getStdout(), result.getStderr());
-            throw new AmbariException(String.format("Failed to update password for %s", principal));
-          }
-        } else {
-          String message = String.format("Failed to set password for %s - Unknown reason", principal);
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } catch (AmbariException e) {
+        invokeKAdmin(String.format("change_password -pw %s %s", password, principal));
+      } catch (KerberosOperationException e) {
         LOG.error(String.format("Failed to set password for %s", principal), e);
         throw e;
       }
+
+      return getKeyNumber(principal);
     }
   }
 
@@ -237,37 +195,33 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param principal a String containing the principal to remove
    * @return true if the principal was successfully removed; otherwise false
-   * @throws AmbariException
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   @Override
-  public boolean removeServicePrincipal(String principal) throws AmbariException {
+  public boolean removeServicePrincipal(String principal) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
+
     if ((principal == null) || principal.isEmpty()) {
-      throw new AmbariException("Failed to remove new principal - no principal specified");
+      throw new KerberosOperationException("Failed to remove new principal - no principal specified");
     } else {
+      ShellCommandUtil.Result result;
+
       try {
-        ShellCommandUtil.Result result = invokeKAdmin(String.format("delete_principal -force %s", principal));
-
-        if (result != null) {
-          if (result.isSuccessful()) {
-            String stdOut = result.getStdout();
-
-            // If there is data from STDOUT, see if the following string exists:
-            //    Principal "<principal>" created
-            return (stdOut != null) && !stdOut.contains("Principal does not exist");
-          } else {
-            LOG.warn("Failed to remove service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
-                principal, result.getExitCode(), result.getStdout(), result.getStderr());
-            throw new AmbariException(String.format("Failed to remove service principal for %s", principal));
-          }
-        } else {
-          String message = String.format("Failed to remove service principal for %s - Unknown reason", principal);
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } catch (AmbariException e) {
+        result = invokeKAdmin(String.format("delete_principal -force %s", principal));
+      } catch (KerberosOperationException e) {
         LOG.error(String.format("Failed to remove new principal for %s", principal), e);
         throw e;
       }
+
+      // If there is data from STDOUT, see if the following string exists:
+      //    Principal "<principal>" created
+      String stdOut = result.getStdout();
+      return (stdOut != null) && !stdOut.contains("Principal does not exist");
     }
   }
 
@@ -276,62 +230,59 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param principal a String declaring the principal to look up
    * @return an Integer declaring the current key number
-   * @throws AmbariException if an error occurs while looking up the relevant key number
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
-  private Integer getKeyNumber(String principal) throws AmbariException {
+  private Integer getKeyNumber(String principal) throws KerberosOperationException {
+    if (!isOpen()) {
+      throw new KerberosOperationException("This operation handler has not be opened");
+    }
+
     if ((principal == null) || principal.isEmpty()) {
-      throw new AmbariException("Failed to get key number for principal  - no principal specified");
+      throw new KerberosOperationException("Failed to get key number for principal  - no principal specified");
     } else {
       // Create the kdamin query:  get_principal <principal>
       String query = String.format("get_principal %s", principal);
 
+      ShellCommandUtil.Result result;
       try {
-        ShellCommandUtil.Result result = invokeKAdmin(query);
-
-        if (result != null) {
-          if (result.isSuccessful()) {
-            String stdOut = result.getStdout();
-
-            if (stdOut == null) {
-              LOG.warn("Failed to get key number for {}:\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}",
-                  principal, result.getExitCode(), result.getStderr());
-              throw new AmbariException(String.format("Failed to get key number for %s", principal));
-            }
-
-            Matcher matcher = PATTERN_GET_KEY_NUMBER.matcher(stdOut);
-
-            if (matcher.matches()) {
-              NumberFormat numberFormat = NumberFormat.getIntegerInstance();
-              String keyNumber = matcher.group(1);
-
-              numberFormat.setGroupingUsed(false);
-              try {
-                Number number = numberFormat.parse(keyNumber);
-                return (number == null) ? 0 : number.intValue();
-              } catch (ParseException e) {
-                LOG.warn("Failed to get key number for {} - invalid key number value ({}):\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}",
-                    principal, keyNumber, result.getExitCode(), result.getStderr());
-                throw new AmbariException(String.format("Failed to get key number for %s", principal));
-              }
-            } else {
-              LOG.warn("Failed to get key number for {} - unexpected STDOUT data:\n\tExitCode: {}\n\tSTDOUT: NULL\n\tSTDERR: {}",
-                  principal, result.getExitCode(), result.getStderr());
-              throw new AmbariException(String.format("Failed to get key number for %s", principal));
-            }
-          } else {
-            LOG.warn("Failed to get key number for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
-                principal, result.getExitCode(), result.getStdout(), result.getStderr());
-            throw new AmbariException(String.format("Failed to get key number for %s", principal));
-          }
-        } else {
-          String message = String.format("Failed to get key number for %s - Unknown reason", principal);
-          LOG.warn(message);
-          throw new AmbariException(message);
-        }
-      } catch (AmbariException e) {
+        result = invokeKAdmin(query);
+      } catch (KerberosOperationException e) {
         LOG.error(String.format("Failed to get key number for %s", principal), e);
         throw e;
       }
+
+      String stdOut = result.getStdout();
+      if (stdOut == null) {
+        String message = String.format("Failed to get key number for %s:\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s",
+            principal, result.getExitCode(), result.getStderr());
+        LOG.warn(message);
+        throw new KerberosOperationException(message);
+      }
+
+      Matcher matcher = PATTERN_GET_KEY_NUMBER.matcher(stdOut);
+      if (matcher.matches()) {
+        NumberFormat numberFormat = NumberFormat.getIntegerInstance();
+        String keyNumber = matcher.group(1);
+
+        numberFormat.setGroupingUsed(false);
+        try {
+          Number number = numberFormat.parse(keyNumber);
+          return (number == null) ? 0 : number.intValue();
+        } catch (ParseException e) {
+          String message = String.format("Failed to get key number for %s - invalid key number value (%s):\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s",
+              principal, keyNumber, result.getExitCode(), result.getStderr());
+          LOG.warn(message);
+          throw new KerberosOperationException(message);
+        }
+      } else {
+        String message = String.format("Failed to get key number for %s - unexpected STDOUT data:\n\tExitCode: %s\n\tSTDOUT: NULL\n\tSTDERR: %s",
+            principal, result.getExitCode(), result.getStderr());
+        LOG.warn(message);
+        throw new KerberosOperationException(message);
+      }
     }
   }
 
@@ -340,77 +291,104 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler {
    *
    * @param query a String containing the query to send to the kdamin command
    * @return a ShellCommandUtil.Result containing the result of the operation
-   * @throws AmbariException
+   * @throws KerberosKDCConnectionException       if a connection to the KDC cannot be made
+   * @throws KerberosAdminAuthenticationException if the administrator credentials fail to authenticate
+   * @throws KerberosRealmException               if the realm does not map to a KDC
+   * @throws KerberosOperationException           if an unexpected error occurred
    */
   private ShellCommandUtil.Result invokeKAdmin(String query)
-      throws AmbariException {
+      throws KerberosOperationException {
     ShellCommandUtil.Result result = null;
 
-    if ((query != null) && !query.isEmpty()) {
-      KerberosCredential administratorCredentials = getAdministratorCredentials();
-      String defaultRealm = getDefaultRealm();
+    if ((query == null) || query.isEmpty()) {
+      throw new KerberosOperationException("Missing kadmin query");
+    }
+    KerberosCredential administratorCredentials = getAdministratorCredentials();
+    String defaultRealm = getDefaultRealm();
 
-      List<String> command = new ArrayList<String>();
-      File tempKeytabFile = null;
+    List<String> command = new ArrayList<String>();
+    File tempKeytabFile = null;
 
-      try {
-        String adminPrincipal = (administratorCredentials == null)
-            ? null
-            : administratorCredentials.getPrincipal();
+    try {
+      String adminPrincipal = (administratorCredentials == null)
+          ? null
+          : administratorCredentials.getPrincipal();
 
-        if ((adminPrincipal == null) || adminPrincipal.isEmpty()) {
-          // Set the kdamin interface to be kadmin.local
-          command.add("kadmin.local");
-        } else {
-          String adminPassword = administratorCredentials.getPassword();
-          String adminKeyTab = administratorCredentials.getKeytab();
-
-          // Set the kdamin interface to be kadmin
-          command.add("kadmin");
-
-          // Add the administrative principal
-          command.add("-p");
-          command.add(adminPrincipal);
-
-          if ((adminKeyTab != null) && !adminKeyTab.isEmpty()) {
-            tempKeytabFile = createKeytabFile(adminKeyTab);
-
-            if (tempKeytabFile != null) {
-              // Add keytab file administrative principal
-              command.add("-k");
-              command.add("-t");
-              command.add(tempKeytabFile.getAbsolutePath());
-            }
-          } else if (adminPassword != null) {
-            // Add password for administrative principal
-            command.add("-w");
-            command.add(adminPassword);
+      if ((adminPrincipal == null) || adminPrincipal.isEmpty()) {
+        // Set the kdamin interface to be kadmin.local
+        command.add("kadmin.local");
+      } else {
+        String adminPassword = administratorCredentials.getPassword();
+        String adminKeyTab = administratorCredentials.getKeytab();
+
+        // Set the kdamin interface to be kadmin
+        command.add("kadmin");
+
+        // Add the administrative principal
+        command.add("-p");
+        command.add(adminPrincipal);
+
+        if ((adminKeyTab != null) && !adminKeyTab.isEmpty()) {
+          tempKeytabFile = createKeytabFile(adminKeyTab);
+
+          if (tempKeytabFile != null) {
+            // Add keytab file administrative principal
+            command.add("-k");
+            command.add("-t");
+            command.add(tempKeytabFile.getAbsolutePath());
           }
+        } else if (adminPassword != null) {
+          // Add password for administrative principal
+          command.add("-w");
+          command.add(adminPassword);
         }
+      }
 
-        if ((defaultRealm != null) && !defaultRealm.isEmpty()) {
-          // Add default realm clause
-          command.add("-r");
-          command.add(defaultRealm);
-        }
+      if ((defaultRealm != null) && !defaultRealm.isEmpty()) {
+        // Add default realm clause
+        command.add("-r");
+        command.add(defaultRealm);
+      }
 
-        // Add kadmin query
-        command.add("-q");
-        command.add(query.replace("\"", "\\\""));
+      // Add kadmin query
+      command.add("-q");
+      command.add(query.replace("\"", "\\\""));
 
-        result = executeCommand(command.toArray(new String[command.size()]));
-      } finally {
-        // If a temporary keytab file was created, clean it up.
-        if (tempKeytabFile != null) {
-          if (!tempKeytabFile.delete()) {
-            tempKeytabFile.deleteOnExit();
-          }
+      result = executeCommand(command.toArray(new String[command.size()]));
+
+      if (!result.isSuccessful()) {
+        String message = String.format("Failed to execute kadmin:\n\tExitCode: %s\n\tSTDOUT: %s\n\tSTDERR: %s",
+            result.getExitCode(), result.getStdout(), result.getStderr());
+        LOG.warn(message);
+
+        // Test STDERR to see of any "expected" error conditions were encountered...
+        String stdErr = result.getStderr();
+        // Did admin credentials fail?
+        if (stdErr.contains("Client not found in Kerberos database")) {
+          throw new KerberosAdminAuthenticationException(stdErr);
+        } else if (stdErr.contains("Incorrect password while initializing")) {
+          throw new KerberosAdminAuthenticationException(stdErr);
+        }
+        // Did we fail to connect to the KDC?
+        else if (stdErr.contains("Cannot contact any KDC")) {
+          throw new KerberosKDCConnectionException(stdErr);
+        }
+        // Was the realm invalid?
+        else if (stdErr.contains("Missing parameters in krb5.conf required for kadmin client")) {
+          throw new KerberosRealmException(stdErr);
+        } else {
+          throw new KerberosOperationException("Unexpected error condition executing the kadmin command");
+        }
+      }
+    } finally {
+      // If a temporary keytab file was created, clean it up.
+      if (tempKeytabFile != null) {
+        if (!tempKeytabFile.delete()) {
+          tempKeytabFile.deleteOnExit();
         }
       }
     }
 
     return result;
   }
-
-
 }

+ 75 - 3
ambari-server/src/test/java/org/apache/ambari/server/controller/KerberosHelperTest.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.controller;
 import com.google.inject.AbstractModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import junit.framework.Assert;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.ActionManager;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
@@ -32,7 +33,12 @@ import org.apache.ambari.server.controller.internal.RequestStageContainer;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.orm.DBAccessor;
 import org.apache.ambari.server.security.SecurityHelper;
+import org.apache.ambari.server.serveraction.kerberos.KDCType;
 import org.apache.ambari.server.serveraction.kerberos.KerberosCredential;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerTest;
+import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Config;
@@ -77,6 +83,43 @@ public class KerberosHelperTest extends EasyMockSupport {
 
   @Before
   public void setUp() throws Exception {
+    final KerberosOperationHandlerFactory kerberosOperationHandlerFactory = createNiceMock(KerberosOperationHandlerFactory.class);
+
+    expect(kerberosOperationHandlerFactory.getKerberosOperationHandler(KDCType.MIT_KDC))
+        .andReturn(new KerberosOperationHandler() {
+          @Override
+          public void open(KerberosCredential administratorCredentials, String defaultRealm) throws KerberosOperationException {
+            setAdministratorCredentials(administratorCredentials);
+            setDefaultRealm(defaultRealm);
+          }
+
+          @Override
+          public void close() throws KerberosOperationException {
+
+          }
+
+          @Override
+          public boolean principalExists(String principal) throws KerberosOperationException {
+            return "principal".equals(principal);
+          }
+
+          @Override
+          public Integer createServicePrincipal(String principal, String password) throws KerberosOperationException {
+            return null;
+          }
+
+          @Override
+          public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException {
+            return null;
+          }
+
+          @Override
+          public boolean removeServicePrincipal(String principal) throws KerberosOperationException {
+            return false;
+          }
+        })
+        .anyTimes();
+
     injector = Guice.createInjector(new AbstractModule() {
 
       @Override
@@ -96,6 +139,7 @@ public class KerberosHelperTest extends EasyMockSupport {
         bind(StageFactory.class).toInstance(createNiceMock(StageFactory.class));
         bind(Clusters.class).toInstance(createNiceMock(ClustersImpl.class));
         bind(ConfigHelper.class).toInstance(createNiceMock(ConfigHelper.class));
+        bind(KerberosOperationHandlerFactory.class).toInstance(kerberosOperationHandlerFactory);
       }
     });
   }
@@ -141,6 +185,32 @@ public class KerberosHelperTest extends EasyMockSupport {
 
   @Test
   public void testEnableKerberos() throws Exception {
+    testEnableKerberos(new KerberosCredential("principal", "password", "keytab"));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testEnableKerberosMissingCredentials() throws Exception {
+    try {
+      testEnableKerberos(null);
+    }
+    catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage().startsWith("Missing KDC administrator credentials"));
+      throw e;
+    }
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testEnableKerberosInvalidCredentials() throws Exception {
+    try {
+      testEnableKerberos(new KerberosCredential("invalid_principal", "password", "keytab"));
+    }
+    catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage().startsWith("Invalid KDC administrator credentials"));
+      throw e;
+    }
+  }
+
+  private void testEnableKerberos(final KerberosCredential kerberosCredential) throws Exception {
     KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class);
 
     final ServiceComponentHost sch1 = createNiceMock(ServiceComponentHost.class);
@@ -214,9 +284,11 @@ public class KerberosHelperTest extends EasyMockSupport {
         .andReturn(new StackId("HDP", "2.2"))
         .anyTimes();
     expect(cluster.getSessionAttributes()).andReturn(new HashMap<String, Object>(){{
-      put("kerberos_admin/" + KerberosCredential.KEY_NAME_PRINCIPAL, "principal");
-      put("kerberos_admin/" + KerberosCredential.KEY_NAME_PASSWORD, "password");
-      put("kerberos_admin/" + KerberosCredential.KEY_NAME_KEYTAB, "keytab");
+      if(kerberosCredential != null) {
+        put("kerberos_admin/" + KerberosCredential.KEY_NAME_PRINCIPAL, kerberosCredential.getPrincipal());
+        put("kerberos_admin/" + KerberosCredential.KEY_NAME_PASSWORD, kerberosCredential.getPassword());
+        put("kerberos_admin/" + KerberosCredential.KEY_NAME_KEYTAB, kerberosCredential.getKeytab());
+      }
     }}).anyTimes();
 
     final Clusters clusters = injector.getInstance(Clusters.class);

+ 215 - 26
ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/ADKerberosOperationHandlerTest.java

@@ -18,39 +18,228 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
-import junit.framework.Assert;
-import org.apache.ambari.server.AmbariException;
-import org.junit.Before;
+import org.easymock.EasyMockSupport;
+import org.easymock.IAnswer;
+import org.junit.Ignore;
 import org.junit.Test;
 
-public class ADKerberosOperationHandlerTest  {
+import javax.naming.AuthenticationException;
+import javax.naming.CommunicationException;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.LdapContext;
 
-  @Test
+import java.util.Properties;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+public class ADKerberosOperationHandlerTest extends EasyMockSupport {
+  private static final String DEFAULT_ADMIN_PRINCIPAL = "admin@example.com";
+  private static final String DEFAULT_ADMIN_PASSWORD = "hadoop";
+  private static final String DEFAULT_LDAP_URL = "ldaps://ad.example.com";
+  private static final String DEFAULT_PRINCIPAL_CONTAINER_DN = "ou=cluster,dc=example,dc=com";
+  private static final String DEFAULT_REALM = "EXAMPLE.COM";
+
+  @Test(expected = KerberosKDCConnectionException.class)
   public void testOpenExceptionLdapUrlNotProvided() throws Exception {
-    try {
-      KerberosOperationHandler handler = new ADKerberosOperationHandler();
-      KerberosCredential kc = new KerberosCredential(
-                "Administrator@knox.com", "adminpass", null);  // null keytab
-
-      handler.open(kc, "KNOX.COM");
-      Assert.fail("AmbariException not thrown for null ldapUrl");
-    } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
-    }
+    KerberosOperationHandler handler = new ADKerberosOperationHandler();
+    KerberosCredential kc = new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null);
+    handler.open(kc, DEFAULT_REALM, null, DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.close();
+  }
+
+  @Test(expected = KerberosLDAPContainerException.class)
+  public void testOpenExceptionPrincipalContainerDnNotProvided() throws Exception {
+    KerberosOperationHandler handler = new ADKerberosOperationHandler();
+    KerberosCredential kc = new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null);
+    handler.open(kc, DEFAULT_REALM, DEFAULT_LDAP_URL, null);
+    handler.close();
+  }
+
+  @Test(expected = KerberosAdminAuthenticationException.class)
+  public void testOpenExceptionAdminCredentialsNotProvided() throws Exception {
+    KerberosOperationHandler handler = new ADKerberosOperationHandler();
+    handler.open(null, DEFAULT_REALM, DEFAULT_LDAP_URL, DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.close();
+  }
+
+  @Test(expected = KerberosAdminAuthenticationException.class)
+  public void testTestAdministratorCredentialsIncorrectAdminPassword() throws Exception {
+    ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
+        .createNiceMock();
+
+    expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class))).andAnswer(new IAnswer<LdapContext>() {
+      @Override
+      public LdapContext answer() throws Throwable {
+        throw new AuthenticationException();
+      }
+    }).once();
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, "wrong", null),
+        DEFAULT_REALM, DEFAULT_LDAP_URL, DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test(expected = KerberosAdminAuthenticationException.class)
+  public void testTestAdministratorCredentialsIncorrectAdminPrincipal() throws Exception {
+    ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
+        .createNiceMock();
+
+    expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class))).andAnswer(new IAnswer<LdapContext>() {
+      @Override
+      public LdapContext answer() throws Throwable {
+        throw new AuthenticationException();
+      }
+    }).once();
+
+    replayAll();
+
+    handler.open(new KerberosCredential("wrong", DEFAULT_ADMIN_PASSWORD, null),
+        DEFAULT_REALM, DEFAULT_LDAP_URL, DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test(expected = KerberosKDCConnectionException.class)
+  public void testTestAdministratorCredentialsKDCConnectionException() throws Exception {
+    ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
+        .createNiceMock();
+
+    expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class))).andAnswer(new IAnswer<LdapContext>() {
+      @Override
+      public LdapContext answer() throws Throwable {
+        throw new CommunicationException();
+      }
+    }).once();
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null),
+        DEFAULT_REALM, "invalid", DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+
+  @Test
+  public void testTestAdministratorCredentialsSuccess() throws Exception {
+    ADKerberosOperationHandler handler = createMockBuilder(ADKerberosOperationHandler.class)
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createInitialLdapContext", Properties.class, Control[].class))
+        .addMockedMethod(ADKerberosOperationHandler.class.getDeclaredMethod("createSearchControls"))
+        .createNiceMock();
+
+    expect(handler.createInitialLdapContext(anyObject(Properties.class), anyObject(Control[].class)))
+        .andAnswer(new IAnswer<LdapContext>() {
+          @Override
+          public LdapContext answer() throws Throwable {
+            LdapContext ldapContext = createNiceMock(LdapContext.class);
+            expect(ldapContext.search(anyObject(String.class), anyObject(String.class), anyObject(SearchControls.class)))
+                .andAnswer(new IAnswer<NamingEnumeration<SearchResult>>() {
+                  @Override
+                  public NamingEnumeration<SearchResult> answer() throws Throwable {
+                    NamingEnumeration<SearchResult> result = createNiceMock(NamingEnumeration.class);
+                    expect(result.hasMore()).andReturn(false).once();
+                    replay(result);
+                    return result;
+                  }
+                })
+                .once();
+            replay(ldapContext);
+            return ldapContext;
+          }
+        })
+        .once();
+    expect(handler.createSearchControls()).andAnswer(new IAnswer<SearchControls>() {
+      @Override
+      public SearchControls answer() throws Throwable {
+        SearchControls searchControls = createNiceMock(SearchControls.class);
+        replay(searchControls);
+        return searchControls;
+      }
+    }).once();
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null),
+        DEFAULT_REALM, DEFAULT_LDAP_URL, DEFAULT_PRINCIPAL_CONTAINER_DN);
+    handler.testAdministratorCredentials();
+    handler.close();
   }
 
-    @Test
-    public void testOpenExceptionPrincipalContainerDnNotProvided() throws Exception {
-        try {
-            KerberosOperationHandler handler = new ADKerberosOperationHandler();
-            KerberosCredential kc = new KerberosCredential(
-                    "Administrator@knox.com", "adminpass", null);  // null keytab
+  /**
+   * Implementation to illustrate the use of operations on this class
+   *
+   * @throws Throwable
+   */
+  @Test
+  @Ignore
+  public void testLive() throws Throwable {
+
+    /* ******************************************************************************************
+     * SSL Certificate of AD should have been imported into truststore when that certificate
+     * is not issued by trusted authority. This is typical with self signed certificated in
+     * development environment.  To use specific trust store, set path to it in
+     * javax.net.ssl.trustStore System property.  Example:
+     *      System.setProperty(
+     *        "javax.net.ssl.trustStore",
+     *        "/tmp/workspace/ambari/apache-ambari-rd/cacerts"
+     *       );
+     * ****************************************************************************************** */
+
+    ADKerberosOperationHandler handler = new ADKerberosOperationHandler();
+    String principal = System.getProperty("principal");
+    String password = System.getProperty("password");
+    String realm = System.getProperty("realm");
+    String ldapUrl = System.getProperty("ldap_url");
+    String containerDN = System.getProperty("container_dn");
+
+    if (principal == null) {
+      principal = DEFAULT_ADMIN_PRINCIPAL;
+    }
+
+    if (password == null) {
+      password = DEFAULT_ADMIN_PASSWORD;
+    }
 
-            handler.open(kc, "KNOX.COM", "ldaps://dillwin12.knox.com:636", null);
-            Assert.fail("AmbariException not thrown for null principalContainerDn");
-        } catch (Throwable t) {
-            Assert.assertEquals(AmbariException.class, t.getClass());
-        }
+    if (realm == null) {
+      realm = DEFAULT_REALM;
     }
 
+    if (ldapUrl == null) {
+      ldapUrl = DEFAULT_LDAP_URL;
+    }
+
+    if (containerDN == null) {
+      containerDN = "";
+    }
+
+    KerberosCredential credentials = new KerberosCredential(principal, password, null);
+
+    handler.open(credentials, realm, ldapUrl, containerDN);
+
+    // does the principal already exist?
+    System.out.println("Principal exists: " + handler.principalExists("nn/c1508.ambari.apache.org"));
+
+    //create principal
+    handler.createServicePrincipal("nn/c1508.ambari.apache.org", "welcome");
+
+    //update the password
+    handler.setPrincipalPassword("nn/c1508.ambari.apache.org", "welcome10");
+
+    // remove the principal
+    // handler.removeServicePrincipal("nn/c1508.ambari.apache.org");
+
+    handler.close();
+  }
+
 }

+ 0 - 245
ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java

@@ -1,245 +0,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.
- */
-
-package org.apache.ambari.server.serveraction.kerberos;
-
-import junit.framework.Assert;
-import org.apache.ambari.server.AmbariException;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.directory.server.kerberos.shared.keytab.Keytab;
-import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.Before;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public abstract class AbstractKerberosOperationHandlerTest {
-
-  @Rule
-  public TemporaryFolder folder = new TemporaryFolder();
-
-  protected final KerberosOperationHandler handler;
-
-  protected AbstractKerberosOperationHandlerTest(KerberosOperationHandler handler) {
-    this.handler = handler;
-  }
-
-  @Before
-  public void startUp() throws AmbariException {
-    handler.open(new KerberosCredential(), "EXAMPLE.COM");
-  }
-
-  @After
-  public void cleanUp() throws AmbariException {
-    handler.close();
-  }
-
-  @Test
-  public void testCreateSecurePassword() throws Exception {
-    KerberosOperationHandler handler2 = new KerberosOperationHandler() {
-
-      @Override
-      public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
-        setAdministratorCredentials(administratorCredentials);
-        setDefaultRealm(defaultRealm);
-      }
-
-      @Override
-      public void close() throws AmbariException {
-
-      }
-
-      @Override
-      public boolean principalExists(String principal) throws AmbariException {
-        return false;
-      }
-
-      @Override
-      public Integer createServicePrincipal(String principal, String password) throws AmbariException {
-        return 0;
-      }
-
-      @Override
-      public Integer setPrincipalPassword(String principal, String password) throws AmbariException {
-        return 0;
-      }
-
-      @Override
-      public boolean removeServicePrincipal(String principal) throws AmbariException {
-        return false;
-      }
-    };
-
-    String password1 = handler.createSecurePassword();
-    Assert.assertNotNull(password1);
-    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password1.length());
-
-    String password2 = handler2.createSecurePassword();
-    Assert.assertNotNull(password2);
-    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password2.length());
-
-    // Make sure the passwords are different... if they are the same, that indicated the random
-    // number generators are generating using the same pattern and that is not secure.
-    Assert.assertFalse((password1.equals(password2)));
-  }
-
-  @Test
-  public void testCreateSecurePasswordWithSize() throws Exception {
-    String password;
-
-    password = handler.createSecurePassword(10);
-    Assert.assertNotNull(password);
-    Assert.assertEquals(10, password.length());
-
-    password = handler.createSecurePassword(0);
-    Assert.assertNotNull(password);
-    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
-
-    password = handler.createSecurePassword(-20);
-    Assert.assertNotNull(password);
-    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
-  }
-
-  @Test
-  public void testCreateKeytabFileOneAtATime() throws Exception {
-    File file = folder.newFile();
-    final String principal1 = "principal1@REALM.COM";
-    final String principal2 = "principal2@REALM.COM";
-    int count;
-
-    Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file));
-
-    Keytab keytab = Keytab.read(file);
-    Assert.assertNotNull(keytab);
-
-    List<KeytabEntry> entries = keytab.getEntries();
-    Assert.assertNotNull(entries);
-    Assert.assertFalse(entries.isEmpty());
-
-    count = entries.size();
-
-    for (KeytabEntry entry : entries) {
-      Assert.assertEquals(principal1, entry.getPrincipalName());
-    }
-
-    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
-
-    keytab = Keytab.read(file);
-    Assert.assertNotNull(keytab);
-
-    entries = keytab.getEntries();
-    Assert.assertNotNull(entries);
-    Assert.assertFalse(entries.isEmpty());
-
-    Assert.assertEquals(count * 2, entries.size());
-  }
-
-  @Test
-  public void testEnsureKeytabFileContainsNoDuplicates() throws Exception {
-    File file = folder.newFile();
-    final String principal1 = "principal1@REALM.COM";
-    final String principal2 = "principal2@REALM.COM";
-    Set<String> seenEntries = new HashSet<String>();
-
-    Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file));
-    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
-
-    // Attempt to add duplicate entries
-    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
-
-    Keytab keytab = Keytab.read(file);
-    Assert.assertNotNull(keytab);
-
-    List<KeytabEntry> entries = keytab.getEntries();
-    Assert.assertNotNull(entries);
-    Assert.assertFalse(entries.isEmpty());
-
-    for (KeytabEntry entry : entries) {
-      String seenEntry = String.format("%s|%s", entry.getPrincipalName(), entry.getKey().getKeyType().toString());
-      Assert.assertFalse(seenEntries.contains(seenEntry));
-      seenEntries.add(seenEntry);
-    }
-  }
-
-  @Test
-  public void testCreateKeytabFileExceptions() throws Exception {
-    File file = folder.newFile();
-    final String principal1 = "principal1@REALM.COM";
-
-    try {
-      handler.createKeytabFile(null, handler.createSecurePassword(), 0, file);
-      Assert.fail("AmbariException not thrown with null principal");
-    } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
-    }
-
-    try {
-      handler.createKeytabFile(principal1, null, null, file);
-      Assert.fail("AmbariException not thrown with null password");
-    } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
-    }
-
-    try {
-      handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, null);
-      Assert.fail("AmbariException not thrown with null file");
-    } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
-    }
-  }
-
-  @Test
-  public void testCreateKeytabFileFromBase64EncodedData() throws Exception {
-    File file = folder.newFile();
-    final String principal = "principal@REALM.COM";
-
-    Assert.assertTrue(handler.createKeytabFile(principal, handler.createSecurePassword(), 0, file));
-
-    FileInputStream fis = new FileInputStream(file);
-    byte[] data = new byte[(int) file.length()];
-
-    Assert.assertEquals(data.length, fis.read(data));
-    fis.close();
-
-    File f = handler.createKeytabFile(Base64.encodeBase64String(data));
-
-    try {
-      Keytab keytab = Keytab.read(f);
-      Assert.assertNotNull(keytab);
-
-      List<KeytabEntry> entries = keytab.getEntries();
-      Assert.assertNotNull(entries);
-      Assert.assertFalse(entries.isEmpty());
-
-      for (KeytabEntry entry : entries) {
-        Assert.assertEquals(principal, entry.getPrincipalName());
-      }
-    } finally {
-      if (!f.delete()) {
-        f.deleteOnExit();
-      }
-    }
-  }
-}

+ 2 - 2
ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactoryTest.java

@@ -27,13 +27,13 @@ public class KerberosOperationHandlerFactoryTest {
   @Test
   public void testForAD() {
     Assert.assertEquals(MITKerberosOperationHandler.class,
-      KerberosOperationHandlerFactory.getKerberosOperationHandler(KDCType.MIT_KDC).getClass());
+      new KerberosOperationHandlerFactory().getKerberosOperationHandler(KDCType.MIT_KDC).getClass());
   }
 
   @Test
   public void testForMIT() {
     Assert.assertEquals(ADKerberosOperationHandler.class,
-      KerberosOperationHandlerFactory.getKerberosOperationHandler(KDCType.ACTIVE_DIRECTORY).getClass());
+      new KerberosOperationHandlerFactory().getKerberosOperationHandler(KDCType.ACTIVE_DIRECTORY).getClass());
   }
 
 }

+ 194 - 12
ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java

@@ -18,42 +18,224 @@
 
 package org.apache.ambari.server.serveraction.kerberos;
 
-import org.apache.ambari.server.AmbariException;
+import junit.framework.Assert;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
-public class KerberosOperationHandlerTest extends AbstractKerberosOperationHandlerTest {
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class KerberosOperationHandlerTest {
+
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  @Test
+  public void testCreateSecurePassword() throws Exception {
+
+    KerberosOperationHandler handler1 = createHandler();
+    KerberosOperationHandler handler2 = createHandler();
+
+    String password1 = handler1.createSecurePassword();
+    Assert.assertNotNull(password1);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password1.length());
+
+    String password2 = handler2.createSecurePassword();
+    Assert.assertNotNull(password2);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password2.length());
+
+    // Make sure the passwords are different... if they are the same, that indicated the random
+    // number generators are generating using the same pattern and that is not secure.
+    Assert.assertFalse((password1.equals(password2)));
+  }
+
+  @Test
+  public void testCreateSecurePasswordWithSize() throws Exception {
+    KerberosOperationHandler handler = createHandler();
+
+    String password;
+
+    password = handler.createSecurePassword(10);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(10, password.length());
+
+    password = handler.createSecurePassword(0);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
+
+    password = handler.createSecurePassword(-20);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
+  }
+
+  @Test
+  public void testCreateKeytabFileOneAtATime() throws Exception {
+    KerberosOperationHandler handler = createHandler();
+    File file = folder.newFile();
+    final String principal1 = "principal1@REALM.COM";
+    final String principal2 = "principal2@REALM.COM";
+    int count;
+
+    Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file));
+
+    Keytab keytab = Keytab.read(file);
+    Assert.assertNotNull(keytab);
+
+    List<KeytabEntry> entries = keytab.getEntries();
+    Assert.assertNotNull(entries);
+    Assert.assertFalse(entries.isEmpty());
+
+    count = entries.size();
+
+    for (KeytabEntry entry : entries) {
+      Assert.assertEquals(principal1, entry.getPrincipalName());
+    }
+
+    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
+
+    keytab = Keytab.read(file);
+    Assert.assertNotNull(keytab);
+
+    entries = keytab.getEntries();
+    Assert.assertNotNull(entries);
+    Assert.assertFalse(entries.isEmpty());
+
+    Assert.assertEquals(count * 2, entries.size());
+  }
+
+  @Test
+  public void testEnsureKeytabFileContainsNoDuplicates() throws Exception {
+    KerberosOperationHandler handler = createHandler();
+    File file = folder.newFile();
+    final String principal1 = "principal1@REALM.COM";
+    final String principal2 = "principal2@REALM.COM";
+    Set<String> seenEntries = new HashSet<String>();
+
+    Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, file));
+    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
+
+    // Attempt to add duplicate entries
+    Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), 0, file));
+
+    Keytab keytab = Keytab.read(file);
+    Assert.assertNotNull(keytab);
+
+    List<KeytabEntry> entries = keytab.getEntries();
+    Assert.assertNotNull(entries);
+    Assert.assertFalse(entries.isEmpty());
+
+    for (KeytabEntry entry : entries) {
+      String seenEntry = String.format("%s|%s", entry.getPrincipalName(), entry.getKey().getKeyType().toString());
+      Assert.assertFalse(seenEntries.contains(seenEntry));
+      seenEntries.add(seenEntry);
+    }
+  }
+
+  @Test
+  public void testCreateKeytabFileExceptions() throws Exception {
+    KerberosOperationHandler handler = createHandler();
+    File file = folder.newFile();
+    final String principal1 = "principal1@REALM.COM";
+
+    try {
+      handler.createKeytabFile(null, handler.createSecurePassword(), 0, file);
+      Assert.fail("KerberosOperationException not thrown with null principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
+    try {
+      handler.createKeytabFile(principal1, null, null, file);
+      Assert.fail("KerberosOperationException not thrown with null password");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
+    try {
+      handler.createKeytabFile(principal1, handler.createSecurePassword(), 0, null);
+      Assert.fail("KerberosOperationException not thrown with null file");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+  }
+
+  @Test
+  public void testCreateKeytabFileFromBase64EncodedData() throws Exception {
+    KerberosOperationHandler handler = createHandler();
+    File file = folder.newFile();
+    final String principal = "principal@REALM.COM";
+
+    Assert.assertTrue(handler.createKeytabFile(principal, handler.createSecurePassword(), 0, file));
+
+    FileInputStream fis = new FileInputStream(file);
+    byte[] data = new byte[(int) file.length()];
+
+    Assert.assertEquals(data.length, fis.read(data));
+    fis.close();
+
+    File f = handler.createKeytabFile(Base64.encodeBase64String(data));
+
+    try {
+      Keytab keytab = Keytab.read(f);
+      Assert.assertNotNull(keytab);
+
+      List<KeytabEntry> entries = keytab.getEntries();
+      Assert.assertNotNull(entries);
+      Assert.assertFalse(entries.isEmpty());
+
+      for (KeytabEntry entry : entries) {
+        Assert.assertEquals(principal, entry.getPrincipalName());
+      }
+    } finally {
+      if (!f.delete()) {
+        f.deleteOnExit();
+      }
+    }
+  }
+
+  private KerberosOperationHandler createHandler() throws KerberosOperationException {
+    KerberosOperationHandler handler = new KerberosOperationHandler() {
 
-  public KerberosOperationHandlerTest() {
-    super(new KerberosOperationHandler() {
       @Override
-      public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
+      public void open(KerberosCredential administratorCredentials, String defaultRealm) throws KerberosOperationException {
         setAdministratorCredentials(administratorCredentials);
         setDefaultRealm(defaultRealm);
       }
 
       @Override
-      public void close() throws AmbariException {
+      public void close() throws KerberosOperationException {
 
       }
 
       @Override
-      public boolean principalExists(String principal) throws AmbariException {
+      public boolean principalExists(String principal) throws KerberosOperationException {
         return false;
       }
 
       @Override
-      public Integer createServicePrincipal(String principal, String password) throws AmbariException {
+      public Integer createServicePrincipal(String principal, String password) throws KerberosOperationException {
         return 0;
       }
 
       @Override
-      public Integer setPrincipalPassword(String principal, String password) throws AmbariException {
+      public Integer setPrincipalPassword(String principal, String password) throws KerberosOperationException {
         return 0;
       }
 
       @Override
-      public boolean removeServicePrincipal(String principal) throws AmbariException {
+      public boolean removeServicePrincipal(String principal) throws KerberosOperationException {
         return false;
       }
-    });
+    };
+
+    handler.open(new KerberosCredential("admin/admin", "hadoop", null), "EXAMPLE.COM");
+    return handler;
   }
-}
+}

+ 303 - 17
ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java

@@ -19,48 +19,334 @@
 package org.apache.ambari.server.serveraction.kerberos;
 
 import junit.framework.Assert;
-import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.utils.ShellCommandUtil;
+import org.easymock.EasyMockSupport;
+import org.easymock.IAnswer;
+import org.junit.Ignore;
 import org.junit.Test;
 
-public class MITKerberosOperationHandlerTest extends AbstractKerberosOperationHandlerTest {
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
 
-  public MITKerberosOperationHandlerTest() {
-    super(new MITKerberosOperationHandler());
-  }
 
+public class MITKerberosOperationHandlerTest extends EasyMockSupport {
+
+  private static final String DEFAULT_ADMIN_PRINCIPAL = "admin/admin";
+  private static final String DEFAULT_ADMIN_PASSWORD = "hadoop";
+  private static final String DEFAULT_REALM = "EXAMPLE.COM";
 
   @Test
   public void testSetPrincipalPasswordExceptions() throws Exception {
+    MITKerberosOperationHandler handler = new MITKerberosOperationHandler();
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+
+    try {
+      handler.setPrincipalPassword(DEFAULT_ADMIN_PRINCIPAL, null);
+      Assert.fail("KerberosOperationException not thrown for null password");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
     try {
-      handler.setPrincipalPassword(null, "1234");
-      Assert.fail("AmbariException not thrown for null principal");
+      handler.setPrincipalPassword(DEFAULT_ADMIN_PRINCIPAL, "");
+      Assert.fail("KerberosOperationException not thrown for empty password");
     } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
     }
 
     try {
-      handler.createServicePrincipal("", "1234");
-      Assert.fail("AmbariException not thrown for empty principal");
+      handler.setPrincipalPassword(null, DEFAULT_ADMIN_PASSWORD);
+      Assert.fail("KerberosOperationException not thrown for null principal");
     } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
+    try {
+      handler.setPrincipalPassword("", DEFAULT_ADMIN_PASSWORD);
+      Assert.fail("KerberosOperationException not thrown for empty principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
     }
   }
 
   @Test
   public void testCreateServicePrincipalExceptions() throws Exception {
+    MITKerberosOperationHandler handler = new MITKerberosOperationHandler();
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+
+    try {
+      handler.createServicePrincipal(DEFAULT_ADMIN_PRINCIPAL, null);
+      Assert.fail("KerberosOperationException not thrown for null password");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
+    try {
+      handler.createServicePrincipal(DEFAULT_ADMIN_PRINCIPAL, "");
+      Assert.fail("KerberosOperationException not thrown for empty password");
+    } catch (Throwable t) {
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
+    }
+
     try {
-      handler.createServicePrincipal(null, "1234");
-      Assert.fail("AmbariException not thrown for null principal");
+      handler.createServicePrincipal(null, DEFAULT_ADMIN_PASSWORD);
+      Assert.fail("KerberosOperationException not thrown for null principal");
     } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
     }
 
     try {
-      handler.createServicePrincipal("", "1234");
-      Assert.fail("AmbariException not thrown for empty principal");
+      handler.createServicePrincipal("", DEFAULT_ADMIN_PASSWORD);
+      Assert.fail("KerberosOperationException not thrown for empty principal");
     } catch (Throwable t) {
-      Assert.assertEquals(AmbariException.class, t.getClass());
+      Assert.assertEquals(KerberosOperationException.class, t.getClass());
     }
   }
 
+  @Test(expected = KerberosAdminAuthenticationException.class)
+  public void testTestAdministratorCredentialsIncorrectAdminPassword() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(1).anyTimes();
+            expect(result.isSuccessful()).andReturn(false).anyTimes();
+            expect(result.getStderr())
+                .andReturn("kadmin: Incorrect password while initializing kadmin interface")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test(expected = KerberosAdminAuthenticationException.class)
+  public void testTestAdministratorCredentialsIncorrectAdminPrincipal() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(1).anyTimes();
+            expect(result.isSuccessful()).andReturn(false).anyTimes();
+            expect(result.getStderr())
+                .andReturn("kadmin: Client not found in Kerberos database while initializing kadmin interface")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test(expected = KerberosRealmException.class)
+  public void testTestAdministratorCredentialsInvalidRealm() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(1).anyTimes();
+            expect(result.isSuccessful()).andReturn(false).anyTimes();
+            expect(result.getStderr())
+                .andReturn("kadmin: Missing parameters in krb5.conf required for kadmin client while initializing kadmin interface")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test(expected = KerberosKDCConnectionException.class)
+  public void testTestAdministratorCredentialsKDCConnectionException() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(1).anyTimes();
+            expect(result.isSuccessful()).andReturn(false).anyTimes();
+            expect(result.getStderr())
+                .andReturn("kadmin: Cannot contact any KDC for requested realm while initializing kadmin interface")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test
+  public void testTestAdministratorCredentialsNotFound() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(0).anyTimes();
+            expect(result.isSuccessful()).andReturn(true).anyTimes();
+            expect(result.getStderr())
+                .andReturn("get_principal: Principal does not exist while retrieving \"admin/admi@EXAMPLE.COM\".")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    Assert.assertFalse(handler.testAdministratorCredentials());
+    handler.close();
+  }
+
+  @Test
+  public void testTestAdministratorCredentialsSuccess() throws Exception {
+    MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
+        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand", String[].class))
+        .createNiceMock();
+
+    expect(handler.executeCommand(anyObject(String[].class)))
+        .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
+          @Override
+          public ShellCommandUtil.Result answer() throws Throwable {
+            ShellCommandUtil.Result result = createMock(ShellCommandUtil.Result.class);
+
+            expect(result.getExitCode()).andReturn(0).anyTimes();
+            expect(result.isSuccessful()).andReturn(true).anyTimes();
+            expect(result.getStderr())
+                .andReturn("")
+                .anyTimes();
+            expect(result.getStdout())
+                .andReturn("Authenticating as principal admin/admin with password.\n" +
+                    "Principal: admin/admin@EXAMPLE.COM\n" +
+                    "Expiration date: [never]\n" +
+                    "Last password change: Thu Jan 08 13:09:52 UTC 2015\n" +
+                    "Password expiration date: [none]\n" +
+                    "Maximum ticket life: 1 day 00:00:00\n" +
+                    "Maximum renewable life: 0 days 00:00:00\n" +
+                    "Last modified: Thu Jan 08 13:09:52 UTC 2015 (root/admin@EXAMPLE.COM)\n" +
+                    "Last successful authentication: [never]\n" +
+                    "Last failed authentication: [never]\n" +
+                    "Failed password attempts: 0\n" +
+                    "Number of keys: 6\n" +
+                    "Key: vno 1, aes256-cts-hmac-sha1-96, no salt\n" +
+                    "Key: vno 1, aes128-cts-hmac-sha1-96, no salt\n" +
+                    "Key: vno 1, des3-cbc-sha1, no salt\n" +
+                    "Key: vno 1, arcfour-hmac, no salt\n" +
+                    "Key: vno 1, des-hmac-sha1, no salt\n" +
+                    "Key: vno 1, des-cbc-md5, no salt\n" +
+                    "MKey: vno 1\n" +
+                    "Attributes:\n" +
+                    "Policy: [none]")
+                .anyTimes();
+
+            replay(result);
+            return result;
+          }
+        });
+
+    replayAll();
+
+    handler.open(new KerberosCredential(DEFAULT_ADMIN_PRINCIPAL, DEFAULT_ADMIN_PASSWORD, null), DEFAULT_REALM);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
+  @Test
+  @Ignore
+  public void testTestAdministratorCredentialsLive() throws KerberosOperationException {
+    MITKerberosOperationHandler handler = new MITKerberosOperationHandler();
+    String principal = System.getProperty("principal");
+    String password = System.getProperty("password");
+    String realm = System.getProperty("realm");
+
+    if (principal == null) {
+      principal = DEFAULT_ADMIN_PRINCIPAL;
+    }
+
+    if (password == null) {
+      password = DEFAULT_ADMIN_PASSWORD;
+    }
+
+    if (realm == null) {
+      realm = DEFAULT_REALM;
+    }
+
+    KerberosCredential credentials = new KerberosCredential(principal, password, null);
+
+    handler.open(credentials, realm);
+    handler.testAdministratorCredentials();
+    handler.close();
+  }
+
 }