Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DBCluster] Support Local Write Forwarding #543

Merged
merged 1 commit into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions aws-rds-dbcluster/aws-rds-dbcluster.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
"description": "A value that indicates whether to enable mapping of AWS Identity and Access Management (IAM) accounts to database accounts. By default, mapping is disabled.",
"type": "boolean"
},
"EnableLocalWriteForwarding": {
"description": "Specifies whether read replicas can forward write operations to the writer DB instance in the DB cluster. By default, write operations aren't allowed on reader DB instances.",
"type": "boolean"
},
"Engine": {
"description": "The name of the database engine to be used for this DB cluster. Valid Values: aurora (for MySQL 5.6-compatible Aurora), aurora-mysql (for MySQL 5.7-compatible Aurora), and aurora-postgresql",
"type": "string"
Expand Down
12 changes: 12 additions & 0 deletions aws-rds-dbcluster/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"<a href="#enableglobalwriteforwarding" title="EnableGlobalWriteForwarding">EnableGlobalWriteForwarding</a>" : <i>Boolean</i>,
"<a href="#enablehttpendpoint" title="EnableHttpEndpoint">EnableHttpEndpoint</a>" : <i>Boolean</i>,
"<a href="#enableiamdatabaseauthentication" title="EnableIAMDatabaseAuthentication">EnableIAMDatabaseAuthentication</a>" : <i>Boolean</i>,
"<a href="#enablelocalwriteforwarding" title="EnableLocalWriteForwarding">EnableLocalWriteForwarding</a>" : <i>Boolean</i>,
"<a href="#engine" title="Engine">Engine</a>" : <i>String</i>,
"<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>" : <i>String</i>,
"<a href="#enginemode" title="EngineMode">EngineMode</a>" : <i>String</i>,
Expand Down Expand Up @@ -103,6 +104,7 @@ Properties:
<a href="#enableglobalwriteforwarding" title="EnableGlobalWriteForwarding">EnableGlobalWriteForwarding</a>: <i>Boolean</i>
<a href="#enablehttpendpoint" title="EnableHttpEndpoint">EnableHttpEndpoint</a>: <i>Boolean</i>
<a href="#enableiamdatabaseauthentication" title="EnableIAMDatabaseAuthentication">EnableIAMDatabaseAuthentication</a>: <i>Boolean</i>
<a href="#enablelocalwriteforwarding" title="EnableLocalWriteForwarding">EnableLocalWriteForwarding</a>: <i>Boolean</i>
<a href="#engine" title="Engine">Engine</a>: <i>String</i>
<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>: <i>String</i>
<a href="#enginemode" title="EngineMode">EngineMode</a>: <i>String</i>
Expand Down Expand Up @@ -382,6 +384,16 @@ _Type_: Boolean

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

#### EnableLocalWriteForwarding

Specifies whether read replicas can forward write operations to the writer DB instance in the DB cluster. By default, write operations aren't allowed on reader DB instances.

_Required_: No

_Type_: Boolean

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

#### Engine

The name of the database engine to be used for this DB cluster. Valid Values: aurora (for MySQL 5.6-compatible Aurora), aurora-mysql (for MySQL 5.7-compatible Aurora), and aurora-postgresql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import software.amazon.awssdk.services.rds.model.InvalidSubnetException;
import software.amazon.awssdk.services.rds.model.InvalidVpcNetworkStateException;
import software.amazon.awssdk.services.rds.model.KmsKeyNotAccessibleException;
import software.amazon.awssdk.services.rds.model.LocalWriteForwardingStatus;
import software.amazon.awssdk.services.rds.model.NetworkTypeNotSupportedException;
import software.amazon.awssdk.services.rds.model.SnapshotQuotaExceededException;
import software.amazon.awssdk.services.rds.model.StorageQuotaExceededException;
Expand Down Expand Up @@ -334,16 +335,22 @@ protected boolean isDBClusterStabilized(
final boolean isNoPendingChangesResult = isNoPendingChanges(dbCluster);
final boolean isMasterUserSecretStabilizedResult = isMasterUserSecretStabilized(dbCluster);
final boolean isGlobalWriteForwardingStabilizedResult = isGlobalWriteForwardingStabilized(dbCluster);
final boolean isLocalWriteForwardingStabilizedResult = isLocalWriteForwardingStabilized(dbCluster);

requestLogger.log(String.format("isDbClusterStabilized: %b", isDBClusterStabilizedResult),
ImmutableMap.of("isDbClusterAvailable", isDBClusterStabilizedResult,
"isNoPendingChanges", isNoPendingChangesResult,
"isMasterUserSecretStabilized", isMasterUserSecretStabilizedResult,
"isGlobalWriteForwardingStabilized", isGlobalWriteForwardingStabilizedResult),
"isGlobalWriteForwardingStabilized", isGlobalWriteForwardingStabilizedResult,
"isLocalWriteForwardingStabilized", isLocalWriteForwardingStabilizedResult),
ImmutableMap.of("Description", "isDBClusterStabilized method will be repeatedly" +
" called with a backoff mechanism after the modify call until it returns true. This" +
" process will continue until all included flags are true."));
return isDBClusterStabilizedResult && isNoPendingChangesResult && isMasterUserSecretStabilizedResult && isGlobalWriteForwardingStabilizedResult;
return isDBClusterStabilizedResult &&
isNoPendingChangesResult &&
isMasterUserSecretStabilizedResult &&
isGlobalWriteForwardingStabilizedResult &&
isLocalWriteForwardingStabilizedResult;
}

