This is an automated email from the ASF dual-hosted git repository.

szetszwo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new 6451f9b3e RATIS-2155. Add a builder for RatisShell. (#1150)
6451f9b3e is described below

commit 6451f9b3ee3784620f6fb8984bf305f4df11a185
Author: Tsz-Wo Nicholas Sze <[email protected]>
AuthorDate: Tue Sep 17 22:13:38 2024 -0700

    RATIS-2155. Add a builder for RatisShell. (#1150)
---
 .../grpc/client/GrpcClientProtocolClient.java      |   1 +
 .../org/apache/ratis/grpc/server/GrpcService.java  |   1 +
 .../apache/ratis/grpc/MiniRaftClusterWithGrpc.java |   7 +-
 .../java/org/apache/ratis/shell/cli/CliUtils.java  |  33 +-----
 .../org/apache/ratis/shell/cli/sh/RatisShell.java  |  44 +++++++-
 .../shell/cli/sh/command/AbstractCommand.java      |  14 ++-
 .../shell/cli/sh/command/AbstractRatisCommand.java |   6 +-
 .../apache/ratis/shell/cli/sh/command/Context.java |  69 ++++++++++++
 .../ratis/shell/cli/sh/election/PauseCommand.java  |   3 +-
 .../ratis/shell/cli/sh/election/ResumeCommand.java |   3 +-
 .../shell/cli/sh/election/StepDownCommand.java     |   3 +-
 .../shell/cli/sh/election/TransferCommand.java     |   3 +-
 .../ratis/shell/cli/sh/group/GroupListCommand.java |   2 +-
 .../apache/ratis/shell/cli/sh/peer/AddCommand.java |   2 +-
 .../ratis/shell/cli/sh/peer/RemoveCommand.java     |   3 +-
 .../shell/cli/sh/peer/SetPriorityCommand.java      |   3 +-
 .../shell/cli/sh/snapshot/TakeSnapshotCommand.java |   3 +-
 .../apache/ratis/security/SecurityTestUtils.java   |  17 +++
 .../ratis/shell/cli/sh/TestSecureRatisShell.java   | 123 +++++++++++++++++++++
 19 files changed, 283 insertions(+), 57 deletions(-)

diff --git 
a/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolClient.java
 
b/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolClient.java
index a1f01a512..3b9d51268 100644
--- 
a/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolClient.java
+++ 
b/ratis-grpc/src/main/java/org/apache/ratis/grpc/client/GrpcClientProtocolClient.java
@@ -132,6 +132,7 @@ public class GrpcClientProtocolClient implements Closeable {
     channelBuilder.proxyDetector(uri -> null);
 
     if (tlsConf != null) {
+      LOG.debug("Setting TLS for {}", address);
       SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
       GrpcUtil.setTrustManager(sslContextBuilder, tlsConf.getTrustManager());
       if (tlsConf.getMtlsEnabled()) {
diff --git 
a/ratis-grpc/src/main/java/org/apache/ratis/grpc/server/GrpcService.java 
b/ratis-grpc/src/main/java/org/apache/ratis/grpc/server/GrpcService.java
index ce434c5e8..510dfcaa2 100644
--- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/server/GrpcService.java
+++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/server/GrpcService.java
@@ -198,6 +198,7 @@ public final class GrpcService extends 
RaftServerRpcWithProxy<GrpcServerProtocol
           .flowControlWindow(flowControlWindow.getSizeInt());
 
       if (tlsConfig != null) {
+        LOG.info("Setting TLS for {}", address);
         SslContextBuilder sslContextBuilder = 
GrpcUtil.initSslContextBuilderForServer(tlsConfig.getKeyManager());
         if (tlsConfig.getMtlsEnabled()) {
           sslContextBuilder.clientAuth(ClientAuth.REQUIRE);
diff --git 
a/ratis-grpc/src/test/java/org/apache/ratis/grpc/MiniRaftClusterWithGrpc.java 
b/ratis-grpc/src/test/java/org/apache/ratis/grpc/MiniRaftClusterWithGrpc.java
index bd3b13750..47f9e1d4b 100644
--- 
a/ratis-grpc/src/test/java/org/apache/ratis/grpc/MiniRaftClusterWithGrpc.java
+++ 
b/ratis-grpc/src/test/java/org/apache/ratis/grpc/MiniRaftClusterWithGrpc.java
@@ -64,8 +64,11 @@ public class MiniRaftClusterWithGrpc extends 
MiniRaftCluster.RpcBase {
   public static final DelayLocalExecutionInjection 
SEND_SERVER_REQUEST_INJECTION =
       new DelayLocalExecutionInjection(GrpcService.GRPC_SEND_SERVER_REQUEST);
 
-  protected MiniRaftClusterWithGrpc(String[] ids, String[] listenerIds, 
RaftProperties properties,
-      Parameters parameters) {
+  public MiniRaftClusterWithGrpc(String[] ids, RaftProperties properties, 
Parameters parameters) {
+    this(ids, new String[0], properties, parameters);
+  }
+
+  public MiniRaftClusterWithGrpc(String[] ids, String[] listenerIds, 
RaftProperties properties, Parameters parameters) {
     super(ids, listenerIds, properties, parameters);
   }
 
diff --git a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/CliUtils.java 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/CliUtils.java
index 98e21be2a..1cecc665c 100644
--- a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/CliUtils.java
+++ b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/CliUtils.java
@@ -18,17 +18,12 @@
 package org.apache.ratis.shell.cli;
 
 import org.apache.ratis.client.RaftClient;
-import org.apache.ratis.client.RaftClientConfigKeys;
-import org.apache.ratis.conf.RaftProperties;
 import org.apache.ratis.protocol.GroupInfoReply;
 import org.apache.ratis.protocol.RaftClientReply;
-import org.apache.ratis.protocol.RaftGroup;
 import org.apache.ratis.protocol.RaftGroupId;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.protocol.exceptions.RaftException;
-import org.apache.ratis.retry.ExponentialBackoffRetry;
-import org.apache.ratis.util.TimeDuration;
 import org.apache.ratis.util.function.CheckedFunction;
 
 import java.io.IOException;
@@ -36,24 +31,16 @@ import java.io.PrintStream;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.TimeUnit;
 import java.util.List;
 import java.util.Optional;
-import java.util.Properties;
+import java.util.UUID;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import java.util.UUID;
 
 /**
  * Utilities for command line interface.
  */
 public final class CliUtils {
-  private static final ExponentialBackoffRetry RETRY_POLICY = 
ExponentialBackoffRetry.newBuilder()
-      .setBaseSleepTime(TimeDuration.valueOf(1000, TimeUnit.MILLISECONDS))
-      .setMaxAttempts(10)
-      .setMaxSleepTime(TimeDuration.valueOf(100_000, TimeUnit.MILLISECONDS))
-      .build();
-
   private CliUtils() {
     // prevent instantiation
   }
@@ -68,24 +55,6 @@ public final class CliUtils {
     return RaftPeerId.getRaftPeerId(host + "_" + port);
   }
 
-  /** Create a new {@link RaftClient} from the given group. */
-  public static RaftClient newRaftClient(RaftGroup group) {
-    RaftProperties properties = new RaftProperties();
-    RaftClientConfigKeys.Rpc.setRequestTimeout(properties,
-        TimeDuration.valueOf(15, TimeUnit.SECONDS));
-
-    // Since ratis-shell support GENERIC_COMMAND_OPTIONS, here we should
-    // merge these options to raft properties to make it work.
-    final Properties sys = System.getProperties();
-    sys.stringPropertyNames().forEach(key -> properties.set(key, 
sys.getProperty(key)));
-
-    return RaftClient.newBuilder()
-        .setRaftGroup(group)
-        .setProperties(properties)
-        .setRetryPolicy(RETRY_POLICY)
-        .build();
-  }
-
   /**
    * Apply the given function to the given parameter a list.
    *
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/RatisShell.java 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/RatisShell.java
index 2e53e3191..cdad68301 100644
--- a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/RatisShell.java
+++ b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/RatisShell.java
@@ -17,6 +17,9 @@
  */
 package org.apache.ratis.shell.cli.sh;
 
+import org.apache.ratis.conf.Parameters;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.retry.RetryPolicy;
 import org.apache.ratis.shell.cli.AbstractShell;
 import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.sh.command.AbstractParentCommand;
@@ -60,7 +63,11 @@ public class RatisShell extends AbstractShell {
   }
 
   public RatisShell(PrintStream out) {
-    super(new Context(out));
+    this(new Context(out));
+  }
+
+  private RatisShell(Context context) {
+    super(context);
   }
 
   @Override
@@ -73,4 +80,39 @@ public class RatisShell extends AbstractShell {
     return allParentCommands(context).stream()
         .collect(Collectors.toMap(Command::getCommandName, 
Function.identity()));
   }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private PrintStream printStream = System.out;
+    private RetryPolicy retryPolicy;
+    private RaftProperties properties;
+    private Parameters parameters;
+
+    public Builder setPrintStream(PrintStream printStream) {
+      this.printStream = printStream;
+      return this;
+    }
+
+    public Builder setRetryPolicy(RetryPolicy retryPolicy) {
+      this.retryPolicy = retryPolicy;
+      return this;
+    }
+
+    public Builder setProperties(RaftProperties properties) {
+      this.properties = properties;
+      return this;
+    }
+
+    public Builder setParameters(Parameters parameters) {
+      this.parameters = parameters;
+      return this;
+    }
+
+    public RatisShell build() {
+      return new RatisShell(new Context(printStream, false, retryPolicy, 
properties, parameters));
+    }
+  }
 }
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
index f02761de4..e1d7f8e0b 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
@@ -26,21 +26,25 @@ import java.io.PrintStream;
  */
 public abstract class AbstractCommand implements Command {
 
-  private final PrintStream printStream;
+  private final Context context;
 
   protected AbstractCommand(Context context) {
-    printStream = context.getPrintStream();
+    this.context = context;
+  }
+
+  protected Context getContext() {
+    return context;
   }
 
   protected PrintStream getPrintStream() {
-    return printStream;
+    return getContext().getPrintStream();
   }
 
   protected void printf(String format, Object... args) {
-    printStream.printf(format, args);
+    getPrintStream().printf(format, args);
   }
 
   protected void println(Object message) {
-    printStream.println(message);
+    getPrintStream().println(message);
   }
 }
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
index a9d391f86..aea1f7c4b 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
@@ -66,7 +66,7 @@ public abstract class AbstractRatisCommand extends 
AbstractCommand {
     final RaftGroupId groupIdSpecified = 
CliUtils.parseRaftGroupId(cl.getOptionValue(GROUPID_OPTION_NAME));
     raftGroup = RaftGroup.valueOf(groupIdSpecified != null? groupIdSpecified: 
RaftGroupId.randomId(), peers);
     PrintStream printStream = getPrintStream();
-    try (final RaftClient client = CliUtils.newRaftClient(raftGroup)) {
+    try (final RaftClient client = newRaftClient()) {
       final RaftGroupId remoteGroupId = CliUtils.getGroupId(client, peers, 
groupIdSpecified, printStream);
       groupInfoReply = CliUtils.getGroupInfo(client, peers, remoteGroupId, 
printStream);
       raftGroup = groupInfoReply.getGroup();
@@ -74,6 +74,10 @@ public abstract class AbstractRatisCommand extends 
AbstractCommand {
     return 0;
   }
 
+  protected RaftClient newRaftClient() {
+    return getContext().newRaftClient(getRaftGroup());
+  }
+
   @Override
   public Options getOptions() {
     return new Options()
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/Context.java 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/Context.java
index bae98dc0b..6f80256d4 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/Context.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/Context.java
@@ -17,27 +17,59 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.client.RaftClientConfigKeys;
+import org.apache.ratis.conf.Parameters;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.protocol.RaftGroup;
+import org.apache.ratis.retry.ExponentialBackoffRetry;
+import org.apache.ratis.retry.RetryPolicy;
 import org.apache.ratis.thirdparty.com.google.common.io.Closer;
+import org.apache.ratis.util.TimeDuration;
 
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A context for ratis-shell.
  */
 public final class Context implements Closeable {
+  private static final TimeDuration DEFAULT_REQUEST_TIMEOUT = 
TimeDuration.valueOf(15, TimeUnit.SECONDS);
+  private static final RetryPolicy DEFAULT_RETRY_POLICY = 
ExponentialBackoffRetry.newBuilder()
+      .setBaseSleepTime(TimeDuration.valueOf(1000, TimeUnit.MILLISECONDS))
+      .setMaxAttempts(10)
+      .setMaxSleepTime(TimeDuration.valueOf(100_000, TimeUnit.MILLISECONDS))
+      .build();
+
   private final PrintStream mPrintStream;
   private final Closer mCloser;
 
+  private final boolean cli;
+  private final RetryPolicy retryPolicy;
+  private final RaftProperties properties;
+  private final Parameters parameters;
+
   /**
    * Build a context.
    * @param printStream the print stream
    */
   public Context(PrintStream printStream) {
+    this(printStream, true, DEFAULT_RETRY_POLICY, new RaftProperties(), null);
+  }
+
+  public Context(PrintStream printStream, boolean cli, RetryPolicy retryPolicy,
+      RaftProperties properties, Parameters parameters) {
     mCloser = Closer.create();
     mPrintStream = mCloser.register(Objects.requireNonNull(printStream, 
"printStream == null"));
+
+    this.cli = cli;
+    this.retryPolicy = retryPolicy != null? retryPolicy : DEFAULT_RETRY_POLICY;
+    this.properties = properties != null? properties : new RaftProperties();
+    this.parameters = parameters;
   }
 
   /**
@@ -47,6 +79,43 @@ public final class Context implements Closeable {
     return mPrintStream;
   }
 
+  /** Is this from CLI? */
+  public boolean isCli() {
+    return cli;
+  }
+
+  public RetryPolicy getRetryPolicy() {
+    return retryPolicy;
+  }
+
+  public RaftProperties getProperties() {
+    return properties;
+  }
+
+  public Parameters getParameters() {
+    return parameters;
+  }
+
+  /** Create a new {@link RaftClient} from the given group. */
+  public RaftClient newRaftClient(RaftGroup group) {
+    final RaftProperties p = getProperties();
+    if (isCli()) {
+      RaftClientConfigKeys.Rpc.setRequestTimeout(p, DEFAULT_REQUEST_TIMEOUT);
+
+      // Since ratis-shell support GENERIC_COMMAND_OPTIONS, here we should
+      // merge these options to raft p to make it work.
+      final Properties sys = System.getProperties();
+      sys.stringPropertyNames().forEach(key -> p.set(key, 
sys.getProperty(key)));
+    }
+
+    return RaftClient.newBuilder()
+        .setRaftGroup(group)
+        .setProperties(p)
+        .setParameters(getParameters())
+        .setRetryPolicy(getRetryPolicy())
+        .build();
+  }
+
   @Override
   public void close() throws IOException {
     mCloser.close();
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/PauseCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/PauseCommand.java
index 242e1886a..f8a627a8f 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/PauseCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/PauseCommand.java
@@ -24,7 +24,6 @@ import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -61,7 +60,7 @@ public class PauseCommand extends AbstractRatisCommand {
       printf("Peer not found: %s", strAddr);
       return -1;
     }
-    try(final RaftClient raftClient = CliUtils.newRaftClient(getRaftGroup())) {
+    try(final RaftClient raftClient = newRaftClient()) {
       RaftClientReply reply = 
raftClient.getLeaderElectionManagementApi(peerId).pause();
       processReply(reply, () -> String.format("Failed to pause leader election 
on peer %s", strAddr));
       printf(String.format("Successful pause leader election on peer %s", 
strAddr));
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/ResumeCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/ResumeCommand.java
index dbcee7bd3..1b5c80fac 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/ResumeCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/ResumeCommand.java
@@ -24,7 +24,6 @@ import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -61,7 +60,7 @@ public class ResumeCommand extends AbstractRatisCommand {
       printf("Can't find a sever with the address:%s", strAddr);
       return -1;
     }
-    try(final RaftClient raftClient = CliUtils.newRaftClient(getRaftGroup())) {
+    try(final RaftClient raftClient = newRaftClient()) {
       RaftClientReply reply = 
raftClient.getLeaderElectionManagementApi(peerId).resume();
       processReply(reply, () -> String.format("Failed to resume leader 
election on peer %s", strAddr));
       printf(String.format("Successful pause leader election on peer %s", 
strAddr));
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/StepDownCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/StepDownCommand.java
index da641a07b..f18921e5e 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/StepDownCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/StepDownCommand.java
@@ -21,7 +21,6 @@ import org.apache.commons.cli.CommandLine;
 import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeerId;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -48,7 +47,7 @@ public class StepDownCommand extends AbstractRatisCommand {
   public int run(CommandLine cl) throws IOException {
     super.run(cl);
 
-    try (RaftClient client = CliUtils.newRaftClient(getRaftGroup())) {
+    try (RaftClient client = newRaftClient()) {
       RaftPeerId leaderId = 
RaftPeerId.valueOf(getLeader(getGroupInfoReply().getRoleInfoProto()).getId());
       final RaftClientReply transferLeadershipReply = 
client.admin().transferLeadership(null, leaderId, 60_000);
       processReply(transferLeadershipReply, () -> "Failed to step down 
leader");
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/TransferCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/TransferCommand.java
index 24eae7353..88cfec914 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/TransferCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/election/TransferCommand.java
@@ -25,7 +25,6 @@ import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.exceptions.TransferLeadershipException;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 import org.apache.ratis.util.TimeDuration;
@@ -74,7 +73,7 @@ public class TransferCommand extends AbstractRatisCommand {
       printf("Peer with address %s not found.", strAddr);
       return -2;
     }
-    try (RaftClient client = CliUtils.newRaftClient(getRaftGroup())) {
+    try (RaftClient client = newRaftClient()) {
       // transfer leadership
       if (!tryTransfer(client, newLeader, highestPriority, 
timeout.orElse(timeoutDefault))) {
         // legacy mode, transfer leadership by setting priority.
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/group/GroupListCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/group/GroupListCommand.java
index 5ee89c53c..214ed1507 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/group/GroupListCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/group/GroupListCommand.java
@@ -69,7 +69,7 @@ public class GroupListCommand extends AbstractRatisCommand {
               + " options are missing.");
     }
 
-    try(final RaftClient raftClient = CliUtils.newRaftClient(getRaftGroup())) {
+    try(final RaftClient raftClient = newRaftClient()) {
       GroupListReply reply = raftClient.getGroupManagementApi(peerId).list();
       processReply(reply, () -> String.format("Failed to get group information 
of peerId %s (server %s)",
               peerId, address));
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/AddCommand.java 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/AddCommand.java
index 62c6c5793..be8f78995 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/AddCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/AddCommand.java
@@ -81,7 +81,7 @@ public class AddCommand extends AbstractRatisCommand {
           "Both " + PEER_ID_OPTION_NAME + " and " + ADDRESS_OPTION_NAME + " 
options are missing.");
     }
 
-    try (RaftClient client = CliUtils.newRaftClient(getRaftGroup())) {
+    try (RaftClient client = newRaftClient()) {
       final Stream<RaftPeer> remaining = getPeerStream(RaftPeerRole.FOLLOWER);
       final Stream<RaftPeer> adding = ids.stream().map(raftPeerId -> 
RaftPeer.newBuilder()
           .setId(raftPeerId)
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/RemoveCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/RemoveCommand.java
index e2aa786b3..904a89788 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/RemoveCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/RemoveCommand.java
@@ -25,7 +25,6 @@ import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeer;
 import org.apache.ratis.protocol.RaftPeerId;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -66,7 +65,7 @@ public class RemoveCommand extends AbstractRatisCommand {
       throw new IllegalArgumentException(
           "Both " + PEER_ID_OPTION_NAME + " and " + ADDRESS_OPTION_NAME + " 
options are missing.");
     }
-    try (RaftClient client = CliUtils.newRaftClient(getRaftGroup())) {
+    try (RaftClient client = newRaftClient()) {
       final List<RaftPeer> peers = getPeerStream(RaftPeerRole.FOLLOWER)
           .filter(raftPeer -> 
!ids.contains(raftPeer.getId())).collect(Collectors.toList());
       final List<RaftPeer> listeners = getPeerStream(RaftPeerRole.LISTENER)
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/SetPriorityCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/SetPriorityCommand.java
index e2d4d1a53..2834ef5cc 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/SetPriorityCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/peer/SetPriorityCommand.java
@@ -24,7 +24,6 @@ import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeer;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -63,7 +62,7 @@ public class SetPriorityCommand extends AbstractRatisCommand {
       addressPriorityMap.put(str[0], Integer.parseInt(str[1]));
     }
 
-    try (RaftClient client = CliUtils.newRaftClient(getRaftGroup())) {
+    try (RaftClient client = newRaftClient()) {
       final List<RaftPeer> peers = 
getPeerStream(RaftPeerRole.FOLLOWER).map(peer -> {
         final Integer newPriority = addressPriorityMap.get(peer.getAddress());
         final int priority = newPriority != null ? newPriority : 
peer.getPriority();
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/snapshot/TakeSnapshotCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/snapshot/TakeSnapshotCommand.java
index e76f215f4..521b22e4e 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/snapshot/TakeSnapshotCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/snapshot/TakeSnapshotCommand.java
@@ -23,7 +23,6 @@ import org.apache.commons.cli.Options;
 import org.apache.ratis.client.RaftClient;
 import org.apache.ratis.protocol.RaftClientReply;
 import org.apache.ratis.protocol.RaftPeerId;
-import org.apache.ratis.shell.cli.CliUtils;
 import org.apache.ratis.shell.cli.sh.command.AbstractRatisCommand;
 import org.apache.ratis.shell.cli.sh.command.Context;
 
@@ -58,7 +57,7 @@ public class TakeSnapshotCommand extends AbstractRatisCommand 
{
     } else {
       timeout = 3000;
     }
-    try(final RaftClient raftClient = CliUtils.newRaftClient(getRaftGroup())) {
+    try(final RaftClient raftClient = newRaftClient()) {
       if (cl.hasOption(PEER_ID_OPTION_NAME)) {
         peerId = 
RaftPeerId.getRaftPeerId(cl.getOptionValue(PEER_ID_OPTION_NAME));
       } else {
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/security/SecurityTestUtils.java 
b/ratis-test/src/test/java/org/apache/ratis/security/SecurityTestUtils.java
index c390f10f4..cbb8e1b79 100644
--- a/ratis-test/src/test/java/org/apache/ratis/security/SecurityTestUtils.java
+++ b/ratis-test/src/test/java/org/apache/ratis/security/SecurityTestUtils.java
@@ -54,6 +54,23 @@ public interface SecurityTestUtils {
 
   ClassLoader CLASS_LOADER = SecurityTestUtils.class.getClassLoader();
 
+  TrustManager EMPTY_TRUST_MANAGER = new X509TrustManager() {
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+      return null;
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] certs, String authType) { 
}
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] certs, String authType) { 
}
+  };
+
+  static TrustManager emptyTrustManager() {
+    return EMPTY_TRUST_MANAGER;
+  }
+
   static File getResource(String name) {
     final File file = Optional.ofNullable(CLASS_LOADER.getResource(name))
         .map(URL::getFile)
diff --git 
a/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/TestSecureRatisShell.java
 
b/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/TestSecureRatisShell.java
new file mode 100644
index 000000000..785cd2e82
--- /dev/null
+++ 
b/ratis-test/src/test/java/org/apache/ratis/shell/cli/sh/TestSecureRatisShell.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ratis.shell.cli.sh;
+
+import org.apache.ratis.BaseTest;
+import org.apache.ratis.client.RaftClientConfigKeys;
+import org.apache.ratis.conf.Parameters;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.grpc.GrpcConfigKeys;
+import org.apache.ratis.grpc.GrpcTlsConfig;
+import org.apache.ratis.grpc.MiniRaftClusterWithGrpc;
+import org.apache.ratis.netty.NettyUtils;
+import org.apache.ratis.protocol.RaftPeer;
+import org.apache.ratis.security.SecurityTestUtils;
+import org.apache.ratis.util.Slf4jUtils;
+import org.apache.ratis.util.TimeDuration;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.event.Level;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+public class TestSecureRatisShell extends BaseTest {
+  {
+    Slf4jUtils.setLogLevel(NettyUtils.LOG, Level.DEBUG);
+  }
+
+  private static final Parameters SERVER_PARAMETERS = new Parameters();
+  private static final Parameters CLIENT_PARAMETERS = new Parameters();
+
+  static {
+    final TrustManager emptyTrustManager = 
SecurityTestUtils.emptyTrustManager();
+    try {
+      final KeyManager serverKeyManager = 
SecurityTestUtils.getKeyManager(SecurityTestUtils::getServerKeyStore);
+      final GrpcTlsConfig serverConfig = new GrpcTlsConfig(serverKeyManager, 
emptyTrustManager, true);
+      GrpcConfigKeys.Server.setTlsConf(SERVER_PARAMETERS, serverConfig);
+      GrpcConfigKeys.Admin.setTlsConf(SERVER_PARAMETERS, serverConfig);
+      GrpcConfigKeys.Client.setTlsConf(SERVER_PARAMETERS, serverConfig);
+    } catch (Exception e) {
+      throw new IllegalStateException("Failed to init SERVER_PARAMETERS", e);
+    }
+
+    try {
+      final KeyManager clientKeyManager = 
SecurityTestUtils.getKeyManager(SecurityTestUtils::getClientKeyStore);
+      final GrpcTlsConfig clientConfig = new GrpcTlsConfig(clientKeyManager, 
emptyTrustManager, true);
+      GrpcConfigKeys.Admin.setTlsConf(CLIENT_PARAMETERS, clientConfig);
+      GrpcConfigKeys.Client.setTlsConf(CLIENT_PARAMETERS, clientConfig);
+    } catch (Exception e) {
+      throw new IllegalStateException("Failed to init CLIENT_PARAMETERS", e);
+    }
+  }
+
+  @Test
+  public void testRatisShell() throws Exception {
+    final String[] ids = {"s0"};
+    final RaftProperties properties = new RaftProperties();
+    RaftClientConfigKeys.Rpc.setRequestTimeout(properties, 
TimeDuration.ONE_MINUTE);
+    GrpcConfigKeys.TLS.setEnabled(properties, true);
+    GrpcConfigKeys.TLS.setMutualAuthnEnabled(properties, true);
+
+    try(MiniRaftClusterWithGrpc cluster = new MiniRaftClusterWithGrpc(ids, 
properties, SERVER_PARAMETERS)) {
+      cluster.start();
+
+      runTestRatisShell(cluster, true);
+      runTestRatisShell(cluster, false);
+    }
+  }
+
+  void runTestRatisShell(MiniRaftClusterWithGrpc cluster, boolean secure) 
throws Exception {
+    try(ByteArrayOutputStream out = new ByteArrayOutputStream(1 << 16);
+        RatisShell shell = newRatisShell(out, cluster.getProperties(), 
secure)) {
+      shell.run("group", "info", "-peers", toCliArg(cluster.getPeers()));
+      final String output = out.toString();
+      LOG.info("output (secure? {}):\n{}", secure, output);
+      final String gid = cluster.getGroup().getGroupId().getUuid().toString();
+      if (secure) {
+        Assertions.assertTrue(output.contains(gid), () -> gid + " not found 
for secure shell");
+      } else {
+        Assertions.assertTrue(output.contains("Failed to get group ID"), 
"Unexpected output for unsecure shell");
+      }
+    }
+  }
+
+  static RatisShell newRatisShell(OutputStream out, RaftProperties properties, 
boolean secure) {
+    final PrintStream printStream = new PrintStream(out, true);
+    if (!secure) {
+      return new RatisShell(printStream);
+    }
+    return RatisShell.newBuilder()
+        .setPrintStream(printStream)
+        .setProperties(properties)
+        .setParameters(CLIENT_PARAMETERS)
+        .build();
+  }
+
+  static String toCliArg(List<RaftPeer> peers) {
+    final StringBuilder b = new StringBuilder();
+    for(RaftPeer peer : peers) {
+      b.append(peer.getAdminAddress()).append(",");
+    }
+    return b.substring(0, b.length() - 1);
+  }
+}

Reply via email to