Skip to content

Commit

Permalink
resource/aws_dynamodb_table: Perform individual replica creations/del…
Browse files Browse the repository at this point in the history
…etions

Reference: #13132

Also includes slight changes to the acceptance testing recommendations for cross-region testing since we now are introducing the framework to test a third region. If approved, will followup this change with refactoring of the existing testing to use the newer functions.

Previously:

```
--- FAIL: TestAccAWSDynamoDbTable_Replica_Multiple (41.02s)
    TestAccAWSDynamoDbTable_Replica_Multiple: testing.go:684: Step 0 error: errors during apply:

        Error: error creating DynamoDB Table (TerraformTestTable--8440396559818741779) replicas: error creating DynamoDB Table (TerraformTestTable--8440396559818741779) replicas: ValidationException: Update table operation with more than one create or delete replica actions not allowed
          status code: 400, request id: 5MA0CQ5LJAVOKM671RT7QTKRTJVV4KQNSO5AEMVJF66Q9ASUAAJG

          on /var/folders/w8/05f3x02n27x72g0mc2jy6_180000gp/T/tf-test945359260/main.tf line 20:
          (source code not available)
```

Output from acceptance testing:

```
--- PASS: TestAccAWSDynamoDbTable_attributeUpdate (756.09s)
--- PASS: TestAccAWSDynamoDbTable_attributeUpdateValidation (17.31s)
--- PASS: TestAccAWSDynamoDbTable_basic (82.00s)
--- PASS: TestAccAWSDynamoDbTable_BillingMode_GSI_PayPerRequestToProvisioned (135.99s)
--- PASS: TestAccAWSDynamoDbTable_BillingMode_GSI_ProvisionedToPayPerRequest (1296.50s)
--- PASS: TestAccAWSDynamoDbTable_BillingMode_PayPerRequestToProvisioned (134.93s)
--- PASS: TestAccAWSDynamoDbTable_BillingMode_ProvisionedToPayPerRequest (934.09s)
--- PASS: TestAccAWSDynamoDbTable_disappears (59.40s)
--- PASS: TestAccAWSDynamoDbTable_disappears_PayPerRequestWithGSI (140.30s)
--- PASS: TestAccAWSDynamoDbTable_enablePitr (152.34s)
--- PASS: TestAccAWSDynamoDbTable_encryption (249.98s)
--- PASS: TestAccAWSDynamoDbTable_extended (443.38s)
--- PASS: TestAccAWSDynamoDbTable_gsiUpdateCapacity (144.47s)
--- PASS: TestAccAWSDynamoDbTable_gsiUpdateNonKeyAttributes (443.69s)
--- PASS: TestAccAWSDynamoDbTable_gsiUpdateOtherAttributes (757.61s)
--- PASS: TestAccAWSDynamoDbTable_Replica_Multiple (1193.16s)
--- PASS: TestAccAWSDynamoDbTable_Replica_Single (1077.60s)
--- PASS: TestAccAWSDynamoDbTable_streamSpecification (148.25s)
--- PASS: TestAccAWSDynamoDbTable_streamSpecificationValidation (12.21s)
--- PASS: TestAccAWSDynamoDbTable_tags (88.24s)
--- PASS: TestAccAWSDynamoDbTable_Ttl_Disabled (116.66s)
--- PASS: TestAccAWSDynamoDbTable_Ttl_Enabled (81.82s)
```
  • Loading branch information
bflad committed May 27, 2020
1 parent a9c9375 commit 8a17222
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 146 deletions.
70 changes: 70 additions & 0 deletions aws/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,14 @@ func testAccGetAlternateRegion() string {
return v
}

func testAccGetThirdRegion() string {
v := os.Getenv("AWS_THIRD_REGION")
if v == "" {
return "us-east-2"
}
return v
}

func testAccGetPartition() string {
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetRegion()); ok {
return partition.ID()
Expand All @@ -403,6 +411,13 @@ func testAccGetAlternateRegionPartition() string {
return "aws"
}

func testAccGetThirdRegionPartition() string {
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetThirdRegion()); ok {
return partition.ID()
}
return "aws"
}

