3
3
from argparse import RawTextHelpFormatter
4
4
import json
5
5
import math
6
+ from typing import Iterable
6
7
import redis
7
8
import subprocess
8
9
import time
@@ -46,6 +47,7 @@ def start_node(node, dragonfly_bin, threads):
46
47
"--dbfilename=" ,
47
48
f"--logtostderr" ,
48
49
"--proactor_affinity_mode=off" ,
50
+ "--omit_basic_usage" ,
49
51
],
50
52
stderr = f ,
51
53
)
@@ -71,6 +73,55 @@ def send_command(node, command, print_errors=True):
71
73
return Exception ()
72
74
73
75
76
+ class SlotRange :
77
+ def __init__ (self , start , end ):
78
+ assert start <= end
79
+ self .start = start
80
+ self .end = end
81
+
82
+ def to_dict (self ):
83
+ return {"start" : self .start , "end" : self .end }
84
+
85
+ @classmethod
86
+ def from_dict (cls , d ):
87
+ return cls (d ["start" ], d ["end" ])
88
+
89
+ def __repr__ (self ):
90
+ return f"({ self .start } -{ self .end } )"
91
+
92
+ def merge (self , other ):
93
+ if self .end + 1 == other .start :
94
+ self .end = other .end
95
+ return True
96
+ elif other .end + 1 == self .start :
97
+ self .start = other .start
98
+ return True
99
+ return False
100
+
101
+ def contains (self , slot_id ):
102
+ return self .start <= slot_id <= self .end
103
+
104
+ def split (self , slot_id ):
105
+ assert self .contains (slot_id )
106
+
107
+ if self .start < self .end :
108
+ if slot_id == self .start :
109
+ return None , SlotRange (self .start + 1 , self .end )
110
+ elif slot_id == self .end :
111
+ return SlotRange (self .start , self .end - 1 ), None
112
+ elif self .start < slot_id < self .end :
113
+ return SlotRange (self .start , slot_id - 1 ), SlotRange (slot_id + 1 , self .end )
114
+ return None , None
115
+
116
+
117
+ # Custom JSON encoder to handle SlotRange objects
118
+ class ClusterConfigEncoder (json .JSONEncoder ):
119
+ def default (self , obj ):
120
+ if isinstance (obj , SlotRange ):
121
+ return obj .to_dict ()
122
+ return super ().default (obj )
123
+
124
+
74
125
def build_node (node ):
75
126
return {"id" : node .id , "ip" : node .host , "port" : node .port }
76
127
@@ -81,15 +132,16 @@ def build_config_from_list(masters):
81
132
82
133
config = []
83
134
for i , master in enumerate (masters ):
135
+ slot_range = SlotRange (i * slots_per_node , (i + 1 ) * slots_per_node - 1 )
84
136
c = {
85
- "slot_ranges" : [{ "start" : i * slots_per_node , "end" : ( i + 1 ) * slots_per_node - 1 } ],
137
+ "slot_ranges" : [slot_range ],
86
138
"master" : build_node (master .node ),
87
139
"replicas" : [build_node (replica ) for replica in master .replicas ],
88
140
}
89
-
90
141
config .append (c )
91
142
92
- config [- 1 ]["slot_ranges" ][- 1 ]["end" ] += total_slots % len (masters )
143
+ # Adjust the last slot range to include any remaining slots
144
+ config [- 1 ]["slot_ranges" ][- 1 ].end += total_slots % len (masters )
93
145
return config
94
146
95
147
@@ -106,7 +158,8 @@ def get_nodes_from_config(config):
106
158
107
159
def push_config (config ):
108
160
def push_to_node (node , config ):
109
- config_str = json .dumps (config , indent = 2 )
161
+ # Use the custom encoder to convert SlotRange objects during serialization
162
+ config_str = json .dumps (config , indent = 2 , cls = ClusterConfigEncoder )
110
163
response = send_command (node , ["dflycluster" , "config" , config_str ])
111
164
print (f"- Push to { node .port } : { response } " )
112
165
@@ -191,7 +244,7 @@ def build_node(node_list):
191
244
def build_slots (slot_list ):
192
245
slots = []
193
246
for i in range (0 , len (slot_list ), 2 ):
194
- slots .append ({ "start" : slot_list [i ], "end" : slot_list [i + 1 ]} )
247
+ slots .append (SlotRange ( slot_list [i ], slot_list [i + 1 ]) )
195
248
return slots
196
249
197
250
client = redis .Redis (decode_responses = True , host = args .target_host , port = args .target_port )
@@ -308,76 +361,51 @@ def move(args):
308
361
config = build_config_from_existing (args )
309
362
new_owner = find_master (config , args .target_host , args .target_port )
310
363
311
- def remove_slot (slot , from_range , from_shard ):
312
- if from_range ["start" ] == slot :
313
- from_range ["start" ] += 1
314
- if from_range ["start" ] > from_range ["end" ]:
315
- from_shard ["slot_ranges" ].remove (from_range )
316
- elif from_range ["end" ] == slot :
317
- from_range ["end" ] -= 1
318
- if from_range ["start" ] > from_range ["end" ]:
319
- from_shard ["slot_ranges" ].remove (from_range )
320
- else :
321
- assert (
322
- slot > from_range ["start" ] and slot < from_range ["end" ]
323
- ), f'{ slot } { from_range ["start" ]} { from_range ["end" ]} '
324
- from_shard ["slot_ranges" ].append ({"start" : slot + 1 , "end" : from_range ["end" ]})
325
- from_range ["end" ] = slot - 1
364
+ def remove_slot (slot , from_range : SlotRange , from_shard ):
365
+ left , right = from_range .split (slot )
366
+ if left :
367
+ from_shard ["slot_ranges" ].append (left )
368
+ if right :
369
+ from_shard ["slot_ranges" ].append (right )
370
+ from_shard ["slot_ranges" ].remove (from_range )
326
371
327
372
def add_slot (slot , to_shard ):
328
- for slot_range in to_shard ["slot_ranges" ]:
329
- if slot == slot_range ["start" ] - 1 :
330
- slot_range ["start" ] -= 1
331
- return
332
- if slot == slot_range ["end" ] + 1 :
333
- slot_range ["end" ] += 1
373
+ slot_range = SlotRange (slot , slot )
374
+ for existing_range in to_shard ["slot_ranges" ]:
375
+ if existing_range .merge (slot_range ):
334
376
return
335
- to_shard ["slot_ranges" ].append ({ "start" : slot , "end" : slot } )
377
+ to_shard ["slot_ranges" ].append (slot_range )
336
378
337
379
def find_slot (slot , config ):
338
380
for shard in config :
339
381
if shard == new_owner :
340
382
continue
341
383
for slot_range in shard ["slot_ranges" ]:
342
- if slot >= slot_range [ "start" ] and slot <= slot_range [ "end" ] :
384
+ if slot_range . contains ( slot ) :
343
385
return shard , slot_range
344
386
return None , None
345
387
346
388
def pack (slot_ranges ):
347
- new_range = []
348
- while True :
349
- changed = False
350
- new_range = []
351
- slot_ranges .sort (key = lambda x : x ["start" ])
352
- for i , slot_range in enumerate (slot_ranges ):
353
- added = False
354
- for j in range (i ):
355
- prev_slot_range = slot_ranges [j ]
356
- if prev_slot_range ["end" ] + 1 == slot_range ["start" ]:
357
- prev_slot_range ["end" ] = slot_range ["end" ]
358
- changed = True
359
- added = True
360
- break
361
- if not added :
362
- new_range .append (slot_range )
363
- slot_ranges = new_range
364
- if not changed :
365
- break
366
- return new_range
389
+ slot_objects = sorted (slot_ranges , key = lambda x : x .start )
390
+ packed = []
391
+ for slot_range in slot_objects :
392
+ if packed and packed [- 1 ].merge (slot_range ):
393
+ continue
394
+ packed .append (slot_range )
395
+ return packed
367
396
368
397
for slot in range (args .slot_start , args .slot_end + 1 ):
369
398
shard , slot_range = find_slot (slot , config )
370
- if shard == None :
371
- continue
372
- if shard == new_owner :
399
+ if shard == None or shard == new_owner :
373
400
continue
374
401
remove_slot (slot , slot_range , shard )
375
402
add_slot (slot , new_owner )
376
403
377
404
for shard in config :
378
405
shard ["slot_ranges" ] = pack (shard ["slot_ranges" ])
379
406
380
- print (f"Pushing new config:\n { json .dumps (config , indent = 2 )} \n " )
407
+ # Use the custom encoder for printing the JSON
408
+ print (f"Pushing new config:\n { json .dumps (config , indent = 2 , cls = ClusterConfigEncoder )} \n " )
381
409
push_config (config )
382
410
383
411
@@ -390,9 +418,9 @@ def migrate(args):
390
418
# Find source node
391
419
source = None
392
420
for node in config :
393
- slots = node ["slot_ranges" ]
421
+ slots : Iterable [ SlotRange ] = node ["slot_ranges" ]
394
422
for slot in slots :
395
- if slot [ " start" ] <= args .slot_start and slot [ " end" ] >= args .slot_end :
423
+ if slot . start <= args .slot_start and slot . end >= args .slot_end :
396
424
source = node
397
425
break
398
426
if source == None :
@@ -428,7 +456,7 @@ def migrate(args):
428
456
429
457
def print_config (args ):
430
458
config = build_config_from_existing (args )
431
- print (json .dumps (config , indent = 2 ))
459
+ print (json .dumps (config , indent = 2 , cls = ClusterConfigEncoder ))
432
460
433
461
434
462
def shutdown (args ):
0 commit comments