Skip to content

Commit 505979c

Browse files
feat: Add annotation to tweak providedThroughput control for tables
1 parent 5dd6dbd commit 505979c

File tree

7 files changed

+197
-5
lines changed

7 files changed

+197
-5
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
ack_generate_info:
2-
build_date: "2025-10-21T04:38:02Z"
3-
build_hash: 6b4211163dcc34776b01da9a18217bac0f4103fd
2+
build_date: "2025-10-24T13:21:34Z"
3+
build_hash: eaabefb6bd7b2be8a1baf4478f22b3310e6921c8
44
go_version: go1.24.6
5-
version: v0.52.0
6-
api_directory_checksum: d2887bf57c4e94a2687e17c41f74c875131c0beb
5+
version: v0.52.0-6-geaabefb
6+
api_directory_checksum: e4d87325b7f3dab8a75e052c7538bc803f871048
77
api_version: v1alpha1
88
aws_sdk_go_version: v1.32.6
99
generator_config_info:
10-
file_checksum: 3c4832feff83bc9c29b40bc73bafc1d7e75ab1cd
10+
file_checksum: 171412a113b31a5564a67d45ddb3face9ca90e15
1111
original_file_name: generator.yaml
1212
last_modification:
1313
reason: API generation

apis/v1alpha1/annotation.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package v1alpha1
15+
16+
import "fmt"
17+
18+
var (
19+
// TableProvisionedThroughputManagedByAnnotation is the annotation key used to set the management
20+
// style for the provisioned throughput of a DynamoDB table. This annotation can only be set on a
21+
// table custom resource.
22+
//
23+
// The value of this annotation must be one of the following:
24+
//
25+
// - 'external-autoscaler': The provisioned throughput is managed by an external entity. Causing
26+
// the controller to completly ignore the fields `readCapacityUnits`
27+
// and `writeCapacityUnits` of `provisionedThroughput` and not reconcile
28+
// the provisioned throughput of a table.
29+
//
30+
// - 'ack-dynamodb-controller': The provisioned throughput is managed by the ACK controller.
31+
// Causing the controller to reconcile the provisioned throughput of the
32+
// table with the values of the `spec.provisionedThroughput` field
33+
// (`readCapacityUnits` and `writeCapacityUnits`).
34+
//
35+
// By default the provisioned throughput is managed by the controller. If the annotation is not set, or
36+
// the value is not one of the above, the controller will default to managing the provisioned throughput
37+
// as if the annotation was set to "controller".
38+
TableProvisionedThroughputManagedByAnnotation = fmt.Sprintf("%s/table-provisioned-throughput-managed-by", GroupVersion.Group)
39+
)
40+
41+
const (
42+
// TableProvisionedThroughputManagedByExternalAutoscaler is the value of the
43+
// TableProvisionedThroughputManagedByAnnotation annotation that indicates that the provisioned
44+
// throughput of a table is managed by an external autoscaler.
45+
TableProvisionedThroughputManagedByExternalAutoscaler = "external-autoscaler"
46+
// TableProvisionedThroughputManagedByACKController is the value of the
47+
// TableProvisionedThroughputManagedByAnnotation annotation that indicates that the provisioned
48+
// throughput of a table is managed by the ACK controller.
49+
TableProvisionedThroughputManagedByACKController = "ack-dynamodb-controller"
50+
)

apis/v1alpha1/generator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ resources:
9393
hooks:
9494
delta_pre_compare:
9595
code: customPreCompare(delta, a, b)
96+
delta_post_compare:
97+
code: customPostCompare(delta, a, b)
9698
sdk_create_post_set_output:
9799
template_path: hooks/table/sdk_create_post_set_output.go.tpl
98100
sdk_read_one_post_set_output:

generator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ resources:
9393
hooks:
9494
delta_pre_compare:
9595
code: customPreCompare(delta, a, b)
96+
delta_post_compare:
97+
code: customPostCompare(delta, a, b)
9698
sdk_create_post_set_output:
9799
template_path: hooks/table/sdk_create_post_set_output.go.tpl
98100
sdk_read_one_post_set_output:

