Create 'fluo remove' command #991
diff --git a/modules/api/src/main/java/org/apache/fluo/api/client/FluoAdmin.java b/modules/api/src/main/java/org/apache/fluo/api/client/FluoAdmin.java
index fb25759..12f43f7 100644
--- a/modules/api/src/main/java/org/apache/fluo/api/client/FluoAdmin.java
+++ b/modules/api/src/main/java/org/apache/fluo/api/client/FluoAdmin.java
@@ -120,6 +120,14 @@
       throws AlreadyInitializedException, TableExistsException;
 
   /**
+   * Removes Fluo application and shared configuration in Zookeeper. Shared configuration
+   * consists of all properties except those with
+   * {@value org.apache.fluo.api.config.FluoConfiguration#CONNECTION_PREFIX} prefix.
+   */
+  void remove(InitializationOptions opts)
+      throws AlreadyInitializedException, TableExistsException;
+
+  /**
    * Updates shared configuration in Zookeeper. Shared configuration consists of all properties
    * except those with {@value org.apache.fluo.api.config.FluoConfiguration#CONNECTION_PREFIX}
    * prefix. This method is called if a user has previously called
diff --git a/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java b/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java
new file mode 100644
index 0000000..2feb0e7
--- /dev/null
+++ b/modules/command/src/main/java/org/apache/fluo/command/FluoRemove.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.fluo.command;
+
+import java.io.File;
+
+import com.beust.jcommander.Parameter;
+import com.google.common.base.Preconditions;
+import org.apache.fluo.api.client.FluoAdmin;
+import org.apache.fluo.api.config.FluoConfiguration;
+import org.apache.fluo.core.client.FluoAdminImpl;
+
+public class FluoRemove {
+
+  public static class RemoveOptions extends CommonOpts {
+
+    @Parameter(names = "-p", required = true, description = "Path to application properties file")
+    private String appPropsPath;
+
+    String getAppPropsPath() {
+      return appPropsPath;
+    }
+
+    public static RemoveOptions parse(String[] args) {
+      RemoveOptions opts = new RemoveOptions();
+      parse("fluo remove", opts, args);
+      return opts;
+    }
+  }
+
+  public static void main(String[] args) {
+    RemoveOptions opts = RemoveOptions.parse(args);
+    File applicationPropsFile = new File(opts.getAppPropsPath());
+    Preconditions.checkArgument(applicationPropsFile.exists(),
+        opts.getAppPropsPath() + " does not exist");
+
+    FluoConfiguration config = CommandUtil.resolveFluoConfig();
+    config.load(applicationPropsFile);
+    config.setApplicationName(opts.getApplicationName());
+    opts.overrideFluoConfig(config);
+
+    if (!config.hasRequiredAdminProps()) {
+      System.err.println("Error - Required properties are not set in " + opts.getAppPropsPath());
+      System.exit(-1);
+    }
+    try {
+      config.validate();
+    } catch (Exception e) {
+      System.err.println("Error - Invalid configuration due to " + e.getMessage());
+      System.exit(-1);
+    }
+
+    try (FluoAdminImpl admin = new FluoAdminImpl(config)) {
+
+      if (admin.applicationRunning()) {
+        System.err.println("Error - The Fluo '" + config.getApplicationName() + "' application"
+            + " is already running and must be stopped before running 'fluo remove'. "
+            + " Aborted remove.");
+        System.exit(-1);
+      }
+
+      FluoAdmin.InitializationOptions initOpts = new FluoAdmin.InitializationOptions();
+      initOpts.setClearZookeeper(true).setClearTable(true);
+      initOpts.setClearTable(true);
+
+      System.out.println("Removing Fluo '" + config.getApplicationName() + "' application using "
+          + opts.getAppPropsPath());
+      try {
+        admin.remove(initOpts);
+      } catch (Exception e) {
+        System.out.println("Remove failed due to the following exception:");
+        e.printStackTrace();
+        System.exit(-1);
+      }
+      System.out.println("Remove is complete.");
+
+    }
+  }
+}
diff --git a/modules/core/src/main/java/org/apache/fluo/core/client/FluoAdminImpl.java b/modules/core/src/main/java/org/apache/fluo/core/client/FluoAdminImpl.java
index e711705..25bb839 100644
--- a/modules/core/src/main/java/org/apache/fluo/core/client/FluoAdminImpl.java
+++ b/modules/core/src/main/java/org/apache/fluo/core/client/FluoAdminImpl.java
@@ -201,6 +201,45 @@
     }
   }
 
+  @Override
+  public void remove(InitializationOptions opts)
+      throws AlreadyInitializedException, TableExistsException {
+    if (!config.hasRequiredAdminProps()) {
+      throw new IllegalArgumentException("Admin configuration is missing required properties");
+    }
+    Preconditions.checkArgument(
+        !ZookeeperUtil.parseRoot(config.getInstanceZookeepers()).equals("/"),
+        "The Zookeeper connection string (set by 'fluo.connection.zookeepers') "
+            + " must have a chroot suffix.");
+
+    Connector conn = AccumuloUtil.getConnector(config);
+
+    boolean tableExists = conn.tableOperations().exists(config.getAccumuloTable());
+    // With preconditions met, it's now OK to delete table & zookeeper root (if they exist)
+    if (tableExists) {
+      logger.info("The Accumulo table '{}' will be dropped", config.getAccumuloTable());
+      try {
+        conn.tableOperations().delete(config.getAccumuloTable());
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    try {
+      if (rootCurator.checkExists().forPath(appRootDir) != null) {
+        logger.info("Clearing Fluo '{}' application in Zookeeper at {}",
+            config.getApplicationName(), config.getAppZookeepers());
+        rootCurator.delete().deletingChildrenIfNeeded().forPath(appRootDir);
+      }
+    } catch (KeeperException.NoNodeException nne) {
+      // it's ok if node doesn't exist
+    } catch (Exception e) {
+      logger.error("An error occurred deleting Zookeeper root of [" + config.getAppZookeepers()
+          + "], error=[" + e.getMessage() + "]");
+      throw new RuntimeException(e);
+    }
+  }
+
   private void initializeApplicationInZooKeeper(Connector conn) throws Exception {
 
     final String accumuloInstanceName = conn.getInstance().getInstanceName();
diff --git a/modules/distribution/src/main/scripts/fluo b/modules/distribution/src/main/scripts/fluo
index 4116669..7239159 100755
--- a/modules/distribution/src/main/scripts/fluo
+++ b/modules/distribution/src/main/scripts/fluo
@@ -66,6 +66,7 @@
   echo -e "Usage: fluo <command> (<argument> ...)\n"
   echo -e "Possible commands:\n"
   echo "  init -a <app> -p <appProps>   Initializes Fluo application for <app> using <appProps>. Run with -h to see additional args."
+  echo "  remove -a <app> -p <appProps> Removes Fluo application for <app> using <appProps>."
   echo "  classpath                     Prints the classpath setup in fluo-env.sh"
   echo "  config -a <app>               Prints application configuration stored in Zookeeper for <app>"
   echo "  get-jars -a <app> -d <dir>    Copies <app> jars from DFS to local <dir>"
@@ -184,6 +185,25 @@
     java org.apache.fluo.cluster.command.FluoCommand "$basedir" "$HADOOP_PREFIX" "$@"
   fi
   ;;
+remove)
+  if [ -f "$FLUO_CONN_PROPS" ]; then
+    if [[ $2 = *"-h"* ]]; then
+      $JAVA org.apache.fluo.command.FluoRemove -h
+      exit 0
+    fi
+    init_dir=$($JAVA org.apache.fluo.command.FluoInit "${@:2}" --retrieveProperty fluo.observer.init.dir)
+    if [ -d "$init_dir" ]; then
+      echo "Adding $init_dir/* to CLASSPATH"
+      export CLASSPATH="$init_dir/*:$CLASSPATH"
+    fi
+    $JAVA org.apache.fluo.command.FluoRemove "${@:2}"
+  else
+    deprecated_verify_full "$2"
+    check_hadoop
+    export CLASSPATH="$APP_LIB_DIR/*:$CLASSPATH"
+    java org.apache.fluo.cluster.command.FluoCommand "$basedir" "$HADOOP_PREFIX" "$@"
+  fi
+  ;;
 oracle)
   if [[ $2 = *"-h"* ]]; then
     $JAVA org.apache.fluo.command.FluoOracle -h
@@ -208,7 +228,7 @@
     java org.apache.fluo.cluster.command.FluoCommand "$basedir" "$HADOOP_PREFIX" "$@"
   fi
   ;;
-ps) 
+ps)
   jps -m | grep Fluo
   ;;
 list)