Skip to content

Commit

Permalink
Merge pull request #31336 from AdamTylerLynch/f-rds_aurora_storage_type
Browse files Browse the repository at this point in the history
Aurora I/O-Optimized (storage_type)
  • Loading branch information
jar-b committed May 11, 2023
2 parents 5ed9cbe + 9d852cf commit 62ebec3
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 20 deletions.
3 changes: 3 additions & 0 deletions .changelog/31336.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_rds_cluster: Add ability to specify Aurora IO Optimized `storage_type`
```
26 changes: 24 additions & 2 deletions internal/service/rds/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/aws/aws-sdk-go/service/rds"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -489,7 +490,7 @@ func ResourceCluster() *schema.Resource {
"storage_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
names.AttrTags: tftags.TagsSchema(),
names.AttrTagsAll: tftags.TagsSchemaComputed(),
Expand All @@ -501,7 +502,28 @@ func ResourceCluster() *schema.Resource {
},
},

CustomizeDiff: verify.SetTagsDiff,
CustomizeDiff: customdiff.Sequence(
verify.SetTagsDiff,
customdiff.ForceNewIf("storage_type", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool {
// Aurora supports mutation of the storage_type parameter, other engines do not
return !strings.HasPrefix(d.Get("engine").(string), "aurora")
}),
func(_ context.Context, diff *schema.ResourceDiff, _ any) error {
if diff.Id() == "" {
return nil
}
// The control plane will always return an empty string if a cluster is created with a storage_type of aurora
old, new := diff.GetChange("storage_type")

if new.(string) == "aurora" && old.(string) == "" {
if err := diff.SetNew("storage_type", ""); err != nil {
return err
}
return nil
}
return nil
},
),
}
}

Expand Down
203 changes: 200 additions & 3 deletions internal/service/rds/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ func TestAccRDSCluster_availabilityZones(t *testing.T) {
})
}

func TestAccRDSCluster_storageType(t *testing.T) {
func TestAccRDSCluster_storageTypeIo1(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}
Expand All @@ -542,7 +542,7 @@ func TestAccRDSCluster_storageType(t *testing.T) {
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_storageType(rName),
Config: testAccClusterConfig_storageTypeIo1(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster),
resource.TestCheckResourceAttr(resourceName, "storage_type", "io1"),
Expand All @@ -552,6 +552,101 @@ func TestAccRDSCluster_storageType(t *testing.T) {
})
}

// For backwards compatibility, the control plane should always return a blank string even if sending "aurora" as the storage type
func TestAccRDSCluster_storageTypeAuroraReturnsBlank(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

ctx := acctest.Context(t)
var dbCluster1 rds.DBCluster
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
storageTypeAurora := "aurora"
storageTypeEmpty := ""
resourceName := "aws_rds_cluster.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_auroraStorageType(rName, storageTypeAurora),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster1),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageTypeEmpty),
),
},
},
})
}

func TestAccRDSCluster_storageTypeAuroraIopt1(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

ctx := acctest.Context(t)
var dbCluster rds.DBCluster
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
storageType := "aurora-iopt1"
resourceName := "aws_rds_cluster.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_auroraStorageType(rName, storageType),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageType),
),
},
},
})
}

func TestAccRDSCluster_storageTypeAuroraUpdateAuroraIopt1(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

ctx := acctest.Context(t)
var dbCluster1, dbCluster2 rds.DBCluster
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
storageTypeEmpty := ""
storageTypeAuroraIOPT1 := "aurora-iopt1"
resourceName := "aws_rds_cluster.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_auroraStorageTypeNotDefined(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster1),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageTypeEmpty),
),
},
{
Config: testAccClusterConfig_auroraStorageType(rName, storageTypeAuroraIOPT1),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster2),
testAccCheckClusterNotRecreated(&dbCluster1, &dbCluster2),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageTypeAuroraIOPT1),
),
},
},
})
}

func TestAccRDSCluster_allocatedStorage(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
Expand Down Expand Up @@ -579,6 +674,45 @@ func TestAccRDSCluster_allocatedStorage(t *testing.T) {
})
}

// Verify storage_type from aurora-iopt1 to aurora
func TestAccRDSCluster_storageTypeAuroraIopt1UpdateAurora(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

ctx := acctest.Context(t)
var dbCluster1, dbCluster2 rds.DBCluster
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
storageTypeAuroraIOPT1 := "aurora-iopt1"
storageTypeAurora := "aurora"
storageTypeEmpty := ""
resourceName := "aws_rds_cluster.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckClusterDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccClusterConfig_auroraStorageType(rName, storageTypeAuroraIOPT1),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster1),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageTypeAuroraIOPT1),
),
},
{
Config: testAccClusterConfig_auroraStorageType(rName, storageTypeAurora),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterExists(ctx, resourceName, &dbCluster2),
testAccCheckClusterNotRecreated(&dbCluster1, &dbCluster2),
resource.TestCheckResourceAttr(resourceName, "storage_type", storageTypeEmpty),
),
},
},
})
}

func TestAccRDSCluster_iops(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
Expand Down Expand Up @@ -2498,6 +2632,16 @@ func testAccCheckClusterRecreated(i, j *rds.DBCluster) resource.TestCheckFunc {
}
}

func testAccCheckClusterNotRecreated(i, j *rds.DBCluster) resource.TestCheckFunc {
return func(s *terraform.State) error {
if !aws.TimeValue(i.ClusterCreateTime).Equal(aws.TimeValue(j.ClusterCreateTime)) {
return errors.New("RDS Cluster was recreated")
}

return nil
}
}

func testAccClusterConfig_basic(rName string) string {
return fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
Expand Down Expand Up @@ -2801,7 +2945,7 @@ resource "aws_rds_cluster" "test" {
`, rName))
}

