provider/aws: Add EBS Volume support for EMR Instance Groups

Adds EBS Volume support and tests for EMR Instnace Groups

```
$ make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSEMRInstanceGroup_ebsBasic'
==> Checking that code complies with gofmt requirements...
go generate $(go list ./... | grep -v /terraform/vendor/)
2017/01/25 10:14:58 Generated command/internal_plugin_list.go
TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSEMRInstanceGroup_ebsBasic -timeout 120m
=== RUN   TestAccAWSEMRInstanceGroup_ebsBasic
--- PASS: TestAccAWSEMRInstanceGroup_ebsBasic (675.14s)
PASS
ok      github.com/hashicorp/terraform/builtin/providers/aws    675.171s
```
This commit is contained in:
Jake Champlin 2017-01-25 10:29:41 -05:00
parent 27e29420e5
commit a60f35e694
No known key found for this signature in database
GPG Key ID: DC31F41958EF4AC2
4 changed files with 186 additions and 20 deletions

View File

@ -22,38 +22,100 @@ func resourceAwsEMRInstanceGroup() *schema.Resource {
Update: resourceAwsEMRInstanceGroupUpdate, Update: resourceAwsEMRInstanceGroupUpdate,
Delete: resourceAwsEMRInstanceGroupDelete, Delete: resourceAwsEMRInstanceGroupDelete,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"cluster_id": &schema.Schema{ "cluster_id": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
}, },
"instance_type": &schema.Schema{ "instance_type": {
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ForceNew: true, ForceNew: true,
}, },
"instance_count": &schema.Schema{ "instance_count": {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Default: 0, Default: 0,
}, },
"running_instance_count": &schema.Schema{ "running_instance_count": {
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Computed: true,
}, },
"status": &schema.Schema{ "status": {
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"name": &schema.Schema{ "name": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"ebs_optimized": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"ebs_config": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"iops": {
Type: schema.TypeInt,
Optional: true,
},
"size": {
Type: schema.TypeInt,
Required: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateAwsEmrEbsVolumeType,
},
"volumes_per_instance": {
Type: schema.TypeInt,
Optional: true,
},
},
},
},
}, },
} }
} }
// Populates an emr.EbsConfiguration struct
func readEmrEBSConfig(d *schema.ResourceData) *emr.EbsConfiguration {
result := &emr.EbsConfiguration{}
if v, ok := d.GetOk("ebs_optimized"); ok {
result.EbsOptimized = aws.Bool(v.(bool))
}
ebsConfigs := make([]*emr.EbsBlockDeviceConfig, 0)
if rawConfig, ok := d.GetOk("ebs_config"); ok {
configList := rawConfig.(*schema.Set).List()
for _, config := range configList {
conf := config.(map[string]interface{})
ebs := &emr.EbsBlockDeviceConfig{}
volumeSpec := &emr.VolumeSpecification{
SizeInGB: aws.Int64(int64(conf["size"].(int))),
VolumeType: aws.String(conf["type"].(string)),
}
if v, ok := conf["iops"].(int); ok && v != 0 {
volumeSpec.Iops = aws.Int64(int64(v))
}
if v, ok := conf["volumes_per_instance"].(int); ok && v != 0 {
ebs.VolumesPerInstance = aws.Int64(int64(v))
}
ebs.VolumeSpecification = volumeSpec
ebsConfigs = append(ebsConfigs, ebs)
}
}
result.EbsBlockDeviceConfigs = ebsConfigs
return result
}
func resourceAwsEMRInstanceGroupCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsEMRInstanceGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).emrconn conn := meta.(*AWSClient).emrconn
@ -62,6 +124,8 @@ func resourceAwsEMRInstanceGroupCreate(d *schema.ResourceData, meta interface{})
instanceCount := d.Get("instance_count").(int) instanceCount := d.Get("instance_count").(int)
groupName := d.Get("name").(string) groupName := d.Get("name").(string)
ebsConfig := readEmrEBSConfig(d)
params := &emr.AddInstanceGroupsInput{ params := &emr.AddInstanceGroupsInput{
InstanceGroups: []*emr.InstanceGroupConfig{ InstanceGroups: []*emr.InstanceGroupConfig{
{ {
@ -69,6 +133,7 @@ func resourceAwsEMRInstanceGroupCreate(d *schema.ResourceData, meta interface{})
InstanceCount: aws.Int64(int64(instanceCount)), InstanceCount: aws.Int64(int64(instanceCount)),
InstanceType: aws.String(instanceType), InstanceType: aws.String(instanceType),
Name: aws.String(groupName), Name: aws.String(groupName),
EbsConfiguration: ebsConfig,
}, },
}, },
JobFlowId: aws.String(clusterId), JobFlowId: aws.String(clusterId),

View File

@ -21,7 +21,7 @@ func TestAccAWSEMRInstanceGroup_basic(t *testing.T) {
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEmrInstanceGroupDestroy, CheckDestroy: testAccCheckAWSEmrInstanceGroupDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ {
Config: testAccAWSEmrInstanceGroupConfig(rInt), Config: testAccAWSEmrInstanceGroupConfig(rInt),
Check: testAccCheckAWSEmrInstanceGroupExists("aws_emr_instance_group.task", &ig), Check: testAccCheckAWSEmrInstanceGroupExists("aws_emr_instance_group.task", &ig),
}, },
@ -29,6 +29,28 @@ func TestAccAWSEMRInstanceGroup_basic(t *testing.T) {
}) })
} }
func TestAccAWSEMRInstanceGroup_ebsBasic(t *testing.T) {
var ig emr.InstanceGroup
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEmrInstanceGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEmrInstanceGroupConfig_ebsBasic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEmrInstanceGroupExists("aws_emr_instance_group.task", &ig),
resource.TestCheckResourceAttr(
"aws_emr_instance_group.task", "ebs_config.#", "1"),
resource.TestCheckResourceAttr(
"aws_emr_instance_group.task", "ebs_optimized", "true"),
),
},
},
})
}
func testAccCheckAWSEmrInstanceGroupDestroy(s *terraform.State) error { func testAccCheckAWSEmrInstanceGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).emrconn conn := testAccProvider.Meta().(*AWSClient).emrconn
@ -85,8 +107,7 @@ func testAccCheckAWSEmrInstanceGroupExists(n string, v *emr.InstanceGroup) resou
} }
} }
func testAccAWSEmrInstanceGroupConfig(r int) string { const testAccAWSEmrInstanceGroupBase = `
return fmt.Sprintf(`
provider "aws" { provider "aws" {
region = "us-west-2" region = "us-west-2"
} }
@ -126,12 +147,6 @@ resource "aws_emr_cluster" "tf-test-cluster" {
depends_on = ["aws_internet_gateway.gw"] depends_on = ["aws_internet_gateway.gw"]
} }
resource "aws_emr_instance_group" "task" {
cluster_id = "${aws_emr_cluster.tf-test-cluster.id}"
instance_count = 1
instance_type = "m3.xlarge"
}
resource "aws_security_group" "allow_all" { resource "aws_security_group" "allow_all" {
name = "allow_all" name = "allow_all"
description = "Allow all inbound traffic" description = "Allow all inbound traffic"
@ -352,5 +367,30 @@ resource "aws_iam_policy" "iam_emr_profile_policy" {
}] }]
} }
EOT EOT
}`, r, r, r, r, r, r) }
`
func testAccAWSEmrInstanceGroupConfig(r int) string {
return fmt.Sprintf(testAccAWSEmrInstanceGroupBase+`
resource "aws_emr_instance_group" "task" {
cluster_id = "${aws_emr_cluster.tf-test-cluster.id}"
instance_count = 1
instance_type = "m3.xlarge"
}
`, r, r, r, r, r, r)
}
func testAccAWSEmrInstanceGroupConfig_ebsBasic(r int) string {
return fmt.Sprintf(testAccAWSEmrInstanceGroupBase+`
resource "aws_emr_instance_group" "task" {
cluster_id = "${aws_emr_cluster.tf-test-cluster.id}"
instance_count = 1
instance_type = "m3.xlarge"
ebs_optimized = true
ebs_config {
"size" = 10,
"type" = "gp2",
}
}
`, r, r, r, r, r, r)
} }

