423 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import pytest
 | |
| import datetime
 | |
| 
 | |
| import boto3
 | |
| from botocore.exceptions import ClientError
 | |
| import pytz
 | |
| 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=pytz.utc)
 | |
|     end_dt = datetime.datetime(2013, 1, 2).replace(tzinfo=pytz.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 RequestSpotInstance 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 CancelSpotInstance 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)
 |