Skip to content

Commit

Permalink
Add EngineLifecycleSupport integration to Global Clusters (aws-cloudf…
Browse files Browse the repository at this point in the history
…ormation#539)

Global clusters can be created with the EngineLifecycleSupport
field via CloudFormation. All subsequent updates to this field
on the resource will be blocked to prevent unintended customer
resource replacement. EngineLifecycleSupport is marked as
"Some Interruption" causing to preserve future compatibility for
when a formal modify option is available for this field.

cr: https://code.amazon.com/reviews/CR-132055479
  • Loading branch information
anson1014 authored and Diogo Henriques committed Jun 12, 2024
1 parent 6f48354 commit 78b8f08
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 4 deletions.
4 changes: 4 additions & 0 deletions aws-rds-globalcluster/aws-rds-globalcluster.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"aurora-postgresql"
]
},
"EngineLifecycleSupport": {
"description": "The life cycle type of the global cluster. You can use this setting to enroll your global cluster into Amazon RDS Extended Support.",
"type": "string"
},
"EngineVersion": {
"description": "The version number of the database engine to use. If you specify the SourceDBClusterIdentifier property, don't specify this property. The value is inherited from the cluster.",
"type": "string"
Expand Down
12 changes: 12 additions & 0 deletions aws-rds-globalcluster/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"Type" : "AWS::RDS::GlobalCluster",
"Properties" : {
"<a href="#engine" title="Engine">Engine</a>" : <i>String</i>,
"<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>" : <i>String</i>,
"<a href="#engineversion" title="EngineVersion">EngineVersion</a>" : <i>String</i>,
"<a href="#deletionprotection" title="DeletionProtection">DeletionProtection</a>" : <i>Boolean</i>,
"<a href="#globalclusteridentifier" title="GlobalClusterIdentifier">GlobalClusterIdentifier</a>" : <i>String</i>,
Expand All @@ -28,6 +29,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
Type: AWS::RDS::GlobalCluster
Properties:
<a href="#engine" title="Engine">Engine</a>: <i>String</i>
<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a> : <i>String</i>,
<a href="#engineversion" title="EngineVersion">EngineVersion</a>: <i>String</i>
<a href="#deletionprotection" title="DeletionProtection">DeletionProtection</a>: <i>Boolean</i>
<a href="#globalclusteridentifier" title="GlobalClusterIdentifier">GlobalClusterIdentifier</a>: <i>String</i>
Expand All @@ -50,6 +52,16 @@ _Allowed Values_: <code>aurora</code> | <code>aurora-mysql</code> | <code>aurora

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)

#### EngineLifecycleSupport

The life cycle type of the global cluster. You can use this setting to enroll your global cluster into Amazon RDS Extended Support.

_Required_: No

_Type_: String

_Update requires_: [Some interruptions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt)

#### EngineVersion

The version number of the database engine to use. If you specify the SourceDBClusterIdentifier property, don't specify this property. The value is inherited from the cluster.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ResourceModel toResourceModel(GlobalCluster cluster) {
builder.engineVersion(cluster.engineVersion());
builder.storageEncrypted(cluster.storageEncrypted());
builder.deletionProtection(cluster.deletionProtection());
builder.engineLifecycleSupport(cluster.engineLifecycleSupport());

if (cluster.hasGlobalClusterMembers()) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static CreateGlobalClusterRequest createGlobalClusterRequest(final ResourceModel
.globalClusterIdentifier(model.getGlobalClusterIdentifier())
.sourceDBClusterIdentifier(StringUtils.isBlank(dbClusterArn) ? model.getSourceDBClusterIdentifier() : dbClusterArn)
.storageEncrypted(model.getStorageEncrypted())
.engineLifecycleSupport(model.getEngineLifecycleSupport())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package software.amazon.rds.globalcluster;

import java.util.Objects;
import org.apache.commons.lang3.BooleanUtils;

import software.amazon.awssdk.services.rds.RdsClient;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
Expand All @@ -19,14 +21,21 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(final Amaz
ResourceModel previousModel = request.getPreviousResourceState();
ResourceModel desiredModel = request.getDesiredResourceState();

return proxy.initiate("rds::update-global-cluster", proxyClient, request.getDesiredResourceState(), callbackContext)
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
.then(progress -> {
if(!Objects.equals(previousModel.getEngineLifecycleSupport(), desiredModel.getEngineLifecycleSupport())) {
throw new CfnInvalidRequestException("EngineLifecycleSupport cannot be modified.");
}
return progress;
})
.then(progress -> proxy.initiate("rds::update-global-cluster", proxyClient, request.getDesiredResourceState(), callbackContext)
// request to update global cluster
.translateToServiceRequest(model -> Translator.modifyGlobalClusterRequest(previousModel, desiredModel, BooleanUtils.isTrue(request.getRollback())))
.backoffDelay(BACKOFF_STRATEGY)
.makeServiceCall((modifyGlobalClusterRequest, proxyClient1) -> proxyClient1.injectCredentialsAndInvokeV2(modifyGlobalClusterRequest, proxyClient1.client()::modifyGlobalCluster))
.stabilize(((modifyGlobalClusterRequest, modifyGlobalClusterResponse, proxyClient1, resourceModel, callbackContext1) ->
isGlobalClusterStabilized(proxyClient1, desiredModel)))
.progress()
.then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
.then(readProgress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package software.amazon.rds.globalcluster;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -11,6 +12,7 @@
import software.amazon.awssdk.http.SdkHttpResponse;
import software.amazon.awssdk.services.rds.RdsClient;
import software.amazon.awssdk.services.rds.model.*;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.proxy.*;

import java.time.Duration;
Expand Down Expand Up @@ -51,7 +53,9 @@ public void handleRequest_SimpleSuccess() {
final DescribeGlobalClustersResponse describeGlobalClustersResponse = DescribeGlobalClustersResponse.builder().globalClusters(GLOBAL_CLUSTER_ACTIVE).build();
when(proxyRdsClient.client().describeGlobalClusters(any(DescribeGlobalClustersRequest.class))).thenReturn(describeGlobalClustersResponse);

final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder().desiredResourceState(RESOURCE_MODEL_UPDATE).build();
final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
.previousResourceState(RESOURCE_MODEL)
.desiredResourceState(RESOURCE_MODEL_UPDATE).build();
final ProgressEvent<ResourceModel, CallbackContext> response = handler.handleRequest(proxy, request, new CallbackContext(), proxyRdsClient, logger);
assertThat(response).isNotNull();
assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
Expand Down Expand Up @@ -99,7 +103,9 @@ public void handleRequest_ReturnsFailedResponse_WhenRdsClientThrowsClusterNotFou

when(proxyRdsClient.client().modifyGlobalCluster(any(ModifyGlobalClusterRequest.class))).thenThrow(exception);

final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder().desiredResourceState(RESOURCE_MODEL_UPDATE).build();
final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
.previousResourceState(RESOURCE_MODEL)
.desiredResourceState(RESOURCE_MODEL_UPDATE).build();
final ProgressEvent<ResourceModel, CallbackContext> response = handler.handleRequest(proxy, request, new CallbackContext(), proxyRdsClient, logger);

assertThat(response).isNotNull();
Expand All @@ -112,4 +118,31 @@ public void handleRequest_ReturnsFailedResponse_WhenRdsClientThrowsClusterNotFou
verify(rds).serviceName();
verifyNoMoreInteractions(rds);
}

@Test
public void handleRequest_updateEngineLifecycleSupportShouldFail() {
ResourceModel previousState = ResourceModel.builder()
.globalClusterIdentifier(GLOBALCLUSTER_IDENTIFIER)
.engineVersion(ENGINE_VERSION)
.engine(ENGINE)
.engineLifecycleSupport("open-source-rds-extended-support")
.build();

ResourceModel desiredState = ResourceModel.builder()
.globalClusterIdentifier(GLOBALCLUSTER_IDENTIFIER)
.engineVersion(ENGINE_VERSION)
.engine(ENGINE)
.engineLifecycleSupport("open-source-rds-extended-support-disabled")
.build();

final ResourceHandlerRequest<ResourceModel> request = ResourceHandlerRequest.<ResourceModel>builder()
.previousResourceState(previousState)
.desiredResourceState(desiredState).build();

try {
handler.handleRequest(proxy, request, new CallbackContext(), proxyRdsClient, logger);
} catch (CfnInvalidRequestException e) {
Assertions.assertEquals(e.getMessage(), "Invalid request provided: EngineLifecycleSupport cannot be modified.");
}
}
}

0 comments on commit 78b8f08

Please sign in to comment.