You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by zz...@apache.org on 2013/11/01 00:48:34 UTC

git commit: [HELIX-285] add integration test util's, rb=15160

Updated Branches:
  refs/heads/helix-0.6.2-release 711561b8a -> f2c245f79


[HELIX-285] add integration test util's, rb=15160


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

Branch: refs/heads/helix-0.6.2-release
Commit: f2c245f79d9abdcff6366153a7ed6246a219f872
Parents: 711561b
Author: zzhang <zz...@apache.org>
Authored: Thu Oct 31 16:48:16 2013 -0700
Committer: zzhang <zz...@apache.org>
Committed: Thu Oct 31 16:48:16 2013 -0700

----------------------------------------------------------------------
 helix-core/pom.xml                              |   4 +
 .../helix/controller/HelixControllerMain.java   |   4 +
 .../apache/helix/examples/ExampleProcess.java   |  13 +-
 .../manager/zk/HelixManagerShutdownHook.java    |  26 +++
 .../tools/ClusterExternalViewVerifier.java      | 151 +++++++++++++++
 .../helix/tools/ClusterLiveNodesVerifier.java   |  26 +++
 .../org/apache/helix/tools/ClusterVerifier.java | 128 +++++++++++++
 .../apache/helix/tools/IntegrationTestUtil.java | 185 +++++++++++++++++++
 8 files changed, 535 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/pom.xml
----------------------------------------------------------------------
diff --git a/helix-core/pom.xml b/helix-core/pom.xml
index 0c42af8..c6d2fca 100644
--- a/helix-core/pom.xml
+++ b/helix-core/pom.xml
@@ -226,6 +226,10 @@ under the License.
               <mainClass>org.apache.helix.tools.YAMLClusterSetup</mainClass>
               <name>yaml-cluster-setup</name>
             </program>
+            <program>
+              <mainClass>org.apache.helix.tools.IntegrationTestUtil</mainClass>
+              <name>test-util</name>
+            </program>
           </programs>
         </configuration>
       </plugin>

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/controller/HelixControllerMain.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/controller/HelixControllerMain.java b/helix-core/src/main/java/org/apache/helix/controller/HelixControllerMain.java
index 4aae39b..62f3b23 100644
--- a/helix-core/src/main/java/org/apache/helix/controller/HelixControllerMain.java
+++ b/helix-core/src/main/java/org/apache/helix/controller/HelixControllerMain.java
@@ -48,6 +48,7 @@ import org.apache.helix.HelixManager;
 import org.apache.helix.HelixManagerFactory;
 import org.apache.helix.InstanceType;
 import org.apache.helix.controller.restlet.ZKPropertyTransferServer;
+import org.apache.helix.manager.zk.HelixManagerShutdownHook;
 import org.apache.helix.participant.DistClusterControllerStateModelFactory;
 import org.apache.helix.participant.StateMachineEngine;
 import org.apache.log4j.Logger;