pkg/resource/table/delta.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/resource/table/hooks.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,3 +910,44 @@ func (rm *resourceManager) updateContributorInsights(
910910

911911
return nil
912912
}
913+
914+
func getTableProvisionedThroughputManagedByAnnotation(table *svcapitypes.Table) (string, bool) {
915+
if len(table.Annotations) == 0 {
916+
return "", false
917+
}
918+
managedBy, ok := table.Annotations[svcapitypes.TableProvisionedThroughputManagedByAnnotation]
919+
return managedBy, ok
920+
}
921+
922+
func isTableProvisionedThroughputManagedByExternalAutoscaler(table *svcapitypes.Table) bool {
923+
managedBy, ok := getTableProvisionedThroughputManagedByAnnotation(table)
924+
if !ok {
925+
return false
926+
}
927+
return managedBy == svcapitypes.TableProvisionedThroughputManagedByExternalAutoscaler
928+
}
929+
930+
func customPostCompare(
931+
delta *ackcompare.Delta,
932+
a *resource,
933+
b *resource,
934+
) {
935+
// We only want to compare the ProvisionedThroughput field if and only if the
936+
// ProvisionedThroughput is managed by the controller, meaning that in the case
937+
// where the ProvisionedThroughput is managed by an external entity, we do not
938+
// want to compare the ProvisionedThroughput field.
939+
// When managed by an external entity, an annotation is set on the
940+
// table resource to indicate that the ProvisionedThroughput is managed
941+
// externally.
942+
if isTableProvisionedThroughputManagedByExternalAutoscaler(a.ko) && delta.DifferentAt("Spec.ProvisionedThroughput") {
943+
// We need to unset the ProvisionedThroughput field in the delta so that the
944+
// controller does not attempt to reconcile it.
945+
newDiffs := make([]*ackcompare.Difference, 0)
946+
for _, d := range delta.Differences {
947+
if !d.Path.Contains("Spec.ProvisionedThroughput") {
948+
newDiffs = append(newDiffs, d)
949+
}
950+
}
951+
delta.Differences = newDiffs
952+
}
953+
}

pkg/resource/table/hooks_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/stretchr/testify/require"
2323

2424
"github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1"
25+
svcapitypes "github.com/aws-controllers-k8s/dynamodb-controller/apis/v1alpha1"
2526
)
2627

