Merge branch 'master' into 0.14.6
diff --git a/heron/common/src/java/com/twitter/heron/common/utils/logging/LoggingHelper.java b/heron/common/src/java/com/twitter/heron/common/utils/logging/LoggingHelper.java
index d4f76aa..b245eb0 100644
--- a/heron/common/src/java/com/twitter/heron/common/utils/logging/LoggingHelper.java
+++ b/heron/common/src/java/com/twitter/heron/common/utils/logging/LoggingHelper.java
@@ -34,7 +34,7 @@
  */
 public final class LoggingHelper {
   public static final String FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format";
-  public static final String DEFAULT_FORMAT = "[%1$tF %1$tT %1$tz] %3$s %4$s:  %5$s %6$s %n";
+  public static final String DEFAULT_FORMAT = "[%1$tF %1$tT %1$tz] [%4$s] %3$s: %5$s %6$s %n";
 
   private LoggingHelper() {
   }
diff --git a/heron/common/src/python/utils/log.py b/heron/common/src/python/utils/log.py
index 01c5a96..533c698 100644
--- a/heron/common/src/python/utils/log.py
+++ b/heron/common/src/python/utils/log.py
@@ -24,9 +24,9 @@
 # time formatter - date - time - UTC offset
 # e.g. "08/16/1988 21:30:00 +1030"
 # see time formatter documentation for more
-date_format = "%m/%d/%Y %H:%M:%S %z"
+date_format = "%Y-%m-%d %H:%M:%S %z"
 
-def configure(level=logging.INFO, logfile=None, with_time=False):
+def configure(level=logging.INFO, logfile=None):
   """ Configure logger which dumps log on terminal
 
   :param level: logging level: info, warning, verbose...
@@ -46,20 +46,14 @@
 
   # if logfile is specified, FileHandler is used
   if logfile is not None:
-    if with_time:
-      log_format = "%(asctime)s:%(levelname)s: %(message)s"
-    else:
-      log_format = "%(levelname)s: %(message)s"
+    log_format = "[%(asctime)s] [%(levelname)s]: %(message)s"
     formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
     file_handler = logging.FileHandler(logfile)
     file_handler.setFormatter(formatter)
     Log.addHandler(file_handler)
   # otherwise, use StreamHandler to output to stream (stdout, stderr...)
   else:
-    if with_time:
-      log_format = "%(log_color)s%(levelname)s:%(reset)s %(asctime)s %(message)s"
-    else:
-      log_format = "%(log_color)s%(levelname)s:%(reset)s %(message)s"
+    log_format = "[%(asctime)s] %(log_color)s[%(levelname)s]%(reset)s: %(message)s"
     # pylint: disable=redefined-variable-type
     formatter = colorlog.ColoredFormatter(fmt=log_format, datefmt=date_format)
     stream_handler = logging.StreamHandler()
@@ -76,7 +70,7 @@
   logging.basicConfig()
 
   root_logger = logging.getLogger()
-  log_format = "%(asctime)s:%(levelname)s:%(filename)s: %(message)s"
+  log_format = "[%(asctime)s] [%(levelname)s] %(filename)s: %(message)s"
 
   root_logger.setLevel(level)
   handler = RotatingFileHandler(logfile, maxBytes=max_bytes, backupCount=max_files)
@@ -89,7 +83,7 @@
       root_logger.debug("Removing StreamHandler: " + str(handler))
       root_logger.handlers.remove(handler)
 
-def set_logging_level(cl_args, with_time=False):
+def set_logging_level(cl_args):
   """simply set verbose level based on command-line args
 
   :param cl_args: CLI arguments
@@ -97,7 +91,7 @@
   :return: None
   :rtype: None
   """
-  if cl_args['verbose']:
-    configure(logging.DEBUG, with_time=with_time)
+  if 'verbose' in cl_args and cl_args['verbose']:
+    configure(logging.DEBUG)
   else:
-    configure(logging.INFO, with_time=with_time)
+    configure(logging.INFO)
diff --git a/heron/executor/src/python/heron_executor.py b/heron/executor/src/python/heron_executor.py
index e70d388..4e58588 100755
--- a/heron/executor/src/python/heron_executor.py
+++ b/heron/executor/src/python/heron_executor.py
@@ -735,7 +735,7 @@
   def setup(shardid):
     # Redirect stdout and stderr to files in append mode
     # The filename format is heron-executor-<container_id>.stdxxx
-    log.configure(logfile='heron-executor-%s.stdout' % shardid, with_time=True)
+    log.configure(logfile='heron-executor-%s.stdout' % shardid)
 
     Log.info('Set up process group; executor becomes leader')
     os.setpgrp() # create new process group, become its leader
diff --git a/heron/scheduler-core/src/java/com/twitter/heron/scheduler/SubmitterMain.java b/heron/scheduler-core/src/java/com/twitter/heron/scheduler/SubmitterMain.java
index f95f656..5b68273 100644
--- a/heron/scheduler-core/src/java/com/twitter/heron/scheduler/SubmitterMain.java
+++ b/heron/scheduler-core/src/java/com/twitter/heron/scheduler/SubmitterMain.java
@@ -375,6 +375,14 @@
    *
    */
   public void submitTopology() throws TopologySubmissionException {
+    // build primary runtime config first
+    Config primaryRuntime = Config.newBuilder()
+          .putAll(LauncherUtils.getInstance().createPrimaryRuntime(topology)).build();
+    // call launcher directly here if in dry-run mode
+    if (Context.dryRun(config)) {
+      callLauncherRunner(primaryRuntime);
+      return;
+    }
     // 1. Do prepare work
     // create an instance of state manager
     String statemgrClass = Context.stateManagerClass(config);
@@ -414,16 +422,6 @@
 
     // Put it in a try block so that we can always clean resources
     try {
-      // Build the basic runtime config
-      Config primaryRuntime = Config.newBuilder()
-          .putAll(LauncherUtils.getInstance().createPrimaryRuntime(topology)).build();
-
-      // Bypass validation and upload if in dry-run mode
-      if (Context.dryRun(config)) {
-        callLauncherRunner(primaryRuntime);
-        return;
-      }
-
       // initialize the state manager
       statemgr.initialize(config);
 
@@ -435,7 +433,6 @@
 
       LOG.log(Level.FINE, "Topology {0} to be submitted", topology.getName());
 
-      // First, create the basic runtime config to generate the packing plan
       Config runtimeWithoutPackageURI = Config.newBuilder()
           .putAll(primaryRuntime)
           .putAll(LauncherUtils.getInstance().createAdaptorRuntime(adaptor))
diff --git a/heron/scheduler-core/src/java/com/twitter/heron/scheduler/utils/SchedulerUtils.java b/heron/scheduler-core/src/java/com/twitter/heron/scheduler/utils/SchedulerUtils.java
index 3aab25b..78c0591 100644
--- a/heron/scheduler-core/src/java/com/twitter/heron/scheduler/utils/SchedulerUtils.java
+++ b/heron/scheduler-core/src/java/com/twitter/heron/scheduler/utils/SchedulerUtils.java
@@ -281,7 +281,7 @@
 
     Scheduler.SchedulerLocation location = builder.build();
 
-    LOG.log(Level.INFO, "Setting SchedulerLocation: {0}", location);
+    LOG.log(Level.INFO, "Setting Scheduler locations: {0}", location);
     SchedulerStateManagerAdaptor statemgr = Runtime.schedulerStateManagerAdaptor(runtime);
     Boolean result =
         statemgr.setSchedulerLocation(location, Runtime.topologyName(runtime));
diff --git a/heron/schedulers/src/java/com/twitter/heron/scheduler/aurora/AuroraCLIController.java b/heron/schedulers/src/java/com/twitter/heron/scheduler/aurora/AuroraCLIController.java
index c559704..6840720 100644
--- a/heron/schedulers/src/java/com/twitter/heron/scheduler/aurora/AuroraCLIController.java
+++ b/heron/schedulers/src/java/com/twitter/heron/scheduler/aurora/AuroraCLIController.java
@@ -126,7 +126,7 @@
     StringBuilder stdout = new StringBuilder();
     StringBuilder stderr = new StringBuilder();
     int status =
-        ShellUtils.runProcess(auroraCmd.toArray(new String[auroraCmd.size()]), stdout, stderr);
+        ShellUtils.runProcess(auroraCmd.toArray(new String[auroraCmd.size()]), stderr);
 
     if (status != 0) {
       LOG.severe(String.format(
diff --git a/heron/schedulers/src/java/com/twitter/heron/scheduler/slurm/SlurmController.java b/heron/schedulers/src/java/com/twitter/heron/scheduler/slurm/SlurmController.java
index 3ee754d..acce7dc 100644
--- a/heron/schedulers/src/java/com/twitter/heron/scheduler/slurm/SlurmController.java
+++ b/heron/schedulers/src/java/com/twitter/heron/scheduler/slurm/SlurmController.java
@@ -71,11 +71,8 @@
     String[] slurmCmdArray = slurmCmd.toArray(new String[0]);
     LOG.log(Level.INFO, "Executing job [" + topologyWorkingDirectory + "]:",
         Arrays.toString(slurmCmdArray));
-    StringBuilder stdout = new StringBuilder();
     StringBuilder stderr = new StringBuilder();
-    boolean ret = runProcess(topologyWorkingDirectory, slurmCmdArray, stdout, stderr);
-    LOG.log(Level.FINE, "Stdout for Slurm script: ", stdout);
-    LOG.log(Level.FINE, "Stderror for Slurm script: ", stderr);
+    boolean ret = runProcess(topologyWorkingDirectory, slurmCmdArray, stderr);
     return ret;
   }
 
@@ -124,9 +121,9 @@
    * This is for unit testing
    */
   protected boolean runProcess(String topologyWorkingDirectory, String[] slurmCmd,
-                         StringBuilder stdout, StringBuilder stderr) {
+                               StringBuilder stderr) {
     File file = topologyWorkingDirectory == null ? null : new File(topologyWorkingDirectory);
-    return 0 == ShellUtils.runSyncProcess(isVerbose, false, slurmCmd, stdout, stderr, file);
+    return 0 == ShellUtils.runSyncProcess(false, slurmCmd, stderr, file);
   }
 
   /**
@@ -140,7 +137,7 @@
     List<String> jobIdFileContent = readFromFile(jobIdFile);
     if (jobIdFileContent.size() > 0) {
       String[] slurmCmd = new String[]{"scancel", jobIdFileContent.get(0)};
-      return runProcess(null, slurmCmd, new StringBuilder(), new StringBuilder());
+      return runProcess(null, slurmCmd, new StringBuilder());
     } else {
       LOG.log(Level.SEVERE, "Failed to read the Slurm Job id from file: {0}", jobIdFile);
       return false;
diff --git a/heron/schedulers/tests/java/com/twitter/heron/scheduler/slurm/SlurmControllerTest.java b/heron/schedulers/tests/java/com/twitter/heron/scheduler/slurm/SlurmControllerTest.java
index a5ba594..0770456 100644
--- a/heron/schedulers/tests/java/com/twitter/heron/scheduler/slurm/SlurmControllerTest.java
+++ b/heron/schedulers/tests/java/com/twitter/heron/scheduler/slurm/SlurmControllerTest.java
@@ -65,15 +65,13 @@
 
     // Failed
     Mockito.doReturn(false).when(controller).runProcess(Matchers.anyString(),
-        Matchers.any(String[].class), Matchers.any(StringBuilder.class),
-        Matchers.any(StringBuilder.class));
+        Matchers.any(String[].class), Matchers.any(StringBuilder.class));
     Assert.assertFalse(controller.createJob(slurmFileName, "slurm",
         expectedCommand, WORKING_DIRECTORY, 1));
 
     // Happy path
     Mockito.doReturn(true).when(controller).runProcess(Matchers.anyString(),
-        Matchers.any(String[].class), Matchers.any(StringBuilder.class),
-        Matchers.any(StringBuilder.class));
+        Matchers.any(String[].class), Matchers.any(StringBuilder.class));
     Assert.assertTrue(controller.createJob(slurmFileName, "slurm",
         expectedCommand, WORKING_DIRECTORY, 1));
   }
@@ -87,14 +85,12 @@
     Assert.assertFalse(controller.killJob(jobIdFile));
     // fail if process creation fails
     Mockito.doReturn(false).when(controller).runProcess(Matchers.anyString(),
-        Matchers.any(String[].class), Matchers.any(StringBuilder.class),
-        Matchers.any(StringBuilder.class));
+        Matchers.any(String[].class), Matchers.any(StringBuilder.class));
     Mockito.doReturn(jobIds).when(controller).readFromFile(jobIdFile);
     Assert.assertFalse(controller.killJob(jobIdFile));
     // happy path
     Mockito.doReturn(true).when(controller).runProcess(Matchers.anyString(),
-        Matchers.any(String[].class), Matchers.any(StringBuilder.class),
-        Matchers.any(StringBuilder.class));
+        Matchers.any(String[].class), Matchers.any(StringBuilder.class));
     Mockito.doReturn(jobIds).when(controller).readFromFile(jobIdFile);
     Assert.assertTrue(controller.killJob(jobIdFile));
   }
diff --git a/heron/spi/src/java/com/twitter/heron/spi/utils/ShellUtils.java b/heron/spi/src/java/com/twitter/heron/spi/utils/ShellUtils.java
index 94aa6d7..160e990 100644
--- a/heron/spi/src/java/com/twitter/heron/spi/utils/ShellUtils.java
+++ b/heron/spi/src/java/com/twitter/heron/spi/utils/ShellUtils.java
@@ -14,6 +14,7 @@
 
 package com.twitter.heron.spi.utils;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -21,7 +22,6 @@
 import java.io.Reader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -60,42 +60,64 @@
     return builder.toString();
   }
 
-  public static int runProcess(String[] cmdline, StringBuilder stdout, StringBuilder stderr) {
-    return runSyncProcess(false, false, cmdline, stdout, stderr, null);
+  public static int runProcess(String[] cmdline, StringBuilder outputBuilder) {
+    return runSyncProcess(false, cmdline, outputBuilder, null);
   }
 
   public static int runProcess(
-      boolean verbose, String cmdline, StringBuilder stdout, StringBuilder stderr) {
-    return runSyncProcess(verbose, false, splitTokens(cmdline), stdout, stderr, null);
+      String cmdline, StringBuilder outputBuilder) {
+    return runSyncProcess(false, splitTokens(cmdline), outputBuilder, null);
   }
 
   public static int runSyncProcess(
-      boolean verbose, boolean isInheritIO, String[] cmdline, StringBuilder stdout,
-      StringBuilder stderr, File workingDirectory) {
-    return runSyncProcess(isInheritIO, cmdline, stdout, stderr, workingDirectory,
+      boolean isInheritIO, String[] cmdline, StringBuilder outputBuilder, File workingDirectory) {
+    return runSyncProcess(isInheritIO, cmdline, outputBuilder, workingDirectory,
         new HashMap<String, String>());
   }
 
   /**
-   * Start a daemon thread to read data from "input" to "out".
+   * Read and print line from input, and save each line in a string builder
+   * line read from input is printed to stderr directly instead of using LOG (which will
+   * add redundant timestamp and class name info).
+   *
+   * This method does not start the thread. Instead, caller should start this thread.
+   *
+   * @param input input stream
+   * @param processOutputStringBuilder string builder used to save input lines
+   * @return thread
    */
-  private static Thread asyncProcessStream(final InputStream input, final StringBuilder out) {
+  private static Thread createAsyncStreamThread(final InputStream input,
+                                                final StringBuilder processOutputStringBuilder) {
     Thread thread = new Thread() {
       @Override
       public void run() {
-        try {
-          out.append(inputstreamToString(input));
-        } finally {
+        // do not buffer
+        LOG.log(Level.INFO, "Process output (stdout+stderr):");
+        BufferedReader reader = new BufferedReader(new InputStreamReader(input), 1);
+        while (true) {
+          String line = null;
           try {
-            input.close();
+            line = reader.readLine();
           } catch (IOException e) {
-            LOG.log(Level.WARNING, "Failed to close the input stream", e);
+            LOG.log(Level.SEVERE, "Error when reading line from subprocess", e);
           }
+          if (line == null) {
+            break;
+          } else {
+            System.err.println(line);
+            if (processOutputStringBuilder != null) {
+              processOutputStringBuilder.append(line);
+            }
+          }
+        }
+        try {
+          input.close();
+        } catch (IOException e) {
+          LOG.log(Level.WARNING, "Failed to close the input stream", e);
         }
       }
     };
     thread.setDaemon(true);
-    thread.start();
     return thread;
   }
 
@@ -103,61 +125,51 @@
    * run sync process
    */
   private static int runSyncProcess(
-      boolean isInheritIO, String[] cmdline, StringBuilder stdout,
-      StringBuilder stderr, File workingDirectory, Map<String, String> envs) {
-    final StringBuilder pStdOut = stdout == null ? new StringBuilder() : stdout;
-    final StringBuilder pStdErr = stderr == null ? new StringBuilder() : stderr;
+      boolean isInheritIO, String[] cmdline,
+      StringBuilder outputBuilder, File workingDirectory, Map<String, String> envs) {
+    final StringBuilder builder = outputBuilder == null ? new StringBuilder() : outputBuilder;
 
     // Log the command for debugging
-    LOG.log(Level.FINE, "Process command: `$ {0}`", Arrays.toString(cmdline));
+    LOG.log(Level.INFO, "Running synced process: ``{0}''''", String.join(" ", cmdline));
     ProcessBuilder pb = getProcessBuilder(isInheritIO, cmdline, workingDirectory, envs);
+    /* combine input stream and error stream into stderr because
+       1. this preserves order of process's stdout/stderr message
+       2. there is no need to distinguish stderr from stdout
+       3. follow one basic pattern of the design of Python<~>Java I/O redirection:
+          stdout contains useful messages Java program needs to propagate back, stderr
+          contains all other information */
+    pb.redirectErrorStream(true);
 
     Process process;
     try {
       process = pb.start();
     } catch (IOException e) {
-      LOG.log(Level.SEVERE, "Failed to run Sync Process ", e);
+      LOG.log(Level.SEVERE, "Failed to run synced process", e);
       return -1;
     }
 
-    // Launching threads to consume stdout and stderr before "waitFor". Otherwise, output from the
-    // "process" can exhaust the available buffer for the output or error stream because neither
-    // stream is read while waiting for the process to complete. If either buffer becomes full, it
-    // can block the "process" as well, preventing all progress for both the "process" and the
-    // current thread.
-    Thread stdoutThread = asyncProcessStream(process.getInputStream(), pStdOut);
-    Thread stderrThread = asyncProcessStream(process.getErrorStream(), pStdErr);
-
-    int exitValue;
+    // Launching threads to consume combined stdout and stderr before "waitFor".
+    // Otherwise, output from the "process" can exhaust the available buffer for the combined stream
+    // because stream is not read while waiting for the process to complete.
+    // If buffer becomes full, it can block the "process" as well,
+    // preventing all progress for both the "process" and the current thread.
+    Thread outputsThread = createAsyncStreamThread(process.getInputStream(), builder);
 
     try {
-      exitValue = process.waitFor();
-      // Make sure `pStdOut` and `pStdErr` get the buffered data
-      stdoutThread.join();
-      stderrThread.join();
+      outputsThread.start();
+      int exitValue = process.waitFor();
+      outputsThread.join();
+      return exitValue;
     } catch (InterruptedException e) {
       // The current thread is interrupted, so try to interrupt reading threads and kill
       // the process to return quickly.
-      stdoutThread.interrupt();
-      stderrThread.interrupt();
+      outputsThread.interrupt();
       process.destroy();
-      LOG.log(Level.SEVERE, "Running Sync Process was interrupted", e);
+      LOG.log(Level.SEVERE, "Running synced process was interrupted", e);
       // Reset the interrupt status to allow other codes noticing it.
       Thread.currentThread().interrupt();
       return -1;
     }
-
-    String stdoutString = pStdOut.toString();
-    String stderrString = pStdErr.toString();
-    if (!stdoutString.isEmpty()) {
-      LOG.log(Level.FINE, "\nSTDOUT:\n {0}", stdoutString);
-    }
-
-    if (!stderrString.isEmpty()) {
-      LOG.log(Level.FINE, "\nSTDERR:\n {0}", stderrString);
-    }
-
-    return exitValue;
   }
 
   public static Process runASyncProcess(
@@ -183,7 +195,7 @@
 
   private static Process runASyncProcess(String[] command, File workingDirectory,
       Map<String, String> envs, String logFileUuid, boolean logStderr) {
-    LOG.log(Level.FINE, "$> {0}", Arrays.toString(command));
+    LOG.log(Level.INFO, "Running async process: ``{0}''''", String.join(" ", command));
 
     // the log file can help people to find out what happened between pb.start()
     // and the async process started
@@ -209,7 +221,7 @@
     try {
       process = pb.start();
     } catch (IOException e) {
-      LOG.log(Level.SEVERE, "Failed to run Async Process ", e);
+      LOG.log(Level.SEVERE, "Failed to run async process", e);
     }
 
     return process;
@@ -259,6 +271,7 @@
   }
 
   static Process establishSocksProxyProcess(String proxyHost, int proxyPort) {
+    LOG.info(String.format("Establishing SOCKS proxy to: %s:%d", proxyHost, proxyPort));
     return ShellUtils.runASyncProcess(String.format("ssh -ND %d %s", proxyPort, proxyHost));
   }
 
@@ -278,8 +291,8 @@
 
     // using curl copy the url to the target file
     String cmd = String.format("curl %s -o %s", uri, destination);
-    int ret = runSyncProcess(isVerbose, isInheritIO,
-        splitTokens(cmd), new StringBuilder(), new StringBuilder(), parentDirectory);
+    int ret = runSyncProcess(isInheritIO,
+        splitTokens(cmd), new StringBuilder(), parentDirectory);
 
     return ret == 0;
   }
@@ -296,8 +309,8 @@
       String packageName, String targetFolder, boolean isVerbose, boolean isInheritIO) {
     String cmd = String.format("tar -xvf %s", packageName);
 
-    int ret = runSyncProcess(isVerbose, isInheritIO,
-        splitTokens(cmd), new StringBuilder(), new StringBuilder(), new File(targetFolder));
+    int ret = runSyncProcess(isInheritIO,
+        splitTokens(cmd), new StringBuilder(), new File(targetFolder));
 
     return ret == 0;
   }
diff --git a/heron/spi/src/java/com/twitter/heron/spi/utils/TopologyUtils.java b/heron/spi/src/java/com/twitter/heron/spi/utils/TopologyUtils.java
index 96596ee..c37bb3c 100644
--- a/heron/spi/src/java/com/twitter/heron/spi/utils/TopologyUtils.java
+++ b/heron/spi/src/java/com/twitter/heron/spi/utils/TopologyUtils.java
@@ -50,7 +50,7 @@
 
       return topology;
     } catch (IOException e) {
-      throw new RuntimeException("Failed to read/parse content of " + topologyDefnFile);
+      throw new RuntimeException("Failed to read/parse content of " + topologyDefnFile, e);
     }
   }
 
diff --git a/heron/spi/tests/java/com/twitter/heron/spi/utils/ShellUtilsTest.java b/heron/spi/tests/java/com/twitter/heron/spi/utils/ShellUtilsTest.java
index f727392..474fe50 100644
--- a/heron/spi/tests/java/com/twitter/heron/spi/utils/ShellUtilsTest.java
+++ b/heron/spi/tests/java/com/twitter/heron/spi/utils/ShellUtilsTest.java
@@ -53,11 +53,10 @@
   @Test
   public void testRunProcess() {
     String testString = "testString";
-    StringBuilder stdout = new StringBuilder();
     StringBuilder stderr = new StringBuilder();
-    Assert.assertEquals(0, ShellUtils.runProcess(true, "echo " + testString, stdout, stderr));
-    Assert.assertEquals(testString, stdout.toString().trim());
-    Assert.assertTrue(stderr.toString().trim().isEmpty());
+    Assert.assertEquals(0, ShellUtils.runProcess("echo " + testString, stderr));
+    Assert.assertEquals(testString, stderr.toString().trim());
+    Assert.assertTrue(!stderr.toString().trim().isEmpty());
   }
 
   @Test(timeout = 60000)
@@ -73,13 +72,11 @@
         input.close();
       }
       Assert.assertTrue("Cannot make the test script executable", testScript.setExecutable(true));
-      StringBuilder stdout = new StringBuilder();
       StringBuilder stderr = new StringBuilder();
       Assert.assertEquals(0,
-          ShellUtils.runProcess(true,
-              "/bin/bash -c " + testScript.getAbsolutePath(), stdout, stderr));
+          ShellUtils.runProcess(
+              "/bin/bash -c " + testScript.getAbsolutePath(), stderr));
       // Only checks stdout and stderr are not empty. Correctness is checked in "testRunProcess".
-      Assert.assertTrue(!stdout.toString().trim().isEmpty());
       Assert.assertTrue(!stderr.toString().trim().isEmpty());
     } finally {
       testScript.delete();
diff --git a/heron/statemgrs/src/java/com/twitter/heron/statemgr/zookeeper/curator/CuratorStateManager.java b/heron/statemgrs/src/java/com/twitter/heron/statemgr/zookeeper/curator/CuratorStateManager.java
index eb774b7..e699fcb 100644
--- a/heron/statemgrs/src/java/com/twitter/heron/statemgr/zookeeper/curator/CuratorStateManager.java
+++ b/heron/statemgrs/src/java/com/twitter/heron/statemgr/zookeeper/curator/CuratorStateManager.java
@@ -79,8 +79,8 @@
 
       String newConnectionString = tunneledResults.first;
       if (newConnectionString.isEmpty()) {
-        throw new IllegalArgumentException("Cannot connect to tunnel host: "
-            + tunnelConfig.getTunnelHost());
+        throw new IllegalArgumentException("Failed to connect to tunnel host '"
+            + tunnelConfig.getTunnelHost() + "'");
       }
 
       // Use the new connection string
@@ -90,7 +90,7 @@
 
     // Start it
     client = getCuratorClient();
-    LOG.info("Starting client to: " + connectionString);
+    LOG.info("Starting Curator client connecting to: " + connectionString);
     client.start();
 
     try {
@@ -170,7 +170,7 @@
   protected void initTree() {
     // Make necessary directories
     for (StateLocation location : StateLocation.values()) {
-      LOG.info(String.format("%s directory: %s", location.getName(), getStateDirectory(location)));
+      LOG.fine(String.format("%s directory: %s", location.getName(), getStateDirectory(location)));
     }
 
     try {
@@ -196,8 +196,10 @@
 
     // Close the tunneling
     LOG.info("Closing the tunnel processes");
-    for (Process process : tunnelProcesses) {
-      process.destroy();
+    if (tunnelProcesses != null) {
+      for (Process process : tunnelProcesses) {
+        process.destroy();
+      }
     }
   }
 
diff --git a/heron/tools/cli/src/python/BUILD b/heron/tools/cli/src/python/BUILD
index 846b05a..558e206 100644
--- a/heron/tools/cli/src/python/BUILD
+++ b/heron/tools/cli/src/python/BUILD
@@ -13,7 +13,10 @@
         "//heron/tools/common/src/python:common-py",
         "//heron/proto:proto-py",
     ],
-    reqs = ["pyyaml==3.10"],
+    reqs = [
+        "pyyaml==3.10",
+        "enum34==1.1.6"
+    ],
 )
 
 pex_binary(
diff --git a/heron/tools/cli/src/python/cli_helper.py b/heron/tools/cli/src/python/cli_helper.py
index c3eff55..c47cb60 100644
--- a/heron/tools/cli/src/python/cli_helper.py
+++ b/heron/tools/cli/src/python/cli_helper.py
@@ -77,7 +77,7 @@
     new_args.append("--verbose")
 
   # invoke the runtime manager to kill the topology
-  resp = execute.heron_class(
+  result = execute.heron_class(
       'com.twitter.heron.scheduler.RuntimeManagerMain',
       lib_jars,
       extra_jars=[],
@@ -86,5 +86,5 @@
 
   err_msg = "Failed to %s %s" % (action, topology_name)
   succ_msg = "Successfully %s %s" % (action, topology_name)
-  resp.add_context(err_msg, succ_msg)
-  return resp
+  result.add_context(err_msg, succ_msg)
+  return result
diff --git a/heron/tools/cli/src/python/execute.py b/heron/tools/cli/src/python/execute.py
index 9343642..6136393 100644
--- a/heron/tools/cli/src/python/execute.py
+++ b/heron/tools/cli/src/python/execute.py
@@ -20,7 +20,7 @@
 import traceback
 
 from heron.common.src.python.utils.log import Log
-from heron.tools.cli.src.python.response import Response, Status
+from heron.tools.cli.src.python.result import SimpleResult, ProcessResult, Status
 import heron.common.src.python.pex_loader as pex_loader
 import heron.tools.cli.src.python.opts as opts
 import heron.tools.cli.src.python.jars as jars
@@ -67,17 +67,11 @@
   Log.debug("Heron options: {%s}", str(heron_env["HERON_OPTIONS"]))
 
   # invoke the command with subprocess and print error message, if any
-  proc = subprocess.Popen(all_args, env=heron_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  proc = subprocess.Popen(all_args, env=heron_env, stdout=subprocess.PIPE,
+                          stderr=subprocess.PIPE, bufsize=1)
   # stdout message has the information Java program sends back
   # stderr message has extra information, such as debugging message
-  msg, detailed_msg = proc.communicate()
-  # remove trailing newlines
-  if msg and msg[-1] == '\n':
-    msg = msg[:-1]
-  if detailed_msg and detailed_msg[-1] == '\n':
-    detailed_msg = detailed_msg[:-1]
-  Log.debug("shelled-out program's return code: %d", proc.returncode)
-  return Response(proc.returncode, msg, detailed_msg)
+  return ProcessResult(proc)
 
 def heron_tar(class_name, topology_tar, arguments, tmpdir_root, java_defines):
   '''
@@ -118,23 +112,16 @@
     # loading topology by running its main method (if __name__ == "__main__")
     heron_env = os.environ.copy()
     heron_env['HERON_OPTIONS'] = opts.get_heron_config()
-
     cmd = [topology_pex]
     if args is not None:
       cmd.extend(args)
     Log.debug("Invoking class using command: ``%s''", ' '.join(cmd))
     Log.debug('Heron options: {%s}', str(heron_env['HERON_OPTIONS']))
-
     # invoke the command with subprocess and print error message, if any
-    proc = subprocess.Popen(cmd, env=heron_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    proc = subprocess.Popen(cmd, env=heron_env, stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE, bufsize=1)
     # todo(rli): improve python topology submission workflow
-    proc.communicate()
-    retcode = proc.returncode
-    if retcode != 0:
-      err_str = "Topology class %s failed to be loaded from the given pex" % topology_class_name
-      return Response(retcode, err_str, None)
-    else:
-      return Response(retcode)
+    return ProcessResult(proc)
   else:
     try:
       # loading topology from Topology's subclass (no main method)
@@ -147,8 +134,9 @@
       pex_loader.load_pex(topology_pex)
       topology_class = pex_loader.import_and_get_class(topology_pex, topology_class_name)
       topology_class.write()
-      return Response(Status.Ok)
+      return SimpleResult(Status.Ok)
     except Exception as ex:
       Log.debug(traceback.format_exc())
-      err_str = "Topology %s failed to be loaded from the given pex" % topology_class_name
-      return Response(Status.HeronError, err_str, str(ex))
+      err_context = "Topology %s failed to be loaded from the given pex: %s" %\
+                (topology_class_name, ex)
+      return SimpleResult(Status.HeronError, err_context)
diff --git a/heron/tools/cli/src/python/help.py b/heron/tools/cli/src/python/help.py
index f923273..b9b04d3 100644
--- a/heron/tools/cli/src/python/help.py
+++ b/heron/tools/cli/src/python/help.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 ''' help.py '''
 from heron.common.src.python.utils.log import Log
-from heron.tools.cli.src.python.response import Response, Status
+from heron.tools.cli.src.python.result import SimpleResult, Status
 import heron.tools.common.src.python.utils.config as config
 
 def create_parser(subparsers):
@@ -55,13 +55,13 @@
   # if no command is provided, just print main help
   if command_help == 'help':
     parser.print_help()
-    return Response(Status.Ok)
+    return SimpleResult(Status.Ok)
 
   # get the subparser for the specific command
   subparser = config.get_subparser(parser, command_help)
   if subparser:
     print subparser.format_help()
-    return Response(Status.Ok)
+    return SimpleResult(Status.Ok)
   else:
     Log.error("Unknown subcommand \'%s\'", command_help)
-    return Response(Status.InvocationError)
+    return SimpleResult(Status.InvocationError)
diff --git a/heron/tools/cli/src/python/main.py b/heron/tools/cli/src/python/main.py
index 7160df9..b665620 100644
--- a/heron/tools/cli/src/python/main.py
+++ b/heron/tools/cli/src/python/main.py
@@ -29,12 +29,14 @@
 import heron.tools.cli.src.python.activate as activate
 import heron.tools.cli.src.python.deactivate as deactivate
 import heron.tools.cli.src.python.kill as kill
-import heron.tools.cli.src.python.response as response
+import heron.tools.cli.src.python.result as result
 import heron.tools.cli.src.python.restart as restart
 import heron.tools.cli.src.python.submit as submit
 import heron.tools.cli.src.python.update as update
 import heron.tools.cli.src.python.version as version
 
+from heron.tools.cli.src.python.opts import cleaned_up_files
+
 Log = log.Log
 
 HELP_EPILOG = '''Getting more help:
@@ -116,8 +118,8 @@
   if command in runners:
     return runners[command].run(command, parser, command_args, unknown_args)
   else:
-    detailed_msg = 'Unknown subcommand: %s' % command
-    return response.Response(response.Status.InvocationError, detailed_msg=detailed_msg)
+    err_context = 'Unknown subcommand: %s' % command
+    return result.SimpleResult(result.Status.InvocationError, err_context)
 
 def cleanup(files):
   '''
@@ -125,7 +127,10 @@
   :return:
   '''
   for cur_file in files:
-    shutil.rmtree(os.path.dirname(cur_file))
+    if os.path.isdir(cur_file):
+      shutil.rmtree(cur_file)
+    else:
+      shutil.rmtree(os.path.dirname(cur_file))
 
 
 ################################################################################
@@ -215,9 +220,6 @@
   # command to be execute
   command = command_line_args['subcommand']
 
-  # file resources to be cleaned when exit
-  files = []
-
   if command not in ('help', 'version'):
     log.set_logging_level(command_line_args)
     command_line_args = extract_common_args(command, parser, command_line_args)
@@ -225,26 +227,24 @@
     if not command_line_args:
       return 1
     # register dirs cleanup function during exit
-    files.append(command_line_args['override_config_file'])
+    cleaned_up_files.append(command_line_args['override_config_file'])
 
-  atexit.register(cleanup, files)
+  atexit.register(cleanup, cleaned_up_files)
 
   # print the input parameters, if verbose is enabled
   Log.debug(command_line_args)
 
   start = time.time()
-  resp = run(command, parser, command_line_args, unknown_args)
+  results = run(command, parser, command_line_args, unknown_args)
   if command not in ('help', 'version'):
-    response.render(resp)
+    result.render(results)
   end = time.time()
 
   if command not in ('help', 'version'):
     sys.stdout.flush()
     Log.info('Elapsed time: %.3fs.', (end - start))
-    return 0 if response.isAllSuccessful(resp) else 1
-  else:
-    return 0 if resp else 1
 
+  return 0 if result.isAllSuccessful(results) else 1
 
 if __name__ == "__main__":
   sys.exit(main())
diff --git a/heron/tools/cli/src/python/opts.py b/heron/tools/cli/src/python/opts.py
index 309ed55..edb2c30 100644
--- a/heron/tools/cli/src/python/opts.py
+++ b/heron/tools/cli/src/python/opts.py
@@ -21,6 +21,8 @@
 verbose_flag = False
 trace_execution_flag = False
 
+cleaned_up_files = []
+
 
 ################################################################################
 def get_heron_config():
diff --git a/heron/tools/cli/src/python/response.py b/heron/tools/cli/src/python/response.py
deleted file mode 100644
index 1196094..0000000
--- a/heron/tools/cli/src/python/response.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2016 Twitter. All rights reserved.
-#
-# Licensed 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.
-'''response.py'''
-from heron.common.src.python.utils.log import Log
-
-# Meaning of exit status code:
-#  - status code = 0:
-#    program exits without error
-#  - 0 < status code < 100:
-#    program fails to execute before program execution. For example,
-#    JVM cannot find or load main class
-#  - 100 <= status code < 200:
-#    program fails to launch after program execution. For example,
-#    topology definition file fails to be loaded
-#  - status code == 200:
-#    program sends out dry-run response
-
-# Definition corresponds to definition in com.twitter.heron.scheduler.AbstractMain
-
-# pylint: disable=no-init
-class Status(object):
-  """Status code enum"""
-  Ok = 0
-  InvocationError = 1
-  HeronError = 100
-  DryRun = 200
-
-def status_type(status_code):
-  if status_code == 0:
-    return Status.Ok
-  elif status_code < 100:
-    return Status.InvocationError
-  elif status_code == 200:
-    return Status.DryRun
-  else:
-    return Status.HeronError
-
-class Response(object):
-  """Response class that captures result of executing an action
-
-     If the response object is generated by a statement that
-     shells out program, `msg` is stdout, `detailed_msg` is stderr
-  """
-  def __init__(self, status_code, msg=None, detailed_msg=None):
-    self.status = status_type(status_code)
-    self.msg = msg
-    self.detailed_msg = detailed_msg
-    self.err_context = None
-    self.succ_context = None
-
-  def add_context(self, err_context, succ_context=None):
-    """ Prepend msg to add some context information
-
-    :param pmsg: context info
-    :return: None
-    """
-    self.err_context = err_context
-    self.succ_context = succ_context
-
-def render(resp):
-  def do_log(log, msg):
-    if msg:
-      log(msg)
-  if isinstance(resp, list):
-    for r in resp:
-      render(r)
-  elif isinstance(resp, Response):
-    if resp.status == Status.Ok:
-      do_log(Log.info, resp.succ_context)
-      do_log(Log.info, resp.msg)
-      do_log(Log.debug, resp.detailed_msg)
-    elif resp.status == Status.HeronError:
-      do_log(Log.error, resp.err_context)
-      do_log(Log.error, resp.msg)
-      do_log(Log.debug, resp.detailed_msg)
-    # If status code is InvocationError, invocation of shelled-out program fails. The error
-    # message will be in stderr, so we log.error detailed message(stderr) only
-    elif resp.status == Status.InvocationError:
-      do_log(Log.error, resp.detailed_msg)
-    elif resp.status == Status.DryRun:
-      do_log(Log.info, resp.succ_context)
-      # No need to prefix [INFO] here. We want to display dry-run response in a clean way
-      print resp.msg
-      do_log(Log.debug, resp.detailed_msg)
-    else:
-      raise RuntimeError("Unknown status type of value %d", resp.status)
-  else:
-    raise RuntimeError("Unknown response instance: %s", str(resp.__class__))
-
-# check if all responses are successful
-def isAllSuccessful(resps):
-  if isinstance(resps, list):
-    return all([resp.status == Status.Ok for resp in resps])
-  elif isinstance(resps, Response):
-    return resps.status == Status.Ok
-  else:
-    raise RuntimeError("Unknown response instance: %s", str(resps.__class__))
diff --git a/heron/tools/cli/src/python/result.py b/heron/tools/cli/src/python/result.py
new file mode 100644
index 0000000..e1643dc
--- /dev/null
+++ b/heron/tools/cli/src/python/result.py
@@ -0,0 +1,188 @@
+# Copyright 2016 Twitter. All rights reserved.
+#
+# Licensed 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.
+'''result.py'''
+import abc
+import sys
+from enum import Enum
+
+from heron.common.src.python.utils.log import Log
+
+# Meaning of exit status code:
+#  - status code = 0:
+#    program exits without error
+#  - 0 < status code < 100:
+#    program fails to execute before program execution. For example,
+#    JVM cannot find or load main class
+#  - 100 <= status code < 200:
+#    program fails to launch after program execution. For example,
+#    topology definition file fails to be loaded
+#  - status code == 200:
+#    program sends out dry-run response
+
+# Definition corresponds to definition in com.twitter.heron.scheduler.AbstractMain
+
+# pylint: disable=no-init
+class Status(Enum):
+  """Status code enum"""
+  Ok = 0
+  InvocationError = 1
+  HeronError = 100
+  DryRun = 200
+
+def status_type(status_code):
+  if status_code == 0:
+    return Status.Ok
+  elif status_code < 100:
+    return Status.InvocationError
+  elif status_code == 200:
+    return Status.DryRun
+  else:
+    return Status.HeronError
+
+class Result(object):
+  """Result class"""
+  def __init__(self, status=None, err_context=None, succ_context=None):
+    self.status = status
+    self.err_context = err_context
+    self.succ_context = succ_context
+
+  @staticmethod
+  def _do_log(log_f, msg):
+    if msg:
+      if msg[-1] == '\n':
+        msg = msg[:-1]
+      log_f(msg)
+
+  def _log_context(self):
+    # render context only after process exits
+    assert self.status is not None
+    if self.status == Status.Ok or self.status == Status.DryRun:
+      self._do_log(Log.info, self.succ_context)
+    elif self.status == Status.HeronError:
+      self._do_log(Log.error, self.err_context)
+    elif self.status == Status.InvocationError:
+      # invocation error has no context
+      pass
+    else:
+      raise RuntimeError(
+          "Unknown status type of value %d. Expected value: %s", self.status.value, list(Status))
+
+  def add_context(self, err_context, succ_context=None):
+    """ Prepend msg to add some context information
+
+    :param pmsg: context info
+    :return: None
+    """
+    self.err_context = err_context
+    self.succ_context = succ_context
+
+  def is_successful(self):
+    return self.status == Status.Ok
+
+  @abc.abstractmethod
+  def render(self):
+    pass
+
+
+class SimpleResult(Result):
+  """Simple result: result that already and only
+     contains status of the result"""
+  def __init__(self, status):
+    super(SimpleResult, self).__init__(status)
+
+  def render(self):
+    self._log_context()
+
+
+class ProcessResult(Result):
+  """Process result: a wrapper of result class"""
+  def __init__(self, proc):
+    super(ProcessResult, self).__init__()
+    self.proc = proc
+
+  def renderProcessStdErr(self, stderr_line):
+    """ render stderr of shelled-out process
+        stderr could be error message of failure of invoking process or
+        normal stderr output from successfully shelled-out process.
+        In the first case, ``Popen'' should fail fast and we should be able to
+        get return code immediately. We then render the failure message.
+        In the second case, we simply print stderr line in stderr.
+        The way to handle the first case is shaky but should be the best we can
+        do since we have conflicts of design goals here.
+    :param stderr_line: one line from shelled-out process
+    :return:
+    """
+    retcode = self.proc.poll()
+    if retcode is not None and status_type(retcode) == Status.InvocationError:
+      self._do_log(Log.error, stderr_line)
+    else:
+      print >> sys.stderr, stderr_line,
+
+  def renderProcessStdOut(self, stdout):
+    """ render stdout of shelled-out process
+        stdout always contains information Java process wants to
+        propagate back to cli, so we do special rendering here
+    :param stdout: all lines from shelled-out process
+    :return:
+    """
+    # since we render stdout line based on Java process return code,
+    # ``status'' has to be already set
+    assert self.status is not None
+    # remove pending newline
+    if self.status == Status.Ok:
+      self._do_log(Log.info, stdout)
+    elif self.status == Status.HeronError:
+      # remove last newline since logging will append newline
+      self._do_log(Log.error, stdout)
+    # No need to prefix [INFO] here. We want to display dry-run response in a clean way
+    elif self.status == Status.DryRun:
+      print >> sys.stdout, stdout,
+    else:
+      raise RuntimeError(
+          "Unknown status type of value %d. Expected value: %s", self.status.value, list(Status))
+
+  def render(self):
+    while True:
+      stderr_line = self.proc.stderr.readline()
+      if not stderr_line:
+        if self.proc.poll() is None:
+          continue
+        else:
+          break
+      else:
+        self.renderProcessStdErr(stderr_line)
+    self.proc.wait()
+    self.status = status_type(self.proc.returncode)
+    stdout = "".join(self.proc.stdout.readlines())
+    self.renderProcessStdOut(stdout)
+    self._log_context()
+
+
+def render(results):
+  if isinstance(results, Result):
+    results.render()
+  elif isinstance(results, list):
+    for r in results:
+      r.render()
+  else:
+    raise RuntimeError("Unknown result instance: %s", str(results.__class__))
+
+# check if all results are successful
+def isAllSuccessful(results):
+  if isinstance(results, list):
+    return all([result.is_successful() for result in results])
+  elif isinstance(results, Result):
+    return results.is_successful()
+  else:
+    raise RuntimeError("Unknown result instance: %s", str(results.__class__))
diff --git a/heron/tools/cli/src/python/submit.py b/heron/tools/cli/src/python/submit.py
index da2bb37..9d0ba76 100644
--- a/heron/tools/cli/src/python/submit.py
+++ b/heron/tools/cli/src/python/submit.py
@@ -15,16 +15,16 @@
 import glob
 import logging
 import os
-import shutil
 import tempfile
 
 from heron.common.src.python.utils.log import Log
 from heron.proto import topology_pb2
-from heron.tools.cli.src.python.response import Response, Status
+from heron.tools.cli.src.python.result import SimpleResult, Status
 import heron.tools.cli.src.python.args as cli_args
 import heron.tools.cli.src.python.execute as execute
 import heron.tools.cli.src.python.jars as jars
 import heron.tools.cli.src.python.opts as opts
+import heron.tools.cli.src.python.result as result
 import heron.tools.common.src.python.utils.config as config
 import heron.tools.common.src.python.utils.classpath as classpath
 
@@ -112,7 +112,7 @@
 
   # invoke the submitter to submit and launch the topology
   main_class = 'com.twitter.heron.scheduler.SubmitterMain'
-  resp = execute.heron_class(
+  res = execute.heron_class(
       class_name=main_class,
       lib_jars=lib_jars,
       extra_jars=extra_jars,
@@ -124,8 +124,8 @@
   succ_context = "Successfully launched topology '%s'" % topology_name
   if cl_args["dry_run"]:
     succ_context += " in dry-run mode"
-  resp.add_context(err_context, succ_context)
-  return resp
+  res.add_context(err_context, succ_context)
+  return res
 
 ################################################################################
 def launch_topologies(cl_args, topology_file, tmp_dir):
@@ -140,9 +140,9 @@
   defn_files = glob.glob(tmp_dir + '/*.defn')
 
   if len(defn_files) == 0:
-    return Response(Status.HeronError, "No topologies found under %s" % tmp_dir)
+    return SimpleResult(Status.HeronError, "No topologies found under %s" % tmp_dir)
 
-  responses = []
+  results = []
   for defn_file in defn_files:
     # load the topology definition from the file
     topology_defn = topology_pb2.Topology()
@@ -151,15 +151,14 @@
       topology_defn.ParseFromString(handle.read())
       handle.close()
     except Exception as e:
-      msg = "Cannot load topology definition '%s'" % defn_file
-      return Response(Status.HeronError, msg, str(e))
-
+      err_context = "Cannot load topology definition '%s': %s" % (defn_file, e)
+      return SimpleResult(Status.HeronError, err_context)
     # launch the topology
     Log.info("Launching topology: \'%s\'", topology_defn.name)
-    resp = launch_a_topology(
+    res = launch_a_topology(
         cl_args, tmp_dir, topology_file, defn_file, topology_defn.name)
-    responses.append(resp)
-  return responses
+    results.append(res)
+  return results
 
 
 ################################################################################
@@ -183,23 +182,25 @@
   topology_file = cl_args['topology-file-name']
 
   main_class = cl_args['topology-class-name']
-  resp = execute.heron_class(
+
+  res = execute.heron_class(
       class_name=main_class,
       lib_jars=config.get_heron_libs(jars.topology_jars()),
       extra_jars=[topology_file],
       args=tuple(unknown_args),
       java_defines=cl_args['topology_main_jvm_property'])
 
-  if resp.status != Status.Ok:
+  result.render(res)
+
+  if not res.is_successful():
     err_context = "Failed to create topology definition \
       file when executing class '%s' of file '%s'" % (main_class, topology_file)
-    resp.add_context(err_context)
-    return resp
+    res.add_context(err_context)
+    return res
 
-  responses = launch_topologies(cl_args, topology_file, tmp_dir)
-  shutil.rmtree(tmp_dir)
+  results = launch_topologies(cl_args, topology_file, tmp_dir)
 
-  return responses
+  return results
 
 
 ################################################################################
@@ -226,23 +227,22 @@
   topology_file = cl_args['topology-file-name']
   java_defines = cl_args['topology_main_jvm_property']
   main_class = cl_args['topology-class-name']
-  resp = execute.heron_tar(
+  res = execute.heron_tar(
       main_class,
       topology_file,
       tuple(unknown_args),
       tmp_dir,
       java_defines)
 
-  if resp.status != Status.Ok:
+  res.render()
+
+  if not res.is_successful():
     err_context = "Failed to create topology definition \
       file when executing class '%s' of file '%s'" % (main_class, topology_file)
-    resp.add_context(err_context)
-    return resp
+    res.add_context(err_context)
+    return res
 
-  responses = launch_topologies(cl_args, topology_file, tmp_dir)
-  shutil.rmtree(tmp_dir)
-
-  return responses
+  return launch_topologies(cl_args, topology_file, tmp_dir)
 
 ################################################################################
 #  Execute the pex file to create topology definition file by running
@@ -253,18 +253,18 @@
   # execute main of the topology to create the topology definition
   topology_file = cl_args['topology-file-name']
   topology_class_name = cl_args['topology-class-name']
-  resp = execute.heron_pex(
+  res = execute.heron_pex(
       topology_file, topology_class_name, tuple(unknown_args))
-  if resp.status != Status.Ok:
+
+  res.render()
+
+  if not res.is_successful():
     err_context = "Failed to create topology definition \
       file when executing class '%s' of file '%s'" % (topology_class_name, topology_file)
-    resp.add_context(err_context)
-    return resp
+    res.add_context(err_context)
+    return res
 
-  responses = launch_topologies(cl_args, topology_file, tmp_dir)
-  shutil.rmtree(tmp_dir)
-
-  return responses
+  return launch_topologies(cl_args, topology_file, tmp_dir)
 
 ################################################################################
 # pylint: disable=unused-argument
@@ -287,8 +287,8 @@
 
   # check to see if the topology file exists
   if not os.path.isfile(topology_file):
-    err_msg = "Topology jar|tar|pex file '%s' does not exist" % topology_file
-    return Response(Status.InvocationError, detailed_msg=err_msg)
+    err_context = "Topology jar|tar|pex file '%s' does not exist" % topology_file
+    return SimpleResult(Status.InvocationError, err_context)
 
   # check if it is a valid file type
   jar_type = topology_file.endswith(".jar")
@@ -296,19 +296,21 @@
   pex_type = topology_file.endswith(".pex")
   if not jar_type and not tar_type and not pex_type:
     ext_name = os.path.splitext(topology_file)
-    err_msg = "Unknown file type '%s'. Please use .tar or .tar.gz or .jar or .pex file" % ext_name
-    return Response(Status.InvocationError, detailed_msg=err_msg)
+    err_context = "Unknown file type '%s'. Please use .tar or .tar.gz or .jar or .pex file"\
+                  % ext_name
+    return SimpleResult(Status.InvocationError, err_context)
 
   # check if extra launch classpath is provided and if it is validate
   if cl_args['extra_launch_classpath']:
     valid_classpath = classpath.valid_java_classpath(cl_args['extra_launch_classpath'])
     if not valid_classpath:
-      err_msg = "One of jar or directory in extra launch classpath does not exist: %s" % \
+      err_context = "One of jar or directory in extra launch classpath does not exist: %s" % \
         cl_args['extra_launch_classpath']
-      return Response(Status.InvocationError, detailed_msg=err_msg)
+      return SimpleResult(Status.InvocationError, err_context)
 
   # create a temporary directory for topology definition file
   tmp_dir = tempfile.mkdtemp()
+  opts.cleaned_up_files.append(tmp_dir)
 
   # if topology needs to be launched in deactivated state, do it so
   if cl_args['deploy_deactivated']:
@@ -323,10 +325,7 @@
   # check the extension of the file name to see if it is tar/jar file.
   if jar_type:
     return submit_fatjar(cl_args, unknown_args, tmp_dir)
-
   elif tar_type:
     return submit_tar(cl_args, unknown_args, tmp_dir)
-
   else:
     return submit_pex(cl_args, unknown_args, tmp_dir)
-
diff --git a/heron/tools/cli/src/python/version.py b/heron/tools/cli/src/python/version.py
index 01030ae..8cf487d 100644
--- a/heron/tools/cli/src/python/version.py
+++ b/heron/tools/cli/src/python/version.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ''' version.py '''
-from heron.tools.cli.src.python.response import Response, Status
+from heron.tools.cli.src.python.result import SimpleResult, Status
 import heron.tools.cli.src.python.args as cli_args
 import heron.tools.common.src.python.utils.config as config
 
@@ -44,4 +44,4 @@
   :return:
   '''
   config.print_build_info()
-  return Response(Status.Ok)
+  return SimpleResult(Status.Ok)
diff --git a/heron/tools/tracker/src/python/main.py b/heron/tools/tracker/src/python/main.py
index 363fbf2..fb9a93d 100644
--- a/heron/tools/tracker/src/python/main.py
+++ b/heron/tools/tracker/src/python/main.py
@@ -197,7 +197,7 @@
 
   namespace = vars(args)
 
-  log.set_logging_level(namespace, with_time=True)
+  log.set_logging_level(namespace)
 
   # set Tornado global option
   define_options(namespace['port'], namespace['config_file'])
diff --git a/heron/tools/ui/src/python/main.py b/heron/tools/ui/src/python/main.py
index 703a475..02857a4 100644
--- a/heron/tools/ui/src/python/main.py
+++ b/heron/tools/ui/src/python/main.py
@@ -125,7 +125,7 @@
   :param argv:
   :return:
   '''
-  log.configure(logging.DEBUG, with_time=True)
+  log.configure(logging.DEBUG)
   tornado.log.enable_pretty_logging()
 
   # create the parser and parse the arguments
diff --git a/heron/uploaders/src/java/com/twitter/heron/uploader/hdfs/HdfsController.java b/heron/uploaders/src/java/com/twitter/heron/uploader/hdfs/HdfsController.java
index 3058b72..2b5bd8a 100644
--- a/heron/uploaders/src/java/com/twitter/heron/uploader/hdfs/HdfsController.java
+++ b/heron/uploaders/src/java/com/twitter/heron/uploader/hdfs/HdfsController.java
@@ -27,22 +27,22 @@
 
   public boolean exists(String filePath) {
     String command = String.format("hadoop --config %s fs -test -e %s", configDir, filePath);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 
   public boolean mkdirs(String dir) {
     String command = String.format("hadoop --config %s fs -mkdir -p %s", configDir, dir);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 
   public boolean copyFromLocalFile(String source, String destination) {
     String command = String.format("hadoop --config %s fs -copyFromLocal -f %s %s",
         configDir, source, destination);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 
   public boolean delete(String filePath) {
     String command = String.format("hadoop --config %s fs -rm %s", configDir, filePath);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 }
diff --git a/heron/uploaders/src/java/com/twitter/heron/uploader/scp/ScpController.java b/heron/uploaders/src/java/com/twitter/heron/uploader/scp/ScpController.java
index 989bf48..16a22ec 100644
--- a/heron/uploaders/src/java/com/twitter/heron/uploader/scp/ScpController.java
+++ b/heron/uploaders/src/java/com/twitter/heron/uploader/scp/ScpController.java
@@ -41,7 +41,7 @@
     // an example ssh command created by the format looks like this:
     // ssh -i ~/.ssh/id_rsa -p 23 user@example.com mkdir -p /heron/repository/...
     String command = String.format("ssh %s %s mkdir -p %s", sshOptions, sshConnection, dir);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 
   public boolean copyFromLocalFile(String source, String destination) {
@@ -49,11 +49,11 @@
     // scp -i ~/.ssh/id_rsa -p 23 ./foo.tar.gz user@example.com:/heron/foo.tar.gz
     String command =
         String.format("scp %s %s %s:%s", scpOptions, source, scpConnection, destination);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 
   public boolean delete(String filePath) {
     String command = String.format("ssh %s %s rm -rf %s", sshOptions, sshConnection, filePath);
-    return 0 == ShellUtils.runProcess(isVerbose, command, null, null);
+    return 0 == ShellUtils.runProcess(command, null);
   }
 }
diff --git a/scripts/centos/BUILD b/scripts/centos/BUILD
deleted file mode 100644
index b3229e7..0000000
--- a/scripts/centos/BUILD
+++ /dev/null
@@ -1,491 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-load("/tools/rules/pex_rules", "pex_binary")
-
-release_version = "unversioned"
-
-filegroup(
-    name = "tarpkgs",
-    srcs = [
-        ":heron-api",
-        ":heron-core",
-        ":heron-client",
-        ":heron-tools",
-    ],
-)
-
-filegroup(
-    name = "binpkgs",
-    srcs = [
-        ":heron-api-install.sh",
-        ":heron-client-install.sh",
-        ":heron-tools-install.sh",
-    ],
-)
-
-################################################################################
-# Heron core packaging
-################################################################################
-genrule(
-    name = "heron-core",
-    srcs = [
-        ":release.yaml",
-        ":hexecutor",
-        ":hshell",
-        ":hstmgr",
-        ":htmaster",
-        ":hpyinstance",
-        ":hscheduler",
-        ":hscheduler-aurora",
-        ":hscheduler-local",
-        ":hbppacking",
-        ":hrrpacking",
-        ":hmetricsmgr",
-        ":hlfsstatemgr",
-        ":hzkstatemgr",
-        ":hinstance",
-    ],
-    outs = [
-        "heron-core.tar.gz",
-    ],
-    cmd = " ".join([
-        "export GENDIR=$(GENDIR);",
-        "export BINDIR=$(BINDIR);",
-        "$(location package_release.sh)            $(location heron-core.tar.gz)",
-        "--cp $(location release.yaml)             heron-core/release.yaml",
-        "--cp $(location hexecutor)                heron-core/bin/heron-executor",
-        "--cp $(location hshell)                   heron-core/bin/heron-shell",
-        "--cp $(location hstmgr)                   heron-core/bin/heron-stmgr",
-        "--cp $(location htmaster)                 heron-core/bin/heron-tmaster",
-        "--cp $(location hpyinstance)              heron-core/bin/heron-python-instance",
-        "--cp $(location hscheduler)               heron-core/lib/scheduler/heron-scheduler.jar",
-        "--cp $(location hscheduler-aurora)        heron-core/lib/scheduler/heron-aurora-scheduler.jar",
-        "--cp $(location hscheduler-local)         heron-core/lib/scheduler/heron-local-scheduler.jar",
-        "--cp $(location hbppacking)               heron-core/lib/packing/heron-binpacking-packing.jar",
-        "--cp $(location hrrpacking)               heron-core/lib/packing/heron-roundrobin-packing.jar",
-        "--cp $(location hmetricsmgr)              heron-core/lib/metricsmgr/heron-metricsmgr.jar",
-        "--cp $(location hlfsstatemgr)             heron-core/lib/statemgr/heron-localfs-statemgr.jar",
-        "--cp $(location hzkstatemgr)              heron-core/lib/statemgr/heron-zookeeper-statemgr.jar",
-        "--cp $(location hinstance)                heron-core/lib/instance/heron-instance.jar",
-    ]),
-    heuristic_label_expansion = False,
-    tags = ["manual"],
-    tools = ["package_release.sh"],
-)
-
-################################################################################
-# Heron client packaging
-################################################################################
-genrule(
-    name = "heron-client",
-    srcs = [
-        ":release.yaml",
-        ":heron-core",
-        ":conf-local-heron-internals",
-        ":conf-local-metrics-sinks",
-        ":conf-local-client",
-        ":conf-local-packing",
-        ":conf-local-scheduler",
-        ":conf-local-statemgr",
-        ":conf-local-uploader",
-        ":hcli",
-        ":hexamples",
-        ":hscheduler",
-        ":hscheduler-aurora",
-        ":hscheduler-local",
-        ":hbppacking",
-        ":hrrpacking",
-        ":hlfsstatemgr",
-        ":hzkstatemgr",
-        ":huploader-localfs",
-        ":huploader-null",
-        ":protobuf-java",
-        ":slf4j-api-java",
-        ":slf4j-jdk-java",
-    ],
-    outs = [
-        "heron-client.tar.gz",
-    ],
-    cmd = " ".join([
-        "export GENDIR=$(GENDIR);",
-        "export BINDIR=$(BINDIR);",
-        "$(location package_release.sh) $(location heron-client.tar.gz)",
-        "--cp $(location release.yaml)               release.yaml",
-        "--cp $(location hcli)                       bin/heron",
-        "--cp $(location conf-local-heron-internals) conf/local/heron_internals.yaml",
-        "--cp $(location conf-local-metrics-sinks)   conf/local/metrics_sinks.yaml",
-        "--cp $(location conf-local-client)          conf/local/client.yaml",
-        "--cp $(location conf-local-packing)         conf/local/packing.yaml",
-        "--cp $(location conf-local-scheduler)       conf/local/scheduler.yaml",
-        "--cp $(location conf-local-statemgr)        conf/local/statemgr.yaml",
-        "--cp $(location conf-local-uploader)        conf/local/uploader.yaml",
-        "--cp $(location heron-core)                 dist/heron-core.tar.gz",
-        "--cp $(location hexamples)                  examples/heron-examples.jar",
-        "--cp $(location hscheduler)                 lib/scheduler/heron-scheduler.jar",
-        "--cp $(location hscheduler-aurora)          lib/scheduler/heron-aurora-scheduler.jar",
-        "--cp $(location hscheduler-local)           lib/scheduler/heron-local-scheduler.jar",
-        "--cp $(location hbppacking)                 lib/packing/heron-binpacking-packing.jar",
-        "--cp $(location hrrpacking)                 lib/packing/heron-roundrobin-packing.jar",
-        "--cp $(location hlfsstatemgr)               lib/statemgr/heron-localfs-statemgr.jar",
-        "--cp $(location hzkstatemgr)                lib/statemgr/heron-zookeeper-statemgr.jar",
-        "--cp $(location huploader-localfs)          lib/uploader/heron-localfs-uploader.jar",
-        "--cp $(location huploader-null)             lib/uploader/heron-null-uploader.jar",
-        "--cp $(location protobuf-java)              lib/third_party/$$(basename $(location protobuf-java))",
-        "--cp $(location slf4j-api-java)             lib/third_party/$$(basename $(location slf4j-api-java))",
-        "--cp $(location slf4j-jdk-java)             lib/third_party/$$(basename $(location slf4j-jdk-java))",
-    ]),
-    heuristic_label_expansion = False,
-    tags = ["manual"],
-    tools = ["package_release.sh"],
-)
-
-################################################################################
-# Heron tools packaging
-################################################################################
-genrule(
-    name = "heron-tools",
-    srcs = [
-        ":release.yaml",
-        ":htracker",
-        ":hui",
-    ],
-    outs = [
-        "heron-tools.tar.gz",
-    ],
-    cmd = " ".join([
-        "export GENDIR=$(GENDIR);",
-        "export BINDIR=$(BINDIR);",
-        "$(location package_release.sh) $(location heron-tools.tar.gz)",
-        "--cp $(location release.yaml)               release.yaml",
-        "--cp $(location htracker)                   bin/heron-tracker",
-        "--cp $(location hui)                        bin/heron-ui",
-    ]),
-    heuristic_label_expansion = False,
-    tags = ["manual"],
-    tools = ["package_release.sh"],
-)
-
-################################################################################
-# Heron api packaging
-################################################################################
-genrule(
-    name = "heron-api",
-    srcs = [
-        ":release.yaml",
-        ":hapi",
-        ":hspi",
-        ":hstorm",
-    ],
-    outs = [
-        "heron-api.tar.gz",
-    ],
-    cmd = " ".join([
-        "export GENDIR=$(GENDIR);",
-        "export BINDIR=$(BINDIR);",
-        "$(location package_release.sh)              $(location heron-api.tar.gz)",
-        "--cp $(location release.yaml)               release.yaml",
-        "--cp $(location hapi)                       heron-api.jar",
-        "--cp $(location hspi)                       heron-spi.jar",
-        "--cp $(location hstorm)                     heron-storm.jar",
-    ]),
-    heuristic_label_expansion = False,
-    tags = ["manual"],
-    tools = ["package_release.sh"],
-)
-
-filegroup(
-    name = "conf-local-heron-internals",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-heron-internals"],
-)
-
-filegroup(
-    name = "conf-local-metrics-sinks",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-metrics-sinks"],
-)
-
-filegroup(
-    name = "conf-local-client",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-client"],
-)
-
-filegroup(
-    name = "conf-local-packing",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-packing"],
-)
-
-filegroup(
-    name = "conf-local-scheduler",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-scheduler"],
-)
-
-filegroup(
-    name = "conf-local-statemgr",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-statemgr"],
-)
-
-filegroup(
-    name = "conf-local-uploader",
-    srcs = ["//heron/tools/config/src/yaml:conf-local-uploader"],
-)
-
-filegroup(
-    name = "hexamples",
-    srcs = ["//heron/examples/src/java:heron-examples"],
-)
-
-filegroup(
-    name = "htmaster",
-    srcs = ["//heron/tmaster/src/cpp:heron-tmaster"],
-)
-
-filegroup(
-    name = "hpyinstance",
-    srcs = ["//heron/instance/src/python/instance:heron-python-instance"],
-)
-
-filegroup(
-    name = "hstmgr",
-    srcs = ["//heron/stmgr/src/cpp:heron-stmgr"],
-)
-
-filegroup(
-    name = "hinstance",
-    srcs = ["//heron/instance/src/java:heron-instance"],
-)
-
-filegroup(
-    name = "hlogging",
-    srcs = ["//heron/instance/src/java:aurora-logging-properties"],
-)
-
-filegroup(
-    name = "hscheduler",
-    srcs = ["//heron/scheduler-core/src/java:heron-scheduler"],
-)
-
-filegroup(
-    name = "hscheduler-aurora",
-    srcs = ["//heron/schedulers/src/java:heron-aurora-scheduler"],
-)
-
-filegroup(
-    name = "hscheduler-local",
-    srcs = ["//heron/schedulers/src/java:heron-local-scheduler"],
-)
-
-filegroup(
-    name = "hbppacking",
-    srcs = ["//heron/packing/src/java:heron-binpacking-packing"],
-)
-
-filegroup(
-    name = "hrrpacking",
-    srcs = ["//heron/packing/src/java:heron-roundrobin-packing"],
-)
-
-filegroup(
-    name = "hmetricsmgr",
-    srcs = ["//heron/metricsmgr/src/java:heron-metricsmgr"],
-)
-
-filegroup(
-    name = "hexecutor",
-    srcs = ["//heron/executor/src/python:heron-executor"],
-)
-
-filegroup(
-    name = "hshell",
-    srcs = ["//heron/shell/src/python:heron-shell"],
-)
-
-filegroup(
-    name = "hcli",
-    srcs = ["//heron/tools/cli/src/python:heron"],
-)
-
-filegroup(
-    name = "haurora-job",
-    srcs = ["//heron/tools/cli/src/python:heron-aurora"],
-)
-
-filegroup(
-    name = "hinternals-config",
-    srcs = ["//heron/config:config-internals-yaml"],
-)
-
-filegroup(
-    name = "hcli2",
-    srcs = ["//heron/cli2/src/python:heron-cli2"],
-)
-
-filegroup(
-    name = "hscheduler-config",
-    srcs = ["//heron/cli2/src/python:scheduler-config"],
-)
-
-filegroup(
-    name = "haurora-scheduler-config",
-    srcs = ["//heron/cli2/src/python:aurora-scheduler-config"],
-)
-
-filegroup(
-    name = "hlocal-scheduler-config",
-    srcs = ["//heron/cli2/src/python:local-scheduler-config"],
-)
-
-filegroup(
-    name = "hmesos-scheduler-config",
-    srcs = ["//heron/cli2/src/python:mesos-scheduler-config"],
-)
-
-filegroup(
-    name = "hlfsstatemgr",
-    srcs = ["//heron/statemgrs/src/java:heron-localfs-statemgr"],
-)
-
-filegroup(
-    name = "hzkstatemgr",
-    srcs = ["//heron/statemgrs/src/java:heron-zookeeper-statemgr"],
-)
-
-filegroup(
-    name = "protobuf-java",
-    srcs = ["@com_google_protobuf_protobuf_java//jar"],
-)
-
-filegroup(
-    name = "slf4j-api-java",
-    srcs = ["@org_slf4j_slf4j_api//jar"],
-)
-
-filegroup(
-    name = "slf4j-jdk-java",
-    srcs = ["@org_slf4j_slf4j_jdk14//jar"],
-)
-
-filegroup(
-    name = "hapi",
-    srcs = ["//heron/api/src/java:heron-api"],
-)
-
-filegroup(
-    name = "hspi",
-    srcs = ["//heron/spi/src/java:heron-spi"],
-)
-
-filegroup(
-    name = "hmetrics-api",
-    srcs = ["//heron/metricsmgr-api/src/java:metricsmgr-api-java"],
-)
-
-filegroup(
-    name = "hstorm",
-    srcs = ["//heron/storm/src/java:heron-storm"],
-)
-
-filegroup(
-    name = "hviz",
-    srcs = ["//heron/viz/src/python:heron-viz"],
-)
-
-filegroup(
-    name = "htracker",
-    srcs = ["//heron/tools/tracker/src/python:heron-tracker"],
-)
-
-filegroup(
-    name = "hui",
-    srcs = ["//heron/tools/ui/src/python:heron-ui"],
-)
-
-filegroup(
-    name = "huploader-localfs",
-    srcs = ["//heron/uploaders/src/java:heron-localfs-uploader"],
-)
-
-filegroup(
-    name = "huploader-null",
-    srcs = ["//heron/uploaders/src/java:heron-null-uploader"],
-)
-
-genrule(
-    name = "generate-package-info",
-    outs = ["release.yaml"],
-    cmd = "$(location //scripts/packages:package-info-generator) $$(find . -name '*status*.txt') >$@",
-    stamp = 1,
-    tools = ["//scripts/packages:package-info-generator"],
-)
-
-genrule(
-    name = "generate-api-launcher",
-    srcs = [
-        ":release.yaml",
-        "//scripts/packages:api-template-bin.sh",
-        "//scripts/packages:bin-common.sh"
-    ],
-    outs = ["api_launcher_bin.sh"],
-    cmd = """
-        release_info="$$(cat $(location :release.yaml))"
-        bin_common="$$(cat $(location //scripts/packages:bin-common.sh))"
-        template="$$(cat $(location //scripts/packages:api-template-bin.sh))"
-        echo "$${bin_common}\n\n$${template//%release_info%/$${release_info}}" >$@
-        """,
-)
-
-genrule(
-    name = "generate-client-launcher",
-    srcs = [
-        ":release.yaml",
-        "//scripts/packages:client-template-bin.sh",
-        "//scripts/packages:bin-common.sh"
-    ],
-    outs = ["client_launcher_bin.sh"],
-    cmd = """
-        release_info="$$(cat $(location :release.yaml))"
-        bin_common="$$(cat $(location //scripts/packages:bin-common.sh))"
-        template="$$(cat $(location //scripts/packages:client-template-bin.sh))"
-        echo "$${bin_common}\n\n$${template//%release_info%/$${release_info}}" >$@
-        """,
-)
-
-genrule(
-    name = "generate-tools-launcher",
-    srcs = [
-        ":release.yaml",
-        "//scripts/packages:tools-template-bin.sh",
-        "//scripts/packages:bin-common.sh"
-    ],
-    outs = ["tools_launcher_bin.sh"],
-    cmd = """
-        release_info="$$(cat $(location :release.yaml))"
-        bin_common="$$(cat $(location //scripts/packages:bin-common.sh))"
-        template="$$(cat $(location //scripts/packages:tools-template-bin.sh))"
-        echo "$${bin_common}\n\n$${template//%release_info%/$${release_info}}" >$@
-        """,
-)
-
-load("/scripts/packages/self_extract_binary", "self_extract_binary")
-
-self_extract_binary(
-    name = "heron-api-install.sh",
-    flatten_resources = [
-        ":heron-api",
-    ],
-    launcher = ":api_launcher_bin.sh",
-)
-
-self_extract_binary(
-    name = "heron-client-install.sh",
-    flatten_resources = [
-        ":heron-client",
-    ],
-    launcher = ":client_launcher_bin.sh",
-)
-
-self_extract_binary(
-    name = "heron-tools-install.sh",
-    flatten_resources = [
-        ":heron-tools",
-    ],
-    launcher = ":tools_launcher_bin.sh",
-)
diff --git a/scripts/centos/package_release.sh b/scripts/centos/package_release.sh
deleted file mode 100755
index 71ee2d5..0000000
--- a/scripts/centos/package_release.sh
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/bin/bash -e
-# Copyright 2015 Google Inc. All rights reserved.
-#
-# Licensed 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.
-
-# Script to package a release tar and create its associated .md5 checksum.
-#
-# Usage: package_release.sh <path-to-output-tar.gz> [package contents]
-#
-# In the simplest case, each file given will be placed in the root of the
-# resulting archive.  The --relpath, --path, and --cp flags change this behavior
-# so that file paths can be structured.
-#
-#   --path <path>: Each file is copied to ARCHIVE_ROOT/<path>/$(basename file).
-#   --relpaths <prefix>: Strip $GENBIR, $BINDIR and then <prefix> from each
-#                        file's path.  The resulting path is used for the file
-#                        inside of the archive.  This combines with --path to
-#                        change the root of the resulting file path.
-#   --cp <path> <path>: Copy the first file to the archive using exactly the
-#                       second path.
-#
-# Example:
-#   BINDIR=bazel-bin/ \
-#     package_release.sh /tmp/b.tar README.adoc LICENSE \
-#       --path some/path/for/docs kythe/docs/kythe-{overview,storage}.txt \
-#       --relpaths kythe/docs bazel-bin/kythe/docs/schema/schema.html \
-#       --cp CONTRIBUTING.md kythe/docs/how-to-contribute.md
-#
-#   Resulting tree in /tmp/b.tar:
-#     README.adoc
-#     LICENSE
-#     kythe/docs/
-#       kythe-overview.txt
-#       kythe-storage.txt
-#       schema.html
-#       how-to-contribute.md
-
-OUT="$1"
-shift
-
-PBASE="$OUT.dir"
-P=$PBASE
-
-mkdir -p "$PBASE"
-trap "rm -rf '$PWD/$OUT.dir'" EXIT ERR INT
-
-while [[ $# -gt 0 ]]; do
-  case "$1" in
-    --relpaths)
-      RELPATHS=$2
-      shift
-      ;;
-    --path)
-      P="$PBASE/$2"
-      mkdir -p "$P"
-      shift
-      ;;
-    --cp)
-      mkdir -p "$PBASE/$(dirname "$3")"
-      cp "$2" "$PBASE/$3"
-      shift 2
-      ;;
-    *)
-      if [[ -z "$RELPATHS" ]]; then
-        cp "$1" "$P"/
-      else
-        rp="${1#$GENDIR/}"
-        rp="${rp#$BINDIR/}"
-        rp="$(dirname "${rp#$RELPATHS/}")"
-        mkdir -p "$P/$rp"
-        cp "$1" "$P/$rp"
-      fi
-      ;;
-  esac
-  shift
-done
-
-tar czf "$OUT" -C "$OUT.dir" .
-
-cd "$(dirname "$OUT")"
-# md5sum "$(basename "$OUT")".gz > "$(basename "$OUT").gz.md5"