diff --git a/.github/workflows/tests_real_aws.yml b/.github/workflows/tests_real_aws.yml index b3fa3303a8bf..6ef5bf734ee3 100644 --- a/.github/workflows/tests_real_aws.yml +++ b/.github/workflows/tests_real_aws.yml @@ -42,4 +42,4 @@ jobs: env: MOTO_TEST_ALLOW_AWS_REQUEST: ${{ true }} run: | - pytest -sv tests/test_s3 -m aws_verified + pytest -sv tests/test_ec2/ tests/test_s3 -m aws_verified diff --git a/moto/ec2/models/fleets.py b/moto/ec2/models/fleets.py index 9563efe4e902..a76a26ea53bf 100644 --- a/moto/ec2/models/fleets.py +++ b/moto/ec2/models/fleets.py @@ -308,12 +308,19 @@ def describe_fleets(self, fleet_ids: Optional[List[str]]) -> List[Fleet]: def delete_fleets( self, fleet_ids: List[str], terminate_instances: bool ) -> List[Fleet]: + fleets = [] for fleet_id in fleet_ids: fleet = self.fleets[fleet_id] if terminate_instances: + # State indicates the fleet is in the process of being terminated + # AWS will change the state to `deleted` after a few seconds/minutes + # Note that it will stay in the `deleted`-state for at least a few hours + fleet.state = "deleted_terminating" fleet.target_capacity = 0 fleet.terminate_instances() + else: + # State is different, and indicates that instances are still running + fleet.state = "deleted_running" fleets.append(fleet) - fleet.state = "deleted" return fleets diff --git a/moto/ec2/responses/fleets.py b/moto/ec2/responses/fleets.py index 9bf247672d22..e63a74322812 100644 --- a/moto/ec2/responses/fleets.py +++ b/moto/ec2/responses/fleets.py @@ -4,7 +4,7 @@ class Fleets(EC2BaseResponse): def delete_fleets(self) -> str: fleet_ids = self._get_multi_param("FleetId.") - terminate_instances = self._get_param("TerminateInstances") + terminate_instances = self._get_bool_param("TerminateInstances") fleets = self.ec2_backend.delete_fleets(fleet_ids, terminate_instances) template = self.response_template(DELETE_FLEETS_TEMPLATE) return template.render(fleets=fleets) diff --git a/tests/test_ec2/__init__.py b/tests/test_ec2/__init__.py index e69de29bb2d1..3e0ec5ea046d 100644 --- a/tests/test_ec2/__init__.py +++ b/tests/test_ec2/__init__.py @@ -0,0 +1,33 @@ +import os +from functools import wraps +from moto import mock_ec2, mock_ssm + + +def ec2_aws_verified(func): + """ + Function that is verified to work against AWS. + Can be run against AWS at any time by setting: + MOTO_TEST_ALLOW_AWS_REQUEST=true + + If this environment variable is not set, the function runs in a `mock_s3` context. + + This decorator will: + - Create a bucket + - Run the test and pass the bucket_name as an argument + - Delete the objects and the bucket itself + """ + + @wraps(func) + def pagination_wrapper(): + allow_aws = ( + os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true" + ) + + if allow_aws: + resp = func() + else: + with mock_ec2(), mock_ssm(): + resp = func() + return resp + + return pagination_wrapper diff --git a/tests/test_ec2/test_fleets.py b/tests/test_ec2/test_fleets.py index 7ad3ee551385..2dfda6991b1b 100644 --- a/tests/test_ec2/test_fleets.py +++ b/tests/test_ec2/test_fleets.py @@ -5,6 +5,8 @@ from tests import EXAMPLE_AMI_ID from uuid import uuid4 +from . import ec2_aws_verified + def get_subnet_id(conn): vpc = conn.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] @@ -15,11 +17,11 @@ def get_subnet_id(conn): return subnet_id -def get_launch_template(conn, instance_type="t2.micro"): +def get_launch_template(conn, instance_type="t2.micro", ami_id=EXAMPLE_AMI_ID): launch_template = conn.create_launch_template( LaunchTemplateName="test" + str(uuid4()), LaunchTemplateData={ - "ImageId": EXAMPLE_AMI_ID, + "ImageId": ami_id, "InstanceType": instance_type, "KeyName": "test", "SecurityGroups": ["sg-123456"], @@ -40,6 +42,29 @@ def get_launch_template(conn, instance_type="t2.micro"): return launch_template_id, launch_template_name +class launch_template_context: + def __init__(self, region: str = "us-west-2"): + self.ec2 = boto3.client("ec2", region_name=region) + self.ssm = boto3.client("ssm", region_name=region) + + def __enter__(self): + kernel_61 = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" + ami_id = self.ssm.get_parameter(Name=kernel_61)["Parameter"]["Value"] + self.lt_id, self.lt_name = get_launch_template(self.ec2, ami_id=ami_id) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.ec2.delete_launch_template(LaunchTemplateId=self.lt_id) + + +@ec2_aws_verified +def test_launch_template_is_created_properly(): + with launch_template_context() as ctxt: + template = ctxt.ec2.describe_launch_templates()["LaunchTemplates"][0] + assert template["DefaultVersionNumber"] == 1 + assert template["LatestVersionNumber"] == 1 + + @mock_ec2 def test_create_spot_fleet_with_lowest_price(): conn = boto3.client("ec2", region_name="us-west-2") @@ -354,61 +379,66 @@ def test_create_fleet_using_launch_template_config__overrides(): assert instance["SubnetId"] == subnet_id -@mock_ec2 +@ec2_aws_verified def test_delete_fleet(): - conn = boto3.client("ec2", region_name="us-west-2") - - launch_template_id, _ = get_launch_template(conn) + with launch_template_context() as ctxt: - fleet_res = conn.create_fleet( - LaunchTemplateConfigs=[ - { - "LaunchTemplateSpecification": { - "LaunchTemplateId": launch_template_id, - "Version": "1", + fleet_res = ctxt.ec2.create_fleet( + LaunchTemplateConfigs=[ + { + "LaunchTemplateSpecification": { + "LaunchTemplateId": ctxt.lt_id, + "Version": "1", + }, }, + ], + TargetCapacitySpecification={ + "DefaultTargetCapacityType": "spot", + "OnDemandTargetCapacity": 1, + "SpotTargetCapacity": 2, + "TotalTargetCapacity": 3, }, - ], - TargetCapacitySpecification={ - "DefaultTargetCapacityType": "spot", - "OnDemandTargetCapacity": 1, - "SpotTargetCapacity": 2, - "TotalTargetCapacity": 3, - }, - SpotOptions={ - "AllocationStrategy": "lowestPrice", - "InstanceInterruptionBehavior": "terminate", - }, - Type="maintain", - ValidFrom="2020-01-01T00:00:00Z", - ValidUntil="2020-12-31T00:00:00Z", - ) - fleet_id = fleet_res["FleetId"] + SpotOptions={ + "AllocationStrategy": "lowestPrice", + "InstanceInterruptionBehavior": "terminate", + }, + Type="maintain", + ) + fleet_id = fleet_res["FleetId"] - delete_fleet_out = conn.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True) + successfull_deletions = ctxt.ec2.delete_fleets( + FleetIds=[fleet_id], TerminateInstances=False + )["SuccessfulFleetDeletions"] - assert len(delete_fleet_out["SuccessfulFleetDeletions"]) == 1 - assert delete_fleet_out["SuccessfulFleetDeletions"][0]["FleetId"] == fleet_id - assert ( - delete_fleet_out["SuccessfulFleetDeletions"][0]["CurrentFleetState"] - == "deleted" - ) + assert len(successfull_deletions) == 1 + assert successfull_deletions[0]["FleetId"] == fleet_id + assert successfull_deletions[0]["CurrentFleetState"] == "deleted_running" - fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"] - assert len(fleets) == 1 + successfull_deletions = ctxt.ec2.delete_fleets( + FleetIds=[fleet_id], TerminateInstances=True + )["SuccessfulFleetDeletions"] - target_capacity_specification = fleets[0]["TargetCapacitySpecification"] - assert target_capacity_specification["TotalTargetCapacity"] == 0 - assert fleets[0]["FleetState"] == "deleted" + assert len(successfull_deletions) == 1 + assert successfull_deletions[0]["FleetId"] == fleet_id + assert successfull_deletions[0]["CurrentFleetState"] == "deleted_terminating" - # Instances should be terminated - instance_res = conn.describe_fleet_instances(FleetId=fleet_id) - instances = instance_res["ActiveInstances"] - assert len(instances) == 0 + fleets = ctxt.ec2.describe_fleets(FleetIds=[fleet_id])["Fleets"] + assert len(fleets) == 1 + + # AWS doesn't reset this, but Moto does + # we should figure out why Moto sets this value to 0 + # target_capacity_specification = fleets[0]["TargetCapacitySpecification"] + # assert target_capacity_specification["TotalTargetCapacity"] == 3 + assert fleets[0]["FleetState"] == "deleted_terminating" + + # Instances should be terminated + instance_res = ctxt.ec2.describe_fleet_instances(FleetId=fleet_id) + instances = instance_res["ActiveInstances"] + assert len(instances) == 0 @mock_ec2 -def test_describe_fleet_instences_api(): +def test_describe_fleet_instances_api(): conn = boto3.client("ec2", region_name="us-west-1") launch_template_id, _ = get_launch_template(conn)