Skip to content

Commit 08ebf41

Browse files
committed
delete fixes
1 parent 534418a commit 08ebf41

File tree

8 files changed

+292
-394
lines changed

8 files changed

+292
-394
lines changed

Diff for: apis/v1alpha1/ack-generate-metadata.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ack_generate_info:
2-
build_date: "2025-03-23T21:30:39Z"
2+
build_date: "2025-03-24T15:20:40Z"
33
build_hash: 3722729cebe6d3c03c7e442655ef0846f91566a2
44
go_version: go1.24.0
55
version: v0.43.2-7-g3722729

Diff for: pkg/resource/table/hooks.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,13 @@ func (rm *resourceManager) customUpdateTable(
234234
return nil, err
235235
}
236236
case delta.DifferentAt("Spec.TableReplicas"):
237-
if !hasStreamSpecificationWithNewAndOldImages(desired) {
237+
// Enabling replicas required streams enabled and StreamViewType to be NEW_AND_OLD_IMAGES
238+
// Version 2019.11.21 TableUpdate API requirement
239+
if !hasStreamSpecificationWithNewAndOldImages(desired) {
238240
msg := "table must have DynamoDB Streams enabled with StreamViewType set to NEW_AND_OLD_IMAGES for replica updates"
239241
rlog.Debug(msg)
240242
return nil, ackerr.NewTerminalError(errors.New(msg))
241243
}
242-
243-
if !canUpdateTableReplicas(latest) {
244-
return nil, requeueWaitReplicasActive
245-
}
246244
if err := rm.syncReplicas(ctx, latest, desired); err != nil {
247245
return nil, err
248246
}

Diff for: pkg/resource/table/hooks_replica_updates.go

+78-56
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,14 @@ package table
1515

1616
import (
1717
"context"
18-
"strings"
19-
"time"
2018

2119
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
22-
ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
23-
ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue"
2420
"github.com/aws/aws-sdk-go-v2/aws"
2521
svcsdk "github.com/aws/aws-sdk-go-v2/service/dynamodb"
2622
svcsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
2723

2824
"github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1"
25+
svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1"
2926
)
3027

3128
// equalCreateReplicationGroupMemberActions compares two CreateReplicationGroupMemberAction objects
@@ -178,7 +175,7 @@ func createReplicaUpdate(replica *v1alpha1.CreateReplicationGroupMemberAction) s
178175
for _, gsi := range replica.GlobalSecondaryIndexes {
179176
replicaGSI := svcsdktypes.ReplicaGlobalSecondaryIndex{}
180177
if gsi.IndexName != nil {
181-
replicaGSI.IndexName = aws.String(*gsi.IndexName)
178+
replicaGSI.IndexName = gsi.IndexName
182179
}
183180
if gsi.ProvisionedThroughputOverride != nil {
184181
replicaGSI.ProvisionedThroughputOverride = &svcsdktypes.ProvisionedThroughputOverride{}
@@ -250,7 +247,9 @@ func updateReplicaUpdate(replica *v1alpha1.CreateReplicationGroupMemberAction) s
250247
}
251248

252249
// If no valid updates, return an empty ReplicationGroupUpdate
253-
return svcsdktypes.ReplicationGroupUpdate{}
250+
return svcsdktypes.ReplicationGroupUpdate{
251+
Update: nil,
252+
}
254253
}
255254

256255
// deleteReplicaUpdate creates a ReplicationGroupUpdate for deleting an existing replica
@@ -262,25 +261,6 @@ func deleteReplicaUpdate(regionName string) svcsdktypes.ReplicationGroupUpdate {
262261
}
263262
}
264263

265-
// canUpdateTableReplicas returns true if it's possible to update table replicas.
266-
// We can only modify replicas when they are in ACTIVE state.
267-
func canUpdateTableReplicas(r *resource) bool {
268-
// Check if Status or Replicas is nil
269-
// needed when called by sdkdelete
270-
if r == nil || r.ko == nil || r.ko.Status.Replicas == nil {
271-
return true // If no replicas exist, we can proceed with updates
272-
}
273-
// Check if any replica is not in ACTIVE state
274-
for _, replicaDesc := range r.ko.Status.Replicas {
275-
if replicaDesc.RegionName != nil && replicaDesc.ReplicaStatus != nil {
276-
if *replicaDesc.ReplicaStatus != string(svcsdktypes.ReplicaStatusActive) {
277-
return false
278-
}
279-
}
280-
}
281-
return true
282-
}
283-
284264
// hasStreamSpecificationWithNewAndOldImages checks if the table has DynamoDB Streams enabled
285265
// with the stream containing both the new and the old images of the item.
286266
func hasStreamSpecificationWithNewAndOldImages(r *resource) bool {
@@ -314,33 +294,7 @@ func (rm *resourceManager) syncReplicas(
314294
_, err = rm.sdkapi.UpdateTable(ctx, input)
315295
rm.metrics.RecordAPICall("UPDATE", "UpdateTable", err)
316296
if err != nil {
317-
// Handle specific errors
318-
if awsErr, ok := ackerr.AWSError(err); ok {
319-
// Handle ValidationException - when replicas are not part of the global table
320-
if awsErr.ErrorCode() == "ValidationException" &&
321-
strings.Contains(awsErr.ErrorMessage(), "not part of the global table") {
322-
// A replica was already deleted
323-
rlog.Debug("replica already deleted from global table",
324-
"table", *latest.ko.Spec.TableName,
325-
"error", awsErr.ErrorMessage())
326-
return ackrequeue.NeededAfter(
327-
ErrTableUpdating,
328-
30*time.Second,
329-
)
330-
}
331-
332-
// Handle ResourceInUseException - when the table is being updated
333-
if awsErr.ErrorCode() == "ResourceInUseException" {
334-
rlog.Debug("table is currently in use, will retry",
335-
"table", *latest.ko.Spec.TableName,
336-
"error", awsErr.ErrorMessage())
337-
return ackrequeue.NeededAfter(
338-
ErrTableUpdating,
339-
30*time.Second,
340-
)
341-
}
342-
return err
343-
}
297+
return err
344298
}
345299

346300
// If there are more replicas to process, requeue
@@ -367,7 +321,7 @@ func (rm *resourceManager) newUpdateTableReplicaUpdatesOneAtATimePayload(
367321
exit(err)
368322
}()
369323

370-
createReplicas, updateReplicas, deleteRegions := calculateReplicaUpdates(latest, desired)
324+
createReplicas, updateReplicas, deleteRegions := computeReplicaupdatesDelta(latest, desired)
371325

372326
input = &svcsdk.UpdateTableInput{
373327
TableName: aws.String(*desired.ko.Spec.TableName),
@@ -382,20 +336,33 @@ func (rm *resourceManager) newUpdateTableReplicaUpdatesOneAtATimePayload(
382336

383337
if len(createReplicas) > 0 {
384338
replica := *createReplicas[0]
339+
if checkIfReplicasInProgress(latest.ko.Status.Replicas, *replica.RegionName) {
340+
return nil, 0, requeueWaitReplicasActive
341+
}
385342
rlog.Debug("creating replica in region", "table", *desired.ko.Spec.TableName, "region", *replica.RegionName)
386343
input.ReplicaUpdates = append(input.ReplicaUpdates, createReplicaUpdate(createReplicas[0]))
387344
return input, replicasInQueue, nil
388345
}
389346

390347
if len(updateReplicas) > 0 {
391348
replica := *updateReplicas[0]
349+
if checkIfReplicasInProgress(latest.ko.Status.Replicas, *replica.RegionName) {
350+
return nil, 0, requeueWaitReplicasActive
351+
}
392352
rlog.Debug("updating replica in region", "table", *desired.ko.Spec.TableName, "region", *replica.RegionName)
393-
input.ReplicaUpdates = append(input.ReplicaUpdates, updateReplicaUpdate(updateReplicas[0]))
353+
updateReplica := updateReplicaUpdate(updateReplicas[0])
354+
if updateReplica.Update == nil {
355+
return nil, 0, requeueWaitReplicasActive
356+
}
357+
input.ReplicaUpdates = append(input.ReplicaUpdates, updateReplica)
394358
return input, replicasInQueue, nil
395359
}
396360

397361
if len(deleteRegions) > 0 {
398362
replica := deleteRegions[0]
363+
if checkIfReplicasInProgress(latest.ko.Status.Replicas, replica) {
364+
return nil, 0, requeueWaitReplicasActive
365+
}
399366
rlog.Debug("deleting replica in region", "table", *desired.ko.Spec.TableName, "region", replica)
400367
input.ReplicaUpdates = append(input.ReplicaUpdates, deleteReplicaUpdate(deleteRegions[0]))
401368
return input, replicasInQueue, nil
@@ -404,9 +371,9 @@ func (rm *resourceManager) newUpdateTableReplicaUpdatesOneAtATimePayload(
404371
return input, replicasInQueue, nil
405372
}
406373

407-
// calculateReplicaUpdates calculates the replica updates needed to reconcile the latest state with the desired state
374+
// computeReplicaupdatesDelta calculates the replica updates needed to reconcile the latest state with the desired state
408375
// Returns three slices: replicas to create, replicas to update, and region names to delete
409-
func calculateReplicaUpdates(
376+
func computeReplicaupdatesDelta(
410377
latest *resource,
411378
desired *resource,
412379
) (
@@ -451,3 +418,58 @@ func calculateReplicaUpdates(
451418

452419
return createReplicas, updateReplicas, deleteRegions
453420
}
421+
422+
func setTableReplicas(ko *svcapitypes.Table, replicas []svcsdktypes.ReplicaDescription) {
423+
if len(replicas) > 0 {
424+
tableReplicas := []*v1alpha1.CreateReplicationGroupMemberAction{}
425+
for _, replica := range replicas {
426+
replicaElem := &v1alpha1.CreateReplicationGroupMemberAction{}
427+
if replica.RegionName != nil {
428+
replicaElem.RegionName = replica.RegionName
429+
}
430+
if replica.KMSMasterKeyId != nil {
431+
replicaElem.KMSMasterKeyID = replica.KMSMasterKeyId
432+
}
433+
if replica.ProvisionedThroughputOverride != nil {
434+
replicaElem.ProvisionedThroughputOverride = &v1alpha1.ProvisionedThroughputOverride{
435+
ReadCapacityUnits: replica.ProvisionedThroughputOverride.ReadCapacityUnits,
436+
}
437+
}
438+
if replica.GlobalSecondaryIndexes != nil {
439+
gsiList := []*v1alpha1.ReplicaGlobalSecondaryIndex{}
440+
for _, gsi := range replica.GlobalSecondaryIndexes {
441+
gsiElem := &v1alpha1.ReplicaGlobalSecondaryIndex{
442+
IndexName: gsi.IndexName,
443+
}
444+
if gsi.ProvisionedThroughputOverride != nil {
445+
gsiElem.ProvisionedThroughputOverride = &v1alpha1.ProvisionedThroughputOverride{
446+
ReadCapacityUnits: gsi.ProvisionedThroughputOverride.ReadCapacityUnits,
447+
}
448+
}
449+
gsiList = append(gsiList, gsiElem)
450+
}
451+
replicaElem.GlobalSecondaryIndexes = gsiList
452+
}
453+
if replica.ReplicaTableClassSummary != nil && replica.ReplicaTableClassSummary.TableClass != "" {
454+
replicaElem.TableClassOverride = aws.String(string(replica.ReplicaTableClassSummary.TableClass))
455+
}
456+
tableReplicas = append(tableReplicas, replicaElem)
457+
}
458+
ko.Spec.TableReplicas = tableReplicas
459+
} else {
460+
ko.Spec.TableReplicas = nil
461+
}
462+
}
463+
464+
func checkIfReplicasInProgress(ReplicaDescription []*svcapitypes.ReplicaDescription, regionName string) bool {
465+
for _, replica := range ReplicaDescription {
466+
if *replica.RegionName == regionName {
467+
replicaStatus := replica.ReplicaStatus
468+
if *replicaStatus == string(svcsdktypes.ReplicaStatusCreating) || *replicaStatus == string(svcsdktypes.ReplicaStatusDeleting) || *replicaStatus == string(svcsdktypes.ReplicaStatusUpdating) {
469+
return true
470+
}
471+
}
472+
}
473+
474+
return false
475+
}

Diff for: pkg/resource/table/sdk.go

+1-42
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: templates/hooks/table/sdk_delete_pre_build_request.go.tpl

-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
}
77

88
// If there are replicas, we need to remove them before deleting the table
9-
if !canUpdateTableReplicas(latest) {
10-
return nil, requeueWaitReplicasActive
11-
}
129
if len(r.ko.Spec.TableReplicas) > 0 {
1310
desired := &resource{
1411
ko: r.ko.DeepCopy(),

Diff for: templates/hooks/table/sdk_read_one_post_set_output.go.tpl

+1-39
Original file line numberDiff line numberDiff line change
@@ -53,45 +53,7 @@
5353
} else {
5454
ko.Spec.BillingMode = aws.String("PROVISIONED")
5555
}
56-
if len(resp.Table.Replicas) > 0 {
57-
tableReplicas := []*svcapitypes.CreateReplicationGroupMemberAction{}
58-
for _, replica := range resp.Table.Replicas {
59-
replicaElem := &svcapitypes.CreateReplicationGroupMemberAction{}
60-
if replica.RegionName != nil {
61-
replicaElem.RegionName = replica.RegionName
62-
}
63-
if replica.KMSMasterKeyId != nil {
64-
replicaElem.KMSMasterKeyID = replica.KMSMasterKeyId
65-
}
66-
if replica.ProvisionedThroughputOverride != nil {
67-
replicaElem.ProvisionedThroughputOverride = &svcapitypes.ProvisionedThroughputOverride{
68-
ReadCapacityUnits: replica.ProvisionedThroughputOverride.ReadCapacityUnits,
69-
}
70-
}
71-
if replica.GlobalSecondaryIndexes != nil {
72-
gsiList := []*svcapitypes.ReplicaGlobalSecondaryIndex{}
73-
for _, gsi := range replica.GlobalSecondaryIndexes {
74-
gsiElem := &svcapitypes.ReplicaGlobalSecondaryIndex{
75-
IndexName: gsi.IndexName,
76-
}
77-
if gsi.ProvisionedThroughputOverride != nil {
78-
gsiElem.ProvisionedThroughputOverride = &svcapitypes.ProvisionedThroughputOverride{
79-
ReadCapacityUnits: gsi.ProvisionedThroughputOverride.ReadCapacityUnits,
80-
}
81-
}
82-
gsiList = append(gsiList, gsiElem)
83-
}
84-
replicaElem.GlobalSecondaryIndexes = gsiList
85-
}
86-
if replica.ReplicaTableClassSummary != nil && replica.ReplicaTableClassSummary.TableClass != "" {
87-
replicaElem.TableClassOverride = aws.String(string(replica.ReplicaTableClassSummary.TableClass))
88-
}
89-
tableReplicas = append(tableReplicas, replicaElem)
90-
}
91-
ko.Spec.TableReplicas = tableReplicas
92-
} else {
93-
ko.Spec.TableReplicas = nil
94-
}
56+
setTableReplicas(r, resp.Table.Replicas)
9557
if isTableCreating(&resource{ko}) {
9658
return &resource{ko}, requeueWaitWhileCreating
9759
}

Diff for: test/e2e/resources/table_with_gsi_and_replicas.yaml

+6-10
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ spec:
1414
attributeType: S
1515
- attributeName: GSI1SK
1616
attributeType: S
17-
- attributeName: GSI2PK
18-
attributeType: S
1917
keySchema:
2018
- attributeName: PK
2119
keyType: HASH
@@ -25,9 +23,13 @@ spec:
2523
streamSpecification:
2624
streamEnabled: true
2725
streamViewType: "NEW_AND_OLD_IMAGES"
28-
replicationGroup:
26+
tableReplicas:
2927
- regionName: $REPLICA_REGION_1
28+
globalSecondaryIndexes:
29+
- indexName: GSI1
3030
- regionName: $REPLICA_REGION_2
31+
globalSecondaryIndexes:
32+
- indexName: GSI1
3133
globalSecondaryIndexes:
3234
- indexName: GSI1
3335
keySchema:
@@ -36,10 +38,4 @@ spec:
3638
- attributeName: GSI1SK
3739
keyType: RANGE
3840
projection:
39-
projectionType: ALL
40-
- indexName: GSI2
41-
keySchema:
42-
- attributeName: GSI2PK
43-
keyType: HASH
44-
projection:
45-
projectionType: KEYS_ONLY
41+
projectionType: ALL

0 commit comments

Comments
 (0)