func testAccAlternateAccountPreCheck(t *testing.T) {
if os.Getenv("AWS_ALTERNATE_PROFILE") == "" && os.Getenv("AWS_ALTERNATE_ACCESS_KEY_ID") == "" {
t.Fatal("AWS_ALTERNATE_ACCESS_KEY_ID or AWS_ALTERNATE_PROFILE must be set for acceptance tests")
Expand All @@ -423,6 +438,18 @@ func testAccAlternateRegionPreCheck(t *testing.T) {
}
}

func testAccThirdRegionPreCheck(t *testing.T) {
testAccAlternateRegionPreCheck(t)

if testAccGetRegion() == testAccGetThirdRegion() {
t.Fatal("AWS_DEFAULT_REGION and AWS_THIRD_REGION must be set to different values for acceptance tests")
}

if testAccGetPartition() != testAccGetThirdRegionPartition() {
t.Fatalf("AWS_THIRD_REGION partition (%s) does not match AWS_DEFAULT_REGION partition (%s)", testAccGetThirdRegionPartition(), testAccGetPartition())
}
}

func testAccEC2ClassicPreCheck(t *testing.T) {
client := testAccProvider.Meta().(*AWSClient)
platforms := client.supportedplatforms
Expand Down Expand Up @@ -451,6 +478,24 @@ func testAccPartitionHasServicePreCheck(serviceId string, t *testing.T) {
}
}

func testAccMultipleRegionPreCheck(t *testing.T, expected int) {
switch expected {
case 2:
testAccAlternateRegionPreCheck(t)
case 3:
testAccThirdRegionPreCheck(t)
default:
t.Fatalf("unknown expected region: %d", expected)
}

if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetRegion()); ok {
if len(partition.Regions()) < expected {
t.Skipf("skipping tests; partition includes %d regions, %d expected", len(partition.Regions()), expected)
}
}
}

