Update Application Auto Scaling to support scaling an Amazon EC2 Spot fleet. (#8697)

* provider/aws: Update Application Auto Scaling service model

  - Add support for automatically scaling an Amazon EC2 Spot fleet.

* Remove duplicate policy_type check.

* Test creating a scalable target for a splot fleet request.

* Test creating a scaling policy for a splot fleet request.

* Update resource docs to support scaling an Amazon EC2 Spot fleet.

  - aws_appautoscaling_policy
  - aws_appautoscaling_target

* Remove arn attribute from aws_appautoscaling_target

  - No arn is generated or returned for this resource.

* Remove optional name attribute from aws_appautoscaling_target

  - ScalableTargets do not have a name
  - I think this was copied from aws_appautoscaling_policy

* AWS Application Autoscaling resource documentation tweaks

  - include a target resource in the policy example
  - sort attributes by alpha
  - fixup markdown
  - add spaces to test config
This commit is contained in:
Raymond Fallon 2017-02-02 04:39:22 -05:00 committed by Paul Stack
parent e2603bedec
commit b30ef0f58d
8 changed files with 376 additions and 81 deletions

View File

@ -47,16 +47,16 @@ func resourceAwsAppautoscalingPolicy() *schema.Resource {
Required: true,
},
"scalable_dimension": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs:service:DesiredCount",
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAppautoscalingScalableDimension,
},
"service_namespace": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs",
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAppautoscalingServiceNamespace,
},
"adjustment_type": &schema.Schema{
Type: schema.TypeString,
@ -258,10 +258,6 @@ func getAwsAppautoscalingPutScalingPolicyInput(d *schema.ResourceData) (applicat
params.ServiceNamespace = aws.String(v.(string))
}
if v, ok := d.GetOk("policy_type"); ok {
params.PolicyType = aws.String(v.(string))
}
if v, ok := d.GetOk("scalable_dimension"); ok {
params.ScalableDimension = aws.String(v.(string))
}

View File

@ -39,6 +39,29 @@ func TestAccAWSAppautoScalingPolicy_basic(t *testing.T) {
})
}
func TestAccAWSAppautoScalingPolicy_spotFleetRequest(t *testing.T) {
var policy applicationautoscaling.ScalingPolicy
randPolicyName := fmt.Sprintf("test-appautoscaling-policy-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAppautoscalingPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAppautoscalingPolicySpotFleetRequestConfig(randPolicyName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAppautoscalingPolicyExists("aws_appautoscaling_policy.test", &policy),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.test", "name", randPolicyName),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.test", "service_namespace", "ec2"),
resource.TestCheckResourceAttr("aws_appautoscaling_policy.test", "scalable_dimension", "ec2:spot-fleet-request:TargetCapacity"),
),
},
},
})
}
func testAccCheckAWSAppautoscalingPolicyExists(n string, policy *applicationautoscaling.ScalingPolicy) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
@ -171,3 +194,99 @@ resource "aws_appautoscaling_policy" "foobar_simple" {
}
`, randClusterName, randClusterName, randClusterName, randPolicyName)
}
func testAccAWSAppautoscalingPolicySpotFleetRequestConfig(
randPolicyName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "fleet_role" {
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"spotfleet.amazonaws.com",
"ec2.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "fleet_role_policy" {
role = "${aws_iam_role.fleet_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole"
}
resource "aws_spot_fleet_request" "test" {
iam_fleet_role = "${aws_iam_role.fleet_role.arn}"
spot_price = "0.005"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
launch_specification {
instance_type = "m3.medium"
ami = "ami-d06a90b0"
}
}
resource "aws_iam_role" "autoscale_role" {
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "application-autoscaling.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "autoscale_role_policy_a" {
role = "${aws_iam_role.autoscale_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole"
}
resource "aws_iam_role_policy_attachment" "autoscale_role_policy_b" {
role = "${aws_iam_role.autoscale_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetAutoscaleRole"
}
resource "aws_appautoscaling_target" "test" {
service_namespace = "ec2"
resource_id = "spot-fleet-request/${aws_spot_fleet_request.test.id}"
scalable_dimension = "ec2:spot-fleet-request:TargetCapacity"
role_arn = "${aws_iam_role.autoscale_role.arn}"
min_capacity = 1
max_capacity = 3
}
resource "aws_appautoscaling_policy" "test" {
name = "%s"
service_namespace = "ec2"
resource_id = "spot-fleet-request/${aws_spot_fleet_request.test.id}"
scalable_dimension = "ec2:spot-fleet-request:TargetCapacity"
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
depends_on = ["aws_appautoscaling_target.test"]
}
`, randPolicyName)
}

