浏览代码

HDFS-11162. Block Storage: add command line tool. Contributed by Chen Liang.

Anu Engineer 8 年之前
父节点
当前提交
1e892f5748

+ 9 - 3
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/CBlockConfigKeys.java

@@ -25,13 +25,19 @@ public final class CBlockConfigKeys {
       "dfs.cblock.enabled";
   public static final String DFS_CBLOCK_SERVICERPC_ADDRESS_KEY =
       "dfs.cblock.servicerpc-address";
-  public static final int DFS_CBLOCK_RPCSERVICE_PORT_DEFAULT =
+  public static final String DFS_CBLOCK_SERVICERPC_PORT_KEY =
+      "dfs.cblock.servicerpc.port";
+  public static final int DFS_CBLOCK_SERVICERPC_PORT_DEFAULT =
       9810;
+  public static final String DFS_CBLOCK_SERVICERPC_HOSTNAME_KEY =
+      "dfs.cblock.servicerpc.hostname";
+  public static final String DFS_CBLOCK_SERVICERPC_HOSTNAME_DEFAULT =
+      "0.0.0.0";
   public static final String DFS_CBLOCK_RPCSERVICE_IP_DEFAULT =
       "0.0.0.0";
   public static final String DFS_CBLOCK_SERVICERPC_ADDRESS_DEFAULT =
-      DFS_CBLOCK_RPCSERVICE_IP_DEFAULT
-          + ":" + DFS_CBLOCK_RPCSERVICE_PORT_DEFAULT;
+      DFS_CBLOCK_SERVICERPC_HOSTNAME_DEFAULT
+          + ":" + DFS_CBLOCK_SERVICERPC_PORT_DEFAULT;
 
   public static final String DFS_CBLOCK_JSCSIRPC_ADDRESS_KEY =
       "dfs.cblock.jscsi-address";

+ 284 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/cli/CBlockCli.java

@@ -0,0 +1,284 @@
+/*
+ * 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.hadoop.cblock.cli;
+
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.hadoop.cblock.client.CBlockVolumeClient;
+import org.apache.hadoop.cblock.meta.VolumeInfo;
+import org.apache.hadoop.cblock.protocolPB.CBlockServiceProtocolPB;
+import org.apache.hadoop.conf.Configured;
+import org.apache.hadoop.ipc.ProtobufRpcEngine;
+import org.apache.hadoop.ipc.RPC;
+import org.apache.hadoop.ozone.OzoneConfiguration;
+import org.apache.hadoop.util.Tool;
+import org.apache.hadoop.util.ToolRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The command line tool class.
+ */
+public class CBlockCli extends Configured implements Tool {
+
+  private static final String CREATE_VOLUME = "createVolume";
+
+  private static final String DELETE_VOLUME = "deleteVolume";
+
+  private static final String INFO_VOLUME = "infoVolume";
+
+  private static final String LIST_VOLUME = "listVolume";
+
+  private static final String SERVER_ADDR = "serverAddr";
+
+  private static final String HELP = "help";
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(CBlockCli.class);
+  private OzoneConfiguration conf;
+
+  private PrintStream printStream;
+
+  private Options options;
+
+  private BasicParser parser;
+
+  private CBlockVolumeClient localProxy;
+
+  public CBlockCli(OzoneConfiguration conf, PrintStream printStream)
+      throws IOException {
+    this.printStream = printStream;
+    this.conf = conf;
+    this.options = getOptions();
+    this.parser = new BasicParser();
+  }
+
+  public CBlockCli(OzoneConfiguration conf) throws IOException{
+    this(conf, System.out);
+  }
+
+  private CommandLine parseArgs(String[] argv)
+      throws ParseException {
+    return parser.parse(options, argv);
+  }
+
+  private static Options getOptions() {
+    Options options = new Options();
+    Option serverAddress = OptionBuilder
+        .withArgName("serverAddress>:<serverPort")
+        .withLongOpt(SERVER_ADDR)
+        .withValueSeparator(':')
+        .hasArgs(2)
+        .withDescription("specify server address:port")
+        .create("s");
+    options.addOption(serverAddress);
+
+    // taking 4 args: userName, volumeName, volumeSize, blockSize
+    Option createVolume = OptionBuilder
+        .withArgName("user> <volume> <volumeSize in [GB/TB]> <blockSize")
+        .withLongOpt(CREATE_VOLUME)
+        .withValueSeparator(' ')
+        .hasArgs(4)
+        .withDescription("create a fresh new volume")
+        .create("c");
+    options.addOption(createVolume);
+
+    // taking 2 args: userName, volumeName
+    Option deleteVolume = OptionBuilder
+        .withArgName("user> <volume")
+        .withLongOpt(DELETE_VOLUME)
+        .hasArgs(2)
+        .withDescription("delete a volume")
+        .create("d");
+    options.addOption(deleteVolume);
+
+    // taking 2 args: userName, volumeName
+    Option infoVolume = OptionBuilder
+        .withArgName("user> <volume")
+        .withLongOpt(INFO_VOLUME)
+        .hasArgs(2)
+        .withDescription("info a volume")
+        .create("i");
+    options.addOption(infoVolume);
+
+    // taking 1 arg: userName
+    Option listVolume = OptionBuilder
+        .withArgName("user")
+        .withLongOpt(LIST_VOLUME)
+        .hasOptionalArgs(1)
+        .withDescription("list all volumes")
+        .create("l");
+    options.addOption(listVolume);
+
+    Option help = OptionBuilder
+        .withLongOpt(HELP)
+        .withDescription("help")
+        .create("h");
+    options.addOption(help);
+
+    return options;
+  }
+
+  @Override
+  public int run(String[] args) throws ParseException, IOException {
+    CommandLine commandLine = parseArgs(args);
+    if (commandLine.hasOption("s")) {
+      String[] serverAddrArgs = commandLine.getOptionValues("s");
+      LOG.info("server address" + Arrays.toString(serverAddrArgs));
+      String serverHost = serverAddrArgs[0];
+      int serverPort = Integer.parseInt(serverAddrArgs[1]);
+      InetSocketAddress serverAddress =
+          new InetSocketAddress(serverHost, serverPort);
+      this.localProxy = new CBlockVolumeClient(conf, serverAddress);
+    } else {
+      this.localProxy = new CBlockVolumeClient(conf);
+    }
+
+    if (commandLine.hasOption("h")) {
+      LOG.info("help");
+      help();
+    }
+
+    if (commandLine.hasOption("c")) {
+      String[] createArgs = commandLine.getOptionValues("c");
+      LOG.info("create volume:" + Arrays.toString(createArgs));
+      createVolume(createArgs);
+    }
+
+    if (commandLine.hasOption("d")) {
+      String[] deleteArgs = commandLine.getOptionValues("d");
+      LOG.info("delete args:" + Arrays.toString(deleteArgs));
+      deleteVolume(deleteArgs);
+    }
+
+    if (commandLine.hasOption("l")) {
+      String[] listArg = commandLine.getOptionValues("l");
+      LOG.info("list args:" + Arrays.toString(listArg));
+      listVolume(listArg);
+    }
+
+    if (commandLine.hasOption("i")) {
+      String[] infoArgs = commandLine.getOptionValues("i");
+      LOG.info("info args:" + Arrays.toString(infoArgs));
+      infoVolume(infoArgs);
+    }
+    return 0;
+  }
+
+  public static void main(String[] argv) throws Exception {
+    OzoneConfiguration cblockConf = new OzoneConfiguration();
+    RPC.setProtocolEngine(cblockConf, CBlockServiceProtocolPB.class,
+        ProtobufRpcEngine.class);
+    int res = 0;
+    Tool shell = new CBlockCli(cblockConf, System.out);
+    try {
+      ToolRunner.run(shell, argv);
+    } catch (Exception ex) {
+      LOG.error(ex.toString());
+      res = 1;
+    }
+    System.exit(res);
+  }
+
+  private long parseSize(String volumeSizeArgs) throws IOException {
+    long multiplier = 1;
+
+    Pattern p = Pattern.compile("([0-9]+)([a-zA-Z]+)");
+    Matcher m = p.matcher(volumeSizeArgs);
+
+    if (!m.find()) {
+      throw new IOException("Invalid volume size args " + volumeSizeArgs);
+    }
+
+    int size = Integer.parseInt(m.group(1));
+    String s = m.group(2);
+
+    if (s.equalsIgnoreCase("GB")) {
+      multiplier = 1024L * 1024 * 1024;
+    } else if (s.equalsIgnoreCase("TB")) {
+      multiplier = 1024L * 1024 * 1024 * 1024;
+    } else {
+      throw new IOException("Invalid volume size args " + volumeSizeArgs);
+    }
+    return size * multiplier;
+  }
+
+  private void createVolume(String[] createArgs) throws IOException {
+    String userName = createArgs[0];
+    String volumeName = createArgs[1];
+    long volumeSize = parseSize(createArgs[2]);
+    int blockSize = Integer.parseInt(createArgs[3])*1024;
+    localProxy.createVolume(userName, volumeName, volumeSize, blockSize);
+  }
+
+  private void deleteVolume(String[] deleteArgs) throws IOException {
+    String userName = deleteArgs[0];
+    String volumeName = deleteArgs[1];
+    boolean force = false;
+    if (deleteArgs.length > 2) {
+      force = Boolean.parseBoolean(deleteArgs[2]);
+    }
+    localProxy.deleteVolume(userName, volumeName, force);
+  }
+
+  private void infoVolume(String[] infoArgs) throws IOException {
+    String userName = infoArgs[0];
+    String volumeName = infoArgs[1];
+    VolumeInfo volumeInfo = localProxy.infoVolume(userName, volumeName);
+    printStream.println(volumeInfo.toString());
+  }
+
+  private void listVolume(String[] listArgs) throws IOException {
+    StringBuilder stringBuilder = new StringBuilder();
+    List<VolumeInfo> volumeResponse;
+    if (listArgs == null) {
+      volumeResponse = localProxy.listVolume(null);
+    } else {
+      volumeResponse = localProxy.listVolume(listArgs[0]);
+    }
+    for (int i = 0; i<volumeResponse.size(); i++) {
+      stringBuilder.append(
+          String.format("%s:%s\t%d\t%d", volumeResponse.get(i).getUserName(),
+              volumeResponse.get(i).getVolumeName(),
+          volumeResponse.get(i).getVolumeSize(),
+          volumeResponse.get(i).getBlockSize()));
+      if (i < volumeResponse.size() - 1) {
+        stringBuilder.append("\n");
+      }
+    }
+    printStream.println(stringBuilder);
+  }
+
+  private void help() {
+    HelpFormatter formatter = new HelpFormatter();
+    formatter.printHelp(100, "cblock", "", options, "");
+  }
+}

+ 18 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/cli/package-info.java

@@ -0,0 +1,18 @@
+/**
+ * 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.hadoop.cblock.cli;

+ 135 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/client/CBlockServiceProtocolClientSideTranslatorPB.java

@@ -0,0 +1,135 @@
+/*
+ * 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.hadoop.cblock.client;
+
+import com.google.protobuf.ServiceException;
+import org.apache.hadoop.cblock.meta.VolumeInfo;
+import org.apache.hadoop.cblock.proto.CBlockServiceProtocol;
+import org.apache.hadoop.cblock.protocol.proto.CBlockServiceProtocolProtos;
+import org.apache.hadoop.cblock.protocolPB.CBlockServiceProtocolPB;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.ipc.ProtobufHelper;
+import org.apache.hadoop.ipc.ProtocolTranslator;
+import org.apache.hadoop.ipc.RPC;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The client side implement of CBlockServiceProtocol.
+ */
+@InterfaceAudience.Private
+public final class CBlockServiceProtocolClientSideTranslatorPB
+    implements CBlockServiceProtocol, ProtocolTranslator, Closeable {
+
+  private final CBlockServiceProtocolPB rpcProxy;
+
+  public CBlockServiceProtocolClientSideTranslatorPB(
+      CBlockServiceProtocolPB rpcProxy) {
+    this.rpcProxy = rpcProxy;
+  }
+
+  @Override
+  public void close() throws IOException {
+    RPC.stopProxy(rpcProxy);
+  }
+
+  @Override
+  public void createVolume(String userName, String volumeName,
+      long volumeSize, int blockSize) throws IOException {
+    CBlockServiceProtocolProtos.CreateVolumeRequestProto.Builder req =
+        CBlockServiceProtocolProtos.CreateVolumeRequestProto.newBuilder();
+    req.setUserName(userName);
+    req.setVolumeName(volumeName);
+    req.setVolumeSize(volumeSize);
+    req.setBlockSize(blockSize);
+    try {
+      rpcProxy.createVolume(null, req.build());
+    } catch (ServiceException e) {
+      throw ProtobufHelper.getRemoteException(e);
+    }
+  }
+
+  @Override
+  public void deleteVolume(String userName, String volumeName, boolean force)
+      throws IOException {
+    CBlockServiceProtocolProtos.DeleteVolumeRequestProto.Builder req =
+        CBlockServiceProtocolProtos.DeleteVolumeRequestProto.newBuilder();
+    req.setUserName(userName);
+    req.setVolumeName(volumeName);
+    req.setForce(force);
+    try {
+      rpcProxy.deleteVolume(null, req.build());
+    } catch (ServiceException e) {
+      throw ProtobufHelper.getRemoteException(e);
+    }
+  }
+
+  @Override
+  public Object getUnderlyingProxyObject() {
+    return rpcProxy;
+  }
+
+  @Override
+  public VolumeInfo infoVolume(String userName, String volumeName)
+      throws IOException {
+    CBlockServiceProtocolProtos.InfoVolumeRequestProto.Builder req =
+        CBlockServiceProtocolProtos.InfoVolumeRequestProto.newBuilder();
+    req.setUserName(userName);
+    req.setVolumeName(volumeName);
+    try {
+      CBlockServiceProtocolProtos.InfoVolumeResponseProto resp =
+          rpcProxy.infoVolume(null, req.build());
+      return new VolumeInfo(resp.getVolumeInfo().getUserName(),
+          resp.getVolumeInfo().getVolumeName(),
+          resp.getVolumeInfo().getVolumeSize(),
+          resp.getVolumeInfo().getBlockSize(),
+          resp.getVolumeInfo().getUsage());
+    } catch (ServiceException e) {
+      throw ProtobufHelper.getRemoteException(e);
+    }
+  }
+
+  @Override
+  public List<VolumeInfo> listVolume(String userName) throws IOException {
+    CBlockServiceProtocolProtos.ListVolumeRequestProto.Builder req =
+        CBlockServiceProtocolProtos.ListVolumeRequestProto.newBuilder();
+    if (userName != null) {
+      req.setUserName(userName);
+    }
+    try {
+      CBlockServiceProtocolProtos.ListVolumeResponseProto resp =
+          rpcProxy.listVolume(null, req.build());
+      List<VolumeInfo> respList = new ArrayList<>();
+      for (CBlockServiceProtocolProtos.VolumeInfoProto entry :
+          resp.getVolumeEntryList()) {
+        VolumeInfo volumeInfo = new VolumeInfo(
+            entry.getUserName(), entry.getVolumeName(), entry.getVolumeSize(),
+            entry.getBlockSize());
+        respList.add(volumeInfo);
+      }
+      return respList;
+    } catch (ServiceException e) {
+      throw ProtobufHelper.getRemoteException(e);
+    } catch (Exception e) {
+      throw new IOException("got" + e.getCause() + " " + e.getMessage());
+    }
+  }
+}

+ 96 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/client/CBlockVolumeClient.java

@@ -0,0 +1,96 @@
+/*
+ * 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.hadoop.cblock.client;
+
+import org.apache.hadoop.cblock.meta.VolumeInfo;
+import org.apache.hadoop.cblock.protocolPB.CBlockServiceProtocolPB;
+import org.apache.hadoop.io.retry.RetryPolicies;
+import org.apache.hadoop.ipc.RPC;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.ozone.OzoneConfiguration;
+import org.apache.hadoop.security.UserGroupInformation;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.hadoop.cblock.CBlockConfigKeys.DFS_CBLOCK_SERVICERPC_HOSTNAME_DEFAULT;
+import static org.apache.hadoop.cblock.CBlockConfigKeys.DFS_CBLOCK_SERVICERPC_HOSTNAME_KEY;
+import static org.apache.hadoop.cblock.CBlockConfigKeys.DFS_CBLOCK_SERVICERPC_PORT_DEFAULT;
+import static org.apache.hadoop.cblock.CBlockConfigKeys.DFS_CBLOCK_SERVICERPC_PORT_KEY;
+
+/**
+ * Implementation of client used by CBlock command line tool.
+ */
+public class CBlockVolumeClient {
+  private final CBlockServiceProtocolClientSideTranslatorPB cblockClient;
+  private final OzoneConfiguration conf;
+
+  public CBlockVolumeClient(OzoneConfiguration conf) throws IOException {
+    this.conf = conf;
+    long version = RPC.getProtocolVersion(CBlockServiceProtocolPB.class);
+    String serverAddress = conf.get(DFS_CBLOCK_SERVICERPC_HOSTNAME_KEY,
+        DFS_CBLOCK_SERVICERPC_HOSTNAME_DEFAULT);
+    int serverPort = conf.getInt(DFS_CBLOCK_SERVICERPC_PORT_KEY,
+        DFS_CBLOCK_SERVICERPC_PORT_DEFAULT);
+    InetSocketAddress address = new InetSocketAddress(
+        serverAddress, serverPort);
+    // currently the largest supported volume is about 8TB, which might take
+    // > 20 seconds to finish creating containers. thus set timeout to 30 sec.
+    cblockClient = new CBlockServiceProtocolClientSideTranslatorPB(
+        RPC.getProtocolProxy(CBlockServiceProtocolPB.class, version,
+            address, UserGroupInformation.getCurrentUser(), conf,
+            NetUtils.getDefaultSocketFactory(conf), 30000, RetryPolicies
+                .retryUpToMaximumCountWithFixedSleep(300, 1, TimeUnit
+                    .SECONDS)).getProxy());
+  }
+
+  public CBlockVolumeClient(OzoneConfiguration conf,
+      InetSocketAddress serverAddress) throws IOException {
+    this.conf = conf;
+    long version = RPC.getProtocolVersion(CBlockServiceProtocolPB.class);
+    cblockClient = new CBlockServiceProtocolClientSideTranslatorPB(
+        RPC.getProtocolProxy(CBlockServiceProtocolPB.class, version,
+            serverAddress, UserGroupInformation.getCurrentUser(), conf,
+            NetUtils.getDefaultSocketFactory(conf), 30000, RetryPolicies
+                .retryUpToMaximumCountWithFixedSleep(300, 1, TimeUnit
+                    .SECONDS)).getProxy());
+  }
+
+  public void createVolume(String userName, String volumeName,
+      long volumeSize, int blockSize) throws IOException {
+    cblockClient.createVolume(userName, volumeName,
+        volumeSize, blockSize);
+  }
+
+  public void deleteVolume(String userName, String volumeName, boolean force)
+      throws IOException {
+    cblockClient.deleteVolume(userName, volumeName, force);
+  }
+
+  public VolumeInfo infoVolume(String userName, String volumeName)
+      throws IOException {
+    return cblockClient.infoVolume(userName, volumeName);
+  }
+
+  public List<VolumeInfo> listVolume(String userName)
+      throws IOException {
+    return cblockClient.listVolume(userName);
+  }
+}

+ 18 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/cblock/client/package-info.java

@@ -0,0 +1,18 @@
+/**
+ * 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.hadoop.cblock.client;

+ 224 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/cblock/TestCBlockCLI.java

@@ -0,0 +1,224 @@
+/*
+ * 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.hadoop.cblock;
+
+import org.apache.hadoop.cblock.cli.CBlockCli;
+import org.apache.hadoop.cblock.meta.VolumeDescriptor;
+import org.apache.hadoop.cblock.util.MockStorageClient;
+import org.apache.hadoop.ozone.OzoneConfiguration;
+import org.apache.hadoop.scm.client.ScmClient;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.List;
+
+import static org.apache.hadoop.cblock.CBlockConfigKeys.DFS_CBLOCK_SERVICE_LEVELDB_PATH_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * A testing class for cblock command line tool.
+ */
+public class TestCBlockCLI {
+  private static final long GB = 1 * 1024 * 1024 * 1024L;
+  private static final int KB = 1024;
+  private static CBlockCli cmd;
+  private static OzoneConfiguration conf;
+  private static CBlockManager cBlockManager;
+  private static ByteArrayOutputStream outContent;
+  private static PrintStream testPrintOut;
+
+  @BeforeClass
+  public static void setup() throws IOException {
+    outContent = new ByteArrayOutputStream();
+    ScmClient storageClient = new MockStorageClient();
+    conf = new OzoneConfiguration();
+    conf.set(DFS_CBLOCK_SERVICE_LEVELDB_PATH_KEY, "/tmp/testCblockCli.dat");
+    cBlockManager = new CBlockManager(conf, storageClient);
+    cBlockManager.start();
+    testPrintOut = new PrintStream(outContent);
+    cmd = new CBlockCli(conf, testPrintOut);
+  }
+
+  @AfterClass
+  public static void clean() {
+    cBlockManager.stop();
+    cBlockManager.join();
+    cBlockManager.clean();
+  }
+
+  @After
+  public void reset() {
+    outContent.reset();
+  }
+
+  /**
+   * Test the help command.
+   * @throws Exception
+   */
+  @Test
+  public void testCliHelp() throws Exception {
+    PrintStream initialStdOut = System.out;
+    System.setOut(testPrintOut);
+    String[] args = {"-h"};
+    cmd.run(args);
+    String helpPrints =
+        "usage: cblock\n" +
+        " -c,--createVolume <user> <volume> <volumeSize in [GB/TB]> " +
+            "<blockSize>   create a fresh new volume\n" +
+        " -d,--deleteVolume <user> <volume>                         " +
+            "              delete a volume\n" +
+        " -h,--help                                                 " +
+            "              help\n" +
+        " -i,--infoVolume <user> <volume>                           " +
+            "              info a volume\n" +
+        " -l,--listVolume <user>                                    " +
+            "              list all volumes\n" +
+        " -s,--serverAddr <serverAddress>:<serverPort>              " +
+            "              specify server address:port\n";
+    assertEquals(helpPrints, outContent.toString());
+    outContent.reset();
+    System.setOut(initialStdOut);
+  }
+
+  /**
+   * Test volume listing command.
+   * @throws Exception
+   */
+  @Test
+  public void testCliList() throws Exception {
+    String userName0 = "userTestCliList0";
+    String userName1 = "userTestCliList1";
+    String userTestNotExist = "userTestNotExist";
+    String volumeName0 = "volumeTest0";
+    String volumeName1 = "volumeTest1";
+    String volumeSize0 = "30GB";
+    String volumeSize1 = "40GB";
+    String blockSize = Integer.toString(4);
+    String[] argsCreate0 =
+        {"-c", userName0, volumeName0, volumeSize0, blockSize};
+    cmd.run(argsCreate0);
+    String[] argsCreate1 =
+        {"-c", userName0, volumeName1, volumeSize1, blockSize};
+    cmd.run(argsCreate1);
+    String[] argsCreate2 =
+        {"-c", userName1, volumeName0, volumeSize0, blockSize};
+    cmd.run(argsCreate2);
+    String[] argsList0 = {"-l"};
+    cmd.run(argsList0);
+    String[] outExpected1 = {
+        "userTestCliList1:volumeTest0\t32212254720\t4096\n",
+        "userTestCliList0:volumeTest0\t32212254720\t4096\n",
+        "userTestCliList0:volumeTest1\t42949672960\t4096\n"};
+    int length = 0;
+    for (String str : outExpected1) {
+      assertTrue(outContent.toString().contains(str));
+      length += str.length();
+    }
+    assertEquals(length, outContent.toString().length());
+    outContent.reset();
+
+    String[] argsList1 = {"-l", userName1};
+    cmd.run(argsList1);
+    String outExpected2 = "userTestCliList1:volumeTest0\t32212254720\t4096\n";
+    assertEquals(outExpected2, outContent.toString());
+    outContent.reset();
+
+    String[] argsList2 = {"-l", userTestNotExist};
+    cmd.run(argsList2);
+    String outExpected3 = "\n";
+    assertEquals(outExpected3, outContent.toString());
+  }
+
+  /**
+   * Test create volume command.
+   * @throws Exception
+   */
+  @Test
+  public void testCliCreate() throws Exception {
+    String userName = "userTestCliCreate";
+    String volumeName = "volumeTest";
+    String volumeSize = "30GB";
+    String blockSize = "4";
+    String[] argsCreate = {"-c", userName, volumeName, volumeSize, blockSize};
+    cmd.run(argsCreate);
+    List<VolumeDescriptor> allVolumes = cBlockManager.getAllVolumes(userName);
+    assertEquals(1, allVolumes.size());
+    VolumeDescriptor volume = allVolumes.get(0);
+    assertEquals(userName, volume.getUserName());
+    assertEquals(volumeName, volume.getVolumeName());
+    long volumeSizeB = volume.getVolumeSize();
+    assertEquals(30, (int)(volumeSizeB/ GB));
+    assertEquals(4, volume.getBlockSize()/ KB);
+  }
+
+  /**
+   * Test delete volume command.
+   * @throws Exception
+   */
+  @Test
+  public void testCliDelete() throws Exception {
+    String userName = "userTestCliDelete";
+    String volumeName = "volumeTest";
+    String volumeSize = "30GB";
+    String blockSize = "4";
+    String[] argsCreate = {"-c", userName, volumeName, volumeSize, blockSize};
+    cmd.run(argsCreate);
+    List<VolumeDescriptor> allVolumes = cBlockManager.getAllVolumes(userName);
+    assertEquals(1, allVolumes.size());
+    VolumeDescriptor volume = allVolumes.get(0);
+    assertEquals(userName, volume.getUserName());
+    assertEquals(volumeName, volume.getVolumeName());
+    long volumeSizeB = volume.getVolumeSize();
+    assertEquals(30, (int)(volumeSizeB/ GB));
+    assertEquals(4, volume.getBlockSize()/ KB);
+
+    String[] argsDelete = {"-d", userName, volumeName};
+    cmd.run(argsDelete);
+    allVolumes = cBlockManager.getAllVolumes(userName);
+    assertEquals(0, allVolumes.size());
+  }
+
+  /**
+   * Test info volume command.
+   * @throws Exception
+   */
+  @Test
+  public void testCliInfoVolume() throws Exception {
+    String userName0 = "userTestCliInfo";
+    String volumeName0 = "volumeTest0";
+    String volumeSize = "8000GB";
+    String blockSize = "4";
+    String[] argsCreate0 = {
+        "-c", userName0, volumeName0, volumeSize, blockSize};
+    cmd.run(argsCreate0);
+    String[] argsInfo = {"-i", userName0, volumeName0};
+    cmd.run(argsInfo);
+    // TODO : the usage field is not implemented yet, always 0 now.
+    String outExpected = " userName:userTestCliInfo " +
+        "volumeName:volumeTest0 " +
+        "volumeSize:8589934592000 " +
+        "blockSize:4096 (sizeInBlocks:2097152000) usageInBlocks:0\n";
+    assertEquals(outExpected, outContent.toString());
+  }
+}