peerList = buildRaftPeersFromStr(peers);
+ RaftGroupId raftGroupIdFromConfig = buildRaftGroupIdFromStr(groupid);
+ raftGroup = RaftGroup.valueOf(raftGroupIdFromConfig, peerList);
+
+ try (RaftClient client = buildRaftClient(raftGroup)) {
+ RaftGroupId remoteGroupId = retrieveRemoteGroupId(raftGroupIdFromConfig, peerList, client, System.out);
+ groupInfoReply = retrieveGroupInfoByGroupId(remoteGroupId, peerList, client, System.out);
+ raftGroup = groupInfoReply.getGroup();
+ }
+
+ }
+
+ protected RaftClient buildRaftClient(RaftGroup raftGroup) throws Exception {
+ GrpcTlsConfig tlsConfig = null;
+ if (config.getBoolean(OZONE_SECURITY_ENABLED_KEY, OZONE_SECURITY_ENABLED_DEFAULT) &&
+ config.getBoolean(HDDS_GRPC_TLS_ENABLED, HDDS_GRPC_TLS_ENABLED_DEFAULT)) {
+ tlsConfig = createGrpcTlsConf(omServiceId);
+ }
+
+ return RatisHelper.newRaftClient(
+ SupportedRpcType.GRPC, null, raftGroup,
+ RatisHelper.createRetryPolicy(config), tlsConfig, config);
+ }
+
+ public RaftGroup getRaftGroup() {
+ return raftGroup;
+ }
+
+ public 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();
+ }
+
+ private GrpcTlsConfig createGrpcTlsConf(String omServiceId) throws Exception {
+ OzoneClient certClient = OzoneClientFactory.getRpcClient(omServiceId,
+ config);
+
+ ServiceInfoEx serviceInfoEx = certClient
+ .getObjectStore()
+ .getClientProxy()
+ .getOzoneManagerClient()
+ .getServiceInfo();
+
+ CACertificateProvider remoteCAProvider =
+ serviceInfoEx::provideCACerts;
+ ClientTrustManager trustManager = new ClientTrustManager(remoteCAProvider, serviceInfoEx);
+
+ return RatisHelper.createTlsClientConfig(new SecurityConfig(config), trustManager);
+ }
+}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/RatisAdmin.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/RatisAdmin.java
new file mode 100644
index 000000000000..9820adb55109
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/RatisAdmin.java
@@ -0,0 +1,65 @@
+/**
+ * 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.ozone.admin.ratis;
+
+import org.apache.hadoop.hdds.cli.GenericCli;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.cli.OzoneAdmin;
+import org.apache.hadoop.hdds.cli.SubcommandWithParent;
+import org.apache.hadoop.ozone.admin.ratis.group.GroupCommand;
+import org.kohsuke.MetaInfServices;
+import picocli.CommandLine;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Spec;
+
+
+/**
+ * Subcommand for ratis operations.
+ */
+@CommandLine.Command(
+ name = "ratis",
+ description = "Ozone ratis operations",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class,
+ subcommands = {
+ GroupCommand.class
+ })
+@MetaInfServices(SubcommandWithParent.class)
+public class RatisAdmin extends GenericCli implements SubcommandWithParent {
+
+ @CommandLine.ParentCommand
+ private OzoneAdmin parent;
+
+ @Spec
+ private CommandSpec spec;
+
+ public OzoneAdmin getParent() {
+ return parent;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ GenericCli.missingSubcommand(spec);
+ return null;
+ }
+
+ @Override
+ public Class> getParentType() {
+ return OzoneAdmin.class;
+ }
+}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/GroupCommand.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/GroupCommand.java
new file mode 100644
index 000000000000..5ff42c429fab
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/GroupCommand.java
@@ -0,0 +1,47 @@
+package org.apache.hadoop.ozone.admin.ratis.group;
+
+import org.apache.hadoop.hdds.cli.GenericCli;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.cli.SubcommandWithParent;
+import org.apache.hadoop.ozone.admin.ratis.RatisAdmin;
+import org.kohsuke.MetaInfServices;
+import picocli.CommandLine;
+
+/**
+ * Subcommand for ratis.
+ */
+@CommandLine.Command(
+ name = "group",
+ description = "ratis group operations",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class,
+ subcommands = {
+ InfoCommand.class,
+ ListCommand.class
+ })
+@MetaInfServices(SubcommandWithParent.class)
+public class GroupCommand extends GenericCli implements SubcommandWithParent {
+
+ @CommandLine.ParentCommand
+ private RatisAdmin parent;
+
+ @CommandLine.Spec
+ private CommandLine.Model.CommandSpec spec;
+
+ public RatisAdmin getParent() {
+ return parent;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ GenericCli.missingSubcommand(spec);
+ return null;
+ }
+
+ @Override
+ public Class> getParentType() {
+ return RatisAdmin.class;
+ }
+
+
+}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/InfoCommand.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/InfoCommand.java
new file mode 100644
index 000000000000..bb1b32577dd2
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/InfoCommand.java
@@ -0,0 +1,72 @@
+/*
+ * 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.ozone.admin.ratis.group;
+
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.admin.ratis.BaseRatisCommand;
+import org.apache.ratis.proto.RaftProtos;
+import org.apache.ratis.protocol.GroupInfoReply;
+import picocli.CommandLine;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Display the information of a specific raft group
+ */
+@CommandLine.Command(
+ name = "info",
+ description = "Display the information of a specific raft group",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class InfoCommand extends BaseRatisCommand implements Callable {
+
+ @CommandLine.ParentCommand
+ public GroupCommand parent;
+
+ @CommandLine.Option(names = {"-peers"},
+ description = "list of peers",
+ required = true)
+ private String peers;
+
+ @CommandLine.Option(names = { "-groupid" },
+ description = "groupid")
+ private String groupid;
+
+ @CommandLine.Option(names = {"-id", "--service-id"},
+ description = "OM Service ID",
+ required = true)
+ private String omServiceId;
+
+
+ @Override
+ public Void call() throws Exception {
+ super.run(peers, groupid, omServiceId);
+ System.out.println("group id: " + getRaftGroup().getGroupId().getUuid());
+ final GroupInfoReply reply = getGroupInfoReply();
+ RaftProtos.RaftPeerProto leader = getLeader(reply.getRoleInfoProto());
+ if (leader == null) {
+ System.out.println("leader not found");
+ } else {
+ System.out.printf("leader info: %s(%s)%n%n", leader.getId().toStringUtf8(), leader.getAddress());
+ }
+ System.out.println(reply.getCommitInfos());
+ return null;
+ }
+
+}
diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/ListCommand.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/ListCommand.java
new file mode 100644
index 000000000000..e331bdb8b084
--- /dev/null
+++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/group/ListCommand.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ozone.admin.ratis.group;
+
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.ozone.admin.ratis.BaseRatisCommand;
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.protocol.GroupListReply;
+import org.apache.ratis.protocol.RaftPeerId;
+import picocli.CommandLine;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Callable;
+
+import static org.apache.ratis.shell.cli.RaftUtils.getPeerId;
+import static org.apache.ratis.shell.cli.RaftUtils.parseInetSocketAddress;
+import static org.apache.ratis.shell.cli.RaftUtils.processReply;
+
+
+/**
+ * Display the group information of a specific raft server
+ */
+@CommandLine.Command(
+ name = "list",
+ description = "Display the group information of a specific raft server",
+ mixinStandardHelpOptions = true,
+ versionProvider = HddsVersionProvider.class)
+public class ListCommand extends BaseRatisCommand implements Callable {
+
+ public static final String SERVER_ADDRESS_OPTION_NAME = "serverAddress";
+ public static final String PEER_ID_OPTION_NAME = "peerId";
+
+
+ @picocli.CommandLine.ParentCommand
+ public GroupCommand parent;
+
+ @picocli.CommandLine.Option(names = {"-peers"},
+ description = "list of peers",
+ required = true)
+ private String peers;
+
+ @picocli.CommandLine.Option(names = {"-" + PEER_ID_OPTION_NAME},
+ description = "Id of peer")
+ private String peerIdStr;
+
+ @picocli.CommandLine.Option(names = { "-groupid" },
+ description = "groupid")
+ private String groupid;
+
+ @picocli.CommandLine.Option(names = { "-" + SERVER_ADDRESS_OPTION_NAME},
+ description = "serverAddress")
+ private String serverAddress;
+
+ @CommandLine.Option(names = {"-id", "--service-id"},
+ description = "OM Service ID",
+ required = true)
+ private String omServiceId;
+
+
+ @Override
+ public Void call() throws Exception {
+ super.run(peers, groupid, omServiceId);
+ final RaftPeerId peerId;
+ final String address;
+
+ if (StringUtils.isNotEmpty(peerIdStr)) {
+ peerId = RaftPeerId.getRaftPeerId(peerIdStr);
+ address = getRaftGroup().getPeer(peerId).getAddress();
+ } else if (StringUtils.isNotEmpty(serverAddress)) {
+ address = serverAddress;
+ final InetSocketAddress serverAddress = parseInetSocketAddress(address);
+ peerId = RaftUtils.getPeerId(serverAddress);
+ } else {
+ throw new IllegalArgumentException(
+ "Both " + PEER_ID_OPTION_NAME + " and " + SERVER_ADDRESS_OPTION_NAME
+ + " options are missing.");
+ }
+
+ try(final RaftClient raftClient = buildRaftClient(getRaftGroup())) {
+ GroupListReply reply = raftClient.getGroupManagementApi(peerId).list();
+ processReply(reply,
+ System.out, String.format("Failed to get group information of peerId %s (server %s)", peerId, address));
+ System.out.println(String.format("The peerId %s (server %s) is in %d groups, and the groupIds is: %s",
+ peerId, address, reply.getGroupIds().size(), reply.getGroupIds()));
+ }
+ return null;
+ }
+
+
+}