View File

@ -20,25 +20,6 @@ func resourceAwsAppautoscalingTarget() *schema.Resource {
Delete: resourceAwsAppautoscalingTargetDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
// https://github.com/boto/botocore/blob/9f322b1/botocore/data/autoscaling/2011-01-01/service-2.json#L1862-L1873
value := v.(string)
if len(value) > 255 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 255 characters", k))
}
return
},
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"max_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
@ -60,16 +41,16 @@ func resourceAwsAppautoscalingTarget() *schema.Resource {
ForceNew: true,
},
"scalable_dimension": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs:service:DesiredCount",
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAppautoscalingScalableDimension,
},
"service_namespace": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "ecs",
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateAppautoscalingServiceNamespace,
},
},
}

View File

@ -46,6 +46,27 @@ func TestAccAWSAppautoScalingTarget_basic(t *testing.T) {
})
}
func TestAccAWSAppautoScalingTarget_spotFleetRequest(t *testing.T) {
var target applicationautoscaling.ScalableTarget
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_appautoscaling_target.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAppautoscalingTargetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAppautoscalingTargetSpotFleetRequestConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAppautoscalingTargetExists("aws_appautoscaling_target.test", &target),
resource.TestCheckResourceAttr("aws_appautoscaling_target.test", "service_namespace", "ec2"),
resource.TestCheckResourceAttr("aws_appautoscaling_target.test", "scalable_dimension", "ec2:spot-fleet-request:TargetCapacity"),
),
},
},
})
}
func testAccCheckAWSAppautoscalingTargetDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).appautoscalingconn
@ -174,6 +195,7 @@ EOF
resource "aws_ecs_cluster" "foo" {
name = "%s"
}
resource "aws_ecs_task_definition" "task" {
family = "foobar"
container_definitions = <<EOF
@ -188,6 +210,7 @@ resource "aws_ecs_task_definition" "task" {
]
EOF
}
resource "aws_ecs_service" "service" {
name = "foobar"
cluster = "${aws_ecs_cluster.foo.id}"
@ -197,11 +220,12 @@ resource "aws_ecs_service" "service" {
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
}
resource "aws_appautoscaling_target" "bar" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "${aws_iam_role.autoscale_role.arn}"
role_arn = "${aws_iam_role.autoscale_role.arn}"
min_capacity = 1
max_capacity = 3
}
@ -266,6 +290,7 @@ EOF
resource "aws_ecs_cluster" "foo" {
name = "%s"
}
resource "aws_ecs_task_definition" "task" {
family = "foobar"
container_definitions = <<EOF
@ -280,6 +305,7 @@ resource "aws_ecs_task_definition" "task" {
]
EOF
}
resource "aws_ecs_service" "service" {
name = "foobar"
cluster = "${aws_ecs_cluster.foo.id}"
@ -289,13 +315,90 @@ resource "aws_ecs_service" "service" {
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
}
resource "aws_appautoscaling_target" "bar" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.foo.name}/${aws_ecs_service.service.name}"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "${aws_iam_role.autoscale_role.arn}"
role_arn = "${aws_iam_role.autoscale_role.arn}"
min_capacity = 2
max_capacity = 8
}
`, randClusterName, randClusterName, randClusterName)
}
var testAccAWSAppautoscalingTargetSpotFleetRequestConfig = fmt.Sprintf(`
resource "aws_iam_role" "fleet_role" {
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"spotfleet.amazonaws.com",
"ec2.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "fleet_role_policy" {
role = "${aws_iam_role.fleet_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole"
}
resource "aws_spot_fleet_request" "test" {
iam_fleet_role = "${aws_iam_role.fleet_role.arn}"
spot_price = "0.005"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
terminate_instances_with_expiration = true
launch_specification {
instance_type = "m3.medium"
ami = "ami-d06a90b0"
}
}
resource "aws_iam_role" "autoscale_role" {
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "application-autoscaling.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "autoscale_role_policy_a" {
role = "${aws_iam_role.autoscale_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole"
}
resource "aws_iam_role_policy_attachment" "autoscale_role_policy_b" {
role = "${aws_iam_role.autoscale_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetAutoscaleRole"
}
resource "aws_appautoscaling_target" "test" {
service_namespace = "ec2"
resource_id = "spot-fleet-request/${aws_spot_fleet_request.test.id}"
scalable_dimension = "ec2:spot-fleet-request:TargetCapacity"
role_arn = "${aws_iam_role.autoscale_role.arn}"
min_capacity = 1
max_capacity = 3
}
`)

View File

@ -773,3 +773,29 @@ func validateSfnStateMachineName(v interface{}, k string) (ws []string, errors [
}
return
}
func validateAppautoscalingScalableDimension(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
dimensions := map[string]bool{
"ecs:service:DesiredCount": true,
"ec2:spot-fleet-request:TargetCapacity": true,
}
if !dimensions[value] {
errors = append(errors, fmt.Errorf("%q must be a valid scalable dimension value: %q", k, value))
}
return
}
func validateAppautoscalingServiceNamespace(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
namespaces := map[string]bool{
"ecs": true,
"ec2": true,
}
if !namespaces[value] {
errors = append(errors, fmt.Errorf("%q must be a valid service namespace value: %q", k, value))
}
return
}

View File

@ -1311,5 +1311,78 @@ func TestValidateEmrEbsVolumeType(t *testing.T) {
t.Fatalf("Expected %d errors, got %d: %s", tc.ErrCount, len(errors), errors)
}
}
}
func TestValidateAppautoscalingScalableDimension(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "ecs:service:DesiredCount",
ErrCount: 0,
},
{
Value: "ec2:spot-fleet-request:TargetCapacity",
ErrCount: 0,
},
{
Value: "ec2:service:DesiredCount",
ErrCount: 1,
},
{
Value: "ecs:spot-fleet-request:TargetCapacity",
ErrCount: 1,
},
{
Value: "",
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateAppautoscalingScalableDimension(tc.Value, "scalable_dimension")
if len(errors) != tc.ErrCount {
t.Fatalf("Scalable Dimension validation failed for value %q: %q", tc.Value, errors)
}
}
}
func TestValidateAppautoscalingServiceNamespace(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "ecs",
ErrCount: 0,
},
{
Value: "ec2",
ErrCount: 0,
},
{
Value: "autoscaling",
ErrCount: 1,
},
{
Value: "s3",
ErrCount: 1,
},
{
Value: "es",
ErrCount: 1,
},
{
Value: "",
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateAppautoscalingServiceNamespace(tc.Value, "service_namespace")
if len(errors) != tc.ErrCount {
t.Fatalf("Service Namespace validation failed for value %q: %q", tc.Value, errors)
}
}
}

View File

@ -12,21 +12,30 @@ Provides an Application AutoScaling Policy resource.
## Example Usage
```
resource "aws_appautoscaling_policy" "down" {
name = "scale-down"
service_namespace = "ecs"
resource_id = "service/ecsclustername/servicename"
resource "aws_appautoscaling_target" "ecs_target" {
max_capacity = 4
min_capacity = 1
resource_id = "service/clusterName/serviceName"
role_arn = "${var.ecs_iam_role}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "ecs_policy" {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Maximum"
name = "scale-down"
resource_id = "service/clusterName/serviceName"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = -1
}
depends_on = ["aws_appautoscaling_target.target"]
depends_on = ["aws_appautoscaling_target.ecs_target"]
}
```
@ -34,41 +43,35 @@ resource "aws_appautoscaling_policy" "down" {
The following arguments are supported:
* `name` - (Required) The name of the policy.
* `policy_type` - (Optional) Defaults to "StepScaling" because it is the only option available.
* `resource_id` - (Required) The Resource ID on which you want the Application AutoScaling policy to apply to. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`.
* `scalable_dimension` - (Optional) The scalable dimension of the scalable target that this scaling policy applies to. The scalable dimension contains the service names- pace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service. Defaults to `ecs:service:DesiredCount` since that is the only allowed value.
* `service_namespace` - (Optional) The AWS service namespace of the scalable target that this scaling policy applies to. Defaults to `ecs`, because that is currently the only supported option.
* `adjustment_type` - (Required) Specifies whether the adjustment is an absolute number or a percentage of the current capacity. Valid values are `ChangeInCapacity`, `ExactCapacity`, and `PercentChangeInCapacity`.
* `cooldown` - (Required) The amount of time, in seconds, after a scaling activity completes and before the next scaling activity can start.
* `metric_aggregation_type` - (Required) The aggregation type for the policy's metrics. Valid values are "Minimum", "Maximum", and "Average". Without a value, AWS will treat the aggregation type as "Average".
* `step_adjustments` - (Optional) A set of adjustments that manage scaling. These have the following structure:
* `name` - (Required) The name of the policy.
* `policy_type` - (Optional) Defaults to "StepScaling" because it is the only option available.
* `resource_id` - (Required) The resource type and unique identifier string for the resource associated with the scaling policy. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`. For Amazon EC2 Spot fleet requests, the resource type is `spot-fleet-request`, and the identifier is the Spot fleet request ID; for example, `spot-fleet-request/sfr-73fbd2ce-aa30-494c-8788-1cee4EXAMPLE`.
* `scalable_dimension` - (Required) The scalable dimension of the scalable target. The scalable dimension contains the service namespace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service, or `ec2:spot-fleet-request:TargetCapacity` for the target capacity of an Amazon EC2 Spot fleet request.
* `service_namespace` - (Required) The AWS service namespace of the scalable target. Valid values are `ecs` for Amazon ECS services and `ec2` Amazon EC2 Spot fleet requests.
* `step_adjustment` - (Optional) A set of adjustments that manage scaling. These have the following structure:
```
step_adjustment {
scaling_adjustment = -1
metric_interval_lower_bound = 1.0
metric_interval_upper_bound = 2.0
scaling_adjustment = -1
}
step_adjustment {
scaling_adjustment = 1
metric_interval_lower_bound = 2.0
metric_interval_upper_bound = 3.0
scaling_adjustment = 1
}
```
* `scaling_adjustment` - (Required) The number of members by which to
scale, when the adjustment bounds are breached. A positive value scales
up. A negative value scales down.
* `metric_interval_lower_bound` - (Optional) The lower bound for the
difference between the alarm threshold and the CloudWatch metric.
Without a value, AWS will treat this bound as infinity.
* `metric_interval_upper_bound` - (Optional) The upper bound for the
difference between the alarm threshold and the CloudWatch metric.
Without a value, AWS will treat this bound as infinity. The upper bound
must be greater than the lower bound.
* `metric_interval_lower_bound` - (Optional) The lower bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity.
* `metric_interval_upper_bound` - (Optional) The upper bound for the difference between the alarm threshold and the CloudWatch metric. Without a value, AWS will treat this bound as infinity. The upper bound must be greater than the lower bound.
* `scaling_adjustment` - (Required) The number of members by which to scale, when the adjustment bounds are breached. A positive value scales up. A negative value scales down.
## Attribute Reference
* `adjustment_type` - The scaling policy's adjustment type.
* `arn` - The ARN assigned by AWS to the scaling policy.
* `name` - The scaling policy's name.
* `adjustment_type` - The scaling policy's adjustment type.
* `policy_type` - The scaling policy's type.

View File

@ -12,13 +12,13 @@ Provides an Application AutoScaling ScalableTarget resource.
## Example Usage
```
resource "aws_appautoscaling_target" "tgt" {
service_namespace = "ecs"
resource_id = "service/clusterName/serviceName"
scalable_dimension = "ecs:service:DesiredCount"
role_arn = "${var.ecs_iam_role}"
min_capacity = 1
resource "aws_appautoscaling_target" "ecs_target" {
max_capacity = 4
min_capacity = 1
resource_id = "service/clusterName/serviceName"
role_arn = "${var.ecs_iam_role}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
```
@ -26,15 +26,9 @@ resource "aws_appautoscaling_target" "tgt" {
The following arguments are supported:
* `name` - (Required) The name of the policy.
* `resource_id` - (Required) The Resource ID on which you want the Application AutoScaling policy to apply to. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`.
* `scalable_dimension` - (Optional) The scalable dimension of the scalable target. The scalable dimension contains the service namespace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service. Defaults to `ecs:service:DesiredCount` since that is the only allowed value.
* `service_namespace` - (Optional) The AWS service namespace of the scalable target. Defaults to `ecs`, because that is currently the only supported option.
* `max_capacity` - (Required) The max capacity of the scalable target.
* `min_capacity` - (Required) The min capacity of the scalable target.
* `resource_id` - (Required) The resource type and unique identifier string for the resource associated with the scalable target. For Amazon ECS services, this value is the resource type, followed by the cluster name and service name, such as `service/default/sample-webapp`. For Amazon EC2 Spot fleet requests, the resource type is `spot-fleet-request`, and the identifier is the Spot fleet request ID; for example, `spot-fleet-request/sfr-73fbd2ce-aa30-494c-8788-1cee4EXAMPLE`.
* `role_arn` - (Required) The ARN of the IAM role that allows Application AutoScaling to modify your scalable target on your behalf.
## Attribute Reference
* `arn` - The ARN assigned by AWS to the scaling policy.
* `name` - The scaling policy's name.
* `scalable_dimension` - (Required) The scalable dimension of the scalable target. The scalable dimension contains the service namespace, resource type, and scaling property, such as `ecs:service:DesiredCount` for the desired task count of an Amazon ECS service, or `ec2:spot-fleet-request:TargetCapacity` for the target capacity of an Amazon EC2 Spot fleet request.
* `service_namespace` - (Required) The AWS service namespace of the scalable target. Valid values are `ecs` for Amazon ECS services and `ec2` Amazon EC2 Spot fleet requests.