@@ -27,6 +27,18 @@ func (m *mockMemberStatusChecker) MemberStatus(ctx context.Context, member *etcd
27
27
return nil , errors .New ("member status not found" )
28
28
}
29
29
30
+ // mockLeaderMover implements LeaderMover for testing
31
+ type mockLeaderMover struct {
32
+ moveLeaderErrors map [uint64 ]error
33
+ }
34
+
35
+ func (m * mockLeaderMover ) MoveLeader (ctx context.Context , toMember uint64 ) error {
36
+ if err , exists := m .moveLeaderErrors [toMember ]; exists {
37
+ return err
38
+ }
39
+ return nil
40
+ }
41
+
30
42
func TestFindLeader (t * testing.T ) {
31
43
ctx := context .Background ()
32
44
@@ -164,81 +176,140 @@ func TestFindLeader(t *testing.T) {
164
176
}
165
177
}
166
178
167
- func TestFindLeader_ContextCancellation (t * testing.T ) {
168
- ctx , cancel := context .WithCancel (context .Background ())
169
- cancel () // Cancel immediately
170
-
171
- mockClient := & mockMemberStatusChecker {
172
- memberStatuses : map [uint64 ]* clientv3.StatusResponse {
173
- 1 : {Header : & etcdserverpb.ResponseHeader {MemberId : 1 }, Leader : 1 },
174
- },
175
- }
176
-
177
- memberList := []* etcdserverpb.Member {
178
- {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }},
179
- }
180
-
181
- // The function should handle context cancellation gracefully
182
- // Since our mock doesn't actually check context, this test verifies
183
- // that the function signature accepts context and passes it through
184
- leader , err := FindLeader (ctx , mockClient , memberList )
185
- require .NoError (t , err )
186
- require .NotNil (t , leader )
187
- assert .Equal (t , uint64 (1 ), leader .ID )
188
- }
189
-
190
- func TestFindLeader_EdgeCases (t * testing.T ) {
179
+ func TestMoveLeaderToAnotherMember (t * testing.T ) {
191
180
ctx := context .Background ()
192
181
193
- t .Run ("member with no client URLs" , func (t * testing.T ) {
194
- mockClient := & mockMemberStatusChecker {
195
- memberStatuses : map [uint64 ]* clientv3.StatusResponse {
196
- 1 : {Header : & etcdserverpb.ResponseHeader {MemberId : 1 }, Leader : 1 },
182
+ tests := []struct {
183
+ name string
184
+ leader * etcdserverpb.Member
185
+ memberList []* etcdserverpb.Member
186
+ moveLeaderErrors map [uint64 ]error
187
+ expectedSuccess bool
188
+ expectedError string
189
+ }{
190
+ {
191
+ name : "successfully move leader to another member" ,
192
+ leader : & etcdserverpb.Member {
193
+ ID : 1 ,
194
+ Name : "etcd-1" ,
195
+ ClientURLs : []string {"https://10.0.0.1:2379" },
196
+ PeerURLs : []string {"https://10.0.0.1:2380" },
197
197
},
198
- }
199
-
200
- memberList := []* etcdserverpb.Member {
201
- {ID : 1 , Name : "etcd-1" , ClientURLs : []string {}},
202
- }
203
-
204
- // This should work fine since our mock doesn't actually use ClientURLs
205
- leader , err := FindLeader (ctx , mockClient , memberList )
206
- require .NoError (t , err )
207
- require .NotNil (t , leader )
208
- assert .Equal (t , uint64 (1 ), leader .ID )
209
- })
210
-
211
- t .Run ("member with nil client URLs" , func (t * testing.T ) {
212
- mockClient := & mockMemberStatusChecker {
213
- memberStatuses : map [uint64 ]* clientv3.StatusResponse {
214
- 1 : {Header : & etcdserverpb.ResponseHeader {MemberId : 1 }, Leader : 1 },
198
+ memberList : []* etcdserverpb.Member {
199
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
200
+ {ID : 2 , Name : "etcd-2" , ClientURLs : []string {"https://10.0.0.2:2379" }, PeerURLs : []string {"https://10.0.0.2:2380" }},
201
+ {ID : 3 , Name : "etcd-3" , ClientURLs : []string {"https://10.0.0.3:2379" }, PeerURLs : []string {"https://10.0.0.3:2380" }},
215
202
},
216
- }
217
-
218
- memberList := []* etcdserverpb.Member {
219
- {ID : 1 , Name : "etcd-1" , ClientURLs : nil },
220
- }
203
+ expectedSuccess : true ,
204
+ },
205
+ {
206
+ name : "successfully move leader when leader is in middle of list" ,
207
+ leader : & etcdserverpb.Member {
208
+ ID : 2 ,
209
+ Name : "etcd-2" ,
210
+ ClientURLs : []string {"https://10.0.0.2:2379" },
211
+ PeerURLs : []string {"https://10.0.0.2:2380" },
212
+ },
213
+ memberList : []* etcdserverpb.Member {
214
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
215
+ {ID : 2 , Name : "etcd-2" , ClientURLs : []string {"https://10.0.0.2:2379" }, PeerURLs : []string {"https://10.0.0.2:2380" }},
216
+ {ID : 3 , Name : "etcd-3" , ClientURLs : []string {"https://10.0.0.3:2379" }, PeerURLs : []string {"https://10.0.0.3:2380" }},
217
+ },
218
+ expectedSuccess : true ,
219
+ },
220
+ {
221
+ name : "successfully move leader when leader is last in list" ,
222
+ leader : & etcdserverpb.Member {
223
+ ID : 3 ,
224
+ Name : "etcd-3" ,
225
+ ClientURLs : []string {"https://10.0.0.3:2379" },
226
+ PeerURLs : []string {"https://10.0.0.3:2380" },
227
+ },
228
+ memberList : []* etcdserverpb.Member {
229
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
230
+ {ID : 2 , Name : "etcd-2" , ClientURLs : []string {"https://10.0.0.2:2379" }, PeerURLs : []string {"https://10.0.0.2:2380" }},
231
+ {ID : 3 , Name : "etcd-3" , ClientURLs : []string {"https://10.0.0.3:2379" }, PeerURLs : []string {"https://10.0.0.3:2380" }},
232
+ },
233
+ expectedSuccess : true ,
234
+ },
235
+ {
236
+ name : "move leader fails with error" ,
237
+ leader : & etcdserverpb.Member {
238
+ ID : 1 ,
239
+ Name : "etcd-1" ,
240
+ ClientURLs : []string {"https://10.0.0.1:2379" },
241
+ PeerURLs : []string {"https://10.0.0.1:2380" },
242
+ },
243
+ memberList : []* etcdserverpb.Member {
244
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
245
+ {ID : 2 , Name : "etcd-2" , ClientURLs : []string {"https://10.0.0.2:2379" }, PeerURLs : []string {"https://10.0.0.2:2380" }},
246
+ },
247
+ moveLeaderErrors : map [uint64 ]error {
248
+ 2 : errors .New ("move leader failed: connection timeout" ),
249
+ },
250
+ expectedSuccess : false ,
251
+ expectedError : "move leader failed: connection timeout" ,
252
+ },
253
+ {
254
+ name : "no follower member found - single member cluster" ,
255
+ leader : & etcdserverpb.Member {
256
+ ID : 1 ,
257
+ Name : "etcd-1" ,
258
+ ClientURLs : []string {"https://10.0.0.1:2379" },
259
+ PeerURLs : []string {"https://10.0.0.1:2380" },
260
+ },
261
+ memberList : []* etcdserverpb.Member {
262
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
263
+ },
264
+ expectedSuccess : false ,
265
+ expectedError : "no follower member found for the members: [ID:1 name:\" etcd-1\" peerURLs:\" https://10.0.0.1:2380\" clientURLs:\" https://10.0.0.1:2379\" ]" ,
266
+ },
267
+ {
268
+ name : "no follower member found - all members have same ID" ,
269
+ leader : & etcdserverpb.Member {
270
+ ID : 1 ,
271
+ Name : "etcd-1" ,
272
+ ClientURLs : []string {"https://10.0.0.1:2379" },
273
+ PeerURLs : []string {"https://10.0.0.1:2380" },
274
+ },
275
+ memberList : []* etcdserverpb.Member {
276
+ {ID : 1 , Name : "etcd-1" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
277
+ {ID : 1 , Name : "etcd-1-copy" , ClientURLs : []string {"https://10.0.0.1:2379" }, PeerURLs : []string {"https://10.0.0.1:2380" }},
278
+ },
279
+ expectedSuccess : false ,
280
+ expectedError : "no follower member found for the members: [ID:1 name:\" etcd-1\" peerURLs:\" https://10.0.0.1:2380\" clientURLs:\" https://10.0.0.1:2379\" ID:1 name:\" etcd-1-copy\" peerURLs:\" https://10.0.0.1:2380\" clientURLs:\" https://10.0.0.1:2379\" ]" ,
281
+ },
282
+ {
283
+ name : "empty member list" ,
284
+ leader : & etcdserverpb.Member {
285
+ ID : 1 ,
286
+ Name : "etcd-1" ,
287
+ ClientURLs : []string {"https://10.0.0.1:2379" },
288
+ PeerURLs : []string {"https://10.0.0.1:2380" },
289
+ },
290
+ memberList : []* etcdserverpb.Member {},
291
+ expectedSuccess : false ,
292
+ expectedError : "no follower member found for the members: []" ,
293
+ },
294
+ }
221
295
222
- leader , err := FindLeader ( ctx , mockClient , memberList )
223
- require . NoError ( t , err )
224
- require . NotNil ( t , leader )
225
- assert . Equal ( t , uint64 ( 1 ), leader . ID )
226
- })
296
+ for _ , tt := range tests {
297
+ t . Run ( tt . name , func ( t * testing. T ) {
298
+ mockClient := & mockLeaderMover {
299
+ moveLeaderErrors : tt . moveLeaderErrors ,
300
+ }
227
301
228
- t .Run ("member with zero ID" , func (t * testing.T ) {
229
- mockClient := & mockMemberStatusChecker {
230
- memberStatuses : map [uint64 ]* clientv3.StatusResponse {
231
- 0 : {Header : & etcdserverpb.ResponseHeader {MemberId : 0 }, Leader : 0 },
232
- },
233
- }
302
+ success , err := MoveLeaderToAnotherMember (ctx , mockClient , tt .leader , tt .memberList )
234
303
235
- memberList := []* etcdserverpb.Member {
236
- {ID : 0 , Name : "etcd-0" , ClientURLs : []string {"https://10.0.0.0:2379" }},
237
- }
304
+ if tt .expectedError != "" {
305
+ require .Error (t , err )
306
+ assert .Contains (t , err .Error (), tt .expectedError )
307
+ assert .False (t , success )
308
+ return
309
+ }
238
310
239
- leader , err := FindLeader (ctx , mockClient , memberList )
240
- require .NoError (t , err )
241
- require .NotNil (t , leader )
242
- assert .Equal (t , uint64 (0 ), leader .ID )
243
- })
311
+ require .NoError (t , err )
312
+ assert .Equal (t , tt .expectedSuccess , success )
313
+ })
314
+ }
244
315
}
0 commit comments