diff --git a/aws-rds-dbcluster/aws-rds-dbcluster.json b/aws-rds-dbcluster/aws-rds-dbcluster.json index ec68e95da..fee73ca7d 100644 --- a/aws-rds-dbcluster/aws-rds-dbcluster.json +++ b/aws-rds-dbcluster/aws-rds-dbcluster.json @@ -116,6 +116,10 @@ "type": "string" } }, + "EnableGlobalWriteForwarding": { + "description": "Specifies whether to enable this DB cluster to forward write operations to the primary cluster of a global cluster (Aurora global database). By default, write operations are not allowed on Aurora DB clusters that are secondary clusters in an Aurora global database.", + "type": "boolean" + }, "EnableHttpEndpoint": { "description": "A value that indicates whether to enable the HTTP endpoint for an Aurora Serverless DB cluster. By default, the HTTP endpoint is disabled.", "type": "boolean" diff --git a/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/BaseHandlerStd.java b/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/BaseHandlerStd.java index 8f23fd03b..286d94d04 100644 --- a/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/BaseHandlerStd.java +++ b/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/BaseHandlerStd.java @@ -14,6 +14,7 @@ import java.util.function.Predicate; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -56,6 +57,7 @@ import software.amazon.awssdk.services.rds.model.StorageTypeNotAvailableException; import software.amazon.awssdk.services.rds.model.StorageTypeNotSupportedException; import software.amazon.awssdk.services.rds.model.Tag; +import software.amazon.awssdk.services.rds.model.WriteForwardingStatus; import software.amazon.awssdk.utils.StringUtils; import software.amazon.cloudformation.exceptions.CfnNotStabilizedException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; @@ -305,10 +307,11 @@ protected boolean isDBClusterStabilized( return isDBClusterAvailable(dbCluster) && isNoPendingChanges(dbCluster) && - isMasterUserSecretStabilized(dbCluster); + isMasterUserSecretStabilized(dbCluster) && + isGlobalWriteForwardingStabilized(dbCluster); } - private static boolean isMasterUserSecretStabilized(DBCluster dbCluster) { + protected static boolean isMasterUserSecretStabilized(DBCluster dbCluster) { if (dbCluster.masterUserSecret() == null || CollectionUtils.isEmpty(dbCluster.dbClusterMembers())) { return true; @@ -316,6 +319,13 @@ private static boolean isMasterUserSecretStabilized(DBCluster dbCluster) { return MASTER_USER_SECRET_ACTIVE.equalsIgnoreCase(dbCluster.masterUserSecret().secretStatus()); } + protected static boolean isGlobalWriteForwardingStabilized(DBCluster dbCluster) { + return BooleanUtils.isNotTrue(dbCluster.globalWriteForwardingRequested()) || + // Even if GWF is requested the WF will not start until a replica is created by customers + (dbCluster.globalWriteForwardingStatus() != WriteForwardingStatus.ENABLING && + dbCluster.globalWriteForwardingStatus() != WriteForwardingStatus.DISABLING); + } + protected boolean isClusterRemovedFromGlobalCluster( final ProxyClient proxyClient, final String previousGlobalClusterIdentifier, diff --git a/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/Translator.java b/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/Translator.java index 4b870e048..c3bfe47d3 100644 --- a/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/Translator.java +++ b/aws-rds-dbcluster/src/main/java/software/amazon/rds/dbcluster/Translator.java @@ -63,6 +63,7 @@ static CreateDbClusterRequest createDbClusterRequest( .domain(model.getDomain()) .domainIAMRoleName(model.getDomainIAMRoleName()) .enableCloudwatchLogsExports(model.getEnableCloudwatchLogsExports()) + .enableGlobalWriteForwarding(model.getEnableGlobalWriteForwarding()) .enableHttpEndpoint(model.getEnableHttpEndpoint()) .enableIAMDatabaseAuthentication(model.getEnableIAMDatabaseAuthentication()) .enablePerformanceInsights(model.getPerformanceInsightsEnabled()) @@ -212,6 +213,7 @@ static ModifyDbClusterRequest modifyDbClusterAfterCreateRequest(final ResourceMo .deletionProtection(desiredModel.getDeletionProtection()) .domain(desiredModel.getDomain()) .domainIAMRoleName(desiredModel.getDomainIAMRoleName()) + .enableGlobalWriteForwarding(desiredModel.getEnableGlobalWriteForwarding()) .enableHttpEndpoint(desiredModel.getEnableHttpEndpoint()) .enablePerformanceInsights(desiredModel.getPerformanceInsightsEnabled()) .iops(desiredModel.getIops()) @@ -262,6 +264,7 @@ static ModifyDbClusterRequest modifyDbClusterRequest( .deletionProtection(desiredModel.getDeletionProtection()) .domain(desiredModel.getDomain()) .domainIAMRoleName(desiredModel.getDomainIAMRoleName()) + .enableGlobalWriteForwarding(desiredModel.getEnableGlobalWriteForwarding()) .enableHttpEndpoint(desiredModel.getEnableHttpEndpoint()) .enableIAMDatabaseAuthentication(diff(previousModel.getEnableIAMDatabaseAuthentication(), desiredModel.getEnableIAMDatabaseAuthentication())) .enablePerformanceInsights(desiredModel.getPerformanceInsightsEnabled()) @@ -506,6 +509,7 @@ public static ResourceModel translateDbClusterFromSdk( .domain(domain) .domainIAMRoleName(domainIAMRoleName) .enableCloudwatchLogsExports(dbCluster.enabledCloudwatchLogsExports()) + .enableGlobalWriteForwarding(dbCluster.globalWriteForwardingRequested()) .enableHttpEndpoint(dbCluster.httpEndpointEnabled()) .enableIAMDatabaseAuthentication(dbCluster.iamDatabaseAuthenticationEnabled()) .endpoint( diff --git a/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/BaseHandlerStdTest.java b/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/BaseHandlerStdTest.java new file mode 100644 index 000000000..fc7c281f1 --- /dev/null +++ b/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/BaseHandlerStdTest.java @@ -0,0 +1,166 @@ +package software.amazon.rds.dbcluster; + +import java.time.Instant; +import java.util.Collections; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.apache.commons.collections.CollectionUtils; + +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.MasterUserSecret; +import software.amazon.awssdk.services.rds.model.WriteForwardingStatus; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.rds.common.handler.HandlerConfig; +import software.amazon.rds.common.logging.RequestLogger; +import software.amazon.rds.common.request.RequestValidationException; +import software.amazon.rds.common.request.ValidatedRequest; + +class BaseHandlerStdTest { + + static class TestBaseHandlerStd extends BaseHandlerStd { + + public TestBaseHandlerStd(HandlerConfig config) { + super(config); + } + + @Override + protected ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ValidatedRequest request, + final CallbackContext callbackContext, + final ProxyClient rdsProxyClient, + final ProxyClient ec2ProxyClient, + final RequestLogger logger + ) { + return null; + } + } + + private TestBaseHandlerStd handler; + + @BeforeEach + public void setUp() { + handler = new TestBaseHandlerStd(null); + } + + @Test + void isMasterUserSecretStabilized_masterUserSecretIsNull() { + Assertions.assertThat(handler.isMasterUserSecretStabilized( + DBCluster.builder() + .build() + )).isTrue(); + } + + @Test + void isMasterUserSecretStabilized_masterUserSecretStatusActive() { + Assertions.assertThat(handler.isMasterUserSecretStabilized( + DBCluster.builder() + .masterUserSecret(MasterUserSecret.builder() + .secretStatus("Active") + .build()) + .build() + )).isTrue(); + } + + @Test + void isMasterUserSecretStabilized_masterUserSecretStatusCreatingWithEmptyMembers() { + DBCluster dbCluster = DBCluster.builder() + .masterUserSecret(MasterUserSecret.builder() + .secretStatus("Creating") + .build()) + .build(); + Assertions.assertThat(CollectionUtils.isEmpty(dbCluster.dbClusterMembers())); + Assertions.assertThat(handler.isMasterUserSecretStabilized(dbCluster)).isTrue(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingNotRequested() { + Assertions.assertThat(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .build() + )).isTrue(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingRequested() { + Assertions.assertThat(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .globalWriteForwardingRequested(true) + .build() + )).isTrue(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingEnabled() { + Assertions.assertThat(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .globalWriteForwardingRequested(true) + .globalWriteForwardingStatus(WriteForwardingStatus.ENABLED) + .build() + )).isTrue(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingEnabling() { + Assertions.assertThat(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .globalWriteForwardingRequested(true) + .globalWriteForwardingStatus(WriteForwardingStatus.ENABLING) + .build() + )).isFalse(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingDisabled() { + // GlobalWriteForwarding status will not enable until a replica 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(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .globalWriteForwardingRequested(true) + .globalWriteForwardingStatus(WriteForwardingStatus.DISABLED) + .build() + )).isTrue(); + } + + @Test + void isGlobalWriteForwardingStabilized_globalWriteForwardingDisabling() { + Assertions.assertThat(handler.isGlobalWriteForwardingStabilized( + DBCluster.builder() + .globalWriteForwardingRequested(true) + .globalWriteForwardingStatus(WriteForwardingStatus.DISABLING) + .build() + )).isFalse(); + } + + + @Test + void validateRequest_BlankRegionIsAccepted() { + final ResourceHandlerRequest request = new ResourceHandlerRequest<>(); + request.setDesiredResourceState(ResourceModel.builder() + .sourceRegion("") + .build()); + Assertions.assertThatCode(() -> { + handler.validateRequest(request); + }).doesNotThrowAnyException(); + } + + @Test + void validateRequest_UnknownRegionIsRejected() { + final ResourceHandlerRequest request = new ResourceHandlerRequest<>(); + request.setDesiredResourceState(ResourceModel.builder() + .sourceRegion("foo-bar-baz") + .build()); + Assertions.assertThatExceptionOfType(RequestValidationException.class).isThrownBy(() -> { + handler.validateRequest(request); + }); + } +} diff --git a/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/TranslatorTest.java b/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/TranslatorTest.java index 0ec937b65..e183ca790 100644 --- a/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/TranslatorTest.java +++ b/aws-rds-dbcluster/src/test/java/software/amazon/rds/dbcluster/TranslatorTest.java @@ -11,6 +11,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.CreateDbClusterRequest; import software.amazon.awssdk.services.rds.model.DomainMembership; import software.amazon.awssdk.services.rds.model.ModifyDbClusterRequest; import software.amazon.awssdk.services.rds.model.RestoreDbClusterFromSnapshotRequest; @@ -27,6 +28,14 @@ public class TranslatorTest extends AbstractHandlerTest { private final static String STORAGE_TYPE_AURORA_IOPT1 = "aurora-opt1"; + @Test + public void createDbClusterRequest_enableGlobalWriteForwarding() { + final ResourceModel model = RESOURCE_MODEL.toBuilder().enableGlobalWriteForwarding(true).build(); + + final CreateDbClusterRequest request = Translator.createDbClusterRequest(model, Tagging.TagSet.emptySet()); + assertThat(request.enableGlobalWriteForwarding()).isEqualTo(Boolean.TRUE); + } + @Test public void modifyDbClusterRequest_omitPreferredMaintenanceWindowIfUnchanged() { final ResourceModel model = RESOURCE_MODEL.toBuilder().preferredMaintenanceWindow("old").build(); @@ -85,6 +94,16 @@ public void modifyDbClusterRequest_setEnableIAMDatabaseAuthentication() { assertThat(request.enableIAMDatabaseAuthentication()).isEqualTo(Boolean.TRUE); } + @Test + public void modifyDbClusterRequest_setEnableGlobalWriteForwarding() { + final ResourceModel previousModel = RESOURCE_MODEL.toBuilder().enableGlobalWriteForwarding(false).build(); + final ResourceModel desiredModel = RESOURCE_MODEL.toBuilder().enableGlobalWriteForwarding(true).build(); + final Boolean isRollback = false; + + final ModifyDbClusterRequest request = Translator.modifyDbClusterRequest(previousModel, desiredModel, isRollback); + assertThat(request.enableGlobalWriteForwarding()).isEqualTo(Boolean.TRUE); + } + @Test public void ModifyDbClusterRequest_dbInstanceParameterGroupNameIsNotSetWhenEngineVersionIsNotUpgrading() { final ResourceModel previousModel = RESOURCE_MODEL.toBuilder().engineVersion("old-engine").dBInstanceParameterGroupName("old-pg").build(); diff --git a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/BaseHandlerStd.java b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/BaseHandlerStd.java index 6afd4c95f..8448d3d9a 100644 --- a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/BaseHandlerStd.java +++ b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/BaseHandlerStd.java @@ -1105,7 +1105,7 @@ protected ProgressEvent stopAutomaticBackupRepli final ProxyClient sourceRegionClient, final String region ) { - final ProxyClient rdsClient = proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(region)); + final ProxyClient rdsClient = new LoggingProxyClient<>(logger, proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(region))); return proxy.initiate("rds::stop-db-instance-automatic-backup-replication", rdsClient, progress.getResourceModel(), progress.getCallbackContext()) .translateToServiceRequest(resourceModel -> Translator.stopDbInstanceAutomatedBackupsReplicationRequest(dbInstanceArn)) @@ -1131,7 +1131,7 @@ protected ProgressEvent startAutomaticBackupRepl final ProxyClient sourceRegionClient, final String region ) { - final ProxyClient rdsClient = proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(region)); + final ProxyClient rdsClient = new LoggingProxyClient<>(logger, proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(region))); return proxy.initiate("rds::start-db-instance-automatic-backup-replication", rdsClient, progress.getResourceModel(), progress.getCallbackContext()) .translateToServiceRequest(resourceModel -> Translator.startDbInstanceAutomatedBackupsReplicationRequest(dbInstanceArn)) diff --git a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CallbackContext.java b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CallbackContext.java index c7f57a094..997218fd9 100644 --- a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CallbackContext.java +++ b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CallbackContext.java @@ -24,6 +24,7 @@ public class CallbackContext extends StdCallbackContext implements TaggingContex private boolean automaticBackupReplicationStopped; private boolean automaticBackupReplicationStarted; private String dbInstanceArn; + private String currentRegion; private TaggingContext taggingContext; private Map timestamps; diff --git a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CreateHandler.java b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CreateHandler.java index 0500d8b2b..ffb6a0ce6 100644 --- a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CreateHandler.java +++ b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/CreateHandler.java @@ -23,11 +23,13 @@ import software.amazon.rds.common.handler.HandlerConfig; import software.amazon.rds.common.handler.HandlerMethod; import software.amazon.rds.common.handler.Tagging; +import software.amazon.rds.common.logging.LoggingProxyClient; import software.amazon.rds.common.request.RequestValidationException; import software.amazon.rds.common.request.ValidatedRequest; import software.amazon.rds.common.request.Validations; import software.amazon.rds.common.util.IdentifierFactory; import software.amazon.rds.dbinstance.client.ApiVersion; +import software.amazon.rds.dbinstance.client.RdsClientProvider; import software.amazon.rds.dbinstance.client.VersionedProxyClient; import software.amazon.rds.dbinstance.util.ResourceModelHelper; @@ -70,6 +72,7 @@ protected ProgressEvent handleRequest( final ResourceModel model = request.getDesiredResourceState(); final Collection desiredRoles = model.getAssociatedRoles(); final boolean isMultiAZ = BooleanUtils.isTrue(model.getMultiAZ()); + callbackContext.setCurrentRegion(request.getRegion()); if (StringUtils.isNullOrEmpty(model.getDBInstanceIdentifier())) { model.setDBInstanceIdentifier(instanceIdentifierFactory.newIdentifier() @@ -89,7 +92,7 @@ protected ProgressEvent handleRequest( .then(progress -> { if (StringUtils.isNullOrEmpty(progress.getResourceModel().getEngine())) { try { - model.setEngine(fetchEngine(rdsProxyClient.defaultClient(), progress.getResourceModel())); + model.setEngine(fetchEngine(rdsProxyClient.defaultClient(), progress, proxy)); } catch (Exception e) { return Commons.handleException(progress, e, DB_INSTANCE_FETCH_ENGINE_RULE_SET); } @@ -201,7 +204,12 @@ private HandlerMethod safeAddTags(final HandlerM return (proxy, rdsProxyClient, progress, tagSet) -> progress.then(p -> Tagging.safeCreate(proxy, rdsProxyClient, handlerMethod, progress, tagSet)); } - private String fetchEngine(final ProxyClient client, final ResourceModel model) { + private String fetchEngine(final ProxyClient client, + final ProgressEvent progress, + final AmazonWebServicesClientProxy proxy) { + final ResourceModel model = progress.getResourceModel(); + final String currentRegion = progress.getCallbackContext().getCurrentRegion(); + if (ResourceModelHelper.isRestoreFromSnapshot(model)) { return fetchDBSnapshot(client, model).engine(); } @@ -210,10 +218,29 @@ private String fetchEngine(final ProxyClient client, final ResourceMo } if (ResourceModelHelper.isDBInstanceReadReplica(model)) { - return fetchDBInstance(client, model.getSourceDBInstanceIdentifier()).engine(); + final String sourceDBInstanceArn = model.getSourceDBInstanceIdentifier(); + final String sourceDBInstanceIdOrArn = ResourceModelHelper.isValidArn(sourceDBInstanceArn) ? + ResourceModelHelper.getResourceNameFromArn(sourceDBInstanceArn) : sourceDBInstanceArn; + if (ResourceModelHelper.isCrossRegionDBInstanceReadReplica(model, currentRegion)) { + final String sourceRegion = ResourceModelHelper.getRegionFromArn(sourceDBInstanceArn); + final ProxyClient sourceRegionClient = new LoggingProxyClient<>(logger, + proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(sourceRegion))); + return fetchDBInstance(sourceRegionClient, sourceDBInstanceIdOrArn).engine(); + } else { + return fetchDBInstance(client, sourceDBInstanceIdOrArn ).engine(); + } } if (ResourceModelHelper.isDBClusterReadReplica(model)) { - return fetchDBCluster(client, model.getSourceDBClusterIdentifier()).engine(); + final String sourceDBClusterArn = model.getSourceDBClusterIdentifier(); + final String sourceDBClusterIdOrArn = ResourceModelHelper.isValidArn(sourceDBClusterArn) ? + ResourceModelHelper.getResourceNameFromArn(sourceDBClusterArn) : sourceDBClusterArn; + if (ResourceModelHelper.isCrossRegionDBClusterReadReplica(model, currentRegion)) { + final String sourceRegion = ResourceModelHelper.getRegionFromArn(sourceDBClusterArn); + final ProxyClient sourceRegionClient = proxy.newProxy(() -> new RdsClientProvider().getClientForRegion(sourceRegion)); + return fetchDBCluster(sourceRegionClient, sourceDBClusterIdOrArn).engine(); + } else { + return fetchDBCluster(client, sourceDBClusterIdOrArn).engine(); + } } if (ResourceModelHelper.isRestoreToPointInTime(model)) { @@ -378,12 +405,13 @@ private ProgressEvent createDbInstanceReadReplic final ProgressEvent progress, final Tagging.TagSet tagSet ) { + final String currentRegion = progress.getCallbackContext().getCurrentRegion(); return proxy.initiate( "rds::create-db-instance-read-replica", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() - ).translateToServiceRequest(model -> Translator.createDbInstanceReadReplicaRequest(model, tagSet)) + ).translateToServiceRequest(model -> Translator.createDbInstanceReadReplicaRequest(model, tagSet, currentRegion)) .backoffDelay(config.getBackoff()) .makeServiceCall((createRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( createRequest, diff --git a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/Translator.java b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/Translator.java index 8f55acfad..bd94c0462 100644 --- a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/Translator.java +++ b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/Translator.java @@ -16,11 +16,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.amazonaws.arn.Arn; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; +import com.amazonaws.arn.Arn; import com.google.common.annotations.VisibleForTesting; import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest; import software.amazon.awssdk.services.ec2.model.Filter; @@ -108,7 +108,8 @@ public static DescribeDbClusterSnapshotsRequest describeDbClusterSnapshotsReques public static CreateDbInstanceReadReplicaRequest createDbInstanceReadReplicaRequest( final ResourceModel model, - final Tagging.TagSet tagSet + final Tagging.TagSet tagSet, + final String currentRegion ) { final CreateDbInstanceReadReplicaRequest.Builder builder = CreateDbInstanceReadReplicaRequest.builder() .autoMinorVersionUpgrade(model.getAutoMinorVersionUpgrade()) @@ -152,6 +153,11 @@ public static CreateDbInstanceReadReplicaRequest createDbInstanceReadReplicaRequ builder.storageThroughput(model.getStorageThroughput()); builder.storageType(model.getStorageType()); } + + if (ResourceModelHelper.isCrossRegionDBInstanceReadReplica(model, currentRegion) && ResourceModelHelper.isMySQL(model) ) { + builder.dbParameterGroupName(model.getDBParameterGroupName()); + } + return builder.build(); } diff --git a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/util/ResourceModelHelper.java b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/util/ResourceModelHelper.java index 666577b18..d2af76f26 100644 --- a/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/util/ResourceModelHelper.java +++ b/aws-rds-dbinstance/src/main/java/software/amazon/rds/dbinstance/util/ResourceModelHelper.java @@ -1,23 +1,24 @@ package software.amazon.rds.dbinstance.util; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.lang3.BooleanUtils; + +import com.amazonaws.arn.Arn; import com.amazonaws.util.StringUtils; import com.google.common.collect.ImmutableSet; -import org.apache.commons.lang3.BooleanUtils; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.rds.dbinstance.ResourceModel; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - public final class ResourceModelHelper { private static final Set SQLSERVER_ENGINES_WITH_MIRRORING = ImmutableSet.of( "sqlserver-ee", "sqlserver-se" ); private static final String SQLSERVER_ENGINE = "sqlserver"; + private static final String MYSQL_ENGINE_PREFIX = "mysql"; + private static final String ORACLE_ENGINE_PREFIX = "oracle"; public static boolean shouldUpdateAfterCreate(final ResourceModel model) { return (isReadReplica(model) || @@ -48,6 +49,10 @@ public static boolean isSqlServer(final ResourceModel model) { return engine == null || engine.contains(SQLSERVER_ENGINE); } + public static boolean isMySQL(final ResourceModel model) { + final String engine = model.getEngine(); + return engine != null && engine.toLowerCase().startsWith(MYSQL_ENGINE_PREFIX); + } public static boolean isStorageParametersModified(final ResourceModel model) { return StringUtils.hasValue(model.getAllocatedStorage()) || @@ -72,10 +77,40 @@ public static boolean isDBInstanceReadReplica(final ResourceModel model) { return StringUtils.hasValue(model.getSourceDBInstanceIdentifier()); } + public static boolean isCrossRegionDBInstanceReadReplica(final ResourceModel model, final String currentRegion) { + final String sourceDBInstanceIdentifier = model.getSourceDBInstanceIdentifier(); + return isDBInstanceReadReplica(model) && + isValidArn(sourceDBInstanceIdentifier) && + !getRegionFromArn(sourceDBInstanceIdentifier).equals(currentRegion); + } public static boolean isDBClusterReadReplica(final ResourceModel model) { return StringUtils.hasValue(model.getSourceDBClusterIdentifier()); } + public static boolean isCrossRegionDBClusterReadReplica(final ResourceModel model, final String currentRegion) { + final String sourceDBClusterIdentifier = model.getSourceDBClusterIdentifier(); + return isDBClusterReadReplica(model) && + isValidArn(sourceDBClusterIdentifier) && + !getRegionFromArn(sourceDBClusterIdentifier).equals(currentRegion); + } + + public static boolean isValidArn(final String arn) { + try { + Arn.fromString(arn); + return true; + } catch (IllegalArgumentException e) { + return false; + } + + } + public static String getRegionFromArn(final String arn) { + return Arn.fromString(arn).getRegion(); + } + + public static String getResourceNameFromArn(final String arn) { + return Arn.fromString(arn).getResource().getResource(); + } + public static boolean isRestoreFromSnapshot(final ResourceModel model) { return StringUtils.hasValue(model.getDBSnapshotIdentifier()); } diff --git a/aws-rds-dbinstance/src/test/java/software/amazon/rds/dbinstance/AbstractHandlerTest.java b/aws-rds-dbinstance/src/test/java/software/amazon/rds/dbinstance/AbstractHandlerTest.java index bf715f8e9..cc312a918 100644 --- a/aws-rds-dbinstance/src/test/java/software/amazon/rds/dbinstance/AbstractHandlerTest.java +++ b/aws-rds-dbinstance/src/test/java/software/amazon/rds/dbinstance/AbstractHandlerTest.java @@ -184,6 +184,7 @@ public abstract class AbstractHandlerTest extends AbstractTestBase