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 5507ca9  RATIS-1427. Add ratis-shell info command (#529)
5507ca9 is described below

commit 5507ca9813de6dff8084829e97102150c55f3b56
Author: maobaolong <[email protected]>
AuthorDate: Thu Nov 11 11:18:57 2021 +0800

    RATIS-1427. Add ratis-shell info command (#529)
---
 ratis-shell/pom.xml                                |  16 ++
 .../java/org/apache/ratis/shell/cli/RaftUtils.java |  82 +++++++++
 .../shell/cli/sh/command/AbstractRatisCommand.java | 194 +++++++++++++++++++++
 .../ratis/shell/cli/sh/command/InfoCommand.java    |  83 +++++++++
 4 files changed, 375 insertions(+)

diff --git a/ratis-shell/pom.xml b/ratis-shell/pom.xml
index 1b3a26f..176c2a3 100644
--- a/ratis-shell/pom.xml
+++ b/ratis-shell/pom.xml
@@ -29,8 +29,24 @@
 
   <dependencies>
     <dependency>
+      <artifactId>ratis-client</artifactId>
       <groupId>org.apache.ratis</groupId>
+    </dependency>
+    <dependency>
       <artifactId>ratis-common</artifactId>
+      <groupId>org.apache.ratis</groupId>
+    </dependency>
+    <dependency>
+      <artifactId>ratis-grpc</artifactId>
+      <groupId>org.apache.ratis</groupId>
+    </dependency>
+    <dependency>
+      <artifactId>ratis-metrics</artifactId>
+      <groupId>org.apache.ratis</groupId>
+    </dependency>
+    <dependency>
+      <artifactId>ratis-server-api</artifactId>
+      <groupId>org.apache.ratis</groupId>
     </dependency>
 
     <dependency>
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/RaftUtils.java 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/RaftUtils.java
new file mode 100644
index 0000000..dfbfdd3
--- /dev/null
+++ b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/RaftUtils.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.client.RaftClientConfigKeys;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.protocol.RaftGroup;
+import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.retry.ExponentialBackoffRetry;
+import org.apache.ratis.util.TimeDuration;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for raft operations.
+ */
+public final class RaftUtils {
+
+  private RaftUtils() {
+    // prevent instantiation
+  }
+
+  /**
+   * Gets the raft peer id.
+   *
+   * @param address the address of the server
+   * @return the raft peer id
+   */
+  public static RaftPeerId getPeerId(InetSocketAddress address) {
+    return getPeerId(address.getHostString(), address.getPort());
+  }
+
+  /**
+   * Gets the raft peer id.
+   *
+   * @param host the hostname of the server
+   * @param port the port of the server
+   * @return the raft peer id
+   */
+  public static RaftPeerId getPeerId(String host, int port) {
+    return RaftPeerId.getRaftPeerId(host + "_" + port);
+  }
+
+  /**
+   * Create a raft client to communicate to ratis server.
+   * @param raftGroup the raft group
+   * @return return a raft client
+   */
+  public static RaftClient createClient(RaftGroup raftGroup) {
+    RaftProperties properties = new RaftProperties();
+    RaftClientConfigKeys.Rpc.setRequestTimeout(properties,
+        TimeDuration.valueOf(15, TimeUnit.SECONDS));
+    ExponentialBackoffRetry retryPolicy = ExponentialBackoffRetry.newBuilder()
+        .setBaseSleepTime(TimeDuration.valueOf(1000, TimeUnit.MILLISECONDS))
+        .setMaxAttempts(10)
+        .setMaxSleepTime(
+            TimeDuration.valueOf(100_000, TimeUnit.MILLISECONDS))
+        .build();
+    return RaftClient.newBuilder()
+        .setRaftGroup(raftGroup)
+        .setProperties(properties)
+        .setRetryPolicy(retryPolicy)
+        .build();
+  }
+}
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
new file mode 100644
index 0000000..07e76cf
--- /dev/null
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
@@ -0,0 +1,194 @@
+/*
+ * 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.command;
+
+import org.apache.ratis.protocol.*;
+import org.apache.ratis.protocol.exceptions.RaftException;
+import org.apache.ratis.shell.cli.Command;
+import org.apache.ratis.shell.cli.RaftUtils;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.proto.RaftProtos.FollowerInfoProto;
+import org.apache.ratis.proto.RaftProtos.RaftPeerProto;
+import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
+import org.apache.ratis.proto.RaftProtos.RoleInfoProto;
+import org.apache.ratis.util.function.CheckedFunction;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * The base class for all the ratis shell {@link Command} classes.
+ */
+public abstract class AbstractRatisCommand implements Command {
+  public static final String PEER_OPTION_NAME = "peers";
+  public static final String GROUPID_OPTION_NAME = "groupid";
+  public static final RaftGroupId DEFAULT_RAFT_GROUP_ID = 
RaftGroupId.randomId();
+
+  /**
+   * Execute a given function with input parameter from the members of a list.
+   *
+   * @param list the input parameters
+   * @param function the function to be executed
+   * @param <T> parameter type
+   * @param <K> return value type
+   * @param <E> the exception type thrown by the given function.
+   * @return the value returned by the given function.
+   */
+  public static <T, K, E extends Throwable> K run(Collection<T> list, 
CheckedFunction<T, K, E> function) {
+    for (T t : list) {
+      try {
+        K ret = function.apply(t);
+        if (ret != null) {
+          return ret;
+        }
+      } catch (Throwable e) {
+        e.printStackTrace();
+      }
+    }
+    return null;
+  }
+
+  private final PrintStream printStream;
+  private RaftGroup raftGroup;
+  private GroupInfoReply groupInfoReply;
+
+  protected AbstractRatisCommand(Context context) {
+    printStream = context.getPrintStream();
+  }
+
+  @Override
+  public int run(CommandLine cl) throws IOException {
+    List<InetSocketAddress> addresses = new ArrayList<>();
+    String peersStr = "";
+    if (cl.hasOption(PEER_OPTION_NAME)) {
+      peersStr = cl.getOptionValue(PEER_OPTION_NAME);
+    }
+    String[] peersArray = peersStr.split(",");
+    for (int i = 0; i < peersArray.length; i++) {
+      String[] hostPortPair = peersArray[i].split(":");
+      InetSocketAddress addr =
+          new InetSocketAddress(hostPortPair[0], 
Integer.parseInt(hostPortPair[1]));
+      addresses.add(addr);
+    }
+
+    final RaftGroupId raftGroupIdFromConfig = 
cl.hasOption(GROUPID_OPTION_NAME)?
+        
RaftGroupId.valueOf(UUID.fromString(cl.getOptionValue(GROUPID_OPTION_NAME)))
+        : DEFAULT_RAFT_GROUP_ID;
+
+    List<RaftPeer> peers = addresses.stream()
+        .map(addr -> RaftPeer.newBuilder()
+            .setId(RaftUtils.getPeerId(addr))
+            .setAddress(addr)
+            .build()
+        ).collect(Collectors.toList());
+    raftGroup = RaftGroup.valueOf(raftGroupIdFromConfig, peers);
+    try (final RaftClient client = RaftUtils.createClient(raftGroup)) {
+      final RaftGroupId remoteGroupId;
+      if (raftGroupIdFromConfig != DEFAULT_RAFT_GROUP_ID) {
+        remoteGroupId = raftGroupIdFromConfig;
+      } else {
+        final List<RaftGroupId> groupIds = run(peers,
+            p -> 
client.getGroupManagementApi((p.getId())).list().getGroupIds());
+
+        if (groupIds == null) {
+          println("Failed to get group ID from " + peers);
+          return -1;
+        } else if (groupIds.size() == 1) {
+          remoteGroupId = groupIds.get(0);
+        } else {
+          println("There are more than one groups, you should specific one. " 
+ groupIds);
+          return -2;
+        }
+      }
+
+      groupInfoReply = run(peers, p -> 
client.getGroupManagementApi((p.getId())).info(remoteGroupId));
+      processReply(groupInfoReply,
+          () -> "Failed to get group info for group id " + 
remoteGroupId.getUuid() + " from " + peers);
+      raftGroup = groupInfoReply.getGroup();
+    }
+    return 0;
+  }
+
+  @Override
+  public void validateArgs(CommandLine cl) throws IllegalArgumentException {
+    if (!cl.hasOption(PEER_OPTION_NAME)) {
+      throw new IllegalArgumentException(String.format(
+          "should provide [%s]", PEER_OPTION_NAME));
+    }
+  }
+
+  @Override
+  public Options getOptions() {
+    return new Options()
+            .addOption(PEER_OPTION_NAME, true, "Peer addresses seperated by 
comma")
+            .addOption(GROUPID_OPTION_NAME, true, "Raft group id");
+  }
+
+  protected void printf(String format, Object... args) {
+    printStream.printf(format, args);
+  }
+
+  protected void println(Object message) {
+    printStream.println(message);
+  }
+
+  protected RaftGroup getRaftGroup() {
+    return raftGroup;
+  }
+
+  protected GroupInfoReply getGroupInfoReply() {
+    return groupInfoReply;
+  }
+
+  /**
+   * Get the leader id.
+   *
+   * @param roleInfo the role info
+   * @return the leader id
+   */
+  protected RaftPeerProto getLeader(RoleInfoProto roleInfo) {
+    if (roleInfo == null) {
+      return null;
+    }
+    if (roleInfo.getRole() == RaftPeerRole.LEADER) {
+      return roleInfo.getSelf();
+    }
+    FollowerInfoProto followerInfo = roleInfo.getFollowerInfo();
+    if (followerInfo == null) {
+      return null;
+    }
+    return followerInfo.getLeaderInfo().getId();
+  }
+
+  protected void processReply(RaftClientReply reply, Supplier<String> 
messageSupplier) throws IOException {
+    if (reply == null || !reply.isSuccess()) {
+      final RaftException e = Optional.ofNullable(reply)
+          .map(RaftClientReply::getException)
+          .orElseGet(() -> new RaftException("Reply: " + reply));
+      final String message = messageSupplier.get();
+      printf("%s. Error: %s%n", message, e);
+      throw new IOException(message, e);
+    }
+  }
+}
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/InfoCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/InfoCommand.java
new file mode 100644
index 0000000..6e05a65
--- /dev/null
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/InfoCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.command;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.ratis.proto.RaftProtos;
+import org.apache.ratis.protocol.GroupInfoReply;
+
+import java.io.IOException;
+
+/**
+ * Command for querying ratis group information.
+ */
+public class InfoCommand extends AbstractRatisCommand {
+
+  /**
+   * @param context command context
+   */
+  public InfoCommand(Context context) {
+    super(context);
+  }
+
+  @Override
+  public String getCommandName() {
+    return "info";
+  }
+
+  @Override
+  public int run(CommandLine cl) throws IOException {
+    super.run(cl);
+    println("group id: " + getRaftGroup().getGroupId().getUuid());
+    final GroupInfoReply reply = getGroupInfoReply();
+    RaftProtos.RaftPeerProto leader = getLeader(reply.getRoleInfoProto());
+    if (leader == null) {
+      println("leader not found");
+    } else {
+      printf("leader info: %s(%s)%n%n", leader.getId().toStringUtf8(), 
leader.getAddress());
+    }
+    println(reply.getCommitInfos());
+    return 0;
+  }
+
+  @Override
+  public String getUsage() {
+    return String.format("%s"
+        + " [-%s 
PEER0_HOST:PEER0_PORT,PEER1_HOST:PEER1_PORT,PEER2_HOST:PEER2_PORT]"
+        + " [-%s RAFT_GROUP_ID]",
+        getCommandName(), PEER_OPTION_NAME, GROUPID_OPTION_NAME);
+  }
+
+  @Override
+  public String getDescription() {
+    return description();
+  }
+
+  @Override
+  public Options getOptions() {
+    return super.getOptions();
+  }
+
+  /**
+   * @return command's description
+   */
+  public static String description() {
+    return "Display the information of a specific raft group";
+  }
+}

Reply via email to