@@ -1188,6 +1188,20 @@ async def test_cluster_scaling_up_starts_multiple_instances(
11881188 mock_rabbitmq_post_message .reset_mock ()
11891189
11901190
1191+ @pytest .fixture
1192+ async def mocked_associate_ec2_instances_with_nodes (mocker : MockerFixture ) -> mock .Mock :
1193+ async def _ (
1194+ nodes : list [Node ], ec2_instances : list [EC2InstanceData ]
1195+ ) -> tuple [list [AssociatedInstance ], list [EC2InstanceData ]]:
1196+ return [], ec2_instances
1197+
1198+ return mocker .patch (
1199+ "simcore_service_autoscaling.modules.auto_scaling_core.associate_ec2_instances_with_nodes" ,
1200+ autospec = True ,
1201+ side_effect = _ ,
1202+ )
1203+
1204+
11911205@pytest .mark .parametrize (
11921206 "with_docker_join_drained" , ["with_AUTOSCALING_DOCKER_JOIN_DRAINED" ], indirect = True
11931207)
@@ -1240,6 +1254,11 @@ async def test_cluster_adapts_machines_on_the_fly(
12401254 async_docker_client : aiodocker .Docker ,
12411255 scale_up_params1 : _ScaleUpParams ,
12421256 scale_up_params2 : _ScaleUpParams ,
1257+ mocked_associate_ec2_instances_with_nodes : mock .Mock ,
1258+ create_fake_node : Callable [..., Node ],
1259+ mock_docker_tag_node : mock .Mock ,
1260+ mock_compute_node_used_resources : mock .Mock ,
1261+ spied_cluster_analysis : MockType ,
12431262):
12441263 # pre-requisites
12451264 assert app_settings .AUTOSCALING_EC2_INSTANCES
@@ -1276,20 +1295,73 @@ async def test_cluster_adapts_machines_on_the_fly(
12761295 for _ in range (scale_up_params1 .num_services )
12771296 )
12781297 )
1279- for _ in range (3 ):
1280- # it will only scale once and do nothing else
1281- await auto_scale_cluster (
1282- app = initialized_app , auto_scaling_mode = DynamicAutoscaling ()
1283- )
1284- await assert_autoscaled_dynamic_ec2_instances (
1285- ec2_client ,
1286- expected_num_reservations = 1 ,
1287- expected_num_instances = scale_up_params1 .expected_num_instances ,
1288- expected_instance_type = scale_up_params1 .expected_instance_type ,
1289- expected_instance_state = "running" ,
1290- expected_additional_tag_keys = list (ec2_instance_custom_tags ),
1291- instance_filters = instance_type_filters ,
1292- )
1298+
1299+ # it will only scale once and do nothing else
1300+ await auto_scale_cluster (
1301+ app = initialized_app , auto_scaling_mode = DynamicAutoscaling ()
1302+ )
1303+ await assert_autoscaled_dynamic_ec2_instances (
1304+ ec2_client ,
1305+ expected_num_reservations = 1 ,
1306+ expected_num_instances = scale_up_params1 .expected_num_instances ,
1307+ expected_instance_type = scale_up_params1 .expected_instance_type ,
1308+ expected_instance_state = "running" ,
1309+ expected_additional_tag_keys = list (ec2_instance_custom_tags ),
1310+ instance_filters = instance_type_filters ,
1311+ )
1312+ _assert_cluster_state (
1313+ spied_cluster_analysis ,
1314+ expected_calls = 1 ,
1315+ expected_num_machines = 0 ,
1316+ )
1317+ mocked_associate_ec2_instances_with_nodes .assert_called_once_with ([], [])
1318+ mocked_associate_ec2_instances_with_nodes .reset_mock ()
1319+
1320+ fake_node_to_instance_map = {}
1321+
1322+ async def _fake_node_creator (
1323+ nodes : list [Node ], ec2_instances : list [EC2InstanceData ]
1324+ ) -> tuple [list [AssociatedInstance ], list [EC2InstanceData ]]:
1325+ def _create_fake_node_with_labels (instance : EC2InstanceData ) -> Node :
1326+ if instance not in fake_node_to_instance_map :
1327+ fake_node = create_fake_node ()
1328+ assert fake_node .spec
1329+ fake_node .spec .availability = Availability .active
1330+ assert fake_node .status
1331+ fake_node .status .state = NodeState .ready
1332+ assert fake_node .spec .labels
1333+ fake_node .spec .labels |= {
1334+ _OSPARC_SERVICES_READY_DATETIME_LABEL_KEY : arrow .utcnow ().isoformat (),
1335+ _OSPARC_SERVICE_READY_LABEL_KEY : "true" ,
1336+ }
1337+ fake_node_to_instance_map [instance ] = fake_node
1338+ return fake_node_to_instance_map [instance ]
1339+
1340+ associated_instances = [
1341+ AssociatedInstance (node = _create_fake_node_with_labels (i ), ec2_instance = i )
1342+ for i in ec2_instances
1343+ ]
1344+
1345+ return associated_instances , []
1346+
1347+ mocked_associate_ec2_instances_with_nodes .side_effect = _fake_node_creator
1348+
1349+ #
1350+ # 2. now the machines are associated
1351+ await auto_scale_cluster (
1352+ app = initialized_app , auto_scaling_mode = DynamicAutoscaling ()
1353+ )
1354+ _assert_cluster_state (
1355+ spied_cluster_analysis ,
1356+ expected_calls = 1 ,
1357+ expected_num_machines = app_settings .AUTOSCALING_EC2_INSTANCES .EC2_INSTANCES_MAX_INSTANCES ,
1358+ )
1359+ mocked_associate_ec2_instances_with_nodes .assert_called_once ()
1360+ mock_docker_tag_node .assert_called ()
1361+ assert (
1362+ mock_docker_tag_node .call_count
1363+ == app_settings .AUTOSCALING_EC2_INSTANCES .EC2_INSTANCES_MAX_INSTANCES
1364+ )
12931365
12941366 #
12951367 # 2. now we start the second batch of services requiring a different type of machines
@@ -1330,6 +1402,10 @@ async def test_cluster_adapts_machines_on_the_fly(
13301402 instance_filters = instance_type_filters ,
13311403 )
13321404
1405+ assert isinstance (spied_cluster_analysis .spy_return , Cluster )
1406+ assert spied_cluster_analysis .spy_return .active_nodes
1407+ assert not spied_cluster_analysis .spy_return .drained_nodes
1408+
13331409 # now we simulate that some of the services in the 1st batch have completed and that we are 1 below the max
13341410 # a machine should switch off and another type should be started
13351411 completed_services_to_stop = random .sample (
@@ -1350,6 +1426,10 @@ async def test_cluster_adapts_machines_on_the_fly(
13501426 await auto_scale_cluster (
13511427 app = initialized_app , auto_scaling_mode = DynamicAutoscaling ()
13521428 )
1429+
1430+ assert spied_cluster_analysis .spy_return .active_nodes
1431+ assert not spied_cluster_analysis .spy_return .drained_nodes
1432+
13531433 all_instances = await ec2_client .describe_instances ()
13541434 assert len (all_instances ["Reservations" ]) == 2 , "there should be 2 Reservations"
13551435 reservation1 = all_instances ["Reservations" ][0 ]
0 commit comments