Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add wait_for_fulfillment to spot_fleet_request #1241

Merged
merged 2 commits into from
Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions aws/resource_aws_spot_fleet_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
Delete: resourceAwsSpotFleetRequestDelete,
Update: resourceAwsSpotFleetRequestUpdate,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
},

SchemaVersion: 1,
MigrateState: resourceAwsSpotFleetRequestMigrateState,

Expand All @@ -37,6 +41,12 @@ func resourceAwsSpotFleetRequest() *schema.Resource {
ForceNew: true,
Default: false,
},
"wait_for_fulfillment": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
},
// http://docs.aws.amazon.com/sdk-for-go/api/service/ec2.html#type-SpotFleetLaunchSpecification
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_SpotFleetLaunchSpecification.html
"launch_specification": {
Expand Down Expand Up @@ -623,6 +633,24 @@ func resourceAwsSpotFleetRequestCreate(d *schema.ResourceData, meta interface{})
return err
}

if d.Get("wait_for_fulfillment").(bool) {
log.Println("[INFO] Waiting for Spot Fleet Request to be fulfilled")
spotStateConf := &resource.StateChangeConf{
Pending: []string{"pending_fulfillment"},
Target: []string{"fulfilled"},
Refresh: resourceAwsSpotFleetRequestFulfillmentRefreshFunc(d, meta),
Timeout: 10 * time.Minute,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customizable timeout is currently ignored here - do you mind changing it to d.Timeout(schema.TimeoutCreate)? 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = spotStateConf.WaitForState()

if err != nil {
return err
}
}

return resourceAwsSpotFleetRequestRead(d, meta)
}

Expand Down Expand Up @@ -653,6 +681,33 @@ func resourceAwsSpotFleetRequestStateRefreshFunc(d *schema.ResourceData, meta in
}
}

func resourceAwsSpotFleetRequestFulfillmentRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nitpick but I feel the interface of this refresh function could be less greedy, i.e. it doesn't need all of the resource data nor all metadata. How about reducing it to something like this?

func resourceAwsSpotFleetRequestFulfillmentRefreshFunc(id string, conn *ec2.EC2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me. I just copied the whole function from above. Also I already thought about merging these two somehow since they look almost the same.

return func() (interface{}, string, error) {
conn := meta.(*AWSClient).ec2conn
req := &ec2.DescribeSpotFleetRequestsInput{
SpotFleetRequestIds: []*string{aws.String(d.Id())},
}
resp, err := conn.DescribeSpotFleetRequests(req)

if err != nil {
log.Printf("Error on retrieving Spot Fleet Request when waiting: %s", err)
return nil, "", nil
}

if resp == nil {
return nil, "", nil
}

if len(resp.SpotFleetRequestConfigs) == 0 {
return nil, "", nil
}

spotFleetRequest := resp.SpotFleetRequestConfigs[0]

return spotFleetRequest, *spotFleetRequest.ActivityStatus, nil
}
}

func resourceAwsSpotFleetRequestRead(d *schema.ResourceData, meta interface{}) error {
// http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSpotFleetRequests.html
conn := meta.(*AWSClient).ec2conn
Expand Down
90 changes: 39 additions & 51 deletions aws/resource_aws_spot_fleet_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestAccAWSSpotFleetRequest_associatePublicIpAddress(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.#", "1"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.2633484960.associate_public_ip_address", "true"),
"aws_spot_fleet_request.foo", "launch_specification.24370212.associate_public_ip_address", "true"),
),
},
},
Expand Down Expand Up @@ -127,9 +127,9 @@ func TestAccAWSSpotFleetRequest_lowestPriceAzInGivenList(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.#", "2"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.335709043.availability_zone", "us-west-2a"),
"aws_spot_fleet_request.foo", "launch_specification.1991689378.availability_zone", "us-west-2a"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.1671188867.availability_zone", "us-west-2b"),
"aws_spot_fleet_request.foo", "launch_specification.19404370.availability_zone", "us-west-2b"),
),
},
},
Expand Down Expand Up @@ -181,9 +181,9 @@ func TestAccAWSSpotFleetRequest_multipleInstanceTypesInSameAz(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.#", "2"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.335709043.instance_type", "m1.small"),
"aws_spot_fleet_request.foo", "launch_specification.1991689378.instance_type", "m1.small"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.335709043.availability_zone", "us-west-2a"),
"aws_spot_fleet_request.foo", "launch_specification.1991689378.availability_zone", "us-west-2a"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.590403189.instance_type", "m3.large"),
resource.TestCheckResourceAttr(
Expand Down Expand Up @@ -237,17 +237,17 @@ func TestAccAWSSpotFleetRequest_overriddingSpotPrice(t *testing.T) {
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "spot_request_state", "active"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "spot_price", "0.005"),
"aws_spot_fleet_request.foo", "spot_price", "0.035"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.#", "2"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.4143232216.spot_price", "0.01"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.4143232216.instance_type", "m3.large"),
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.335709043.spot_price", ""), //there will not be a value here since it's not overriding
"aws_spot_fleet_request.foo", "launch_specification.1991689378.spot_price", ""), //there will not be a value here since it's not overriding
resource.TestCheckResourceAttr(
"aws_spot_fleet_request.foo", "launch_specification.335709043.instance_type", "m1.small"),
"aws_spot_fleet_request.foo", "launch_specification.1991689378.instance_type", "m1.small"),
),
},
},
Expand Down Expand Up @@ -500,33 +500,10 @@ resource "aws_key_pair" "debugging" {
public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com"
}

