Skip to content

Commit 7fa0950

Browse files
authored
Merge pull request #43908 from shawnaws/f/ddb-mrsc-witness-updates
feat: Add support for Witness Region for Global Tables with MRSC
2 parents f4da751 + 4291191 commit 7fa0950

File tree

4 files changed

+1586
-105
lines changed

4 files changed

+1586
-105
lines changed

.changelog/43908.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resource/aws_dynamodb_table: Add `global_table_witness` argument
3+
```

internal/service/dynamodb/table.go

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,22 @@ func resourceTable() *schema.Resource {
226226
},
227227
},
228228
},
229+
"global_table_witness": {
230+
Type: schema.TypeList,
231+
Optional: true,
232+
Computed: true,
233+
MaxItems: 1,
234+
Elem: &schema.Resource{
235+
Schema: map[string]*schema.Schema{
236+
"region_name": {
237+
Type: schema.TypeString,
238+
Optional: true,
239+
Computed: true,
240+
ValidateFunc: verify.ValidRegionName,
241+
},
242+
},
243+
},
244+
},
229245
"hash_key": {
230246
Type: schema.TypeString,
231247
Optional: true,
@@ -884,7 +900,7 @@ func resourceTableCreate(ctx context.Context, d *schema.ResourceData, meta any)
884900
}
885901

886902
if v := d.Get("replica").(*schema.Set); v.Len() > 0 {
887-
if err := createReplicas(ctx, conn, d.Id(), v.List(), true, d.Timeout(schema.TimeoutCreate)); err != nil {
903+
if err := createReplicas(ctx, conn, d.Id(), v.List(), expandGlobalTableWitness(d.Get("global_table_witness")), true, d.Timeout(schema.TimeoutCreate)); err != nil {
888904
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionCreating, resNameTable, d.Id(), fmt.Errorf("replicas: %w", err))
889905
}
890906

@@ -951,6 +967,10 @@ func resourceTableRead(ctx context.Context, d *schema.ResourceData, meta any) di
951967
return create.AppendDiagSettingError(diags, names.DynamoDB, resNameTable, d.Id(), "global_secondary_index", err)
952968
}
953969

970+
if err := d.Set("global_table_witness", flattenGlobalTableWitnesses(table.GlobalTableWitnesses)); err != nil {
971+
return create.AppendDiagSettingError(diags, names.DynamoDB, resNameTable, d.Id(), "global_table_witness", err)
972+
}
973+
954974
if err := d.Set("on_demand_throughput", flattenOnDemandThroughput(table.OnDemandThroughput)); err != nil {
955975
return create.AppendDiagSettingError(diags, names.DynamoDB, resNameTable, d.Id(), "on_demand_throughput", err)
956976
}
@@ -1393,10 +1413,11 @@ func resourceTableDelete(ctx context.Context, d *schema.ResourceData, meta any)
13931413

13941414
if replicas := d.Get("replica").(*schema.Set).List(); len(replicas) > 0 {
13951415
log.Printf("[DEBUG] Deleting DynamoDB Table replicas: %s", d.Id())
1396-
if err := deleteReplicas(ctx, conn, d.Id(), replicas, d.Timeout(schema.TimeoutDelete)); err != nil {
1416+
if err := deleteReplicas(ctx, conn, d.Id(), replicas, expandGlobalTableWitness(d.Get("global_table_witness")), d.Timeout(schema.TimeoutDelete)); err != nil {
13971417
// ValidationException: Replica specified in the Replica Update or Replica Delete action of the request was not found.
13981418
// ValidationException: Cannot add, delete, or update the local region through ReplicaUpdates. Use CreateTable, DeleteTable, or UpdateTable as required.
13991419
if !tfawserr.ErrMessageContains(err, errCodeValidationException, "request was not found") &&
1420+
!tfawserr.ErrMessageContains(err, errCodeValidationException, "MultiRegionConsistency must be set as STRONG when GlobalTableWitnessUpdates parameter is present") &&
14001421
!tfawserr.ErrMessageContains(err, errCodeValidationException, "Cannot add, delete, or update the local region through ReplicaUpdates") {
14011422
return create.AppendDiagError(diags, names.DynamoDB, create.ErrActionDeleting, resNameTable, d.Id(), err)
14021423
}
@@ -1471,7 +1492,7 @@ func cycleStreamEnabled(ctx context.Context, conn *dynamodb.Client, id string, s
14711492
return nil
14721493
}
14731494

1474-
func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string, tfList []any, create bool, timeout time.Duration) error {
1495+
func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string, tfList []any, globalTableWitnessRegionName string, create bool, timeout time.Duration) error {
14751496
// Duplicating this for MRSC Adoption. If using MRSC and CreateReplicationGroupMemberAction list isn't initiated for at least 2 replicas
14761497
// then the update table action will fail with
14771498
// "Unsupported table replica count for global tables with MultiRegionConsistency set to STRONG"
@@ -1492,14 +1513,18 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
14921513
}
14931514

14941515
if numReplicasMRSC > 0 {
1516+
mrscErrorMsg := "creating replicas: Using MultiRegionStrongConsistency requires exactly 2 replicas, or 1 replica and 1 witness region."
14951517
if numReplicasMRSC > 0 && numReplicasMRSC != numReplicas {
1496-
return fmt.Errorf("creating replicas: Using MultiRegionStrongConsistency requires all replicas to use 'consistency_mode' set to 'STRONG' ")
1518+
return errors.New(mrscErrorMsg)
1519+
}
1520+
if numReplicasMRSC == 1 && globalTableWitnessRegionName == "" {
1521+
return fmt.Errorf("%s Only MRSC Replica count of 1 was provided but no Witness region was provided", mrscErrorMsg)
14971522
}
1498-
if numReplicasMRSC == 1 {
1499-
return fmt.Errorf("creating replicas: Using MultiRegionStrongConsistency requires exactly 2 replicas. ")
1523+
if numReplicasMRSC == 2 && (numReplicasMRSC == numReplicas && globalTableWitnessRegionName != "") {
1524+
return fmt.Errorf("%s MRSC Replica count of 2 was provided and a Witness region was also provided", mrscErrorMsg)
15001525
}
15011526
if numReplicasMRSC > 2 {
1502-
return fmt.Errorf("creating replicas: Using MultiRegionStrongConsistency supports at most 2 replicas. ")
1527+
return fmt.Errorf("%s Too many Replicas were provided %d", mrscErrorMsg, numReplicasMRSC)
15031528
}
15041529

15051530
mrscInput = awstypes.MultiRegionConsistencyStrong
@@ -1530,14 +1555,24 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
15301555
})
15311556
}
15321557

1533-
input := &dynamodb.UpdateTableInput{
1534-
TableName: aws.String(tableName),
1535-
ReplicaUpdates: replicaCreates,
1536-
MultiRegionConsistency: mrscInput,
1558+
var gtgwu []awstypes.GlobalTableWitnessGroupUpdate
1559+
if globalTableWitnessRegionName != "" {
1560+
var cgtwgma = awstypes.CreateGlobalTableWitnessGroupMemberAction{
1561+
RegionName: aws.String(globalTableWitnessRegionName),
1562+
}
1563+
gtgwu = append(gtgwu, awstypes.GlobalTableWitnessGroupUpdate{
1564+
Create: &cgtwgma,
1565+
})
1566+
}
1567+
input := dynamodb.UpdateTableInput{
1568+
GlobalTableWitnessUpdates: gtgwu,
1569+
MultiRegionConsistency: mrscInput,
1570+
ReplicaUpdates: replicaCreates,
1571+
TableName: aws.String(tableName),
15371572
}
15381573

15391574
err := tfresource.Retry(ctx, max(replicaUpdateTimeout, timeout), func(ctx context.Context) *tfresource.RetryError {
1540-
_, err := conn.UpdateTable(ctx, input)
1575+
_, err := conn.UpdateTable(ctx, &input)
15411576
if err != nil {
15421577
if tfawserr.ErrCodeEquals(err, errCodeThrottlingException) {
15431578
return tfresource.RetryableError(err)
@@ -1655,7 +1690,7 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
16551690
// ValidationException: One or more parameter values were invalid: KMSMasterKeyId must be specified for each replica.
16561691

16571692
if create && tfawserr.ErrMessageContains(err, errCodeValidationException, "already exist") {
1658-
return createReplicas(ctx, conn, tableName, tfList, false, timeout)
1693+
return createReplicas(ctx, conn, tableName, tfList, globalTableWitnessRegionName, false, timeout)
16591694
}
16601695

16611696
if err != nil && !tfawserr.ErrMessageContains(err, errCodeValidationException, "no actions specified") {
@@ -1898,20 +1933,22 @@ func updateReplica(ctx context.Context, conn *dynamodb.Client, d *schema.Resourc
18981933
}
18991934
}
19001935

1936+
globalTableWitnessRegionName := expandGlobalTableWitness(d.Get("global_table_witness"))
1937+
19011938
if len(removeFirst) > 0 { // mini ForceNew, recreates replica but doesn't recreate the table
1902-
if err := deleteReplicas(ctx, conn, d.Id(), removeFirst, d.Timeout(schema.TimeoutUpdate)); err != nil {
1939+
if err := deleteReplicas(ctx, conn, d.Id(), removeFirst, globalTableWitnessRegionName, d.Timeout(schema.TimeoutUpdate)); err != nil {
19031940
return fmt.Errorf("updating replicas, while deleting: %w", err)
19041941
}
19051942
}
19061943

19071944
if len(toRemove) > 0 {
1908-
if err := deleteReplicas(ctx, conn, d.Id(), toRemove, d.Timeout(schema.TimeoutUpdate)); err != nil {
1945+
if err := deleteReplicas(ctx, conn, d.Id(), toRemove, globalTableWitnessRegionName, d.Timeout(schema.TimeoutUpdate)); err != nil {
19091946
return fmt.Errorf("updating replicas, while deleting: %w", err)
19101947
}
19111948
}
19121949

19131950
if len(toAdd) > 0 {
1914-
if err := createReplicas(ctx, conn, d.Id(), toAdd, true, d.Timeout(schema.TimeoutCreate)); err != nil {
1951+
if err := createReplicas(ctx, conn, d.Id(), toAdd, globalTableWitnessRegionName, true, d.Timeout(schema.TimeoutCreate)); err != nil {
19151952
return fmt.Errorf("updating replicas, while creating: %w", err)
19161953
}
19171954
}
@@ -2164,7 +2201,7 @@ func deleteTable(ctx context.Context, conn *dynamodb.Client, tableName string) e
21642201
return err
21652202
}
21662203

2167-
func deleteReplicas(ctx context.Context, conn *dynamodb.Client, tableName string, tfList []any, timeout time.Duration) error {
2204+
func deleteReplicas(ctx context.Context, conn *dynamodb.Client, tableName string, tfList []any, globalTableWitnessRegionName string, timeout time.Duration) error {
21682205
var g multierror.Group
21692206

21702207
var replicaDeletes []awstypes.ReplicationGroupUpdate
@@ -2199,12 +2236,22 @@ func deleteReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
21992236
// We built an array of MultiRegionStrongConsistency replicas that need deletion.
22002237
// These need to all happen concurrently
22012238
if len(replicaDeletes) > 0 {
2202-
input := &dynamodb.UpdateTableInput{
2203-
TableName: aws.String(tableName),
2204-
ReplicaUpdates: replicaDeletes,
2239+
var witnessDeletes []awstypes.GlobalTableWitnessGroupUpdate
2240+
if globalTableWitnessRegionName != "" {
2241+
witnessDeletes = append(witnessDeletes, awstypes.GlobalTableWitnessGroupUpdate{
2242+
Delete: &awstypes.DeleteGlobalTableWitnessGroupMemberAction{
2243+
RegionName: aws.String(globalTableWitnessRegionName),
2244+
},
2245+
})
2246+
}
2247+
2248+
input := dynamodb.UpdateTableInput{
2249+
GlobalTableWitnessUpdates: witnessDeletes,
2250+
ReplicaUpdates: replicaDeletes,
2251+
TableName: aws.String(tableName),
22052252
}
22062253
err := tfresource.Retry(ctx, updateTableTimeout, func(ctx context.Context) *tfresource.RetryError {
2207-
_, err := conn.UpdateTable(ctx, input)
2254+
_, err := conn.UpdateTable(ctx, &input)
22082255
notFoundRetries := 0
22092256
if err != nil {
22102257
if tfawserr.ErrCodeEquals(err, errCodeThrottlingException) {
@@ -2694,6 +2741,24 @@ func flattenGSIWarmThroughput(apiObject *awstypes.GlobalSecondaryIndexWarmThroug
26942741
return []any{m}
26952742
}
26962743

2744+
func expandGlobalTableWitness(v any) string {
2745+
if v == nil || len(v.([]any)) == 0 || v.([]any)[0] == nil {
2746+
return ""
2747+
}
2748+
2749+
return v.([]any)[0].(map[string]any)["region_name"].(string)
2750+
}
2751+
2752+
func flattenGlobalTableWitnesses(apiObjects []awstypes.GlobalTableWitnessDescription) []any {
2753+
if len(apiObjects) != 1 {
2754+
return []any{}
2755+
}
2756+
2757+
return []any{map[string]any{
2758+
"region_name": aws.ToString(apiObjects[0].RegionName),
2759+
}}
2760+
}
2761+
26972762
func flattenReplicaDescription(apiObject *awstypes.ReplicaDescription) map[string]any {
26982763
if apiObject == nil {
26992764
return nil

0 commit comments

Comments
 (0)