private void resourceStabilizationTime(final CallbackContext context) {
Expand All @@ -369,6 +376,11 @@ protected static boolean isGlobalWriteForwardingStabilized(DBCluster dbCluster)
dbCluster.globalWriteForwardingStatus() != WriteForwardingStatus.DISABLING);
}

protected static boolean isLocalWriteForwardingStabilized(DBCluster dbCluster) {
return (dbCluster.localWriteForwardingStatus() != LocalWriteForwardingStatus.ENABLING &&
dbCluster.localWriteForwardingStatus() != LocalWriteForwardingStatus.DISABLING);
}

protected boolean isClusterRemovedFromGlobalCluster(
final ProxyClient<RdsClient> proxyClient,
final String previousGlobalClusterIdentifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import software.amazon.awssdk.services.rds.model.DescribeGlobalClustersRequest;
import software.amazon.awssdk.services.rds.model.DisableHttpEndpointRequest;
import software.amazon.awssdk.services.rds.model.EnableHttpEndpointRequest;
import software.amazon.awssdk.services.rds.model.LocalWriteForwardingStatus;
import software.amazon.awssdk.services.rds.model.ModifyDbClusterRequest;
import software.amazon.awssdk.services.rds.model.RebootDbInstanceRequest;
import software.amazon.awssdk.services.rds.model.RemoveFromGlobalClusterRequest;
Expand Down Expand Up @@ -68,6 +69,7 @@ static CreateDbClusterRequest createDbClusterRequest(
.enableGlobalWriteForwarding(model.getEnableGlobalWriteForwarding())
.enableHttpEndpoint(model.getEnableHttpEndpoint())
.enableIAMDatabaseAuthentication(model.getEnableIAMDatabaseAuthentication())
.enableLocalWriteForwarding(model.getEnableLocalWriteForwarding())
.enablePerformanceInsights(model.getPerformanceInsightsEnabled())
.engine(model.getEngine())
.engineMode(model.getEngineMode())
Expand Down Expand Up @@ -219,6 +221,7 @@ static ModifyDbClusterRequest modifyDbClusterAfterCreateRequest(final ResourceMo
.domain(desiredModel.getDomain())
.domainIAMRoleName(desiredModel.getDomainIAMRoleName())
.enableGlobalWriteForwarding(desiredModel.getEnableGlobalWriteForwarding())
.enableLocalWriteForwarding(desiredModel.getEnableLocalWriteForwarding())
.enablePerformanceInsights(desiredModel.getPerformanceInsightsEnabled())
.iops(desiredModel.getIops())
.masterUserPassword(desiredModel.getMasterUserPassword())
Expand Down Expand Up @@ -272,6 +275,7 @@ static ModifyDbClusterRequest modifyDbClusterRequest(
.domainIAMRoleName(desiredModel.getDomainIAMRoleName())
.enableGlobalWriteForwarding(desiredModel.getEnableGlobalWriteForwarding())
.enableIAMDatabaseAuthentication(diff(previousModel.getEnableIAMDatabaseAuthentication(), desiredModel.getEnableIAMDatabaseAuthentication()))
.enableLocalWriteForwarding(desiredModel.getEnableLocalWriteForwarding())
.enablePerformanceInsights(desiredModel.getPerformanceInsightsEnabled())
.iops(desiredModel.getIops())
.masterUserPassword(diff(previousModel.getMasterUserPassword(), desiredModel.getMasterUserPassword()))
Expand Down Expand Up @@ -432,6 +436,17 @@ static Set<Tag> translateTagsFromSdk(
.collect(Collectors.toSet());
}

static boolean translateLocalWriteForwardingStatus(final LocalWriteForwardingStatus status) {
/*
* LocalWriteForwarding reports status as an enum rather than a boolean.
* CFN stabilization requires a boolean value to stabilize.
* This method projects the status into ENABLED X DISABLED.
* Both ENABLING and DISABLING states are stabilized on and are considered transient.
*/
return status == LocalWriteForwardingStatus.REQUESTED ||
status == LocalWriteForwardingStatus.ENABLED;
}

static software.amazon.awssdk.services.rds.model.ServerlessV2ScalingConfiguration translateServerlessV2ScalingConfiguration(
final ServerlessV2ScalingConfiguration serverlessV2ScalingConfiguration
) {
Expand Down Expand Up @@ -542,6 +557,7 @@ public static ResourceModel translateDbClusterFromSdk(
.enableGlobalWriteForwarding(dbCluster.globalWriteForwardingRequested())
.enableHttpEndpoint(dbCluster.httpEndpointEnabled())
.enableIAMDatabaseAuthentication(dbCluster.iamDatabaseAuthenticationEnabled())
.enableLocalWriteForwarding(translateLocalWriteForwardingStatus(dbCluster.localWriteForwardingStatus()))
.endpoint(
Endpoint.builder()
.address(dbCluster.endpoint())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.rds.RdsClient;
import software.amazon.awssdk.services.rds.model.DBCluster;
import software.amazon.awssdk.services.rds.model.LocalWriteForwardingStatus;
import software.amazon.awssdk.services.rds.model.MasterUserSecret;
import software.amazon.awssdk.services.rds.model.WriteForwardingStatus;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
Expand Down Expand Up @@ -135,6 +136,62 @@ void isGlobalWriteForwardingStabilized_globalWriteForwardingDisabling() {
)).isFalse();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingNotRequested() {
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.build()
)).isTrue();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingRequested() {
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.REQUESTED)
.build()
)).isTrue();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingEnabled() {
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.ENABLED)
.build()
)).isTrue();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingEnabling() {
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.ENABLING)
.build()
)).isFalse();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingDisabled() {
// LocalWriteForwarding status will not enable until a reader is requested by customer
// This prevents customers from creating a stack with only primary and setting the property
// As WS does not validate this parameter the stack will wait on stabilization until timeout.
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.DISABLED)
.build()
)).isTrue();
}

