Skip to content

ec2: Enhance security group handling in Spot Fleet launch specifications#159

Open
nik-localstack wants to merge 1 commit intolocalstack:localstackfrom
nik-localstack:unc-404-ec2-createfleet-instances-launched-from-launchtemplate
Open

ec2: Enhance security group handling in Spot Fleet launch specifications#159
nik-localstack wants to merge 1 commit intolocalstack:localstackfrom
nik-localstack:unc-404-ec2-createfleet-instances-launched-from-launchtemplate

Conversation

@nik-localstack
Copy link
Copy Markdown

Motivation

When CreateFleet uses a LaunchTemplateSpecification, security groups were never applied to launched instances because group_set was read exclusively from the non-standard GroupSet field, ignoring the actual SecurityGroupIds and NetworkInterfaces[N].Groups fields that AWS clients use.

Solution

The fix falls back through all three sources (GroupSet → SecurityGroupIds → NetworkInterfaces[N].Groups) when building the SpotFleetLaunchSpec, and two AWS-verified regression tests covering both code paths are added in TestFleetSecurityGroups.

@nik-localstack nik-localstack self-assigned this Apr 9, 2026
@nik-localstack nik-localstack marked this pull request as ready for review April 9, 2026 16:34
Comment thread moto/ec2/models/fleets.py
for g in (nic.get("Groups") or [])
]
or []
),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does AWS follow this exact precedence order?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWS disallows specifying all these parameters on the creation.

Comment on lines +1237 to +1290
@staticmethod
def _get_ami_id(ec2_client):
"""Mock AMI in moto; real Amazon Linux AMI via SSM on AWS."""
from tests import allow_aws_request

if not allow_aws_request():
return EXAMPLE_AMI_ID
ssm = boto3.client("ssm", region_name=ec2_client.meta.region_name)
kernel_61 = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
return ssm.get_parameter(Name=kernel_61)["Parameter"]["Value"]

@staticmethod
def _create_vpc_subnet_sg(ec2_client, cleanups, sg_description="fleet sg test"):
"""Create VPC / subnet / SG, registering cleanups in creation order.

Reversed teardown (SG → subnet → VPC) respects AWS dependency rules.

Returns:
(vpc_id, subnet_id, sg_id)
"""
vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
cleanups.append(lambda: ec2_client.delete_vpc(VpcId=vpc_id))

subnet_id = ec2_client.create_subnet(
VpcId=vpc_id,
CidrBlock="10.0.0.0/24",
AvailabilityZone=ec2_client.meta.region_name + "a",
)["Subnet"]["SubnetId"]
cleanups.append(lambda: ec2_client.delete_subnet(SubnetId=subnet_id))

sg_id = ec2_client.create_security_group(
GroupName="test-fleet-" + str(uuid4())[:6],
Description=sg_description,
VpcId=vpc_id,
)["GroupId"]
cleanups.append(lambda: ec2_client.delete_security_group(GroupId=sg_id))

return vpc_id, subnet_id, sg_id

@staticmethod
def _cleanup_fleet(ec2_client, fleet_id, instance_ids, subnet_id):
"""Delete the fleet, wait for instance termination, then wait for ENIs
to be fully released from the subnet before delete_subnet / delete_vpc run.
"""
ec2_client.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True)
if instance_ids:
ec2_client.get_waiter("instance_terminated").wait(InstanceIds=instance_ids)
for _ in range(20):
enis = ec2_client.describe_network_interfaces(
Filters=[{"Name": "subnet-id", "Values": [subnet_id]}]
)["NetworkInterfaces"]
if not enis:
break
sleep(3)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These static methods feel like they should be fixtures.

Comment on lines +1284 to +1290
for _ in range(20):
enis = ec2_client.describe_network_interfaces(
Filters=[{"Name": "subnet-id", "Values": [subnet_id]}]
)["NetworkInterfaces"]
if not enis:
break
sleep(3)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please implement the retry fixture as in LocalStack if possible because this pattern is starting to be quite common.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we need to implement proper fixtures and improve testing for EC2 in general, but doing it here seems like a wasted effort to me. I think we should discuss about how we test EC2 and maybe move all the tests in localstack repository but this should be a follow-up. Consider that this PR is supposed to be a small bugfix to unblock another initiative.

"Instance has no security groups — SecurityGroupIds from launch template was not applied"
)
assert sg_id in instance_sg_ids, (
f"Expected security group {sg_id!r} to be attached to the instance, "
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why !r? Why not just {sg_id}?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific reason, we can remove the !r

lambda: self._cleanup_fleet(ec2_client, fleet_id, instance_ids, subnet_id)
)

fleet_errors = fleet_res.get("Errors", [])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per AWS docs, CreateFleet response has no Errors field, but there is an ErrorSet

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return ssm.get_parameter(Name=kernel_61)["Parameter"]["Value"]

@staticmethod
def _create_vpc_subnet_sg(ec2_client, cleanups, sg_description="fleet sg test"):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ec2_aws_verified decorator is already capable of creating this, what's the reason it's not used?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, the reason is that it doesn't work well with cleanups. The cleanup runs after the ec2_aws_verified finally blocks, which ends up with VPCs / subnets not being removed.

Copy link
Copy Markdown
Author

@nik-localstack nik-localstack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback @viren-nadkarni
I agree with most of your comments and I think we need to discuss about ec2 testing in general. Would you be willing to have a call tomorrow or early next week ?
In the meantime, can you specify if these comments are blockers for the current PR ?

return ssm.get_parameter(Name=kernel_61)["Parameter"]["Value"]

@staticmethod
def _create_vpc_subnet_sg(ec2_client, cleanups, sg_description="fleet sg test"):
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, the reason is that it doesn't work well with cleanups. The cleanup runs after the ec2_aws_verified finally blocks, which ends up with VPCs / subnets not being removed.

Comment on lines +1284 to +1290
for _ in range(20):
enis = ec2_client.describe_network_interfaces(
Filters=[{"Name": "subnet-id", "Values": [subnet_id]}]
)["NetworkInterfaces"]
if not enis:
break
sleep(3)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we need to implement proper fixtures and improve testing for EC2 in general, but doing it here seems like a wasted effort to me. I think we should discuss about how we test EC2 and maybe move all the tests in localstack repository but this should be a follow-up. Consider that this PR is supposed to be a small bugfix to unblock another initiative.

Comment thread moto/ec2/models/fleets.py
for g in (nic.get("Groups") or [])
]
or []
),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWS disallows specifying all these parameters on the creation.

lambda: self._cleanup_fleet(ec2_client, fleet_id, instance_ids, subnet_id)
)

fleet_errors = fleet_res.get("Errors", [])
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Instance has no security groups — SecurityGroupIds from launch template was not applied"
)
assert sg_id in instance_sg_ids, (
f"Expected security group {sg_id!r} to be attached to the instance, "
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific reason, we can remove the !r

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants