Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RATIS-1594. Support ADD mode to SetConfiguration #658

Merged
merged 8 commits into from
Jul 4, 2022

Conversation

codings-dan
Copy link
Contributor

@codings-dan
Copy link
Contributor Author

@szetszwo @SzyWilliam Can you help review this patch, thanks!

Copy link
Member

@SzyWilliam SzyWilliam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM : )

Copy link
Contributor

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , thanks a lot for working on this! Some comments inlined.

We should think about what should happen in ADD mode when a new peer already exists in the current peer list? Should the request fail? Cc @SzyWilliam .

Comment on lines 45 to 57
RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, SetConfigurationRequest.Mode mode)
throws IOException;

/** Set the configuration request to the raft service. */
RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf)
throws IOException;

RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf,
SetConfigurationRequest.Mode mode) throws IOException;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With ADD and CAS modes, the combination of arguments becomes complicated. Let's add a SetConfigurationArguments class and a Builder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I have refactored this part of the interface related code, please take a look, thank you!

@@ -23,19 +23,26 @@
import java.util.List;

public class SetConfigurationRequest extends RaftClientRequest {

public enum Mode {
NORMAL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it SET_UNCONDITIONALLY.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -409,6 +409,12 @@ message SetConfigurationRequestProto {
RaftRpcRequestProto rpcRequest = 1;
repeated RaftPeerProto peers = 2;
repeated RaftPeerProto listeners = 3;
enum RaftConfigurationMode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to SetConfigurationRequest, let's simply call it "Mode". Also, please move the enum to the top.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1114 to 1115
List<RaftPeer> peers = Stream.of(peersInNewConf, new ArrayList<>(current.getAllPeers()))
.flatMap(List :: stream).distinct().collect(Collectors.toList());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should happen in ADD mode when a new peer already exists in the current peer list? Should the request fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emm, I think if the new peer already exists in the current peer list, we could return success, I have change the related code, PTAL, thank you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the new peer may have a different priority. Should we allow it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only check peers that are not in the current list, other semantics do not change, what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ADD an existing peer with a different priority, we may either

  1. return failure and the configuration remains unchanged, or
  2. return success and the configuration changes to the new server priority.

If we do the following, then the user request (priority change) is ignored silently. It sounds like a bug.

  • return success and the configuration remains unchanged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code already implements the second point:

  1. return success and the configuration changes to the new server priority.

Please take a look at RaftConfigurationImpl#hasNoChange

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , But we use distinct() so that we are not sure whether the existing or the new server is used.

BTW, RaftConfigurationImpl#hasNoChange does not check listeners at all. It is a bug.

@szetszwo
Copy link
Contributor

Below are some rough ideas. Forgot to post it previously.

diff --git a/ratis-client/src/main/java/org/apache/ratis/client/api/AdminApi.java b/ratis-client/src/main/java/org/apache/ratis/client/api/AdminApi.java
index c9c3f7b4..fc11c9b0 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/api/AdminApi.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/api/AdminApi.java
@@ -32,6 +32,22 @@ import java.util.List;
  * such as setting raft configuration and transferring leadership.
  */
 public interface AdminApi {
+  class SetConfigurationArguments {
+    // TODO add details
+
+    public static Builder newBuilder() {
+      return new Builder();
+    }
+
+    public static class Builder {
+      // TODO add details
+
+      public SetConfigurationArguments build() {
+        return null;
+      }
+    }
+  }
+
   /** The same as setConfiguration(serversInNewConf, Collections.emptyList()). */
   default RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf) throws IOException {
     return setConfiguration(serversInNewConf, Collections.emptyList());
@@ -42,15 +58,15 @@ public interface AdminApi {
     return setConfiguration(Arrays.asList(serversInNewConf));
   }
 
-  RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, SetConfigurationRequest.Mode mode)
-      throws IOException;
-
   /** Set the configuration request to the raft service. */
-  RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf)
-      throws IOException;
+  default RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf)
+      throws IOException {
+    // TODO use Builder
+    return setConfiguration(SetConfigurationArguments.newBuilder().build());
+  }
 
-  RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf,
-      SetConfigurationRequest.Mode mode) throws IOException;
+  RaftClientReply setConfiguration(SetConfigurationArguments arguments)
+      throws IOException;
 
   /** The same as setConfiguration(Arrays.asList(serversInNewConf), Arrays.asList(listenersInNewConf)). */
   default RaftClientReply setConfiguration(RaftPeer[] serversInNewConf, RaftPeer[] listenersInNewConf)
