Skip to content
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
2 changes: 1 addition & 1 deletion aws-rds-cfn-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
<minimum>0.7</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package software.amazon.rds.common.util;

import software.amazon.cloudformation.proxy.ProgressEvent;

public class WaiterHelper {
/**
* A function which introduces artificial delay during any ProgressEvent, usually used in Aurora codepaths, for example
* there might be asynchronous workflows running to update resources after the resource has become available and which
* we don't have a valid status to stabilise on
*
* This function will wait for callbackDelay, return the progress event and allow the handler
* to be reinvoke itself and keep retrying until the maxTimeSeconds is breached
*
* @param evt ProgressEvent of the handler
* @param maxSeconds The max total time we will wait
* @param pollSeconds Wait time before the next invocation of the handler, until we reach maxSeconds
* @return ProgressEvent
* @param <ResourceT> The generic resource model
* @param <CallbackT> The generic callback
*/
public static <ResourceT, CallbackT extends DelayContext> ProgressEvent<ResourceT, CallbackT> delay(final ProgressEvent<ResourceT, CallbackT> evt, final int maxSeconds, final int pollSeconds) {
final CallbackT callbackContext = evt.getCallbackContext();
if (callbackContext.getWaitTime() <= maxSeconds) {
callbackContext.setWaitTime(callbackContext.getWaitTime() + pollSeconds);
return ProgressEvent.defaultInProgressHandler(callbackContext, pollSeconds, evt.getResourceModel());
} else {
return ProgressEvent.progress(evt.getResourceModel(), callbackContext);
}
}

public interface DelayContext {
int getWaitTime();
void setWaitTime(int waitTime);
}
}
5 changes: 5 additions & 0 deletions aws-rds-dbcluster/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.amazon.cloudformation</groupId>
<artifactId>aws-cloudformation-rpdk-java-plugin</artifactId>
<version>[2.0.0,3.0.0)</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
import software.amazon.rds.common.handler.TaggingContext;
import software.amazon.rds.common.handler.TimestampContext;
import software.amazon.rds.common.util.IdempotencyHelper;
import software.amazon.rds.common.util.WaiterHelper;