2728
var (
@@ -505,3 +506,98 @@ func Test_newResourceDelta_customDeltaFunction_AttributeDefinitions(t *testing.T
505506
})
506507
}
507508
}
509+
510+
func Test_compareProvisionedThroughput(t *testing.T) {
511+
type managementType int
512+
const (
513+
managedByDefault managementType = iota
514+
managedByACKController
515+
managedByExternalAutoscaler
516+
)
517+
518+
// Helper to create table with given throughput parameters
519+
createTable := func(rpu, wpu *int64) *v1alpha1.Table {
520+
if rpu == nil && wpu == nil {
521+
return &v1alpha1.Table{
522+
Spec: v1alpha1.TableSpec{
523+
ProvisionedThroughput: nil,
524+
},
525+
}
526+
}
527+
return &v1alpha1.Table{
528+
Spec: v1alpha1.TableSpec{
529+
ProvisionedThroughput: &v1alpha1.ProvisionedThroughput{
530+
ReadCapacityUnits: rpu,
531+
WriteCapacityUnits: wpu,
532+
},
533+
},
534+
}
535+
}
536+
537+
// Helper function to apply management type to a table
538+
applyManagement := func(table *v1alpha1.Table, mgmt managementType) *v1alpha1.Table {
539+
switch mgmt {
540+
case managedByACKController:
541+
if table.Annotations == nil {
542+
table.Annotations = make(map[string]string)
543+
}
544+
table.Annotations[svcapitypes.TableProvisionedThroughputManagedByAnnotation] = svcapitypes.TableProvisionedThroughputManagedByACKController
545+
return table
546+
case managedByExternalAutoscaler:
547+
if table.Annotations == nil {
548+
table.Annotations = make(map[string]string)
549+
}
550+
table.Annotations[svcapitypes.TableProvisionedThroughputManagedByAnnotation] = svcapitypes.TableProvisionedThroughputManagedByExternalAutoscaler
551+
return table
552+
default:
553+
return table // managedByDefault
554+
}
555+
}
556+
557+
tests := []struct {
558+
name string
559+
mgmt managementType
560+
aRpu, aWpu *int64 // Table A provisioned throughput (nil means no throughput spec)
561+
bRpu, bWpu *int64 // Table B provisioned throughput (nil means no throughput spec)
562+
expectDelta bool
563+
}{
564+
// ManagedByDefault scenarios
565+
{"default: both nil ProvisionedThroughput", managedByDefault, nil, nil, nil, nil, false},
566+
{"default: (a) nil ProvisionedThroughput", managedByDefault, nil, nil, aws.Int64(5), aws.Int64(5), true},
567+
{"default: (b) nil ProvisionedThroughput", managedByDefault, aws.Int64(5), aws.Int64(5), nil, nil, true},
568+
{"default: equal ProvisionedThroughput", managedByDefault, aws.Int64(5), aws.Int64(5), aws.Int64(5), aws.Int64(5), false},
569+
{"default: different ProvisionedThroughput", managedByDefault, aws.Int64(5), aws.Int64(5), aws.Int64(10), aws.Int64(5), true},
570+
571+
// ManagedByACKController scenarios
572+
{"ack: both nil ProvisionedThroughput", managedByACKController, nil, nil, nil, nil, false},
573+
{"ack: (a) nil ProvisionedThroughput", managedByACKController, nil, nil, aws.Int64(5), aws.Int64(5), true},
574+
{"ack: (b) nil ProvisionedThroughput", managedByACKController, aws.Int64(5), aws.Int64(5), nil, nil, true},
575+
{"ack: equal ProvisionedThroughput", managedByACKController, aws.Int64(5), aws.Int64(5), aws.Int64(5), aws.Int64(5), false},
576+
{"ack: different ProvisionedThroughput", managedByACKController, aws.Int64(5), aws.Int64(5), aws.Int64(10), aws.Int64(5), true},
577+
578+
// ManagedByExternalAutoscaler scenarios (delta should be false for changes)
579+
{"external: both nil ProvisionedThroughput", managedByExternalAutoscaler, nil, nil, nil, nil, false},
580+
{"external: (a) nil ProvisionedThroughput", managedByExternalAutoscaler, nil, nil, aws.Int64(5), aws.Int64(5), false},
581+
{"external: (b) nil ProvisionedThroughput", managedByExternalAutoscaler, aws.Int64(5), aws.Int64(5), nil, nil, false},
582+
{"external: equal ProvisionedThroughput", managedByExternalAutoscaler, aws.Int64(5), aws.Int64(5), aws.Int64(5), aws.Int64(5), false},
583+
{"external: different ProvisionedThroughput", managedByExternalAutoscaler, aws.Int64(5), aws.Int64(5), aws.Int64(10), aws.Int64(5), false},
584+
}
585+
586+
for _, tt := range tests {
587+
t.Run(tt.name, func(t *testing.T) {
588+
// Create tables
589+
tableA := createTable(tt.aRpu, tt.aWpu)
590+
tableB := createTable(tt.bRpu, tt.bWpu)
591+
592+
// Apply management type
593+
tableA = applyManagement(tableA, tt.mgmt)
594+
tableB = applyManagement(tableB, tt.mgmt)
595+
596+
// Test comparison
597+
delta := newResourceDelta(&resource{tableA}, &resource{tableB})
598+
if tt.expectDelta != delta.DifferentAt("Spec.ProvisionedThroughput") {
599+
t.Errorf("customPostCompare() has delta at ProvisionedThroughput = %v, want %v", delta.DifferentAt("Spec.ProvisionedThroughput"), tt.expectDelta)
600+
}
601+
})
602+
}
603+
}

0 commit comments

Comments
 (0)