ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rle...@apache.org
Subject ambari git commit: AMBARI-17356. Add the ability to perform interactive shell commands from Ambari server side actions (rlevas)
Date Thu, 23 Jun 2016 13:59:08 GMT
Repository: ambari
Updated Branches:
  refs/heads/branch-2.4 cce492169 -> efe18c24f


AMBARI-17356. Add the ability to perform interactive shell commands from Ambari server side
actions (rlevas)


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

Branch: refs/heads/branch-2.4
Commit: efe18c24f932400404f0053a8dfc8be0db1b25d1
Parents: cce4921
Author: Robert Levas <rlevas@hortonworks.com>
Authored: Thu Jun 23 09:59:00 2016 -0400
Committer: Robert Levas <rlevas@hortonworks.com>
Committed: Thu Jun 23 09:59:00 2016 -0400

----------------------------------------------------------------------
 .../kerberos/KerberosOperationHandler.java      |  23 +++-
 .../kerberos/MITKerberosOperationHandler.java   | 103 ++++++++-------
 .../ambari/server/utils/ShellCommandUtil.java   | 128 ++++++++++++++-----
 .../MITKerberosOperationHandlerTest.java        |  66 +++++-----
 .../server/utils/TestShellCommandUtil.java      |  34 ++++-
 .../test/resources/interactive_shell_test.sh    |  24 ++++
 6 files changed, 267 insertions(+), 111 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
