feat: Add GlobalReplicationGroup resource support - #228
NicholasBlaskey wants to merge 1 commit into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: NicholasBlaskey The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @NicholasBlaskey. Thanks for your PR. I'm waiting for a aws-controllers-k8s member to verify that this patch is reasonable to test. If it is, they should reply with Regular contributors should join the org to skip this step. Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
| # 3->2 decrease against live AWS, so this budget is deliberately generous -- | ||
| # the wait returns as soon as the target state is reached, so a large ceiling | ||
| # costs nothing on the happy path and only prevents flakes. | ||
| SCALE_WAIT_PERIODS = 80 |
There was a problem hiding this comment.
this test takes around 70 minutes to run. Is that too long for ACK?
There was a problem hiding this comment.
Does the bulk of that come from nodegroup scaling? You shouldn't need to wait for that entire operation in order to make sure that it works, I think confirming that the right scaling call is made once the GRG reaches the modifying state would be enough to ensure that the update for this case is working properly.
There was a problem hiding this comment.
Yeah it is node group scaling.
This is a good idea, but unfortunately I think the API requires us to wait for the resource to finish scaling before deleting, so while this would speed up the test I think the overall run would take the same as it waits for the scale to finish in cleanup.
There was a problem hiding this comment.
Then maybe it might be better to test the scaling logic in a unit test rather than an integration test. The other tests already cover the CRUD-reconcile-to-synced path, so we're not losing anything by testing the scaling logic with unit tests.
gobos12
left a comment
There was a problem hiding this comment.
Nice work @NicholasBlaskey! Left some comments below.
| # 3->2 decrease against live AWS, so this budget is deliberately generous -- | ||
| # the wait returns as soon as the target state is reached, so a large ceiling | ||
| # costs nothing on the happy path and only prevents flakes. | ||
| SCALE_WAIT_PERIODS = 80 |
There was a problem hiding this comment.
Does the bulk of that come from nodegroup scaling? You shouldn't need to wait for that entire operation in order to make sure that it works, I think confirming that the right scaling call is made once the GRG reaches the modifying state would be enough to ensure that the update for this case is working properly.
| ModifyGlobalReplicationGroup: | ||
| custom_implementation: CustomModifyGlobalReplicationGroup | ||
| override_values: | ||
| ApplyImmediately: aws.Bool(true) |
There was a problem hiding this comment.
Should this always default to true?
There was a problem hiding this comment.
default/override_values here I don't believe ends up doing anything here because of the custom implementation required.
Removed override_values block here.
There was a problem hiding this comment.
To add some detail here, my understanding is we need a complete custom implementation for the whole method because certain fields on the global replication group cannot be sent in the same update (and others must be sent in the same update)
For example the API rejects sending Description, AutomaticFailoverEnabled, and CacheNodeType with anything else with
│ InvalidParameterValue: Global Replication Group modifications only support modifying one field per request.
| // We can only proceed with delete logic when in a stable state: | ||
| // - "available" means secondaries are attached → need to disassociate first | ||
| // - "primary-only" means no secondaries → can delete directly | ||
| if !isGlobalPrimaryOnly(r) && !isGlobalAvailable(r) { |
There was a problem hiding this comment.
I'm probably just confused on the wording in the comments, but wouldn't the delete get stuck here? It seems it's waiting for the resource to be in both the primary-only and available states, but that would require the resources to have secondaries attached while also not having any secondaries? Some clarification in the comments (whether that be here or in generator.yaml) would be nice.
There was a problem hiding this comment.
updated comment to be clearer
| name: $RG_ID | ||
| spec: | ||
| cacheNodeType: cache.t3.micro | ||
| cacheNodeType: cache.r6g.large |
There was a problem hiding this comment.
Does changing this affect the e2e tests for replication groups?
There was a problem hiding this comment.
Yes. I don't think it caused it to fail but split this up to avoid impacting and causing the existing tests to use larger instance types than needed.
| GlobalReplicationGroupID: | ||
| is_read_only: true | ||
| ARN: | ||
| is_read_only: true | ||
| ClusterEnabled: | ||
| is_read_only: true | ||
| AtRestEncryptionEnabled: | ||
| is_read_only: true | ||
| TransitEncryptionEnabled: | ||
| is_read_only: true | ||
| AuthTokenEnabled: | ||
| is_read_only: true |
There was a problem hiding this comment.
Are these "read-only" specs necessary? They seem to be a part of the data type and aren't required for any of the API calls, adding them here doesn't make sense to me.
There was a problem hiding this comment.
These shouldn't have been added. Removing them.
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned by Describe at all. Their last-requested values are tracked in annotations, matching the existing ReplicationGroup resource, so the delta compares against what was actually last sent to AWS. - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values/custom_implementation config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controller` from code-generator v0.62.1 (build hash 65d45b2e), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
b1fed40 to
5f5a582
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned by Describe at all. Their last-requested values are tracked in annotations, matching the existing ReplicationGroup resource, so the delta compares against what was actually last sent to AWS. - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile, comparing desired against observed state per field and letting any remaining changes converge over subsequent reconciles. Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed, and reflected-back fields are non-nil on every reconcile after the first -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controller` from code-generator v0.62.1, matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
5f5a582 to
70c7b78
Compare
|
/ok-to-test |
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned by Describe at all. Their last-requested values are tracked in annotations, matching the existing ReplicationGroup resource, so the delta compares against what was actually last sent to AWS. - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile, comparing desired against observed state per field and letting any remaining changes converge over subsequent reconciles. Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed, and reflected-back fields are non-nil on every reconcile after the first -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controller` from code-generator v0.62.1, matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
70c7b78 to
29bdf9f
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned by Describe at all. Their last-requested values are tracked in annotations, matching the existing ReplicationGroup resource, so the delta compares against what was actually last sent to AWS. - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile, comparing desired against observed state per field and letting any remaining changes converge over subsequent reconciles. Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed, and reflected-back fields are non-nil on every reconcile after the first -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
29bdf9f to
a9ad254
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, no-op automatic-failover handling, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned as top-level fields by DescribeGlobalReplicationGroups. AutomaticFailover is derived from live per-member state (which Describe does return), so the delta compares against reality rather than a tracked value; a global datastore's primary always has failover enabled, so a redundant Modify (which AWS rejects with "Requested product type is the same current product type") is never issued. CacheParameterGroupName cannot be read back at all -- ElastiCache copies it per member -- so its last-requested value is tracked in an annotation, and it is only valid as part of a major engine upgrade (a change on its own is surfaced as terminal). - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile. When more than one field changed it applies the first and returns an explicit requeue for the rest, because IsSynced() is status-based: a fast change (e.g. Description) returns the group to a synced status almost immediately, so the remaining delta would otherwise be stranded until the next resync. The delta is recomputed from the CR spec and live AWS state each pass, so this converges over multiple reconciles and is correct across controller restarts (no in-memory pending-change state). Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). Permanent misconfigurations (a major upgrade with no parameter group, or a parameter-group change outside an upgrade) are surfaced as terminal errors rather than requeued forever. The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
a9ad254 to
998ede1
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, no-op automatic-failover handling, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned as top-level fields by DescribeGlobalReplicationGroups. AutomaticFailover is derived from live per-member state (which Describe does return), so the delta compares against reality rather than a tracked value; a global datastore's primary always has failover enabled, so a redundant Modify (which AWS rejects with "Requested product type is the same current product type") is never issued. CacheParameterGroupName cannot be read back at all -- ElastiCache copies it per member -- so its last-requested value is tracked in an annotation, and it is only valid as part of a major engine upgrade (a change on its own is surfaced as terminal). - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile. When more than one field changed it applies the first and returns an explicit requeue for the rest, because IsSynced() is status-based: a fast change (e.g. Description) returns the group to a synced status almost immediately, so the remaining delta would otherwise be stranded until the next resync. The delta is recomputed from the CR spec and live AWS state each pass, so this converges over multiple reconciles and is correct across controller restarts (no in-memory pending-change state). Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). Permanent misconfigurations (a major upgrade with no parameter group, or a parameter-group change outside an upgrade) are surfaced as terminal errors rather than requeued forever. The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.10 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
998ede1 to
6e044a8
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, no-op automatic-failover handling, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned as top-level fields by DescribeGlobalReplicationGroups. AutomaticFailover is derived from live per-member state (which Describe does return), so the delta compares against reality rather than a tracked value; a global datastore's primary always has failover enabled, so a redundant Modify (which AWS rejects with "Requested product type is the same current product type") is never issued. CacheParameterGroupName cannot be read back at all -- ElastiCache copies it per member -- so its last-requested value is tracked in an annotation, and it is only valid as part of a major engine upgrade (a change on its own is surfaced as terminal). - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile. When more than one field changed it applies the first and returns an explicit requeue for the rest, because IsSynced() is status-based: a fast change (e.g. Description) returns the group to a synced status almost immediately, so the remaining delta would otherwise be stranded until the next resync. The delta is recomputed from the CR spec and live AWS state each pass, so this converges over multiple reconciles and is correct across controller restarts (no in-memory pending-change state). Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). Permanent misconfigurations (a major upgrade with no parameter group, or a parameter-group change outside an upgrade) are surfaced as terminal errors rather than requeued forever. The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.10 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
6e044a8 to
6d904fc
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, no-op automatic-failover handling, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned as top-level fields by DescribeGlobalReplicationGroups. AutomaticFailover is derived from live per-member state (which Describe does return), so the delta compares against reality rather than a tracked value; a global datastore's primary always has failover enabled, so a redundant Modify (which AWS rejects with "Requested product type is the same current product type") is never issued. CacheParameterGroupName cannot be read back at all -- ElastiCache copies it per member -- so its last-requested value is tracked in an annotation, and it is only valid as part of a major engine upgrade (a change on its own is surfaced as terminal). - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile. When more than one field changed it applies the first and returns an explicit requeue for the rest, because IsSynced() is status-based: a fast change (e.g. Description) returns the group to a synced status almost immediately, so the remaining delta would otherwise be stranded until the next resync. The delta is recomputed from the CR spec and live AWS state each pass, so this converges over multiple reconciles and is correct across controller restarts (no in-memory pending-change state). Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). Permanent misconfigurations (a major upgrade with no parameter group, or a parameter-group change outside an upgrade) are surfaced as terminal errors rather than requeued forever. The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.10 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
6d904fc to
a27ad2d
Compare
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication. Implementation notes: - generator.yaml: resource config, field renames, custom delete/update hooks, and a NodeGroupCount custom field for declarative shard scaling - hooks.go: multi-phase delete (disassociate secondaries before deleting the global group), custom update (shard scaling plus modify), and Describe output mapping - E2E tests covering create, modify, no-op automatic-failover handling, multi-field convergence, shard scale up/down, and invalid-primary terminal handling Behaviors worth noting, all verified against live AWS: - A global group with no secondaries reports 'primary-only' and never 'available', so readiness cannot key off 'available' alone. - Description must be renamed in the DescribeGlobalReplicationGroups output as well as the Create/Modify inputs. Without the Describe rename the field is never mapped back from AWS, so the observed state keeps the desired value and the delta never detects a change. - AutomaticFailoverEnabled and CacheParameterGroupName are not returned as top-level fields by DescribeGlobalReplicationGroups. AutomaticFailover is derived from live per-member state (which Describe does return), so the delta compares against reality rather than a tracked value; a global datastore's primary always has failover enabled, so a redundant Modify (which AWS rejects with "Requested product type is the same current product type") is never issued. CacheParameterGroupName cannot be read back at all -- ElastiCache copies it per member -- so its last-requested value is tracked in an annotation, and it is only valid as part of a major engine upgrade (a change on its own is surfaced as terminal). - A decrease in shard count requires GlobalNodeGroupsToRetain, which is why increase and decrease are separate API calls. - Node group scaling holds the group in 'modifying' for 15-30 minutes, and on a decrease the shard count only drops at the very end, so the E2E waits key off shard count plus a settled status rather than status alone. - Global Datastore requires large-and-above M/R/C-family node types and automatic failover enabled on the primary, so the GRG E2E tests use a dedicated ReplicationGroup fixture rather than the shared one used by test_replicationgroup.py. Update is handled entirely by a custom hook (update_operation.custom_method_name) rather than the generated request path, because ModifyGlobalReplicationGroup does not accept arbitrary combinations of fields in one request. Verified empirically against the live API: - The independent fields (Description, AutomaticFailoverEnabled, CacheNodeType) must each be sent in their own Modify call; a request carrying more than one is rejected. - The engine-upgrade fields must be sent together: Engine + EngineVersion for a minor upgrade, and Engine + EngineVersion + CacheParameterGroupName for a major upgrade (a lone Engine yields "No modifications requested"; Engine + EngineVersion without a parameter group yields "Parameter group must be specified for major engine version upgrade"). The hook therefore applies one logical change per reconcile. When more than one field changed it applies the first and returns an explicit requeue for the rest, because IsSynced() is status-based: a fast change (e.g. Description) returns the group to a synced status almost immediately, so the remaining delta would otherwise be stranded until the next resync. The delta is recomputed from the CR spec and live AWS state each pass, so this converges over multiple reconciles and is correct across controller restarts (no in-memory pending-change state). Engine versions are compared with util.EngineVersionsMatch so an AWS-normalized "7.1.0" is not seen as different from a requested "7.1" (which would otherwise re-issue the upgrade every reconcile). Permanent misconfigurations (a major upgrade with no parameter group, or a parameter-group change outside an upgrade) are surfaced as terminal errors rather than requeued forever. The generated request builder cannot express these rules -- it only checks whether a Spec field is non-nil, not whether it changed -- so a fully custom Update handler is required here even though the sibling ReplicationGroup resource can rely on the generated path (its ModifyReplicationGroup API has no such field-combination restriction). Incorporates review feedback (see PR aws-controllers-k8s#228): - Removed dead override_values config for ModifyGlobalReplicationGroup and the Increase/DecreaseNodeGroups operations -- Update bypasses the generated request path entirely via update_operation.custom_method_name, so this config had no effect. - Removed redundant is_read_only: true on six Status-only fields that the generator already places in Status by default. - Clarified the delete-phase stable-state comment in hooks.go. Generated with `SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.10 make build-controller` from code-generator v0.62.1-4-gd4aaca5 (main), matching the version recorded in ack-generate-metadata.yaml. Resolves aws-controllers-k8s/community#1048
a27ad2d to
bb62e8b
Compare
gustavodiaz7722
left a comment
There was a problem hiding this comment.
Reviewed this on top of bb62e8b, and validated the behavior on a live cluster (EKS + this controller image, 60s resync) against real ElastiCache in us-west-2, rather than by reading the diff alone. Build and unit tests are clean, the delete state machine works exactly as described (verified end-to-end, primary correctly retained), and the one-change-per-Modify strategy matches what the API actually accepts.
Five things need fixing, four of them one-liners in generator.yaml. The two substantive ones are that automaticFailoverEnabled and cacheParameterGroupName are currently discarded while the CR reports ACK.ResourceSynced=True. Details inline.
For reference, what I ran live: created a cache.r6g.large / redis 7.0 primary with failover enabled, then a GRG with cacheParameterGroupName set and no nodeGroupCount, waited for primary-only + Synced, then patched automaticFailoverEnabled: false, then patched description + cacheParameterGroupName together, then deleted. nodeGroupCount write-back and the scaling paths I did not exercise live (my primary was cluster-mode disabled, so GlobalNodeGroups came back empty).
| override_values: | ||
| ApplyImmediately: aws.Bool(true) | ||
| DescribeGlobalReplicationGroups: | ||
| set_output_custom_method_name: CustomDescribeGlobalReplicationGroupsSetOutput |
There was a problem hiding this comment.
automaticFailoverEnabled is silently dropped — the read path never asks for member info.
newListRequestPayload builds DescribeGlobalReplicationGroupsInput without ShowMemberInfo, and AWS only returns Members when that flag is true (// Returns the list of members that comprise the Global datastore.). So in the read path ko.Status.Members is always empty, deriveAutomaticFailoverEnabled always returns nil, and the fallback in CustomDescribeGlobalReplicationGroupsSetOutput sets latest.Spec.AutomaticFailoverEnabled = r.ko.Spec.AutomaticFailoverEnabled — i.e. observed is copied from desired, so the delta can never fire.
Observed live: patched spec.automaticFailoverEnabled: false on a synced GRG. Across 6 reconciles (60s resync) the CR stayed ACK.ResourceSynced=True, status.members stayed null, and AWS still reported the primary member as AutomaticFailover: enabled. describe-global-replication-groups --show-member-info returned the member the whole time.
Two consequences: the field is a no-op in both directions, and status.members is a CRD field that never populates.
This also means test_modify_automatic_failover_is_noop passes for a different reason than the one documented — no Modify is issued because there is no observed state at all, not because the derived value matched.
Suggested fix, declaratively here rather than in a hook (setSDKReadMany honors override_values):
DescribeGlobalReplicationGroups:
override_values:
ShowMemberInfo: aws.Bool(true)
set_output_custom_method_name: CustomDescribeGlobalReplicationGroupsSetOutputWorth noting that once member info is actually present, customSetGlobalReplicationGroupOutput becomes a problem: it re-maps Members (which generated sdkFind already mapped) but always takes &afStr, whereas the generated code guards with if f10iter.AutomaticFailover != "". An empty enum then becomes a non-nil "", which deriveAutomaticFailoverEnabled sends to its default: branch and reports false instead of "undeterminable" — the opposite of the "nil status on a member -> nil" case the unit test asserts. Since the generated mapping already covers GlobalReplicationGroupId, GlobalNodeGroups, and Members, dropping that function looks like the cleanest fix.
| } | ||
| // Record the values of fields the Describe API does not return, so future | ||
| // reconciles can detect when the user actually changes them. | ||
| rm.setLastRequestedUnreadableFields(desired, ko) |
There was a problem hiding this comment.
cacheParameterGroupName is recorded as "last requested" without ever being sent, so the field is silently discarded.
CreateGlobalReplicationGroupInput has exactly three members — GlobalReplicationGroupIdSuffix, PrimaryReplicationGroupId, GlobalReplicationGroupDescription. There is no parameter group on create, and the generated newCreateRequestPayload confirms it. But this line records spec.cacheParameterGroupName unconditionally, so restoreUnreadableFieldsFromAnnotations then hands back the user's own value as "observed" and the delta never fires. A cacheParameterGroupName set at creation time is therefore ignored, permanently, with no error and no condition.
The same thing happens on update. A CPGN change is not counted in pending (only descChanged, afeChanged, nodeTypeChanged, engineGroupChanged are), so when it accompanies another change the switch takes the other branch, the request goes out without CPGN, and setGlobalReplicationGroupOutput still records it as applied. The comment on modifyGlobalReplicationGroup — "the only annotated field is CacheParameterGroupName, which is only sent when it is the sole pending change" — holds for the requeue path but not for the annotation write.
Observed live: created with cacheParameterGroupName: default.redis7 (annotation written, nothing sent), then patched description + cacheParameterGroupName: default.redis7.cluster.on in one apply. The description landed on AWS, the annotation flipped to default.redis7.cluster.on, no ACK.Terminal, Synced=True — and AWS never received either value. default.redis7.cluster.on would have been rejected for that group had it actually been sent, which is what makes it a clean probe.
Suggested fix: only write the annotation where input.CacheParameterGroupName is actually populated (inside the engineGroupChanged branch, after the call succeeds), and drop the setLastRequestedUnreadableFields call from this create hook. Also fold the CPGN-differs condition into pending so a CPGN change combined with another change still reaches the existing terminal error instead of being swallowed.
| - InvalidParameterCombination | ||
| - ServiceLinkedRoleNotFoundFault | ||
| - ReplicationGroupNotFoundFault | ||
| - InvalidReplicationGroupState |
There was a problem hiding this comment.
InvalidReplicationGroupState is retryable, not terminal.
This is the error AWS returns while the primary is busy, and the message says so explicitly. Called directly against a primary that was not available:
An error occurred (InvalidReplicationGroupState) when calling the CreateGlobalReplicationGroup
operation: Replication Group is not in a valid state. Please retry after the modification on the
Replication group grg-review-primary is complete.
As a terminal code, applying a GRG manifest alongside a still-creating primary — the normal case when primaryReplicationGroupID is used rather than the ref, which is also what the e2e fixtures do — drives the resource permanently to ACK.Terminal=True and requires deleting and recreating the CR. Same for a GRG update issued while the primary happens to be modifying.
Suggested fix: drop this entry and let it requeue. ReplicationGroupNotFoundFault (line above) already covers the genuinely terminal case that test_invalid_primary_terminal exercises, so the test is unaffected.
| Status: | ||
| is_read_only: true | ||
| print: | ||
| name: STATUS |
There was a problem hiding this comment.
Duplicate STATUS printer column.
This field-level print: and the additional_columns STATUS entry below both emit .status.status, so the CRD carries the column twice:
- additionalPrinterColumns:
- jsonPath: .status.status
name: STATUS
- jsonPath: .status.status
name: STATUSLive kubectl get:
NAME STATUS STATUS NODE-TYPE SYNCED AGE
grg-review primary-only primary-only True 7m
Suggested fix: drop this print: block and keep the additional_columns entry, which carries the index that order_by: index uses.
Minor, in the same two lines: is_read_only: true is redundant here — Status appears only in Describe output, so the generator places it in Status by default. It is the same redundancy already removed from the other six fields in the last revision, so with the print: block gone the whole Status: entry can go.
| type: string | ||
| index: 10 | ||
| - name: NODE-TYPE | ||
| json_path: .status.cacheNodeType |
There was a problem hiding this comment.
NODE-TYPE column points at a path that is always empty.
CacheNodeType is generated into Spec, not Status — generated sdkFind does ko.Spec.CacheNodeType = elem.CacheNodeType (it is configured from: ModifyGlobalReplicationGroup). So .status.cacheNodeType never resolves and the column renders blank. Live, with spec.cacheNodeType: cache.r6g.large set:
NAME STATUS STATUS NODE-TYPE SYNCED AGE
grg-review primary-only primary-only True 7m
Suggested fix: json_path: .spec.cacheNodeType.
bb62e8b to
9f22fb8
Compare
|
@NicholasBlaskey: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
@NicholasBlaskey Can you fix merge conflict? There was a new runtime release that was merged #232 |
| // After a successful Create call, capture the full GlobalReplicationGroupId | ||
| // (which includes the auto-generated region prefix) into status. | ||
| // The user only provides the suffix in spec; this is the canonical ID for all | ||
| // subsequent API calls. | ||
| if resp.GlobalReplicationGroup != nil && resp.GlobalReplicationGroup.GlobalReplicationGroupId != nil { | ||
| ko.Status.GlobalReplicationGroupID = resp.GlobalReplicationGroup.GlobalReplicationGroupId | ||
| } | ||
| if resp.GlobalReplicationGroup != nil && resp.GlobalReplicationGroup.Status != nil { | ||
| ko.Status.Status = resp.GlobalReplicationGroup.Status | ||
| } |
There was a problem hiding this comment.
- File:
templates/hooks/global_replication_group/sdk_create_post_set_output.go.tpl,generator.yaml - Issue: The hook is dead code. Generated
sdkCreatealready assigns both values from the same response before the hook body runs; the hook re-does exactly those two assignments. - Fix: Delete the template file and the
hooks.sdk_create_post_set_outputblock underGlobalReplicationGroupingenerator.yaml, then regenerate. - Verified fix: Removing both and regenerating deletes exactly 11 lines from
sdk.goand changes nothing else anywhere in the tree. The surviving generated code still setsko.Status.GlobalReplicationGroupID(sdk.go:319) andko.Status.Status(sdk.go:349). Controller builds.
AI-generated comment (Kiro).
| # --- DELETE --- | ||
| k8s.delete_custom_resource(reference) | ||
|
|
||
| # The delete is multi-step: disassociate secondaries (none here), then delete |
There was a problem hiding this comment.
- File:
test/e2e/tests/test_global_replication_group.py - Issue: two of the most intricate new paths are never reached by a test. The suite's full inventory is six tests --
test_create_and_verify,test_invalid_primary_terminal,test_modify_description,test_modify_automatic_failover_is_noop,test_modify_multiple_fields_converges,test_scale_up_and_down-- and every one of them deletes fromprimary-only. Searching all 767 lines fordisassoc|secondary|associate_global|engineVersion|cacheParameterGroupreturns exactly one hit: the comment on this line. So delete Phase 2 (disassociate secondaries before deleting the global group,hooks.go:125-153) and the engine-upgrade branch ofmodifyGlobalReplicationGroup(hooks.go:378-392) are both untested. The engine branch is the one place the Engine + EngineVersion + CacheParameterGroupName grouping rule is applied, and the only path that writes thelast-requested-cache-parameter-group-nameannotation the resource relies on for delta correctness. - Fix: add
test_engine_version_upgradetoTestGlobalReplicationGroupModifycovering a major upgrade withengine,engineVersion, andcacheParameterGroupNamepatched together, asserting the AWS-sideEngineVersionlands, the resource returns to Synced, and the annotation is present on the CR afterward. That is single-region and runnable in CI. For Phase 2, either add a cross-region test gated to the multi-region suite, or note in the PR description that it was validated manually and cannot run in CI.
AI-generated comment (Kiro).
Add support for the ElastiCache GlobalReplicationGroup resource, which backs the Global Datastore feature for cross-region replication.
Implementation notes:
Generated with
SERVICE=elasticache AWS_SDK_GO_VERSION=v1.41.0 make build-controllerfrom code-generator v0.62.0 (build hash db232581), matching the version recorded in ack-generate-metadata.yaml.Resolves aws-controllers-k8s/community#1048
Issue #, if available:
Description of changes:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.