// Deprecated: Use testAccMultipleRegionPreCheck instead.
func testAccMultipleRegionsPreCheck(t *testing.T) {
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), testAccGetRegion()); ok {
if len(partition.Regions()) < 2 {
Expand Down Expand Up @@ -536,6 +581,7 @@ provider "aws" {
`, os.Getenv("AWS_ALTERNATE_ACCESS_KEY_ID"), os.Getenv("AWS_ALTERNATE_PROFILE"), testAccGetAlternateRegion(), os.Getenv("AWS_ALTERNATE_SECRET_ACCESS_KEY"))
}

// Deprecated: Use testAccMultipleRegionProviderConfig instead
func testAccAlternateRegionProviderConfig() string {
//lintignore:AT004
return fmt.Sprintf(`
Expand All @@ -546,6 +592,30 @@ provider "aws" {
`, testAccGetAlternateRegion())
}

func testAccMultipleRegionProviderConfig(regions int) string {
var config strings.Builder

//lintignore:AT004
fmt.Fprintf(&config, `
provider "aws" {
alias = "alternate"
region = %[1]q
}
`, testAccGetAlternateRegion())

if regions >= 3 {
//lintignore:AT004
fmt.Fprintf(&config, `
provider "aws" {
alias = "third"
region = %[1]q
}
`, testAccGetThirdRegion())
}

return config.String()
}

func testAccProviderConfigIgnoreTagsKeyPrefixes1(keyPrefix1 string) string {
//lintignore:AT004
return fmt.Sprintf(`
Expand Down
130 changes: 58 additions & 72 deletions aws/resource_aws_dynamodb_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,9 +760,6 @@ func deleteAwsDynamoDbTable(tableName string, conn *dynamodb.DynamoDB) error {
}

func deleteDynamoDbReplicas(tableName string, tfList []interface{}, timeout time.Duration, conn *dynamodb.DynamoDB) error {
var ops []*dynamodb.ReplicationGroupUpdate
var regionNames []string

for _, tfMapRaw := range tfList {
tfMap, ok := tfMapRaw.(map[string]interface{})

Expand All @@ -780,47 +777,43 @@ func deleteDynamoDbReplicas(tableName string, tfList []interface{}, timeout time
continue
}

ops = append(ops, &dynamodb.ReplicationGroupUpdate{
Delete: &dynamodb.DeleteReplicationGroupMemberAction{
RegionName: aws.String(regionName),
input := &dynamodb.UpdateTableInput{
TableName: aws.String(tableName),
ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{
{
Delete: &dynamodb.DeleteReplicationGroupMemberAction{
RegionName: aws.String(regionName),
},
},
},
})
regionNames = append(regionNames, regionName)
}

if len(ops) == 0 {
return nil
}
}

input := &dynamodb.UpdateTableInput{
TableName: aws.String(tableName),
ReplicaUpdates: ops,
}
err := resource.Retry(20*time.Minute, func() *resource.RetryError {
_, err := conn.UpdateTable(input)
if err != nil {
if isAWSErr(err, "ThrottlingException", "") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeResourceInUseException, "") {
return resource.RetryableError(err)
}

err := resource.Retry(20*time.Minute, func() *resource.RetryError {
_, err := conn.UpdateTable(input)
if err != nil {
if isAWSErr(err, "ThrottlingException", "") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") {
return resource.RetryableError(err)
return resource.NonRetryableError(err)
}
return nil
})

return resource.NonRetryableError(err)
if isResourceTimeoutError(err) {
_, err = conn.UpdateTable(input)
}
return nil
})

if isResourceTimeoutError(err) {
_, err = conn.UpdateTable(input)
}

if err != nil {
return fmt.Errorf("error deleting DynamoDB Table (%s) replicas: %s", tableName, err)
}
if err != nil {
return fmt.Errorf("error deleting DynamoDB Table (%s) replica (%s): %s", tableName, regionName, err)
}

for _, regionName := range regionNames {
if err := waitForDynamoDbReplicaDeleteToBeCompleted(tableName, regionName, timeout, conn); err != nil {
return fmt.Errorf("error waiting for DynamoDB Table (%s) replica (%s) deletion: %s", tableName, regionName, err)
}
Expand Down Expand Up @@ -947,9 +940,6 @@ func waitForDynamoDbReplicaDeleteToBeCompleted(tableName string, region string,
}

func createDynamoDbReplicas(tableName string, tfList []interface{}, timeout time.Duration, conn *dynamodb.DynamoDB) error {
var ops []*dynamodb.ReplicationGroupUpdate
var regionNames []string

for _, tfMapRaw := range tfList {
tfMap, ok := tfMapRaw.(map[string]interface{})

Expand All @@ -967,47 +957,43 @@ func createDynamoDbReplicas(tableName string, tfList []interface{}, timeout time
continue
}

ops = append(ops, &dynamodb.ReplicationGroupUpdate{
Create: &dynamodb.CreateReplicationGroupMemberAction{
RegionName: aws.String(regionName),
input := &dynamodb.UpdateTableInput{
TableName: aws.String(tableName),
ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{
{
Create: &dynamodb.CreateReplicationGroupMemberAction{
RegionName: aws.String(regionName),
},
},
},
})
regionNames = append(regionNames, regionName)
}

if len(ops) == 0 {
return nil
}
}

input := &dynamodb.UpdateTableInput{
TableName: aws.String(tableName),
ReplicaUpdates: ops,
}
err := resource.Retry(20*time.Minute, func() *resource.RetryError {
_, err := conn.UpdateTable(input)
if err != nil {
if isAWSErr(err, "ThrottlingException", "") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeResourceInUseException, "") {
return resource.RetryableError(err)
}

err := resource.Retry(20*time.Minute, func() *resource.RetryError {
_, err := conn.UpdateTable(input)
if err != nil {
if isAWSErr(err, "ThrottlingException", "") {
return resource.RetryableError(err)
}
if isAWSErr(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") {
return resource.RetryableError(err)
return resource.NonRetryableError(err)
}
return nil
})

return resource.NonRetryableError(err)
if isResourceTimeoutError(err) {
_, err = conn.UpdateTable(input)
}
return nil
})

if isResourceTimeoutError(err) {
_, err = conn.UpdateTable(input)
}

if err != nil {
return fmt.Errorf("error creating DynamoDB Table (%s) replicas: %s", tableName, err)
}
if err != nil {
return fmt.Errorf("error creating DynamoDB Table (%s) replica (%s): %s", tableName, regionName, err)
}

for _, regionName := range regionNames {
if err := waitForDynamoDbReplicaUpdateToBeCompleted(tableName, regionName, timeout, conn); err != nil {
return fmt.Errorf("error waiting for DynamoDB Table (%s) replica (%s) creation: %s", tableName, regionName, err)
}
Expand Down
Loading

0 comments on commit 8a17222

Please sign in to comment.