func testAccClusterConfig_storageType(rName string) string {
func testAccClusterConfig_storageTypeIo1(rName string) string {
return fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
apply_immediately = true
Expand Down Expand Up @@ -4493,6 +4637,59 @@ resource "aws_rds_cluster" "test" {
`, n, f)
}

func testAccClusterConfig_auroraStorageType(rName, storageType string) string {
return fmt.Sprintf(`
data "aws_rds_engine_version" "default" {
engine = "aurora-postgresql"
preferred_versions = ["14.7", "15.2"]
}
data "aws_rds_orderable_db_instance" "default" {
engine = data.aws_rds_engine_version.default.engine
engine_version = data.aws_rds_engine_version.default.version
preferred_instance_classes = ["db.m6g.large", "db.m5.large", "db.r5.large", "db.c5.large"]
}
resource "aws_rds_cluster" "test" {
apply_immediately = true
cluster_identifier = %[1]q
engine = data.aws_rds_engine_version.default.engine
engine_version = data.aws_rds_engine_version.default.version
master_password = "avoid-plaintext-passwords"
master_username = "tfacctest"
skip_final_snapshot = true
storage_type = %[2]q
}
`, rName, storageType)
}

func testAccClusterConfig_auroraStorageTypeNotDefined(rName string) string {
return fmt.Sprintf(`
data "aws_rds_engine_version" "default" {
engine = "aurora-postgresql"
preferred_versions = ["14.7", "15.2"]
}
data "aws_rds_orderable_db_instance" "default" {
engine = data.aws_rds_engine_version.default.engine
engine_version = data.aws_rds_engine_version.default.version
preferred_instance_classes = ["db.m6g.large", "db.m5.large", "db.r5.large", "db.c5.large"]
}
resource "aws_rds_cluster" "test" {
apply_immediately = true
cluster_identifier = %[1]q
engine = data.aws_rds_engine_version.default.engine
engine_version = data.aws_rds_engine_version.default.version
master_password = "avoid-plaintext-passwords"
master_username = "tfacctest"
skip_final_snapshot = true
}
`, rName)
}

func testAccClusterConfig_enableHTTPEndpoint(rName string, enableHttpEndpoint bool) string {
return fmt.Sprintf(`
resource "aws_rds_cluster" "test" {
Expand Down
10 changes: 6 additions & 4 deletions internal/service/rds/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ const (
)

const (
storageTypeStandard = "standard"
storageTypeGP2 = "gp2"
storageTypeGP3 = "gp3"
storageTypeIO1 = "io1"
storageTypeStandard = "standard"
storageTypeGP2 = "gp2"
storageTypeGP3 = "gp3"
storageTypeIO1 = "io1"
storageTypeAuroraIOPT1 = "aurora-iopt1"
)

func StorageType_Values() []string {
Expand All @@ -43,6 +44,7 @@ func StorageType_Values() []string {
storageTypeGP2,
storageTypeGP3,
storageTypeIO1,
storageTypeAuroraIOPT1,
}
}

Expand Down
7 changes: 3 additions & 4 deletions internal/service/rds/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,10 +576,9 @@ func ResourceInstance() *schema.Resource {
Computed: true,
},
"storage_type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice(StorageType_Values(), false),
Type: schema.TypeString,
Optional: true,
Computed: true,
},
names.AttrTags: tftags.TagsSchema(),
names.AttrTagsAll: tftags.TagsSchemaComputed(),
Expand Down
Loading

0 comments on commit 62ebec3

Please sign in to comment.