diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java index 5288c0bf50bb..ed8b2ecca0c7 100644 --- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java +++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/ratis/RatisHelper.java @@ -267,6 +267,13 @@ public static RaftClient newRaftClient(RpcType rpcType, RaftPeer leader, ozoneConfiguration); } + public static RaftClient newRaftClient(RpcType rpcType, RaftPeer leader, RaftGroup group, + RetryPolicy retryPolicy, GrpcTlsConfig tlsConfig, + ConfigurationSource configuration) { + return newRaftClient(rpcType, leader.getId(), leader, group, retryPolicy, tlsConfig, configuration); + } + + @SuppressWarnings("checkstyle:ParameterNumber") private static RaftClient newRaftClient(RpcType rpcType, RaftPeerId leader, RaftPeer primary, RaftGroup group, RetryPolicy retryPolicy, diff --git a/hadoop-ozone/tools/pom.xml b/hadoop-ozone/tools/pom.xml index 839d01f0fa84..4864e9777c0d 100644 --- a/hadoop-ozone/tools/pom.xml +++ b/hadoop-ozone/tools/pom.xml @@ -67,6 +67,16 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd"> org.apache.ozone hdds-hadoop-dependency-server + + ratis-common + org.apache.ratis + ${ratis.version} + + + ratis-shell + org.apache.ratis + ${ratis.version} + jakarta.xml.bind diff --git a/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/BaseRatisCommand.java b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/BaseRatisCommand.java new file mode 100644 index 000000000000..37e0c6616d62 --- /dev/null +++ b/hadoop-ozone/tools/src/main/java/org/apache/hadoop/ozone/admin/ratis/BaseRatisCommand.java @@ -0,0 +1,132 @@ +/* + * 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.conf.OzoneConfiguration; +import org.apache.hadoop.hdds.ratis.RatisHelper; +import org.apache.hadoop.hdds.scm.client.ClientTrustManager; +import org.apache.hadoop.hdds.security.SecurityConfig; +import org.apache.hadoop.hdds.security.x509.certificate.client.CACertificateProvider; +import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.client.OzoneClientFactory; +import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx; +import org.apache.ratis.client.RaftClient; +import org.apache.ratis.grpc.GrpcTlsConfig; +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.protocol.GroupInfoReply; +import org.apache.ratis.protocol.RaftGroup; +import org.apache.ratis.protocol.RaftGroupId; +import org.apache.ratis.protocol.RaftPeer; +import org.apache.ratis.rpc.SupportedRpcType; + +import java.util.List; + +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_GRPC_TLS_ENABLED; +import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_GRPC_TLS_ENABLED_DEFAULT; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_ENABLED_DEFAULT; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_ENABLED_KEY; +import static org.apache.ratis.shell.cli.RaftUtils.buildRaftGroupIdFromStr; +import static org.apache.ratis.shell.cli.RaftUtils.buildRaftPeersFromStr; +import static org.apache.ratis.shell.cli.RaftUtils.retrieveGroupInfoByGroupId; +import static org.apache.ratis.shell.cli.RaftUtils.retrieveRemoteGroupId; + +/** + * The base class for all the ratis command. + */ +public class BaseRatisCommand { + private RaftGroup raftGroup; + private GroupInfoReply groupInfoReply; + private OzoneConfiguration config; + private String omServiceId; + + public void run(String peers, String groupid, String omServiceId) throws Exception { + this.config = new OzoneConfiguration(); + this.omServiceId = omServiceId; + List 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; + } + + +}