moto/tests/test_ec2/test_spot_instances.py
2023-02-14 12:43:28 -01:00

422 lines
15 KiB
Python

import pytest
import datetime
import boto3
from botocore.exceptions import ClientError
import sure # noqa # pylint: disable=unused-import
from moto import mock_ec2, settings
from moto.core.utils import iso_8601_datetime_with_milliseconds
from tests import EXAMPLE_AMI_ID
from uuid import uuid4
from unittest import SkipTest
@mock_ec2
def test_request_spot_instances():
conn = boto3.client("ec2", "us-east-1")
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"]
sec_name_1 = str(uuid4())
sec_name_2 = str(uuid4())
conn.create_security_group(GroupName=sec_name_1, Description="description")
conn.create_security_group(GroupName=sec_name_2, Description="description")
start_dt = datetime.datetime(2013, 1, 1).replace(tzinfo=datetime.timezone.utc)
end_dt = datetime.datetime(2013, 1, 2).replace(tzinfo=datetime.timezone.utc)
start = iso_8601_datetime_with_milliseconds(start_dt)
end = iso_8601_datetime_with_milliseconds(end_dt)
with pytest.raises(ClientError) as ex:
request = conn.request_spot_instances(
SpotPrice="0.5",
InstanceCount=1,
Type="one-time",
ValidFrom=start,
ValidUntil=end,
LaunchGroup="the-group",
AvailabilityZoneGroup="my-group",
LaunchSpecification={
"ImageId": EXAMPLE_AMI_ID,
"KeyName": "test",
"SecurityGroups": [sec_name_1, sec_name_2],
"UserData": "some test data",
"InstanceType": "m1.small",
"Placement": {"AvailabilityZone": "us-east-1c"},
"KernelId": "test-kernel",
"RamdiskId": "test-ramdisk",
"Monitoring": {"Enabled": True},
"SubnetId": subnet_id,
},
DryRun=True,
)
ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
ex.value.response["Error"]["Message"].should.equal(
"An error occurred (DryRunOperation) when calling the RequestSpotInstances operation: Request would have succeeded, but DryRun flag is set"
)
request = conn.request_spot_instances(
SpotPrice="0.5",
InstanceCount=1,
Type="one-time",
ValidFrom=start,
ValidUntil=end,
LaunchGroup="the-group",
AvailabilityZoneGroup="my-group",
LaunchSpecification={
"ImageId": EXAMPLE_AMI_ID,
"KeyName": "test",
"SecurityGroups": [sec_name_1, sec_name_2],
"UserData": "some test data",
"InstanceType": "m1.small",
"Placement": {"AvailabilityZone": "us-east-1c"},
"KernelId": "test-kernel",
"RamdiskId": "test-ramdisk",
"Monitoring": {"Enabled": True},
"SubnetId": subnet_id,
},
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
all_requests = conn.describe_spot_instance_requests()["SpotInstanceRequests"]
requests = [r for r in all_requests if r["SpotInstanceRequestId"] == request_id]
requests.should.have.length_of(1)
request = requests[0]
request["State"].should.equal("active")
request["SpotPrice"].should.equal("0.500000")
request["Type"].should.equal("one-time")
request["ValidFrom"].should.equal(start_dt)
request["ValidUntil"].should.equal(end_dt)
request["LaunchGroup"].should.equal("the-group")
request["AvailabilityZoneGroup"].should.equal("my-group")
launch_spec = request["LaunchSpecification"]
security_group_names = [
group["GroupName"] for group in launch_spec["SecurityGroups"]
]
set(security_group_names).should.equal(set([sec_name_1, sec_name_2]))
launch_spec["ImageId"].should.equal(EXAMPLE_AMI_ID)
launch_spec["KeyName"].should.equal("test")
launch_spec["InstanceType"].should.equal("m1.small")
launch_spec["KernelId"].should.equal("test-kernel")
launch_spec["RamdiskId"].should.equal("test-ramdisk")
launch_spec["SubnetId"].should.equal(subnet_id)
@mock_ec2
def test_request_spot_instances_default_arguments():
"""
Test that moto set the correct default arguments
"""
conn = boto3.client("ec2", "us-east-1")
request = conn.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
all_requests = conn.describe_spot_instance_requests()["SpotInstanceRequests"]
requests = [r for r in all_requests if r["SpotInstanceRequestId"] == request_id]
requests.should.have.length_of(1)
request = requests[0]
request["State"].should.equal("active")
request["SpotPrice"].should.equal("0.500000")
request["Type"].should.equal("one-time")
request.shouldnt.contain("ValidFrom")
request.shouldnt.contain("ValidUntil")
request.shouldnt.contain("LaunchGroup")
request.shouldnt.contain("AvailabilityZoneGroup")
request.should.have.key("InstanceInterruptionBehavior").equals("terminate")
launch_spec = request["LaunchSpecification"]
security_group_names = [
group["GroupName"] for group in launch_spec["SecurityGroups"]
]
security_group_names.should.equal(["default"])
launch_spec["ImageId"].should.equal(EXAMPLE_AMI_ID)
request.shouldnt.contain("KeyName")
launch_spec["InstanceType"].should.equal("m1.small")
request.shouldnt.contain("KernelId")
request.shouldnt.contain("RamdiskId")
request.shouldnt.contain("SubnetId")
@mock_ec2
def test_cancel_spot_instance_request():
client = boto3.client("ec2", region_name="us-west-1")
rsi = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
spot_id = rsi["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
requests = client.describe_spot_instance_requests(SpotInstanceRequestIds=[spot_id])[
"SpotInstanceRequests"
]
requests.should.have.length_of(1)
request = requests[0]
request.should.have.key("CreateTime")
request.should.have.key("Type").equal("one-time")
request.should.have.key("SpotInstanceRequestId")
request.should.have.key("SpotPrice").equal("0.500000")
request["LaunchSpecification"]["ImageId"].should.equal(EXAMPLE_AMI_ID)
with pytest.raises(ClientError) as ex:
client.cancel_spot_instance_requests(
SpotInstanceRequestIds=[request["SpotInstanceRequestId"]], DryRun=True
)
ex.value.response["Error"]["Code"].should.equal("DryRunOperation")
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(412)
ex.value.response["Error"]["Message"].should.equal(
"An error occurred (DryRunOperation) when calling the CancelSpotInstanceRequests operation: Request would have succeeded, but DryRun flag is set"
)
client.cancel_spot_instance_requests(
SpotInstanceRequestIds=[request["SpotInstanceRequestId"]]
)
requests = client.describe_spot_instance_requests(SpotInstanceRequestIds=[spot_id])[
"SpotInstanceRequests"
]
requests.should.have.length_of(0)
@mock_ec2
def test_request_spot_instances_fulfilled():
"""
Test that moto correctly fullfills a spot instance request
"""
client = boto3.client("ec2", region_name="us-east-1")
request = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
requests = client.describe_spot_instance_requests(
SpotInstanceRequestIds=[request_id]
)["SpotInstanceRequests"]
requests.should.have.length_of(1)
request = requests[0]
request["State"].should.equal("active")
@mock_ec2
def test_tag_spot_instance_request():
"""
Test that moto correctly tags a spot instance request
"""
client = boto3.client("ec2", region_name="us-west-1")
request = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
client.create_tags(
Resources=[request_id],
Tags=[{"Key": "tag1", "Value": "value1"}, {"Key": "tag2", "Value": "value2"}],
)
requests = client.describe_spot_instance_requests(
SpotInstanceRequestIds=[request_id]
)["SpotInstanceRequests"]
requests.should.have.length_of(1)
request = requests[0]
request["Tags"].should.have.length_of(2)
request["Tags"].should.contain({"Key": "tag1", "Value": "value1"})
request["Tags"].should.contain({"Key": "tag2", "Value": "value2"})
@mock_ec2
def test_get_all_spot_instance_requests_filtering():
"""
Test that moto correctly filters spot instance requests
"""
client = boto3.client("ec2", region_name="us-west-1")
request1 = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request1_id = request1["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
request2 = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request2_id = request2["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
tag_value1 = str(uuid4())
client.create_tags(
Resources=[request1_id],
Tags=[{"Key": "tag1", "Value": tag_value1}, {"Key": "tag2", "Value": "value2"}],
)
client.create_tags(
Resources=[request2_id],
Tags=[{"Key": "tag1", "Value": tag_value1}, {"Key": "tag2", "Value": "wrong"}],
)
requests = client.describe_spot_instance_requests(
Filters=[{"Name": "state", "Values": ["failed"]}]
)["SpotInstanceRequests"]
r_ids = [r["SpotInstanceRequestId"] for r in requests]
r_ids.shouldnt.contain(request1_id)
r_ids.shouldnt.contain(request2_id)
requests = client.describe_spot_instance_requests(
Filters=[{"Name": "state", "Values": ["active"]}]
)["SpotInstanceRequests"]
r_ids = [r["SpotInstanceRequestId"] for r in requests]
r_ids.should.contain(request1_id)
r_ids.should.contain(request2_id)
requests = client.describe_spot_instance_requests(
Filters=[{"Name": "tag:tag1", "Values": [tag_value1]}]
)["SpotInstanceRequests"]
requests.should.have.length_of(2)
requests = client.describe_spot_instance_requests(
Filters=[
{"Name": "tag:tag1", "Values": [tag_value1]},
{"Name": "tag:tag2", "Values": ["value2"]},
]
)["SpotInstanceRequests"]
requests.should.have.length_of(1)
@mock_ec2
@pytest.mark.filterwarnings("ignore")
def test_request_spot_instances_instance_lifecycle():
if settings.TEST_SERVER_MODE:
# Currently no easy way to check which instance was created by request_spot_instance
# And we can't just pick the first instance in ServerMode and expect it to be the right one
raise SkipTest("ServerMode is not guaranteed to be empty")
client = boto3.client("ec2", region_name="us-east-1")
client.request_spot_instances(SpotPrice="0.5")
response = client.describe_instances()
instance = response["Reservations"][0]["Instances"][0]
instance["InstanceLifecycle"].should.equal("spot")
@mock_ec2
@pytest.mark.filterwarnings("ignore")
def test_request_spot_instances_with_tags():
client = boto3.client("ec2", region_name="us-east-1")
request = client.request_spot_instances(
SpotPrice="0.5",
TagSpecifications=[
{
"ResourceType": "spot-instances-request",
"Tags": [{"Key": "k", "Value": "v"}],
}
],
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
request = client.describe_spot_instance_requests(
SpotInstanceRequestIds=[request_id]
)["SpotInstanceRequests"][0]
request.should.have.key("Tags").equals([{"Key": "k", "Value": "v"}])
@mock_ec2
def test_launch_spot_instance_instance_lifecycle():
client = boto3.client("ec2", region_name="us-east-1")
kwargs = {
"KeyName": "foobar",
"ImageId": EXAMPLE_AMI_ID,
"MinCount": 1,
"MaxCount": 1,
"InstanceType": "c4.2xlarge",
"TagSpecifications": [
{"ResourceType": "instance", "Tags": [{"Key": "key", "Value": "val"}]},
],
"InstanceMarketOptions": {"MarketType": "spot"},
}
instance = client.run_instances(**kwargs)["Instances"][0]
instance_id = instance["InstanceId"]
response = client.describe_instances(InstanceIds=[instance_id])
instance = response["Reservations"][0]["Instances"][0]
instance["InstanceLifecycle"].should.equal("spot")
@mock_ec2
def test_launch_instance_instance_lifecycle():
client = boto3.client("ec2", region_name="us-east-1")
kwargs = {
"KeyName": "foobar",
"ImageId": EXAMPLE_AMI_ID,
"MinCount": 1,
"MaxCount": 1,
"InstanceType": "c4.2xlarge",
"TagSpecifications": [
{"ResourceType": "instance", "Tags": [{"Key": "key", "Value": "val"}]},
],
}
instance = client.run_instances(**kwargs)["Instances"][0]
instance_id = instance["InstanceId"]
response = client.describe_instances(InstanceIds=[instance_id])
instance = response["Reservations"][0]["Instances"][0]
instance.get("InstanceLifecycle").should.equal(None)
@mock_ec2
def test_spot_price_history():
client = boto3.client("ec2", region_name="us-east-1")
# test filter
response = client.describe_spot_price_history(
Filters=[
{"Name": "availability-zone", "Values": ["us-east-1a"]},
{"Name": "instance-type", "Values": ["t3a.micro"]},
]
)
price = response["SpotPriceHistory"][0]
price["InstanceType"].should.equal("t3a.micro")
price["AvailabilityZone"].should.equal("us-east-1a")
# test instance types
i_types = ["t3a.micro", "t3.micro"]
response = client.describe_spot_price_history(InstanceTypes=i_types)
price = response["SpotPriceHistory"][0]
assert price["InstanceType"] in i_types
@mock_ec2
def test_request_spot_instances__instance_should_exist():
client = boto3.client("ec2", region_name="us-east-1")
request = client.request_spot_instances(
SpotPrice="0.5", LaunchSpecification={"ImageId": EXAMPLE_AMI_ID}
)
request_id = request["SpotInstanceRequests"][0]["SpotInstanceRequestId"]
request = client.describe_spot_instance_requests(
SpotInstanceRequestIds=[request_id]
)["SpotInstanceRequests"][0]
request.should.have.key("InstanceId")
instance_id = request["InstanceId"]
response = client.describe_instances(InstanceIds=[instance_id])
instance = response["Reservations"][0]["Instances"][0]
instance.should.have.key("InstanceId").equals(instance_id)
instance.should.have.key("ImageId").equals(EXAMPLE_AMI_ID)