View File

@ -727,3 +727,19 @@ func validateAwsEcsPlacementStrategy(stratType, stratField string) error {
} }
return nil return nil
} }
func validateAwsEmrEbsVolumeType(v interface{}, k string) (ws []string, errors []error) {
validTypes := map[string]struct{}{
"gp2": {},
"io1": {},
"standard": {},
}
value := v.(string)
if _, ok := validTypes[value]; !ok {
errors = append(errors, fmt.Errorf(
"%q must be one of ['gp2', 'io1', 'standard']", k))
}
return
}

View File

@ -1172,3 +1172,48 @@ func TestValidateEcsPlacementStrategy(t *testing.T) {
} }
} }
func TestValidateEmrEbsVolumeType(t *testing.T) {
cases := []struct {
VolType string
ErrCount int
}{
{
VolType: "gp2",
ErrCount: 0,
},
{
VolType: "io1",
ErrCount: 0,
},
{
VolType: "standard",
ErrCount: 0,
},
{
VolType: "stand",
ErrCount: 1,
},
{
VolType: "io",
ErrCount: 1,
},
{
VolType: "gp1",
ErrCount: 1,
},
{
VolType: "fast-disk",
ErrCount: 1,
},
}
for _, tc := range cases {
_, errors := validateAwsEmrEbsVolumeType(tc.VolType, "volume")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %d errors, got %d: %s", tc.ErrCount, len(errors), errors)
}
}
}