66import random
77from collections .abc import AsyncIterator , Awaitable , Callable
88from dataclasses import fields
9- from typing import Any , cast , get_args
9+ from typing import Any , Final , cast , get_args
1010
1111import botocore .exceptions
1212import pytest
1515 EC2InstanceNotFoundError ,
1616 EC2InstanceTypeInvalidError ,
1717 EC2InsufficientCapacityError ,
18+ EC2SubnetsNotEnoughIPsError ,
1819 EC2TooManyInstancesError ,
1920)
2021from aws_library .ec2 ._models import (
@@ -841,6 +842,9 @@ async def mock_run_instances(*args, **kwargs):
841842 )
842843
843844
845+ _RESERVED_IPS : Final [int ] = 5 # AWS reserves 5 IPs in each subnet
846+
847+
844848@pytest .fixture
845849async def with_small_subnet (
846850 create_aws_subnet_id : Callable [..., Awaitable [str ]],
@@ -849,7 +853,10 @@ async def with_small_subnet(
849853 single_ip_cidr = (
850854 "10.0.11.0/29" # /29 is the minimum allowed by AWS, gives 8 addresses
851855 )
852- return (await create_aws_subnet_id (single_ip_cidr ), 8 - 5 ) # 5 are reserved by AWS
856+ return (
857+ await create_aws_subnet_id (single_ip_cidr ),
858+ 8 - _RESERVED_IPS ,
859+ ) # 5 are reserved by AWS
853860
854861
855862async def test_launch_instances_with_small_subnet (
@@ -902,3 +909,88 @@ async def test_launch_instances_with_small_subnet(
902909 min_number_of_instances = 1 ,
903910 number_of_instances = 1 ,
904911 )
912+
913+
914+ async def test_launch_instances_raises_ec2_subnets_not_enough_ips_error (
915+ simcore_ec2_api : SimcoreEC2API ,
916+ ec2_client : EC2Client ,
917+ fake_ec2_instance_type : EC2InstanceType ,
918+ faker : Faker ,
919+ create_aws_subnet_id : Callable [..., Awaitable [str ]],
920+ aws_security_group_id : str ,
921+ aws_ami_id : str ,
922+ mocker : MockerFixture ,
923+ ) -> None :
924+ """Test that EC2SubnetsNotEnoughIPsError is raised when subnets don't have enough IPs."""
925+ await _assert_no_instances_in_ec2 (ec2_client )
926+
927+ # Create additional small subnets
928+ subnet1_id = await create_aws_subnet_id ("10.0.200.0/29" ) # 3 usable IPs
929+ subnet2_id = await create_aws_subnet_id ("10.0.201.0/29" ) # 3 usable IPs
930+
931+ ec2_instance_config = EC2InstanceConfig (
932+ type = fake_ec2_instance_type ,
933+ tags = faker .pydict (allowed_types = (str ,)),
934+ startup_script = faker .pystr (),
935+ ami_id = aws_ami_id ,
936+ key_name = faker .pystr (),
937+ security_group_ids = [aws_security_group_id ],
938+ subnet_ids = [subnet1_id , subnet2_id ],
939+ iam_instance_profile = "" ,
940+ )
941+
942+ with pytest .raises (EC2SubnetsNotEnoughIPsError ) as exc_info :
943+ await simcore_ec2_api .launch_instances (
944+ ec2_instance_config ,
945+ min_number_of_instances = 7 ,
946+ number_of_instances = 7 ,
947+ )
948+
949+ error = exc_info .value
950+ assert error .subnet_ids == [subnet1_id , subnet2_id ] # type: ignore
951+ assert error .instance_type == fake_ec2_instance_type .name # type: ignore
952+ assert error .available_ips == 6 # type: ignore
953+
954+
955+ async def test_launch_instances_distributes_instances_among_subnets (
956+ simcore_ec2_api : SimcoreEC2API ,
957+ ec2_client : EC2Client ,
958+ fake_ec2_instance_type : EC2InstanceType ,
959+ faker : Faker ,
960+ create_aws_subnet_id : Callable [..., Awaitable [str ]],
961+ aws_security_group_id : str ,
962+ aws_ami_id : str ,
963+ mocker : MockerFixture ,
964+ ) -> None :
965+ """Test that EC2SubnetsNotEnoughIPsError is raised when subnets don't have enough IPs."""
966+ await _assert_no_instances_in_ec2 (ec2_client )
967+
968+ # Create additional small subnets
969+ subnet1_id = await create_aws_subnet_id ("10.0.200.0/29" ) # 3 usable IPs
970+ subnet2_id = await create_aws_subnet_id ("10.0.201.0/29" ) # 3 usable IPs
971+
972+ ec2_instance_config = EC2InstanceConfig (
973+ type = fake_ec2_instance_type ,
974+ tags = faker .pydict (allowed_types = (str ,)),
975+ startup_script = faker .pystr (),
976+ ami_id = aws_ami_id ,
977+ key_name = faker .pystr (),
978+ security_group_ids = [aws_security_group_id ],
979+ subnet_ids = [subnet1_id , subnet2_id ],
980+ iam_instance_profile = "" ,
981+ )
982+
983+ await simcore_ec2_api .launch_instances (
984+ ec2_instance_config ,
985+ min_number_of_instances = 5 ,
986+ number_of_instances = 5 ,
987+ )
988+
989+ await _assert_instances_in_ec2 (
990+ ec2_client ,
991+ expected_num_reservations = 1 ,
992+ expected_num_instances = 5 ,
993+ expected_instance_type = ec2_instance_config .type ,
994+ expected_tags = ec2_instance_config .tags ,
995+ expected_state = "running" ,
996+ )
0 commit comments