index 7eb88bf..02cbb57 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
@@ -721,17 +721,19 @@ public abstract class KerberosOperationHandler {
    *
    * @param command an array of String value representing the command and its arguments
    * @param envp a map of string, string of environment variables
+   * @param interactiveHandler a handler to provide responses to queries from the command,
+   *                           or null if no queries are expected
    * @return a ShellCommandUtil.Result declaring the result of the operation
    * @throws KerberosOperationException
    */
-  protected ShellCommandUtil.Result executeCommand(String[] command, Map<String, String>
envp)
+  protected ShellCommandUtil.Result executeCommand(String[] command, Map<String, String>
envp, ShellCommandUtil.InteractiveHandler interactiveHandler)
       throws KerberosOperationException {
 
     if ((command == null) || (command.length == 0)) {
       return null;
     } else {
       try {
-        return ShellCommandUtil.runCommand(command, envp);
+        return ShellCommandUtil.runCommand(command, envp, interactiveHandler, false);
       } catch (IOException e) {
         String message = String.format("Failed to execute the command: %s", e.getLocalizedMessage());
         LOG.error(message, e);
@@ -752,6 +754,7 @@ 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 KerberosOperationException
+   * @see #executeCommand(String[], Map, ShellCommandUtil.InteractiveHandler)
    */
   protected ShellCommandUtil.Result executeCommand(String[] command)
           throws KerberosOperationException {
@@ -759,6 +762,22 @@ public abstract class KerberosOperationHandler {
   }
 
   /**
+   * Executes a shell command.
+   * <p/>
+   * See {@link org.apache.ambari.server.utils.ShellCommandUtil#runCommand(String[])}
+   *
+   * @param command an array of String value representing the command and its arguments
+   * @param interactiveHandler a handler to provide responses to queries from the command,
+   *                           or null if no queries are expected
+   * @return a ShellCommandUtil.Result declaring the result of the operation
+   * @throws KerberosOperationException
+   * @see #executeCommand(String[], Map, ShellCommandUtil.InteractiveHandler)
+   */
+  protected ShellCommandUtil.Result executeCommand(String[] command, ShellCommandUtil.InteractiveHandler
interactiveHandler) throws KerberosOperationException {
+    return executeCommand(command, null, interactiveHandler);
+  }
+
+  /**
    * Given a principal, attempt to create a new DeconstructedPrincipal
    *
    * @param principal a String containing the principal to deconstruct

http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
index 57a36d9..7e915fc 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
@@ -28,9 +28,10 @@ import org.slf4j.LoggerFactory;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -145,7 +146,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       return false;
     } else {
       // Create the KAdmin query to execute:
-      ShellCommandUtil.Result result = invokeKAdmin(String.format("get_principal %s", principal));
+      ShellCommandUtil.Result result = invokeKAdmin(String.format("get_principal %s", principal),
null);
 
       // If there is data from STDOUT, see if the following string exists:
       //    Principal: <principal>
@@ -186,8 +187,8 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
     } else {
       String createAttributes = getCreateAttributes();
       // Create the kdamin query:  add_principal <-randkey|-pw <password>> [<options>]
<principal>
-      ShellCommandUtil.Result result = invokeKAdmin(String.format("add_principal -pw \"%s\"
%s %s",
-          password, (createAttributes == null) ? "" : createAttributes, principal));
+      ShellCommandUtil.Result result = invokeKAdmin(String.format("add_principal %s %s",
+          (createAttributes == null) ? "" : createAttributes, principal), password);
 
       // If there is data from STDOUT, see if the following string exists:
       //    Principal "<principal>" created
@@ -233,7 +234,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       throw new KerberosOperationException("Failed to set password - no password specified");
     } else {
       // Create the kdamin query:  change_password <-randkey|-pw <password>>
<principal>
-      ShellCommandUtil.Result result = invokeKAdmin(String.format("change_password -pw \"%s\"
%s", password, principal));
+      ShellCommandUtil.Result result = invokeKAdmin(String.format("change_password %s", principal),
password);
 
       String stdOut = result.getStdout();
       String stdErr = result.getStderr();
@@ -271,7 +272,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
     if (StringUtils.isEmpty(principal)) {
       throw new KerberosOperationException("Failed to remove new principal - no principal
specified");
     } else {
-      ShellCommandUtil.Result result = invokeKAdmin(String.format("delete_principal -force
%s", principal));
+      ShellCommandUtil.Result result = invokeKAdmin(String.format("delete_principal -force
%s", principal), null);
 
       // If there is data from STDOUT, see if the following string exists:
       //    Principal "<principal>" created
@@ -335,7 +336,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       throw new KerberosOperationException("Failed to get key number for principal  - no
principal specified");
     } else {
       // Create the kdamin query:  get_principal <principal>
-      ShellCommandUtil.Result result = invokeKAdmin(String.format("get_principal %s", principal));
+      ShellCommandUtil.Result result = invokeKAdmin(String.format("get_principal %s", principal),
null);
 
       String stdOut = result.getStdout();
       if (stdOut == null) {
@@ -372,14 +373,16 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
   /**
    * Invokes the kadmin shell command to issue queries
    *
-   * @param query a String containing the query to send to the kdamin command
+   * @param query        a String containing the query to send to the kdamin command
+   * @param userPassword a String containing the user's password to set or update if necessary,
+   *                     null if not needed
    * @return a ShellCommandUtil.Result containing the result of the operation
    * @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
    */
-  protected ShellCommandUtil.Result invokeKAdmin(String query)
+  protected ShellCommandUtil.Result invokeKAdmin(String query, String userPassword)
       throws KerberosOperationException {
     if (StringUtils.isEmpty(query)) {
       throw new KerberosOperationException("Missing kadmin query");
@@ -395,6 +398,8 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
         ? null
         : administratorCredential.getPrincipal();
 
+    ShellCommandUtil.InteractiveHandler interactiveHandler = null;
+
     if (StringUtils.isEmpty(adminPrincipal)) {
       // Set the kdamin interface to be kadmin.local
       if (StringUtils.isEmpty(executableKadminLocal)) {
@@ -402,6 +407,10 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       }
 
       command.add(executableKadminLocal);
+
+      if (userPassword != null) {
+        interactiveHandler = new InteractivePasswordHandler(null, userPassword);
+      }
     } else {
       if (StringUtils.isEmpty(executableKadmin)) {
         throw new KerberosOperationException("No path for kadmin is available - this KerberosOperationHandler
may not have been opened.");
@@ -412,7 +421,7 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       command.add(executableKadmin);
 
       // Add explicit KDC admin host, if available
-      if (getAdminServerHost() != null) {
+      if (!StringUtils.isEmpty(getAdminServerHost())) {
         command.add("-s");
         command.add(getAdminServerHost());
       }
@@ -422,9 +431,9 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
       command.add(adminPrincipal);
 
       if (!ArrayUtils.isEmpty(adminPassword)) {
-        // Add password for administrative principal
-        command.add("-w");
-        command.add(String.valueOf(adminPassword));
+        interactiveHandler = new InteractivePasswordHandler(String.valueOf(adminPassword),
userPassword);
+      } else if (userPassword != null) {
+        interactiveHandler = new InteractivePasswordHandler(null, userPassword);
       }
     }
 
@@ -439,14 +448,14 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
     command.add(query);
 
     if (LOG.isDebugEnabled()) {
-      LOG.debug(String.format("Executing: %s", createCleanCommand(command)));
+      LOG.debug(String.format("Executing: %s", command));
     }
 
-    result = executeCommand(command.toArray(new String[command.size()]));
+    result = executeCommand(command.toArray(new String[command.size()]), null, interactiveHandler);
 
     if (!result.isSuccessful()) {
       String message = String.format("Failed to execute kadmin:\n\tCommand: %s\n\tExitCode:
%s\n\tSTDOUT: %s\n\tSTDERR: %s",
-          createCleanCommand(command), result.getExitCode(), result.getStdout(), result.getStderr());
+          command, result.getExitCode(), result.getStdout(), result.getStderr());
       LOG.warn(message);
 
       // Test STDERR to see of any "expected" error conditions were encountered...
@@ -477,41 +486,43 @@ public class MITKerberosOperationHandler extends KerberosOperationHandler
{
   }
 
   /**
-   * Build the kadmin command string, replacing administrator password with "********"
-   *
-   * @param command a List of items making up the command
-   * @return the cleaned command string
+   * InteractivePasswordHandler is a {@link org.apache.ambari.server.utils.ShellCommandUtil.InteractiveHandler}
+   * implementation that answers queries from kadmin or kdamin.local command for the admin
and/or user
+   * passwords.
    */
-  private String createCleanCommand(List<String> command) {
-    StringBuilder cleanedCommand = new StringBuilder();
-    Iterator<String> iterator = command.iterator();
-
-    if (iterator.hasNext()) {
-      cleanedCommand.append(iterator.next());
-    }
-
-    while (iterator.hasNext()) {
-      String part = iterator.next();
-
-      cleanedCommand.append(' ');
-
-      if (part.contains(" ")) {
-        cleanedCommand.append('"');
-        cleanedCommand.append(part);
-        cleanedCommand.append('"');
-      } else {
-        cleanedCommand.append(part);
+  protected static class InteractivePasswordHandler implements ShellCommandUtil.InteractiveHandler
{
+    /**
+     * The queue of responses to return
+     */
+    private final Queue<String> responses = new LinkedList<String>();
+
+
+    /**
+     * Constructor.
+     *
+     * @param adminPassword the KDC administrator's password (optional)
+     * @param userPassword  the user's password (optional)
+     */
+    public InteractivePasswordHandler(String adminPassword, String userPassword) {
+
+      if (adminPassword != null) {
+        responses.offer(adminPassword);
       }
 
-      if ("-w".equals(part)) {
-        // Skip the password and use "********" instead
-        if (iterator.hasNext()) {
-          iterator.next();
-        }
-        cleanedCommand.append(" ********");
+      if (userPassword != null) {
+        responses.offer(userPassword);
+        responses.offer(userPassword);  // Add a 2nd time for the password "confirmation"
request
       }
     }
 
-    return cleanedCommand.toString();
+    @Override
+    public boolean done() {
+      return responses.size() == 0;
+    }
+
+    @Override
+    public String getResponse(String query) {
+      return responses.poll();
+    }
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java
b/ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java
index c5c4d34..39981ef 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/utils/ShellCommandUtil.java
@@ -21,9 +21,11 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.util.ArrayList;
 import java.util.Map;
 
@@ -52,12 +54,12 @@ public class ShellCommandUtil {
 
   }
 
-  public static String hideOpenSslPassword(String command){
+  public static String hideOpenSslPassword(String command) {
     int start;
-    if(command.contains(PASS_TOKEN)){
-      start = command.indexOf(PASS_TOKEN)+PASS_TOKEN.length();
-    } else if (command.contains(KEY_TOKEN)){
-      start = command.indexOf(KEY_TOKEN)+KEY_TOKEN.length();
+    if (command.contains(PASS_TOKEN)) {
+      start = command.indexOf(PASS_TOKEN) + PASS_TOKEN.length();
+    } else if (command.contains(KEY_TOKEN)) {
+      start = command.indexOf(KEY_TOKEN) + KEY_TOKEN.length();
     } else {
       return command;
     }
@@ -67,7 +69,7 @@ public class ShellCommandUtil {
 
   public static String getOpenSslCommandResult(String command, int exitCode) {
     return new StringBuilder().append("Command ").append(hideOpenSslPassword(command)).append("
was finished with exit code: ")
-            .append(exitCode).append(" - ").append(getOpenSslExitCodeDescription(exitCode)).toString();
+        .append(exitCode).append(" - ").append(getOpenSslExitCodeDescription(exitCode)).toString();
   }
 
   private static String getOpenSslExitCodeDescription(int exitCode) {
@@ -96,19 +98,27 @@ public class ShellCommandUtil {
   }
 
 
-  /** Set to true when run on Windows platforms */
+  /**
+   * Set to true when run on Windows platforms
+   */
   public static final boolean WINDOWS
-          = System.getProperty("os.name").startsWith("Windows");
+      = System.getProperty("os.name").startsWith("Windows");
 
-  /** Set to true when run on Linux platforms */
+  /**
+   * Set to true when run on Linux platforms
+   */
   public static final boolean LINUX
-          = System.getProperty("os.name").startsWith("Linux");
+      = System.getProperty("os.name").startsWith("Linux");
 
-  /** Set to true when run on Mac OS platforms */
+  /**
+   * Set to true when run on Mac OS platforms
+   */
   public static final boolean MAC
-          = System.getProperty("os.name").startsWith("Mac");
+      = System.getProperty("os.name").startsWith("Mac");
 
-  /** Set to true when run if platform is detected to be UNIX compatible */
+  /**
+   * Set to true when run if platform is detected to be UNIX compatible
+   */
   public static final boolean UNIX_LIKE = LINUX || MAC;
 
   /**
@@ -126,6 +136,7 @@ public class ShellCommandUtil {
   /**
    * Gets file permissions on Linux systems.
    * Under Windows/Mac, command always returns MASK_EVERYBODY_RWX
+   *
    * @param path
    */
   public static String getUnixFilePermissions(String path) {
@@ -141,7 +152,7 @@ public class ShellCommandUtil {
       }
     } else {
       LOG.debug(String.format("Not performing stat -s \"%%a\" command on file %s " +
-              "because current OS is not Linux. Returning 777", path));
+          "because current OS is not Linux. Returning 777", path));
     }
     return result.trim();
   }
@@ -149,6 +160,7 @@ public class ShellCommandUtil {
   /**
    * Sets file permissions to a given value on Linux systems.
    * On Windows/Mac, command is silently ignored
+   *
    * @param mode
    * @param path
    */
@@ -164,7 +176,7 @@ public class ShellCommandUtil {
       }
     } else {
       LOG.debug(String.format("Not performing chmod %s command for file %s " +
-              "because current OS is not Linux ", mode, path));
+          "because current OS is not Linux ", mode, path));
     }
   }
 
@@ -183,7 +195,7 @@ public class ShellCommandUtil {
         path
     };
 
-    return runCommand(command, null, sudo);
+    return runCommand(command, null, null, sudo);
   }
 
   /**
@@ -198,8 +210,7 @@ public class ShellCommandUtil {
     // If this directory already exists, do not try to create it
     if (pathExists(directoryPath, sudo).isSuccessful()) {
       return new Result(0, "The directory already exists, skipping.", ""); // Success!
-    }
-    else {
+    } else {
       ArrayList<String> command = new ArrayList<String>();
 
       command.add("/bin/mkdir");
@@ -210,7 +221,7 @@ public class ShellCommandUtil {
 
       command.add(directoryPath);
 
-    return runCommand(command.toArray(new String[command.size()]), null, sudo);
+      return runCommand(command.toArray(new String[command.size()]), null, null, sudo);
     }
   }
 
@@ -245,30 +256,31 @@ public class ShellCommandUtil {
     command.add(srcFile);
     command.add(destFile);
 
-    return runCommand(command.toArray(new String[command.size()]), null, sudo);
+    return runCommand(command.toArray(new String[command.size()]), null, null, sudo);
   }
 
   /**
    * Runs a command with a given set of environment variables
    *
-   * @param args a String[] of the command and its arguments
-   * @param vars a Map of String,String setting an environment variable to run the command
with
-   * @param sudo true to execute the command using sudo (ambari-sudo); otherwise false
+   * @param args               a String[] of the command and its arguments
+   * @param vars               a Map of String,String setting an environment variable to
run the command with
+   * @param interactiveHandler a handler to provide responses to queries from the command,
+   *                           or null if no queries are expected
+   * @param sudo               true to execute the command using sudo (ambari-sudo); otherwise
false
    * @return Result
    * @throws IOException
    * @throws InterruptedException
    */
-  public static Result runCommand(String [] args, Map<String, String> vars, boolean
sudo)
+  public static Result runCommand(String[] args, Map<String, String> vars, InteractiveHandler
interactiveHandler, boolean sudo)
       throws IOException, InterruptedException {
 
     String[] processArgs;
 
-    if(sudo) {
+    if (sudo) {
       processArgs = new String[args.length + 1];
       processArgs[0] = AMBARI_SUDO;
       System.arraycopy(args, 0, processArgs, 1, args.length);
-    }
-    else {
+    } else {
       processArgs = args;
     }
 
@@ -292,10 +304,38 @@ public class ShellCommandUtil {
     } else {
       process = builder.start();
     }
+
+    // If an interactiveHandler is supplied ask it for responses to queries from the command
+    // using the InputStream and OutputStream retrieved from the Process object. Use the
remainder
+    // of the data from the InputStream as the data for stdout.
+    InputStream inputStream = process.getInputStream();
+    if (interactiveHandler != null) {
+      BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
+      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+
+      while (!interactiveHandler.done()) {
+        StringBuilder query = new StringBuilder();
+
+        while (reader.ready()) {
+          query.append((char) reader.read());
+        }
+
+        String response = interactiveHandler.getResponse(query.toString());
+
+        if (response != null) {
+          writer.write(response);
+          writer.newLine();
+          writer.flush();
+        }
+      }
+
+      writer.close();
+    }
+
     //TODO: not sure whether output buffering will work properly
     // if command output is too intensive
     process.waitFor();
-    String stdout = streamToString(process.getInputStream());
+    String stdout = streamToString(inputStream);
     String stderr = streamToString(process.getErrorStream());
     int exitCode = process.exitValue();
     return new Result(exitCode, stdout, stderr);
@@ -312,18 +352,19 @@ public class ShellCommandUtil {
    */
   public static Result runCommand(String[] args, Map<String, String> vars)
       throws IOException, InterruptedException {
-    return runCommand(args, vars, false);
+    return runCommand(args, vars, null, false);
   }
 
   /**
    * Run a command
+   *
    * @param args A String[] of the command and its arguments
    * @return Result
    * @throws IOException
    * @throws InterruptedException
    */
-  public static Result runCommand(String [] args) throws IOException,
-          InterruptedException {
+  public static Result runCommand(String[] args) throws IOException,
+      InterruptedException {
     return runCommand(args, null);
   }
 
@@ -366,4 +407,29 @@ public class ShellCommandUtil {
       return exitCode == 0;
     }
   }
+
+  /**
+   * InteractiveHandler is a handler for interactive sessions with command line commands.
+   * <p>
+   * Classes should implement this interface if there is a need to supply responses to queries
from
+   * the executed command (interactively, via stdin).
+   */
+  public interface InteractiveHandler {
+
+    /**
+     * Indicates whether this {@link InteractiveHandler} expects more queries (<code>true</code>
+     * or not (<code>false</code>)
+     *
+     * @return true if more queries are expected; false otherwise
+     */
+    boolean done();
+
+    /**
+     * Gnven a query, returns the relative response to send to the shell command (via stdin)
+     *
+     * @param query a string containing the query that needs a response
+     * @return a string or null if no response is needed
+     */
+    String getResponse(String query);
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
index 36a4381..6fd30ee 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
@@ -24,7 +24,6 @@ import com.google.inject.Injector;
 
 import junit.framework.Assert;
 
-import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
 import org.apache.ambari.server.state.Clusters;
@@ -41,11 +40,7 @@ import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
+import static org.easymock.EasyMock.*;
 
 public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTest {
 
@@ -55,6 +50,8 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
 
   private static Injector injector;
 
+  private static Method methodExecuteCommand;
+
   private static final Map<String, String> KERBEROS_ENV_MAP = new HashMap<String,
String>() {
     {
       put(MITKerberosOperationHandler.KERBEROS_ENV_ENCRYPTION_TYPES, null);
@@ -66,7 +63,7 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   };
 
   @BeforeClass
-  public static void beforeClass() throws AmbariException {
+  public static void beforeClass() throws Exception {
     injector = Guice.createInjector(new AbstractModule() {
       @Override
       protected void configure() {
@@ -79,6 +76,12 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
         bind(OsFamily.class).toInstance(EasyMock.createNiceMock(OsFamily.class));
       }
     });
+
+    methodExecuteCommand = KerberosOperationHandler.class.getDeclaredMethod(
+        "executeCommand",
+        String[].class,
+        Map.class,
+        ShellCommandUtil.InteractiveHandler.class);
   }
 
   @Test
@@ -120,10 +123,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosPrincipalDoesNotExistException.class)
   public void testSetPrincipalPasswordPrincipalDoesNotExist() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -152,9 +155,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
 
   @Test
   public void testCreateServicePrincipal_AdditionalAttributes() throws Exception {
-    Method invokeKAdmin = MITKerberosOperationHandler.class.getDeclaredMethod("invokeKAdmin",
String.class);
+    Method invokeKAdmin = MITKerberosOperationHandler.class.getDeclaredMethod("invokeKAdmin",
String.class, String.class);
 
-    Capture<? extends String> query = new Capture<String>();
+    Capture<? extends String> query = newCapture();
+    Capture<? extends String> password = newCapture();
 
     ShellCommandUtil.Result result1 = createNiceMock(ShellCommandUtil.Result.class);
     expect(result1.getStderr()).andReturn("").anyTimes();
@@ -168,8 +172,8 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
         .addMockedMethod(invokeKAdmin)
         .createStrictMock();
 
-    expect(handler.invokeKAdmin(capture(query))).andReturn(result1).once();
-    expect(handler.invokeKAdmin("get_principal " + DEFAULT_ADMIN_PRINCIPAL)).andReturn(result2).once();
+    expect(handler.invokeKAdmin(capture(query), anyString())).andReturn(result1).once();
+    expect(handler.invokeKAdmin("get_principal " + DEFAULT_ADMIN_PRINCIPAL, null)).andReturn(result2).once();
 
     replay(handler, result1, result2);
 
@@ -184,10 +188,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosPrincipalAlreadyExistsException.class)
   public void testCreatePrincipalPrincipalAlreadyNotExists() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -251,10 +255,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosAdminAuthenticationException.class)
   public void testTestAdministratorCredentialsIncorrectAdminPassword() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -284,10 +288,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosAdminAuthenticationException.class)
   public void testTestAdministratorCredentialsIncorrectAdminPrincipal() throws Exception
{
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -317,10 +321,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosRealmException.class)
   public void testTestAdministratorCredentialsInvalidRealm() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -350,10 +354,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosRealmException.class)
   public void testTestAdministratorCredentialsInvalidRealm2() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -383,10 +387,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosKDCConnectionException.class)
   public void testTestAdministratorCredentialsKDCConnectionException() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -416,10 +420,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test(expected = KerberosKDCConnectionException.class)
   public void testTestAdministratorCredentialsKDCConnectionException2() throws Exception
{
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -449,10 +453,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test
   public void testTestAdministratorCredentialsNotFound() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {
@@ -482,10 +486,10 @@ public class MITKerberosOperationHandlerTest extends KerberosOperationHandlerTes
   @Test
   public void testTestAdministratorCredentialsSuccess() throws Exception {
     MITKerberosOperationHandler handler = createMockBuilder(MITKerberosOperationHandler.class)
-        .addMockedMethod(KerberosOperationHandler.class.getDeclaredMethod("executeCommand",
String[].class))
+        .addMockedMethod(methodExecuteCommand)
         .createNiceMock();
 
-    expect(handler.executeCommand(anyObject(String[].class)))
+    expect(handler.executeCommand(anyObject(String[].class), anyObject(Map.class), anyObject(MITKerberosOperationHandler.InteractivePasswordHandler.class)))
         .andAnswer(new IAnswer<ShellCommandUtil.Result>() {
           @Override
           public ShellCommandUtil.Result answer() throws Throwable {

http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java
b/ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java
index a944d29..34ac91f 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/utils/TestShellCommandUtil.java
@@ -112,7 +112,39 @@ public class TestShellCommandUtil {
       // Skipping this test under Windows/Mac
     }
   }
-  
+
+  @Test
+  public void testRunInteractiveCommand() throws Exception {
+
+    ShellCommandUtil.InteractiveHandler interactiveHandler = new ShellCommandUtil.InteractiveHandler()
{
+      boolean done = false;
+
+      @Override
+      public boolean done() {
+        return done;
+      }
+
+      @Override
+      public String getResponse(String query) {
+        if(query.contains("Arg1")) {
+          return "a1";
+        }
+        else if(query.contains("Arg2")) {
+          done = true; // this is the last expected prompt
+          return "a2";
+        }
+        else {
+          return null;
+        }
+      }
+    };
+
+    ShellCommandUtil.Result result = ShellCommandUtil.runCommand(new String[]{"./src/test/resources/interactive_shell_test.sh"},
null, interactiveHandler, false);
+    Assert.assertEquals(0, result.getExitCode());
+    Assert.assertTrue(result.isSuccessful());
+    Assert.assertEquals("a1\na2\n", result.getStdout());
+  }
+
   @Test
   public void testHideOpenSslPassword(){
     String command_pass = "openssl ca -config ca.config -in agent_hostname1.csr -out "+

http://git-wip-us.apache.org/repos/asf/ambari/blob/efe18c24/ambari-server/src/test/resources/interactive_shell_test.sh
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/resources/interactive_shell_test.sh b/ambari-server/src/test/resources/interactive_shell_test.sh
new file mode 100755
index 0000000..51f667d
--- /dev/null
+++ b/ambari-server/src/test/resources/interactive_shell_test.sh
@@ -0,0 +1,24 @@
+# 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.
+#!/usr/bin/env bash
+
+echo "Arg1: "
+read a1
+
+echo -n "Arg2: "
+read a2
+
+echo $a1
+echo $a2


Mime
View raw message