diff --git a/moto/ec2/models.py b/moto/ec2/models.py index c2fa8480c..d8345da81 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -526,6 +526,7 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel): super(Instance, self).__init__() self.ec2_backend = ec2_backend self.id = random_instance_id() + self.lifecycle = kwargs.get("lifecycle", "") self.image_id = image_id self._state = InstanceState("running", 16) self._reason = "" @@ -1001,6 +1002,8 @@ class InstanceBackend(object): ) else: new_instance.setup_defaults() + if kwargs.get("instance_market_options"): + new_instance.lifecycle = "spot" # Tag all created volumes. for _, device in new_instance.get_block_device_mapping: volumes = self.describe_volumes(volume_ids=[device.volume_id]) @@ -5172,6 +5175,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource): security_group_ids=self.launch_specification.groups, spot_fleet_id=self.spot_fleet_id, tags=self.tags, + lifecycle="spot", ) instance = reservation.instances[0] return instance diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index b92b2ea1b..82098a04a 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -62,6 +62,10 @@ class InstanceResponse(BaseResponse): "associate_public_ip": self._get_param("AssociatePublicIpAddress"), "tags": self._parse_tag_specification("TagSpecification"), "ebs_optimized": self._get_param("EbsOptimized") or False, + "instance_market_options": self._get_param( + "InstanceMarketOptions.MarketType" + ) + or {}, "instance_initiated_shutdown_behavior": self._get_param( "InstanceInitiatedShutdownBehavior" ), @@ -382,6 +386,7 @@ EC2_RUN_INSTANCES = ( {{ instance.ami_launch_index }} {{ instance.instance_type }} {{ instance.launch_time }} + {{ instance.lifecycle }} {{ instance.placement}} @@ -533,6 +538,7 @@ EC2_DESCRIBE_INSTANCES = ( {{ instance.instance_type }} {{ instance.launch_time }} + {{ instance.lifecycle }} {{ instance.placement }} diff --git a/moto/packages/boto/ec2/instance.py b/moto/packages/boto/ec2/instance.py index 3ba81ee95..38da9b9f6 100644 --- a/moto/packages/boto/ec2/instance.py +++ b/moto/packages/boto/ec2/instance.py @@ -177,6 +177,7 @@ class Instance(TaggedEC2Object): self.monitoring_state = None self.spot_instance_request_id = None self.subnet_id = None + self.lifecycle = None self.vpc_id = None self.private_ip_address = None self.ip_address = None diff --git a/moto/packages/boto/ec2/launchspecification.py b/moto/packages/boto/ec2/launchspecification.py index df6c99fc5..a667063bb 100644 --- a/moto/packages/boto/ec2/launchspecification.py +++ b/moto/packages/boto/ec2/launchspecification.py @@ -39,6 +39,7 @@ class LaunchSpecification(EC2Object): self.ramdisk = None self.monitored = False self.subnet_id = None + self.lifecycle = None self._in_monitoring_element = False self.block_device_mapping = None self.instance_profile = None diff --git a/tests/test_ec2/test_spot_instances.py b/tests/test_ec2/test_spot_instances.py index 0c226186b..5a80874f1 100644 --- a/tests/test_ec2/test_spot_instances.py +++ b/tests/test_ec2/test_spot_instances.py @@ -257,3 +257,59 @@ def test_request_spot_instances_setting_instance_id(): request = conn.get_all_spot_instance_requests()[0] assert request.state == "active" assert request.instance_id == "i-12345678" + + +@mock_ec2 +def test_request_spot_instances_instance_lifecycle(): + client = boto3.client("ec2", region_name="us-east-1") + request = client.request_spot_instances(SpotPrice="0.5") + + response = client.describe_instances() + + instance = response["Reservations"][0]["Instances"][0] + instance["InstanceLifecycle"].should.equal("spot") + + +@mock_ec2 +def test_launch_spot_instance_instance_lifecycle(): + client = boto3.client("ec2", region_name="us-east-1") + + kwargs = { + "KeyName": "foobar", + "ImageId": "ami-pytest", + "MinCount": 1, + "MaxCount": 1, + "InstanceType": "c4.2xlarge", + "TagSpecifications": [ + {"ResourceType": "instance", "Tags": [{"Key": "key", "Value": "val"}]}, + ], + "InstanceMarketOptions": {"MarketType": "spot"}, + } + + client.run_instances(**kwargs)["Instances"][0] + + response = client.describe_instances() + 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": "ami-pytest", + "MinCount": 1, + "MaxCount": 1, + "InstanceType": "c4.2xlarge", + "TagSpecifications": [ + {"ResourceType": "instance", "Tags": [{"Key": "key", "Value": "val"}]}, + ], + } + + client.run_instances(**kwargs)["Instances"][0] + + response = client.describe_instances() + instance = response["Reservations"][0]["Instances"][0] + instance["InstanceLifecycle"].should.equal("")