import boto3 import sure # noqa # pylint: disable=unused-import import pytest from moto import mock_ec2 from tests import EXAMPLE_AMI_ID from uuid import uuid4 def get_subnet_id(conn): vpc = conn.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] subnet = conn.create_subnet( VpcId=vpc["VpcId"], CidrBlock="10.0.0.0/16", AvailabilityZone="us-east-1a" )["Subnet"] subnet_id = subnet["SubnetId"] return subnet_id def get_launch_template(conn, instance_type="t2.micro"): launch_template = conn.create_launch_template( LaunchTemplateName="test" + str(uuid4()), LaunchTemplateData={ "ImageId": EXAMPLE_AMI_ID, "InstanceType": instance_type, "KeyName": "test", "SecurityGroups": ["sg-123456"], "DisableApiTermination": False, "TagSpecifications": [ { "ResourceType": "instance", "Tags": [ {"Key": "test", "Value": "value"}, {"Key": "Name", "Value": "test"}, ], } ], }, )["LaunchTemplate"] launch_template_id = launch_template["LaunchTemplateId"] launch_template_name = launch_template["LaunchTemplateName"] return launch_template_id, launch_template_name @mock_ec2 def test_create_spot_fleet_with_lowest_price(): conn = boto3.client("ec2", region_name="us-west-2") launch_template_id, _ = get_launch_template(conn) fleet_res = conn.create_fleet( ExcessCapacityTerminationPolicy="terminate", LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 0, "SpotTargetCapacity": 1, "TotalTargetCapacity": 1, }, SpotOptions={ "AllocationStrategy": "lowest-price", }, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] fleet_id.should.be.a(str) fleet_id.should.have.length_of(42) fleets_res = conn.describe_fleets(FleetIds=[fleet_id]) fleets_res.should.have.key("Fleets") fleets = fleets_res["Fleets"] fleet = fleets[0] fleet["FleetState"].should.equal("active") fleet_config = fleet["LaunchTemplateConfigs"][0] launch_template_spec = fleet_config["LaunchTemplateSpecification"] launch_template_spec["LaunchTemplateId"].should.equal(launch_template_id) launch_template_spec["Version"].should.equal("1") instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] len(instances).should.equal(1) @mock_ec2 def test_create_on_demand_fleet(): conn = boto3.client("ec2", region_name="us-west-2") launch_template_id, _ = get_launch_template(conn) fleet_res = conn.create_fleet( ExcessCapacityTerminationPolicy="terminate", LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "on-demand", "OnDemandTargetCapacity": 1, "SpotTargetCapacity": 0, "TotalTargetCapacity": 1, }, OnDemandOptions={ "AllocationStrategy": "lowestPrice", }, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] fleet_id.should.be.a(str) fleet_id.should.have.length_of(42) fleets_res = conn.describe_fleets(FleetIds=[fleet_id]) fleets_res.should.have.key("Fleets") fleets = fleets_res["Fleets"] fleet = fleets[0] fleet["FleetState"].should.equal("active") fleet_config = fleet["LaunchTemplateConfigs"][0] launch_template_spec = fleet_config["LaunchTemplateSpecification"] launch_template_spec["LaunchTemplateId"].should.equal(launch_template_id) launch_template_spec["Version"].should.equal("1") instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] len(instances).should.equal(1) @mock_ec2 def test_create_diversified_spot_fleet(): conn = boto3.client("ec2", region_name="us-west-2") launch_template_id_1, _ = get_launch_template(conn, instance_type="t2.small") launch_template_id_2, _ = get_launch_template(conn, instance_type="t2.large") fleet_res = conn.create_fleet( ExcessCapacityTerminationPolicy="terminate", LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id_1, "Version": "1", }, }, { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id_2, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 0, "SpotTargetCapacity": 2, "TotalTargetCapacity": 2, }, SpotOptions={ "AllocationStrategy": "diversified", }, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] len(instances).should.equal(2) instance_types = set([instance["InstanceType"] for instance in instances]) instance_types.should.equal(set(["t2.small", "t2.large"])) instances[0]["InstanceId"].should.contain("i-") @mock_ec2 @pytest.mark.parametrize( "spot_allocation_strategy", [ "diversified", "lowest-price", "capacity-optimized", "capacity-optimized-prioritized", ], ) @pytest.mark.parametrize( "on_demand_allocation_strategy", ["lowestPrice", "prioritized"], ) def test_request_fleet_using_launch_template_config__name( spot_allocation_strategy, on_demand_allocation_strategy ): conn = boto3.client("ec2", region_name="us-east-2") _, launch_template_name = get_launch_template(conn, instance_type="t2.medium") fleet_res = conn.create_fleet( LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateName": launch_template_name, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 1, "SpotTargetCapacity": 2, "TotalTargetCapacity": 3, }, SpotOptions={ "AllocationStrategy": spot_allocation_strategy, }, OnDemandOptions={ "AllocationStrategy": on_demand_allocation_strategy, }, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] len(instances).should.equal(3) instance_types = set([instance["InstanceType"] for instance in instances]) instance_types.should.equal(set(["t2.medium"])) instances[0]["InstanceId"].should.contain("i-") @mock_ec2 def test_create_fleet_request_with_tags(): conn = boto3.client("ec2", region_name="us-west-2") launch_template_id, _ = get_launch_template(conn) tags = [ {"Key": "Name", "Value": "test-fleet"}, {"Key": "Another", "Value": "tag"}, ] tags_instance = [ {"Key": "test", "Value": "value"}, {"Key": "Name", "Value": "test"}, ] fleet_res = conn.create_fleet( DryRun=False, SpotOptions={ "AllocationStrategy": "lowestPrice", "InstanceInterruptionBehavior": "terminate", }, LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 1, "SpotTargetCapacity": 2, "TotalTargetCapacity": 3, }, Type="request", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", TagSpecifications=[ { "ResourceType": "fleet", "Tags": tags, }, ], ) fleet_id = fleet_res["FleetId"] fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"] fleets[0]["Tags"].should.equal(tags) instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = conn.describe_instances( InstanceIds=[i["InstanceId"] for i in instance_res["ActiveInstances"]] ) for instance in instances["Reservations"][0]["Instances"]: for tag in tags_instance: instance["Tags"].should.contain(tag) @mock_ec2 def test_create_fleet_using_launch_template_config__overrides(): conn = boto3.client("ec2", region_name="us-east-2") subnet_id = get_subnet_id(conn) template_id, _ = get_launch_template(conn, instance_type="t2.medium") template_config_overrides = [ { "InstanceType": "t2.nano", "SubnetId": subnet_id, "AvailabilityZone": "us-west-1", "WeightedCapacity": 2, } ] fleet_res = conn.create_fleet( LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": template_id, "Version": "1", }, "Overrides": template_config_overrides, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 1, "SpotTargetCapacity": 0, "TotalTargetCapacity": 1, }, SpotOptions={ "AllocationStrategy": "lowest-price", "InstanceInterruptionBehavior": "terminate", }, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] instances.should.have.length_of(1) instances[0].should.have.key("InstanceType").equals("t2.nano") instance = conn.describe_instances( InstanceIds=[i["InstanceId"] for i in instances] )["Reservations"][0]["Instances"][0] instance.should.have.key("SubnetId").equals(subnet_id) @mock_ec2 def test_delete_fleet(): conn = boto3.client("ec2", region_name="us-west-2") launch_template_id, _ = get_launch_template(conn) fleet_res = conn.create_fleet( LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], 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"] delete_fleet_out = conn.delete_fleets(FleetIds=[fleet_id], TerminateInstances=True) delete_fleet_out["SuccessfulFleetDeletions"].should.have.length_of(1) delete_fleet_out["SuccessfulFleetDeletions"][0]["FleetId"].should.equal(fleet_id) delete_fleet_out["SuccessfulFleetDeletions"][0]["CurrentFleetState"].should.equal( "deleted" ) fleets = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"] len(fleets).should.equal(1) target_capacity_specification = fleets[0]["TargetCapacitySpecification"] target_capacity_specification.should.have.key("TotalTargetCapacity").equals(0) fleets[0]["FleetState"].should.equal("deleted") # Instances should be terminated instance_res = conn.describe_fleet_instances(FleetId=fleet_id) instances = instance_res["ActiveInstances"] len(instances).should.equal(0) @mock_ec2 def test_describe_fleet_instences_api(): conn = boto3.client("ec2", region_name="us-west-1") launch_template_id, _ = get_launch_template(conn) fleet_res = conn.create_fleet( LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], 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"] fleet_res = conn.describe_fleet_instances(FleetId=fleet_id) fleet_res["FleetId"].should.equal(fleet_id) fleet_res["ActiveInstances"].should.have.length_of(3) instance_ids = [i["InstanceId"] for i in fleet_res["ActiveInstances"]] for instance_id in instance_ids: instance_id.startswith("i-").should.be.true instance_types = [i["InstanceType"] for i in fleet_res["ActiveInstances"]] instance_types.should.equal(["t2.micro", "t2.micro", "t2.micro"]) instance_healths = [i["InstanceHealth"] for i in fleet_res["ActiveInstances"]] instance_healths.should.equal(["healthy", "healthy", "healthy"]) @mock_ec2 def test_create_fleet_api(): conn = boto3.client("ec2", region_name="us-west-1") launch_template_id, _ = get_launch_template(conn) fleet_res = conn.create_fleet( LaunchTemplateConfigs=[ { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, }, ], TargetCapacitySpecification={ "DefaultTargetCapacityType": "spot", "OnDemandTargetCapacity": 1, "SpotTargetCapacity": 2, "TotalTargetCapacity": 3, }, SpotOptions={ "AllocationStrategy": "lowestPrice", "InstanceInterruptionBehavior": "terminate", }, Type="instant", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_res.should.have.key("FleetId") fleet_res["FleetId"].startswith("fleet-").should.be.true fleet_res.should.have.key("Instances") fleet_res["Instances"].should.have.length_of(3) instance_ids = [i["InstanceIds"] for i in fleet_res["Instances"]] for instance_id in instance_ids: instance_id[0].startswith("i-").should.be.true instance_types = [i["InstanceType"] for i in fleet_res["Instances"]] instance_types.should.equal(["t2.micro", "t2.micro", "t2.micro"]) lifecycle = [i["Lifecycle"] for i in fleet_res["Instances"]] lifecycle.should.contain("spot") lifecycle.should.contain("on-demand") @mock_ec2 def test_create_fleet_api_response(): conn = boto3.client("ec2", region_name="us-west-2") subnet_id = get_subnet_id(conn) launch_template_id, _ = get_launch_template(conn) lt_config = { "LaunchTemplateSpecification": { "LaunchTemplateId": launch_template_id, "Version": "1", }, "Overrides": [ { "AvailabilityZone": "us-west-1a", "InstanceRequirements": { "AcceleratorCount": { "Max": 10, "Min": 1, }, "AcceleratorManufacturers": ["nvidia"], "AcceleratorNames": ["t4"], "AcceleratorTotalMemoryMiB": { "Max": 20972, "Min": 1, }, "AcceleratorTypes": ["gpu"], "BareMetal": "included", "BaselineEbsBandwidthMbps": { "Max": 10000, "Min": 125, }, "BurstablePerformance": "included", "CpuManufacturers": ["amd", "intel"], "ExcludedInstanceTypes": ["m5.8xlarge"], "InstanceGenerations": ["current"], "LocalStorage": "included", "LocalStorageTypes": ["ssd"], "MemoryGiBPerVCpu": { "Min": 1, "Max": 160, }, "MemoryMiB": { "Min": 2048, "Max": 40960, }, "NetworkInterfaceCount": { "Max": 1, "Min": 1, }, "OnDemandMaxPricePercentageOverLowestPrice": 99999, "RequireHibernateSupport": True, "SpotMaxPricePercentageOverLowestPrice": 99999, "TotalLocalStorageGB": { "Min": 100, "Max": 10000, }, "VCpuCount": { "Min": 2, "Max": 160, }, }, "MaxPrice": "0.5", "Priority": 2, "SubnetId": subnet_id, "WeightedCapacity": 1, }, ], } fleet_res = conn.create_fleet( ExcessCapacityTerminationPolicy="no-termination", LaunchTemplateConfigs=[lt_config], TargetCapacitySpecification={ "DefaultTargetCapacityType": "on-demand", "OnDemandTargetCapacity": 10, "SpotTargetCapacity": 10, "TotalTargetCapacity": 30, }, SpotOptions={ "AllocationStrategy": "lowest-price", "InstanceInterruptionBehavior": "terminate", "InstancePoolsToUseCount": 1, "MaintenanceStrategies": { "CapacityRebalance": { "ReplacementStrategy": "launch-before-terminate", "TerminationDelay": 120, }, }, "MaxTotalPrice": "50", "MinTargetCapacity": 1, "SingleAvailabilityZone": True, "SingleInstanceType": True, }, OnDemandOptions={ "AllocationStrategy": "lowest-price", "MaxTotalPrice": "50", "MinTargetCapacity": 1, "SingleAvailabilityZone": True, "SingleInstanceType": True, }, ReplaceUnhealthyInstances=True, TerminateInstancesWithExpiration=True, Type="maintain", ValidFrom="2020-01-01T00:00:00Z", ValidUntil="2020-12-31T00:00:00Z", ) fleet_id = fleet_res["FleetId"] fleet_res = conn.describe_fleets(FleetIds=[fleet_id])["Fleets"] fleet_res.should.have.length_of(1) fleet_res[0].should.have.key("FleetId").equals(fleet_id) fleet_res[0].should.have.key("ExcessCapacityTerminationPolicy").equals( "no-termination" ) fleet_res[0].should.have.key("LaunchTemplateConfigs").equals([lt_config]) fleet_res[0].should.have.key("TargetCapacitySpecification").equals( { "DefaultTargetCapacityType": "on-demand", "OnDemandTargetCapacity": 10, "SpotTargetCapacity": 10, "TotalTargetCapacity": 30, } ) fleet_res[0].should.have.key("SpotOptions").equals( { "AllocationStrategy": "lowest-price", "InstanceInterruptionBehavior": "terminate", "InstancePoolsToUseCount": 1, "MaintenanceStrategies": { "CapacityRebalance": { "ReplacementStrategy": "launch-before-terminate", "TerminationDelay": 120, }, }, "MaxTotalPrice": "50", "MinTargetCapacity": 1, "SingleAvailabilityZone": True, "SingleInstanceType": True, } ) fleet_res[0].should.have.key("OnDemandOptions").equals( { "AllocationStrategy": "lowest-price", "MaxTotalPrice": "50", "MinTargetCapacity": 1, "SingleAvailabilityZone": True, "SingleInstanceType": True, } ) fleet_res[0].should.have.key("ReplaceUnhealthyInstances").equals(True) fleet_res[0].should.have.key("TerminateInstancesWithExpiration").equals(True) fleet_res[0].should.have.key("Type").equals("maintain") fleet_res[0].should.have.key("ValidFrom") fleet_res[0]["ValidFrom"].isoformat().should.equal("2020-01-01T00:00:00+00:00") fleet_res[0].should.have.key("ValidUntil") fleet_res[0]["ValidUntil"].isoformat().should.equal("2020-12-31T00:00:00+00:00")