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";
+ }
+}