resource "aws_iam_policy" "test-policy" {
name = "test-policy-%d"
path = "/"
description = "Spot Fleet Request ACCTest Policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"ec2:DescribeImages",
"ec2:DescribeSubnets",
"ec2:RequestSpotInstances",
"ec2:TerminateInstances",
"ec2:DescribeInstanceStatus",
"iam:PassRole"
],
"Resource": ["*"]
}]
}
EOF
}

resource "aws_iam_policy_attachment" "test-attach" {
name = "test-attachment-%d"
roles = ["${aws_iam_role.test-role.name}"]
policy_arn = "${aws_iam_policy.test-policy.arn}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole"
}

resource "aws_iam_role" "test-role" {
Expand All @@ -553,17 +530,19 @@ EOF

resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "${aws_iam_role.test-role.arn}"
spot_price = "0.005"
spot_price = "0.027"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
associate_public_ip_address = true
}
}`, rName, rInt, rInt, rName)
depends_on = ["aws_iam_policy_attachment.test-attach"]
}`, rName, rInt, rName)
}

func testAccAWSSpotFleetRequestConfig(rName string, rInt int) string {
Expand Down Expand Up @@ -630,9 +609,10 @@ resource "aws_spot_fleet_request" "foo" {
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
Expand Down Expand Up @@ -704,9 +684,10 @@ resource "aws_spot_fleet_request" "foo" {
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
Expand Down Expand Up @@ -778,17 +759,18 @@ resource "aws_spot_fleet_request" "foo" {
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
availability_zone = "us-west-2a"
availability_zone = "us-west-2a"
}
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
availability_zone = "us-west-2b"
availability_zone = "us-west-2b"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
}
Expand Down Expand Up @@ -871,21 +853,22 @@ resource "aws_subnet" "bar" {

resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "${aws_iam_role.test-role.arn}"
spot_price = "0.005"
spot_price = "0.025"
target_capacity = 4
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m3.large"
ami = "ami-d0f506b0"
key_name = "${aws_key_pair.debugging.key_name}"
subnet_id = "${aws_subnet.foo.id}"
subnet_id = "${aws_subnet.foo.id}"
}
launch_specification {
instance_type = "m3.large"
ami = "ami-d0f506b0"
key_name = "${aws_key_pair.debugging.key_name}"
subnet_id = "${aws_subnet.bar.id}"
subnet_id = "${aws_subnet.bar.id}"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
}
Expand Down Expand Up @@ -952,13 +935,14 @@ EOF

resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "${aws_iam_role.test-role.arn}"
spot_price = "0.005"
spot_price = "0.025"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
availability_zone = "us-west-2a"
}
Expand Down Expand Up @@ -1043,21 +1027,22 @@ resource "aws_subnet" "foo" {

resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "${aws_iam_role.test-role.arn}"
spot_price = "0.005"
spot_price = "0.035"
target_capacity = 4
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m3.large"
ami = "ami-d0f506b0"
key_name = "${aws_key_pair.debugging.key_name}"
subnet_id = "${aws_subnet.foo.id}"
subnet_id = "${aws_subnet.foo.id}"
}
launch_specification {
instance_type = "r3.large"
ami = "ami-d0f506b0"
key_name = "${aws_key_pair.debugging.key_name}"
subnet_id = "${aws_subnet.foo.id}"
subnet_id = "${aws_subnet.foo.id}"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
}
Expand Down Expand Up @@ -1124,13 +1109,14 @@ EOF

resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "${aws_iam_role.test-role.arn}"
spot_price = "0.005"
spot_price = "0.035"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
ami = "ami-516b9131"
key_name = "${aws_key_pair.debugging.key_name}"
availability_zone = "us-west-2a"
}
Expand Down Expand Up @@ -1211,6 +1197,7 @@ resource "aws_spot_fleet_request" "foo" {
valid_until = "2019-11-04T20:44:20Z"
allocation_strategy = "diversified"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
Expand Down Expand Up @@ -1298,6 +1285,7 @@ resource "aws_spot_fleet_request" "foo" {
target_capacity = 10
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
wait_for_fulfillment = true
launch_specification {
instance_type = "m3.large"
ami = "ami-d06a90b0"
Expand Down
9 changes: 9 additions & 0 deletions website/docs/r/spot_fleet_request.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ across different markets and instance types.
[reference documentation](http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_SpotFleetLaunchSpecification.html). Any normal [`aws_instance`](instance.html) parameter that corresponds to those inputs may be used.

* `spot_price` - (Required) The bid price per unit hour.
* `wait_for_fulfillment` - (Optional; Default: false) If set, Terraform will
wait for the Spot Request to be fulfilled, and will throw an error if the
timeout of 10m is reached.
* `target_capacity` - The number of units to request. You can choose to set the
target capacity in terms of instances or a performance characteristic that is
important to your application workload, such as vCPUs, memory, or I/O.
Expand All @@ -110,6 +113,12 @@ lowestPrice.
(for example, YYYY-MM-DDTHH:MM:SSZ). At this point, no new Spot instance
requests are placed or enabled to fulfill the request. Defaults to 24 hours.

### Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions:

* `create` - (Defaults to 10 mins) Used when requesting the spot instance (only valid if `wait_for_fulfillment = true`)

## Attributes Reference

The following attributes are exported:
Expand Down