@@ -207,22 +207,20 @@ def is_node_ejected():
207207 f"ejected node: { ejected_otp_node } "
208208
209209
210- def assert_per_node_storage_mode_in_memcached (
211- node , bucket_name , expected_storage_mode
212- ):
213- storage_mode = (
214- testlib .diag_eval (
215- node ,
216- f'ns_memcached:get_config_stats("{ bucket_name } ", <<"ep_backend">>).' ,
217- )
218- .content .decode ("ascii" )
219- .strip ('<<"' )
220- .strip ('">>' )
221- )
210+ def wait_for_bucket_online_on_all_nodes (cluster , bucket_name ):
211+ """Wait for bucket to be online on all nodes in the cluster"""
222212
223- if expected_storage_mode == "couchstore" :
224- expected_storage_mode = "couchdb"
225- assert storage_mode == expected_storage_mode
213+ def is_bucket_online_on_all_nodes ():
214+ r = get_bucket (cluster , bucket_name )
215+ return all ([node ["status" ] == "healthy" for node in r ["nodes" ]])
216+
217+ testlib .poll_for_condition (
218+ is_bucket_online_on_all_nodes ,
219+ sleep_time = 0.5 ,
220+ attempts = 20 ,
221+ timeout = 60 ,
222+ msg = f"waiting for bucket { bucket_name } to be online on all nodes" ,
223+ )
226224
227225
228226def assert_per_node_eviction_policy_in_memcached (
@@ -251,6 +249,24 @@ def assert_per_node_eviction_policy_in_memcached(
251249 assert eviction_policy == expected_eviction_policy
252250
253251
252+ def assert_per_node_storage_mode_in_memcached (
253+ node , bucket_name , expected_storage_mode
254+ ):
255+ storage_mode = (
256+ testlib .diag_eval (
257+ node ,
258+ f'ns_memcached:get_config_stats("{ bucket_name } ", <<"ep_backend">>).' ,
259+ )
260+ .content .decode ("ascii" )
261+ .strip ('<<"' )
262+ .strip ('">>' )
263+ )
264+
265+ if expected_storage_mode == "couchstore" :
266+ expected_storage_mode = "couchdb"
267+ assert storage_mode == expected_storage_mode
268+
269+
254270def _find_available_new_node (cluster , old_nodes ):
255271 """Find an available new node for swap rebalance"""
256272 for candidate_node in cluster ._nodes :
@@ -806,17 +822,7 @@ def perform_delta_recovery_mid_storage_and_eviction_policy_migration_test(
806822 new_eviction_policy = "fullEviction" ,
807823 )
808824
809- def is_bucket_online_on_all_nodes ():
810- r = get_bucket (self .cluster , bucket_name )
811- return all ([node ["status" ] == "healthy" for node in r ["nodes" ]])
812-
813- testlib .poll_for_condition (
814- is_bucket_online_on_all_nodes ,
815- sleep_time = 0.5 ,
816- attempts = 20 ,
817- timeout = 60 ,
818- msg = "poll bucket is online on all nodes" ,
819- )
825+ wait_for_bucket_online_on_all_nodes (self .cluster , bucket_name )
820826
821827 # Failover a node and delta-recover it - the bucket should still have
822828 # per-node storage_mode/eviction_policy override props. Storage mode
@@ -844,3 +850,199 @@ def is_bucket_online_on_all_nodes():
844850 assert_per_node_eviction_policy_in_memcached (
845851 failover_node , bucket_name , "valueOnly"
846852 )
853+
854+ def eviction_policy_only_via_rebalance_test (self ):
855+ """Test eviction policy change on magma bucket with --no-restart"""
856+ bucket_name = "bucket-magma-eviction-test"
857+
858+ # Create bucket with magma and fullEviction eviction policy
859+ create_bucket (self .cluster , bucket_name , "magma" , 1024 , "fullEviction" )
860+
861+ # Update bucket with eviction policy change + --no-restart
862+ update_data = {
863+ "name" : bucket_name ,
864+ "evictionPolicy" : "valueOnly" ,
865+ "noRestart" : "true" ,
866+ }
867+
868+ self .cluster .update_bucket (update_data )
869+
870+ # Verify eviction policy overrides are added
871+ assert_per_node_eviction_policy_keys_added (
872+ self .cluster , bucket_name , "fullEviction"
873+ )
874+
875+ # Wait for bucket to be online on all nodes before checking memcached
876+ wait_for_bucket_online_on_all_nodes (self .cluster , bucket_name )
877+
878+ for node in self .cluster .connected_nodes :
879+ assert_per_node_eviction_policy_in_memcached (
880+ node ,
881+ bucket_name ,
882+ "fullEviction" ,
883+ )
884+
885+ # Perform single node swap rebalance to apply the changes
886+ perform_single_node_swap_rebalance (
887+ self .cluster ,
888+ bucket_name ,
889+ node_index = 0 ,
890+ expected_eviction_policy = "valueOnly" ,
891+ )
892+
893+ self .cluster .delete_bucket (bucket_name )
894+
895+ def eviction_policy_only_via_full_recovery_test (self ):
896+ """Test eviction policy change with --no-restart, then full recovery"""
897+ bucket_name = "bucket-eviction-fullrec-test"
898+
899+ # Create bucket with couchstore + valueOnly
900+ create_bucket (
901+ self .cluster , bucket_name , "couchstore" , 1024 , "valueOnly"
902+ )
903+
904+ # Change eviction policy with --no-restart
905+ update_data = {
906+ "name" : bucket_name ,
907+ "evictionPolicy" : "fullEviction" ,
908+ "noRestart" : "true" ,
909+ }
910+
911+ self .cluster .update_bucket (update_data )
912+
913+ # Verify eviction policy overrides are added
914+ assert_per_node_eviction_policy_keys_added (
915+ self .cluster , bucket_name , "valueOnly"
916+ )
917+
918+ wait_for_bucket_online_on_all_nodes (self .cluster , bucket_name )
919+
920+ for node in self .cluster .connected_nodes :
921+ assert_per_node_eviction_policy_in_memcached (
922+ node ,
923+ bucket_name ,
924+ "valueOnly" , # Should still be original value
925+ )
926+
927+ perform_failover_full_recovery (
928+ self .cluster , bucket_name , expected_eviction_policy = "fullEviction"
929+ )
930+
931+ self .cluster .delete_bucket (bucket_name )
932+
933+ def eviction_policy_only_via_delta_recovery_test (self ):
934+ """Test eviction policy change with --no-restart, then delta recovery"""
935+ bucket_name = "bucket-eviction-deltarec-test"
936+
937+ # Create bucket with couchstore + fullEviction
938+ create_bucket (
939+ self .cluster , bucket_name , "couchstore" , 1024 , "fullEviction"
940+ )
941+
942+ # Change eviction policy with --no-restart
943+ update_data = {
944+ "name" : bucket_name ,
945+ "evictionPolicy" : "valueOnly" ,
946+ "noRestart" : "true" ,
947+ }
948+
949+ self .cluster .update_bucket (update_data )
950+
951+ # Verify eviction policy overrides are added
952+ assert_per_node_eviction_policy_keys_added (
953+ self .cluster , bucket_name , "fullEviction"
954+ )
955+
956+ wait_for_bucket_online_on_all_nodes (self .cluster , bucket_name )
957+
958+ for node in self .cluster .connected_nodes :
959+ assert_per_node_eviction_policy_in_memcached (
960+ node ,
961+ bucket_name ,
962+ "fullEviction" , # Should still be original value
963+ )
964+
965+ # Perform delta recovery on each node
966+ for node in self .cluster .connected_nodes :
967+ self .cluster .failover_node (node , graceful = False )
968+ self .cluster .recover_node (
969+ node , recovery_type = "delta" , do_rebalance = True
970+ )
971+
972+ # Verify overrides are removed and memcached has new eviction policy
973+ assert_per_node_eviction_policy_not_present (
974+ self .cluster , node , bucket_name
975+ )
976+ assert_per_node_eviction_policy_in_memcached (
977+ node , bucket_name , "valueOnly"
978+ )
979+
980+ self .cluster .delete_bucket (bucket_name )
981+
982+ def eviction_policy_only_interleaved_test (self ):
983+ """Test eviction policy change with --no-restart, then immediate change without --no-restart"""
984+ bucket_name = "bucket-eviction-immediate-test"
985+
986+ # Create bucket with magma + valueOnly
987+ create_bucket (self .cluster , bucket_name , "magma" , 1024 , "valueOnly" )
988+
989+ # Change eviction policy with --no-restart
990+ update_data_with_no_restart = {
991+ "name" : bucket_name ,
992+ "evictionPolicy" : "fullEviction" ,
993+ "noRestart" : "true" ,
994+ }
995+
996+ self .cluster .update_bucket (update_data_with_no_restart )
997+
998+ # Verify eviction policy overrides are added
999+ assert_per_node_eviction_policy_keys_added (
1000+ self .cluster , bucket_name , "valueOnly"
1001+ )
1002+
1003+ # Wait for bucket to be online on all nodes before checking memcached
1004+ wait_for_bucket_online_on_all_nodes (self .cluster , bucket_name )
1005+
1006+ for node in self .cluster .connected_nodes :
1007+ assert_per_node_eviction_policy_in_memcached (
1008+ node ,
1009+ bucket_name ,
1010+ "valueOnly" , # Should still be original value
1011+ )
1012+
1013+ # Now change eviction policy WITHOUT --no-restart
1014+ update_data_without_no_restart = {
1015+ "name" : bucket_name ,
1016+ "evictionPolicy" : "fullEviction" ,
1017+ }
1018+
1019+ self .cluster .update_bucket (update_data_without_no_restart )
1020+
1021+ # Verify overrides are gone and memcached immediately reflects the change
1022+ for node in self .cluster .connected_nodes :
1023+ assert_per_node_eviction_policy_not_present (
1024+ self .cluster , node , bucket_name
1025+ )
1026+
1027+ # Poll for eviction policy change in memcached (since bucket restart is async)
1028+ def check_eviction_policy_changed ():
1029+ eviction_policy = (
1030+ testlib .diag_eval (
1031+ self .cluster ,
1032+ f'ns_memcached:get_config_stats("{ bucket_name } ", <<"ep_item_eviction_policy">>).' ,
1033+ )
1034+ .content .decode ("ascii" )
1035+ .strip ('<<"' )
1036+ .strip ('">>' )
1037+ )
1038+ return eviction_policy == "full_eviction"
1039+
1040+ testlib .poll_for_condition (
1041+ check_eviction_policy_changed ,
1042+ sleep_time = 0.5 ,
1043+ attempts = 20 , # 10 seconds total with 1-second check interval
1044+ timeout = 30 ,
1045+ msg = f"waiting for eviction policy to change to full_eviction on { node .hostname ()} " ,
1046+ )
1047+
1048+ self .cluster .delete_bucket (bucket_name )
0 commit comments