@Test
void isLocalWriteForwardingStabilized_localWriteForwardingDisabling() {
Assertions.assertThat(BaseHandlerStd.isLocalWriteForwardingStabilized(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.DISABLING)
.build()
)).isFalse();
}


@Test
void validateRequest_BlankRegionIsAccepted() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import software.amazon.awssdk.services.rds.model.CreateDbClusterRequest;
import software.amazon.awssdk.services.rds.model.DBCluster;
import software.amazon.awssdk.services.rds.model.DomainMembership;
import software.amazon.awssdk.services.rds.model.LocalWriteForwardingStatus;
import software.amazon.awssdk.services.rds.model.ModifyDbClusterRequest;
import software.amazon.awssdk.services.rds.model.RestoreDbClusterFromSnapshotRequest;
import software.amazon.awssdk.services.rds.model.RestoreDbClusterToPointInTimeRequest;
Expand All @@ -39,6 +40,14 @@ public void createDbClusterRequest_enableGlobalWriteForwarding() {
assertThat(request.enableGlobalWriteForwarding()).isEqualTo(Boolean.TRUE);
}

@Test
public void createDbClusterRequest_enableLocalWriteForwarding() {
final ResourceModel model = RESOURCE_MODEL.toBuilder().enableLocalWriteForwarding(true).build();

final CreateDbClusterRequest request = Translator.createDbClusterRequest(model, Tagging.TagSet.emptySet());
assertThat(request.enableLocalWriteForwarding()).isEqualTo(Boolean.TRUE);
}

@Test
public void createDbClusterRequest_storageTypeAndIops_shouldBeSet() {
final ResourceModel model = ResourceModel.builder()
Expand Down Expand Up @@ -567,6 +576,36 @@ public void translateDbCluterFromSdk_useProvidedStorageType() {
assertThat(model.getStorageType()).isEqualTo(STORAGE_TYPE_AURORA_IOPT1);
}

@Test
public void translateDbCluterFromSdk_translateLocalWriteForwardingStatusRequested() {
final ResourceModel model = Translator.translateDbClusterFromSdk(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.REQUESTED)
.build()
);
assertThat(model.getEnableLocalWriteForwarding()).isTrue();
}

@Test
public void translateDbCluterFromSdk_translateLocalWriteForwardingStatusEnabling() {
final ResourceModel model = Translator.translateDbClusterFromSdk(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.ENABLING)
.build()
);
assertThat(model.getEnableLocalWriteForwarding()).isFalse();
}

@Test
public void translateDbCluterFromSdk_translateLocalWriteForwardingStatusDisabling() {
final ResourceModel model = Translator.translateDbClusterFromSdk(
DBCluster.builder()
.localWriteForwardingStatus(LocalWriteForwardingStatus.DISABLING)
.build()
);
assertThat(model.getEnableLocalWriteForwarding()).isFalse();
}

@Override
protected BaseHandlerStd getHandler() {
return null;
Expand Down
Loading