Skip to content

Commit b8659fc

Browse files
committed
workaround moto issue
1 parent f6f0ce6 commit b8659fc

File tree

3 files changed

+94
-4
lines changed

3 files changed

+94
-4
lines changed

packages/aws-library/src/aws_library/ec2/_client.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,41 @@ async def launch_instances(
154154
max_total_number_of_instances=max_total_number_of_instances,
155155
)
156156

157+
subnet_descriptions = await self.client.describe_subnets(
158+
SubnetIds=instance_config.subnet_ids
159+
)
160+
# check available IPs in subnets to give early feedback
161+
subnet_id_to_available_ips: dict[str, int] = {}
162+
for subnet in subnet_descriptions["Subnets"]:
163+
if "SubnetId" in subnet and "AvailableIpAddressCount" in subnet:
164+
subnet_id_to_available_ips[subnet["SubnetId"]] = subnet[
165+
"AvailableIpAddressCount"
166+
]
167+
total_available_ips = sum(subnet_id_to_available_ips.values())
168+
if total_available_ips < min_number_of_instances:
169+
raise EC2InsufficientCapacityError(
170+
subnet_id=", ".join(instance_config.subnet_ids),
171+
instance_type=instance_config.type.name,
172+
details=(
173+
f"Not enough available IPs in subnets {subnet_id_to_available_ips}. "
174+
f"Total available IPs={total_available_ips} < min required instances={min_number_of_instances}"
175+
),
176+
)
177+
# now let's not try to run instances in subnets that have not enough IPs
178+
subnet_ids_with_capacity = [
179+
subnet_id
180+
for subnet_id, capacity in subnet_id_to_available_ips.items()
181+
if capacity >= min_number_of_instances
182+
]
183+
157184
resource_tags: list[TagTypeDef] = [
158185
{"Key": tag_key, "Value": tag_value}
159186
for tag_key, tag_value in instance_config.tags.items()
160187
]
161188

162189
# Try each subnet in order until one succeeds
163190
last_error = None
164-
for subnet_id in instance_config.subnet_ids:
191+
for subnet_id in subnet_ids_with_capacity:
165192
try:
166193
_logger.debug(
167194
"Attempting to launch instances in subnet %s", subnet_id

packages/aws-library/tests/test_ec2_client.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,3 +839,66 @@ async def mock_run_instances(*args, **kwargs):
839839
expected_tags=ec2_instance_config.tags,
840840
expected_state="running",
841841
)
842+
843+
844+
@pytest.fixture
845+
async def with_small_subnet(
846+
create_aws_subnet_id: Callable[..., Awaitable[str]],
847+
) -> tuple[str, int]:
848+
"""Creates a subnet with a single IP address to simulate InsufficientInstanceCapacity"""
849+
single_ip_cidr = (
850+
"10.0.11.0/29" # /29 is the minimum allowed by AWS, gives 8 addresses
851+
)
852+
return (await create_aws_subnet_id(single_ip_cidr), 8 - 5) # 5 are reserved by AWS
853+
854+
855+
async def test_launch_instances_with_small_subnet(
856+
simcore_ec2_api: SimcoreEC2API,
857+
ec2_client: EC2Client,
858+
fake_ec2_instance_type: EC2InstanceType,
859+
faker: Faker,
860+
with_small_subnet: tuple[str, int],
861+
aws_subnet_id: str,
862+
aws_security_group_id: str,
863+
aws_ami_id: str,
864+
mocker: MockerFixture,
865+
):
866+
await _assert_no_instances_in_ec2(ec2_client)
867+
small_subnet_id, capacity = with_small_subnet
868+
# Create a config with a single subnet (as requested)
869+
ec2_instance_config = EC2InstanceConfig(
870+
type=fake_ec2_instance_type,
871+
tags=faker.pydict(allowed_types=(str,)),
872+
startup_script=faker.pystr(),
873+
ami_id=aws_ami_id,
874+
key_name=faker.pystr(),
875+
security_group_ids=[aws_security_group_id],
876+
subnet_ids=[small_subnet_id, aws_subnet_id],
877+
iam_instance_profile="",
878+
)
879+
880+
# first call shall work in the first subnet
881+
instances = await simcore_ec2_api.launch_instances(
882+
ec2_instance_config,
883+
min_number_of_instances=capacity,
884+
number_of_instances=capacity,
885+
)
886+
887+
# Verify we got 2 instances (partial capacity)
888+
assert len(instances) == capacity
889+
890+
# Verify instances were created
891+
await _assert_instances_in_ec2(
892+
ec2_client,
893+
expected_num_reservations=1,
894+
expected_num_instances=capacity,
895+
expected_instance_type=ec2_instance_config.type,
896+
expected_tags=ec2_instance_config.tags,
897+
expected_state="running",
898+
)
899+
900+
instances = await simcore_ec2_api.launch_instances(
901+
ec2_instance_config,
902+
min_number_of_instances=1,
903+
number_of_instances=1,
904+
)

packages/pytest-simcore/src/pytest_simcore/aws_ec2_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ async def create_aws_subnet_id(
8787
aws_vpc_id: str,
8888
ec2_client: EC2Client,
8989
create_subnet_cidr_block: Callable[[], str],
90-
) -> AsyncIterator[Callable[[], Awaitable[str]]]:
90+
) -> AsyncIterator[Callable[..., Awaitable[str]]]:
9191
created_subnet_ids: set[str] = set()
9292

93-
async def _() -> str:
93+
async def _(cidr_override: str | None = None) -> str:
9494
subnet = await ec2_client.create_subnet(
95-
CidrBlock=create_subnet_cidr_block(), VpcId=aws_vpc_id
95+
CidrBlock=cidr_override or create_subnet_cidr_block(), VpcId=aws_vpc_id
9696
)
9797
assert "Subnet" in subnet
9898
assert "SubnetId" in subnet["Subnet"]

0 commit comments

Comments
 (0)