Skip to content

Commit 334d483

Browse files
author
mchimang
committed
feat: add promote member support.
Signed-off-by: mchimang <[email protected]>
1 parent 03a3b99 commit 334d483

File tree

6 files changed

+126
-0
lines changed

6 files changed

+126
-0
lines changed

jetcd-core/src/main/java/io/etcd/jetcd/Cluster.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import io.etcd.jetcd.cluster.MemberAddResponse;
2424
import io.etcd.jetcd.cluster.MemberListResponse;
25+
import io.etcd.jetcd.cluster.MemberPromoteResponse;
2526
import io.etcd.jetcd.cluster.MemberRemoveResponse;
2627
import io.etcd.jetcd.cluster.MemberUpdateResponse;
2728
import io.etcd.jetcd.support.CloseableClient;
@@ -72,4 +73,11 @@ public interface Cluster extends CloseableClient {
7273
*/
7374
CompletableFuture<MemberUpdateResponse> updateMember(long memberID, List<URI> peerAddrs);
7475

76+
/**
77+
* promotes a member from raft learner (non-voting) to raft voting member addresses of the member.
78+
*
79+
* @param memberID the raft learner to be promoted to a raft voting member.
80+
* @return the response
81+
*/
82+
CompletableFuture<MemberPromoteResponse> promoteMember(long memberID);
7583
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2016-2021 The jetcd authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.etcd.jetcd.cluster;
18+
19+
import java.util.List;
20+
21+
import io.etcd.jetcd.Cluster;
22+
import io.etcd.jetcd.impl.AbstractResponse;
23+
24+
/**
25+
* MemberPromoteResponse returned by {@link Cluster#promoteMember(long)}
26+
* contains a header and a list of members after the member update.
27+
*/
28+
public class MemberPromoteResponse extends AbstractResponse<io.etcd.jetcd.api.MemberPromoteResponse> {
29+
30+
private List<Member> members;
31+
32+
public MemberPromoteResponse(io.etcd.jetcd.api.MemberPromoteResponse response) {
33+
super(response, response.getHeader());
34+
}
35+
36+
/**
37+
* Return a list of all members after promoting the member.
38+
*
39+
* @return the list of members.
40+
*/
41+
public synchronized List<Member> getMembers() {
42+
if (members == null) {
43+
members = Util.toMembers(getResponse().getMembersList());
44+
}
45+
46+
return members;
47+
}
48+
}

jetcd-core/src/main/java/io/etcd/jetcd/impl/ClusterImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import io.etcd.jetcd.Cluster;
2525
import io.etcd.jetcd.api.MemberAddRequest;
2626
import io.etcd.jetcd.api.MemberListRequest;
27+
import io.etcd.jetcd.api.MemberPromoteRequest;
2728
import io.etcd.jetcd.api.MemberRemoveRequest;
2829
import io.etcd.jetcd.api.MemberUpdateRequest;
2930
import io.etcd.jetcd.api.VertxClusterGrpc;
3031
import io.etcd.jetcd.cluster.MemberAddResponse;
3132
import io.etcd.jetcd.cluster.MemberListResponse;
33+
import io.etcd.jetcd.cluster.MemberPromoteResponse;
3234
import io.etcd.jetcd.cluster.MemberRemoveResponse;
3335
import io.etcd.jetcd.cluster.MemberUpdateResponse;
3436

@@ -116,4 +118,22 @@ public CompletableFuture<MemberUpdateResponse> updateMember(long memberID, List<
116118
this.stub.memberUpdate(memberUpdateRequest),
117119
MemberUpdateResponse::new);
118120
}
121+
122+
/**
123+
* promotes a member from raft learner (non-voting) to raft voting member addresses of the member.
124+
*
125+
* @param memberID the raft learner to be promoted to a raft voting member.
126+
* @return the response
127+
*/
128+
@Override
129+
public CompletableFuture<MemberPromoteResponse> promoteMember(long memberID) {
130+
MemberPromoteRequest memberPromoteRequest = MemberPromoteRequest.newBuilder()
131+
.setID(memberID)
132+
.build();
133+
134+
return completable(
135+
this.stub.memberPromote(memberPromoteRequest),
136+
MemberPromoteResponse::new);
137+
}
138+
119139
}