@@ -233,6 +234,9 @@ public class HelixControllerMain {
 
     HelixManager manager =
         startHelixController(zkConnectString, clusterName, controllerName, controllerMode);
+
+    Runtime.getRuntime().addShutdownHook(new HelixManagerShutdownHook(manager));
+
     try {
       Thread.currentThread().join();
     } catch (InterruptedException e) {

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/examples/ExampleProcess.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/examples/ExampleProcess.java b/helix-core/src/main/java/org/apache/helix/examples/ExampleProcess.java
index 34a13e5..e023cf9 100644
--- a/helix-core/src/main/java/org/apache/helix/examples/ExampleProcess.java
+++ b/helix-core/src/main/java/org/apache/helix/examples/ExampleProcess.java
@@ -33,13 +33,16 @@ import org.apache.commons.cli.ParseException;
 import org.apache.helix.HelixManager;
 import org.apache.helix.HelixManagerFactory;
 import org.apache.helix.InstanceType;
+import org.apache.helix.manager.zk.HelixManagerShutdownHook;
 import org.apache.helix.model.Message.MessageType;
 import org.apache.helix.participant.StateMachineEngine;
 import org.apache.helix.participant.statemachine.StateModel;
 import org.apache.helix.participant.statemachine.StateModelFactory;
 import org.apache.helix.tools.ClusterStateVerifier;
+import org.apache.log4j.Logger;
 
 public class ExampleProcess {
+  private static final Logger LOG = Logger.getLogger(ExampleProcess.class);
 
   public static final String zkServer = "zkSvr";
   public static final String cluster = "cluster";
@@ -57,8 +60,6 @@ public class ExampleProcess {
   private final String stateModelType;
   private HelixManager manager;
 
-  // private StateMachineEngine genericStateMachineHandler;
-
   private StateModelFactory<StateModel> stateModelFactory;
   private final int delay;
 
@@ -98,6 +99,10 @@ public class ExampleProcess {
     manager.disconnect();
   }
 
+  public HelixManager getManager() {
+    return manager;
+  }
+
   @SuppressWarnings("static-access")
   private static Options constructCommandLineOptions() {
     Option helpOption =
@@ -168,6 +173,7 @@ public class ExampleProcess {
 
   public static void printUsage(Options cliOptions) {
     HelpFormatter helpFormatter = new HelpFormatter();
+    helpFormatter.setWidth(1000);
     helpFormatter.printHelp("java " + ExampleProcess.class.getName(), cliOptions);
   }
 
@@ -232,6 +238,9 @@ public class ExampleProcess {
         new ExampleProcess(zkConnectString, clusterName, instanceName, file, stateModelValue, delay);
 
     process.start();
+
+    Runtime.getRuntime().addShutdownHook(new HelixManagerShutdownHook(process.getManager()));
+
     Thread.currentThread().join();
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/manager/zk/HelixManagerShutdownHook.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/HelixManagerShutdownHook.java b/helix-core/src/main/java/org/apache/helix/manager/zk/HelixManagerShutdownHook.java
new file mode 100644
index 0000000..f91bf71
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/HelixManagerShutdownHook.java
@@ -0,0 +1,26 @@
+package org.apache.helix.manager.zk;
+
+import org.apache.helix.HelixManager;
+import org.apache.log4j.Logger;
+
+/**
+ * Shutdown hook for helix manager
+ * Working for kill -2/-15
+ * NOT working for kill -9
+ */
+public class HelixManagerShutdownHook extends Thread {
+  private static Logger LOG = Logger.getLogger(HelixManagerShutdownHook.class);
+
+  final HelixManager _manager;
+
+  public HelixManagerShutdownHook(HelixManager manager) {
+    _manager = manager;
+  }
+
+  @Override
+  public void run() {
+    LOG.info("HelixControllerMainShutdownHook invoked on manager: " + _manager.getClusterName()
+        + ", " + _manager.getInstanceName());
+    _manager.disconnect();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/tools/ClusterExternalViewVerifier.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterExternalViewVerifier.java
new file mode 100644
index 0000000..b72170f
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterExternalViewVerifier.java
@@ -0,0 +1,151 @@
+package org.apache.helix.tools;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.helix.controller.pipeline.Stage;
+import org.apache.helix.controller.pipeline.StageContext;
+import org.apache.helix.controller.stages.AttributeName;
+import org.apache.helix.controller.stages.BestPossibleStateCalcStage;
+import org.apache.helix.controller.stages.BestPossibleStateOutput;
+import org.apache.helix.controller.stages.ClusterDataCache;
+import org.apache.helix.controller.stages.ClusterEvent;
+import org.apache.helix.controller.stages.CurrentStateComputationStage;
+import org.apache.helix.controller.stages.ResourceComputationStage;
+import org.apache.helix.manager.zk.ZkClient;
+import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.Partition;
+import org.apache.log4j.Logger;
+
+/**
+ * given zk, cluster, and a list of expected live-instances
+ * check whether cluster's external-view reaches best-possible states
+ */
+public class ClusterExternalViewVerifier extends ClusterVerifier {
+  private static Logger LOG = Logger.getLogger(ClusterExternalViewVerifier.class);
+
+  final List<String> _expectSortedLiveNodes; // always sorted
+
+  public ClusterExternalViewVerifier(ZkClient zkclient, String clusterName,
+      List<String> expectLiveNodes) {
+    super(zkclient, clusterName);
+    _expectSortedLiveNodes = expectLiveNodes;
+    Collections.sort(_expectSortedLiveNodes);
+  }
+
+  boolean verifyLiveNodes(List<String> actualLiveNodes) {
+    Collections.sort(actualLiveNodes);
+    return _expectSortedLiveNodes.equals(actualLiveNodes);
+  }
+
+  /**
+   * @param externalView
+   * @param bestPossibleState map of partition to map of instance to state
+   * @return
+   */
+  boolean verifyExternalView(ExternalView externalView,
+      Map<Partition, Map<String, String>> bestPossibleState) {
+    Map<String, Map<String, String>> bestPossibleStateMap =
+        convertBestPossibleState(bestPossibleState);
+    // trimBestPossibleState(bestPossibleStateMap);
+
+    Map<String, Map<String, String>> externalViewMap = externalView.getRecord().getMapFields();
+    return externalViewMap.equals(bestPossibleStateMap);
+  }
+
+  static void runStage(ClusterEvent event, Stage stage) throws Exception {
+    StageContext context = new StageContext();
+    stage.init(context);
+    stage.preProcess();
+    stage.process(event);
+    stage.postProcess();
+  }
+
+  BestPossibleStateOutput calculateBestPossibleState(ClusterDataCache cache) throws Exception {
+    ClusterEvent event = new ClusterEvent("event");
+    event.addAttribute("ClusterDataCache", cache);
+
+    List<Stage> stages = new ArrayList<Stage>();
+    stages.add(new ResourceComputationStage());
+    stages.add(new CurrentStateComputationStage());
+    stages.add(new BestPossibleStateCalcStage());
+
+    for (Stage stage : stages) {
+      runStage(event, stage);
+    }
+
+    return event.getAttribute(AttributeName.BEST_POSSIBLE_STATE.toString());
+  }
+
+  /**
+   * remove empty map and DROPPED state from best possible state
+   * @param bestPossibleState
+   */
+  // static void trimBestPossibleState(Map<String, Map<String, String>> bestPossibleState) {
+  // Iterator<Entry<String, Map<String, String>>> iter = bestPossibleState.entrySet().iterator();
+  // while (iter.hasNext()) {
+  // Map.Entry<String, Map<String, String>> entry = iter.next();
+  // Map<String, String> instanceStateMap = entry.getValue();
+  // if (instanceStateMap.isEmpty()) {
+  // iter.remove();
+  // } else {
+  // // remove instances with DROPPED state
+  // Iterator<Map.Entry<String, String>> insIter = instanceStateMap.entrySet().iterator();
+  // while (insIter.hasNext()) {
+  // Map.Entry<String, String> insEntry = insIter.next();
+  // String state = insEntry.getValue();
+  // if (state.equalsIgnoreCase(HelixDefinedState.DROPPED.toString())) {
+  // insIter.remove();
+  // }
+  // }
+  // }
+  // }
+  // }
+
+  static Map<String, Map<String, String>> convertBestPossibleState(
+      Map<Partition, Map<String, String>> bestPossibleState) {
+    Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
+    for (Partition partition : bestPossibleState.keySet()) {
+      result.put(partition.getPartitionName(), bestPossibleState.get(partition));
+    }
+    return result;
+  }
+
+  @Override
+  public boolean verify() throws Exception {
+    ClusterDataCache cache = new ClusterDataCache();
+    cache.refresh(_accessor);
+
+    List<String> liveInstances = new ArrayList<String>();
+    liveInstances.addAll(cache.getLiveInstances().keySet());
+    boolean success = verifyLiveNodes(liveInstances);
+    if (!success) {
+      LOG.info("liveNodes not match, expect: " + _expectSortedLiveNodes + ", actual: "
+          + liveInstances);
+      return false;
+    }
+
+    BestPossibleStateOutput bestPossbileStates = calculateBestPossibleState(cache);
+    Map<String, ExternalView> externalViews =
+        _accessor.getChildValuesMap(_keyBuilder.externalViews());
+
+    // TODO all ideal-states should be included in external-views
+
+    for (String resourceName : externalViews.keySet()) {
+      ExternalView externalView = externalViews.get(resourceName);
+      Map<Partition, Map<String, String>> bestPossbileState =
+          bestPossbileStates.getResourceMap(resourceName);
+      success = verifyExternalView(externalView, bestPossbileState);
+      if (!success) {
+        LOG.info("external-view for resource: " + resourceName + " not match");
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/tools/ClusterLiveNodesVerifier.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterLiveNodesVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterLiveNodesVerifier.java
new file mode 100644
index 0000000..2912247
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterLiveNodesVerifier.java
@@ -0,0 +1,26 @@
+package org.apache.helix.tools;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.helix.manager.zk.ZkClient;
+
+public class ClusterLiveNodesVerifier extends ClusterVerifier {
+
+  final List<String> _expectSortedLiveNodes; // always sorted
+
+  public ClusterLiveNodesVerifier(ZkClient zkclient, String clusterName,
+      List<String> expectLiveNodes) {
+    super(zkclient, clusterName);
+    _expectSortedLiveNodes = expectLiveNodes;
+    Collections.sort(_expectSortedLiveNodes);
+  }
+
+  @Override
+  public boolean verify() throws Exception {
+    List<String> actualLiveNodes = _accessor.getChildNames(_keyBuilder.liveInstances());
+    Collections.sort(actualLiveNodes);
+    return _expectSortedLiveNodes.equals(actualLiveNodes);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifier.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifier.java
new file mode 100644
index 0000000..ca8eea7
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifier.java
@@ -0,0 +1,128 @@
+package org.apache.helix.tools;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.I0Itec.zkclient.IZkChildListener;
+import org.I0Itec.zkclient.IZkDataListener;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZKHelixDataAccessor;
+import org.apache.helix.manager.zk.ZkBaseDataAccessor;
+import org.apache.helix.manager.zk.ZkClient;
+import org.apache.log4j.Logger;
+
+public abstract class ClusterVerifier implements IZkChildListener, IZkDataListener {
+  private static Logger LOG = Logger.getLogger(ClusterVerifier.class);
+
+  protected final ZkClient _zkclient;
+  protected final String _clusterName;
+  protected final HelixDataAccessor _accessor;
+  protected final PropertyKey.Builder _keyBuilder;
+  private CountDownLatch _countdown;
+
+  static class ClusterVerifyTrigger {
+    final PropertyKey _triggerKey;
+    final boolean _triggerOnChildDataChange;
+
+    public ClusterVerifyTrigger(PropertyKey triggerKey, boolean triggerOnChildDataChange) {
+      _triggerKey = triggerKey;
+      _triggerOnChildDataChange = triggerOnChildDataChange;
+    }
+  }
+
+  public ClusterVerifier(ZkClient zkclient, String clusterName) {
+    _zkclient = zkclient;
+    _clusterName = clusterName;
+    _accessor = new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(zkclient));
+    _keyBuilder = _accessor.keyBuilder();
+  }
+
+  public boolean verifyByCallback(long timeout, List<ClusterVerifyTrigger> triggers) {
+    _countdown = new CountDownLatch(1);
+
+    for (ClusterVerifyTrigger trigger : triggers) {
+      String path = trigger._triggerKey.getPath();
+      _zkclient.subscribeChildChanges(path, this);
+      if (trigger._triggerOnChildDataChange) {
+        List<String> childs = _zkclient.getChildren(path);
+        for (String child : childs) {
+          String childPath = String.format("%s/%s", path, child);
+          _zkclient.subscribeDataChanges(childPath, this);
+        }
+      }
+    }
+
+    boolean success = false;
+    try {
+      success = verify();
+      if (!success) {
+
+        success = _countdown.await(timeout, TimeUnit.MILLISECONDS);
+        if (!success) {
+          // make a final try if timeout
+          success = verify();
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Exception in verifier", e);
+    }
+
+    // clean up
+    _zkclient.unsubscribeAll();
+
+    return success;
+  }
+
+  @Override
+  public void handleDataChange(String dataPath, Object data) throws Exception {
+    boolean success = verify();
+    if (success) {
+      _countdown.countDown();
+    }
+  }
+
+  @Override
+  public void handleDataDeleted(String dataPath) throws Exception {
+    _zkclient.unsubscribeDataChanges(dataPath, this);
+  }
+
+  @Override
+  public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
+    for (String child : currentChilds) {
+      String childPath = String.format("%s/%s", parentPath, child);
+      _zkclient.subscribeDataChanges(childPath, this);
+    }
+
+    boolean success = verify();
+    if (success) {
+      _countdown.countDown();
+    }
+  }
+
+  public boolean verifyByPolling(long timeout) {
+    try {
+      long start = System.currentTimeMillis();
+      boolean success;
+      do {
+        success = verify();
+        if (success) {
+          return true;
+        }
+        TimeUnit.MILLISECONDS.sleep(500);
+      } while ((System.currentTimeMillis() - start) <= timeout);
+    } catch (Exception e) {
+      LOG.error("Exception in verifier", e);
+    }
+    return false;
+  }
+
+  /**
+   * verify
+   * @return
+   * @throws Exception
+   */
+  public abstract boolean verify() throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/incubator-helix/blob/f2c245f7/helix-core/src/main/java/org/apache/helix/tools/IntegrationTestUtil.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/tools/IntegrationTestUtil.java b/helix-core/src/main/java/org/apache/helix/tools/IntegrationTestUtil.java
new file mode 100644
index 0000000..8c70bbc
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/tools/IntegrationTestUtil.java
@@ -0,0 +1,185 @@
+package org.apache.helix.tools;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.ZkClient;
+import org.apache.log4j.Logger;
+
+/**
+ * collection of test utilities for integration tests
+ */
+public class IntegrationTestUtil {
+  private static Logger LOG = Logger.getLogger(IntegrationTestUtil.class);
+
+  public static final long defaultTimeout = 30 * 1000; // in milliseconds
+  public static final String help = "help";
+  public static final String zkSvr = "zkSvr";
+
+  public static final String verifyExternalView = "verifyExternalView";
+  public static final String verifyLiveNodes = "verifyLiveNodes";
+  public static final String readZNode = "readZNode";
+  public static final String readLeader = "readLeader";
+
+  final ZkClient _zkclient;
+  final ZNRecordSerializer _serializer;
+
+  public IntegrationTestUtil(ZkClient zkclient) {
+    _zkclient = zkclient;
+    _serializer = new ZNRecordSerializer();
+  }
+
+  public void verifyExternalView(String[] args) {
+    if (args == null || args.length == 0) {
+      System.err.println("Illegal arguments for " + verifyExternalView);
+      return;
+    }
+
+    long timeoutValue = defaultTimeout;
+
+    String clusterName = args[0];
+    List<String> liveNodes = new ArrayList<String>();
+    for (int i = 1; i < args.length; i++) {
+      liveNodes.add(args[i]);
+    }
+
+    ClusterExternalViewVerifier verifier =
+        new ClusterExternalViewVerifier(_zkclient, clusterName, liveNodes);
+    boolean success = verifier.verifyByPolling(timeoutValue);
+    System.out.println(success ? "Successful" : "Failed");
+
+  }
+
+  public void verifyLiveNodes(String[] args) {
+    if (args == null || args.length == 0) {
+      System.err.println("Illegal arguments for " + verifyLiveNodes);
+      return;
+    }
+
+    long timeoutValue = defaultTimeout;
+
+    String clusterName = args[0];
+    List<String> liveNodes = new ArrayList<String>();
+    for (int i = 1; i < args.length; i++) {
+      liveNodes.add(args[i]);
+    }
+
+    ClusterLiveNodesVerifier verifier =
+        new ClusterLiveNodesVerifier(_zkclient, clusterName, liveNodes);
+    boolean success = verifier.verifyByPolling(timeoutValue);
+    System.out.println(success ? "Successful" : "Failed");
+  }
+
+  public void readZNode(String path) {
+    ZNRecord record = _zkclient.readData(path, true);
+    if (record == null) {
+      System.out.println("null");
+    } else {
+      System.out.println(new String(_serializer.serialize(record)));
+    }
+  }
+
+  @SuppressWarnings("static-access")
+  static Options constructCommandLineOptions() {
+    Option helpOption =
+        OptionBuilder.withLongOpt(help).withDescription("Prints command-line options information")
+            .create();
+
+    Option zkSvrOption =
+        OptionBuilder.hasArgs(1).isRequired(true).withArgName("zookeeperAddress")
+            .withLongOpt(zkSvr).withDescription("Provide zookeeper-address").create();
+
+    Option verifyExternalViewOption =
+        OptionBuilder.hasArgs().isRequired(false).withArgName("clusterName node1 node2..")
+            .withLongOpt(verifyExternalView).withDescription("Verify external-view").create();
+
+    Option verifyLiveNodesOption =
+        OptionBuilder.hasArg().isRequired(false).withArgName("clusterName node1, node2..")
+            .withLongOpt(verifyLiveNodes).withDescription("Verify live-nodes").create();
+
+    Option readZNodeOption =
+        OptionBuilder.hasArgs(1).isRequired(false).withArgName("zkPath").withLongOpt(readZNode)
+            .withDescription("Read znode").create();
+
+    Option readLeaderOption =
+        OptionBuilder.hasArgs(1).isRequired(false).withArgName("clusterName")
+            .withLongOpt(readLeader).withDescription("Read cluster controller").create();
+
+    OptionGroup optGroup = new OptionGroup();
+    optGroup.setRequired(true);
+    optGroup.addOption(verifyExternalViewOption);
+    optGroup.addOption(verifyLiveNodesOption);
+    optGroup.addOption(readZNodeOption);
+    optGroup.addOption(readLeaderOption);
+
+    Options options = new Options();
+    options.addOption(helpOption);
+    options.addOption(zkSvrOption);
+    options.addOptionGroup(optGroup);
+
+    return options;
+  }
+
+  static void printUsage(Options cliOptions) {
+    HelpFormatter helpFormatter = new HelpFormatter();
+    helpFormatter.setWidth(1000);
+    helpFormatter.printHelp("java " + ClusterExternalViewVerifier.class.getName(), cliOptions);
+  }
+
+  static void processCommandLineArgs(String[] cliArgs) {
+    CommandLineParser cliParser = new GnuParser();
+    Options cliOptions = constructCommandLineOptions();
+    CommandLine cmd = null;
+    try {
+      cmd = cliParser.parse(cliOptions, cliArgs);
+    } catch (ParseException pe) {
+      System.err.println("failed to parse command-line args: " + Arrays.asList(cliArgs)
+          + ", exception: " + pe.toString());
+      printUsage(cliOptions);
+      System.exit(1);
+    }
+
+    String zkServer = cmd.getOptionValue(zkSvr);
+
+    ZkClient zkclient =
+        new ZkClient(zkServer, ZkClient.DEFAULT_SESSION_TIMEOUT,
+            ZkClient.DEFAULT_CONNECTION_TIMEOUT, new ZNRecordSerializer());
+    IntegrationTestUtil util = new IntegrationTestUtil(zkclient);
+
+    if (cmd != null) {
+      if (cmd.hasOption(verifyExternalView)) {
+        String[] args = cmd.getOptionValues(verifyExternalView);
+        util.verifyExternalView(args);
+      } else if (cmd.hasOption(verifyLiveNodes)) {
+        String[] args = cmd.getOptionValues(verifyLiveNodes);
+        util.verifyLiveNodes(args);
+      } else if (cmd.hasOption(readZNode)) {
+        String path = cmd.getOptionValue(readZNode);
+        util.readZNode(path);
+      } else if (cmd.hasOption(readLeader)) {
+        String clusterName = cmd.getOptionValue(readLeader);
+        PropertyKey.Builder keyBuilder = new PropertyKey.Builder(clusterName);
+        util.readZNode(keyBuilder.controllerLeader().getPath());
+      } else {
+        printUsage(cliOptions);
+      }
+    }
+  }
+
+  public static void main(String[] args) {
+    processCommandLineArgs(args);
+  }
+}