@@ -209,6 +209,84 @@ def assert_autoscaling_action(
209209 )
210210
211211
212+ @pytest .fixture
213+ def autoscaler_max_upscaling_delta_setup ():
214+ resource_manager = MagicMock (
215+ spec = ResourceManager , get_budget = MagicMock (return_value = None )
216+ )
217+
218+ actor_pool = MagicMock (
219+ spec = _ActorPool ,
220+ min_size = MagicMock (return_value = 5 ),
221+ max_size = MagicMock (return_value = 20 ),
222+ current_size = MagicMock (return_value = 10 ),
223+ get_current_size = MagicMock (return_value = 10 ),
224+ num_pending_actors = MagicMock (return_value = 0 ),
225+ get_pool_util = MagicMock (return_value = 2.0 ),
226+ )
227+
228+ op = MagicMock (
229+ spec = InternalQueueOperatorMixin ,
230+ completed = MagicMock (return_value = False ),
231+ _inputs_complete = False ,
232+ )
233+ op_state = MagicMock (
234+ spec = OpState ,
235+ total_enqueued_input_blocks = MagicMock (return_value = 1 ),
236+ )
237+ op_state ._scheduling_status = MagicMock (under_resource_limits = True )
238+ return resource_manager , actor_pool , op , op_state
239+
240+
241+ def test_actor_pool_scaling_respects_small_max_upscaling_delta (
242+ autoscaler_max_upscaling_delta_setup ,
243+ ):
244+ resource_manager , actor_pool , op , op_state = autoscaler_max_upscaling_delta_setup
245+ autoscaler = DefaultActorAutoscaler (
246+ topology = MagicMock (),
247+ resource_manager = resource_manager ,
248+ config = AutoscalingConfig (
249+ actor_pool_util_upscaling_threshold = 1.0 ,
250+ actor_pool_util_downscaling_threshold = 0.5 ,
251+ actor_pool_max_upscaling_delta = 3 ,
252+ ),
253+ )
254+ request = autoscaler ._derive_target_scaling_config (
255+ actor_pool = actor_pool ,
256+ op = op ,
257+ op_state = op_state ,
258+ )
259+ # With current_size=10, util=2.0, threshold=1.0:
260+ # plan_delta = ceil(10 * (2.0/1.0 - 1)) = ceil(10) = 10
261+ # However, delta is limited by max_upscaling_delta=3, so delta = min(10, 3) = 3
262+ assert request .delta == 3
263+
264+
265+ def test_actor_pool_scaling_respects_large_max_upscaling_delta (
266+ autoscaler_max_upscaling_delta_setup ,
267+ ):
268+ resource_manager , actor_pool , op , op_state = autoscaler_max_upscaling_delta_setup
269+ autoscaler = DefaultActorAutoscaler (
270+ topology = MagicMock (),
271+ resource_manager = resource_manager ,
272+ config = AutoscalingConfig (
273+ actor_pool_util_upscaling_threshold = 1.0 ,
274+ actor_pool_util_downscaling_threshold = 0.5 ,
275+ actor_pool_max_upscaling_delta = 100 ,
276+ ),
277+ )
278+ request = autoscaler ._derive_target_scaling_config (
279+ actor_pool = actor_pool ,
280+ op = op ,
281+ op_state = op_state ,
282+ )
283+ # With current_size=10, util=2.0, threshold=1.0:
284+ # plan_delta = ceil(10 * (2.0/1.0 - 1)) = ceil(10) = 10
285+ # max_upscaling_delta=100 is large enough, but delta is limited by max_size:
286+ # max_size(20) - current_size(10) = 10, so delta = min(10, 100, 10) = 10
287+ assert request .delta == 10
288+
289+
212290def test_cluster_scaling ():
213291 """Test `_try_scale_up_cluster` in `DefaultAutoscaler`"""
214292 op1 = MagicMock (
@@ -417,6 +495,101 @@ def __call__(self, row):
417495 assert expected_message not in wanr_log_args_str
418496
419497
498+ @pytest .fixture
499+ def autoscaler_config_mocks ():
500+ resource_manager = MagicMock (spec = ResourceManager )
501+ topology = MagicMock ()
502+ topology .items = MagicMock (return_value = [])
503+ return resource_manager , topology
504+
505+
506+ def test_autoscaling_config_validation_zero_delta (autoscaler_config_mocks ):
507+ resource_manager , topology = autoscaler_config_mocks
508+
509+ with pytest .raises (
510+ ValueError , match = "actor_pool_max_upscaling_delta must be positive"
511+ ):
512+ DefaultActorAutoscaler (
513+ topology = topology ,
514+ resource_manager = resource_manager ,
515+ config = AutoscalingConfig (
516+ actor_pool_util_upscaling_threshold = 1.0 ,
517+ actor_pool_util_downscaling_threshold = 0.5 ,
518+ actor_pool_max_upscaling_delta = 0 ,
519+ ),
520+ )
521+
522+
523+ def test_autoscaling_config_validation_negative_delta (autoscaler_config_mocks ):
524+ resource_manager , topology = autoscaler_config_mocks
525+
526+ with pytest .raises (
527+ ValueError , match = "actor_pool_max_upscaling_delta must be positive"
528+ ):
529+ DefaultActorAutoscaler (
530+ topology = topology ,
531+ resource_manager = resource_manager ,
532+ config = AutoscalingConfig (
533+ actor_pool_util_upscaling_threshold = 1.0 ,
534+ actor_pool_util_downscaling_threshold = 0.5 ,
535+ actor_pool_max_upscaling_delta = - 1 ,
536+ ),
537+ )
538+
539+
540+ def test_autoscaling_config_validation_positive_delta (autoscaler_config_mocks ):
541+ resource_manager , topology = autoscaler_config_mocks
542+
543+ autoscaler = DefaultActorAutoscaler (
544+ topology = topology ,
545+ resource_manager = resource_manager ,
546+ config = AutoscalingConfig (
547+ actor_pool_util_upscaling_threshold = 1.0 ,
548+ actor_pool_util_downscaling_threshold = 0.5 ,
549+ actor_pool_max_upscaling_delta = 5 ,
550+ ),
551+ )
552+ assert autoscaler ._actor_pool_max_upscaling_delta == 5
553+
554+
555+ def test_autoscaling_config_validation_zero_upscaling_threshold (
556+ autoscaler_config_mocks ,
557+ ):
558+ resource_manager , topology = autoscaler_config_mocks
559+
560+ with pytest .raises (
561+ ValueError , match = "actor_pool_util_upscaling_threshold must be positive"
562+ ):
563+ DefaultActorAutoscaler (
564+ topology = topology ,
565+ resource_manager = resource_manager ,
566+ config = AutoscalingConfig (
567+ actor_pool_util_upscaling_threshold = 0 ,
568+ actor_pool_util_downscaling_threshold = 0.5 ,
569+ actor_pool_max_upscaling_delta = 5 ,
570+ ),
571+ )
572+
573+
574+ def test_autoscaling_config_validation_negative_upscaling_threshold (
575+ autoscaler_config_mocks ,
576+ ):
577+ resource_manager , topology = autoscaler_config_mocks
578+
579+ with pytest .raises (
580+ ValueError , match = "actor_pool_util_upscaling_threshold must be positive"
581+ ):
582+ DefaultActorAutoscaler (
583+ topology = topology ,
584+ resource_manager = resource_manager ,
585+ config = AutoscalingConfig (
586+ actor_pool_util_upscaling_threshold = - 1.0 ,
587+ actor_pool_util_downscaling_threshold = 0.5 ,
588+ actor_pool_max_upscaling_delta = 5 ,
589+ ),
590+ )
591+
592+
420593if __name__ == "__main__" :
421594 import sys
422595
0 commit comments