jetcd-core/src/main/proto/rpc.proto

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ service Cluster {
9999

100100
// MemberList lists all the members in the cluster.
101101
rpc MemberList(MemberListRequest) returns (MemberListResponse) {}
102+
103+
// MemberPromote promotes a member from raft learner (non-voting) to raft voting member.
104+
rpc MemberPromote(MemberPromoteRequest) returns (MemberPromoteResponse) {}
102105
}
103106

104107
service Maintenance {
@@ -659,6 +662,17 @@ message MemberListResponse {
659662
repeated Member members = 2;
660663
}
661664

665+
message MemberPromoteRequest {
666+
// ID is the member ID of the member to promote.
667+
uint64 ID = 1;
668+
}
669+
670+
message MemberPromoteResponse {
671+
ResponseHeader header = 1;
672+
// members is a list of all members after promoting the member.
673+
repeated Member members = 2;
674+
}
675+
662676
message DefragmentRequest {
663677
}
664678

jetcd-core/src/test/java/io/etcd/jetcd/impl/ClusterMembersTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020
import java.util.concurrent.ExecutionException;
21+
import java.util.concurrent.Future;
2122
import java.util.concurrent.TimeUnit;
2223
import java.util.concurrent.TimeoutException;
2324

@@ -29,9 +30,11 @@
2930
import io.etcd.jetcd.Client;
3031
import io.etcd.jetcd.Cluster;
3132
import io.etcd.jetcd.cluster.Member;
33+
import io.etcd.jetcd.cluster.MemberPromoteResponse;
3234
import io.etcd.jetcd.test.EtcdClusterExtension;
3335

3436
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
3538

3639
@Timeout(value = 30, unit = TimeUnit.SECONDS)
3740
public class ClusterMembersTest {
@@ -112,4 +115,23 @@ public void testMemberManagementAddLearner() throws ExecutionException, Interrup
112115
assertThat(members).hasSize(2);
113116
assertThat(members.stream().filter(Member::isLearner).findAny()).isPresent();
114117
}
118+
119+
@Test
120+
public void testMemberManagementAddLearnerAndPromote() throws ExecutionException, InterruptedException, TimeoutException {
121+
final Client client = Client.builder().endpoints(n1.clientEndpoints()).build();
122+
final Cluster clusterClient = client.getClusterClient();
123+
124+
Member m2 = clusterClient.addMember(n2.peerEndpoints(), true)
125+
.get(5, TimeUnit.SECONDS)
126+
.getMember();
127+
128+
assertThat(m2).isNotNull();
129+
assertThat(m2.isLearner()).isTrue();
130+
131+
// Now attempt to promote a member. Although it fails, it confirms that API was executed.
132+
Future<MemberPromoteResponse> promoteResponseFuture = clusterClient.promoteMember(m2.getId());
133+
assertThatExceptionOfType(ExecutionException.class)
134+
.isThrownBy(promoteResponseFuture::get).withMessageEndingWith(
135+
"io.etcd.jetcd.common.exception.EtcdException: etcdserver: can only promote a learner member which is in sync with leader");
136+
}
115137
}

jetcd-grpc/src/main/proto/rpc.proto

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ service Cluster {
9999

100100
// MemberList lists all the members in the cluster.
101101
rpc MemberList(MemberListRequest) returns (MemberListResponse) {}
102+
103+
// MemberPromote promotes a member from raft learner (non-voting) to raft voting member.
104+
rpc MemberPromote(MemberPromoteRequest) returns (MemberPromoteResponse) {}
102105
}
103106

104107
service Maintenance {
@@ -659,6 +662,17 @@ message MemberListResponse {
659662
repeated Member members = 2;
660663
}
661664

665+
message MemberPromoteRequest {
666+
// ID is the member ID of the member to promote.
667+
uint64 ID = 1;
668+
}
669+
670+
message MemberPromoteResponse {
671+
ResponseHeader header = 1;
672+
// members is a list of all members after promoting the member.
673+
repeated Member members = 2;
674+
}
675+
662676
message DefragmentRequest {
663677
}
664678

0 commit comments

Comments
 (0)