diff --git a/ratis-proto/src/main/proto/Raft.proto b/ratis-proto/src/main/proto/Raft.proto
index 2c149846..f6b38c14 100644
--- a/ratis-proto/src/main/proto/Raft.proto
+++ b/ratis-proto/src/main/proto/Raft.proto
@@ -406,15 +406,14 @@ message RaftClientReplyProto {
 
 // setConfiguration request
 message SetConfigurationRequestProto {
+  enum Mode {
+    SET_UNCONDITIONALLY = 0;
+    ADD = 1;
+  }
   RaftRpcRequestProto rpcRequest = 1;
   repeated RaftPeerProto peers = 2;
   repeated RaftPeerProto listeners = 3;
-  enum RaftConfigurationMode {
-    NORMAL = 0;
-    ADD = 1;
-  }
-
-  optional RaftConfigurationMode mode = 4;
+  optional Mode mode = 4;
 }
 
 // transfer leadership request

Copy link
Contributor

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , thanks for the update. Some comments inlined.

Comment on lines 45 to 50
RaftClientReply setConfiguration(List<RaftPeer> serversInNewConf, List<RaftPeer> listenersInNewConf)
throws IOException;

/** The same as setConfiguration(Arrays.asList(serversInNewConf), Arrays.asList(listenersInNewConf)). */
default RaftClientReply setConfiguration(RaftPeer[] serversInNewConf, RaftPeer[] listenersInNewConf)
throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot remove these methods. Otherwise, it will become an incompatible change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out, I have fixed it

private final List<RaftPeer> peers;
private final List<RaftPeer> listeners;
private Mode mode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it final in order to keep it immutable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 1114 to 1115
List<RaftPeer> peers = Stream.of(peersInNewConf, new ArrayList<>(current.getAllPeers()))
.flatMap(List :: stream).distinct().collect(Collectors.toList());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the new peer may have a different priority. Should we allow it?

Copy link
Contributor

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , thanks for the update. For the detailed comments, see https://issues.apache.org/jira/secure/attachment/13045825/658_review.patch

Comment on lines 1114 to 1115
List<RaftPeer> peers = Stream.of(peersInNewConf, new ArrayList<>(current.getAllPeers()))
.flatMap(List :: stream).distinct().collect(Collectors.toList());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , But we use distinct() so that we are not sure whether the existing or the new server is used.

BTW, RaftConfigurationImpl#hasNoChange does not check listeners at all. It is a bug.

@@ -31,24 +32,108 @@
* such as setting raft configuration and transferring leadership.
*/
public interface AdminApi {

class SetConfigurationArguments {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move it to SetConfigurationRequest so that it can be used everywhere.

Copy link
Contributor

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , thanks for the update. Some minor comments inlined.

if (!isStable() || conf.size() != newMembers.size()) {
boolean hasNoChange(Collection<RaftPeer> newMembers, Collection<RaftPeer> newListeners) {
if (!isStable() || conf.size() != newMembers.size()
|| conf.getPeers(RaftPeerRole.LISTENER).size() != newListeners.size()) {
return false;
}
for (RaftPeer peer : newMembers) {
if (!conf.contains(peer.getId()) || conf.getPeer(peer.getId()).getPriority() != peer.getPriority()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if-statement is no longer needed since the protos are compared below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -23,6 +23,7 @@
import org.apache.ratis.RaftTestUtil.SimpleMessage;
import org.apache.ratis.client.RaftClient;
import org.apache.ratis.client.RaftClientRpc;
import org.apache.ratis.client.api.AdminApi;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have remove the extra code

Comment on lines 36 to 38
private List<RaftPeer> serversInNewConf;
private List<RaftPeer> listenersInNewConf;
private Mode mode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add final so that the class becomes immutable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@szetszwo
Copy link
Contributor

szetszwo commented Jul 2, 2022

@codings-dan , there are some test failures. Please take a look.

if (!inConf.getRaftPeerProto().equals(peer.getRaftPeerProto())) {
if (inConf.getPriority() != peer.getPriority()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan , I guess comparing protos has caused test failures. Why it is not working? It seems comparing protos is correct since it will compare all the fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now. The hostnames could be different

  • inConf:
id: "s0"
address: "localhost:59232"
dataStreamAddress: "localhost:59235"
clientAddress: "localhost:59234"
adminAddress: "localhost:59233"
  • peer :
id: "s0"
address: "0.0.0.0:59232"
clientAddress: "0.0.0.0:59234"
adminAddress: "0.0.0.0:59233"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codings-dan, Let's compare only the priority as you have already done. We should think about how to support updating hostnames and ports. Perhaps it needs a new mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szetszwo Yean, the hostname could be different, so I only compare the priority.

We should think about how to support updating hostnames and ports

I will try, thanks!

Copy link
Contributor

@szetszwo szetszwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 the change looks good.

@szetszwo szetszwo merged commit aeec309 into apache:master Jul 4, 2022
@codings-dan codings-dan deleted the 1594 branch July 5, 2022 07:56
symious pushed a commit to symious/ratis that referenced this pull request Mar 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants