66import random
77from collections .abc import AsyncIterator , Awaitable , Callable
88from dataclasses import fields
9- from typing import cast , get_args
9+ from typing import Any , cast , get_args
1010
1111import botocore .exceptions
1212import pytest
@@ -592,10 +592,10 @@ async def test_launch_instances_insufficient_capacity_fallback(
592592 aws_ami_id : str ,
593593 mocker : MockerFixture ,
594594):
595- """Test that launch_instances falls back to next subnet when InsufficientInstanceCapacity occurs."""
596595 await _assert_no_instances_in_ec2 (ec2_client )
597596
598597 # Create additional valid subnets for testing
598+ subnet1_id = aws_subnet_id
599599 subnet2_id = await create_aws_subnet_id ()
600600
601601 # Create a config with multiple valid subnet IDs
@@ -606,20 +606,21 @@ async def test_launch_instances_insufficient_capacity_fallback(
606606 ami_id = aws_ami_id ,
607607 key_name = faker .pystr (),
608608 security_group_ids = [aws_security_group_id ],
609- subnet_ids = [aws_subnet_id , subnet2_id ],
609+ subnet_ids = [subnet1_id , subnet2_id ],
610610 iam_instance_profile = "" ,
611611 )
612612
613613 # Mock the EC2 client to simulate InsufficientInstanceCapacity on first subnet
614614 original_run_instances = simcore_ec2_api .client .run_instances
615615 call_count = 0
616616
617- async def mock_run_instances (* args , ** kwargs ):
617+ async def mock_run_instances (* args , ** kwargs ) -> Any :
618618 nonlocal call_count
619619 call_count += 1
620620 if call_count == 1 :
621+ assert kwargs ["NetworkInterfaces" ][0 ]["SubnetId" ] == subnet1_id
621622 # First call (first subnet) - simulate insufficient capacity
622- error_response = {
623+ error_response : dict [ str , Any ] = {
623624 "Error" : {
624625 "Code" : "InsufficientInstanceCapacity" ,
625626 "Message" : "Insufficient capacity." ,
@@ -633,17 +634,18 @@ async def mock_run_instances(*args, **kwargs):
633634 }
634635 raise botocore .exceptions .ClientError (error_response , "RunInstances" )
635636 # Second call (second subnet) - succeed normally
637+ assert kwargs ["NetworkInterfaces" ][0 ]["SubnetId" ] == subnet2_id
636638 return await original_run_instances (* args , ** kwargs )
637639
638640 # Apply the mock
639- with mocker .patch .object (
641+ mocker .patch .object (
640642 simcore_ec2_api .client , "run_instances" , side_effect = mock_run_instances
641- ):
642- instances = await simcore_ec2_api .launch_instances (
643- ec2_instance_config ,
644- min_number_of_instances = 1 ,
645- number_of_instances = 1 ,
646- )
643+ )
644+ instances = await simcore_ec2_api .launch_instances (
645+ ec2_instance_config ,
646+ min_number_of_instances = 1 ,
647+ number_of_instances = 1 ,
648+ )
647649
648650 # Verify that run_instances was called twice (once for each subnet)
649651 assert call_count == 2
@@ -682,11 +684,12 @@ async def test_launch_instances_all_subnets_insufficient_capacity_raises_error(
682684 aws_ami_id : str ,
683685 mocker : MockerFixture ,
684686):
685- """Test that launch_instances raises EC2InsufficientCapacityError when all subnets have insufficient capacity."""
686687 await _assert_no_instances_in_ec2 (ec2_client )
687688
688689 # Create additional valid subnets for testing
690+ subnet1_id = aws_subnet_id
689691 subnet2_id = await create_aws_subnet_id ()
692+ subnet3_id = await create_aws_subnet_id ()
690693
691694 # Create a config with multiple valid subnet IDs
692695 ec2_instance_config = EC2InstanceConfig (
@@ -696,14 +699,14 @@ async def test_launch_instances_all_subnets_insufficient_capacity_raises_error(
696699 ami_id = aws_ami_id ,
697700 key_name = faker .pystr (),
698701 security_group_ids = [aws_security_group_id ],
699- subnet_ids = [aws_subnet_id , subnet2_id ],
702+ subnet_ids = [subnet1_id , subnet2_id , subnet3_id ],
700703 iam_instance_profile = "" ,
701704 )
702705
703706 # Mock the EC2 client to simulate InsufficientInstanceCapacity on ALL subnets
704707 call_count = 0
705708
706- async def mock_run_instances (* args , ** kwargs ):
709+ async def mock_run_instances (* args , ** kwargs ) -> Any :
707710 nonlocal call_count
708711 call_count += 1
709712 # Always simulate insufficient capacity
@@ -722,20 +725,18 @@ async def mock_run_instances(*args, **kwargs):
722725 raise botocore .exceptions .ClientError (error_response , "RunInstances" )
723726
724727 # Apply the mock and expect EC2InsufficientCapacityError
725- with (
726- mocker .patch .object (
727- simcore_ec2_api .client , "run_instances" , side_effect = mock_run_instances
728- ),
729- pytest .raises (EC2InsufficientCapacityError ) as exc_info ,
730- ):
728+ mocker .patch .object (
729+ simcore_ec2_api .client , "run_instances" , side_effect = mock_run_instances
730+ )
731+ with pytest .raises (EC2InsufficientCapacityError ) as exc_info :
731732 await simcore_ec2_api .launch_instances (
732733 ec2_instance_config ,
733734 min_number_of_instances = 1 ,
734735 number_of_instances = 1 ,
735736 )
736737
737738 # Verify that run_instances was called for both subnets
738- assert call_count == 2
739+ assert call_count == 3
739740
740741 # Verify the error contains the expected information
741742 assert hasattr (exc_info .value , "instance_type" )
@@ -785,72 +786,74 @@ async def mock_run_instances(*args, **kwargs):
785786 if call_count == 1 :
786787 # First call: return only 2 instances when 3 were requested
787788 # Simulate that the subnet has capacity for only 2 machines
789+ required_instances = kwargs ["MaxCount" ]
788790 kwargs_copy = kwargs .copy ()
789- kwargs_copy ["MinCount" ] = 2
790- kwargs_copy ["MaxCount" ] = 2
791+ kwargs_copy ["MinCount" ] = required_instances - 1
792+ kwargs_copy ["MaxCount" ] = required_instances - 1
791793 return await original_run_instances (* args , ** kwargs_copy )
792- else :
793- # Second call: simulate insufficient capacity (subnet is full)
794- error_response = {
795- "Error" : {
796- "Code" : "InsufficientInstanceCapacity" ,
797- "Message" : "Insufficient capacity." ,
798- },
799- "ResponseMetadata" : {
800- "RequestId" : "12345678-1234-1234-1234-123456789012" ,
801- "HTTPStatusCode" : 400 ,
802- "HTTPHeaders" : {},
803- "RetryAttempts" : 0 ,
804- },
805- }
806- raise botocore .exceptions .ClientError (error_response , "RunInstances" )
794+
795+ # Second call: simulate insufficient capacity (subnet is full)
796+ error_response = {
797+ "Error" : {
798+ "Code" : "InsufficientInstanceCapacity" ,
799+ "Message" : "Insufficient capacity." ,
800+ },
801+ "ResponseMetadata" : {
802+ "RequestId" : "12345678-1234-1234-1234-123456789012" ,
803+ "HTTPStatusCode" : 400 ,
804+ "HTTPHeaders" : {},
805+ "RetryAttempts" : 0 ,
806+ },
807+ }
808+ raise botocore .exceptions .ClientError (error_response , "RunInstances" )
807809
808810 # Apply the mock for the first call
809- with mocker .patch .object (
811+ mocker .patch .object (
810812 simcore_ec2_api .client , "run_instances" , side_effect = mock_run_instances
811- ):
812- # First call: ask for 3 instances (min 1) -> should get 2, no error
813- instances = await simcore_ec2_api .launch_instances (
813+ )
814+ # First call: ask for 3 instances (min 1) -> should get 2, no error
815+ instances = await simcore_ec2_api .launch_instances (
816+ ec2_instance_config ,
817+ min_number_of_instances = 1 ,
818+ number_of_instances = 3 ,
819+ )
820+
821+ # Verify we got 2 instances (partial capacity)
822+ assert len (instances ) == 2
823+ assert call_count == 1
824+
825+ # Verify instances were created
826+ await _assert_instances_in_ec2 (
827+ ec2_client ,
828+ expected_num_reservations = 1 ,
829+ expected_num_instances = 2 ,
830+ expected_instance_type = ec2_instance_config .type ,
831+ expected_tags = ec2_instance_config .tags ,
832+ expected_state = "running" ,
833+ )
834+
835+ # Second call: ask for 3 instances (min 1) -> should raise EC2InsufficientCapacityError
836+ with pytest .raises (EC2InsufficientCapacityError ) as exc_info :
837+ await simcore_ec2_api .launch_instances (
814838 ec2_instance_config ,
815839 min_number_of_instances = 1 ,
816840 number_of_instances = 3 ,
817841 )
818842
819- # Verify we got 2 instances (partial capacity)
820- assert len (instances ) == 2
821- assert call_count == 1
822-
823- # Verify instances were created
824- await _assert_instances_in_ec2 (
825- ec2_client ,
826- expected_num_reservations = 1 ,
827- expected_num_instances = 2 ,
828- expected_instance_type = ec2_instance_config .type ,
829- expected_tags = ec2_instance_config .tags ,
830- expected_state = "running" ,
831- )
843+ # Verify that run_instances was called twice total
844+ assert call_count == 2
832845
833- # Second call: ask for 3 instances (min 1) -> should raise EC2InsufficientCapacityError
834- with pytest .raises (EC2InsufficientCapacityError ) as exc_info :
835- await simcore_ec2_api .launch_instances (
836- ec2_instance_config ,
837- min_number_of_instances = 1 ,
838- number_of_instances = 3 ,
839- )
846+ # Verify the error contains the expected information
847+ assert hasattr (exc_info .value , "instance_type" )
848+ assert exc_info .value .instance_type == fake_ec2_instance_type .name # type: ignore
849+ assert exc_info .value .subnet_id == aws_subnet_id # type: ignore
840850
841- # Verify that run_instances was called twice total
842- assert call_count == 2
843-
844- # Verify the error contains the expected information
845- assert hasattr (exc_info .value , "instance_type" )
846- assert exc_info .value .instance_type == fake_ec2_instance_type .name
847-
848- # Verify still only 2 instances exist (no new ones were created)
849- await _assert_instances_in_ec2 (
850- ec2_client ,
851- expected_num_reservations = 1 ,
852- expected_num_instances = 2 ,
853- expected_instance_type = ec2_instance_config .type ,
854- expected_tags = ec2_instance_config .tags ,
855- expected_state = "running" ,
856- )
851+ # Verify still only 2 instances exist (no new ones were created)
852+ await _assert_instances_in_ec2 (
853+ ec2_client ,
854+ expected_num_reservations = 1 ,
855+ expected_num_instances = 2 ,
856+ expected_instance_type = ec2_instance_config .type ,
857+ expected_tags = ec2_instance_config .tags ,
858+ expected_state = "running" ,
859+ )
0 commit comments