@lombok.Getter
@lombok.Setter
@lombok.ToString
@lombok.EqualsAndHashCode(callSuper = true)
public class CallbackContext extends StdCallbackContext implements TaggingContext.Provider, ProbingContext.Provider, TimestampContext.Provider, IdempotencyHelper.PreExistenceContext {
public class CallbackContext extends StdCallbackContext implements TaggingContext.Provider, ProbingContext.Provider, TimestampContext.Provider, IdempotencyHelper.PreExistenceContext, WaiterHelper.DelayContext {
private Boolean preExistenceCheckDone;
private boolean modified;
private boolean rebooted;
Expand All @@ -29,12 +30,17 @@ public class CallbackContext extends StdCallbackContext implements TaggingContex
private TaggingContext taggingContext;
private ProbingContext probingContext;

// wait time is used for delaying in Aurora Serverless V2 due to async workflows modifying properties
// which may occur after the DBCluster is available
private int waitTime;

public CallbackContext() {
super();
this.taggingContext = new TaggingContext();
this.probingContext = new ProbingContext();
this.timestamps = new HashMap<>();
this.timeDelta = new HashMap<>();
this.waitTime = 0;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
import software.amazon.rds.common.handler.Probing;
import software.amazon.rds.common.handler.Tagging;
import software.amazon.rds.common.request.ValidatedRequest;
import software.amazon.rds.common.util.WaiterHelper;
import software.amazon.rds.dbcluster.util.ImmutabilityHelper;
import software.amazon.rds.dbcluster.util.ResourceModelHelper;

public class UpdateHandler extends BaseHandlerStd {
static final int AURORA_SERVERLESS_V2_MAX_WAIT_SECONDS = 300;
static final int AURORA_SERVERLESS_V2_POLL_SECONDS = 10;

public UpdateHandler() {
this(DB_CLUSTER_HANDLER_CONFIG_36H);
Expand Down Expand Up @@ -124,7 +128,16 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
requestLogger
))
.then(progress -> updateTags(proxy, rdsProxyClient, progress, previousTags, desiredTags))
.then(progress -> {
.then(progress -> {
// Required delay for Aurora Serverless V2, because when scaling capacity, it kicks off an async
// workflow which could occur after the DBCluster has completed CFN update
// This delay attempts to force the async workflow to complete before the DBCluster update returns
if (ResourceModelHelper.hasServerlessV2ScalingConfigurationChanged(previousResourceState, desiredResourceState)) {
return WaiterHelper.delay(progress, AURORA_SERVERLESS_V2_MAX_WAIT_SECONDS, AURORA_SERVERLESS_V2_POLL_SECONDS);
}
return progress;
})
.then(progress -> {
desiredResourceState.setTags(Translator.translateTagsFromSdk(Tagging.translateTagsToSdk(desiredTags)));
return Commons.reportResourceDrift(
desiredResourceState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import software.amazon.rds.dbcluster.EngineMode;
import software.amazon.rds.dbcluster.ResourceModel;

import static software.amazon.rds.common.util.DifferenceUtils.diff;

public class ResourceModelHelper {

public static boolean isRestoreToPointInTime(final ResourceModel model) {
Expand All @@ -22,4 +24,8 @@ public static boolean shouldUpdateAfterCreate(final ResourceModel model) {
public static boolean shouldEnableHttpEndpointV2AfterCreate(final ResourceModel model) {
return BooleanUtils.isTrue(model.getEnableHttpEndpoint()) && !EngineMode.Serverless.equals(EngineMode.fromString(model.getEngineMode()));
}

public static boolean hasServerlessV2ScalingConfigurationChanged(final ResourceModel previousModel, final ResourceModel desiredModel) {
return diff(previousModel.getServerlessV2ScalingConfiguration(), desiredModel.getServerlessV2ScalingConfiguration()) != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,11 @@ void handleRequest_ServerlessV2ScalingConfiguration_Success(
.secondsUntilAutoPause(secondsUntilAutoPauseAfter)
.build();

final CallbackContext callbackContext = new CallbackContext();
callbackContext.setWaitTime(301); // force a large wait time so we don't have to wait during tests

test_handleRequest_base(
new CallbackContext(),
callbackContext,
ResourceHandlerRequest.<ResourceModel>builder()
.previousResourceTags(Translator.translateTagsToRequest(TAG_LIST))
.desiredResourceTags(Translator.translateTagsToRequest(TAG_LIST_ALTER)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import software.amazon.cloudformation.proxy.StdCallbackContext;
import software.amazon.rds.common.handler.TaggingContext;
import software.amazon.rds.common.util.IdempotencyHelper;
import software.amazon.rds.common.util.WaiterHelper;


@lombok.Getter
@lombok.Setter
@lombok.ToString
@lombok.EqualsAndHashCode(callSuper = true)
public class CallbackContext extends StdCallbackContext implements TaggingContext.Provider, IdempotencyHelper.PreExistenceContext {
public class CallbackContext extends StdCallbackContext implements TaggingContext.Provider, IdempotencyHelper.PreExistenceContext, WaiterHelper.DelayContext {
private Boolean preExistenceCheckDone;
private boolean described;
private boolean updated;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import software.amazon.rds.common.handler.Commons;
import software.amazon.rds.common.handler.HandlerConfig;
import software.amazon.rds.common.handler.Tagging;
import software.amazon.rds.common.util.WaiterHelper;

import java.util.HashSet;

Expand Down Expand Up @@ -58,7 +59,7 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(final Amaz
.progress(), CallbackContext::isUpdated, CallbackContext::setUpdated))
.then(progress -> Commons.execOnce(progress, () -> updateTags(proxyClient, request, progress, previousTags, desiredTags), CallbackContext::isAddTagsComplete, CallbackContext::setAddTagsComplete))
// There is a lag between the modifyDbShardGroup request call and the shard group state moving to "modifying", so we introduce a fixed delay prior to stabilization
.then((progress) -> delay(progress, POST_MODIFY_DELAY_SEC))
.then((progress) -> WaiterHelper.delay(progress, POST_MODIFY_DELAY_SEC, CALLBACK_DELAY))
.then(progress -> proxy.initiate("rds::update-db-shard-group-stabilize", proxyClient, request.getDesiredResourceState(), callbackContext)
.translateToServiceRequest(Function.identity())
.backoffDelay(config.getBackoff())
Expand All @@ -73,15 +74,4 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(final Amaz
.progress())
.then(progress -> new ReadHandler().handleRequest(proxy, proxyClient, request, callbackContext));
}

/** Inserts an artificial delay */
private ProgressEvent<ResourceModel, CallbackContext> delay(final ProgressEvent<ResourceModel, CallbackContext> evt, final int seconds) {
CallbackContext callbackContext = evt.getCallbackContext();
if (callbackContext.getWaitTime() <= seconds) {
callbackContext.setWaitTime(callbackContext.getWaitTime() + CALLBACK_DELAY);
return ProgressEvent.defaultInProgressHandler(callbackContext, CALLBACK_DELAY, evt.getResourceModel());
} else {
return ProgressEvent.progress(evt.getResourceModel(), callbackContext);
}
}
}
Loading