@@ -731,3 +731,108 @@ async def mock_run_instances(*args, **kwargs):
731731
732732 # Verify no instances were created
733733 await _assert_no_instances_in_ec2 (ec2_client )
734+
735+
736+ async def test_launch_instances_partial_capacity_then_insufficient_capacity (
737+ simcore_ec2_api : SimcoreEC2API ,
738+ ec2_client : EC2Client ,
739+ fake_ec2_instance_type : EC2InstanceType ,
740+ faker : Faker ,
741+ aws_subnet_id : str ,
742+ aws_security_group_id : str ,
743+ aws_ami_id : str ,
744+ mocker : MockerFixture ,
745+ ):
746+ """Test that launch_instances handles partial capacity correctly.
747+
748+ First call: ask for 3 instances (min 1) -> should get 2, no error
749+ Second call: ask for 3 instances (min 1) -> should raise EC2InsufficientCapacityError
750+ """
751+ await _assert_no_instances_in_ec2 (ec2_client )
752+
753+ # Create a config with a single subnet (as requested)
754+ ec2_instance_config = EC2InstanceConfig (
755+ type = fake_ec2_instance_type ,
756+ tags = faker .pydict (allowed_types = (str ,)),
757+ startup_script = faker .pystr (),
758+ ami_id = aws_ami_id ,
759+ key_name = faker .pystr (),
760+ security_group_ids = [aws_security_group_id ],
761+ subnet_ids = [aws_subnet_id ], # Single subnet only
762+ iam_instance_profile = "" ,
763+ )
764+
765+ # Mock the EC2 client to simulate partial capacity behavior
766+ original_run_instances = simcore_ec2_api .client .run_instances
767+ call_count = 0
768+
769+ async def mock_run_instances (* args , ** kwargs ):
770+ nonlocal call_count
771+ call_count += 1
772+
773+ if call_count == 1 :
774+ # First call: return only 2 instances when 3 were requested
775+ # Simulate that the subnet has capacity for only 2 machines
776+ kwargs_copy = kwargs .copy ()
777+ kwargs_copy ["MinCount" ] = 2
778+ kwargs_copy ["MaxCount" ] = 2
779+ return await original_run_instances (* args , ** kwargs_copy )
780+ else :
781+ # Second call: simulate insufficient capacity (subnet is full)
782+ error_response = {
783+ "Error" : {
784+ "Code" : "InsufficientInstanceCapacity" ,
785+ "Message" : "Insufficient capacity." ,
786+ }
787+ }
788+ raise botocore .exceptions .ClientError (error_response , "RunInstances" )
789+
790+ # Apply the mock for the first call
791+ with mocker .patch .object (
792+ simcore_ec2_api .client , "run_instances" , side_effect = mock_run_instances
793+ ):
794+ # First call: ask for 3 instances (min 1) -> should get 2, no error
795+ instances = await simcore_ec2_api .launch_instances (
796+ ec2_instance_config ,
797+ min_number_of_instances = 1 ,
798+ number_of_instances = 3 ,
799+ )
800+
801+ # Verify we got 2 instances (partial capacity)
802+ assert len (instances ) == 2
803+ assert call_count == 1
804+
805+ # Verify instances were created
806+ await _assert_instances_in_ec2 (
807+ ec2_client ,
808+ expected_num_reservations = 1 ,
809+ expected_num_instances = 2 ,
810+ expected_instance_type = ec2_instance_config .type ,
811+ expected_tags = ec2_instance_config .tags ,
812+ expected_state = "running" ,
813+ )
814+
815+ # Second call: ask for 3 instances (min 1) -> should raise EC2InsufficientCapacityError
816+ with pytest .raises (EC2InsufficientCapacityError ) as exc_info :
817+ await simcore_ec2_api .launch_instances (
818+ ec2_instance_config ,
819+ min_number_of_instances = 1 ,
820+ number_of_instances = 3 ,
821+ )
822+
823+ # Verify that run_instances was called twice total
824+ assert call_count == 2
825+
826+ # Verify the error contains the expected information
827+ assert hasattr (exc_info .value , "instance_type" )
828+ assert exc_info .value .instance_type == fake_ec2_instance_type .name
829+
830+ # Verify still only 2 instances exist (no new ones were created)
831+ await _assert_instances_in_ec2 (
832+ ec2_client ,
833+ expected_num_reservations = 1 ,
834+ expected_num_instances = 2 ,
835+ expected_instance_type = ec2_instance_config .type ,
836+ expected_tags = ec2_instance_config .tags ,
837+ expected_state = "running" ,
838+ )
0 commit comments